E9 fix + E11: out-of-band QSOs are no longer dropped; disallowed modes flagged #12

Merged
hermes-agent merged 2 commits from feature/band-and-mode-flags into main 2026-10-28 20:30:20 +00:00
Owner

Two reglement checks the owner asked about after D merged, both built as advisory flags per the never-auto-reject rule.

The bug that was found on the way (E9)

The owner asked how wrong-band logging is handled. Answering that properly exposed a real defect: all three parsers (Cabrillo, EDI, ADIF) returned None for an unmappable band and ed/adif skipped the record, so a QSO outside the contest bands vanished at import. A 6cm/3cm EDI file (whose PBand the parser never knew) arrived as an empty log with no warning — silent loss, invisible to entrant and manager alike.

Fix, per the standing rules: the QSO is kept everywhere it can be seen and scores nothing.

  • ParsedQSO.band and qso.band are nullable (migration 0008).
  • Manager drill-in and the summary sheet show it as band -; view-layer banner mirrors the stored flag so preview and manager agree.
  • Scoring excludes it from QSO points and multipliers; cross-log pairing ignores it (pairing unknowns to unknowns manufactures findings).
  • The Entry carries one INELIGIBLE_BAND flag, not one per QSO — a 6cm file would otherwise flood the report with identical rows.
  • The EDI export files no band file for such a QSO: EDI cannot declare a PBand, and inventing one would corrupt the export.

E11: allowed modes

MODE_NOT_ALLOWED per offending QSO, advisory only, from the verified reglement fact ("Toegestane modes zijn FM, SSB en CW").

  • USB/LSB normalise to SSB — they are SSB's emission labels, and flagging them would accuse correct logs.
  • A blank EDI mode column is not a violation: the standard allows leaving it blank, and absence cannot violate.
  • Bug found on the way: the EDI parser stored the numeric mode code verbatim, so every legitimate FM log would have read as mode 6 and tripped the new check. Codes 1/2/6 now translate to SSB/CW/FM at parse time (IARU R1 Vienna 1998 4.12.2, the same table verified for the export). Unknown codes stay verbatim and are flagged — correct, since cross-mode (4) is not permitted by the reglement.

One catch a green suite could not see

SQLite models enums as text, so tests passed while the first real ingest with the new flag would have died on Postgres enum validation: invalid input value for enum flagtype: INELIGIBLE_BAND. Caught in live verification; migration 0009 adds both values via ALTER TYPE ... ADD VALUE in an autocommit block. The lesson is recorded in the postgres-alembic-migrations skill: growing a Python SAEnum is a schema change.

Verification

  • 296 tests (was 283): parsers keep out-of-band QSOs in all three formats, scoring excludes them, mode normalisation, blank-mode non-violation, EDI unknown-PBand keeps the whole log, stored band=None row persists, view banner mirrors the flag, EDI export skips ineligible bands, and the upload-path end-to-end (preview shows both flags, submit stores the QSO and the flag row).
  • Live against real Postgres (migration 0008 + 0009 applied, verified via alembic_version and pg_type): corpus 16 ingests as entry with 3 QSOs (1 band=None), score 4 (2 points × 2 multipliers), flag INELIGIBLE_BAND; corpus 17 ingests with two MODE_NOT_ALLOWED flags and scoring untouched; both new flag values round-trip through the enum.
  • Corpus gains 16-FLAG-ineligible-band.cbr, 17-FLAG-disallowed-mode.cbr with README outcomes.

Deliberately out of scope (waiting on the Contest Manager)

Whether "70cm and up" is one band or separate 23cm/13cm/6cm/3cm — until that lands, an above-440 MHz QSO is Band.HIGHER and eligible, which is exactly the behaviour the corpus tests.

Two reglement checks the owner asked about after D merged, both built as advisory flags per the never-auto-reject rule. ## The bug that was found on the way (E9) The owner asked how wrong-band logging is handled. Answering that properly exposed a real defect: all three parsers (Cabrillo, EDI, ADIF) returned None for an unmappable band and ed/adif skipped the record, so a QSO outside the contest bands vanished at import. A 6cm/3cm EDI file (whose `PBand` the parser never knew) arrived as an empty log with no warning — silent loss, invisible to entrant and manager alike. Fix, per the standing rules: the QSO is **kept** everywhere it can be seen and **scores nothing**. - `ParsedQSO.band` and `qso.band` are nullable (migration 0008). - Manager drill-in and the summary sheet show it as band `-`; view-layer banner mirrors the stored flag so preview and manager agree. - Scoring excludes it from QSO points and multipliers; cross-log pairing ignores it (pairing unknowns to unknowns manufactures findings). - The Entry carries **one** `INELIGIBLE_BAND` flag, not one per QSO — a 6cm file would otherwise flood the report with identical rows. - The EDI export files no band file for such a QSO: EDI cannot declare a `PBand`, and inventing one would corrupt the export. ## E11: allowed modes `MODE_NOT_ALLOWED` per offending QSO, advisory only, from the verified reglement fact ("Toegestane modes zijn FM, SSB en CW"). - USB/LSB normalise to SSB — they are SSB's emission labels, and flagging them would accuse correct logs. - A blank EDI mode column is not a violation: the standard allows leaving it blank, and absence cannot violate. - Bug found on the way: the EDI parser stored the numeric **mode code** verbatim, so every legitimate FM log would have read as mode `6` and tripped the new check. Codes 1/2/6 now translate to SSB/CW/FM at parse time (IARU R1 Vienna 1998 4.12.2, the same table verified for the export). Unknown codes stay verbatim and are flagged — correct, since cross-mode (`4`) is not permitted by the reglement. ## One catch a green suite could not see SQLite models enums as text, so tests passed while the first real ingest with the new flag would have died on Postgres enum validation: `invalid input value for enum flagtype: INELIGIBLE_BAND`. Caught in live verification; migration 0009 adds both values via `ALTER TYPE ... ADD VALUE` in an autocommit block. The lesson is recorded in the postgres-alembic-migrations skill: growing a Python SAEnum is a schema change. ## Verification - **296 tests** (was 283): parsers keep out-of-band QSOs in all three formats, scoring excludes them, mode normalisation, blank-mode non-violation, EDI unknown-PBand keeps the whole log, stored `band=None` row persists, view banner mirrors the flag, EDI export skips ineligible bands, and the upload-path end-to-end (preview shows both flags, submit stores the QSO and the flag row). - Live against real Postgres (migration 0008 + 0009 applied, verified via `alembic_version` and `pg_type`): corpus 16 ingests as entry with 3 QSOs (1 `band=None`), score 4 (2 points × 2 multipliers), flag `INELIGIBLE_BAND`; corpus 17 ingests with two `MODE_NOT_ALLOWED` flags and scoring untouched; both new flag values round-trip through the enum. - Corpus gains `16-FLAG-ineligible-band.cbr`, `17-FLAG-disallowed-mode.cbr` with README outcomes. ## Deliberately out of scope (waiting on the Contest Manager) Whether "70cm and up" is one band or separate 23cm/13cm/6cm/3cm — until that lands, an above-440 MHz QSO is `Band.HIGHER` and eligible, which is exactly the behaviour the corpus tests.
Found while answering the owner's question about wrong-band checks: all three
parsers returned None on an unmappable band and edi/adif skipped the record, so a
QSO outside the contest bands vanished from the import — a 6cm/3cm EDI file (whose
PBand the parser never knew) arrived as an empty log with no warning. Silent loss on
import violates the never-auto-reject rule's own spirit: the manager cannot
adjudicate a contact nobody can see.

E9 fix (out-of-band):
- ParsedQSO.band and qso.band are nullable (migration 0008 drops NOT NULL).
- The QSO is kept everywhere: manager drill-in and summary sheet show it as band '-',
  scoring excludes it from QSO points and multipliers, cross-log pairing ignores it,
  and the entry carries ONE INELIGIBLE_BAND flag (a 6cm file would otherwise flood
  the report with identical rows).
- EDI export files no band file for it — EDI cannot declare a PBand, and inventing
  one would corrupt the export.
- The view layer mirrors the flag in its banner so preview/manager agree with what
  was stored.

E11 (modes):
- MODE_NOT_ALLOWED per offending QSO; never a rejection. USB/LSB normalise to SSB,
  a blank EDI mode column is not a violation (the standard allows leaving it blank).
- Bug found on the way: the EDI parser stored the numeric mode code verbatim, so
  every legitimate FM log would now read as mode "6" and trip the check. Codes
  1/2/6 translate to SSB/CW/FM at parse time (IARU R1 Vienna 1998 4.12.2); unknown
  codes stay verbatim and get flagged, which is correct because cross-mode (4) is
  not permitted by the reglement.

Corpus gains 16-FLAG-ineligible-band.cbr and 17-FLAG-disallowed-mode.cbr with README
outcomes. Tests: 296 (was 283).
Caught in live verification, not by the suite: SQLite models enums as plain text,
so the two new FlagType values passed every test, but the first real ingest with an
INELIGIBLE_BAND flag would die on Postgres enum input validation. Migration 0009
adds both values with ALTER TYPE ... ADD VALUE inside an autocommit block (the
ALTER cannot run in a transactional DDL step); downgrade raises, because enum
values cannot be removed and older builds would then refuse the column.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
public/vrza-contestrobot!12
No description provided.