hytek-parser 2.4.0__tar.gz → 3.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/PKG-INFO +1 -1
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/_utils.py +9 -3
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/line_parsers/e_event_parsers.py +16 -5
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/line_parsers/f_relay_parsers.py +16 -5
- hytek_parser-3.0.1/hytek_parser/hy3/line_parsers/h_dq_parsers.py +75 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/schemas.py +2 -2
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/pyproject.toml +1 -1
- hytek_parser-2.4.0/hytek_parser/hy3/line_parsers/h_dq_parsers.py +0 -81
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/LICENSE.md +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/README.md +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/__init__.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/export_xls/__init__.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/export_xls/_utils.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/export_xls/schemas.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/__init__.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/_utils.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/enums.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/line_parsers/__init__.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/line_parsers/a_file_parsers.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/line_parsers/b_meet_parsers.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/line_parsers/c_team_parsers.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/line_parsers/d_swimmer_parsers.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/line_parsers/g_split_parsers.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3_parser.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hyv/__init__.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hyv/enums.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hyv/schemas.py +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/py.typed +0 -0
- {hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/types.py +0 -0
|
@@ -143,11 +143,17 @@ def int_or_none(value:str|None) -> int|None:
|
|
|
143
143
|
return int(value) if value and value.isdigit() else None
|
|
144
144
|
|
|
145
145
|
def date_or_none(value: str|None) -> date|None:
|
|
146
|
-
"""Safely return either a date from a numeric string value, or None
|
|
146
|
+
"""Safely return either a valid date from a numeric string value, or None
|
|
147
147
|
|
|
148
148
|
Args:
|
|
149
149
|
value (str|None): The value to cast to date
|
|
150
150
|
|
|
151
151
|
Returns:
|
|
152
|
-
date|None:
|
|
153
|
-
|
|
152
|
+
date|None: The parsed date, or None for blank, malformed, or invalid values"""
|
|
153
|
+
if not value or not value.isnumeric():
|
|
154
|
+
return None
|
|
155
|
+
try:
|
|
156
|
+
return datetime.strptime(value, "%m%d%Y").date()
|
|
157
|
+
except ValueError:
|
|
158
|
+
# Some Meet Manager exports contain numeric but impossible dates.
|
|
159
|
+
return None
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
from typing import Any
|
|
3
2
|
|
|
4
|
-
from hytek_parser._utils import
|
|
3
|
+
from hytek_parser._utils import (
|
|
4
|
+
date_or_none,
|
|
5
|
+
extract,
|
|
6
|
+
get_age_group,
|
|
7
|
+
safe_cast,
|
|
8
|
+
select_from_enum,
|
|
9
|
+
)
|
|
5
10
|
from hytek_parser.hy3._utils import parse_reaction_time, parse_time, parse_time_or_none
|
|
6
11
|
from hytek_parser.hy3.enums import (
|
|
7
12
|
Course,
|
|
@@ -12,6 +17,7 @@ from hytek_parser.hy3.enums import (
|
|
|
12
17
|
Stroke,
|
|
13
18
|
WithTimeTimeCode,
|
|
14
19
|
)
|
|
20
|
+
from hytek_parser.hy3.line_parsers.h_dq_parsers import LAST_DQ_SLOT_KEY
|
|
15
21
|
from hytek_parser.hy3.schemas import DisqualificationInfo, ParsedHytekFile
|
|
16
22
|
|
|
17
23
|
|
|
@@ -26,7 +32,7 @@ def e1_parser(
|
|
|
26
32
|
# Event info
|
|
27
33
|
event_gender = select_from_enum(Gender, extract(line, 14, 1))
|
|
28
34
|
event_gender_age = select_from_enum(GenderAge, extract(line, 15, 1))
|
|
29
|
-
distance = safe_cast(
|
|
35
|
+
distance = safe_cast(float, extract(line, 16, 6))
|
|
30
36
|
stroke = select_from_enum(Stroke, extract(line, 22, 1))
|
|
31
37
|
age_min, age_max = get_age_group(
|
|
32
38
|
age_min=safe_cast(int, extract(line, 23, 3)),
|
|
@@ -120,8 +126,7 @@ def e2_parser(
|
|
|
120
126
|
# col 83-87. Signed; sentinels are blank / 0.00 in any sign spelling.
|
|
121
127
|
reaction_time = parse_reaction_time(extract(line, 83, 5))
|
|
122
128
|
|
|
123
|
-
|
|
124
|
-
date_ = datetime.strptime(raw_date, "%m%d%Y").date() if raw_date else None
|
|
129
|
+
date_ = date_or_none(extract(line, 88, 8).strip())
|
|
125
130
|
|
|
126
131
|
# Get entry
|
|
127
132
|
event_num, event = file.meet.last_event
|
|
@@ -155,6 +160,12 @@ def e2_parser(
|
|
|
155
160
|
setattr(entry, f"{prefix}_reaction_time", reaction_time)
|
|
156
161
|
setattr(entry, f"{prefix}_alt_time_code", alt_time_code)
|
|
157
162
|
|
|
163
|
+
# Anchor the H1 reason / H2 detail lines that follow to this DQ slot (see
|
|
164
|
+
# h_dq_parsers). File position is the only signal that survives a same-code
|
|
165
|
+
# prelim+finals double-DQ; clear it on a non-DQ result so a later orphaned
|
|
166
|
+
# H1/H2 does not attach to a stale slot.
|
|
167
|
+
opts[LAST_DQ_SLOT_KEY] = f"{prefix}_dq_info" if dq_info is not None else None
|
|
168
|
+
|
|
158
169
|
event.last_entry = entry
|
|
159
170
|
file.meet.last_event = (event_num, event)
|
|
160
171
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
from typing import Any
|
|
3
2
|
|
|
4
|
-
from hytek_parser._utils import
|
|
3
|
+
from hytek_parser._utils import (
|
|
4
|
+
date_or_none,
|
|
5
|
+
extract,
|
|
6
|
+
get_age_group,
|
|
7
|
+
safe_cast,
|
|
8
|
+
select_from_enum,
|
|
9
|
+
)
|
|
5
10
|
from hytek_parser.hy3._utils import parse_reaction_time, parse_time, parse_time_or_none
|
|
6
11
|
from hytek_parser.hy3.enums import (
|
|
7
12
|
Course,
|
|
@@ -11,6 +16,7 @@ from hytek_parser.hy3.enums import (
|
|
|
11
16
|
Stroke,
|
|
12
17
|
WithTimeTimeCode,
|
|
13
18
|
)
|
|
19
|
+
from hytek_parser.hy3.line_parsers.h_dq_parsers import LAST_DQ_SLOT_KEY
|
|
14
20
|
from hytek_parser.hy3.schemas import (
|
|
15
21
|
DisqualificationInfo,
|
|
16
22
|
Gender,
|
|
@@ -30,7 +36,7 @@ def f1_parser(
|
|
|
30
36
|
# Get event info
|
|
31
37
|
event_gender = select_from_enum(Gender, extract(line, 14, 1))
|
|
32
38
|
event_gender_age = select_from_enum(GenderAge, extract(line, 15, 1))
|
|
33
|
-
distance = safe_cast(
|
|
39
|
+
distance = safe_cast(float, extract(line, 16, 6))
|
|
34
40
|
stroke = select_from_enum(Stroke, extract(line, 22, 1))
|
|
35
41
|
|
|
36
42
|
# Have to set these now since swimmer ages are not available yet
|
|
@@ -124,8 +130,7 @@ def f2_parser(
|
|
|
124
130
|
parse_reaction_time(extract(line, 83 + 5 * i, 5)) for i in range(4)
|
|
125
131
|
]
|
|
126
132
|
|
|
127
|
-
|
|
128
|
-
date_ = datetime.strptime(raw_date, "%m%d%Y").date() if raw_date else None
|
|
133
|
+
date_ = date_or_none(extract(line, 103, 8).strip())
|
|
129
134
|
|
|
130
135
|
# Get entry
|
|
131
136
|
event_num, event = file.meet.last_event
|
|
@@ -159,6 +164,12 @@ def f2_parser(
|
|
|
159
164
|
setattr(entry, f"{prefix}_reaction_times", reaction_times)
|
|
160
165
|
setattr(entry, f"{prefix}_alt_time_code", alt_time_code)
|
|
161
166
|
|
|
167
|
+
# Anchor the H1 reason / H2 detail lines that follow to this DQ slot (see
|
|
168
|
+
# h_dq_parsers). File position is the only signal that survives a same-code
|
|
169
|
+
# prelim+finals double-DQ; clear it on a non-DQ result so a later orphaned
|
|
170
|
+
# H1/H2 does not attach to a stale slot.
|
|
171
|
+
opts[LAST_DQ_SLOT_KEY] = f"{prefix}_dq_info" if dq_info is not None else None
|
|
172
|
+
|
|
162
173
|
event.last_entry = entry
|
|
163
174
|
file.meet.last_event = (event_num, event)
|
|
164
175
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from hytek_parser._utils import extract
|
|
4
|
+
from hytek_parser.hy3.schemas import ParsedHytekFile
|
|
5
|
+
|
|
6
|
+
# Key under which the E2/F2 result parser records the DQ slot it just populated,
|
|
7
|
+
# so the H1 reason line and H2 detail line that immediately follow attach to that
|
|
8
|
+
# same slot. A swim DQ'd in both prelims and finals resolves to ONE shared entry
|
|
9
|
+
# with two populated slots; the result line preceding each round's H1/H2 in file
|
|
10
|
+
# order is the authoritative anchor for which slot those detail lines belong to.
|
|
11
|
+
# This holds even when both rounds carry the SAME DQ code (e.g. a false start in
|
|
12
|
+
# both prelims and finals): matching on the code alone cannot tell the two slots
|
|
13
|
+
# apart, but file position always can.
|
|
14
|
+
LAST_DQ_SLOT_KEY = "_last_dq_slot"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def h1_parser(
|
|
18
|
+
line: str, file: ParsedHytekFile, opts: dict[str, Any]
|
|
19
|
+
) -> ParsedHytekFile:
|
|
20
|
+
"""Parse an H1 line: the DQ reason string, routed to its own DQ slot.
|
|
21
|
+
|
|
22
|
+
Attaches the reason to the slot the immediately-preceding E2/F2 result line
|
|
23
|
+
populated (recorded in ``opts`` under ``LAST_DQ_SLOT_KEY``). That result line
|
|
24
|
+
is the authoritative anchor: a swim DQ'd in both prelims and finals carries
|
|
25
|
+
two populated slots on one shared entry, and file position — not the DQ code —
|
|
26
|
+
tells the two H1s apart, including when both rounds share the same code.
|
|
27
|
+
|
|
28
|
+
No-op when no slot is anchored or the anchored slot is unpopulated — the DQ
|
|
29
|
+
status and code are recorded by the E2/F2 result line regardless, so only the
|
|
30
|
+
human-readable reason string is ever at stake. Leaves the anchor in place so
|
|
31
|
+
the H2 detail line that follows attaches to the same slot.
|
|
32
|
+
"""
|
|
33
|
+
event_num, event = file.meet.last_event
|
|
34
|
+
entry = event.last_entry
|
|
35
|
+
|
|
36
|
+
info_str = extract(line, 5, 124) # Whitespace is stripped
|
|
37
|
+
|
|
38
|
+
slot = opts.get(LAST_DQ_SLOT_KEY)
|
|
39
|
+
if slot is not None:
|
|
40
|
+
info = getattr(entry, slot)
|
|
41
|
+
if info is not None:
|
|
42
|
+
info.info_str = info_str
|
|
43
|
+
|
|
44
|
+
event.last_entry = entry
|
|
45
|
+
file.meet.last_event = (event_num, event)
|
|
46
|
+
|
|
47
|
+
return file
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def h2_parser(
|
|
51
|
+
line: str, file: ParsedHytekFile, opts: dict[str, Any]
|
|
52
|
+
) -> ParsedHytekFile:
|
|
53
|
+
"""Parse an H2 line: the specific, human-readable DQ infraction detail.
|
|
54
|
+
|
|
55
|
+
Attaches the detail to the same slot the preceding E2/F2 result line anchored
|
|
56
|
+
(``LAST_DQ_SLOT_KEY``) — the slot h1_parser also wrote to. H2 cannot
|
|
57
|
+
self-identify its slot: its own 2-char code is the stroke/leg infraction (e.g.
|
|
58
|
+
"2L"), not the slot's ``DisqualificationCode`` (e.g. the relay-leg code "6A"),
|
|
59
|
+
so the anchor is the only reliable signal. No-op when no slot is anchored or
|
|
60
|
+
the anchored slot is unpopulated.
|
|
61
|
+
"""
|
|
62
|
+
event_num, event = file.meet.last_event
|
|
63
|
+
entry = event.last_entry
|
|
64
|
+
|
|
65
|
+
detail = extract(line, 5, 124) or None
|
|
66
|
+
|
|
67
|
+
slot = opts.get(LAST_DQ_SLOT_KEY)
|
|
68
|
+
if slot is not None:
|
|
69
|
+
info = getattr(entry, slot)
|
|
70
|
+
if info is not None:
|
|
71
|
+
info.info_str_detail = detail
|
|
72
|
+
|
|
73
|
+
event.last_entry = entry
|
|
74
|
+
file.meet.last_event = (event_num, event)
|
|
75
|
+
return file
|
|
@@ -264,7 +264,7 @@ class Event:
|
|
|
264
264
|
|
|
265
265
|
# ID info
|
|
266
266
|
number: str
|
|
267
|
-
distance:
|
|
267
|
+
distance: float
|
|
268
268
|
stroke: Stroke
|
|
269
269
|
course: Course
|
|
270
270
|
date_: Optional[date]
|
|
@@ -432,7 +432,7 @@ class Meet:
|
|
|
432
432
|
def get_or_create_event(
|
|
433
433
|
self,
|
|
434
434
|
number: str,
|
|
435
|
-
distance:
|
|
435
|
+
distance: float,
|
|
436
436
|
stroke: Stroke,
|
|
437
437
|
course: Course,
|
|
438
438
|
gender: Gender,
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
from hytek_parser._utils import extract, select_from_enum
|
|
4
|
-
from hytek_parser.hy3.enums import DisqualificationCode
|
|
5
|
-
from hytek_parser.hy3.schemas import ParsedHytekFile
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def h1_parser(
|
|
9
|
-
line: str, file: ParsedHytekFile, opts: dict[str, Any]
|
|
10
|
-
) -> ParsedHytekFile:
|
|
11
|
-
"""Parse a G1 split time entry."""
|
|
12
|
-
# Get event
|
|
13
|
-
event_num, event = file.meet.last_event
|
|
14
|
-
entry = event.last_entry
|
|
15
|
-
|
|
16
|
-
# Get dq code and message
|
|
17
|
-
dq_code = select_from_enum(DisqualificationCode, extract(line, 3, 2))
|
|
18
|
-
dq_info = extract(line, 5, 124) # Whitespace is stripped
|
|
19
|
-
|
|
20
|
-
# No-op if the entry carries no DQ slot to attach to — mirrors h2_parser.
|
|
21
|
-
# An H1 can appear whose last_entry is not the DQ'd swim it describes (e.g. a
|
|
22
|
-
# relay DQ, or a non-DQ entry emitted between the DQ result and its H1); skip
|
|
23
|
-
# the detail rather than raising. The DQ result itself is unaffected, only the
|
|
24
|
-
# human-readable reason string is dropped.
|
|
25
|
-
if not (entry.prelim_dq_info or entry.swimoff_dq_info or entry.finals_dq_info):
|
|
26
|
-
return file
|
|
27
|
-
|
|
28
|
-
if entry.finals_dq_info:
|
|
29
|
-
# DQ happened in prelims
|
|
30
|
-
assert (
|
|
31
|
-
entry.finals_dq_info.code == dq_code
|
|
32
|
-
), "DQ Codes should match in the H1 line"
|
|
33
|
-
|
|
34
|
-
entry.finals_dq_info.info_str = dq_info
|
|
35
|
-
elif entry.swimoff_dq_info:
|
|
36
|
-
# DQ happened in prelims
|
|
37
|
-
assert (
|
|
38
|
-
entry.swimoff_dq_info.code == dq_code
|
|
39
|
-
), "DQ Codes should match in the H1 line"
|
|
40
|
-
|
|
41
|
-
entry.swimoff_dq_info.info_str = dq_info
|
|
42
|
-
elif entry.prelim_dq_info:
|
|
43
|
-
# DQ happened in prelims
|
|
44
|
-
assert (
|
|
45
|
-
entry.prelim_dq_info.code == dq_code
|
|
46
|
-
), "DQ Codes should match in the H1 line"
|
|
47
|
-
|
|
48
|
-
entry.prelim_dq_info.info_str = dq_info
|
|
49
|
-
|
|
50
|
-
event.last_entry = entry
|
|
51
|
-
file.meet.last_event = (event_num, event)
|
|
52
|
-
|
|
53
|
-
return file
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def h2_parser(
|
|
57
|
-
line: str, file: ParsedHytekFile, opts: dict[str, Any]
|
|
58
|
-
) -> ParsedHytekFile:
|
|
59
|
-
"""Parse an H2 line: the specific, human-readable DQ infraction detail.
|
|
60
|
-
|
|
61
|
-
Attaches the detail text to whichever DQ slot the preceding H1 populated
|
|
62
|
-
(finals -> swimoff -> prelim), mirroring h1_parser's resolution. Unlike H1,
|
|
63
|
-
the H2 2-char code is NOT asserted against the DQ code (it is a stroke/leg
|
|
64
|
-
sub-classification, not the same DisqualificationCode). No-op if no DQ slot
|
|
65
|
-
is set.
|
|
66
|
-
"""
|
|
67
|
-
event_num, event = file.meet.last_event
|
|
68
|
-
entry = event.last_entry
|
|
69
|
-
|
|
70
|
-
detail = extract(line, 5, 124) or None
|
|
71
|
-
|
|
72
|
-
if entry.finals_dq_info:
|
|
73
|
-
entry.finals_dq_info.info_str_detail = detail
|
|
74
|
-
elif entry.swimoff_dq_info:
|
|
75
|
-
entry.swimoff_dq_info.info_str_detail = detail
|
|
76
|
-
elif entry.prelim_dq_info:
|
|
77
|
-
entry.prelim_dq_info.info_str_detail = detail
|
|
78
|
-
|
|
79
|
-
event.last_entry = entry
|
|
80
|
-
file.meet.last_event = (event_num, event)
|
|
81
|
-
return file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{hytek_parser-2.4.0 → hytek_parser-3.0.1}/hytek_parser/hy3/line_parsers/d_swimmer_parsers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|