merge lavoro/goldrake-u8 (secondo giro 27/09)

This commit is contained in:
2026-09-27 15:40:42 +00:00
6 changed files with 291 additions and 6 deletions
+46
View File
@@ -49,6 +49,9 @@ import numpy as np
MIN_COVERAGE = 0.80 # sotto: la serie ha buchi -> non ci si decide sopra un gate
MIN_BARS_PER_JUDGE = 2 # con una barra sola non esiste ne' coda ne' copertura
GRAZIA_PREMATURO_MIN = 5.0 # tolleranza fra chiusura della barra e mtime del file (clock/IO)
SOGLIA_ORARI_H = 6.0 # (#30) max_age_h <= questa soglia = "sorvegliante orario": i tre
# con max_age 3h (cuscino_watch, venue_watch, collect_chain) oggi,
# derivato dalla tabella e non da una lista di nomi cablata
@dataclass(frozen=True)
@@ -244,3 +247,46 @@ def alerts_from(rows: list[dict]) -> list[str]:
msg += f" | METTE A RISCHIO il gate {r['gate']}"
out.append(msg)
return out
# ===========================================================================
# GIRO ORARIO (#30). Il giro giornaliero (cron_daily) resta com'e': report completo, avviso
# sempre. I sorveglianti a max_age <= 6h (oggi cuscino_watch, venue_watch, collect_chain) si
# scoprono fermi fino a 24h dopo se nessuno li guarda fuori da quel giro: qui si guardano ogni
# ora, ma si notifica solo cio' che e' NUOVO rispetto all'ultimo avviso (niente Telegram a
# raffica sullo stesso guasto fermo). Tutto PURO: il marcatore lo legge/scrive lo script.
# ===========================================================================
def monitors_orari(monitors: tuple[MonitorSpec, ...] = MONITORS,
soglia_h: float = SOGLIA_ORARI_H) -> tuple[MonitorSpec, ...]:
"""I monitor con cadenza di controllo ORARIA: max_age_h <= soglia_h. Derivato dalla tabella
`MONITORS`, non da una lista di nomi cablata: aggiungere un monitor orario lo include qui
senza toccare questa funzione."""
return tuple(m for m in monitors if m.max_age_h <= soglia_h)
def chiave_alert(row: dict) -> str:
"""Chiave stabile di un alert per il marcatore 'gia' detto': nome + stato, NON il testo
(`why` contiene l'eta' dell'ultima barra, che cambia a ogni giro anche se il guasto e' lo
stesso)."""
return f"{row['name']}:{row['status']}"
def alert_nuovi(rows: list[dict], detti: set[str]) -> list[dict]:
"""Le righe-alert (non OK, non NUOVO — vedi `alerts_from`) la cui chiave NON e' nel
marcatore `detti`: quelle davvero nuove, le sole da notificare."""
return [r for r in rows if r["status"] not in ("OK", "NUOVO") and chiave_alert(r) not in detti]
def marcatore_aggiornato(rows: list[dict], detti: set[str]) -> set[str]:
"""Il marcatore dopo questo giro: le chiavi dei monitor ancora in alert restano/si aggiungono,
quelle dei monitor tornati OK si TOLGONO — cosi' un nuovo guasto sullo stesso monitor riparte
con un nuovo avviso invece di restare 'gia' detto' per sempre. Non scrive nulla: lo fa lo
script, e solo se la notifica e' partita (stessa regola di fee_watch #29)."""
out = set(detti)
for r in rows:
chiavi_monitor = {k for k in out if k.startswith(f"{r['name']}:")}
if r["status"] == "OK":
out -= chiavi_monitor
elif r["status"] != "NUOVO":
out.add(chiave_alert(r))
return out