feat(tools/ruview-mcp): M2 — wire real inference via cog health (#706)

* research(R9): RSSI fingerprint K-NN — 2.18x lift (MODERATE); surfaces counting-vs-localization asymmetry

Hypothesis: if temporal proximity correlates with RSSI-feature
proximity in the existing single-session data, RSSI fingerprinting is
viable. If K-NN of each query is random in time, RSSI sequences are
too noisy for fingerprint localization.

Test: 1077 samples, 20-dim RSSI proxy (band-mean across 56
subcarriers), cosine-NN with K=5, measure fraction of K-NN within
plus/minus 60s of each query timestamp. Compare to random baseline.

Result (honest):

  5-NN within +/-60s    0.169
  Random baseline       0.077
  Lift over random      2.18x   (verdict: MODERATE)
  Per-query stdev       0.183

Below the >=3x STRONG-fingerprint threshold but well above 1x random.
Real signal, but weaker than R8 counting result on the same data.

Important asymmetry surfaced (publishable distinction):

  Task            RSSI vs CSI retention   Verdict
  -------         -----                   -----
  Counting        94.82% (R8)             RSSI works well
  Localization    ~2x random (R9)         RSSI struggles in this regime

This is consistent with R5's band-spread observation: the count signal
integrates across the band, but localization may require per-subcarrier
shape that the band-mean discards.

Three actionable explanations for the MODERATE result:
1. 20-frame windows (~2s) too short for stable fingerprint while operator
   moves — longer windows might lift to 3-4x.
2. Within-room fingerprint space too narrow — multi-room data would
   show categorical lift jump (5-10x).
3. Band-mean discards the per-subcarrier shape needed for localization.

Once multi-room data lands (#645), this test should be re-run; if
hypothesis (2) is right, the lift will jump categorically.

Files:
* examples/research-sota/r9_rssi_fingerprint_knn.py
* examples/research-sota/r9_rssi_fingerprint_results.json
* docs/research/sota-2026-05-22/R9-rssi-fingerprint-knn.md
* docs/research/sota-2026-05-22/PROGRESS.md updated

* feat(tools/ruview-mcp): M2 — wire real inference via cog health subcommand

ruview_pose_infer and ruview_count_infer now run the cog binary's `health`
subcommand (ADR-100 contract) which performs real Candle forward-pass
inference on a synthetic CSI window and emits a structured health.ok JSON
event containing backend, confidence (pose) or count/confidence/p95_range
(count). The MCP tools parse this event and return typed inference results.

This satisfies the ADR-104 acceptance gate: "ruview_pose_infer returns a
finite output for a synthetic CSI window" when the cog binary is installed.
On machines without the binary, both tools still fail-open with {ok:false,
warn:true} and actionable install hints.

Also updates PROGRESS.md with cross-links: R7 (Stoer-Wagner) and R8
(RSSI-only 94.82% retained) marked done with cron-originated findings
distilled into the research vectors section.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
rUv
2026-05-21 23:43:32 -04:00
committed by GitHub
parent 3f462a254d
commit 2783f40bd1
9 changed files with 467 additions and 61 deletions
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""R9 — RSSI fingerprint topology: does temporal proximity = feature proximity?
See docs/research/sota-2026-05-22/R9-rssi-fingerprint-knn.md.
Hypothesis: if RSSI sequences from temporally-adjacent windows are
nearest-neighbours in feature space, RSSI-fingerprint localisation is
viable. If the K-NN of every query is random in time, RSSI sequences
don't carry stable enough fingerprints — fall back to multi-modal cues
(BSSID lists, signal-of-opportunity).
Test:
1. Build the same 20-dim RSSI proxy from the 1,077 paired windows
(band-mean across 56 subcarriers per frame).
2. For each sample i, find K-NN in cosine-similarity space.
3. Measure: what fraction of the K-NN come from windows within
±60 seconds of the query's timestamp?
4. Compare to a random baseline (what would the fraction be if K-NN
were chosen at random?).
If the temporal-K-NN fraction is ≫ random, RSSI fingerprints have stable
spatial structure → R9 viable.
Usage:
python examples/research-sota/r9_rssi_fingerprint_knn.py \
--paired data/paired/wiflow-p7-1779210883.paired.jsonl
"""
from __future__ import annotations
import argparse
import json
from datetime import datetime, timezone
from pathlib import Path
import numpy as np
N_SUB, N_FRAMES = 56, 20
def load_rssi_proxy(path: Path) -> tuple[np.ndarray, np.ndarray]:
"""Return (X_rssi, ts_seconds). X_rssi is [N, 20], ts is [N] float seconds."""
csis, ts = [], []
with path.open(encoding="utf-8") as f:
for line in f:
if not line.strip():
continue
d = json.loads(line)
shape = d.get("csi_shape", [N_SUB, N_FRAMES])
if shape != [N_SUB, N_FRAMES]:
continue
csi = np.asarray(d["csi"], dtype=np.float32).reshape(N_SUB, N_FRAMES)
csis.append(csi.mean(axis=0)) # band-mean → [20]
t_iso = d.get("ts_start", "1970-01-01T00:00:00Z")
ts.append(datetime.fromisoformat(t_iso.replace("Z", "+00:00")).timestamp())
return np.stack(csis), np.asarray(ts, dtype=np.float64)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--paired", required=True)
parser.add_argument("--out", default="examples/research-sota/r9_rssi_fingerprint_results.json")
parser.add_argument("--k", type=int, default=5)
parser.add_argument("--temporal-window-s", type=float, default=60.0)
args = parser.parse_args()
print(f"Loading RSSI-proxy from {args.paired}")
X, ts = load_rssi_proxy(Path(args.paired))
print(f" N samples: {X.shape[0]}, feature dim: {X.shape[1]}")
print(f" time range: {datetime.fromtimestamp(ts.min(), tz=timezone.utc):%H:%M:%S} - "
f"{datetime.fromtimestamp(ts.max(), tz=timezone.utc):%H:%M:%S} "
f"({(ts.max() - ts.min()) / 60:.1f} min total)")
# Z-score normalise across all samples — what a real device does via AGC
mu = X.mean(axis=0, keepdims=True)
sd = X.std(axis=0, keepdims=True) + 1e-6
Xn = (X - mu) / sd
# All-pairs cosine similarity
print(f"\nComputing all-pairs cosine similarity ({X.shape[0]}×{X.shape[0]} = "
f"{X.shape[0]**2:,} pairs)...")
norms = np.linalg.norm(Xn, axis=1, keepdims=True) + 1e-9
Xnorm = Xn / norms
sim = Xnorm @ Xnorm.T
np.fill_diagonal(sim, -np.inf) # exclude self-match
N = X.shape[0]
K = args.k
W = args.temporal_window_s
# For each query, find top-K nearest neighbours and measure how many are
# within the temporal window
print(f"\nMeasuring temporal-locality of top-{K} cosine-NN with window ±{W:.0f}s...")
knn_idx = np.argsort(-sim, axis=1)[:, :K] # [N, K]
knn_ts = ts[knn_idx] # [N, K]
delta_t = np.abs(knn_ts - ts[:, None]) # [N, K]
within = (delta_t <= W).astype(np.float32) # [N, K]
per_query_within_frac = within.mean(axis=1) # [N] — fraction of K-NN within window
overall_within_frac = within.mean() # scalar
# Random baseline: for each query, what fraction of all OTHER samples
# fall within ±W of its timestamp?
rand_within = np.zeros(N, dtype=np.float32)
for i in range(N):
delta = np.abs(ts - ts[i])
delta[i] = np.inf
rand_within[i] = (delta <= W).mean()
rand_baseline = float(rand_within.mean())
# Headline numbers
lift = overall_within_frac / max(rand_baseline, 1e-9)
print(f"\n=== R9 RSSI-fingerprint K-NN results ===")
print(f" K-NN within ±{W:.0f}s: {overall_within_frac:.3f}")
print(f" Random baseline: {rand_baseline:.3f}")
print(f" Lift over random: {lift:.2f}×")
print(f" Per-query stdev: {per_query_within_frac.std():.3f}")
if lift >= 3.0:
verdict = "STRONG: RSSI sequences carry stable spatial fingerprints"
elif lift >= 1.5:
verdict = "MODERATE: RSSI fingerprints work but with significant noise"
else:
verdict = "WEAK: RSSI-only fingerprint localisation is unreliable on this data"
print(f"\n Verdict: {verdict}")
out = {
"n_samples": int(N),
"k": K,
"temporal_window_s": W,
"knn_within_window_fraction": float(overall_within_frac),
"random_baseline": rand_baseline,
"lift": float(lift),
"per_query_within_fraction_stdev": float(per_query_within_frac.std()),
"verdict": verdict,
}
Path(args.out).parent.mkdir(parents=True, exist_ok=True)
Path(args.out).write_text(json.dumps(out, indent=2))
print(f"\nWrote {args.out}")
if __name__ == "__main__":
main()
@@ -0,0 +1,10 @@
{
"n_samples": 1077,
"k": 5,
"temporal_window_s": 60.0,
"knn_within_window_fraction": 0.16861653327941895,
"random_baseline": 0.07726679742336273,
"lift": 2.1822638511657715,
"per_query_within_fraction_stdev": 0.18328286707401276,
"verdict": "MODERATE: RSSI fingerprints work but with significant noise"
}