Repository navigation
Conversation
Tri-layer precedence (debug override > fresh cached remote config > BuildConfig default), SharedPreferences-backed stores, validated remote feature-config fetch that never blocks startup or runs in tests. Debug builds default the three flags on; release ships them off. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Framework-free recommendation contract models with validating parser, deterministic goal-weighted scorer with renormalization + separate confidence, API repository (cooldown, 429 back-off, stale/offline cache fallback) and a debug-only deterministic fixture repository that runs through the real scorer. Personal PSK Reporter analytics computation with small-sample suppression and a bounded local baseline store. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…panel Compact advisor card in the Band & Mode sheet (slot param keeps the sheet decoupled), detail overlay with goal chips, expandable evidence, score components, alternatives, freshness/partial/offline states, and the personal PSK Reporter panel (sender reports only, explicit not-a-contact disclaimer, small-sample suppression). One-tap tuning reuses selectBandIndex and refuses dials not in the band plan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tri-state override per flag (Default / Force on / Force off) with the effective value and its source, plus the fixture-data toggle. Reached from a BuildConfig.DEBUG-gated row in Advanced settings; absent from release builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure alert policy (evidence thresholds, 90-min opening identities, 48h dedup history, per-band + global cooldowns, quiet hours with midnight wrap), WorkManager periodic check (30 min, network-constrained, cancelled whenever the flag or preference turns off), dedicated notification channel with Android 13 permission handling, tap-through to the Band Advisor detail, and the Band Advisor settings screen with per-type opt-ins, quiet hours, watched bands/regions, privacy note, and local-data clear. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Containerized FastAPI service: voacapl wrapper (deck builder with a documented FT8 dB-Hz SNR profile, short-filename handling, per grid4/month/hour/power/antenna/noise/region caching), centralized PSK Reporter aggregation (5-min etiquette, gzip, defusedxml, band-hour baseline normalization), deterministic goal-weighted scoring with renormalization + separate confidence, and versioned endpoints: /healthz, /v1/feature-config, /v1/recommendation, /v1/conditions. 190 pytest tests, no network. Dockerfile builds voacapl from source (invoked as an external GPL tool, not linked or vendored). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs/band-advisor.md covers the flag precedence, client architecture, contract, scoring model, alert policy, privacy, testing, and staged rollout. The catch-clause changes replace generic Exception catches with IOException / runCatching so the new code adds no detekt findings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cold-start alerts
There was a problem hiding this comment.
🟡 Changes recommended
One or more issues must be addressed before approval.
17 open issues
Invoke makeitshfbc directly instead of as a make target · New Copy targets of absolute symlinks into the runtime image · New Disable Uvicorn access logs to protect query parameters · New Prevent HTTPX logs from exposing request URLs · New Use an installed antenna model for the WIRE default · New Correct SUNSPOT field alignment and width · New Match CIRCUIT coordinate fields to native reader layout · New Render SYSTEM fields in exact five-column widths · New Strip the native printer-control prefix before parsing · New Reject observations with missing or invalid timestamps · New Refresh remote flags before background worker gating · New Reschedule propagation alerts after remote refresh · New Reconcile alert scheduling after debug flag changes · New Support portable-prefix callsigns in server validation · New Key aggregate caches by observer grid · New Persist cache expiry across process restarts · New Format notification timestamps explicitly in UTC · New
What changed in this PR
Introduces a feature-flagged Band Advisor for Android, backed by a FastAPI propagation service.
Changes:
- Adds advisor UI, recommendation models, personal analytics, and tuning integration.
- Adds remote feature flags and opt-in background propagation alerts.
- Adds VOACAP integration, PSK aggregation, caching, containerization, and tests.
| File | Description |
|---|---|
| server/band-advisor/tests/test_scoring.py | Updated as part of this pull request. |
| server/band-advisor/tests/test_runner.py | Updated as part of this pull request. |
| server/band-advisor/tests/test_regions.py | Updated as part of this pull request. |
| server/band-advisor/tests/test_parser.py | Updated as part of this pull request. |
| server/band-advisor/tests/test_input_builder.py | Updated as part of this pull request. |
| server/band-advisor/tests/test_geo.py | Updated as part of this pull request. |
| server/band-advisor/tests/test_ft8_profile.py | Updated as part of this pull request. |
| server/band-advisor/tests/test_caches.py | Updated as part of this pull request. |
| server/band-advisor/tests/test_baseline.py | Updated as part of this pull request. |
| server/band-advisor/tests/test_aggregator.py | Updated as part of this pull request. |
| server/band-advisor/tests/conftest.py | Updated as part of this pull request. |
| server/band-advisor/pyproject.toml | Updated as part of this pull request. |
| server/band-advisor/feature-config.example.json | Updated as part of this pull request. |
| server/band-advisor/Dockerfile | Updated as part of this pull request. |
| server/band-advisor/docker-compose.yml | Updated as part of this pull request. |
| server/band-advisor/app/voacap/service.py | Updated as part of this pull request. |
| server/band-advisor/app/voacap/runner.py | Updated as part of this pull request. |
| server/band-advisor/app/voacap/regions.py | Updated as part of this pull request. |
| server/band-advisor/app/voacap/parser.py | Updated as part of this pull request. |
| server/band-advisor/app/voacap/input_builder.py | Updated as part of this pull request. |
| server/band-advisor/app/voacap/ft8_profile.py | Updated as part of this pull request. |
| server/band-advisor/app/voacap/cache.py | Updated as part of this pull request. |
| server/band-advisor/app/voacap/__init__.py | Updated as part of this pull request. |
| server/band-advisor/app/ttl_cache.py | Updated as part of this pull request. |
| server/band-advisor/app/pskreporter/cache.py | Updated as part of this pull request. |
| server/band-advisor/app/pskreporter/baseline.py | Updated as part of this pull request. |
| server/band-advisor/app/pskreporter/aggregator.py | Updated as part of this pull request. |
| server/band-advisor/app/pskreporter/__init__.py | Updated as part of this pull request. |
| server/band-advisor/app/main.py | Updated as part of this pull request. |
| server/band-advisor/app/logging_config.py | Updated as part of this pull request. |
| server/band-advisor/app/geo.py | Updated as part of this pull request. |
| server/band-advisor/app/config.py | Updated as part of this pull request. |
| server/band-advisor/app/clock.py | Updated as part of this pull request. |
| server/band-advisor/app/bands.py | Updated as part of this pull request. |
| server/band-advisor/app/__init__.py | Updated as part of this pull request. |
| server/band-advisor/.gitignore | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/ui/settings/FeatureFlagScreenTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/ui/settings/BandAdvisorSettingsTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/ui/bandadvisor/PersonalPanelLoaderTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/ui/bandadvisor/BandAdvisorStateHolderTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/flags/SharedPrefsFlagStoresTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/flags/RemoteFlagConfigTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/flags/RemoteFlagConfigFetcherTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/flags/FeatureFlagRepositoryTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/bandadvisor/personal/PersonalAnalyticsStoreTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/bandadvisor/model/ValidationTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/bandadvisor/FixtureBandAdvisorRepositoryTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/bandadvisor/alerts/PropagationAlertSchedulerTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/bandadvisor/alerts/ConditionsClientTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/bandadvisor/alerts/AlertStoresTest.kt | Updated as part of this pull request. |
| ft8af/app/src/test/kotlin/radio/ks3ckc/ft8af/bandadvisor/alerts/AlertGridTest.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/settings/SettingsScreen.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/settings/FeatureFlagScreen.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/settings/AdvancedSettings.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/components/BandModeSheet.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/bandadvisor/PersonalPanelLoader.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/bandadvisor/BandAdvisorTuning.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ui/bandadvisor/BandAdvisorTelemetry.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/FT8AFApplication.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/FT8AFApp.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/flags/SharedPrefsFlagStores.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/flags/RemoteFlagConfigFetcher.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/flags/RemoteFlagConfig.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/flags/FeatureFlags.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/flags/FeatureFlagRepository.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/flags/FeatureFlag.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/ComposeMainActivity.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/bandadvisor/personal/PersonalAnalyticsStore.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/bandadvisor/model/Validation.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/bandadvisor/model/RecommendationParser.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/bandadvisor/model/Models.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/bandadvisor/BandAdvisorRepository.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/bandadvisor/BandAdvisor.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/bandadvisor/alerts/PropagationAlertScheduler.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/bandadvisor/alerts/PropagationAlertNotifier.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/bandadvisor/alerts/ConditionsClient.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/bandadvisor/alerts/AlertPreferences.kt | Updated as part of this pull request. |
| ft8af/app/src/main/kotlin/radio/ks3ckc/ft8af/bandadvisor/alerts/AlertHistoryStore.kt | Updated as part of this pull request. |
| ft8af/app/build.gradle | Updated as part of this pull request. |
Review effort: Balanced
| # voacapl ships an autotools build; makeitshfbc installs the itshfbc data | ||
| # directory (ionospheric coefficients, antenna files) into $HOME/itshfbc. | ||
| RUN ./configure && make && make install \ | ||
| && HOME=/build make makeitshfbc |
| RUN useradd --create-home --uid 10001 advisor | ||
|
|
||
| COPY --from=voacapl-build /usr/local/bin/voacapl /usr/local/bin/voacapl | ||
| COPY --from=voacapl-build /build/itshfbc /opt/itshfbc |
| HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ | ||
| CMD curl -fsS http://127.0.0.1:8000/healthz || exit 1 | ||
|
|
||
| CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] |
| handler = logging.StreamHandler(sys.stdout) | ||
| handler.setFormatter(JsonFormatter()) | ||
| root.handlers = [handler] | ||
| root.setLevel(level) |
|
|
||
| ANTENNA_CLASS_FILE: dict[AntennaClass, str] = { | ||
| AntennaClass.ISOTROPE: "default/isotrope", | ||
| AntennaClass.WIRE: "default/dipole.voa", |
| androidx.compose.runtime.key(flag, revision) { | ||
| FlagRow( | ||
| flag = flag, | ||
| onChanged = { revision++ }, |
| GRID6_RE = re.compile(r"^[A-R]{2}[0-9]{2}[A-X]{2}$") | ||
| FIELD_RE = re.compile(r"^[A-R]{2}$") | ||
|
|
||
| CALLSIGN_RE = re.compile(r"^[A-Z0-9]{1,3}[0-9][A-Z0-9]{0,3}[A-Z](/[A-Z0-9]{1,4})?$") |
Comment on lines
+29
to
+30
| field = normalize_grid4(observer_grid)[:2] | ||
| return (field, band or "ALL", mode.upper(), int(window_s)) |
Comment on lines
+113
to
+116
| remaining = float(item.get("ttl_remaining", 0)) | ||
| if remaining > 0: | ||
| self._entries[tuple(item["key"])] = ( | ||
| now + remaining, |
|
|
||
| val title = context.getString(R.string.prop_alerts_notif_opening_title, decision.band) | ||
| val regionText = decision.region ?: context.getString(R.string.band_advisor_goal_dx) | ||
| val time = SimpleDateFormat("HH:mm", Locale.US).format(Date(decision.conditionsAtMs)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What this is
First vertical slice of Band Advisor — "what band should I work right now?" — combining VOACAP predictions, live regional PSK Reporter observations, and (optionally) the operator's own reception reports into a deterministic, explainable recommendation. No LLM/generative AI anywhere in the runtime path.
Everything is behind feature flags and off by default in release builds; debug builds default on. Full architecture, contract, scoring model, privacy notes, and staged-rollout guide:
docs/band-advisor.md.Highlights
radio.ks3ckc.ft8af.flags):BAND_ADVISOR,PERSONAL_PSK_ANALYTICS,PROPAGATION_ALERTS. Precedence: debug override → fresh cached remote config (/v1/feature-config, validated, expiry-honoring, never blocks startup) → BuildConfig default. Debug-only tri-state override screen (Settings → Advanced) showing each flag's effective value and source. Explicitly documented as rollout controls, not access control.radio.ks3ckc.ft8af.bandadvisor): framework-free models for the versioned recommendation contract, validating parser, deterministic goal-weighted scorer (weights renormalize when inputs are missing) with a separate confidence function, API repository (60s cooldown, 429/503 back-off, stale/offline cache fallback), and a debug-only fixture repository that runs through the real scorer — fixtures cannot ship as live data.selectBandIndex(RigDialTarget protection, per-band level restore, CAT push only in CAT/RTS/DTR). Refuses dials not in the band plan; degrades to "Set radio to 14.074 MHz" without CAT. Never transmits, never band-hops.PskReporterClient(its 5-min etiquette applies), pure stats computation, small-sample suppression (≥10 reports + ≥3 stored sessions before any baseline comparison), explicit "reception reports are not contacts" copy, clearable local history./v1/conditionsendpoint; pure policy with evidence thresholds, 90-min opening identities persisted 48h for dedup, per-band (3h) + global (30 min) cooldowns, quiet hours with midnight wrap; own notification channel, Android 13 permission flow; tap opens the advisor detail. Disabling the flag or preference cancels scheduled work.server/band-advisor/): containerized FastAPI service wrappingvoacapl(external GPL tool, built from source in Docker, not linked/vendored) with a documented FT8 SNR profile (−21 dB @ 2500 Hz → 13 dB·Hz + 3 dB margin), centralized PSK Reporter aggregation (gzip, defusedxml, appcontact, band-hour baseline normalization so raw volume can't always crown 20m), spec'd cache keying, deterministic errors, structured logs.Safety
Failure of Band Advisor, VOACAP, PSK Reporter, remote config, or alerts cannot interfere with decode/TX/logging/USB/CAT: flag-off composes no UI and schedules no work; every network call is time-boxed and maps to a typed UI state or a skipped background round; the FT8 engine, audio, JNI, and rig-control code paths are untouched except for the two documented reuse points (
selectBandIndex, notification-intent handling).Tests & builds run
./gradlew testDebugUnitTest— 4165 tests, 0 failures (includes ~120 new tests: flag precedence/expiry/overrides, contract parsing, score normalization + goal weights + confidence, repository cooldown/back-off/offline fallback, fixture determinism, grid/callsign validation, personal-analytics math + suppression, alert thresholds/dedup/cooldowns/quiet-hours, WorkManager schedule/cancel via work-testing, tune-index lookup)./gradlew assembleDebug— success (native build included)./gradlew ktlintCheck— clean;detekt— no new findings (11 pre-existing baseline-adjacent findings remain in untouched files; detekt is warn-only in CI)server/band-advisor:pytest— 190 passed (no network; fixture voacapl output, mocked HTTP, fixed clock)Known gaps / follow-ups
/v1/conditionsis anonymous, so PERSONAL_IMPROVEMENT alerts never fire yet.🤖 Generated with Claude Code