From 3038d750455652811975378715c5e36479e0eb71 Mon Sep 17 00:00:00 2001 From: solocla Date: Fri, 16 Oct 2026 15:54:23 +0200 Subject: [PATCH] sommario update --- public/sommario.php | 93 +++++++++++++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 25 deletions(-) diff --git a/public/sommario.php b/public/sommario.php index 611187a..915f31b 100644 --- a/public/sommario.php +++ b/public/sommario.php @@ -26,6 +26,7 @@ $SOGLIE = [ 'classi_deboli' => ['verde' => 0, 'giallo' => 2], // n. classi sotto il 40% di riempimento 'trend' => ['verde' => -5, 'giallo' => -20], // variazione % rispetto al periodo prima 'soglia_debole' => 40, // sotto questo riempimento una classe è "debole" + 'giorni_rinnovo' => 30, // giorni concessi dopo la scadenza per rinnovare ]; if (!function_exists('e')) { @@ -98,7 +99,7 @@ $periodi = [ '180' => 'Ultimi 6 mesi', '365' => 'Ultimi 12 mesi', ]; -$giorni = isset($periodi[$_GET['p'] ?? '']) ? (int) $_GET['p'] : 30; +$giorni = isset($periodi[$_GET['p'] ?? '']) ? (int) $_GET['p'] : 90; $oggi = new DateTime('today'); $fine = (clone $oggi)->modify('+1 day'); // escluso @@ -112,7 +113,7 @@ $now = date('Y-m-d H:i:s'); * Tutte le metriche di un periodo [$d1, $d2). * Le lezioni considerano solo quelle già svolte (fino ad adesso). */ -function metriche(PDO $pdo, string $d1, string $d2, string $now, int $sogliaDebole): array +function metriche(PDO $pdo, string $d1, string $d2, string $now, int $sogliaDebole, int $grazia = 30): array { $m = []; $fineLezioni = min($d2, $now); @@ -194,14 +195,22 @@ function metriche(PDO $pdo, string $d1, string $d2, string $now, int $sogliaDebo $m['last_minute_pct'] = null; } - // --- Riacquisto: chi ha un pacchetto scaduto nel periodo, ha comprato di nuovo? + // --- Rinnovo: chi ha finito un pacchetto ne ha comprato un altro? + // Contano i pacchetti scaduti da almeno $grazia giorni (prima non è ancora "mancato rinnovo"), + // quindi la finestra è spostata indietro di $grazia giorni. Così un pacchetto da 12 lezioni + // (circa 3 mesi) viene valutato solo quando è davvero finito, qualunque sia il periodo scelto. + // Rinnovato = un nuovo ordine dopo quello scaduto, entro $grazia giorni dalla scadenza. + $g1 = date('Y-m-d', strtotime(substr($d1, 0, 10) . " -{$grazia} days")); + $g2 = date('Y-m-d', strtotime(substr($d2, 0, 10) . " -{$grazia} days")); $scad = q($pdo, "SELECT o.iduser, MAX(EXISTS(SELECT 1 FROM orderbook o2 WHERE o2.iduser = o.iduser AND o2.idorderbook <> o.idorderbook - AND o2.order_date_created > o.order_date_created)) AS riacquistato + AND o2.order_date_created > o.order_date_created + AND o2.order_date_created <= DATE_ADD(o.expireon, INTERVAL ? DAY))) AS riacquistato FROM orderbook o - WHERE o.iduser IS NOT NULL AND o.expireon >= ? AND o.expireon < ? AND o.expireon < CURDATE() - GROUP BY o.iduser", [substr($d1, 0, 10), substr($d2, 0, 10)]); + WHERE o.iduser IS NOT NULL AND o.expireon >= ? AND o.expireon < ? + AND o.expireon <= DATE_SUB(CURDATE(), INTERVAL ? DAY) + GROUP BY o.iduser", [$grazia, $g1, $g2, $grazia]); $m['scaduti'] = count($scad); $m['riacquisto'] = pctv(count(array_filter($scad, fn($r) => (int) $r['riacquistato'] === 1)), count($scad)); @@ -226,8 +235,26 @@ $d1 = $inizio->format('Y-m-d 00:00:00'); $d2 = $fine->format('Y-m-d 00:00:00'); $d1p = $inizioP->format('Y-m-d 00:00:00'); -$A = metriche($pdo, $d1, $d2, $now, $SOGLIE['soglia_debole']); -$P = metriche($pdo, $d1p, $d1, $now, $SOGLIE['soglia_debole']); +$A = metriche($pdo, $d1, $d2, $now, $SOGLIE['soglia_debole'], $SOGLIE['giorni_rinnovo']); +$P = metriche($pdo, $d1p, $d1, $now, $SOGLIE['soglia_debole'], $SOGLIE['giorni_rinnovo']); + +// Vendite e presenze sono stagionali (settembre pieno, estate vuota): se ci sono dati, +// le confrontiamo con lo STESSO periodo dell'anno scorso invece che con quello appena prima. +$d1y = date('Y-m-d H:i:s', strtotime($d1 . ' -1 year')); +$d2y = date('Y-m-d H:i:s', strtotime($d2 . ' -1 year')); +$primoOrdine = q1($pdo, "SELECT MIN(order_date_created) FROM orderbook"); +$haAnnoPrima = $primoOrdine && $primoOrdine <= substr($d1y, 0, 10); +$Y = $haAnnoPrima ? metriche($pdo, $d1y, $d2y, $d2y, $SOGLIE['soglia_debole'], $SOGLIE['giorni_rinnovo']) : $P; +$etichettaY = $haAnnoPrima ? 'stesso periodo ' . date('Y', strtotime($d1y)) : 'periodo prima'; + +/** Abbonati con un pacchetto valido in una certa data (non dipende dalla durata dei pacchetti). */ +function abbonati(PDO $pdo, string $data): int +{ + return (int) q1($pdo, "SELECT COUNT(DISTINCT iduser) FROM orderbook + WHERE iduser IS NOT NULL AND order_date_created <= ? AND expireon >= ?", [$data, $data]); +} +$abbOggi = abbonati($pdo, date('Y-m-d')); +$abbPrima = $haAnnoPrima ? abbonati($pdo, date('Y-m-d', strtotime('-1 year'))) : abbonati($pdo, substr($d1, 0, 10)); // --- Certificati (situazione di oggi) $cert = q($pdo, "SELECT COUNT(*) AS attivi, @@ -257,17 +284,18 @@ $add = function (string $titolo, string $icona, string $valore, string $stato, ? }; /** Testo del confronto con il periodo precedente. */ -$conf = function ($cur, $prev, string $unita = '', bool $punti = false): ?string { +$conf = function ($cur, $prev, string $unita = '', bool $punti = false, string $rif = 'prima'): ?string { if ($prev === null || $cur === null) return null; + $tail = ' (' . $rif . ': ' . n0($prev) . $unita . ')'; if ($punti) { $d = round((float) $cur - (float) $prev, 1); - if (abs($d) < 0.5) return '= stabile (prima ' . n0($prev) . $unita . ')'; - return ($d > 0 ? '▲ +' : '▼ ') . number_format($d, 0, ',', '.') . ' punti (prima ' . n0($prev) . $unita . ')'; + if (abs($d) < 0.5) return '= stabile' . $tail; + return ($d > 0 ? '▲ +' : '▼ ') . number_format($d, 0, ',', '.') . ' punti' . $tail; } $d = delta($cur, $prev); - if ($d === null) return 'prima: ' . n0($prev); - if (abs($d) < 1) return '= stabile (prima ' . n0($prev) . ')'; - return ($d > 0 ? '▲ +' : '▼ ') . number_format($d, 0, ',', '.') . '% (prima ' . n0($prev) . ')'; + if ($d === null) return $rif . ': ' . n0($prev); + if (abs($d) < 1) return '= stabile' . $tail; + return ($d > 0 ? '▲ +' : '▼ ') . number_format($d, 0, ',', '.') . '%' . $tail; }; $trendStato = function ($cur, $prev) use ($SOGLIE): string { @@ -290,15 +318,30 @@ $add( n0($A['lezioni_svolte']) . ' lezioni svolte · ' . n0($A['piene_pct'] ?? 0) . '% al completo' ); -// 2. Lezioni vendute +// 2. Abbonati attivi oggi (indipendente dalla durata dei pacchetti) +$add( + 'Abbonati attivi', + 'fa-id-card', + n0($abbOggi), + $trendStato($abbOggi, $abbPrima), + $conf($abbOggi, $abbPrima, '', false, $haAnnoPrima ? 'un anno fa' : 'inizio periodo'), + [ + 'Il numero di persone con un pacchetto valido tiene o cresce.', + 'Ci sono meno abbonati attivi: punta sui rinnovi di chi è in scadenza.', + 'Gli abbonati attivi sono in forte calo: è l\'indicatore più importante da recuperare.', + ], + 'con un pacchetto valido oggi' +); + +// 3. Lezioni vendute $add( 'Lezioni vendute', 'fa-ticket-alt', n0($A['vendute']), - $trendStato($A['vendute'], $P['vendute']), - $conf($A['vendute'], $P['vendute']), + $trendStato($A['vendute'], $Y['vendute']), + $conf($A['vendute'], $Y['vendute'], '', false, $etichettaY), [ - 'Le vendite tengono o crescono rispetto al periodo prima.', + 'Le vendite tengono o crescono rispetto al ' . $etichettaY . '.', 'Vendite in calo: una comunicazione o un\'offerta a chi ha il pacchetto in scadenza può aiutare.', 'Vendite in forte calo: serve un\'azione commerciale (promozione, evento, newsletter).', ], @@ -310,8 +353,8 @@ $add( 'Nuovi clienti', 'fa-user-plus', n0($A['nuovi']), - $trendStato($A['nuovi'], $P['nuovi']), - $conf($A['nuovi'], $P['nuovi']), + $trendStato($A['nuovi'], $Y['nuovi']), + $conf($A['nuovi'], $Y['nuovi'], '', false, $etichettaY), [ 'Arrivano persone nuove: la scuola si fa conoscere.', 'Meno nuovi ingressi del solito: spingi lezioni di prova e passaparola.', @@ -325,11 +368,11 @@ $add( 'Persone che praticano', 'fa-users', n0($A['persone']), - $trendStato($A['persone'], $P['persone']), - $conf($A['persone'], $P['persone']), + $trendStato($A['persone'], $Y['persone']), + $conf($A['persone'], $Y['persone'], '', false, $etichettaY), [ 'La comunità che pratica è stabile o in crescita.', - 'Meno persone in sala del periodo prima: contatta chi non viene da un po\'.', + 'Meno persone in sala rispetto al ' . $etichettaY . ': contatta chi non viene da un po\'.', 'Calo forte di persone attive: verifica abbandoni e pacchetti non rinnovati.', ], 'almeno una lezione frequentata' @@ -347,7 +390,7 @@ $add( 'Una parte non rinnova: un promemoria prima della scadenza può fare la differenza.', 'Molti non rinnovano: chiedi un riscontro a chi non torna e prepara un\'offerta di rinnovo.', ], - n0($A['scaduti']) . ' clienti con pacchetto scaduto nel periodo' + 'entro ' . $SOGLIE['giorni_rinnovo'] . ' giorni dalla scadenza · ' . n0($A['scaduti']) . ' pacchetti finiti, valutati solo dopo ' . $SOGLIE['giorni_rinnovo'] . ' giorni' ); // 6. Lezioni perse @@ -971,7 +1014,7 @@ $prevTxt = $inizioP->format('d/m/Y') . ' – ' . (clone $inizio)->modify('-1
- · confronto con + · confronto con (vendite e presenze: )