Skip to content

Repository files navigation

swim-meet-tools

Two small command-line tools for youth/summer swim league board members and volunteers who get a SwimTopia .hy3 meet-results file and need to:

  1. Import results into TeamUnify — but TeamUnify only wants your own team's results, in .sd3 (SDIF v3) format, and its importer has a few undocumented ways to silently drop data. hy3_to_sd3.py converts and automatically verifies every field against the source before telling you it's safe to upload.
  2. Print/share a results reportmake_meet_report.py generates a dense, multi-column, Meet-Manager-style PDF covering both teams, ranked fastest-to-slowest per event, with optional classification against your league's own time standards (Gold/Silver/Bronze, B/BB/A/AA, or whatever tiers you use).

Built out of a real dual-meet results wrangling session — see "Why this exists" below for the specific TeamUnify import bugs this works around.

Install

git clone https://github.com/hbentel/swim-meet-tools.git
cd swim-meet-tools
python3 -m venv venv
source venv/bin/activate      # Windows: venv\Scripts\activate

# hytek_parser's own metadata claims attrs<22, which is stale in practice --
# everything works fine with a modern attrs (needed by the `sdif` library).
# Installing in two steps avoids pip's resolver treating that as a hard
# conflict; see "Known issues" below.
pip install hytek_parser sdif reportlab
pip install -U "attrs>=23.1.0"
pip install -e . --no-deps

Windows, no command line needed: see windows/ — double-click setup_windows.bat once, then drag your .hy3 file onto Convert to SD3.bat or Generate PDF Report.bat.

Usage

Convert to SD3 for TeamUnify

hy3-to-sd3 "Results 2026-07-08 Home vs Away.hy3" ABC XY

Args: the .hy3 file, your team code as it appears in the file, your league/LSC code (combined as e.g. XYABC — whatever TeamUnify told you it expects). Writes <input>.sd3 and <input>.log next to the input by default; override with -o/--log.

This re-parses the .sd3 it just wrote and cross-checks every name, time, date, and place against the source .hy3. It exits non-zero and prints VERIFICATION FAILED with the specific mismatches if anything doesn't match — don't upload the file in that case. On success it prints VERIFICATION PASSED.

Generate the results PDF

make-meet-report "Results 2026-07-08 Home vs Away.hy3" --standards examples/ompa_2026_scy_standards.json

Writes <input>_Results.pdf by default (override with -o), covering both teams, one ranked table per event (no heat breakdown), DQ/NS/scratch results listed at the bottom. --standards is optional — omit it to skip the classification column entirely, or point it at your own league's time-standards file (see below).

Time standards format

--standards takes a JSON file describing your league's cutoff times per stroke/sex/age-bracket/tier. examples/ompa_2026_scy_standards.json is a real one (Orinda Moraga Pools, 2026 SCY season) to use as a template:

{
  "name": "Your League 2026 SCY Time Standards",
  "tiers": ["gold", "silver", "bronze"],
  "standards": {
    "FREESTYLE": {
      "F": { "11-12": { "gold": "29.49", "silver": "33.00", "bronze": "36.00" } },
      "M": { "11-12": { "gold": "29.21", "silver": "32.50", "bronze": "35.50" } }
    },
    "BACKSTROKE": { "...": "..." },
    "BREASTSTROKE": { "...": "..." },
    "BUTTERFLY": { "...": "..." },
    "MEDLEY": { "...": "..." },
    "FREE_RELAY": { "...": "..." },
    "MEDLEY_RELAY": { "...": "..." }
  }
}
  • tiers is fastest-to-slowest, any names, any number of tiers.
  • Age brackets are "min-max" strings (use "0-6" for 6-and-under).
  • Any tier can be omitted/null for a cell your league doesn't publish (e.g. many leagues only publish one tier for relays).
  • MEDLEY is individual IM; relays use their own FREE_RELAY/MEDLEY_RELAY keys since they're often only published at one tier.

Why this exists

TeamUnify's SD3 importer has a few behaviors that aren't obvious from the outside — each was found by importing a real file, watching results silently not show up, and digging until the cause was clear. hy3_to_sd3.py handles all of these automatically:

  • .hy3 encoding: SwimTopia's export is sometimes CP1252 (accented names etc.), which hard-fails a plain UTF-8 decode. Detected and re-decoded automatically.
  • Missing C2 record: individual (non-relay) results need a C2 "team entry" record alongside C1, or they silently fail to attach to the team — while relay results (which don't depend on C2) import fine. This exact asymmetry (relays work, individuals don't, no error) is what originally pointed at the cause.
  • Blank swimmer ID: TeamUnify matches results to athlete profiles using a 14-character ID derived from birthdate + name (present in the .hy3 as usa_swimming_id). Leaving it blank means the result silently doesn't attach. Carried through via the D3/F0 records, truncated to fit the ID field on D0/F0 itself as SwimTopia's own export does.
  • Zero-padding bug in a third-party library: the sdif package's generic field encoder strips leading zeros from purely-numeric "alpha" fields, silently corrupting the youngest age brackets' event_age field ("0006""6"). Patched by post-processing the encoded output.
  • \r\r\n line endings: passing newline="\r\n" to Python's open() when the string being written already contains \r\n separators causes double-translation. Harmless to most parsers but non-compliant; fixed by disabling translation and writing the pre-formatted separators verbatim.

All of the above were confirmed by decoding a real SwimTopia-generated .sd3 file (from the same meet) and cross-checking every field — see tests/ for the regression tests that came out of that.

Known limitations

  • Scored points aren't carried through. Real HY3 files include per-swim scoring points, but the parsing library this tool uses (hytek_parser) doesn't expose that field. Not needed for a normal TeamUnify import (which just needs times/places), but if you need team-scoring data out of this pipeline, that field isn't there yet.
  • hytek_parser's metadata claims attrs<22, which conflicts with the modern attrs that sdif needs. Both work fine together in practice (verified extensively) — the two-step install above works around pip's resolver rejecting the combination outright. If pip install -e . fails with a resolver error, use the two-step install instead.

Development

pip install -r requirements-dev.txt
pip install -e . --no-deps
pytest tests/ -v

tests/fixtures/sample_meet.hy3 is a small, fully synthetic two-team meet (no real swimmer data) generated by tests/fixtures/generate_sample_meet.py — regenerate it with python3 tests/fixtures/generate_sample_meet.py if you need to extend it.

Contributing

Issues and PRs welcome. If you hit a new TeamUnify import quirk, the most useful contribution is: the specific symptom, the relevant lines from the .log file hy3_to_sd3.py produces, and (if you can get one) a comparison against a results file from the host's own software — that combination is what found every bug fixed so far.

License

MIT — see LICENSE.

About

Convert Hy-Tek .hy3 meet results to SD3 for TeamUnify, and generate a printable results PDF, with optional time-standards classification.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages