mirror of
https://github.com/ruvnet/RuView
synced 2026-07-23 17:33:20 +00:00
feat(adr-185): P3 MAT bindings (wifi_densepose.mat) + parity harness
Bind the ADR-024 MAT (Mass Casualty Assessment Tool) disaster-survivor detection + START triage surface into the wheel behind a gated [mat] extra / Cargo `mat` feature, mirroring the upstream disaster/ML gating. Also adds the [sota] superset extra (aether+meridian+mat). Surface (bound against the REAL code at HEAD, not the ADR wishlist): - DisasterType (9 variants) / TriageStatus (5, START) enums - DisasterConfig (builder-backed, continuous_monitoring forced off) - DisasterResponse: initialize_event / add_zone / push_csi_data / scan_once / survivors / survivors_by_triage - Survivor (id, triage_status, confidence, location, latest_vitals) - VitalSignsReading (breathing/heartbeat rate, movement, confidence) - ScanZone.rectangle / ScanZone.circle push_csi_data + scan_once are GIL-released. Honest deviations from ADR section 3.4 (documented in module header): - ADR proposed adding a Rust-side sync scan_once() (section 11.3). That was UNNECESSARY: the public async start_scanning() runs exactly one scan_cycle and returns when continuous_monitoring == false. The binding forces that flag off and drives one cycle on a private current-thread tokio runtime -- NO change to wifi-densepose-mat. - scan_cycle requires an active event + Active zone, which the ADR surface omitted; initialize_event + add_zone are bound as required additions. - Survivor.vital_signs is a *history* in the real code; bound as Survivor.latest_vitals -> Optional[VitalSignsReading]. - DisasterType has 9 variants at HEAD (adds Landslide/MineCollapse/ Industrial/TunnelCollapse); all bound. Parity (section 4.1, release-blocking): committed fixture mat_input.json (synthetic breathing-modulated CSI stream) -> native Rust reference (tests/mat_parity.rs, drives DisasterResponse directly) locks tests/golden/mat_result.sha256 over a canonical `count=<K>;triage_priorities=<sorted>` string (survivor UUIDs/timestamps excluded as non-deterministic); pytest (tests/test_mat.py) runs the same stream through the binding and asserts the identical hash. Both detect exactly 1 survivor, triage Delayed. Honest: synthetic fixture proves binding==native path equality, NOT live detection accuracy. Verified: cargo test --features mat --test mat_parity -> 2/2 pass maturin develop --features mat + pytest tests/test_mat.py -> 7/7 pass default cargo build clean, 0 mat/tokio refs in the default dep graph. WHEEL-SIZE FINDING (ADR-185 section 9): default-features=false drops MAT's `api` (axum) and `ruvector` features, but MAT still carries NON-optional tokio (rt/sync/time), wifi-densepose-nn (ort/ONNX + reqwest/hyper), rustfft, geo, ndarray. So a [mat] wheel exceeds the ADR-117 section 5.4 <=5 MB budget -- same leaf-crate-hoist follow-up as AETHER/MERIDIAN. The default wheel is untouched (feature-gated).
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
"""MAT — Mass Casualty Assessment Tool (ADR-024 crate, ADR-185 P3).
|
||||
|
||||
WiFi-based disaster-survivor detection and START-protocol triage from CSI:
|
||||
ingest CSI frames, run a scan cycle, and query detected survivors by triage.
|
||||
|
||||
Available **only** when the wheel was built with the ``[mat]`` extra::
|
||||
|
||||
pip install wifi-densepose[mat]
|
||||
|
||||
Quick start::
|
||||
|
||||
from wifi_densepose.mat import DisasterConfig, DisasterResponse, DisasterType, ScanZone
|
||||
|
||||
cfg = DisasterConfig(DisasterType.Earthquake, sensitivity=0.9, confidence_threshold=0.1)
|
||||
resp = DisasterResponse(cfg)
|
||||
resp.initialize_event(0.0, 0.0, "Building A") # required before scanning
|
||||
resp.add_zone(ScanZone.rectangle("North Wing", 0.0, 0.0, 50.0, 30.0))
|
||||
for amp, phase in csi_stream:
|
||||
resp.push_csi_data(amp, phase)
|
||||
resp.scan_once() # one detection cycle
|
||||
for s in resp.survivors():
|
||||
print(s.id, s.triage_status, s.confidence, s.location)
|
||||
|
||||
Honest scope (ADR-185 §3.4): the ADR's Rust-side `scan_once()` wrapper was
|
||||
unnecessary — this binding drives one cycle of the public async
|
||||
`start_scanning()` (with `continuous_monitoring` forced off) on an internal
|
||||
runtime. `initialize_event` + `add_zone` are required before `scan_once`.
|
||||
`Survivor.latest_vitals` returns the latest reading (the Rust accessor is a
|
||||
history). The detection pipeline is real but unvalidated on live rubble.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from wifi_densepose import _native
|
||||
|
||||
# MAT symbols are compiled into `_native` only under the Rust `mat` feature.
|
||||
if not hasattr(_native, "DisasterResponse"):
|
||||
raise ImportError(
|
||||
"wifi_densepose.mat is not available in this wheel. "
|
||||
"It requires the 'mat' extra: pip install wifi-densepose[mat]"
|
||||
)
|
||||
|
||||
DisasterType = _native.DisasterType
|
||||
TriageStatus = _native.TriageStatus
|
||||
DisasterConfig = _native.DisasterConfig
|
||||
DisasterResponse = _native.DisasterResponse
|
||||
ScanZone = _native.ScanZone
|
||||
Survivor = _native.Survivor
|
||||
VitalSignsReading = _native.VitalSignsReading
|
||||
|
||||
__all__ = [
|
||||
"DisasterType",
|
||||
"TriageStatus",
|
||||
"DisasterConfig",
|
||||
"DisasterResponse",
|
||||
"ScanZone",
|
||||
"Survivor",
|
||||
"VitalSignsReading",
|
||||
]
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Type stubs for the MAT bindings (ADR-185 P3).
|
||||
|
||||
Present only when the wheel is built with the ``[mat]`` extra.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
|
||||
class DisasterType(enum.Enum):
|
||||
BuildingCollapse = 0
|
||||
Earthquake = 1
|
||||
Landslide = 2
|
||||
Avalanche = 3
|
||||
Flood = 4
|
||||
MineCollapse = 5
|
||||
Industrial = 6
|
||||
TunnelCollapse = 7
|
||||
Unknown = 8
|
||||
def __repr__(self) -> str: ...
|
||||
|
||||
class TriageStatus(enum.Enum):
|
||||
Immediate = 0
|
||||
Delayed = 1
|
||||
Minor = 2
|
||||
Deceased = 3
|
||||
Unknown = 4
|
||||
@property
|
||||
def priority(self) -> int: ...
|
||||
def __repr__(self) -> str: ...
|
||||
|
||||
class VitalSignsReading:
|
||||
@property
|
||||
def breathing_rate_bpm(self) -> float | None: ...
|
||||
@property
|
||||
def heartbeat_rate_bpm(self) -> float | None: ...
|
||||
@property
|
||||
def movement_intensity(self) -> float: ...
|
||||
@property
|
||||
def confidence(self) -> float: ...
|
||||
def __repr__(self) -> str: ...
|
||||
|
||||
class Survivor:
|
||||
@property
|
||||
def id(self) -> str: ...
|
||||
@property
|
||||
def triage_status(self) -> TriageStatus: ...
|
||||
@property
|
||||
def confidence(self) -> float: ...
|
||||
@property
|
||||
def location(self) -> tuple[float, float, float] | None: ...
|
||||
@property
|
||||
def latest_vitals(self) -> VitalSignsReading | None: ...
|
||||
def __repr__(self) -> str: ...
|
||||
|
||||
class DisasterConfig:
|
||||
def __init__(
|
||||
self,
|
||||
disaster_type: DisasterType,
|
||||
sensitivity: float = ...,
|
||||
confidence_threshold: float = ...,
|
||||
max_depth: float = ...,
|
||||
scan_interval_ms: int = ...,
|
||||
) -> None: ...
|
||||
@property
|
||||
def sensitivity(self) -> float: ...
|
||||
@property
|
||||
def confidence_threshold(self) -> float: ...
|
||||
@property
|
||||
def max_depth(self) -> float: ...
|
||||
def __repr__(self) -> str: ...
|
||||
|
||||
class ScanZone:
|
||||
@staticmethod
|
||||
def rectangle(
|
||||
name: str, min_x: float, min_y: float, max_x: float, max_y: float
|
||||
) -> ScanZone: ...
|
||||
@staticmethod
|
||||
def circle(name: str, center_x: float, center_y: float, radius: float) -> ScanZone: ...
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
def __repr__(self) -> str: ...
|
||||
|
||||
class DisasterResponse:
|
||||
def __init__(self, config: DisasterConfig) -> None: ...
|
||||
def initialize_event(self, x: float, y: float, description: str) -> None: ...
|
||||
def add_zone(self, zone: ScanZone) -> None: ...
|
||||
def push_csi_data(self, amplitudes: list[float], phases: list[float]) -> None: ...
|
||||
def scan_once(self) -> None: ...
|
||||
def survivors(self) -> list[Survivor]: ...
|
||||
def survivors_by_triage(self, status: TriageStatus) -> list[Survivor]: ...
|
||||
def __repr__(self) -> str: ...
|
||||
Reference in New Issue
Block a user