Skip to content

Add APRS JSONL vector corpus and parametrized compliance tests - #4

Merged
afourney merged 15 commits into
scaffold/aprs-modulefrom
scaffold/aprs-vectors
Oct 17, 2026
Merged

afourney merged 15 commits into
scaffold/aprs-modulefrom
scaffold/aprs-vectors

Conversation

@afourney

Copy link
Copy Markdown
Owner

Stacked on #3

Base: scaffold/aprs-module at 16b711cdbb110f3079ea55cacac98db8feb383c6.
Head: scaffold/aprs-vectors. This PR does not change production codecs or modify/merge #3.

Change

Turn the Markdown corpus into executable, individually identified fixtures under packages/aprs/tests/vectors/: one JSONL file for each of the 18 sections, containing all 1,538 individual packets and the 23 packet occurrences in seven stateful sequences (all 1,545 original case IDs).

  • Preserve exact input bytes using JSON Latin-1 carriers, source IDs/profiles/options/notes, and complete original expectations. Validate source byte lengths and supplied hex. Inputs already include envelopes; no synthetic headers were added. Malformed and AEA inputs remain exact so their tests are not changed by normalization.
  • Derive desired public-JSON projections from documented values with an explicit generator that never calls the parser. Projections follow the source's subset-assertion contract; absent fields make no assertion and nulls do. FAP defaults are expanded. Unmapped source paths remain listed alongside their complete source values.
  • Parametrize every file/line with filename, line number and vector ID. Expected failures are tied to reviewed IDs and specific unsupported diagnostics or allowed field differences. Only the dedicated KnownGap exception can xfail; unrelated mismatches and crashes remain failures, and unexpected passes fail strictly.
  • Add provenance/schema/runner regression tests and CI testing both source and a freshly installed wheel. Add pytest as a development dependency; it is required to run this corpus (unittest alone still runs the pre-existing suite).

Explicit limits

This does not claim complete decoding coverage. The 23 state-sequence occurrences retain their state assertions with expected: null and an explicit unresolved reason: a stateless packet API cannot supply station state transitions. They are collected, checked for byte preservation, and visibly expected-failed, not silently skipped or invented as passing results. Other source-only/derived/policy and compatibility assertions remain under source_expected with unmapped paths where not projected. A passing projection does not claim those additional assertions have passed.

No individual packet entry is omitted. Planned codec gaps are not implemented by this PR.

Counts

Section Rows Pass Expected failure
HDR 51 51 0
POS 68 56 12
TIM 20 17 3
EXT 50 39 11
CMP 122 0 122
MIC 265 0 265
OBJ 53 0 53
WX 66 0 66
TLM 18 0 18
MSG 45 25 20
QRY 20 3 17
STA 29 8 21
NME 36 1 35
NET 15 14 1
OTH 38 24 14
BAD 125 66 59
SEQ 23 0 23
FAP 517 327 190
Total 1,561 631 930

Validation

On Python 3.12, the full suite passes from source and from a fresh wheel installed in a separate environment: 776 passed, 930 xfailed (plus 893 existing unittest subtest passes reported by pytest 9). Of the expected failures, 23 are the explicit unresolved state occurrences.

Ruff, Black, mypy (43 files), and the repository Black/Prettier pre-commit hooks pass. The new APRS vector workflow runs both source and installed-wheel tests. Detailed format, limitations, maintenance instructions and counts are in tests/vectors/README.md.

@afourney

Copy link
Copy Markdown
Owner Author

Corpus review: JSONL vs. APRS_COMPLIANCE_VECTORS.md, fixed in b97beb7

I compared the JSONL files against the markdown corpus with an independent script that doesn't reuse the generator's code. The structure was faithful, but several projected expectations could never be met by a correct decoder, and a few source fields were dropped. Those issues are fixed in b97beb7.

What already matched

  • All 1,545 markdown cases are present exactly once, in document order, in the right family file. Sequences expand into 23 rows, for 1,561 rows in total.
  • Titles, profiles, sources, outcomes, notes, packet bytes, byte lengths, hex and every source_expected match the markdown, including the FAP default fields filled in from the defaults block.
  • Rerunning the generator reproduces the files exactly, and the gap manifest IDs are unique and valid.

Expectations a correct decoder couldn't meet (fixed)

These all sat inside expected failures, so the suite was green and nothing would have flagged them until the matching decoders were written.

  1. Mic-E altitude encoding (15 rows). These projected "mic-e-base91", which isn't an allowed Altitude.encoding value. It now maps to mic_e, and the generator fails on any altitude encoding it doesn't recognize instead of passing it through.
  2. ?PING? query (QRY-014). This expected the query name "PING?", which no Query name can be. The mistake was in the markdown source, so the fix is there: the expected query type is now PING. The title and packet bytes are unchanged.
  3. Rain units (WX rows). Rain was projected in millimetres, a conversion, while every other reading stays in its wire unit (°F, mph, hPa, snow in inches). Rain now comes from the source's hundredths of an inch, as inches; the millimetre values stay unmapped. This also fixed a silent loss: WX-025 to WX-030 mark rain as unavailable but have no millimetre value, so those assertions had been dropped. They're now explicit nulls.
  4. DF bearing usability and accuracy (EXT-038 to EXT-047, EXT-049). These derive from a quality-code mapping that APRS101 and the original protocol notes disagree on, and that docs/UNRESOLVED_SPEC.md deliberately doesn't impose. They're no longer projected. The 11-case gap group is removed, and those rows now pass.

Also found and fixed

  • Storm kinds (WX-053 to WX-059) were projected as "hurricane" and similar names rather than the model's HC/TS/TD. They're now mapped explicitly, and the generator fails on any name it doesn't know.
  • Dropped fields: rows now carry scope_note (130 FAP rows) and extension_tags (34 rows) verbatim. The six ignored-csT compression rows (CMP-005 to CMP-010) now include ignored: true.

New guard

test_projection_keys_belong_to_public_json_models now also checks enum values wherever every non-null option of a field is an enum. Run against the previous files, it flags the Mic-E encoding, and it caught the storm kinds.

Results

  • Corpus: 642 passes and 919 expected failures, up from 631 and 930; the 11 new passes are the DF bearing rows.
  • The full suite passes on Python 3.11 and 3.15: 787 passed, 919 xfailed. mypy, ruff, black and prettier are clean.
  • The vectors README now has the updated counts table and explains the unit and enum choices.

Left for a decision

FAP compatibility rows record weather in FAP's own metric units (m/s, °C, mm) across all readings, not just rain. Converting only rain would be inconsistent, and FAP's rounded wind and temperature values can't be converted back exactly. Matching them would take a unit-converting adapter for the FAP compatibility profile. For now this is only noted in the vectors README.

Unrelated changes in this commit

b97beb7 also includes two README fixes carried over from the previous branch:

  • the APRS README now explains send-time extension rules: rounding, ConformanceError for values that can't be sent as given, CodecNotImplementedError for fields the format can't carry;
  • the root README's broken cookbook anchor is fixed.

@afourney
afourney merged commit d12b0cf into scaffold/aprs-module Oct 17, 2026
4 checks passed
@afourney
afourney deleted the scaffold/aprs-vectors branch October 19, 2026 03:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants