Rimuove la serie «Conferme 24h» e i badge collegati

Toglie la serie dal profilo, dalla Home e dalle notifiche di traguardo, insieme
ai badge «Risposta lampo» e «Mai un forfait» e al calcolo di serieConferme.
La colonna risposto_il e il suo trigger restano nel database, inutilizzati.
Test e documentazione allineati.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-06 13:21:14 +02:00
1 parent 8d0a8c7971
commit bfb768fb5a
17 files changed
+69 -718

No files matched your search

-42
View File
@@ -34,7 +34,6 @@ function g(valori: Partial<Giocatore> = {}): Giocatore {
streak: 0,
serieAllenamenti: 0,
seriePartite: 0,
serieConferme: 0,
...valori,
};
}
@@ -146,16 +145,6 @@ assert.equal(gradoRaggiunto(serieAllenamentiDef, 9), "argento");
assert.equal(gradoRaggiunto(serieAllenamentiDef, 10), "oro");
assert.equal(gradoRaggiunto(serieAllenamentiDef, 50), "oro", "oltre l'oro resta oro");
// --- serie-conferme: soglie 3 / 8 / 15 ----------------------------------------
const serieConfermeDef = badgeDefs.find((b) => b.id === "serie-conferme")!;
assert.equal(gradoRaggiunto(serieConfermeDef, 2), null, "sotto la prima soglia nessun grado");
assert.equal(gradoRaggiunto(serieConfermeDef, 3), "bronzo", "la soglia è inclusiva");
assert.equal(gradoRaggiunto(serieConfermeDef, 7), "bronzo");
assert.equal(gradoRaggiunto(serieConfermeDef, 8), "argento");
assert.equal(gradoRaggiunto(serieConfermeDef, 14), "argento");
assert.equal(gradoRaggiunto(serieConfermeDef, 15), "oro");
assert.equal(gradoRaggiunto(serieConfermeDef, 50), "oro", "oltre l'oro resta oro");
// --- badgeGiocatore ----------------------------------------------------------
assert.equal(badgeGiocatore(g()).length, badgeDefs.length, "i badge normali sono sempre tutti");
assert.ok(
@@ -200,36 +189,6 @@ assert.deepEqual(
["s-cacche"],
);
assert.equal(badgeSegretiSbloccati(g({ cacche: 2 })).length, 0, "2 cacche non bastano");
assert.deepEqual(
badgeSegretiSbloccati(g({ serieConferme: 10, presenze: 15 })).map((b) => b.def.id),
["s-mai-forfait"],
);
assert.equal(
badgeSegretiSbloccati(g({ serieConferme: 10, presenze: 14 })).length,
0,
"servono entrambe le condizioni",
);
// Confini isolati: ogni soglia testata da sola, con l'altra abbondantemente sopra.
assert.equal(
badgeSegretiSbloccati(g({ serieConferme: 9, presenze: 30 })).length,
0,
"serieConferme appena sotto soglia, presenze abbondanti: non basta",
);
assert.deepEqual(
badgeSegretiSbloccati(g({ serieConferme: 10, presenze: 30 })).map((b) => b.def.id),
["s-mai-forfait"],
"serieConferme esattamente al confine, presenze abbondanti: sblocca",
);
assert.equal(
badgeSegretiSbloccati(g({ serieConferme: 30, presenze: 14 })).length,
0,
"presenze appena sotto soglia, serieConferme abbondante: non basta",
);
assert.deepEqual(
badgeSegretiSbloccati(g({ serieConferme: 30, presenze: 15 })).map((b) => b.def.id),
["s-mai-forfait"],
"presenze esattamente al confine, serieConferme abbondante: sblocca",
);
// --- collezioneBadge ---------------------------------------------------------
const vuota = collezioneBadge(g());
@@ -262,7 +221,6 @@ assert.equal(
palloni: 10,
presenze: 30,
serieAllenamenti: 10,
serieConferme: 15,
}),
),
null,
+1 -2
View File
@@ -19,7 +19,6 @@ function g(valori: Partial<Giocatore> = {}): Giocatore {
streak: 0,
serieAllenamenti: 0,
seriePartite: 0,
serieConferme: 0,
...valori,
};
}
@@ -57,7 +56,7 @@ assert.ok(
// --- serie: solo sul traguardo esatto ----------------------------------------
assert.ok(ids(g({ serieAllenamenti: 3 })).includes("serie:allenamenti:3"));
assert.ok(!ids(g({ serieAllenamenti: 4 })).includes("serie:allenamenti:4"), "4 non è un traguardo");
assert.ok(ids(g({ serieConferme: 8 })).includes("serie:conferme:8"));
assert.ok(ids(g({ seriePartite: 5 })).includes("serie:partite:5"));
// --- obiettivi di squadra: uno solo, tra il 90% e il 99% --------------------
const obiettivo = (id: string, valore: number): ObiettivoSquadra => ({
+1 -84
View File
@@ -7,7 +7,6 @@ import {
contaPresenzeGiocatore,
destinatariSollecito,
includeInfortunato,
serieConferme,
serieConsecutiva,
testoSollecito,
totaliEventiGiocatore,
@@ -218,91 +217,9 @@ assert.ok(
"senza luogo la riga manca",
);
// --- serie conferme: risposta entro 24h dalla convocazione --------------------
const convocati = (id: string, data: string, creatoIl?: string): Evento => ({
...ev(id, "allenamento", data),
...(creatoIl === undefined ? {} : { creatoIl }),
});
const conConvocazione: Evento[] = [
convocati("c1", "2026-08-06", "2026-08-01T10:00:00Z"),
convocati("c2", "2026-08-13", "2026-08-08T10:00:00Z"),
convocati("c3", "2026-08-20", "2026-08-15T10:00:00Z"),
convocati("c4", "2026-08-27"), // evento generato dal client: nessuna convocazione tracciata
convocati("c5", "2026-09-10", "2026-09-05T10:00:00Z"), // futuro
];
const tempi: MappaTempiRisposta = {
c1: { g1: "2026-08-01T11:00:00Z" }, // un'ora dopo
c2: { g1: "2026-08-10T10:00:00Z" }, // due giorni dopo: buco
c3: { g1: "2026-08-16T09:59:00Z" }, // appena dentro le 24h
c5: { g1: "2026-09-05T10:30:00Z" },
};
assert.equal(serieConferme("g1", conConvocazione, tempi, OGGI), 1, "il ritardo su c2 azzera");
assert.equal(
serieConferme("g1", conConvocazione.slice(0, 1), tempi, OGGI),
1,
"una risposta rapida vale 1",
);
assert.equal(
serieConferme("g1", [conConvocazione[0]!, conConvocazione[3]!, conConvocazione[2]!], tempi, OGGI),
2,
"un evento senza istante di convocazione viene saltato, non spezza la serie",
);
assert.equal(serieConferme("g2", conConvocazione, tempi, OGGI), 0, "chi non risponde è a zero");
// Chi non è convocato non spezza la serie conferme di nessun altro (stesso filtro condiviso
// con `serieConsecutiva`, vedi sopra).
const c6Ristretto: Evento = {
...convocati("c6", "2026-08-08", "2026-08-03T10:00:00Z"),
convocati: ["g9"],
};
assert.equal(
serieConferme("g1", [conConvocazione[0]!, c6Ristretto], tempi, OGGI),
1,
"un evento convocato solo per un altro giocatore resta fuori, la serie di g1 non ne risente",
);
// Le conferme contano partite e allenamenti insieme: nessuna versione per tipo.
const mistiTipo: Evento[] = [
convocati("m1", "2026-08-06", "2026-08-01T10:00:00Z"),
{ ...convocati("m2", "2026-08-13", "2026-08-08T10:00:00Z"), tipo: "partita" },
];
const tempiMisti: MappaTempiRisposta = {
m1: { g1: "2026-08-01T11:00:00Z" }, // un'ora dopo
m2: { g1: "2026-08-08T11:00:00Z" }, // un'ora dopo, ma è una partita
};
assert.equal(
serieConferme("g1", mistiTipo, tempiMisti, OGGI),
2,
"partite e allenamenti si sommano nella stessa serie",
);
// Il confronto con le 24h è inclusivo: esattamente al bordo conta, un secondo oltre azzera.
const bordo: Evento[] = [convocati("b1", "2026-08-06", "2026-08-01T10:00:00Z")];
assert.equal(
serieConferme("g1", bordo, { b1: { g1: "2026-08-02T10:00:00Z" } }, OGGI),
1,
"esattamente 24h dopo: il confronto è <=, quindi conta",
);
assert.equal(
serieConferme("g1", bordo, { b1: { g1: "2026-08-02T10:00:01Z" } }, OGGI),
0,
"un secondo oltre le 24h azzera",
);
// Un evento futuro non conta ancora, anche con una risposta rapidissima già registrata.
const futuro: Evento[] = [convocati("f1", "2026-09-10", "2026-09-05T10:00:00Z")];
assert.equal(
serieConferme("g1", futuro, { f1: { g1: "2026-09-05T10:05:00Z" } }, OGGI),
0,
"l'evento di domani non è ancora passato, non entra nel calcolo",
);
// --- cache locale dopo una risposta: il cronometro non riparte ----------------
// Stessa regola del database: `risposto_il` è la PRIMA risposta e un trigger la congela.
// Qui la cache deve imitarla, altrimenti la serie "Conferme 24h" mente fino al refresh.
// Qui la cache deve imitarla, altrimenti la cache mente fino al refresh.
const PRIMA = "2026-08-01T10:00:00Z";
const POI = "2026-08-08T10:00:00Z";
+6 -6
View File
@@ -4,7 +4,7 @@ import { giocatori, type Giocatore } from "@/lib/crapp-data";
import { aggiornaSerie, serieDefs, serieGiocatore, serieMigliore, statoSerie } from "@/lib/serie";
function g(valori: Partial<Giocatore> = {}): Giocatore {
return { ...giocatori[0]!, serieAllenamenti: 0, seriePartite: 0, serieConferme: 0, ...valori };
return { ...giocatori[0]!, serieAllenamenti: 0, seriePartite: 0, ...valori };
}
const allenamenti = serieDefs.find((d) => d.tipo === "allenamenti")!;
@@ -45,16 +45,16 @@ assert.deepEqual([fuoriScala.prossimo, fuoriScala.manca, fuoriScala.progresso],
assert.equal(fuoriScala.messaggio, "Serie leggendaria: sei fuori scala!");
// --- serieGiocatore / serieMigliore ------------------------------------------
const tutte = serieGiocatore(g({ serieAllenamenti: 4, seriePartite: 1, serieConferme: 9 }));
assert.equal(tutte.length, serieDefs.length, "tre serie indipendenti");
const tutte = serieGiocatore(g({ serieAllenamenti: 4, seriePartite: 6 }));
assert.equal(tutte.length, serieDefs.length, "due serie indipendenti");
assert.deepEqual(
tutte.map((s) => s.valore),
[4, 1, 9],
[4, 6],
"ogni serie legge il proprio contatore",
);
const migliore = serieMigliore(g({ serieAllenamenti: 4, seriePartite: 1, serieConferme: 9 }));
assert.equal(migliore.def.tipo, "conferme", "in home si mostra la serie più lunga");
const migliore = serieMigliore(g({ serieAllenamenti: 4, seriePartite: 6 }));
assert.equal(migliore.def.tipo, "partite", "in home si mostra la serie più lunga");
assert.ok(serieMigliore(g()), "anche a zero c'è sempre una serie da mostrare");
// --- invarianti sulle definizioni --------------------------------------------