121 lines
4.6 KiB
Python
121 lines
4.6 KiB
Python
#!/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()
|