Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions hytek_parser/hy3/line_parsers/d_swimmer_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ def d1_parser(
# unparsed_d1_col_125: col 125 (1 char). Observed: 'N' or blank; semantics unverified.
swimmer.citizenship = extract(line, 113, 3) or None
swimmer.unparsed_d1_col_125 = extract(line, 125, 1) or None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some digging on this, every file I have has it set to N.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I couldn't find figure out what is the N in some meets.

# unparsed_d1_col_100: cols 100-101 (2 chars). "Fr"/"So"/"Jr"/"Sr" school
# class in HS-meet exports; other data / blank in club exports.
swimmer.unparsed_d1_col_100 = extract(line, 100, 2) or None

@egelja egelja Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just verified. This field is called "Class Year" in the Meet Manager GUI.

Suggested change
swimmer.unparsed_d1_col_100 = extract(line, 100, 2) or None
swimmer.class_year = extract(line, 100, 2) or None

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I thought in naming it that, but it is not a 100% class_year, for non HS meets it shows other data. We can keep it class_year if you prefer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the breakdown — D1 cols 100–101 across my hy3 files (~9.5M records):

Bucket Count What it is
(blank) 8,951,793 The vast majority — club/age-group meets don't populate it
class Fr/So/Jr/Sr 321,769 The HS school class (what we capture)
numeric 9–12 144,954 Ambiguous — grade in numeric-HS meets, but mostly 9–12-year-old ages in age-group meets
numeric other 63,636 Swimmer age5,6,7,8, teens 13–18, etc.
numeric 20–30 13,685 Graduation year (26, 25, 21 = class-of-YYYY)
other alpha/mixed 28,165 misc codes (MS, WC, Mi, F2, bare A/B)


swimmer.team_code = team_code

Expand Down
3 changes: 3 additions & 0 deletions hytek_parser/hy3/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class Swimmer:
citizenship: Optional[str] = None
# col 125; semantics unverified — name intentionally non-descriptive
unparsed_d1_col_125: Optional[str] = None
# cols 100-101; "Fr"/"So"/"Jr"/"Sr" school class in HS-meet exports, other
# data in club exports — name intentionally non-descriptive (consumer interprets)
unparsed_d1_col_100: Optional[str] = None


@define
Expand Down
12 changes: 11 additions & 1 deletion tests/hy3/line_parsers/test_d_swimmer_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_d1_parser_no_dob(self) -> None:
self.assertIsNone(swimmer.team_id)

class TestD1NewFields(unittest.TestCase):
"""capture D1 citizenship (cols 113-115) and unparsed col 125."""
"""capture D1 citizenship (cols 113-115), unparsed col 125, and unparsed cols 100-101."""

def _file_with_team(self):
opts = {"default_country": "USA"}
Expand Down Expand Up @@ -67,6 +67,16 @@ def test_d1_blank_citizenship_and_col_125(self):
swimmer = file.meet.swimmers[42]
self.assertIsNone(swimmer.citizenship)
self.assertIsNone(swimmer.unparsed_d1_col_125)
self.assertIsNone(swimmer.unparsed_d1_col_100)

def test_d1_school_class_col_100(self):
file, opts = self._file_with_team()
base = "D1M 42Hayon Gabrielle Gabby B 123 11202001 9 99"
# Place a school-class token at cols 100-101 (1-indexed) via slicing.
d1 = base[:99] + "Sr" + base[101:]
self.assertEqual(130, len(d1))
file = d1_parser(d1, file, opts)
self.assertEqual("Sr", file.meet.swimmers[42].unparsed_d1_col_100)


if __name__ == "__main__":
Expand Down
1 change: 1 addition & 0 deletions tests/hy3/line_parsers/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def _swimmer(meet_id: int, last_name: str = "Smith") -> Swimmer:
s.age = 12
s.citizenship = None
s.unparsed_d1_col_125 = None
s.unparsed_d1_col_100 = None
return s


Expand Down
13 changes: 13 additions & 0 deletions tests/hy3/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,19 @@ def test_bug2b_relay_entries_with_alternates_preserve_leg_numbers(self) -> None:
f"no alternate legs found: {sorted(entry.swimmers.keys())}",
)

def test_d1_school_class_captured_cols_100_101(self) -> None:
# D1 cols 100-101 carry the school class (Fr/So/Jr/Sr) in high-school
# exports; surfaced via the neutrally-named unparsed_d1_col_100.
classes = {
(s.unparsed_d1_col_100 or "").strip().title()
for t in self.parsed.meet.teams.values()
for s in t.swimmers.values()
}
self.assertTrue(
classes & {"Fr", "So", "Jr", "Sr"},
f"expected at least one Fr/So/Jr/Sr school class, got {sorted(classes)}",
)


def _slot_field(entry, slot: str, field: str):
"""Return ``{slot}_{field}`` from an entry object."""
Expand Down