update
This commit is contained in:
97
backend/ledger.py
Normal file
97
backend/ledger.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""Gedächtnis der Inhaltsprüfungen: was einmal beurteilt wurde, bleibt beurteilt.
|
||||
|
||||
Ein Urteil über eine Codestelle kostet sechs Anfragen und fällt bei Grenzfällen mal
|
||||
so, mal so. Genau daran scheiterte die Wiederholbarkeit: derselbe Stand lieferte in
|
||||
Runde 1 einen Befund, in Runde 2 keinen, in Runde 3 wieder einen.
|
||||
|
||||
Gespeichert wird deshalb das Urteil, nicht nur das Ergebnis. Der nächste Lauf fragt
|
||||
nur noch, was er nicht schon weiß. Ein Eintrag gilt weiter, solange seine
|
||||
Voraussetzung nachweisbar dasteht:
|
||||
|
||||
- ein Beleg, solange die zitierte Zeile noch in der Datei steht
|
||||
- eine Abdeckung, solange die nennende Funktion noch auf einer Karte steht
|
||||
- eine Fehlmeldung, solange keine passende Funktion dazugekommen ist
|
||||
|
||||
Fällt die Voraussetzung weg, fällt der Eintrag weg und wird neu gefragt. Damit
|
||||
bleibt gleicher Stand gleiches Ergebnis, und geänderter Stand wird wirklich neu
|
||||
beurteilt.
|
||||
|
||||
Die Datei liegt im Projektordner. Sie wandert beim Umbenennen mit und verschwindet
|
||||
beim Löschen — ohne Zutun, weil beides den Ordner bewegt. Wer von vorn anfangen
|
||||
will, löscht sie; der nächste Lauf fragt dann wieder alles.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
import projekte
|
||||
|
||||
DATEI = ".pruefung.json"
|
||||
|
||||
# Ein Fach je Frage. Getrennt, weil sie unterschiedlich ungültig werden.
|
||||
FAECHER = (
|
||||
"belege", # Funktion -> Codestelle, die sie umsetzt (Prüfung 2)
|
||||
"ohne_code", # Funktion -> kein Code gefunden (Prüfung 2)
|
||||
"paare", # Funktionspaar -> Dublette oder verschieden (Prüfung 2)
|
||||
"abdeckung", # Codestelle -> Funktion, die sie beschreibt (Prüfung 3)
|
||||
"ohne_karte", # Codestelle -> keine Funktion beschreibt sie (Prüfung 3)
|
||||
)
|
||||
|
||||
|
||||
def _pfad(projekt: str):
|
||||
return projekte.wurzel(projekt) / DATEI
|
||||
|
||||
|
||||
def lade(projekt: str) -> dict:
|
||||
"""Der gespeicherte Stand. Kaputte oder fehlende Datei heißt: nichts gemerkt."""
|
||||
try:
|
||||
daten = json.loads(_pfad(projekt).read_text())
|
||||
except (OSError, ValueError):
|
||||
daten = {}
|
||||
return {fach: dict(daten.get(fach) or {}) for fach in FAECHER}
|
||||
|
||||
|
||||
def sichere(projekt: str, stand: dict):
|
||||
"""Sortiert und eingerückt — die Datei soll sich lesen und vergleichen lassen.
|
||||
|
||||
Erst daneben schreiben, dann umbenennen. Ein Absturz mitten im Schreiben ließe
|
||||
sonst eine halbe Datei zurück, und das Gedächtnis eines ganzen Laufs wäre weg —
|
||||
teuer bezahlt und nicht wiederherstellbar.
|
||||
"""
|
||||
text = json.dumps({fach: stand.get(fach) or {} for fach in FAECHER},
|
||||
indent=1, ensure_ascii=False, sort_keys=True)
|
||||
ziel = _pfad(projekt)
|
||||
neben = ziel.with_suffix(".json.neu")
|
||||
neben.write_text(text + "\n")
|
||||
neben.replace(ziel)
|
||||
|
||||
|
||||
def abdruck(*teile: str) -> str:
|
||||
"""Kurzer Fingerabdruck über den Stand, der ein Urteil trägt."""
|
||||
roh = json.dumps([t or "" for t in teile], ensure_ascii=False).encode()
|
||||
return hashlib.sha1(roh).hexdigest()[:16]
|
||||
|
||||
|
||||
# Schlüssel sind JSON-Listen. Ein Trennzeichen wäre einfacher, aber Kartennamen und
|
||||
# Funktionstitel dürfen alles enthalten — und ein Schlüssel, der sich nicht wieder
|
||||
# zerlegen lässt, ist keiner.
|
||||
|
||||
def schluessel(*teile: str) -> str:
|
||||
return json.dumps(list(teile), ensure_ascii=False)
|
||||
|
||||
|
||||
def paarschluessel(a: str, b: str) -> str:
|
||||
return json.dumps(sorted((a, b)), ensure_ascii=False)
|
||||
|
||||
|
||||
def teile(wert: str) -> list:
|
||||
try:
|
||||
gelesen = json.loads(wert)
|
||||
except ValueError:
|
||||
return []
|
||||
return gelesen if isinstance(gelesen, list) else []
|
||||
|
||||
|
||||
def behalte(fach: dict, gueltig: set) -> dict:
|
||||
"""Einträge zu verschwundenen Stellen wegräumen — sonst wächst die Datei ewig."""
|
||||
return {k: v for k, v in fach.items() if k in gueltig}
|
||||
Reference in New Issue
Block a user