MCPcopy Create free account
hub / github.com/PierreGode/Ragnar / rusense_sensitivity

Function rusense_sensitivity

webapp_modern.py:1459–1527  ·  view source on GitHub ↗

Get/set the model-free detection sensitivity: the presence floor and the debounce depth the sensing engine uses. These are the RUVIEW_PRESENCE_FLOOR / RUVIEW_DEBOUNCE_FRAMES env vars; POST writes them to a systemd drop-in (ragnar-sensing.service.d/sensitivity.conf) so they survive a RuSe

()

Source from the content-addressed store, hash-verified

1457
1458 try:
1459 min_conf = float(shared_data.config.get('rusense_notify_min_confidence', 0.95))
1460 except (TypeError, ValueError):
1461 min_conf = 0.95
1462 # Confidence only means something with a trained model. Model-less it is
1463 # uncalibrated (~0.5), so a high bar would silently drop real presence —
1464 # relax the gate to 0 and lean on the motion-corroborated presence rule +
1465 # sustain debounce instead. Never miss presence for lack of a model.
1466 has_model = _rusense_model_active()
1467 if not has_model:
1468 min_conf = 0.0
1469 # Duty gates are model-aware: a loaded model lifts the empty-room baseline
1470 # (see the constants above), so use the higher gates then.
1471 pres_on = _RUSENSE_PRES_ON_MODEL if has_model else _RUSENSE_PRES_ON
1472 pres_off = _RUSENSE_PRES_OFF_MODEL if has_model else _RUSENSE_PRES_OFF
1473 try:
1474 sustain = float(shared_data.config.get('rusense_notify_sustain_s', 2))
1475 except (TypeError, ValueError):
1476 sustain = 2.0
1477 try:
1478 gf_window = int(shared_data.config.get('rusense_geofence_window', 30))
1479 except (TypeError, ValueError):
1480 gf_window = 30
1481 gf_window = max(4, min(600, gf_window))
1482 gf_enabled = bool(shared_data.config.get('rusense_geofence_enabled', True))
1483 now = time.time()
1484 # Surveillance alerts also respect the permitted-times schedule (if enabled);
1485 # node-offline + inactivity keep plain notify_on (maintenance / health).
1486 alerts_now = notify_on and _rusense_within_alert_schedule(now)
1487
1488 # ── Nodes first: the roster feeds BOTH the geofence RSSI windows and the
1489 # offline detector, so fetch it once and reuse it. ──
1490 nodes = _rusense_get('/api/v1/nodes')
1491 node_list = nodes.get('nodes') if isinstance(nodes, dict) else None
1492 if not isinstance(node_list, list):
1493 node_list = None
1494
1495 # Extend per-node RSSI windows and evaluate the geofence verdict. A "ghost"
1496 # is a disturbance with real energy that is NOT corroborated/interior — i.e.
1497 # a walk-by or through-wall neighbour. When detected we suppress the
1498 # occupied/motion/people RISE alerts (the "empty" alert is never a ghost).
1499 block_motion = False
1500 node_people = None
1501 if node_list is not None:
1502 # Per-node person_count is the only count the engine exposes (there is no
1503 # reliable aggregate). It ghosts on individual nodes, so take the MEDIAN
1504 # across active nodes — a single node spiking (e.g. node 2 → 3) can't move
1505 # the median, so a count is only believed when a majority of nodes agree.
1506 counts = sorted(int(n['person_count']) for n in node_list
1507 if n.get('status') == 'active'
1508 and isinstance(n.get('person_count'), (int, float)))
1509 if counts:
1510 node_people = counts[(len(counts) - 1) // 2] # lower median (conservative)
1511 with _rusense_notify_lock:
1512 windows = _rusense_notify_state['rssi_windows']
1513 seen = set()
1514 for n in node_list:
1515 nid, rssi = n.get('node_id'), n.get('rssi_dbm')
1516 if nid is None or rssi is None:

Callers

nothing calls this directly

Calls 4

runFunction · 0.85
save_configMethod · 0.80
getMethod · 0.45
writeMethod · 0.45

Tested by

no test coverage detected