Separate player presence from scout stats

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Cacciari
2026-09-01 22:42:35 +02:00
co-authored by Cursor
parent d63beae04c
commit c52e4c4e27
3 changed files with 46 additions and 26 deletions
+29 -11
View File
@@ -1,15 +1,18 @@
import { useMemo } from "react";
import { giocatori, type Giocatore } from "./crapp-data";
import { giocatoriConScout, useScoutMatches } from "./scout-store";
import { useVotiMvp, vincitoriMvp } from "./mvp-voti";
import { mvpVintiPerGiocatore, useVotiMvp } from "./mvp-voti";
import { mediePagelle, usePagelle } from "./pagelle";
import { statisticheCacche, useCacche } from "./cacche";
import { conteggioTurni } from "./palloni-core";
import { useTurniPalloni } from "./palloni";
import { useInfortuniERitardi } from "./infortuni";
import { useGiocatoreBase } from "./user-store";
import { useGiocatoreId } from "./user-store";
import { useEventi } from "./eventi";
import { useRispostePresenze } from "./presenze";
import {
contaPresenzeGiocatore,
totaliEventiGiocatore,
useRispostePresenze,
} from "./presenze";
import { obiettiviOrdinati } from "./obiettivi";
import { useCsi } from "./csi";
import { partiteGiocate } from "./csi-core";
@@ -20,12 +23,13 @@ import { partiteGiocate } from "./csi-core";
* nessuna query aggiuntiva rispetto a quelle che l'app fa comunque.
*/
export function useRosa(): Giocatore[] {
const scoutMatches = useScoutMatches();
const voti = useVotiMvp();
const { voti: pagelle } = usePagelle();
const { righe: cacche } = useCacche();
const { turni } = useTurniPalloni();
const { infortuni, ritardi } = useInfortuniERitardi();
const { eventi } = useEventi();
const { presenze: mappaPresenze } = useRispostePresenze();
const votiMvp = voti.data ?? [];
@@ -33,24 +37,38 @@ export function useRosa(): Giocatore[] {
const medie = mediePagelle(pagelle);
const statCacche = statisticheCacche(cacche);
const palloni = conteggioTurni(turni);
return giocatoriConScout(scoutMatches, vincitoriMvp(votiMvp)).map((g) => ({
const mvpVinti = mvpVintiPerGiocatore(votiMvp);
return giocatori.map((g) => ({
...g,
mediaVoto: medie[g.id]?.media ?? g.mediaVoto,
presenze: contaPresenzeGiocatore(g.id, eventi, mappaPresenze),
totaliEventi: totaliEventiGiocatore(g.id, eventi),
mvp: mvpVinti[g.id] ?? 0,
mediaVoto: medie[g.id]?.media ?? 0,
palloni: palloni[g.id] ?? 0,
cacche: statCacche[g.id]?.giornateTop ?? 0,
cacchePartita: statCacche[g.id]?.media ?? 0,
infortuni: infortuni[g.id] ?? 0,
ritardi: ritardi[g.id] ?? 0,
}));
}, [scoutMatches, votiMvp, pagelle, cacche, turni, infortuni, ritardi]);
}, [
votiMvp,
pagelle,
cacche,
turni,
infortuni,
ritardi,
eventi,
mappaPresenze,
]);
}
/** Il giocatore selezionato sul dispositivo, con le statistiche complete. */
export function useIo(): Giocatore | null {
const base = useGiocatoreBase();
const id = useGiocatoreId();
const rosa = useRosa();
if (!base) return null;
return rosa.find((g) => g.id === base.id) ?? base;
if (!id) return null;
return rosa.find((g) => g.id === id) ?? null;
}
/** Obiettivi collaborativi calcolati sui dati reali già in cache. */
+5 -7
View File
@@ -186,24 +186,22 @@ export function totaliSquadra(matches: ScoutMatch[]) {
return out;
}
/** Presenze e MVP ricavati dalle partite scoutate: nessuna statistica individuale offensiva.
* `mvpPerMatch` mappa idPartita -> nome dell'MVP eletto dalla squadra. */
/**
* MVP ricavati dalle partite scoutate (mappa matchId → nome vincitore).
* Le presenze personali restano su `risposte_presenze`, non sullo scout.
*/
export function giocatoriConScout(
matches: ScoutMatch[],
mvpPerMatch: Record<string, string> = {},
): Giocatore[] {
return giocatori.map((g) => {
let presenze = 0;
let mvp = 0;
for (const m of matches) {
if (totaliPerGiocatore(m.azioni).has(g.id)) presenze += 1;
if (mvpPerMatch[m.id] === g.nome) mvp += 1;
}
return {
...g,
mvp: g.mvp + mvp,
presenze: g.presenze + presenze,
totaliEventi: g.totaliEventi + matches.length,
mvp,
};
});
}
+12 -8
View File
@@ -59,20 +59,24 @@ assert.deepEqual(totaliSquadra([match("m1", 3, 0, azioni), match("m2", 3, 1, azi
});
assert.deepEqual(totaliSquadra([]), { punti: 0, ace: 0, muri: 0, errori: 0 });
// --- giocatoriConScout: presenze e MVP, nessuna statistica offensiva ---------
// --- giocatoriConScout: solo MVP da scout, presenze separate -----------------
const base = giocatori.find((g) => g.id === "g1")!;
const conScout = giocatoriConScout([match("m1", 3, 0, azioni)], { m1: base.nome });
const dopo = conScout.find((g) => g.id === "g1")!;
assert.equal(dopo.presenze, base.presenze + 1, "chi ha azioni risulta presente");
assert.equal(dopo.mvp, base.mvp + 1, "l'MVP eletto viene sommato");
assert.equal(dopo.totaliEventi, base.totaliEventi + 1);
assert.equal(dopo.presenze, 0, "le azioni scout non alimentano le presenze personali");
assert.equal(dopo.mvp, 1, "l'MVP eletto viene contato");
assert.equal(dopo.totaliEventi, 0, "totaliEventi arriva dagli eventi CrAPP, non dallo scout");
const assente = conScout.find((g) => g.id === "g3")!;
const baseAssente = giocatori.find((g) => g.id === "g3")!;
assert.equal(assente.presenze, baseAssente.presenze, "chi non ha azioni non guadagna presenze");
assert.equal(assente.totaliEventi, baseAssente.totaliEventi + 1, "l'evento conta per tutti");
assert.equal(assente.presenze, 0, "senza risposte presenze resta a zero");
assert.equal(assente.mvp, 0);
assert.equal(assente.totaliEventi, 0);
assert.deepEqual(giocatoriConScout([]), giocatori, "senza partite la rosa resta invariata");
const senzaPartite = giocatoriConScout([]);
assert.ok(
senzaPartite.every((g) => g.presenze === 0 && g.mvp === 0 && g.totaliEventi === 0),
"senza partite scout le statistiche restano a zero",
);
// --- metadati azioni ---------------------------------------------------------
for (const [tipo, meta] of Object.entries(azioniMeta)) {