From 60a4dd1c90f3f7ee673d5f14392a2cc4f130f974 Mon Sep 17 00:00:00 2001 From: James Smith Date: Thu, 2 Jul 2026 09:40:36 +0100 Subject: [PATCH] docs: add signal identification implementation plan 5-task plan covering signals.json seed, db loader + matcher, /signalid/match route, SignalIdModal component, and nav/waterfall integration. Co-Authored-By: Claude Sonnet 4.6 --- .../plans/2026-07-02-signal-identification.md | 1568 +++++++++++++++++ 1 file changed, 1568 insertions(+) create mode 100644 docs/plans/2026-07-02-signal-identification.md diff --git a/docs/plans/2026-07-02-signal-identification.md b/docs/plans/2026-07-02-signal-identification.md new file mode 100644 index 0000000..6c0ab20 --- /dev/null +++ b/docs/plans/2026-07-02-signal-identification.md @@ -0,0 +1,1568 @@ +# Signal Identification Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a bundled JSON signal database (~500 entries) with a scored match API route and a standalone modal overlay accessible from the waterfall and global nav. + +**Architecture:** `utils/signal_db.py` loads `data/signals.json` once at startup (lazy, cached). `routes/signalid.py` gains `POST /signalid/match` which calls the pure match function. `static/js/components/signal-id-modal.js` is a standalone IIFE modal called from both `waterfall.js` and a new nav button in `templates/partials/nav.html`. + +**Tech Stack:** Python 3.11+, Flask blueprints, vanilla JS (IIFE), JSON data file, existing CSS variables. + +## Global Constraints + +- All modulation tokens in `signals.json` must be uppercase strings (`WFM`, `FM`, `AM`, `USB`, `LSB`, `NFM`, `FSK`, `OOK`, `PSK`, etc.) +- All frequencies in `signals.json` stored as integers in Hz +- Match scores are 0–100 integer points +- Route added to existing `signalid_bp` blueprint in `routes/signalid.py`; no new blueprint +- No new Python dependencies — stdlib only +- JS follows IIFE pattern consistent with other components: `window.SignalIdModal = (function() { ... })()` +- Modal styled with existing CSS variables only (`--bg-card`, `--accent-cyan`, `--font-mono`, `--text-primary`, `--text-muted`, `--text-secondary`, `--accent-red`, `--bg-input`) +- `signal-id-modal.js` goes in `static/js/components/` (eagerly loaded, not lazy-loaded per mode) + +--- + +## File Map + +| Action | File | Responsibility | +|---|---|---| +| Create | `data/signals.json` | Bundled signal database | +| Create | `utils/signal_db.py` | Load + cache signals.json; pure `match_signals()` function | +| Create | `tests/test_signals_json.py` | Schema validation for every entry in signals.json | +| Create | `tests/test_signalid_match.py` | Unit tests for matching algorithm | +| Create | `static/js/components/signal-id-modal.js` | Signal ID modal IIFE component | +| Modify | `routes/signalid.py` | Add `POST /signalid/match` route | +| Modify | `config.py` | Add `REGION = _get_env("REGION", "GLOBAL")` | +| Modify | `templates/index.html` | Add eager ` +``` + +- [ ] **Step 3: Manually verify the modal renders correctly** + +Start the dev server: `sudo -E venv/bin/python intercept.py` + +Open the browser console and run: +```javascript +SignalIdModal.open({ frequency_mhz: 98.5, modulation: 'WFM' }) +``` + +Verify: +- Modal and backdrop appear +- Frequency field shows `98.5000` +- Modulation shows `WFM` +- Clicking "Search" fires a network request to `/signalid/match` +- Results render with score bars and match reasons +- FM Broadcast Radio appears as top result +- Clicking × or backdrop closes the modal + +Then run: `SignalIdModal.open({})` and verify the frequency field is blank and focused. + +- [ ] **Step 4: Commit** + +```bash +git add static/js/components/signal-id-modal.js templates/index.html +git commit -m "feat: add SignalIdModal IIFE component with scored results" +``` + +--- + +## Task 5: Nav button + waterfall integration + +**Files:** +- Modify: `templates/partials/nav.html` +- Modify: `templates/partials/modes/waterfall.html` +- Modify: `static/js/modes/waterfall.js` + +**Interfaces:** +- Consumes: `window.SignalIdModal.open(opts)` (Task 4) + +- [ ] **Step 1: Add "Signal ID" button to nav.html** + +In `templates/partials/nav.html`, find the Intel group `
` (the one containing `tscm`, `spystations`, `websdr`, `drone` items — around line 150). Add a Signal ID button as the first item inside that dropdown menu: + +```html + +``` + +The SVG is a magnifying glass with a + symbol — representing "find/identify signal". + +- [ ] **Step 2: Replace Signal ID sidebar panel in waterfall.html** + +In `templates/partials/modes/waterfall.html`, find and replace the entire Signal Identification `
` block (lines 127–154, from `
` through `
` containing `wfSigIdExternal`): + +Replace this entire block: +```html +
+

Signal Identification

+
+ Identify current frequency using local catalog and SigID Wiki matches. +
+
+ + +
+
+ + +
+
+ + +
+
Ready
+ + +
+``` + +With this compact replacement: +```html +
+

Signal Identification

+
+ Identify the current frequency against the local signal database. +
+ +
+``` + +- [ ] **Step 3: Update waterfall.js — replace signalid handoff and add openSignalId()** + +In `static/js/modes/waterfall.js`, find the `handoff('signalid')` branch (around line 1438): + +```javascript + } else if (target === 'signalid') { + useTuneForSignalId(); + _setHandoffStatus(`Running Signal ID at ${currentFreq.toFixed(4)} MHz`); + identifySignal().catch((err) => { + _setSignalIdStatus(`Signal ID failed: ${err && err.message ? err.message : 'unknown error'}`, true); + }); +``` + +Replace that branch with: + +```javascript + } else if (target === 'signalid') { + if (window.SignalIdModal) { + SignalIdModal.open({ frequency_mhz: currentFreq, modulation: _getMonitorMode() }); + } + _setHandoffStatus(`Opening Signal ID for ${currentFreq.toFixed(4)} MHz`); +``` + +Then find the public API block at the bottom of the IIFE (search for `return {` near the end of waterfall.js). Add `openSignalId` to the returned public API: + +Find the existing `return {` block and add `openSignalId` alongside the other exported functions: + +```javascript + openSignalId: function() { + if (window.SignalIdModal) { + SignalIdModal.open({ + frequency_mhz: Number.isFinite(_monitorFreqMhz) ? _monitorFreqMhz : _currentCenter(), + modulation: _getMonitorMode(), + }); + } + }, +``` + +- [ ] **Step 4: Remove dead Signal ID functions from waterfall.js** + +Search `waterfall.js` for these now-unused functions and remove each one entirely: +- `function _setSignalIdStatus(` (around line 250) +- `function _signalIdFreqInput(` (around line 257) +- `function _syncSignalIdFreq(` (around line 261) +- `function _clearSignalIdPanels(` (around line 268) +- `function _signalIdModeHint(` (around line 281) +- `function _renderLocalSignalGuess(` (around line 288) +- `function _renderExternalSignalMatches(` (around line 332) +- `function useTuneForSignalId(` (around line 381) +- `async function identifySignal(` (around line 386) + +Also remove `useTuneForSignalId` and `identifySignal` from the public `return {}` block if they are listed there. + +Also remove `_safeSigIdUrl` if it is only used by the above functions (check for any other references first with grep). + +- [ ] **Step 5: Verify the waterfall still works** + +Start the dev server and open the Waterfall mode. Verify: +- "Identify Signal" button appears in the sidebar +- Clicking it opens the Signal ID modal pre-populated with the current tuned frequency and modulation +- The "Signal ID" handoff button (in the Handoff section) also opens the modal +- Waterfall FFT continues running behind the modal +- The rest of the waterfall (bookmarks, scan, monitor, etc.) is unaffected + +Also open the nav and verify the "Signal ID" button appears in the Intel dropdown and opens the modal with blank fields. + +- [ ] **Step 6: Run full test suite** + +```bash +cd /Users/admin/Dev/intercept && pytest tests/test_signals_json.py tests/test_signalid_match.py tests/test_signalid_match_route.py -v +``` + +Expected: all tests pass with no regressions. + +- [ ] **Step 7: Commit** + +```bash +git add templates/partials/nav.html templates/partials/modes/waterfall.html static/js/modes/waterfall.js +git commit -m "feat: wire SignalIdModal to waterfall and global nav" +``` + +--- + +## Self-Review Checklist + +**Spec coverage:** +- ✅ Bundled JSON database (`data/signals.json`) — Task 1 +- ✅ Schema validation test (`tests/test_signals_json.py`) — Task 1 +- ✅ Database loader (`utils/signal_db.py`) — Task 2 +- ✅ 4-criterion scoring (frequency, bandwidth, modulation, region) — Task 2 +- ✅ match_reasons in response — Task 2 +- ✅ `POST /signalid/match` route — Task 3 +- ✅ REGION config (`INTERCEPT_REGION`) — Task 3 +- ✅ 60-second result cache — Task 3 +- ✅ Modal IIFE component (`signal-id-modal.js`) — Task 4 +- ✅ Pre-populated from waterfall (frequency + modulation) — Task 5 +- ✅ Blank entry from global nav — Task 5 +- ✅ Optional bandwidth field in modal — Task 4 +- ✅ Score bar + match reasons in modal — Task 4 +- ✅ SigID Wiki link when url present — Task 4 +- ✅ Error states (no matches, network failure, invalid freq) — Task 4 +- ✅ Waterfall continues running behind modal — by design (modal is overlay) +- ✅ Existing `/signalid/sigidwiki` route untouched — nothing removed from it +- ✅ `signal_guess.py` untouched — Task 3 imports `match_signals`, not replacing the old module + +**Type consistency:** +- `match_signals()` is defined in Task 2 and imported in Task 3 — same signature used throughout +- `SignalIdModal.open(opts)` defined in Task 4, called in Task 5 — consistent +- `bandwidth_hz` is always integer Hz in Python, converted from kHz in JS before the API call