Files

121 lines
4.6 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""quittungen.py — bekannte Befunde quittieren, damit Gruen wieder etwas bedeutet.
Hintergrund (#0104): Das Alarmprinzip der Gruppe ist "die rote Pipeline IST der
Alarm" (AGENTS.md). Das traegt nur, solange Gruen der Normalfall ist. Am
2026-08-18 waren alle drei geplanten Pruefungen dauerhaft rot — und ein neun Tage
alter, echter Ausfall blieb genau deshalb unbemerkt: ein rotes Kreuz mehr neben
roten Kreuzen faellt niemandem auf.
Eine Quittung nimmt einen bekannten Befund aus der Rot-Wertung. Sie verschweigt
ihn NICHT: er erscheint weiterhin im Log, mit Grund und Frist. Rot wird, was
nicht quittiert ist — also das Neue.
Damit die Liste nicht selbst zur naechsten Blindstelle wird (genau der Vorwurf,
den sich diese Plattform schon zweimal gemacht hat), gilt:
* Jede Quittung braucht eine Frist. Ist sie abgelaufen, quittiert sie nicht
mehr — der Befund faerbt wieder rot, mit Hinweis auf die abgelaufene Zeile.
* Dauerhaft (`ADR-00NN` statt Datum) geht nur mit Verweis auf einen
Entscheidungs-Record. Eine dauerhafte Ausnahme ohne ADR ist laut AGENTS.md
ohnehin ein Fehler; hier ist sie schlicht nicht formulierbar.
* Eine Quittung, auf die kein Befund mehr passt, wird gemeldet. Sonst sammelt
die Datei stillschweigend Zeilen fuer Probleme, die es nicht mehr gibt.
Format von befund_quittungen.tsv (Tab-getrennt, '#' ist Kommentar):
pruefung <TAB> muster <TAB> gueltig_bis <TAB> grund
`pruefung` ist der Geltungsbereich (`stillstand` oder `gruppe`). Ohne ihn meldet
jede Pruefung die Eintraege der jeweils anderen als wirkungslos — beim ersten
Lauf sofort passiert. `muster` ist eine Teilzeichenkette des Befundtextes (keine
Regex — bewusst, damit die Datei ohne Spezialwissen lesbar bleibt).
"""
from __future__ import annotations
import datetime as _dt
import re
from pathlib import Path
DATEI = "scripts/befund_quittungen.tsv"
def _lade(root: Path, pruefung: str) -> list[tuple[str, str, str]]:
pfad = root / DATEI
if not pfad.exists():
return []
zeilen = []
for roh in pfad.read_text(encoding="utf-8").splitlines():
if not roh.strip() or roh.lstrip().startswith("#"):
continue
teile = roh.split("\t")
if len(teile) < 4 or teile[0].strip() != pruefung:
continue
zeilen.append((teile[1].strip(), teile[2].strip(), "\t".join(teile[3:]).strip()))
return zeilen
def anwenden(befunde: list[str], root: Path, pruefung: str,
heute: _dt.date | None = None):
"""Teilt befunde in (offen, quittiert, meldungen).
offen -> faerben rot
quittiert -> Liste (Befund, Frist, Grund), nur Ausgabe
meldungen -> Hinweise zur Liste selbst (abgelaufen, wirkungslos)
"""
heute = heute or _dt.date.today()
regeln = _lade(root, pruefung)
offen: list[str] = []
quittiert: list[tuple[str, str, str]] = []
meldungen: list[str] = []
getroffen: set[int] = set()
for b in befunde:
treffer = None
for i, (muster, frist, grund) in enumerate(regeln):
if muster not in b:
continue
getroffen.add(i)
if re.fullmatch(r"ADR-\d{4}", frist):
treffer = (frist, grund)
break
try:
faellig = _dt.date.fromisoformat(frist)
except ValueError:
meldungen.append(
f"Quittung '{muster}': Frist '{frist}' ist weder ein Datum "
f"noch ein ADR-Verweis — sie quittiert nichts.")
continue
if faellig < heute:
meldungen.append(
f"Quittung '{muster}' ist am {frist} abgelaufen — der Befund "
f"zaehlt wieder. Verlaengern oder erledigen.")
continue
treffer = (frist, grund)
break
if treffer:
quittiert.append((b, treffer[0], treffer[1]))
else:
offen.append(b)
for i, (muster, _, _) in enumerate(regeln):
if i not in getroffen:
meldungen.append(
f"Quittung '{muster}' trifft auf keinen Befund mehr — Zeile "
f"entfernen, sonst verdeckt sie irgendwann etwas Neues.")
return offen, quittiert, meldungen
def ausgeben(quittiert, meldungen) -> None:
"""Einheitliche Ausgabe fuer beide Pruefungen."""
if quittiert:
print(f"Quittiert ({len(quittiert)}) — bekannt, faerbt nicht rot:")
for b, frist, grund in quittiert:
print(f" ~ {b}")
print(f" bis {frist}: {grund}")
print()
for m in meldungen:
print(f"HINWEIS {m}")
if meldungen:
print()