update
This commit is contained in:
@@ -20,6 +20,7 @@ import re
|
||||
|
||||
import database as db
|
||||
import readability
|
||||
from blocks import _sink_json
|
||||
from config import FORMAT_PURPOSE, READABILITY_ACTIVE, TEMPLATES_DIR, MAX_CONCURRENT_AGENTS_PER_TOPIC
|
||||
from fsutil import atomic_write_json
|
||||
from jsonio import read_json_file as _json_file
|
||||
@@ -195,22 +196,34 @@ async def _set(env: _Env, card: dict, **fields):
|
||||
async def _stage_lernziele(env: _Env, card: dict) -> bool:
|
||||
norm = card["block_norm"]
|
||||
if not await db.list_lernziele(env.topic, norm):
|
||||
path = env.slot(f"ziele-{_safe(norm)}.json")
|
||||
subs = "\n".join(f"- [{s.get('level', 'beginner')}] {s['title']}"
|
||||
for s in env.subs_by_title.get(card["block"], [])) or "(keine)"
|
||||
status, ziele = await run_single_slot(
|
||||
env.ctx, f"Lernziele {card['block']}", key=f"{env.guide_id}-ziele-{_safe(norm)}",
|
||||
prompt=_prompt("Guide-Lernziele", topic=env.topic, block=card["block"],
|
||||
subs=subs, facts=_card_facts(env, card["block"]),
|
||||
out_path=path, extra=_extra(env.instructions)),
|
||||
role="judge", capabilities="files",
|
||||
payload=lambda result: _ziele_schema(_json_file(path)),
|
||||
timeout=_timeout("lernziele", len(env.subs_by_title.get(card["block"], []))))
|
||||
|
||||
async def _versuch(suffix: str):
|
||||
path = env.slot(f"ziele-{_safe(norm)}{suffix}.json")
|
||||
return await run_single_slot(
|
||||
env.ctx, f"Lernziele {card['block']}", key=f"{env.guide_id}-ziele-{_safe(norm)}{suffix}",
|
||||
prompt=_prompt("Guide-Lernziele", topic=env.topic, block=card["block"],
|
||||
subs=subs, facts=_card_facts(env, card["block"]),
|
||||
out_path=path, extra=_extra(env.instructions)),
|
||||
role="judge", capabilities="files",
|
||||
payload=lambda result, p=path: _ziele_schema(_json_file(p)),
|
||||
timeout=_timeout("lernziele", len(env.subs_by_title.get(card["block"], []))))
|
||||
|
||||
status, ziele = await _versuch("")
|
||||
if status == CANCELLED:
|
||||
return False
|
||||
if status == FAILED:
|
||||
await _set(env, card, status="error", gate_info="Lernziele ohne Ergebnis")
|
||||
return False
|
||||
if not ziele: # ein Ersatz-Versuch — leere Liste heißt: das Coverage-Gate läuft leer
|
||||
status, ziele = await _versuch("-2")
|
||||
if status == CANCELLED:
|
||||
return False
|
||||
if not isinstance(ziele, list):
|
||||
ziele = []
|
||||
if not ziele:
|
||||
_log(env.topic, f"Lernziele {card['block']}: zweimal leer — Block ohne Coverage-Gate")
|
||||
for z in ziele:
|
||||
await db.put_lernziel(env.topic, norm, z["id"], z["text"], _norm_title(z["sub"]))
|
||||
await _set(env, card, stage="zuweisung", status="open")
|
||||
@@ -254,6 +267,12 @@ def _merge_split_sections(sec_a: dict, sec_b: dict) -> str:
|
||||
return "\n\n".join(lines)
|
||||
|
||||
|
||||
def _writer_budget(n_subs: int, sockel: int = 800) -> int:
|
||||
"""Length guideline (chars) for the detailed version — unguided sections measured 2–4×
|
||||
too long (23k) or, after the readability fix, far too thin (180 chars/sub)."""
|
||||
return sockel + 400 * max(n_subs, 1)
|
||||
|
||||
|
||||
async def _write_split(env: _Env, card: dict, ziele_text: str):
|
||||
"""First draft in two halves (parallel), merged into one section.
|
||||
→ merged text | None (failed) | False (cancelled)."""
|
||||
@@ -290,6 +309,7 @@ async def _write_split(env: _Env, card: dict, ziele_text: str):
|
||||
examples=await _card_examples(env, norm, parts[i],
|
||||
include_unmatched=(i == 0)),
|
||||
gaps="\n" + hints[i] + "\n",
|
||||
budget=_writer_budget(len(parts[i]), sockel=400),
|
||||
spec=env.spec, out_path=path, extra=_extra(env.instructions)),
|
||||
role="guide", capabilities="files", payload=_payload,
|
||||
timeout=_timeout("writer", 1))
|
||||
@@ -338,6 +358,7 @@ async def _stage_writer(env: _Env, card: dict) -> bool:
|
||||
facts=_card_facts(env, card["block"]),
|
||||
examples=await _card_examples(env, norm, env.subs_by_title.get(card["block"], [])),
|
||||
gaps=gaps, spec=env.spec,
|
||||
budget=_writer_budget(len(env.subs_by_title.get(card["block"], []))),
|
||||
out_path=path, extra=_extra(env.instructions)),
|
||||
role="guide", capabilities="files", payload=_payload,
|
||||
timeout=_timeout("writer", 1))
|
||||
@@ -455,13 +476,15 @@ async def _stage_lesbarkeit(env: _Env, card: dict) -> bool:
|
||||
return False
|
||||
problems: list[str] = []
|
||||
path = env.slot(f"lese-{_safe(norm)}-r{card['writer_rounds']}.json")
|
||||
# Text-Antwort + Engine-Sink: Datei-schreibende Judges lieferten invalides JSON
|
||||
# (3 kaputte Check-Dateien im Messlauf) — der Sink validiert vor dem Persistieren
|
||||
status, res = await run_single_slot(
|
||||
env.ctx, f"Lese-Check {card['block']}", key=f"{env.guide_id}-lese-{_safe(norm)}",
|
||||
prompt=_prompt("Guide-Lese-Check", topic=env.topic, format_name=env.format,
|
||||
spec=env.spec, sections=f"SECTION: {card['block']}\n{sec['md']}",
|
||||
out_path=path, extra=_extra(env.instructions)),
|
||||
role="judge", capabilities="files",
|
||||
payload=lambda result: _problems_schema(_json_file(path)),
|
||||
extra=_extra(env.instructions)),
|
||||
role="judge", capabilities="none",
|
||||
payload=lambda result: _sink_json(result, path, _problems_schema),
|
||||
timeout=_timeout("lese_check", 1))
|
||||
if status == CANCELLED:
|
||||
return False
|
||||
@@ -477,6 +500,8 @@ async def _stage_lesbarkeit(env: _Env, card: dict) -> bool:
|
||||
sub_list = "\n".join(f"- [{_level_label(s)}] {s['title']}" for s in subs) or "(none)"
|
||||
tasks = (f"SECTION: {card['block']}\n"
|
||||
f"SUBBLOCKS (set one `<!-- sub: LABEL | title -->` marker each, label/order as here):\n{sub_list}\n"
|
||||
f"LENGTH TARGET: about {_writer_budget(len(subs))} characters for the detailed "
|
||||
f"version (guideline — covering every subblock beats brevity).\n"
|
||||
f"PROBLEM: {' · '.join(problems)}\nCURRENT CONTENT:\n{sec['md']}")
|
||||
fixp = env.slot(f"lesefix-{_safe(norm)}.md")
|
||||
fixp.unlink(missing_ok=True)
|
||||
@@ -562,6 +587,9 @@ async def run_guide_board(guide_id: str, topic: str, format_name: str, entries:
|
||||
from guide import _load_subblocks
|
||||
ctx = GenContext(topic=topic, provider=provider,
|
||||
is_cancelled=lambda: is_guide_cancelled(guide_id), guide_id=guide_id)
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
db.set_current_run(topic, f"{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M')}-g{uuid.uuid4().hex[:4]}")
|
||||
spec = (TEMPLATES_DIR / "Format" / "Section.md").read_text(encoding="utf-8")
|
||||
subs_raw = await _load_subblocks(topic)
|
||||
project = source_folder(topic)
|
||||
@@ -590,6 +618,9 @@ async def run_guide_board(guide_id: str, topic: str, format_name: str, entries:
|
||||
await asyncio.gather(*[_run_card(env, c, sem) for c in open_cards])
|
||||
finally:
|
||||
reporter.cancel()
|
||||
db.set_current_run(topic, None)
|
||||
else:
|
||||
db.set_current_run(topic, None)
|
||||
if is_guide_cancelled(guide_id):
|
||||
return None
|
||||
# assembly — identical shape to the legacy pipeline
|
||||
@@ -617,6 +648,14 @@ async def run_guide_board(guide_id: str, topic: str, format_name: str, entries:
|
||||
})
|
||||
for ch in order:
|
||||
chapters.append({"title": ch, "sections": by_chapter[ch]})
|
||||
if chapters:
|
||||
try: # Abschluss-Guide-QA (best-effort): speist das Badge mit einer frischen Note
|
||||
import guide_qa
|
||||
rep = await guide_qa.guide_qa_report(topic, llm=True)
|
||||
if rep:
|
||||
await asyncio.to_thread(guide_qa._write_report, rep)
|
||||
except Exception:
|
||||
log.exception("[%s] Abschluss-Guide-QA fehlgeschlagen", topic)
|
||||
return chapters or None
|
||||
|
||||
|
||||
@@ -655,7 +694,11 @@ async def board_snapshot(topic: str, format_name: str, limit: int = 20) -> dict:
|
||||
"ziele": f"{zc[0]}/{zc[1]}" if zc else ""})
|
||||
columns.append({"key": stage, "label": STAGE_LABELS[stage],
|
||||
"total": len(in_stage), "cards": views})
|
||||
return {"columns": columns}
|
||||
import qa as qa_mod # lazy wie in board_inventory
|
||||
tdir = qa_mod.QA_DIR / topic
|
||||
greports = sorted(tdir.glob("guide-*.json"), key=lambda p: p.stat().st_mtime) if tdir.is_dir() else []
|
||||
note_guide = (_json_file(greports[-1]) or {}).get("note_guide") if greports else None
|
||||
return {"columns": columns, "qa_guide": note_guide}
|
||||
|
||||
|
||||
async def reset_card(topic: str, format_name: str, block_norm: str, ab_stage: int) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user