Skip to content

fix(hy3): route H1/H2 DQ detail to the matching slot for a prelim+finals double-DQ - #30

Open
fsalum wants to merge 1 commit into
SwimComm:masterfrom
fsalum:fix/h1-double-dq-slot-match
Open

fix(hy3): route H1/H2 DQ detail to the matching slot for a prelim+finals double-DQ#30
fsalum wants to merge 1 commit into
SwimComm:masterfrom
fsalum:fix/h1-double-dq-slot-match

Conversation

@fsalum

@fsalum fsalum commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Stacked on #29 ("type Event.distance as float for non-integer distances").
This branch is based on #29's branch, so #29 needs to merge first. Until it
does, this PR shows #29's commit alongside its own; after #29 lands it collapses
to the single DQ-parser commit (h_dq_parsers.py + its test).

Problem

h1_parser raises AssertionError("DQ Codes should match in the H1 line") on
any swim disqualified in both prelims and finals of the same event for
different infractions — a common case in IM events and championship
prelims/finals formats.

Such a swim's prelim and finals arrive as separate E1/E2 records that resolve
to one shared EventEntry carrying both prelim_dq_info and finals_dq_info.
h1_parser resolves the H1 detail line to a slot by fixed
finals -> swimoff -> prelim priority, takes the first populated slot, and
then asserts that slot's stored DQ code equals the H1's own code. Records arrive
finals-first, so by the time the prelim's H1 is parsed the finals slot is
already populated with a different code:

E2F …Q7T   -> finals slot code = 7T
H17T…      -> finals slot set, 7T == 7T ✓  (attaches to finals)
E2P …Q1M   -> prelim slot code = 1M
H11M…      -> resolves finals-first, takes finals (7T); 7T != 1M -> AssertionError

The assertion aborts the H1 line and the prelim DQ's human-readable reason
string is lost.

Two related latent defects sit behind the visible assertion:

  1. When the two slots happen to share a DQ code the assertion does not fire,
    but finals-first resolution still attaches the reason to the wrong slot (both
    H1s land on finals).
  2. h2_parser uses the identical finals-first resolution with no assertion at
    all, so H2 detail text is silently mis-attributed in the same double-DQ case.

Fix

h1_parser now resolves to the slot whose stored DQ code matches the H1's
own code, preferring a slot not yet filled so the two H1s of a same-code
double-DQ fill their own slots in file order. When no populated, unfilled slot's
code matches, it degrades to a no-op — the same graceful behavior the existing
no-slot guard already used (the DQ status and code are recorded by the E2/F2
result line regardless, so only the reason string is ever at stake). The
assert is removed.

h2_parser cannot match on its own code — an H2's 2-char code is the specific
stroke/leg infraction (e.g. 2L), not the slot's DisqualificationCode (e.g.
the relay-leg code 6A). So h1_parser records the slot it resolved (in the
opts dict threaded through every line parser) and h2_parser attaches its
detail to that same slot, falling back to the first populated slot when no H1
preceded it.

Tests

Added to tests/hy3/line_parsers/test_h_dq_parsers.py:

  • prelim/finals H1s of a different-code double-DQ each route to their own slot
    (previously raised);
  • same-code double-DQ fills each slot in file order rather than finals twice;
  • an H1 whose code matches no populated slot is a no-op, not a raise;
  • a relay leg code (6A) still attaches;
  • an H2's detail follows its immediately-preceding H1's slot across a double-DQ.

All existing DQ-parser tests continue to pass.

@egelja

egelja commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Ooh this is a good catch! Thanks!

This is a good fix, the only thing I worry about is what happens when someone say, false starts, in both prelims and finals. That would give us the same code for both.

What if instead we used the opts array to save the location of the last E2 line. Would that work?

A swim disqualified in both prelims and finals of the same event resolves
to a single shared entry carrying both prelim_dq_info and finals_dq_info.
h1_parser resolved the detail line to a slot by fixed finals -> swimoff ->
prelim priority and asserted the chosen slot's stored code equalled the
H1's code. Records arrive finals-first, so a prelim H1 was checked against
the already-populated finals slot: when the two infractions differ (common
in IM and championship formats) the codes mismatched and h1_parser raised
AssertionError("DQ Codes should match in the H1 line"), aborting the reason
string. h2_parser used the identical priority resolution with no assertion,
so H2 detail was silently mis-attributed in the same case.

Resolve instead by file position. The E2/F2 result line that populates a DQ
slot immediately precedes that round's H1/H2 detail lines, so it is the
authoritative anchor: e2_parser and f2_parser record the slot they populate
under opts[_last_dq_slot] (and clear it on a non-DQ result), and h1_parser
and h2_parser both attach to that slot. This is robust even when both rounds
share the same DQ code — e.g. a false start in both prelims and finals —
which matching on the code alone cannot disambiguate. The assert is removed;
h1/h2 no-op when no slot is anchored, the same graceful degrade the no-slot
case already used (the DQ status and code are recorded by the result line
regardless, so only the human-readable reason string is ever at stake).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fsalum
fsalum force-pushed the fix/h1-double-dq-slot-match branch from f69e4b6 to f01ad46 Compare August 24, 2026 17:15
@fsalum

fsalum commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Great suggestion, I committed the changes.

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