Cresco è la punta di diamante di a3d — la piattaforma di assessment psicometrico costruita per Vittoria Assicurazioni, con Iris (la dashboard interna del cliente) che consuma i dati via API. L'utente finale lavora dentro Iris, ma può anche loggare su a3d se vede il pulsante. Il bisogno specifico di Vittoria è chiaro: una persona non deve vedere shape che ha "contratto" più di N giorni fa, dove N è grande (270 nel caso di cresco). Da questo nasce il concetto di window — la finestra temporale entro cui una shape è valida da mostrare.
Luca ha riaperto l'issue #1817 con un punto secco: gli admin e i tutor su a3d, quando guardano la pagina shape di un utente, non vedono niente. Anche se quell'utente ha compilato le prove. Anche schiacciando "ricalcola". E al CSV vedono numeri, ma nella schermata corrispondente quei numeri non ci sono. Stefania nella call ha sostenuto che la window serve solo per dire all'API se mostrare o no, e che tutto è stato pensato così. Tu invece ricordavi un design diverso, in cui Iris filtrava lato suo e a3d mandava semplicemente i dati con la data di contrazione.
Il sistema vive in stato schizofrenico. Esiste l'endpoint /api/v2/user/shape/alltime per il design originale (manda tutto, ritorna anche la data dell'ultima compilazione). Esiste l'endpoint /api/v2/user/shape con la versione windowed. Esiste anche /me/shape sulla dashboard utente che usa un calculator vecchio senza nessuna window. Esiste la pagina admin /app/user/{id}/shape che invece chiama il calculator nuovo windowed. Cinque superfici, tre comportamenti, zero coerenza.
Il commit chiave è del 9 ottobre 2025, "Refactor windowed shape calculation system". In quel cambio è nato UserShapeWindowCalculator, è stata aggiornata la home utente per usarlo, è stato marcato come obsoleto il vecchio UserShapeScore, ma sono rimaste vive ed accessibili tutte le altre superfici col loro comportamento pre-refactor. Quel refactor ha provato a uniformare ma si è fermato a metà.
Ho fatto la query verifica sul database di cresco su s0. La risposta è netta: in tabella scores c'è il campo type che fa da discriminatore, e contiene contemporaneamente due popolazioni distinte. Per le shape, 5676 record con type='standard' e solo 108 record con type='UserShapeWindowCalculator'. Per i cluster, 1972 standard contro 54 windowed. Per le skill, 26122 standard contro 144 windowed.
Il dato che apre tutto il quadro è la data: i 5676 score "standard" sono fermi al 14 ottobre 2025. Esattamente cinque giorni dopo il commit di refactor. Da allora nessuno li aggiorna più. Il calculator standard è stato abbandonato senza che nessuno lo dicesse. Il calculator nuovo, quello windowed, non solo ne ha popolati pochissimi (rapporto 50:1) ma li popola pure mal fatti, con il campo calculated_at a NULL. L'ultimo ricalcolo a livello di sito risale al 6 novembre 2025 — sei mesi fa. Su 200 utenti che hanno il flag shapeWindowCalculate=1 di "marca per ricalcolo" il cron non li ha ancora pescati.
Tradotto: il sistema mostra alla gente dati di sei mesi fa quando legge il CSV (perché lo standard è popolato ma fossilizzato), e mostra zero quando guarda la schermata (perché quella ricalcola al volo windowed e non trova nulla nei 270 giorni precedenti). Da qui la discrepanza che ha visto Luca su agenzia-030.
I dati nel database danno ragione alla tua memoria. Il design originale era a3d alltime, Iris filtra lato suo, e la window era un concetto di metadato che a3d esponeva ma non applicava come filtro hard. Il refactor di ottobre è stato un compromesso fatto sotto pressione, probabilmente in una settimana caotica, che ha cercato di centralizzare il filtro su a3d ma non ha completato la migrazione e non ha rimosso il vecchio sistema. Adesso convivono entrambi e nessuno funziona del tutto. Stefania probabilmente ricorda quella decisione di compromesso pensando fosse stata quella definitiva. Tu ricordi il design originale. La verità è che entrambi vivete dentro la stessa codebase.
Resta da fare la verifica nelle call con Meti per essere certi al 100% — ma il codice e i dati ti danno ragione abbastanza chiaramente.
Il fix per come l'abbiamo discusso non è un'operazione chirurgica nascosta in una riga, è una piccola operazione architetturale che tocca cinque punti di codice. Non è grande, ma è strutturale.
Primo, la dashboard utente /me/shape oggi mostra alltime per inerzia (usa il vecchio calculator). Il requisito del cliente Vittoria è che l'utente veda solo dati nella window. Quindi questa pagina va spostata sul calculator windowed.
Secondo, la pagina admin /app/user/{id}/shape oggi è windowed, e questo è il punto su cui Luca si lamenta. Va riportata a comportamento alltime, per dare ad admin e tutor la vista di piattaforma. Qui c'è la decisione tecnica pendente.
Terzo, l'API /alltime dichiara di essere alltime ma nella pratica legge dalla tabella scores senza filtrare per type, quindi mescola i risultati standard e windowed in modo casuale. Va aggiunto un filtro esplicito type='standard'.
Quarto, gli export CSV admin hanno lo stesso bug architetturale: leggono scores senza filtrare type. Stessa correzione.
Quinto, l'API principale /api/v2/user/shape che è quella consumata da Iris resta com'è oggi, windowed. Zero modifiche su questo lato. Iris continua a vedere quello che ha sempre visto da ottobre.
Sul punto due, riportare l'admin a comportamento alltime, ci sono due strade tecniche. La prima è chiamare il vecchio ShapeCalculator che calcola tutto al volo senza passare dal database — chiamiamola opzione gamma, o γ. Ogni volta che un admin apre la pagina di un utente parte un calcolo fresco dei 5676 record, immediato e completo, ma a costo di qualche secondo di attesa per ogni page-load. La seconda strada è opzione delta, o δ: usare il calculator vecchio ScoreCalculatorShapeUser che calcola e poi salva in tabella scores con la cache associata. Il primo accesso a un utente è lento, ma i successivi sono istantanei perché leggono dalla cache.
Ti riformulo per essere chiaro: γ è "sempre fresco, sempre lento". δ è "primo lento, poi veloce, ma cache da invalidare quando l'utente compila nuove prove". γ è più semplice da implementare e più ovvio nel comportamento. δ è più ottimizzato ma porta complessità di invalidazione.
La mia preferenza pencola verso γ perché siamo in una situazione di emergenza: meglio una pagina lenta che funziona bene che una pagina veloce che mostra dati cached da invalidare. Ma vorrei capirne con te prima di muovere il codice. Forse hai un punto di vista che non sto cogliendo, o magari conosci la dimensione vera del calcolo per un singolo utente e sai già che è una cosa da centesimi di secondo, e in quel caso γ vince a mani basse.
Prima di toccare niente ho fatto il backup completo di cresco. Il dump del database (13.7MB compressi) è su OVH al path restor/a3d/cresco-prebackfix-2026-05-08.sql.gz. Lo storage media (187MB compressi, 249MB uncompressed) è sullo stesso bucket come cresco-storage-2026-05-08.tar.gz. Il codice non è backuppato perché è ricostruibile dal git SHA 4f85b5dd sul branch main. In caso di disastro, tre comandi e siamo a posto.
Il workflow di sviluppo è quello standard di sempre, niente acrobazie: lavoro su mule modificando direttamente i file sul server, commit lì, push verso GitHub, deploy manuale di cresco via dashboard Forge. Nessun auto-deploy, nessun clone di database sotto staging. Su mule ho creato tre utenti di test per provare i tre ruoli: anacleto-admin@giobi.com con rank admin, anacleto-tutor@giobi.com con rank tutor, anacleto-user@giobi.com con rank normale. La password è la stessa per tutti, salvata nel file di configurazione del brain come variabile A3D_MULE_TEST_PASSWORD.
Mi servono tre risposte per partire col branch del fix. La prima è la decisione γ contro δ sul punto due: vuoi un calcolo live ad ogni page-load oppure un calcolo cached la prima volta che invalidiamo poi al cambio di subscription? La seconda è una conferma sul punto uno: ti va bene che /me/shape diventi windowed? Oggi mostra alltime e cambiare significa che un utente Cresco che andasse a quella URL vedrebbe la sua storia ridotta agli ultimi 270 giorni — coerente col requisito ma è un cambio user-facing. La terza è una verifica nelle call con Meti per chiudere la disputa storica: il database e il codice ti danno ragione, ma vorrei che fossi certo di averla anche quando ne parli con Stefania, perché potrebbe essere che lei abbia il ricordo di una decisione presa in una call specifica che non sta nel git log.
Quando hai le risposte, parto con il branch fix/window-admin-vs-user, scrivo i cinque commit chirurgici, testo su mule.a3d.it con i tre utenti, e poi facciamo il deploy cresco. Tempo stimato del fix in sé: due ore tra scrittura e test. Il rerun dei calcoli vecchi sul database è un capitolo a parte che possiamo fare in un secondo momento se decidiamo che serve.