Skip to content

Case study · DLRG Melle · 2026

Tournament software for a 24-hour charity swim

A staff app and public lookup kiosk for a volunteer lifesaving club. It registers 300–500 swimmers, records every distance they swim over 24 continuous hours, and produces the official rankings and award lists under the 2027 competition rules.

Live ranking screen: a table of swimmers ordered by distance, led by start number 289 with 43,050 m, with tabs for age groups, night owls and group standings.
The live ranking, 16 hours into the demo event. It refreshes on its own while helpers record new distances, with tabs for age groups, gender, night owls and club standings. The UI is German because its users are.

The constraints

What I didn't get to choose

Engineering

Four decisions, and what each one prevents

  1. 1.The booking ledger is append-only.

    A booking row is never updated or deleted. A correction is a new row that points at the booking it replaces, and a unique index allows each booking to be corrected only once, so every history is a single chain.

    Prevents: Arguments at the award ceremony about who changed a distance, and totals that silently count a corrected card twice. The trail the club asked for comes free, without a separate audit table.

    Booking form on a phone: count card 4711, start number 289, 130 m, with a yellow warning that 130 m is not a multiple of 50 m.
    Recording a count card. 130 m is not a multiple of the 50 m pool, so the app asks before it saves. It's a warning, not a block, because the helper is holding the paper card.
  2. 2.“Exactly one active event” is enforced by the database.

    A partial unique index on the event table, UNIQUE (status) WHERE status = 'active', instead of a check in application code.

    Prevents: Two admins on two laptops starting different events in the same second. The second write fails at the database, whatever the code above it gets wrong.

  3. 3.Start numbers are assigned atomically.

    Several helpers register walk-ins at the same time. Each registration increments a per-event counter and creates the participant in one transaction. The row lock on the counter serialises concurrent sign-ups, and a unique (event, start number) constraint backs it up.

    Prevents: The classic read-max-then-insert race, where two swimmers leave the desk with the same number printed on their card.

  4. 4.Privacy is one shared filter, not per-screen logic.

    A participant who objects to publication is redacted in the ranking queries themselves. The live rankings, the public lookup kiosk and every PDF list read from those queries, and the unauthenticated kiosk returns only the fields it displays.

    Prevents: Next year's new export quietly printing an objector's name because nobody remembered to add the check.

    Public lookup on a phone: greeting for start number 289, total distance 43,050 m split into 26,850 m day and 16,200 m night, and a gold medal notice.
    The public kiosk: swimmers type their start number and see their total, day/night split and current medal. It shows a first name only, and none at all for objectors.
First page of the award ceremony list PDF: places one to three per age group and gender, with names and distances.
The award ceremony list: a generated PDF that goes from the ranking to the podium without anyone retyping it. Generated from the demo's fictional data.

Try it yourself

The demo is the real app with fictional swimmers. The logins are printed on its sign-in page, you can click everything, and it resets every night. The source code is available on request.

Available for freelance projects and full-time frontend roles.