Skip to content
Eeman Adeniran
CV

Case study

Automation / Web / Image processing

Lasustennis Club Ladder Games

A system that took over a tennis club’s hand-run ladder competition by drawing the pairings, scoring every match and publishing the standings online. Rebuilt in 2026.

Role
Sole developer
When
2023 · Rebuilt 2026
Team
Solo: design, build and deployment
Status
2026 version live
Built withPython (Flask) · Jinja · PostgreSQL · SQLAlchemy · Pillow · Matplotlib · JavaScript · HTML/CSSisl-tennis.vercel.app ↗
Home page of the 2026 ladder site: the season title, the player at the top of the ladder with their points, the latest result, and counts of ladder days, matches and players.

01

Context

The tennis club where I play runs a weekly ladder competition, and it used to be organised by hand.

On ladder days, the players who turn up are paired into matches. Every match is five games long (it ends 3–2, 4–1 or 5–0), and the games you win go straight onto your ladder points. Each player can play a limited number of matches a week: five in 2023, six in 2024, and three now. In 2023 there were singles and doubles; today it’s doubles only.

On top of the weekly matches there are challenge matches. You can challenge a player up to three places above you, and you have to put something down to do it. Win, and your points become theirs. If they have 100 and you have 70-something, you jump to 100 and join them on the table. Lose, and the table stays as it is.

02

Problem

Done by hand, all of it was stressful: deciding who would play whom, calculating the scores, keeping the totals straight. At one point the paper the scores were written on went missing.

And if you weren’t at the club, you couldn’t really tell what was happening. The only way to find out was to call someone.

03

Insight

I’d watched how the manual process worked, so I could see exactly which parts a computer should be doing. When I automated it, a lot of that stress simply went away, from drawing the pairs to adding up the points.

04 · Architecture

How it works

Fig. · A ladder day (2023)
  1. 01

    Club admin People / place

    An organiser runs the ladder from a signed-in admin area. Everyone else sees the public site.

    → Pairing

  2. 02

    Pairing Process

    The admin ticks the players who are available, and the system pairs them at random.

    → Results

  3. 03

    Results Process

    Scores go in through admin forms: ladder matches, and best-of-three-sets challenge matches. Editing or deleting a result reverses its points.

    → Points & table

  4. 04

    Points & table Process

    Each player earns the games they won. A challenger who wins takes their opponent’s points total. Standings, head-to-heads and statistics come from queries over the stored results.

    → Public site, Weekly images

  5. 05

    Public site Output

    Anyone can check the standings and head-to-head records. No phone call needed.

  6. 06

    Weekly images Output

    Entering the first result of a new week closes the previous one: Pillow draws that week’s results and the standings, with each player’s movement since the week before.

    → Email

  7. 07

    Email Output

    The images are emailed to the club’s account as attachments.

The first version was a single Flask application (Python for essentially everything, with Jinja templates for the pages), served by Gunicorn over PostgreSQL. Players, matches, weeks and seasons are separate tables, with a join table between players and matches so singles and doubles are stored the same way.

05

Engineering

  • Random pairings from the players who were available, for singles or doubles
  • Ladder scoring, and challenge matches that move points between players
  • Statistics from the database: head-to-head records, win ratios, points per match, most points in a day, “bagels” (5–0 wins)
  • Week-on-week movement in the table: who rose and who fell
  • Results and standings drawn as images after every ladder day (more on those below), and Matplotlib charts for season statistics
  • A public join form that emails membership requests to the club

Over the 2023 season the system recorded 457 matches across 38 ladder days, between 23 players. 186 of them were singles, the rest doubles.

06 · Images

The weekly images

The part I’m proudest of: after every ladder day, the system drew two images by itself, the day’s results and the updated standings, ready to be shared.

Day 16 ladder games image: seventeen match results on a green-to-yellow gradient, each row showing the players, the score in a black box, and an arrow pointing to the winning side.

2023 · Day 16 results

Day 16 standings image: the full ladder table on a blue-to-purple gradient, with each player’s position, a green or red movement arrow, games played and points.

2023 · Day 16 standings

There’s no template or design tool behind them. Everything is drawn in code with Pillow:

  • The gradient backgrounds are calculated one pixel at a time, blending two colours along an angle
  • Every name and score is measured and centred by hand. Names with letters that drop below the line (g, j, p, q, y) are positioned differently, so every row sits level
  • An arrow marks the winning side of each match, and a slash separates doubles partners
  • The standings show each player’s movement since the previous ladder day: green for up, red for down, grey for no change. Players on equal points share a position
  • When a day has more matches or players than usual, the canvas grows to fit, so nothing gets cut off

Nobody had to remember to make them. Recording the first result of a new ladder day closed the previous one, rendered both images and emailed them to the club.

07

Difficulties

The hardest part was the challenge matches. Ladder matches just add points, but a challenge is best of three sets, has to work out who won, and then moves one player’s total up to the other’s. And editing or deleting a challenge result has to undo exactly that.

Deploying it was the other battle. It was my first project, I kept running into errors, and I stayed up almost all night working through them until it ran.

08

Outcome

Tournament participants and viewers grew by more than 100% compared with the previous year.

It wasn’t built for players to track their own performance; I hadn’t added that yet. It was for anyone to see the standings. But because it was a website people could check easily, it brought a lot of interaction to the club: everyone was talking about it, checking it, and sending it to friends: “look at my position.”

I built it between March and August 2023, adding features as the season went on: weekly results, then the table, then head-to-head records.

09 · Rebuild

Rebuilding it in 2026

In 2026 I rebuilt it with what I’d learned since: a separate front end and back end, a new design, and the 2023 season’s results carried over so both seasons live in one place.

  • Front end: Next.js and TypeScript, with TanStack Query for data fetching and Better Auth for sign-in
  • Back end: a FastAPI service with SQLAlchemy over PostgreSQL, serving standings, games, statistics and head-to-head records
  • Doubles only, matching how the ladder runs now. The pairing never puts a men’s pair against a women’s pair; if the draw would, it swaps partners so both teams are mixed
  • What 2023 didn’t have: player profiles, recent form, a head-to-head comparison, a ladder chart and season-by-season views, so players can follow their own performance
Season statistics page of the 2026 site, with leaderboards for points per game, win ratio, bagels, close-match wins, bagels conceded and attendance.
Season statistics on the 2026 site: leaderboards worked out from every recorded match.

Next is an assistant that answers questions about the stats, like who leads the ladder, how two players compare. The chat interface is built; the assistant behind it is what I’m working on now.

10

Reflection

If I built the first version again, I wouldn’t do the front end in Jinja. Getting every page to behave meant a lot of permutations and combinations in the templates; a React front end is much easier to manage, and it’s what the 2026 version uses.

I built the 2023 version without any AI help. Every problem, from the challenge logic to lining up each name on the images, I had to reason through myself, and it pushed my logical reasoning and problem solving to their limits. I’m glad I got that experience before AI tools became part of how software is built.

I’m still very proud of it. I saw the impact it had, and I know how much work went into it. It marks the shift from learning how to code to noticing real problems that software could solve.

↑↓ to move · ↵ to open