refactor(radar): typed status fields + soft-fail GenState parser (fixes #129)#130
Merged
Merged
Conversation
#129) Refactors radar.Data's discrete-value fields onto named string types per go-standards.md §4.1/§4.3, and replaces simplifyGeneratorState with a soft-failing ParseGeneratorState so a degraded NWS payload no longer aborts the whole 5-minute poll cycle (issue #129). ## What changed Typed enums for VCP, RadarMode, RadarStatus, OperabilityStatus, PowerSource, GeneratorState — each a named string type with a const block declaring known values, a String() method per §4.3, and a <Type>Unknown sentinel for missing/unparseable data. Underlying string type still accepts arbitrary NWS-supplied values so a surprise input is preserved verbatim for forensic logging. Parse<X> constructors per field: - ParseVCP / ParseRadarStatus / ParseOperabilityStatus / ParsePowerSource: empty input → <Type>Unknown, non-empty passes through verbatim. No error possible. - ParseGeneratorState: semantic token-set parser. `Generator On` in the pipe-separated input → GenStateOn; only `Utility PWR Available` → GenStateOff; everything else (including empty) → GenStateUnknown with ErrUnknownGeneratorState wrapping the raw input. FetchData updated to use errors.Is on the new ErrUnknownGeneratorState sentinel — mirrors the soft-fail shape established for ErrUnknownVCP in #121 (PR #123). Three of five aborted polls observed in v2.13.1 prod logs over 8 days were caused by simplifyGeneratorState returning ("", "unknown input") on degraded NWS payloads; this commit removes that abort path. Comparator (CompareData) now skips reporting a flip when the NEW value equals the field's Unknown sentinel. Caller in monitor.processStation only updates the cache when changed=true, so this leaves the cache holding the last known-good value during a degraded poll; recovery to the same value produces a real no-change. One-sided: Unknown→OK on recovery still fires, since that's genuine state-clarification. ## Tests - TestParseGeneratorState — table-driven, covers all four canonical inputs plus empty / novel / ambiguous fallthrough cases with errors.Is(err, ErrUnknownGeneratorState) assertions. - ExampleParseGeneratorState — §8.3 testable example, output-checked. - TestCompareData adds three sub-tests for the skip-Unknown behavior: "skips flip to Unknown on every field", "flip FROM Unknown is reported (recovery)", and "partial degradation: change on known field only". - TestGetMode / TestGetVCPInfo updated to use the new typed values. - benchmark_test.go [] string of VCP codes → []VCP. - MockDataFetcher default response uses the new constants. `go test ./...` green across all 9 packages; `go vet ./...` clean. ## Notes - Data.Name remains string (identifying metadata, not a status). - simplifyGeneratorState (lowercase, package-private) is removed in favor of ParseGeneratorState. No external callers used the old name; tests update in lockstep. - monitor.go needed no changes — Mode interpolation goes through %s (Stringer) and VCP equality is type-preserving. - Closes #129. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
🎉 This PR is included in version 2.14.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
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.
Summary
Fixes #129. Refactors
radar.Data's discrete-value fields onto named string types per go-standards.md §4.1/§4.3, and replacessimplifyGeneratorStatewith a soft-failingParseGeneratorStateso a degraded NWS payload no longer aborts the 5-minute poll cycle.Combines Option 5 (typed enums + comparator skip-Unknown) and Option E (symmetric refactor of all status fields) from the design discussion. Option 2's sentinel-error pattern is folded into
ParseGeneratorState, and Option 3's token-set parsing lives inside it so the soft-fail is rare in practice.Changes
Typed enums (§4.1 / §4.3)
Six named string types with const blocks,
Stringermethods, and<Type>Unknownsentinels:VCPVCPR12 / R31 / R35 / R112 / R212 / R215VCPUnknownRadarModeModeClearAir,ModePrecipitationModeUnknownRadarStatusStatusOperate / Standby / Test / OfflineOperateStatusUnknownOperabilityStatusOpStatusOnline / Maintenance / Offline / Standby / CoastingOpStatusUnknownPowerSourcePowerSourceUtility / GeneratorPowerSourceUnknownGeneratorStateGenStateOn / OffGenStateUnknownThe underlying string type still accepts arbitrary NWS-supplied values — a surprise input is preserved verbatim for forensic logging rather than squashed into the Unknown bucket. Const blocks document the value space we've actually observed in the wild (per the API survey, though that doc isn't committed in this branch).
Parse<X>constructorsParseVCP/ParseRadarStatus/ParseOperabilityStatus/ParsePowerSource— empty input →<Type>Unknown, non-empty passes through verbatim. No error possible.ParseGeneratorState— semantic token-set parser.Generator On∈ pipe-tokens →GenStateOn; onlyUtility PWR Available→GenStateOff; everything else (including empty) →GenStateUnknownplus an error wrappingErrUnknownGeneratorState.FetchDatasoft-fail (the actual #129 fix)Mirrors the
ErrUnknownVCPshape from #121 (PR #123). WhenParseGeneratorStatereportsErrUnknownGeneratorState,FetchDatalogs at WARN with the raw input + station ID and continues withGenState = GenStateUnknown. The previous implementation returned("", "unknown input")and the caller aborted the entire poll — three of five aborted polls observed in v2.13.1 production logs over 8 days hit that path.Comparator skip-Unknown
CompareDatanow skips reporting a flip when the new value equals the field's Unknown sentinel.monitor.processStationonly updates the cache whenchanged=true, so a degraded poll leaves the cache holding the last known-good value; recovery to the same value produces a true no-change. One-sided by design —Unknown→OKon recovery still fires (genuine state-clarification, not noise).Tests
TestParseGeneratorState— table-driven. Covers all four canonical raw strings, token-order robustness, and three fallthrough cases (empty, totally novel, ambiguous) witherrors.Is(err, ErrUnknownGeneratorState)assertions.ExampleParseGeneratorState— §8.3 testable example, output-checked.TestCompareDatagains three sub-tests for skip-Unknown:skips flip to Unknown on every field— full degraded payload, expect zero notificationsflip FROM Unknown is reported (recovery)— first-run-with-degraded-data recovery still firespartial degradation: change on known field only— VCP→Unknown alone with other fields stable produces no notificationTestGetMode/TestGetVCPInfoupdated to use the new typed values.MockDataFetcherdefault response uses the new constants.go test ./...green across all 9 packages;go vet ./...clean.Notes
Data.Nameremainsstring(identifying metadata, not a status).simplifyGeneratorState(lowercase, package-private) is removed in favor ofParseGeneratorState. No external callers used the old name.monitor.goneeded no changes —Modeinterpolation goes through%s(Stringer) andVCPequality is type-preserving.Test plan
go test ./...— all 9 packages passgo vet ./...cleanExampleParseGeneratorStateround-trips throughgo testselfhostedand confirm no spurious notifications on next NWS-degraded-payload event (validation pending real-world recurrence; this is the same failure mode that hit 5 times in 8 days on v2.13.1)Closes #129. Future symmetric extension targets —
alarmSummaryfrom #128, pluscontrolStatus,superResolutionStatus, and theperformance.properties.*encoder-light fields — can layer onto this same shape.🤖 Generated with Claude Code