From d9b73a24fa32a5a83857d61e378376cddec37501 Mon Sep 17 00:00:00 2001 From: arsen Date: Sun, 17 May 2026 16:49:13 +0700 Subject: [PATCH] docs(adr-113): ADR for day/night baseline profiles Co-Authored-By: claude-flow --- docs/adr/ADR-113-baseline-profiles.md | 156 ++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 docs/adr/ADR-113-baseline-profiles.md diff --git a/docs/adr/ADR-113-baseline-profiles.md b/docs/adr/ADR-113-baseline-profiles.md new file mode 100644 index 00000000..c73f1044 --- /dev/null +++ b/docs/adr/ADR-113-baseline-profiles.md @@ -0,0 +1,156 @@ +# 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:00–20: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.