This commit is contained in:
team3
2026-07-03 12:50:32 +02:00
parent 9754cbcfae
commit 91b0d00aa1
27 changed files with 203 additions and 580 deletions

View File

@@ -43,7 +43,7 @@ from blocks import (
_filter_schema, _filter_suspect, _is_artifact, _is_named_statement,
_is_parentless_noise, _is_reference, _pairs_schema, _read,
_relation_conflict, _root, _supplement_schema, _text_sections, _umbrella_schema,
_aspect_marker,
_aspect_marker, _title_variants, _evidence_pack, _sink_json, source_folder,
)
from config import (
BLOCKS_GRUPPIERUNG_AKTIV, EMBEDDING_AKTIV, EMBEDDING_BLOCK_CAP,
@@ -53,8 +53,8 @@ from config import (
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, run_single_slot)
from textkit import _norm_title, _parse_selection, _title
_runde_schema, _timeout, _yesno_schema, run_single_slot)
from textkit import _norm_title, _parse_selection, _title, clean_title
log = logging.getLogger("creator.board_inventory")
@@ -144,13 +144,13 @@ async def _ingest_titles(flow: Flow, text: str, reader: str, source: str = "") -
reader union. Repeated drains over a growing buffer are idempotent. → new count."""
n, seen = 0, set()
for record in _parse_selection(text).values():
title = _title(record)
title = clean_title(_title(record))
norm = _norm_title(title)
if not norm or norm in seen:
continue
seen.add(norm) # one reader = one vote per concept
parts = [t.strip() for t in record.split("")]
desc = parts[1] if len(parts) >= 2 else ""
desc = clean_title(parts[1]) if len(parts) >= 2 else ""
src = source or (parts[2] if len(parts) >= 3 else "")
async with _ingest_lock:
if await db.kanban_add_title(flow.topic, BOARD, norm, title, desc, src, reader):
@@ -924,6 +924,15 @@ async def _proc_dedup(ctx: GenContext, flow: Flow, cards):
for y in range(x + 1, len(grp)):
if _demotable_pair(grp[x], grp[y]):
pairs.add((grp[x], grp[y]))
# acronym↔expansion pairs ("SAT" vs "Satisfiability Problem (SAT)"): the title cosine
# of short vs long form sits far below the floor — variant match makes them candidates,
# the judge panel decides as usual (no auto-merge)
idents = [{_norm_title(r["title"]), _canonical_key(r["title"])} - {""} for r in allrows]
variants = [_title_variants(r["title"]) for r in allrows]
for i in range(n_all):
for j in range(i + 1, n_all):
if _demotable_pair(i, j) and (variants[i] & idents[j] or variants[j] & idents[i]):
pairs.add((i, j))
ordered = sorted(pairs)
h = _h(*[r["card_id"] for r in allrows])
if not ordered:
@@ -1176,7 +1185,7 @@ async def _proc_grouping(ctx: GenContext, flow: Flow, cards):
readers = sorted(set().union(*[set(r["payload"].get("readers") or []) for r in mrows]))
srcs = sorted(set().union(*[set(r["payload"].get("sources") or []) for r in mrows]))
await db.kanban_upsert_card(topic, BOARD, f"b-u-{uuid.uuid4().hex[:8]}", "block", "gap_check", {
"title": c["umbrella"], "description": c["description"],
"title": clean_title(c["umbrella"]), "description": c["description"],
"readers": readers, "sources": srcs, "umbrella": True,
"children": [r["title"] for r in mrows],
})
@@ -1221,6 +1230,37 @@ async def _proc_gap_check(ctx: GenContext, flow: Flow, cards):
flow.wake.set()
async def _supplement_beleg(ctx: GenContext, flow: Flow, supplements: list) -> list:
"""Evidence gate for supplement proposals: keyword excerpts per proposal, ONE no-tool
judge marks material coverage (ja/nein). Proposals without any matching excerpt drop
immediately; a failed gate keeps nothing (creep is costlier than a lost bonus round)."""
topic = flow.topic
folder = source_folder(topic)
packs = [(t, d, _evidence_pack(folder, None, [t], budget=6000)) for t, d in supplements]
cands = [(t, d, ev) for t, d, ev in packs if ev]
kept: list = []
if cands:
path = flow.work_dir / "supplement-beleg.json"
ids = set(range(1, len(cands) + 1))
verdict = _yesno_schema(_json_file(path), ids)
if verdict is None:
lines = "\n\n".join(f"{k}. {t}{d}\nAUSZÜGE:\n{ev}"
for k, (t, d, ev) in enumerate(cands, 1))
status, verdict = await run_single_slot(
ctx, "Supplement-Beleg", key=f"blocks-{topic}-supplement-beleg",
prompt=_prompt("Blocks-Supplement-Beleg", topic=topic, proposals=lines,
extra=_extra(flow.state.get("instructions", ""))),
role="judge", capabilities="none",
payload=lambda result, p=path, i=ids: _sink_json(result, p, lambda d2: _yesno_schema(d2, i)),
timeout=_timeout("selection_mapping", len(cands)))
if status != OK or not isinstance(verdict, dict):
verdict = {}
kept = [(t, d) for k, (t, d, ev) in enumerate(cands, 1) if str(verdict.get(k, "nein")) == "ja"]
if len(kept) < len(supplements):
_log(topic, f"Supplement: {len(supplements) - len(kept)}/{len(supplements)} ohne Materialbeleg verworfen")
return kept
async def _supplement_producer(ctx: GenContext, flow: Flow, titles: list[str]):
"""One web agent proposes canonically missing blocks → new title cards (reader
'supplement' skips only the ≥2 consensus bar, every other gate applies)."""
@@ -1242,6 +1282,11 @@ async def _supplement_producer(ctx: GenContext, flow: Flow, titles: list[str]):
if status != OK:
_log(topic, "Supplement fehlgeschlagen — übersprungen (optional)")
supplements = []
# Material anchoring (uni/projekt): the web agent proposes canonical knowledge BLIND
# to the source (measured: 22/107 aak blocks were textbook standard the script never
# treats). Only proposals the material itself covers may enter the inventory.
if supplements and source_folder(topic):
supplements = await _supplement_beleg(ctx, flow, supplements)
# Dead lineage: blocks demoted by the fragment filter (and their cluster + title cards)
# must NOT dedup a supplement proposal — their content is gone. A hit on a dead title
# REOPENS the lineage instead: the title card rejoins its cluster (live re-cluster) and
@@ -1281,6 +1326,7 @@ async def _supplement_producer(ctx: GenContext, flow: Flow, titles: list[str]):
known_keys.add(k)
new = reopened = 0
for t, d in (supplements or []):
t, d = clean_title(t), clean_title(d)
norm = _norm_title(t)
key = _canonical_key(t)
if not norm or norm in known_norms or (key and key in known_keys):