The club page now leads with its own numbers that should be zero, then the health
signals underneath.
- Teams with no coach. Not a statistic but a defect in the club's setup: with
nobody in a management position the access service grants no authority over that
team, so nobody can pick the squad. A physio does not count -- the query keys on
Position.management_position, and on this season only.
- Unrostered members: active, paid, and on no team.
- Unpaid money bucketed by age. "€250 overdue past 60 days" drives a phone call;
"€250 outstanding" does not.
- Renewal rate -- last season's actives who signed up again. Exactly computable
because memberships are season-scoped.
- Turnout, plus the share who never responded. Silence is not an absence, so it is
excluded from turnout and reported separately: no-response is the leading
indicator, since it measures whether members use the app at all.
Two of these return None rather than a number, deliberately: a club in its first
season has not failed to renew anyone, and a season with no past events has no
turnout. Rendering either as 0% would libel the club, so the page says why instead.
Money is pinned to two decimals -- SQLite's Sum() drops trailing zeros, so an
aggregate rendered "€250" next to a "€0.00" constant on the same card.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>