This commit is contained in:
team3
2026-07-07 00:25:23 +02:00
parent f2feb1dbd0
commit f0c1bdacfa
15 changed files with 1363 additions and 367 deletions

View File

@@ -1323,8 +1323,8 @@ async def _proc_grouping(ctx: GenContext, flow: Flow, cards):
# ONE wave: TOP + all cluster judges in parallel. The used-ties precedence lives in the
# ORDER of `sources` (TOP first), not in execution order — a failed judge simply leaves
# no valid file and is skipped in the collection below (legacy semantics).
jobs = [("TOP", "Scan the ENTIRE block list below and propose EVERY genuine umbrella "
"you find — do not restrict yourself to any subset.", n)]
jobs = [("TOP", "Card-sort the ENTIRE list below into themes: assign as MANY items as "
"possible to a coherent theme, each item into exactly one.", n)]
jobs += [(str(ci), "\n".join(f"{g + 1}. {texts[g]}" for g in cluster), len(cluster))
for ci, cluster in enumerate(multi)]
await asyncio.gather(*[_assess(tag, cand, count) for tag, cand, count in jobs],
@@ -1346,16 +1346,10 @@ async def _proc_grouping(ctx: GenContext, flow: Flow, cards):
members = [m for m in members if m not in used]
if len(members) < 2:
continue
# Bottom-up card-sorting: keine type-gate/min-cos-Vetos mehr — jedes Item soll in
# ein Thema; ein „Fehl-Merge" ist billig (Item bleibt als Sub erhalten, keine Lücke).
mrows = [rows[m - 1] for m in members]
if any(_GROUP_STANDALONE.search(r["title"]) for r in mrows):
skipped.append({"umbrella": title, "grund": "type-gate",
"mitglieder": [r["title"] for r in mrows]})
continue
mc = _min_cos([m - 1 for m in members])
if mc < GROUP_MIN_COS_FLOOR:
skipped.append({"umbrella": title, "grund": "min-cos", "min_cos": mc,
"mitglieder": [r["title"] for r in mrows]})
continue
unorm = _norm_title(title)
member_norms = {r["title_norm"] for r in mrows}
if unorm not in member_norms and unorm in seen_norm:
@@ -1412,7 +1406,7 @@ async def _proc_grouping(ctx: GenContext, flow: Flow, cards):
add = []
for k, new_members in (add or []):
for m in new_members:
if m in used or not (1 <= m <= n) or _GROUP_STANDALONE.search(rows[m - 1]["title"]):
if m in used or not (1 <= m <= n):
continue
used.add(m)
chosen[k]["members"].append(m)
@@ -1622,6 +1616,19 @@ async def _proc_done(ctx: GenContext, flow: Flow, cards):
await db.upsert_block(topic, norm, title, p.get("description", ""), p.get("sources") or [])
await db.set_block_status(topic, norm, "consensus",
title=title, description=p.get("description", ""))
# Bottom-up-Persistenz: die geclusterten Atome bleiben als Subs (jedes Atom genau
# ein Sub). Ein childless Standalone-Block ist sein eigener einziger Sub. Board 2
# reichert diese an, entdeckt sie nie neu — so überlebt jedes Board-1-Atom.
sub_titles = [(ch.get("title") if isinstance(ch, dict) else ch)
for ch in (p.get("children") or [])]
sub_titles = [s for s in sub_titles if s] or [title]
seen_sub: set[str] = set()
for st in sub_titles:
sn = _norm_title(st)
if not sn or sn in seen_sub:
continue
seen_sub.add(sn)
await db.put_subblock(topic, norm, sn, title, st, status="consensus")
mirrored[norm] = c["card_id"]
p.update(mirrored_norm=norm, title=title)
await db.kanban_set_payload(topic, BOARD, c["card_id"], p)