This commit is contained in:
team3
2026-07-06 14:44:20 +02:00
parent f4c5116abb
commit cc27e53b9e
20 changed files with 504 additions and 267 deletions

View File

@@ -43,7 +43,7 @@ from blocks import (
_build_research_prompt, _canonical, _canonical_key, _chunk_nums, _cliques,
_completion_schema, _containment_parent, _crawl_index, _file_payload,
_filter_schema, _filter_suspect, _is_artifact, _is_named_statement,
_is_parentless_noise, _is_reference, _pairs_schema, _read,
_is_parentless_noise, _is_reference, _reference_strip, _pairs_schema, _read,
_direction_conflict, _relation_conflict, _root, _supplement_schema, _text_sections, _umbrella_schema,
_aspect_marker, _title_variants, _corpus_files, _evidence_pack, _sink_json, source_folder,
)
@@ -59,7 +59,7 @@ from fsutil import atomic_write_json, atomic_write_text
from jsonio import read_json_file as _json_file
from pipeline import (CANCELLED, FAILED, OK, GenContext, _extra, _log, _prompt,
_runde_schema, _timeout, _yesno_schema, run_single_slot)
from textkit import _norm_title, _parse_selection, _title, clean_title
from textkit import _norm_title, _parse_selection, _title, _unclosed, clean_title
log = logging.getLogger("creator.board_inventory")
@@ -491,12 +491,16 @@ def _hat_anker(title: str, ctoks: set[str]) -> bool:
def _sanierung_noetig(p: dict, ctoks: set[str] | None) -> bool:
"""QA-messbare Befund-Formen am Entstehungsort: Titel ohne Korpus-Anker (QA: fremd —
misst Token-Anker, nicht Semantik) oder leere Beschreibung (QA: hygiene). Gilt auch für
Singleton-Cluster, die das Naming sonst überspringen — Reader-Rohtitel gingen wörtlich
bis done_block durch (gemessen: 'k-Coloring' statt Korpus-Form 'k-Color')."""
misst Token-Anker, nicht Semantik) oder leere Beschreibung (QA: hygiene). Zusätzlich ein
Referenz-Titel (reine Katalog-Nummer), eine unbalancierte Klammer oder ein überlanger Titel
(>80). Gilt auch für Singleton-Cluster, die das Naming sonst überspringen — Reader-Rohtitel
gingen wörtlich bis done_block durch (gemessen: 'k-Coloring' statt Korpus-Form 'k-Color')."""
title = p.get("title", "")
if not (p.get("description") or "").strip():
return True
return bool(ctoks) and not _hat_anker(p.get("title", ""), ctoks)
if _is_reference(title) or _unclosed(title) or len(title) > 80:
return True
return bool(ctoks) and not _hat_anker(title, ctoks)
async def _anker_beleg(ctx: GenContext, flow: Flow, kandidaten: list[tuple[str, dict]]) -> set[str]:
@@ -574,6 +578,9 @@ async def _proc_consensus_gate(ctx: GenContext, flow: Flow, cards):
else:
moves.append((cid, "naming"))
else:
# single-reader find: majority of the Klärung panel suffices (was unanimity — a single
# "nein" discarded a real once-mentioned concept as failed-quorum, e.g. Cook-Levin)
p["quorum"] = "majority"
moves.append((cid, "clarify"))
await db.kanban_set_payload(topic, BOARD, cid, p)
if anker_kandidaten:
@@ -593,8 +600,9 @@ async def _proc_consensus_gate(ctx: GenContext, flow: Flow, cards):
async def _proc_clarify(ctx: GenContext, flow: Flow, cards):
"""Single-reader finds: deterministic pre-reject, then a 3-judge panel (Blocks-Klaerung).
Quorum: UNANIMITY for single-reader clusters (origin-split C1), majority for reference-titled
consensus clusters. Kept reference titles get the panel's rename."""
Quorum: MAJORITY of the panel for single-reader clusters and for reference-titled consensus
clusters (unanimity dropped real once-mentioned concepts on a single dissent). Kept reference
titles get the panel's rename."""
topic = flow.topic
moves: list[tuple[str, str]] = []
pending = []
@@ -810,6 +818,12 @@ async def _namecheck_one(ctx: GenContext, flow: Flow, c):
p["description"] = w.get("description") or p.get("description", "")
readers = sorted(set().union(*[set(r.get("readers") or []) for r in rows])) if rows else []
sources = sorted(set().union(*[set(r.get("sources") or []) for r in rows])) if rows else []
# deterministic catalogue-number strip (final title): a source numbering is never canonical —
# "Satz 7.13 (Christofides)" → "Christofides", "Satz 7.6: Kriterium …" → "Kriterium …". Runs AFTER
# naming_check so a re-picked member title cannot re-introduce the number.
stripped = _reference_strip(p.get("title", ""))
if stripped and _norm_title(stripped) != _norm_title(p.get("title", "")) and not _is_reference(stripped):
p["title"] = clean_title(stripped)
await db.kanban_upsert_card(topic, BOARD, f"b-{cid}", "block", "fragment_filter", {
"title": p.get("title", ""), "description": p.get("description", ""),
"readers": readers, "sources": sources, "n_size": len(readers),
@@ -1025,13 +1039,21 @@ async def _proc_fragment_filter(ctx: GenContext, flow: Flow, cards):
for nr in nums:
if nr in fragments or nr in honored:
continue
votes = len(dem.get(nr, []))
if drp.get(nr, 0) >= 2 and (_is_artifact(allrows[nr - 1]["title"])
or _is_parentless_noise(allrows[nr - 1]["title"])):
honored.add(nr)
elif len(dem.get(nr, [])) >= 2:
fragments[nr] = max(set(dem[nr]), key=dem[nr].count)
elif nr in proposals:
overruled.append(nr)
# a pass-1 judge already proposed this demote — overturn it ONLY on a
# UNANIMOUS recheck keep (zero demote votes). 15/17 majority-rescued cards
# were later QA duplicates, so a single recheck demote reconfirms the fragment.
if votes >= 1:
fragments[nr] = max(set(dem[nr]), key=dem[nr].count)
else:
overruled.append(nr)
elif votes >= 2:
# pure ⚠ suspect without a pass-1 proposal: still needs a panel majority
fragments[nr] = max(set(dem[nr]), key=dem[nr].count)
# embedding backstop: veto confirmed non-containment demotes whose direct title pair is
# literally structureless (see FRAGMENT_MIN_COS) — applied BEFORE _root resolution.
floor_veto: list[int] = []