diff --git a/.claude/skills/unifi-wifi/SKILL.md b/.claude/skills/unifi-wifi/SKILL.md index 9c86d51..162feb3 100644 --- a/.claude/skills/unifi-wifi/SKILL.md +++ b/.claude/skills/unifi-wifi/SKILL.md @@ -15,6 +15,9 @@ controller knows and making prioritized, validated changes. Built for any site; to read it (the two planes: Mongo config/interference now, live Network API later). - **[references/methodology.md](references/methodology.md)** — the prioritized tuning playbook (synthesized from a multi-model pass + live recon). Read before recommending any change. +- **[references/interference-model.md](references/interference-model.md)** — design for the + AP-interference / airtime-reduction model (which radios to disable / power down), incl. the data + feasibility (needs Plane 2 + a collector — the signals aren't in Mongo). ## What it does (current = Plane 1, read-only) 1. **Audit a site** — config + interference, no live plane needed: diff --git a/.claude/skills/unifi-wifi/references/interference-model.md b/.claude/skills/unifi-wifi/references/interference-model.md new file mode 100644 index 0000000..cc8195e --- /dev/null +++ b/.claude/skills/unifi-wifi/references/interference-model.md @@ -0,0 +1,61 @@ +# AP interference / airtime-reduction model — design + data feasibility + +Goal (per Mike): a fleet/site-level model that decides **which AP radios to disable, and where to +reduce power**, to cut total airtime contention while preserving client coverage. Per AP **per +radio, all bands** (not 2.4-only, not per-client). Inputs: each AP's view of neighboring APs (RF) ++ historical client connections. + +## Data feasibility (probed on Cascades 2026-06-15) +| Signal the model wants | In Mongo `ace`? | Source to use | +|---|---|---| +| **Our AP ↔ our AP RF visibility** (A hears B at RSSI r) | **NO** — `rogue` is FOREIGN APs only; our managed APs are filtered out (0 rows match our SSIDs) | **Live Network API** `stat/device` neighbor table / triggered RF scan (Plane 2) | +| **Historical client→AP connections / roam overlap** | **NO** — `user` keeps only `last_uplink_mac` (last AP); no sessions, `alarm` empty, `stat` collections empty | **Accumulated `stat/sta` polling** over time (Plane 2 + a collector) | +| **Physical AP coordinates** | **NO** — 0 APs placed on the 1 floorplan | derive coarse topology from **AP names** (room#/floor encoded) | +| Radio config (channel/band/width/power/min_rssi) | **YES** | Mongo `device.radio_table` (Plane 1) | +| Foreign interference per channel | **YES** | Mongo `rogue` aggregate (Plane 1) | + +**Conclusion:** the interference graph the model needs (our-AP mutual RSSI + client overlap) +**cannot** be built from Mongo. It requires **Plane 2 (the live Network API)** plus a **collector** +that accumulates snapshots over time. Mongo gives config + foreign-interference + (via names) a +coarse topology prior to seed the model before enough live data is collected. + +## Model design +Per band `b` (ng/na/6e), build a weighted graph over AP radios: + +- **Nodes:** each AP's radio on band `b`. +- **RF edges** `w_rf(A,B)`: from the live neighbor table — how strongly A hears B (and vice-versa), + scaled up when same/overlapping channel. Strong mutual RSSI on the same channel = high + co-channel interference. +- **Overlap edges** `w_ov(A,B)`: fraction of clients that have associated with BOTH A and B over + the collection window (built by snapshotting `stat/sta` every N minutes). High overlap = they + cover the same space → one is redundant. +- **Per-radio metrics:** `load` (num_sta, live `cu_total`), `unique_coverage` (clients only this + radio serves at good RSSI), `interference_contribution` (Σ strong RF edges on same channel). + +**Recommendation logic (greedy, coverage-safe):** +1. **Disable a radio** when: high `interference_contribution` AND high `coverage_redundancy` + (its clients keep good signal from neighbors) AND `unique_coverage ≈ 0`. Disable the worst + offender, recompute the graph, repeat until a redundancy floor is hit (don't open holes). +2. **Reduce power** when interference is high but `unique_coverage > 0` (can't disable without a + hole) — shrink the cell to cut contention while keeping coverage. +3. **Leave** radios that carry unique coverage and contribute little interference. +Band weighting: 2.4 prunes most aggressively (most redundant + least capacity value); 5/6 lighter; +6GHz usually keep (clean band, steer up). Output = ranked per-AP-per-radio actions with the metric +that justified each, applied **per zone** with live before/after validation. + +## Prerequisites to build it (the real next step) +1. **Wire Plane 2** — provision a dedicated **read-only UniFi admin or Network integration API key** + on `.29` (doable with our root SSH), vault as `infrastructure/uos-server-network-api`. Gives + `stat/device` (live neighbor RSSI, `cu_total`, `num_sta`, satisfaction) + `stat/sta` (client→AP). +2. **Stand up a collector** — a periodic job (cron on `.30`/a fleet host) snapshotting + `stat/device` + `stat/sta` into a small store (sqlite/postgres). The **overlap + RF matrix + accrue over the collection window** (a week+ gives a usable model; longer = better). This is the + "historical look at devices connected" Mike asked for — the controller doesn't retain it, so we + accumulate it ourselves. +3. **Build the model** on the accumulated data; seed early recommendations from the Mongo config + + AP-name topology prior until enough live data exists. + +## Status +Phase 1 (config + foreign-interference audit) is built (`scripts/audit-site.sh`). The interference +model is **blocked on Plane 2 + the collector** — needs a go to provision the UniFi API account and +stand up the collector.