update
This commit is contained in:
@@ -2,16 +2,17 @@
|
||||
|
||||
Blindes Re-Filtern reproduziert die blinden Flecken der Pipeline (sie hat die Befunde ja
|
||||
durchgelassen). Hier fließen die QA-BEFUNDE als Input in gezielte Aktionen: Hygiene
|
||||
deterministisch, bestätigte Dubletten mergen (Zweitmeinung), Fremd/Unecht nur nach
|
||||
Gegen-Judge entfernen (fail-open: Zweifel/Fehler → behalten). Lücken brauchen Recherche,
|
||||
Verwaiste den nächsten Board-2-Lauf — beides wird nur ausgewiesen."""
|
||||
deterministisch (+ Beschreibung/Suffix per Sanierung/Rename), bestätigte Dubletten mergen
|
||||
(Zweitmeinung), Fremd/Unecht nur nach Gegen-Judge entfernen, Beleg-Nachfass je Sub,
|
||||
Lücken per Stichentscheid klären (Freispruch oder bestätigt = braucht Recherche).
|
||||
Fail-open überall: Zweifel/Judge-Fehler → behalten, nichts persistieren."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
import database as db
|
||||
import qa
|
||||
from blocks import _blocks_files, _evidence_pack, source_folder
|
||||
from blocks import _blocks_files, _evidence_pack, material_folder, source_folder
|
||||
from config import EVIDENCE_PER_BLOCK
|
||||
from fsutil import atomic_write_json
|
||||
from jsonio import read_json_file as _json_file
|
||||
@@ -20,9 +21,12 @@ from textkit import _norm_title, _title, clean_title, parse_facts
|
||||
log = logging.getLogger("creator.repair")
|
||||
|
||||
|
||||
async def repair_befunde(topic: str, ebene: str | None = None) -> dict:
|
||||
"""ebene=None → alle Fixes; „inventory" → nur Inventar (Hygiene/Merges/Fremd);
|
||||
„artefacts" → nur Artefakte (Sub-Merges/Waisen). Misst am Ende IMMER beide Noten neu."""
|
||||
async def repair_befunde(topic: str, ebene: str | None = None, luecken_urteil: bool = True) -> dict:
|
||||
"""ebene=None → alle Fixes; „inventory" → nur Inventar; „artefacts" → nur Artefakte.
|
||||
luecken_urteil: offene Lücken/Konzept-Lücken dem Stichentscheid vorlegen (Freispruch
|
||||
oder bestätigt) — der QA-Gate-Watch schaltet es aus, solange seine Lücken-Recherche
|
||||
noch Runden hat. Misst am Ende IMMER beide Noten neu. `aktionen` zählt alles, was die
|
||||
Runde getan hat (Fixes+Merges+Freisprüche) — der Auto-Loop stoppt erst bei 0."""
|
||||
reports = qa.report_paths(topic)
|
||||
report = _json_file(reports[-1]) if reports else None
|
||||
if not report:
|
||||
@@ -34,9 +38,13 @@ async def repair_befunde(topic: str, ebene: str | None = None) -> dict:
|
||||
art = ebene in (None, "artefacts")
|
||||
|
||||
hygiene = await _fix_hygiene(topic, report, by_norm, files) if inv else []
|
||||
merges = await _merge_dubletten(topic, report, by_norm, files) if inv else []
|
||||
hygiene += (await _fix_beschreibungen(topic, report, by_norm)) if inv else []
|
||||
hygiene += (await _fix_suffixe(topic, report, by_norm, files)) if inv else []
|
||||
merges, frei_paare = (await _merge_dubletten(topic, report, by_norm, files)) if inv else ([], [])
|
||||
sub_merges, frei_subs = (await _merge_sub_dubletten(topic, report, files)) if art else ([], [])
|
||||
entfernt, frei_bloecke = (await _entferne_fremd_unecht(topic, report, by_norm, files)) if inv else ([], [])
|
||||
frei_luecken = (await _pruefe_luecken(topic, report, by_norm)) if inv and luecken_urteil else []
|
||||
beleg_fix = (await _belege_nachfassen(topic, report, files)) if art else []
|
||||
aufgeraeumt = (await _raeume_waisen(topic)) if art else 0
|
||||
|
||||
# llm=True: gleiche Messlatte wie QA-Button/Abschluss-QA — der llm=False-Report
|
||||
@@ -45,16 +53,22 @@ async def repair_befunde(topic: str, ebene: str | None = None) -> dict:
|
||||
if neu:
|
||||
await qa.write_report(neu)
|
||||
na = neu.get("note_artefakte") if neu else None
|
||||
freigesprochen = frei_paare + frei_subs + frei_bloecke + frei_luecken
|
||||
lk_rest, kl_rest = qa.offene_luecken(neu or {})
|
||||
return {"hygiene": hygiene, "merges": merges, "sub_merges": sub_merges, "entfernt": entfernt,
|
||||
"aufgeraeumt": aufgeraeumt, "freigesprochen": frei_subs + frei_bloecke,
|
||||
"braucht_research": len(report.get("luecken", [])),
|
||||
"beleg_fix": beleg_fix, "aufgeraeumt": aufgeraeumt, "freigesprochen": freigesprochen,
|
||||
"aktionen": (len(hygiene) + len(merges) + len(sub_merges) + len(entfernt)
|
||||
+ len(beleg_fix) + aufgeraeumt + len(freigesprochen)),
|
||||
"braucht_research": len(lk_rest) + len(kl_rest), # offene Lücken NACH dieser Runde
|
||||
"note": float(neu["note"]) if neu else 10.0, # neu gemessene Inventar-Note
|
||||
"note_artefakte": float(na) if na is not None else None} # None solange Board 2 leer
|
||||
|
||||
|
||||
async def _judge(template: str, topic: str, key: str, slot: str, items: list[str]) -> dict[int, str]:
|
||||
async def _judge(template: str, topic: str, key: str, slot: str, items: list[str],
|
||||
extra: str = "") -> dict[int, str]:
|
||||
"""No-Tool-Judge-Welle (fail-open: Fehler → leeres Verdikt = behalten)."""
|
||||
return await qa.judge_wave(template, topic, key, slot, items, prefix="repair", label="Repair")
|
||||
return await qa.judge_wave(template, topic, key, slot, items, prefix="repair", label="Repair",
|
||||
extra=extra)
|
||||
|
||||
|
||||
def _speichere_freispruch(topic: str, kategorie: str, keys: list[str]) -> None:
|
||||
@@ -67,7 +81,7 @@ def _speichere_freispruch(topic: str, kategorie: str, keys: list[str]) -> None:
|
||||
|
||||
async def _mit_stichentscheid(template: str, topic: str, key: str, slot: str,
|
||||
lines: list[str], befund: str, kategorie: str = "",
|
||||
ids: list[str] | None = None) -> tuple[dict[int, str], list[str]]:
|
||||
ids: list[str] | None = None, extra: str = "") -> tuple[dict[int, str], list[str]]:
|
||||
"""Zweitmeinung + Stichentscheid: Der Repair-Judge kann den QA-Befund kippen — bei
|
||||
Dissens (QA sagt Befund, Judge sagt behalten) entscheidet ein DRITTER Judge nur über
|
||||
die strittigen Items, Mehrheit 2/3 (Muster Crossblock-Tiebreaker). Ohne ihn pendelte
|
||||
@@ -76,11 +90,11 @@ async def _mit_stichentscheid(template: str, topic: str, key: str, slot: str,
|
||||
Explizites 2:1-„behalten" wird als FREISPRUCH persistiert (kategorie+ids) — die QA
|
||||
zählt das Item ab dann nicht mehr (qa.lade_freispruch). j3-AUSFALL persistiert nicht
|
||||
(fail-open ist kein Urteil). → (verdicts, freigesprochene Zeilen)."""
|
||||
v = await _judge(template, topic, key, slot, lines)
|
||||
v = await _judge(template, topic, key, slot, lines, extra=extra)
|
||||
strittig = [i for i in range(1, len(lines) + 1) if v.get(i) != befund]
|
||||
frei: list[str] = []
|
||||
if strittig:
|
||||
v3 = await _judge(template, topic, f"{key}-st", slot, [lines[i - 1] for i in strittig])
|
||||
v3 = await _judge(template, topic, f"{key}-st", slot, [lines[i - 1] for i in strittig], extra=extra)
|
||||
gegen = "nein" if befund == "ja" else "ja"
|
||||
frei_keys: list[str] = []
|
||||
for pos, i in enumerate(strittig, 1):
|
||||
@@ -95,9 +109,165 @@ async def _mit_stichentscheid(template: str, topic: str, key: str, slot: str,
|
||||
return v, frei
|
||||
|
||||
|
||||
async def _fix_beschreibungen(topic: str, report: dict, by_norm: dict) -> list[str]:
|
||||
"""Hygiene-Resttyp „leere-beschreibung": EINE Beschreibung aus Material-Auszügen
|
||||
generieren (Blocks-Sanierung-Template; der Titel bleibt — Repair ist flow-los und
|
||||
hat keinen Korpus-Anker-Kontext für Renames). Ohne Material/Auszüge bleibt der Befund."""
|
||||
from agents import run_agent
|
||||
from pipeline import _prompt, _timeout
|
||||
from jsonio import parse_json_text
|
||||
from board_inventory import _sanierung_schema
|
||||
items = [h for h in report.get("hygiene", []) if "leere-beschreibung" in (h.get("probleme") or [])]
|
||||
folder = source_folder(topic) or material_folder(topic)
|
||||
if not items or not folder:
|
||||
return []
|
||||
out: list[str] = []
|
||||
for h in items:
|
||||
norm = _norm_title(h.get("titel", ""))
|
||||
card = by_norm.get(norm)
|
||||
if not card or (card["payload"].get("description") or "").strip():
|
||||
continue
|
||||
titel = card["payload"].get("title", "")
|
||||
ev = _evidence_pack(folder, card["payload"].get("sources") or None, [titel], budget=4000)
|
||||
if not ev:
|
||||
continue
|
||||
try:
|
||||
rc, txt, _err = await run_agent(
|
||||
f"repair-{topic}-sanierung-{norm[:24]}",
|
||||
_prompt("Blocks-Sanierung", topic=topic, title=titel, description="(leer)", excerpts=ev),
|
||||
_timeout("qa_judge"), role="judge", capabilities="none", scope=topic,
|
||||
label="Repair Beschreibung")
|
||||
except Exception:
|
||||
log.exception("[%s] Beschreibungs-Sanierung fehlgeschlagen — Befund bleibt", topic)
|
||||
continue
|
||||
verdict = _sanierung_schema(parse_json_text(txt)) if rc == 0 else None
|
||||
if not verdict or not verdict[1].strip():
|
||||
continue
|
||||
p = dict(card["payload"])
|
||||
p["description"] = verdict[1].strip()
|
||||
await db.kanban_set_payload(topic, "inventory", card["card_id"], p)
|
||||
await db.set_block_status(topic, norm, "consensus", description=p["description"])
|
||||
card["payload"] = p
|
||||
out.append(f"beschrieben: {titel}")
|
||||
return out
|
||||
|
||||
|
||||
_SUFFIX_RE = re.compile(r"\s*\(\d+\)\s*$")
|
||||
|
||||
|
||||
async def _fix_suffixe(topic: str, report: dict, by_norm: dict, files: dict) -> list[str]:
|
||||
"""Hygiene-Resttyp „kollisions-suffix" („Titel (2)", entstanden beim Spiegeln):
|
||||
Basis-Norm frei (Partner inzwischen gemerged/verworfen) → Rename auf den Basistitel
|
||||
inkl. Re-Key aller angehängten Tabellen (db.rename_block_norm) + Board-2-Karte +
|
||||
Sidecars. Basis besetzt → Judge entscheidet Dublette (Merge); sonst bleibt der
|
||||
Befund sichtbar (zwei echte gleichnamige Konzepte sind ein Naming-Problem)."""
|
||||
items = [h for h in report.get("hygiene", []) if "kollisions-suffix" in (h.get("probleme") or [])]
|
||||
out: list[str] = []
|
||||
for h in items:
|
||||
alt = h.get("titel", "")
|
||||
basis = _SUFFIX_RE.sub("", alt).strip()
|
||||
alt_norm, basis_norm = _norm_title(alt), _norm_title(basis)
|
||||
card = by_norm.get(alt_norm)
|
||||
if not card or not basis or basis_norm == alt_norm:
|
||||
continue
|
||||
if basis_norm in by_norm: # Basis lebt noch → Dubletten-Frage statt Rename
|
||||
merged, _frei = await _merge_dubletten(
|
||||
topic, {"dubletten": [{"a": basis, "b": alt, "llm": "?"}]}, by_norm, files)
|
||||
out += [f"suffix-merge: {z}" for z in merged]
|
||||
continue
|
||||
await db.rename_block_norm(topic, alt_norm, basis_norm, basis)
|
||||
b2 = await db.kanban_get_card(topic, "artefacts", alt_norm)
|
||||
if b2:
|
||||
p2 = dict(b2["payload"])
|
||||
p2["title"] = basis
|
||||
await db.kanban_upsert_card(topic, "artefacts", basis_norm, b2["kind"], b2["stage"], p2)
|
||||
await db.kanban_delete_card(topic, "artefacts", alt_norm)
|
||||
p = dict(card["payload"])
|
||||
p["title"] = basis
|
||||
if p.get("mirrored_norm"):
|
||||
p["mirrored_norm"] = basis_norm
|
||||
await db.kanban_set_payload(topic, "inventory", card["card_id"], p)
|
||||
card["payload"] = p
|
||||
_rename_in_files(files, alt_norm, basis)
|
||||
by_norm.pop(alt_norm, None)
|
||||
by_norm[basis_norm] = card
|
||||
out.append(f"suffix: {alt} → {basis}")
|
||||
return out
|
||||
|
||||
|
||||
async def _pruefe_luecken(topic: str, report: dict, by_norm: dict) -> list[str]:
|
||||
"""Lücken-Urteil (2:1): offene luecken-Fundstellen und konzept_luecken-Namen dem
|
||||
Stichentscheid vorlegen — 2:1-„keine echte Lücke" wird als Freispruch persistiert,
|
||||
bestätigte bleiben als braucht_research sichtbar (Gate-Watch recherchiert, der Nutzer
|
||||
klickt „+ Recherche"). Das Inventar geht als Kontext in den {extra}-Slot, damit die
|
||||
Judges Abdeckung gegen den Bestand prüfen können."""
|
||||
lk, kl = qa.offene_luecken(report)
|
||||
if not lk and not kl:
|
||||
return []
|
||||
extra = ("\n\nEXISTING INVENTORY BLOCKS (judge coverage against these):\n"
|
||||
+ "\n".join(f"- {c['payload'].get('title', '')}" for c in by_norm.values()))
|
||||
frei: list[str] = []
|
||||
if lk:
|
||||
_v, f = await _mit_stichentscheid(
|
||||
"QA-Luecken", topic, "luecken", "sections",
|
||||
[f"[{x.get('datei')} #{x.get('abschnitt')}] {x.get('vorschau', '')}" for x in lk],
|
||||
"ja", kategorie="luecken", ids=[qa.luecken_key(x) for x in lk], extra=extra)
|
||||
frei += f
|
||||
if kl:
|
||||
_v, f = await _mit_stichentscheid(
|
||||
"QA-Konzept-Luecken", topic, "konzept-luecken", "results", list(kl),
|
||||
"ja", kategorie="konzept_luecken", ids=[_norm_title(n) for n in kl], extra=extra)
|
||||
frei += f
|
||||
return frei
|
||||
|
||||
|
||||
async def _belege_nachfassen(topic: str, report: dict, files: dict) -> list[str]:
|
||||
"""subs_ohne_beleg (mentions=0): Beleg-Nachfass je Sub mit Material-Auszügen.
|
||||
Judge+QA einig „nein" (kein Beleg) → Sub verwerfen (Artefakte/Fragen räumt das
|
||||
nachlaufende _raeume_waisen); 2:1-„ja" → mentions=1 nachtragen — Datenfix am
|
||||
gemessenen Datum selbst, kein Freispruch nötig, Detektor unverändert. Produktiv ist
|
||||
der Detektor derzeit inaktiv (alle Schreiber setzen mentions=1) — Pfad ist Robustheit.
|
||||
Ohne Quellordner kein Urteil möglich → Befund bleibt."""
|
||||
eintraege = (report.get("beleg") or {}).get("subs_ohne_beleg") or []
|
||||
folder = source_folder(topic) or material_folder(topic)
|
||||
if not eintraege or not folder:
|
||||
return []
|
||||
rows = {f"{r['block']} · {r['sub_title']}": r for r in await db.list_subblocks(topic)
|
||||
if r["status"] != "variant"}
|
||||
items = [(e, rows[e]) for e in eintraege if e in rows and not rows[e]["mentions"]]
|
||||
if not items:
|
||||
return []
|
||||
lines = []
|
||||
for e, r in items:
|
||||
ev = _evidence_pack(folder, None, [r["block"], r["sub_title"]], budget=EVIDENCE_PER_BLOCK)
|
||||
lines.append(f"{e}\n{ev or '(keine Treffer im Material)'}")
|
||||
v1 = await _judge("QA-Repair-Beleg", topic, "beleg", "blocks", lines)
|
||||
strittig = [i for i in range(1, len(items) + 1) if v1.get(i) == "ja"]
|
||||
v3 = (await _judge("QA-Repair-Beleg", topic, "beleg-st", "blocks",
|
||||
[lines[i - 1] for i in strittig])) if strittig else {}
|
||||
out: list[str] = []
|
||||
|
||||
async def _verwerfen(e: str, r: dict):
|
||||
await db.set_subblock_fields(topic, r["block_norm"], r["sub_norm"], status="discarded")
|
||||
_entferne_sub_in_files(files, r["block_norm"], r["sub_norm"])
|
||||
out.append(f"entfernt: {e[:60]}")
|
||||
|
||||
for i, (e, r) in enumerate(items, 1):
|
||||
if v1.get(i) == "nein": # QA (mentions=0) + Judge einig → weg
|
||||
await _verwerfen(e, r)
|
||||
elif v1.get(i) == "ja": # Dissens → Stichentscheid
|
||||
pos = strittig.index(i) + 1
|
||||
if v3.get(pos) == "ja":
|
||||
await db.set_subblock_fields(topic, r["block_norm"], r["sub_norm"], mentions=1)
|
||||
out.append(f"belegt: {e[:60]}")
|
||||
elif v3.get(pos) == "nein":
|
||||
await _verwerfen(e, r)
|
||||
return out
|
||||
|
||||
|
||||
async def _fix_hygiene(topic: str, report: dict, by_norm: dict, files: dict) -> list[str]:
|
||||
"""Nur der norm-invariante Teil (`**`/Backticks); `(n)`-Suffix und leere Beschreibung
|
||||
ändern die Norm bzw. brauchen Inhalt — bleiben Befund."""
|
||||
ändern die Norm bzw. brauchen Inhalt — eigene Handler (_fix_suffixe/_fix_beschreibungen)."""
|
||||
fixed = []
|
||||
for h in report.get("hygiene", []):
|
||||
alt = h.get("titel", "")
|
||||
@@ -117,33 +287,68 @@ async def _fix_hygiene(topic: str, report: dict, by_norm: dict, files: dict) ->
|
||||
return fixed
|
||||
|
||||
|
||||
async def _merge_dubletten(topic: str, report: dict, by_norm: dict, files: dict) -> list[str]:
|
||||
"""Nur QA-bestätigte Paare (llm=ja); eine Zweitmeinung, Merge nur bei erneut ja.
|
||||
Merge spiegelt die dedup-Stage: Union ins Gewinner-Payload, Verlierer → grouped."""
|
||||
paare = [p for p in report.get("dubletten", []) if p.get("llm") == "ja"
|
||||
and _norm_title(p.get("a", "")) in by_norm and _norm_title(p.get("b", "")) in by_norm]
|
||||
if not paare:
|
||||
return []
|
||||
v, _frei = await _mit_stichentscheid("QA-Dubletten", topic, "dubletten", "pairs",
|
||||
[f"A: {p['a']}\nB: {p['b']}" for p in paare], "ja")
|
||||
merged = []
|
||||
for i, p in enumerate(paare, 1):
|
||||
a, b = by_norm.get(_norm_title(p["a"])), by_norm.get(_norm_title(p["b"]))
|
||||
if v.get(i) != "ja" or not a or not b or a["card_id"] == b["card_id"]:
|
||||
continue
|
||||
win, lose = sorted((a, b), key=lambda c: (len(c["payload"].get("description") or ""),
|
||||
len(c["payload"].get("title") or "")), reverse=True)
|
||||
wp, lp = dict(win["payload"]), dict(lose["payload"])
|
||||
wp["readers"] = sorted(set(wp.get("readers") or []) | set(lp.get("readers") or []))
|
||||
wp["sources"] = sorted(set(wp.get("sources") or []) | set(lp.get("sources") or []))
|
||||
lp.update(reason="merged", merged_into=wp.get("title", ""))
|
||||
await db.kanban_set_payload(topic, "inventory", win["card_id"], wp)
|
||||
await db.kanban_set_payload(topic, "inventory", lose["card_id"], lp)
|
||||
await db.kanban_advance(topic, "inventory", lose["card_id"], "grouped")
|
||||
await _purge_block(topic, lp.get("title", ""), files)
|
||||
by_norm.pop(_norm_title(lp.get("title", "")), None)
|
||||
merged.append(f"{lp.get('title')} → {wp.get('title')}")
|
||||
return merged
|
||||
async def _merge_paar(topic: str, p: dict, by_norm: dict, files: dict) -> str | None:
|
||||
"""Ein bestätigtes Dubletten-Paar mergen (spiegelt die dedup-Stage: Union ins
|
||||
Gewinner-Payload, Verlierer → grouped). → Journalzeile oder None."""
|
||||
a, b = by_norm.get(_norm_title(p["a"])), by_norm.get(_norm_title(p["b"]))
|
||||
if not a or not b or a["card_id"] == b["card_id"]:
|
||||
return None
|
||||
win, lose = sorted((a, b), key=lambda c: (len(c["payload"].get("description") or ""),
|
||||
len(c["payload"].get("title") or "")), reverse=True)
|
||||
wp, lp = dict(win["payload"]), dict(lose["payload"])
|
||||
wp["readers"] = sorted(set(wp.get("readers") or []) | set(lp.get("readers") or []))
|
||||
wp["sources"] = sorted(set(wp.get("sources") or []) | set(lp.get("sources") or []))
|
||||
lp.update(reason="merged", merged_into=wp.get("title", ""))
|
||||
await db.kanban_set_payload(topic, "inventory", win["card_id"], wp)
|
||||
await db.kanban_set_payload(topic, "inventory", lose["card_id"], lp)
|
||||
await db.kanban_advance(topic, "inventory", lose["card_id"], "grouped")
|
||||
await _purge_block(topic, lp.get("title", ""), files)
|
||||
by_norm.pop(_norm_title(lp.get("title", "")), None)
|
||||
return f"{lp.get('title')} → {wp.get('title')}"
|
||||
|
||||
|
||||
async def _merge_dubletten(topic: str, report: dict, by_norm: dict, files: dict) -> tuple[list[str], list[str]]:
|
||||
"""QA-bestätigte Paare (llm=ja): Zweitmeinung, Merge nur bei erneut ja; 2:1-„behalten"
|
||||
persistiert jetzt als Freispruch (fehlte — die Paare pendelten ewig im Verdacht).
|
||||
Widerlegte/unbeurteilte Paare (llm=nein/?) bekommen den Klärungskanal: Judge-„nein" →
|
||||
Freispruch (mit QA-nein 2:0; beim bloßen Verdacht reicht das klare Gegen-Urteil),
|
||||
Judge-„ja" → Stichentscheid (ja → Merge 2:1, nein → Freispruch 2:1, Ausfall → nichts)."""
|
||||
alle = [p for p in report.get("dubletten", []) if not p.get("freispruch")
|
||||
and _norm_title(p.get("a", "")) in by_norm and _norm_title(p.get("b", "")) in by_norm]
|
||||
ja = [p for p in alle if p.get("llm") == "ja"]
|
||||
offen = [p for p in alle if p.get("llm") != "ja"]
|
||||
merged: list[str] = []
|
||||
frei: list[str] = []
|
||||
if ja:
|
||||
v, f = await _mit_stichentscheid("QA-Dubletten", topic, "dubletten", "pairs",
|
||||
[f"A: {p['a']}\nB: {p['b']}" for p in ja], "ja",
|
||||
kategorie="dubletten",
|
||||
ids=[qa._paar_key(p["a"], p["b"]) for p in ja])
|
||||
frei += f
|
||||
for i, p in enumerate(ja, 1):
|
||||
if v.get(i) == "ja" and (zeile := await _merge_paar(topic, p, by_norm, files)):
|
||||
merged.append(zeile)
|
||||
if offen:
|
||||
ids = [qa._paar_key(p["a"], p["b"]) for p in offen]
|
||||
v1 = await _judge("QA-Dubletten", topic, "dubletten-v", "pairs",
|
||||
[f"A: {p['a']}\nB: {p['b']}" for p in offen])
|
||||
frei_keys = [ids[i - 1] for i in range(1, len(offen) + 1) if v1.get(i) == "nein"]
|
||||
frei += [f"{offen[i - 1]['a']} <-> {offen[i - 1]['b']}"[:80]
|
||||
for i in range(1, len(offen) + 1) if v1.get(i) == "nein"]
|
||||
strittig = [i for i in range(1, len(offen) + 1) if v1.get(i) == "ja"]
|
||||
if strittig:
|
||||
v3 = await _judge("QA-Dubletten", topic, "dubletten-v-st", "pairs",
|
||||
[f"A: {offen[i - 1]['a']}\nB: {offen[i - 1]['b']}" for i in strittig])
|
||||
for pos, i in enumerate(strittig, 1):
|
||||
if v3.get(pos) == "ja" and (zeile := await _merge_paar(topic, offen[i - 1], by_norm, files)):
|
||||
merged.append(zeile)
|
||||
elif v3.get(pos) == "nein":
|
||||
frei_keys.append(ids[i - 1])
|
||||
frei.append(f"{offen[i - 1]['a']} <-> {offen[i - 1]['b']}"[:80])
|
||||
if frei_keys:
|
||||
_speichere_freispruch(topic, "dubletten", frei_keys)
|
||||
log.info("[%s] Repair dubletten: %d Verdacht(e) freigesprochen", topic, len(frei_keys))
|
||||
return merged, frei
|
||||
|
||||
|
||||
_SUB_PAAR = re.compile(r"^\[(.+?)\] (.+)$", re.S)
|
||||
|
||||
Reference in New Issue
Block a user