Aqua Points Calculator
World Aquatics point scores for swimming performances. Usable as a Python library, as a CLI, and as a FastAPI service whose OpenAPI document exposes the same schema to services in other languages.
One formula, P = 1000 · (B / T)³, computed in integer arithmetic and truncated the way the published tables read — plus its exact inverse, for turning a point requirement into a qualifying time.
Ships the official World Aquatics base times for the last five seasons of each course, so you can score a swim by naming its event — or pass your own reference time and skip the tables entirely.
Tech Stack
Two Ways To A Base Time
Scoring needs a reference. Name an event and the base time comes from the tables this package ships:
from aqua_points_calculator import Course, Event, Gender, Stroke, score_for
event = Event(distance=100, stroke=Stroke.FREESTYLE, course=Course.LONG, gender=Gender.MALE)
score_for(event, "51.35").points # 737, against the 2026 long-course table
score_for(event, "51.35", 2022).points # 762, against the 2022 one
Or pass a base time of your own and no table is touched at all — for a national record, an age-group standard, or a season older than the five shipped:
from aqua_points_calculator import score
score("46.40", "51.35").points # 737
The two are separate functions rather than one with an either/or argument, so neither has a combination of inputs that makes no sense.
Five seasons ship per course, each the official World Aquatics table for that season, with the document it came from recorded alongside it. Long and short course run on different calendars and are numbered independently. See Base times.
Quick Start
Prerequisites
| Tool | Version | Needed for |
|---|---|---|
| uv | — | dependencies, every make target |
| Python | 3.12 | fetched by uv from .python-version |
| Docker + Compose | — | make up, make image |
| Node (npx) | ≥ 18 | Prettier in make format, fetched on demand |
| pre-commit | — | the formatting hook before a commit (optional) |
Install
As a dependency of another project:
pip install aqua-points-calculator # library + CLI
pip install "aqua-points-calculator[api]" # plus the FastAPI service
uv add aqua-points-calculator
Every GitHub Release is published to PyPI automatically, so the version there tracks the tags in this repository.
To work on the calculator itself:
make install # uv sync --extra api
The api extra adds FastAPI and uvicorn. The calculator itself needs only Pydantic, so a library-only consumer can leave the extra out.
Run
make dev # CLI help with every subcommand
make serve # FastAPI on :8000 with reload
make up # the same in Docker
make ps # container status and health
make logs # follow the logs
make down # stop and remove
Use it
from aqua_points_calculator import Course, Event, Gender, Stroke, points, score, score_for
# base time from the shipped tables
event = Event(distance=100, stroke=Stroke.FREESTYLE, course=Course.LONG, gender=Gender.MALE)
score_for(event, "51.35").points # 737
score_for(event, "51.35", 2022).points # 762, against the 2022 table
# base time of your own
points("46.40", "51.35") # 737
result = score("46.40", "51.35")
print(result.points, result.time, result.base_time)
Times go in either as they are written or as raw milliseconds, and both forms come back out:
score(46_400, 51_350).time # '51.35'
score("1:02,34", "1:05.10").points # comma or dot, minutes optional
# score a swim — base time supplied, or looked up from the tables
aqua-points-calculator points 51.35 --base-time 46.40
aqua-points-calculator points 51.35 --distance 100 --stroke freestyle --gender male
aqua-points-calculator points 51.35 --distance 100 --stroke freestyle --gender male --season 2022
# the inverse, the same two ways
aqua-points-calculator time 800 --distance 100 --stroke freestyle --gender male
# the tables themselves
aqua-points-calculator seasons
aqua-points-calculator base-times --course short --season 2025
aqua-points-calculator convert 1:02,34 # 62340
HTTP Surface
| Method | Path | Purpose |
|---|---|---|
POST |
/points |
Score a swim |
POST |
/time |
The slowest time still worth a given score |
GET |
/seasons |
Every base-time table the service ships |
GET |
/base-times/{course}/{season} |
One whole table |
GET |
/health |
Liveness and the running version |
GET |
/openapi.json |
The schema, for generated clients |
/points and /time take the same either/or as the library — a base_time, or an event to look one up for:
curl -X POST http://localhost:8000/points \
-H 'content-type: application/json' \
-d '{"base_time": "46.40", "time": "51.35"}'
curl -X POST http://localhost:8000/points \
-H 'content-type: application/json' \
-d '{"event": {"distance": 100, "stroke": "freestyle", "course": "long", "gender": "male"}, "time": "51.35"}'
Unlike a file parser, there is no partial success here: a time either reads or it does not, so an unreadable one comes back as 422 rather than as a diagnostic. The document schema in /openapi.json is the same Pydantic model the library returns. See the HTTP API.
Configuration
| Variable | Default | Effect |
|---|---|---|
LOG_LEVEL |
INFO |
Log level |
There is nothing else. The service holds no state and can be mounted into another app with app.include_router(aqua_points_calculator.api.router).
Design
Times are integer milliseconds everywhere inside the package, never floats. The formula cubes a ratio of two times, so a binary float would make the last digit of a score depend on how the input happened to round; milliseconds are also what result files and timing systems actually carry, so no precision is invented on the way in.
flowchart LR
A["'51.35' · 51350"] -->|core/times| B["millis"]
E["event + season"] -->|data/tables| R["base time"]
X["your own base time"] --> R
R -->|core/times| B
B -->|core/points| C["P = 1000·(B/T)³"]
C --> D["Score"]
D --> S(["library · CLI · HTTP"])
C -.->|exact inverse| F(["qualifying time"])
More in the architecture notes.
Common Commands
make help # every target
make test # unit tests, offline, no coverage gate
make test-it # full run incl. integration and coverage
make lint # ruff check
make format # ruff (Python) + prettier (md/yaml/json)
make format-check # the same, read-only
make typecheck # mypy
make audit # pip-audit
make image # build the runtime image
make clean # remove caches and the venv
Further Reading
- Getting Started — install, score a swim, run the service
- Base times — the shipped tables, the two calendars, where the numbers come from
- Architecture — the pipeline and why times are integers
- Scoring — the formula, the truncation, the inverse
- Data schema — how a score is laid out
- HTTP API — endpoints, payloads, client generation
- CLI — subcommands, flags, exit codes
- Extending — wiring in a base-time table
License
MIT — use it, change it, ship it, commercially or not.
Metadata
Release files for aqua-points-calculator 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| aqua_points_calculator-1.0.0.tar.gz | 55.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aqua_points_calculator-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 99.2 kB
Release files / aqua_points_calculator-1.0.0.tar.gz
| Download URL | aqua_points_calculator-1.0.0.tar.gz |
|---|---|
| Size | 55.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
63dd4098f5ad4faa691dd0ed3a8c99475d489ce10e966318957b9852ffeb9010
|
|
BLAKE2b-256 checksum How to use checksums |
2f0829ef6c121632ce86637cea44116dc3d2f7f35717ab88fc9695474492008c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 30, 2026.
Transparency logRelease files / aqua_points_calculator-1.0.0-py3-none-any.whl
| Download URL | aqua_points_calculator-1.0.0-py3-none-any.whl |
|---|---|
| Size | 43.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2203b7c42733f7f0e3c77266f06544064fde4eb6018086d4de03d8fee6fc7063
|
|
BLAKE2b-256 checksum How to use checksums |
d07d72e6968e058077dd4678bd09f783234295a965e313988108f581547c9902
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 30, 2026.
Transparency log