Rugby Performance IntelligenceDocumentazione tecnica · main
Manuale utente →

Piattaforma multi-tenant per l'intelligence della performance nel rugby

SaaS per club: anagrafica, gestione medica e tesseramenti, eventi e presenze, dati di sessione, intelligence (carico, readiness, rischio infortunio, genome, body insight), pianificazione dell'allenamento fino alla periodizzazione stagionale, AI Coach generativo e notifiche.

Backend FastAPI + SQLAlchemy 2 + Alembic + PostgreSQL. Frontend React 18 + Vite + TypeScript + Tailwind. Codice, commenti e commit in italiano.

52 tabelle40 migrazioni ~130 endpoint10 ruoli · 53 permessi 12 motori intelligence482 test
Indice
  1. Stack & struttura
  2. Architettura backend
  3. Multi-tenancy & scoping
  4. RBAC
  5. Storage, audit, notifiche
  6. Modello dati
  7. Intelligence — algoritmi
  8. Pianificazione
  9. API
  10. Frontend
  11. Flussi funzionali
  12. Deploy & operations
  13. Convenzioni & gotchas
01

Stack & struttura

Backend — FastAPI 0.115, SQLAlchemy 2.0.36, Alembic 1.14, Pydantic-settings 2.7, PostgreSQL. OpenAPI disabilitato (openapi_url=None). Prefisso API /api/v1. Config solo da env (nessun segreto hardcoded). X-Request-Id per-richiesta.

Frontend — React 18, Vite, TypeScript, Tailwind, zustand (store auth), axios. PWA (workbox, service worker, scrittura offline coalescente, Web Push). Comandi: npm run dev (:5174), npm run type-check, npm run build, npm test.

Layout repository

app/
  api/v1/        router per dominio (atleti, eventi, wellness, intelligence, …)
  api/deps.py    require_permission, get_current_active_user
  api/scoped_crud.py   scoping helpers + audit_safe
  core/          rbac, scoping, storage, audit, benchmark, notifiche, security
  intelligence/  motori PURI + orchestrator + feature_store + config
  models/        modelli SQLAlchemy (52 tabelle)
  schemas/       schemi Pydantic
migrations/versions/   0001 … 0040 (Alembic)
frontend/src/  pages/ components/ api/ stores/ design-system/ lib/
docs/  DOCUMENTAZIONE.md · STAGING.md · LLM_SETUP.md
CLAUDE.md

Comandi essenziali

python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt
pytest                                  # suite (SQLite in-memory, no DB)
alembic upgrade head                    # migrazioni (Postgres)
python -m app.cli create-superuser --email admin@x.it   # primo platform_admin
python -m app.cli seed-demo             # società demo (solo dev/demo)
02

Architettura del backend

Flusso canonico di un endpoint di dominio (template: app/api/v1/atleti.py):

router → require_permission("codice") → risolve contesto società → scoped_select/get_scoped_or_404 → muta → commitaudit_safe

  • require_permission(codice) — dependency factory che verifica il permesso RBAC nella società di lavoro. platform_admin short-circuita ogni check.
  • Contesto societàcontext_societa(current) / resolve_societa_context(user, requested): l'utente di dominio è confinato alla propria società (403 altrimenti); platform_admin è cross-società.
  • Config versionata — gli algoritmi leggono i parametri da AlgorithmConfig (status active) via get_active_config; i default vivono nel codice e sono materializzati come config v1 (seed idempotente).
03

Multi-tenancy & scoping

Isolamento logico per societa_id (app/core/scoping.py). Ogni modello con colonna societa_id è auto-rilevato come scoped via introspezione del registry → un nuovo modello di dominio è scoped per costruzione.

  • scoped_select(model, societa_id) — ogni lettura di dominio passa di qui; fail-closed (rifiuta modelli non-scoped e societa_id=None).
  • ensure_societa_on_create — timbra/valida societa_id in scrittura (rifiuta mismatch cross-società).
  • get_scoped_or_404 — 404 (non 403) per righe di altri tenant → ne nasconde l'esistenza.
  • scoped_atleta_ids{atleta} | {figli} | None; require_atleta_access per ogni lettura per-atleta.

⚠️ SQL: col.in_([id, None]) non matcha NULL → usare or_(col==id, col.is_(None)). La dep che setta il ContextVar "società di lavoro" (header X-Societa-Id) deve essere async, o il set non arriva al corpo sync.

04

RBAC — ruoli e permessi

Default-deny (app/core/rbac.py). 53 permessi, 10 ruoli, matrice ROLE_PERMISSIONS. seed_rbac riconcilia: la matrice è source-of-truth e rimuove le mappature obsolete.

RuoloPermessiNote
platform_adminALL (53)super-admin piattaforma, cross-società
admin_societaALL (53)pieno nella propria società
presidenteALL (53)vertice società
direttore_tecnico36tecnico + sanitario (con gate) + microciclo
allenatore27tecnico, microciclo, inserimento sessione
preparatore20tecnico, test, inserimento sessione
segreteria16gestionale (tesseramenti/documenti/pagamenti)
medico8sanitario (richiede sanitary_access)
giocatore7self-report /me/*
genitore6vista read-only dei figli
  • Permessi verificati nella società home (nessuna escalation cross-società).
  • Gate sanitario: sanitario.read/write richiedono sanitary_access=True sull'assegnazione, oltre al permesso. /me/capabilities espone solo i permessi effettivi.

Ownership dati: antropometria, contatto e test = staff-only (qualità dato + tutela minori). Wellness/RPE/recupero = self-report atleta via /me/*, con deroga staff tramite i permessi *.write_team (allenatore/preparatore/DT) — l'autore resta tracciato in audit.

05

Storage cifrato, audit, notifiche

Storage file — envelope encryption per-tenant

Standard di piattaforma (app/core/storage.py): ogni oggetto è AES-256-GCM cifrato con la DEK della società; la DEK è salvata wrapped (cifrata con la master KEK, env-only FILE_ENCRYPTION_KEK) in societa_storage_key. I file non toccano il DB e non sono mai in chiaro su S3 (bucket condiviso → isolamento a livello dato). Upload/download proxati dall'API (no presigned). Il read non crea mai la DEK.

Audit

audit_safe(...) registra l'azione dopo il commit, best-effort (non rompe mai l'operazione), legata alla richiesta via request_id.

Notifiche

Inbox (Notifica, dedup per chiave_dedup) + Web Push (VAPID). Cron tick ogni 15' con 6 blocchi: promemoria eventi, scadenze (tesseramenti/certificati), insoluti, diario mancante, carico alto + readiness bassa (staff), dolore ricorrente (medico) + trigger su piano individuale approvato. Web Push iOS solo su PWA installata (Safari 16.4+).

06

Modello dati

52 tabelle, raggruppate per dominio. Ogni tabella di dominio ha societa_id.

Anagrafica & organizzazione

societa, societa_storage_key, sede, stagione, categoria, squadra, gruppo, atleta, assegnazione_atleta (atleta↔squadra, data_da/data_a), tutore, atleta_tutore.

Utenti & RBAC

utente (con atleta_id/tutore_id per collegare account self/genitore), permesso, ruolo, ruolo_permesso, assegnazione_ruolo (scope + sanitary_access).

Medico · consensi · gestionale

certificato_medico, consenso, tesseramento (categoria + deroga, stato FIR, quota), quota_categoria, pagamento, documento_gestionale (file cifrato, condiviso).

Eventi & presenze

evento (allenamento/partita/test/riunione), convocazione, presenza, nota (private staff), indisponibilita (periodo infortunio/malattia, data_fine NULL = in corso).

Dati di sessione & performance

wellness_entry, rpe_entry (session_load, evento/seduta), dato_recupero (HRV/sonno/FC), carico_contatto; test_definizione, sessione_test, tentativo, risultato_test, protocollo_versione, riferimento_test (benchmark), categoria_baseline (population genome), rilevazione_antropometrica (pliche/BF%).

Pianificazione

profilo_metodologico, macrociclo, mesociclo, microciclo, seduta, diario_seduta.

Intelligence & AI

algorithm_config, algorithm_input_snapshot, algorithm_result; ai_conversazione, ai_messaggio, kb_documento, kb_chunk (RAG); notifica, push_subscription, audit_event.

07

Intelligence — motori e algoritmi

I motori vivono in app/intelligence/ come funzioni pure (no DB, no tempo); l'orchestratore legge il DB, chiama i motori e materializza i risultati in AlgorithmResult; il feature_store espone gli ultimi risultati per il serving. Config versionata per 5 algoritmi: load_intelligence, readiness_score, injury_risk, athlete_genome, body_insight.

Paletto trasversale: readiness / injury / carico sono indicatori di supporto alla decisione, non predittori. Nessun modello predittivo; INSUFFICIENT_DATA finché mancano i dati; i pesi si rinormalizzano sui componenti presenti; gate su confidence.

Load intelligence

Serie giornaliera del session_load (RPE×durata). I giorni di riposo dentro la finestra di monitoraggio (dal 1° RPE ad as_of) sono KNOWN_ZERO, i pre-monitoraggio UNKNOWN. EWMA acuto λ=0.25 (N=7), cronico λ≈0.069 (N=28). ACWR / Load Balance Ratio = acuto/cronico, pubblicabile solo con coverage_28 ≥ 70% e history ≥ 21 e cronico ≥ minimo. robust_z dell'acuto vs baseline (median/MAD) → load_state. Monotony (Foster) = media/SD settimanale (riposi inclusi come 0); Strain = weekly × monotony.

Readiness

Punteggio 0–100 di prontezza "oggi". Componenti pesati (rinormalizzati): wellness (z-composito di sonno/fatica/doms/stress/umore/recupero), load_state, opzionali HRV / sonno oggettivo / recupero post-gara. Bande LOW/MODERATE/GOOD; gate su confidence. Override UNAVAILABLE se un periodo di indisponibilità copre as_of.

Injury risk

Indicatore 0–100 + livello. Componenti: acwr (~0.25), monotony_risk, readiness_risk, soreness_risk (DOMS), biomeccanica/stress_articolare opzionale (peso 0.15). Nessuna pretesa predittiva.

Genome

Profilo per aree (velocità/potenza/forza/resistenza): domain_score 0–100 dai test confrontati con la baseline della categoria (CategoriaBaseline, percentile per categoria). Confidenza graduata (min 5 / pieno 20 atleti); sotto 5 → fallback sviluppo-personale. Radar nel FE.

Body insight

Da antropometria: WHtR, BMI, somma pliche, BF% (metodo + errore atteso), masse derivate (solo con BF% valido). Qualità L1/L2/L3. Hodgdon-Beckett = legacy, non pubblicato.

Metriche derivate & beep test

  • Peak power (Sayers): 60.7·CMJ + 45.3·peso − 2055 W.
  • Efficienza aerobica: livello_beep / peso^0.75.
  • Beep test / Léger 20m: inserito come livello + navetta, salvato come navette totali (valore monotòno). VAM = 8.0 + 0.5·L km/h; VO₂max (Léger) = 31.025 + 3.238·v − 3.248·età + 0.1536·v·età.

Correlazioni, simulazione, benchmark

flag_carico_readiness = flag descrittivi (overlay, niente coefficiente). simulate.project_microcycle valida i piani (weekly load, monotonia, ACWR vs cronico). RiferimentoTest per categoria (U14/U16/U18/Seniores; Yo-Yo in metri, Beep in navette); risolvi_benchmark con preferenza società+categoria > società > globale+categoria > globale.

08

Pianificazione & periodizzazione

AI Coach = generatore di piani (non Q&A). Pipeline: ProfiloMetodologicoscaffold deterministicoMicrociclo/Seduta bozza → genera-contenuti (LLM)approvaapplica al calendario (crea Eventi allenamento, idempotente). Scaffold/carico/editor/applica funzionano anche senza LLM.

Piano individuale: tailoring su injury/readiness/aree deboli; non crea Eventi squadra (esposto via /me/sedute); l'atleta compila il diario → upsert RpeEntry per seduta → alimenta Load/Readiness.

Periodizzazione stagionale (Macrociclo→Mesociclo→Microciclo→Seduta): la stagione è esplosa alla generazione. Rugby = partita ~ogni domenica → macro stagionale per fasi; date incontri manuali con flag chiave che guida mini-taper (×0.85) e scarico (×0.6). Aggancio calendario bidirezionale (Evento tipo=partita = sorgente). Analytics pianificato-vs-reale (aderenza per settimana e mesociclo). FE: ridgeline StagioneTimeline.

09

API — superficie completa

~130 endpoint sotto /api/v1. Notazione: METODO path — [permesso].

Auth & utenti

POST /auth/login · GET /me/capabilities · utenti/ruoli [utente.manage] · assegnazioni-ruolo [rbac.manage]

Organizzazione & anagrafica

squadre · sedi · stagioni · categorie · gruppi · atleti [atleta.*] · tutori · legami-tutore · assegnazioni atleta↔squadra + roster [squadra.*]

Medico · gestionale

certificati+file [sanitario.*] · consensi · tesseramenti(+rinnova) · quote-categoria · pagamenti · documenti+file cifrato · GET /ricerca · /gestione/riepilogo

Eventi · presenze · indisponibilità

eventi(+ricorrenti,piano) · convocazioni · GET/PUT /eventi/{id}/presenze · note · GET/POST /atleti/{id}/indisponibilita, PATCH/DELETE /indisponibilita/{id} [presenza.*]

Dati di sessione — self + staff

Self: POST /me/wellness · /me/rpe · /me/recupero. Staff: PUT /eventi/{id}/rpe [rpe.write_team] · POST /atleti/{id}/wellness [wellness.write_team] · POST /atleti/{id}/recupero [recupero.write_team] · /eventi/{id}/contatto [contatto.*] · antropometria [antropometria.*].

Test & benchmark

test-definizioni · POST /atleti/{id}/test · PUT /eventi/{id}/test · sessioni-test/import · Batterie: GET /squadre/{id}/test/confronto|serie|batterie|griglia · PUT …/batteria · GET /test/confronto-squadre · riferimenti-test · /atleti/{id}/test/valutazione|serie · baseline-categoria.

Intelligence

GET /intelligence/{load|readiness|insights|injury-risk|genome|body-insight}/atleti/{id} · GET /intelligence/features/squadre/{id} (cruscotto prontezza: readiness+carico+injury+disponibilità) · POST /intelligence/recompute/… · simulate/{session-load|microcycle} · /atleti/{id}/carico-readiness.

Pianificazione · calendario · AI · notifiche

pianificazione/profilo · microcicli/genera|applica|approva · individuali/genera · macro/genera|incontri|analytics · PUT /me/sedute/{id}/diario · GET /calendario (feed unificato) · aicoach/* · notifiche/* · POST /jobs/notifiche/tick.

10

Frontend

SPA React. Auth in zustand: JWT in localStorage, /me + /me/capabilities al login; can(permission) / <Can> per il gating UI (codici allineati alla matrice RBAC). Axios fa force-logout su 401. Design-system con componente ComeLeggere (spiegazioni "come leggere" sotto ogni grafico). Regole dataviz: niente dual-axis (small-multiples), palette di stato con icona+etichetta, SVG xMidYMid meet.

PaginaRottaContenuto
Dashboard/home
Atleti / dettaglio/atletianagrafica + tab: readiness (+indisponibilità/insights/injury/load), genome, test fisici (trend/VO₂max/beep), body insight, benessere (+form staff), diario, medico, consensi, documenti, note
Prontezza squadra/intelligencecruscotto readiness+injury+carico+disponibilità, ordinamento concern-first
Batterie test/batterieConfronto · Andamento (trend p25–p75) · Squadre · Inserimento (griglia, cella beep L+N) · Baseline
Campo / evento/campoagenda Lista/Mese/Giorno (drag&drop, undo); dettaglio evento con griglia sessione (RPE/contatto/test), convocazioni, presenze
Pianificazione/pianificazionemicrociclo (editor calendario) / individuale / Stagione (timeline + analytics)
AI Coach · Gestione · Tesseramenti/ai-coachconversazioni, gestionale + cruscotto
Il mio benessere / I miei figli/me · /figliself atleta e vista genitore
Notifiche · Tutori · Ruoli staff/notificheinbox + Web Push, tutori, ruoli
11

Flussi funzionali principali

  1. Onboarding società: create-superuser → login → società/stagioni/categorie/squadre → utenti+ruoli (con gate sanitario).
  2. Anagrafica & tesseramento: atleta → tesseramento (categoria auto o deroga) → certificato + consensi → quote/pagamenti.
  3. Agenda & presenze: eventi (anche ricorrenti) → convocazioni → presenze; le partite alimentano periodizzazione e readiness.
  4. Dati di sessione → algoritmi: RPE (self o sRPE di squadra staff) + wellness (self o staff) + recupero + contatto + test/antropometria → materializzazione Load/Readiness/Injury/Genome/BodyInsight → serving per-atleta e cruscotto squadra.
  5. Test batterie: catalogo → griglia roster×test (cella beep L+N) o import CSV → confronto/trend/baseline → Genome/VO₂max.
  6. Pianificazione: profilo → microciclo/individuale (scaffold→LLM→approva→applica) → periodizzazione stagionale → analytics; il diario chiude il loop col carico.
  7. Disponibilità: Indisponibilita (periodo) → override readiness UNAVAILABLE → visibile nel cruscotto.
  8. Notifiche: cron tick → promemoria/scadenze/insoluti + diario mancante + carico↔readiness + dolore ricorrente + seduta assegnata.
12

Deploy & operations

Docker: cp .env.example .env (set PG_PASSWORD, SECRET_KEY, FILE_ENCRYPTION_KEK, VAPID) → docker compose up -d (postgres + api su 127.0.0.1:5010) → curl /health.

Staging (stack separato, progetto rugbypi-stg, porta 5011, dietro Cloudflare):

docker compose -p rugbypi-stg --env-file .env.staging \
  -f docker-compose.staging.yml up -d --build --force-recreate api
docker exec rugbypi_api_stg alembic upgrade head        # se ci sono migrazioni
cp -r frontend/dist/* /var/www/rugby-pi-stg/            # FE statico

⚠️ up -d --build può NON ricreare il container col codice nuovo (cache) → verificare con docker exec … grep -c "<stringa>" <file>; usare --force-recreate. Env nuove nel compose staging vanno nel mapping environment: esplicito. VAPID = raw base64url. Cache Cloudflare/PWA: se il FE nuovo non appare → incognito o purge CF.

Test: pytest su SQLite in-memory (StaticPool, no Postgres); fixture in tests/conftest.py. Suite corrente: 482 test.

13

Convenzioni & gotchas

  • Modelli: importarli in app/models/__init__.py (+ __all__) per la registrazione.
  • Direzione test HIGHER/LOWER coerente ovunque; l'unità nel benchmark deve combaciare col catalogo (Yo-Yo = metri, Beep = navette).
  • Load: distinguere riposo monitorato (KNOWN_ZERO) da non-monitorato (UNKNOWN), altrimenti ACWR non pubblicabile e monotonia sovrastimata.
  • compute_load(persist=False) per i chiamanti interni (readiness/injury); materialize_atleta persiste il load una volta.
  • Nessun admin hardcoded: primo platform_admin da app.cli create-superuser. OpenAPI disabilitato.

⚠️ Config/dati persistiti NON fanno backfill (bug ricorrente): aggiungere una chiave a un DEFAULT non aggiorna i record già salvati → leggere sempre con .get(chiave, default) o {**DEFAULT, **stored}. Vale per ProfiloMetodologico.config, AlgorithmConfig.params e i campi Pydantic non dichiarati (droppati).

Rugby Performance Intelligence · documentazione di sistema generata dal codice in main · 52 tabelle · 40 migrazioni · ~130 endpoint · 12 motori intelligence.