# CoachEval

> Weekly evaluations for a sports club’s young athletes: a deck of cards for the coaches, words for the families.

Case study by Paolo Gianfelici (Full-stack developer & UI/UX designer). https://paologianfelici.com/work/coach-eval

- **Role:** Everything: product design, the Vue web app, the .NET API, the database and the server
- **Client:** A tennis coach and club owner
- **Period:** 2026 – today
- **Platforms:** Web app installable on the phone (PWA); a Flutter companion app in progress
- **Stack:** Vue 3, TypeScript, Pinia, Tailwind CSS, shadcn-vue, .NET 8, ASP.NET Core, EF Core, PostgreSQL, Finbuckle.MultiTenant, Playwright, Docker, GlitchTip
- **Website:** https://coacheval.app

CoachEval follows the young athletes of a sports club through the season. Once a week the coaches rate what they saw, in a deck of cards that takes a few minutes; from those ratings the app draws each athlete’s card, the squads’ trends and ideas for the next training. Around it sit the club’s calendar, the call-ups, line-ups and team sheets of match day, and a board where each family sees its own child’s week.

I built it on my own for a tennis coach who runs a club: the product and its interface, the API, the database and the server it runs on. It started with tennis and now handles football too.

*CoachEval is in use, and what it holds is about children. The recordings on this page do not come near it: they run the app’s current code on my machine, on a database seeded with the two demo clubs that ship with the code (every surname a term of the sport, every address @example.com), and nothing was sent anywhere.*

- 100%: of the commits are mine (536 since July 2026)
- 92k: lines of C#, Vue and TypeScript, excluding tests and migrations
- 1.7k: automated tests: 901 on the API, 795 end to end in the browser
- 2: sports, tennis and football, in Italian and English

## From a week’s ratings to a family’s board

### The week in a deck

Coaches rate their athletes once a week, card by card. Each card is one attribute (the forehand, footwork, attitude in matches) across one squad, rated 1 to 10 with a tap on a bar: comparing a squad on the same thing is faster and fairer than opening each athlete in turn. Whoever was not seen is left alone: nobody gets a mark by default. The deck is sized from the season’s plan and ends when the week’s target is met; experience points and milestones keep the habit going.

### An athlete card

The club’s athletes as an album of cards. Each opens on the average and the areas, then the four dimensions (technical, tactical, physical, mental) with their trend and each attribute’s latest mark. “Quanto lo conosciamo” (how well we know him) measures the observing, not the athlete: it grows when a coach looks, and says where to look next.

### Radar and the training register

The Radar is a feed of what has changed in the coach’s squads: who has been seen too little on an attribute, a squad’s strength or weak spot, a trend, a drill for the next training. Cards about a squad come from aggregates and never single out a child. The lightbulb lists the trainings and matches still to close; yesterday’s training takes three taps.

### Match day

In the football demo club: the call-up starts from the previous one, with shirt numbers and the captain, and comes with a message for the players with meeting time and place. The line-up goes onto a pitch, each player with role and strongest attributes, and is shared with the squad’s staff as a link, so the names stay in the app. The team sheet for the referee fills itself from the roster and is printed: nothing typed on it is ever stored.

### The family board

Parents get in with a six-digit code sent to the address the club has: no password to invent. They see their own child’s week, calendar and call-ups, and nobody else’s. The card speaks in words (strengths, the next step), never in marks, and tokens earned by turning up dress it. A two-tap check-in tells the coaches how the week went, and whether to talk.

## How it was built

### A functional core

Every number the product computes (the week’s workload, who to observe first, the season’s calendar, levels and streaks) is a pure function in the application layer, tested without a database; the API endpoints around it do the input and output. Invariants live on the entities themselves: a photo cannot be attached without consent on file, whichever endpoint tries.

### One database, many clubs

Clubs share one PostgreSQL database: every club-scoped row carries its club, and an EF Core global query filter (Finbuckle.MultiTenant) applies it to every query. Roles are grants read from a single table and checked exhaustively, so a new role that nobody has classified stops the app at startup instead of inheriting someone else’s access.

### Designed around minors

The app holds records on children, so the design starts there. No health data at all (availability is a yes or a no, never a why); photos served only behind authentication; the team sheet never stored; families shown words instead of numbers; no ranking of coaches; error reports that carry the route, never a person. Tests pin the rules down: the team sheet’s, for one, fails if anything typed on it leaves the browser.

### From the phone to the server

A Vue 3 PWA in TypeScript (Pinia, Tailwind, shadcn-vue) with types generated from the API’s OpenAPI description; .NET 8 and EF Core on PostgreSQL; Italian and English throughout. It runs in Docker on an ARM server on Oracle Cloud, behind Cloudflare, with self-hosted error tracking, and clubs can sign up on their own: a verified address and the service contract accepted online. A Flutter companion app is in progress.

## Recording a live product: Its own code, its demo clubs, nobody real

CoachEval is in use, with records on minors. None of that belongs in a portfolio, so the recordings run the app’s current code on a database built on my machine for the purpose.

- **A copy of the code.** The web app and the API of the current release run on my machine in Docker, with a fresh PostgreSQL database. The browser that records may talk to that API and to nothing else.
- **The demo clubs it ships.** The code seeds two showcase clubs, tennis and football, made to look fictional at a glance: every surname is a term of the sport (Rovescio, Smorzata, Contropiede), every address is @example.com. I opened a season and accepted the club’s agreement as an owner would, through the app’s own API.
- **Every take from the same week.** A take saves ratings, a call-up, a line-up, a family session. Before each one the database goes back to a snapshot, so every recording starts from the same state.
- **Shown in Italian.** The app speaks Italian and English, but the sport’s catalogue and the clubs are Italian, so it is recorded in Italian, as its coaches see it.

The interface is the app’s own. The clubs, coaches, athletes, families, ratings and matches are the synthetic demo data that ships with its code.

## Recordings

- **The week in a deck** (58 s): Each card is one attribute across one squad: tap the bar for each athlete, leave alone who you did not see, move on. The week ends when the deck does. https://paologianfelici.com/media/coach-eval/clips/deck.mp4?v=a865946563
- **An athlete card** (37 s): The album of the club’s athletes, then one of them: the average and the areas, each dimension with its trend and attributes, and how well the club knows him, which measures the observing, not the athlete. https://paologianfelici.com/media/coach-eval/clips/athlete.mp4?v=60d19b1993
- **Radar and the training register** (50 s): The Radar: what is new about the squads, one card at a time (who to observe, a squad’s strength, a trend, a drill for the next training). Then the lightbulb: yesterday’s training to close in three taps. https://paologianfelici.com/media/coach-eval/clips/radar.mp4?v=7d5990b2a9
- **Match day** (102 s): The football demo club: Sunday’s match, the call-up (numbered, with the captain), the line-up on the pitch with each player’s role and strengths, and the team sheet for the referee, filled in from the roster and never stored. https://paologianfelici.com/media/coach-eval/clips/match-day.mp4?v=5ebff408b6
- **The family board** (42 s): A parent’s side: in with a six-digit code sent to the address the club has (no password), then the child’s week, call-ups, the card to dress with tokens earned by turning up, and a two-tap weekly check-in for the coaches. https://paologianfelici.com/media/coach-eval/clips/family.mp4?v=664901baf5
