wifi-densepose/docs/adr/ADR-113-baseline-profiles.md

6.6 KiB
Raw Blame History

ADR-113 — Multiple Baseline Profiles (Day/Night)

Status: Accepted Date: 2026-05-17 Scope: v2/crates/wifi-densepose-sensing-server/src/main.rs (resolve_baseline_profile, baseline_profile_watch, --baseline-profile CLI flag). Closes the "Multiple baseline profiles" item in CHECKLIST.

Context

The empty-room baseline that ADR-103 / ADR-104 store in data/baseline.json is captured at one point in time. The channel state it reflects is sensitive to:

  • People walking through corridors / adjacent apartments at night vs. day (different building-wide ambient WiFi traffic).
  • AC / refrigerator compressor duty cycles (broadband noise at the ~Hz scale that changes per-time-of-day).
  • Sunlight on building walls (~mm-scale thermal expansion changes multipath).

In the current deployment we observe the absent baseline mean shift by ~3-5 % between 14:00 and 04:00 — small but enough to push the CV of a stationary subcarrier across the ADR-103 threshold and trigger false present_still flags overnight.

A single baseline can't model both regimes simultaneously. The lowest- complexity fix is to keep two: a day baseline and a night baseline, loaded at startup and hot-swapped at the day/night boundary.

Decisions

D1 — --baseline-profile selector with four modes

--baseline-profile {single,auto,day,night}    (default: single)
Mode Behaviour
single Legacy. Load RUVIEW_BASELINE_FILE or data/baseline.json. No watch task. Default.
auto Pick day/night by local hour. Hot-reload at 07:00 / 21:00 transitions.
day Force data/baseline.day.json. No auto switching.
night Force data/baseline.night.json. No auto switching.

Default is single so existing deployments don't have to migrate. Operators opt in by recording two profiles + flipping the flag.

D2 — Day window: 07:0020:59 local

Hard-coded for now. The split matches the ambient-WiFi pattern in this deployment (residential building, no commercial traffic). Tunable in code (future ADR can parameterise if a second deployment needs different hours), but a flag is premature parameter sprawl.

chrono::Local::now().hour() drives the choice — no UTC offset arithmetic; the OS provides the local hour directly.

D3 — Filename convention

data/baseline.day.json
data/baseline.night.json
data/baseline.json          (legacy / single-profile fallback)

Same JSON schema as ADR-103 v2 (full_broadband_*, per_subcarrier_mean, optionally per_subcarrier_phase_mean per ADR-104). The recording script and REST endpoint can write to any of the three paths via --out / out body field — no schema change.

D4 — Missing-file fallback to data/baseline.json

If a requested profile file doesn't exist (e.g., operator set --baseline-profile auto but only recorded baseline.json), the server logs a warning and loads the legacy single-baseline file instead. This makes the migration path "set the flag, then start recording per-profile baselines one at a time" — no big-bang switch.

D5 — Hot-reload via baseline_profile_watch

Background task fires every 5 min, re-resolves the profile, and if the profile tag changed (day → night or vice versa) calls load_baseline_file on the new path. load_baseline_file already hot-swaps in place — the per-node override maps and per-subcarrier baselines update without touching live frame ingest.

5 min cadence means transitions land within 5 min of the schedule — acceptable lag for a baseline whose channel-side variance is on the ~hour timescale.

A static CURRENT_BASELINE_PROFILE mutex tracks the loaded tag so the watch avoids redundant disk reads when nothing changed.

D6 — Watch is a no-op outside auto

single, day, and night modes don't need switching — those are "set once at startup". The watch task logs a one-line "disabled" message and returns immediately. Saves a tokio task slot and suppresses log noise on the common single-profile deployment.

Trade-offs

  • Operator has to record two baselines. Twice the operator time (~5 min × 2). Unavoidable for the use case.
  • Hard-coded 07:00 / 21:00 split. A different deployment (office, shift-work) would want different hours. Defer to a future ADR; for this deployment the residential cadence works.
  • No smooth interpolation between profiles. At 20:59 we use day, at 21:00 we use night — a step transition. For amplitude/baseline comparison the step is fine (the classifier already smooths over multiple frames). A weighted blend across the transition window would be feasible but adds complexity for limited gain.
  • No more than two profiles. Seasonal (summer/winter), weekday/ weekend etc. would need either more flags or a config-file driven approach. Out of scope.

Files Touched

v2/crates/wifi-densepose-sensing-server/src/main.rs
  - --baseline-profile CLI flag (D1)
  - resolve_baseline_profile (D1, D2, D3, D4)
  - baseline_profile_file_or_fallback (D4)
  - baseline_profile_watch background task (D5, D6)
  - CURRENT_BASELINE_PROFILE static + init helper (D5)
  - startup uses resolve_baseline_profile (D1)
  - spawn baseline_profile_watch alongside other watches (D5)

docs/adr/ADR-113-baseline-profiles.md  (this)

Verified Acceptance

  • cargo build --release -p wifi-densepose-sensing-server clean.
  • cargo test --release -p wifi-densepose-sensing-server --no-default-features — 326 tests pass.
  • sensing-server --help shows the new --baseline-profile flag with the four-mode help text.
  • Running with --baseline-profile single (default) keeps the existing log line baseline-profile: starting in 'single' mode → data/baseline.json and disables the watch task with Baseline profile watch disabled (--baseline-profile single).
  • Running with --baseline-profile auto while no baseline.day.json exists logs baseline-profile day: file data/baseline.day.json not found, falling back to data/baseline.json then proceeds.

References

  • ADR-103 — persistent baseline storage + JSON schema this ADR reuses.
  • ADR-104 — per-subcarrier amplitude + phase drift; both consume whatever baseline the active profile loads.
  • ADR-107 — POST /api/v1/baseline/calibrate can write into any of the three paths via the out body field, so operators can record each profile via the same UI button.