Skip to main content

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

Python Pydantic FastAPI Uvicorn OpenAPI uv Hatch Ruff Prettier mypy pytest pytest-cov pip-audit pre-commit GNU Make Docker GitHub Actions


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)

Source distribution for aqua-points-calculator 1.0.0
File Size Uploaded
aqua_points_calculator-1.0.0.tar.gz 55.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aqua-points-calculator 1.0.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page