3 Commits
Author SHA1 Message Date
cortandClaude Opus 4.7 89c506a224 docs: audit and refresh ahead of merge to main
Documentation cleanup pass across the tree, driven by the upcoming merge
of the dmrd-translation branch to main.

Deletions (obsolete / superseded):
- docs/user_csv_automation_proposal.md — implemented as dashboard/user_db.py
- docs/QUICK_TEST_REFERENCE.md — stale test counts, referenced a
  TEST_COVERAGE.md that no longer exists
- user.old.csv — development backup artifact (also added user.old* and
  user.bak* to .gitignore)

Release notes renumbered to the 4.x series (file kept 1.x-labelled from
a prior numbering scheme that predated the 4.1.0 release):
- docs/RELEASE_NOTES_v1.5.md   -> docs/RELEASE_NOTES_v4.5.md
- docs/RELEASE_NOTES_v1.6.1.md -> docs/RELEASE_NOTES_v4.6.1.md
- Also fixed a stray "v1.5+" -> "v4.5+" in stream_tracking_diagrams.md

Content fixes where docs contradicted code or were stale:
- docs/dmrd_translation.md: replaced the "Payload blanking" section with
  "Link Control rewriting" describing VHEAD/VTERM full-LC and voice-burst
  EMB_LC re-encoding via dmr_utils3.bptc
- docs/connecting_to_hblink4.md: cross-reference updated to match
- docs/protocol.md: corrected call-type bit direction (0=Group,
  1=Private/Unit — previously reversed), replaced "Bits 0-3: Reserved"
  with actual dtype_vseq semantics, updated translation-doc link
- readme.md: "Built on Twisted" -> asyncio (Twisted was removed in the
  outbound-connection release); expanded docs index to include routing,
  DMRD translation, diagrams, TODO, and OpenBridge analysis; added DMRD
  translation to the features list
- docs/stream_tracking.md: StreamState snippet now shows lc_base and
  lc_cache fields added for LC rewriting
- scripts/README.md: noted that dashboard/user_db.py is the primary
  refresh path; script remains as manual fallback
- dashboard/user_db.py: dropped the doctring reference to the deleted
  proposal file

Note: dmrd-translation branch remains in active development but is
fully functional as of this commit. Tests: 75/75 pass (pre-existing
test_access_control.py failures are unrelated and predate this branch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 07:52:37 -05:00
cortandClaude Opus 4.7 16db4f0421 feat: automated radioid.net user database refresh in dashboard
The dashboard now pulls user.csv from radioid.net on a daily schedule,
filters it to the operator's configured country/callsign/ID scope, and
hot-swaps the in-memory radio_id -> callsign map without a restart. No more
stale callsigns when someone new gets their DMR ID.

Pipeline (dashboard/user_db.py):
- Stage 1: conditional HTTPS GET (If-Modified-Since) against
  database.radioid.net/static/user.csv with identifying User-Agent. Uses
  stdlib urllib.request, gzip-aware, 100 MB safety cap, 120s timeout. No
  new dependencies.
- Stage 2: stream through csv.DictReader applying country / callsign_regex
  / radio_id_range filters. Default countries are ["United States",
  "Canada"], reproducing the current scripts/filter_user_csv.py behavior
  byte-for-byte.
- Stage 3: atomic write to data/user_db.json (stringified dict, ~2.4 MB vs
  the current 7.7 MB CSV), sidecar metadata, then wholesale replacement of
  the in-memory dict reference — single bytecode op under the GIL, no lock
  needed. Readers see either the old or new dict, never a partial state.

Failure handling: any stage fault leaves the previous snapshot in place
and logs a warning. A filter that produces fewer than
fallback.min_rows_required rows is treated as a malformed download and
rejected. 304 Not Modified touches the timestamp so the "too stale" check
doesn't re-fire on every restart. The guiding rule: callsign lookups must
never get worse than they were before.

Scheduling (user_db_refresh_task coroutine):
- Catch-up refresh at startup if the snapshot is missing or older than
  on_startup_if_older_than_hours (default 36).
- Then schedules the next run at time_of_day + [0, jitter_minutes)
  random offset. Defaults: 03:17 local, 15 min jitter — deliberately
  off-hour and off-minute to avoid the top-of-hour stampede hitting
  radioid.net from Pi-Star fleets every night.
- "hourly" in config is clamped to "daily" with a warning. Upstream only
  regenerates daily; sub-daily polling is pure waste.

Config: new user_database block in dashboard/config.json with source URL,
User-Agent (with explicit contact placeholder), schedule, filter rules, and
fallback thresholds. Sample and defaults in load_config() match. Startup
warns if the placeholder contact 'operator@example.org' wasn't changed so
radioid.net can reach the operator if there's ever an issue.

Dashboard UI: small "Callsign DB: N entries, updated Xh ago" indicator to
the right of the "Last Heard" section header, with a verbose hover tooltip
showing source URL, last refresh status, upstream Last-Modified, and last
error if any. Gold when the last refresh failed; re-ticks every 60s
client-side. The single call site that consumes user_database (stream_start
handler) was updated from module global to state.user_db.

scripts/filter_user_csv.py gets a deprecation notice but stays functional
for air-gapped workflows. Its filter logic now lives in
dashboard.user_db.filter_rows_from_csv_bytes() as a pure function so the
legacy script could be rewritten to import it without duplicating rules.

Design rationale and sources documented in
docs/user_csv_automation_proposal.md — notably, radioid.net's terms
explicitly flag "bulk mirroring" of the JSON API as blockable behavior, so
the daily static-CSV pull is the intended channel (Pi-Star, HBMonv2, and
others all use this same URL). The dashboard now follows the same
convention.

Locally verified: filter round-trips the existing user.csv to 136,613
rows in ~750ms, snapshot is 2.41 MB on disk, and the import path cleanly
wires into DashboardState without regressing existing stats/last-heard
persistence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:38:15 -05:00
cort b4a4578876 Add script to filter user.csv for US/Canada entries
New utility script:
- scripts/filter_user_csv.py - Filter DMR user database
- scripts/README.md - Documentation and usage examples

Features:
- Process full user database to extract US/Canada only
- Automatic statistics and file size reporting
- Can overwrite input or create new output file
- Preserves all CSV columns

Usage:
  python3 scripts/filter_user_csv.py user_full.csv user.csv

Benefits:
- Reproducible filtering for future database updates
- Clear documentation for maintenance
- Easy to add more countries if needed
2025-10-03 15:32:06 -05:00