This commit is contained in:
team3
2026-07-28 08:36:49 +02:00
parent 7912eae715
commit 58efba988d
16 changed files with 521 additions and 332 deletions

View File

@@ -403,16 +403,21 @@ def _konsens(fassungen: list[dict]) -> dict:
return max(fassungen, key=naehe)
def _mehrheit(proben: list[dict]) -> dict:
"""Nur was mehrere Stimmen unabhängig bemängeln, wird ein Vorschlag.
def _mehrheit(proben: list[dict], gemessen: tuple = ()) -> dict:
"""Nur was alle Stimmen unabhängig bemängeln, wird ein Vorschlag.
Bei Grenzfällen urteilt ein Modell mal so, mal so — einzelne Stimmen führten
dazu, dass jede Runde andere Felder aufschlugen und der Lauf nie zur Ruhe kam.
Gemessene Verstöße sind davon ausgenommen. Sie stehen fest, bevor gefragt wird;
zur Abstimmung steht nur noch der Wortlaut. Vorher verschwand ein gezählter
Verstoß, sobald eine einzige Stimme das Feld nicht anfasste.
"""
zaehler = Counter(feld for p in proben for feld in p)
return {
feld: _konsens([p[feld] for p in proben if feld in p])
for feld, treffer in zaehler.items() if treffer >= MEHRHEIT
for feld, treffer in zaehler.items()
if treffer >= (1 if feld in gemessen else MEHRHEIT)
}
@@ -426,7 +431,18 @@ async def _neue_felder(system: str, aufgabe: dict, offen: list, erlaubt: tuple,
gute = [p for p in proben if isinstance(p, dict)]
if not gute:
raise next(p for p in proben if isinstance(p, BaseException))
return _mehrheit(gute)
gemessen = tuple({b["feld"] for b in offen})
vorschlaege = _mehrheit(gute, gemessen)
# Bleibt ein gezählter Verstoß ohne Vorschlag, greift der eigene Ersatz des
# Messers. Sonst bliebe ein Fehler stehen, den der Code sicher kennt.
for b in offen:
if b["feld"] not in vorschlaege and b["vorschlag"]:
alt = struktur._zeilen(b["feld"], aufgabe["felder"].get(b["feld"], ""))
neu = [b["vorschlag"] if z == b["original"] else z for z in alt]
vorschlaege[b["feld"]] = {"neu": "\n".join(neu), "grund": b["problem"],
"regel": "Vom System gemessen"}
return vorschlaege
async def _pruefe(stil: dict, aufgabe: dict) -> list[dict]:
@@ -457,38 +473,73 @@ async def _stil_aufgaben(projekt: str):
"""Prüfstellen der Stilprüfung samt der Kataloge, die sie brauchen."""
stil = {b: _stil_lesen(b) for b in BEREICHE}
liste = [a for f in features.liste(projekt) for a in aufgaben(f)]
return liste, lambda a: _pruefe(stil, a)
return liste, lambda a, _zaehl: _pruefe(stil, a)
async def _inhalt_aufgaben(projekt: str):
"""Ein Abgleich über alle Karten — Vollständigkeit ist eine Eigenschaft der
Menge, nicht der einzelnen Karte."""
def _quelle(projekt: str):
alle = features.liste(projekt)
quelle = quellcode.dateien()
if not quelle:
raise ValueError("Kein Quelltext gefunden")
return alle, quelle
async def _feature_aufgaben(projekt: str):
"""Ein Lauf über alle Funktionen: steht hinter jeder auch Code?
Ein Aufruf statt vieler, weil das Ergebnis je Karte gebündelt wird — zwei
Vorschläge für dieselbe Funktionsliste wären nicht unabhängig anwendbar.
Der Zähler läuft trotzdem je Funktion mit.
"""
alle, quelle = _quelle(projekt)
text = quellcode.als_text(quelle)
anzahl = sum(len(f["sektionen"]["funktionen"]) for f in alle)
async def abgleich(_):
probleme, _inv = await inhaltspruefung.pruefe(alle, quelle, text)
return probleme
async def abgleich(_, zaehl):
return await inhaltspruefung.pruefe_feature_im_code(alle, quelle, text, zaehl)
return [{"feature": "", "funktion": ""}], abgleich
return _platzhalter(anzahl), abgleich
ARTEN = {"stil": _stil_aufgaben, "inhalt": _inhalt_aufgaben}
async def _code_aufgaben(projekt: str):
"""Ein Lauf über alle Bedienstellen — Vollständigkeit ist eine Eigenschaft der
Menge, nicht der einzelnen Karte."""
alle, quelle = _quelle(projekt)
punkte = quellcode.interaktionen(quelle)
async def abgleich(_, zaehl):
return await inhaltspruefung.pruefe_code_im_feature(alle, quelle, zaehl)
return _platzhalter(len(punkte)), abgleich
def _platzhalter(anzahl: int) -> list:
"""Eine echte Aufgabe, der Rest nur für den Fortschrittsbalken."""
return [True] + [None] * max(0, anzahl - 1)
ARTEN = {
"stil": _stil_aufgaben,
"feature": _feature_aufgaben, # Feature im Code: was steht ohne Code da?
"code": _code_aufgaben, # Code im Feature: was fehlt auf den Karten?
}
async def _lauf(projekt: str, liste: list[dict], arbeite):
lauf = _laeufe[projekt]
schranke = asyncio.Semaphore(config.PRUEFUNG_PARALLEL)
def zaehl():
lauf["fertig"] += 1
async def eine(aufgabe):
if aufgabe is None:
return # Platzhalter: zählt der Sammellauf selbst hoch
async with schranke:
if _laeufe.get(projekt) is not lauf:
return # verworfen (Panel geschlossen) — keine weitere Anfrage
try:
lauf["probleme"].extend(await arbeite(aufgabe))
lauf["probleme"].extend(await arbeite(aufgabe, zaehl))
except Exception as e:
# Zählt trotzdem als erledigt, sonst hängt der Fortschritt.
# Die Meldung nennt die Zahl, damit „100 %" nicht vollständig vortäuscht.
@@ -496,7 +547,8 @@ async def _lauf(projekt: str, liste: list[dict], arbeite):
lauf["meldung"] = (
f"{lauf['fehler']} von {lauf['gesamt']} Prüfungen fehlgeschlagen: {e}"
)
lauf["fertig"] += 1
if aufgabe is not True:
lauf["fertig"] += 1
try:
await asyncio.gather(*(eine(a) for a in liste))