mirror of
https://github.com/ruvnet/RuView
synced 2026-07-22 17:23:19 +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,108 @@
|
||||
"""ADR-185 P3 — MAT binding tests, incl. the §4.1 bit-for-bit parity gate.
|
||||
|
||||
The parity test drives the same committed CSI stream through the binding's
|
||||
DisasterResponse pipeline and asserts the survivor count + triage classes
|
||||
(as a SHA-256 of a canonical string) match the native-Rust golden. A
|
||||
mismatch is a release blocker.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from wifi_densepose import mat
|
||||
|
||||
GOLDEN = Path(__file__).parent / "golden"
|
||||
|
||||
|
||||
def fixture() -> dict:
|
||||
return json.loads((GOLDEN / "mat_input.json").read_text())
|
||||
|
||||
|
||||
def build_response() -> mat.DisasterResponse:
|
||||
cfg = mat.DisasterConfig(
|
||||
mat.DisasterType.Earthquake,
|
||||
sensitivity=0.9,
|
||||
confidence_threshold=0.1,
|
||||
max_depth=5.0,
|
||||
)
|
||||
resp = mat.DisasterResponse(cfg)
|
||||
resp.initialize_event(0.0, 0.0, "parity-fixture")
|
||||
resp.add_zone(mat.ScanZone.rectangle("Zone A", 0.0, 0.0, 50.0, 30.0))
|
||||
return resp
|
||||
|
||||
|
||||
def run_scan(resp: mat.DisasterResponse) -> None:
|
||||
for frame in fixture()["stream"]:
|
||||
resp.push_csi_data(frame["amplitude"], frame["phase"])
|
||||
resp.scan_once()
|
||||
|
||||
|
||||
# ─── enums / config ──────────────────────────────────────────────────
|
||||
|
||||
def test_triage_priority_order() -> None:
|
||||
assert mat.TriageStatus.Immediate.priority == 1
|
||||
assert mat.TriageStatus.Delayed.priority == 2
|
||||
assert mat.TriageStatus.Unknown.priority == 5
|
||||
|
||||
|
||||
def test_disaster_config_fields() -> None:
|
||||
cfg = mat.DisasterConfig(mat.DisasterType.Flood, sensitivity=1.5, confidence_threshold=0.3)
|
||||
assert cfg.sensitivity == 1.0 # clamped to [0, 1]
|
||||
assert abs(cfg.confidence_threshold - 0.3) < 1e-9
|
||||
|
||||
|
||||
# ─── pipeline behaviour ──────────────────────────────────────────────
|
||||
|
||||
def test_scan_requires_event() -> None:
|
||||
resp = mat.DisasterResponse(mat.DisasterConfig(mat.DisasterType.Unknown))
|
||||
# No initialize_event / add_zone -> scan_cycle errors "No active event".
|
||||
with pytest.raises(ValueError):
|
||||
resp.scan_once()
|
||||
|
||||
|
||||
def test_push_csi_rejects_mismatched_lengths() -> None:
|
||||
resp = build_response()
|
||||
with pytest.raises(ValueError):
|
||||
resp.push_csi_data([1.0, 2.0], [1.0])
|
||||
|
||||
|
||||
def test_scan_detects_survivor_from_breathing_stream() -> None:
|
||||
resp = build_response()
|
||||
run_scan(resp)
|
||||
survivors = resp.survivors()
|
||||
# The synthetic breathing-modulated stream trips one detection (matches
|
||||
# the native-Rust reference).
|
||||
assert len(survivors) == 1
|
||||
s = survivors[0]
|
||||
assert isinstance(s.id, str) and len(s.id) > 0
|
||||
assert s.triage_status == mat.TriageStatus.Delayed
|
||||
assert 0.0 <= s.confidence <= 1.0
|
||||
# survivors_by_triage is consistent with the survivor's own class.
|
||||
assert len(resp.survivors_by_triage(mat.TriageStatus.Delayed)) == 1
|
||||
assert len(resp.survivors_by_triage(mat.TriageStatus.Immediate)) == 0
|
||||
|
||||
|
||||
# ─── §4.1 bit-for-bit parity gate (release-blocking) ─────────────────
|
||||
|
||||
def test_bit_for_bit_parity_with_native_rust() -> None:
|
||||
resp = build_response()
|
||||
run_scan(resp)
|
||||
survivors = resp.survivors()
|
||||
priorities = sorted(s.triage_status.priority for s in survivors)
|
||||
canon = f"count={len(survivors)};triage_priorities={priorities}"
|
||||
got = hashlib.sha256(canon.encode()).hexdigest()
|
||||
expected = (GOLDEN / "mat_result.sha256").read_text().strip()
|
||||
assert got == expected, (
|
||||
f"Python MAT result diverged from native-Rust golden "
|
||||
f"(canonical form: {canon}; {got} != {expected})"
|
||||
)
|
||||
|
||||
|
||||
def test_base_wheel_import_error_message() -> None:
|
||||
src = Path(mat.__file__).read_text()
|
||||
assert "pip install wifi-densepose[mat]" in src
|
||||
Reference in New Issue
Block a user