This commit is contained in:
team3
2026-07-04 02:32:31 +02:00
parent 91b0d00aa1
commit c4caf31ed0
38 changed files with 3849 additions and 118 deletions

View File

@@ -213,7 +213,8 @@ CREATE TABLE IF NOT EXISTS events (
status TEXT NOT NULL DEFAULT '',
dur_ms INTEGER,
wait_ms INTEGER,
meta TEXT NOT NULL DEFAULT '{}'
meta TEXT NOT NULL DEFAULT '{}',
run_id TEXT NOT NULL DEFAULT ''
)
"""
@@ -354,6 +355,10 @@ async def init_db():
await db.execute("DROP TABLE IF EXISTS vertiefungen")
await db.execute("DROP TABLE IF EXISTS block_texte")
await db.execute("DROP TABLE IF EXISTS guide_progress")
try: # migration: run_id per generation run (QA groups events by it)
await db.execute("ALTER TABLE events ADD COLUMN run_id TEXT NOT NULL DEFAULT ''")
except aiosqlite.OperationalError:
pass
await db.execute(
"UPDATE guides SET status = 'error', progress = NULL, error_msg = 'Server restart' "
"WHERE status IN ('queued', 'generating')"
@@ -722,14 +727,27 @@ async def kanban_advance(topic: str, board: str, card_id: str, stage: str) -> No
await kanban_advance_many(topic, board, [(card_id, stage)])
# Current generation run per topic — every event writer stamps run_id from here, so no
# signature threading through agents/kanban is needed. Set/cleared by the flow entries
# (board_inventory.run_boards, guide_board.run_guide_board).
_current_run: dict[str, str] = {}
def set_current_run(topic: str, run_id: str | None) -> None:
if run_id:
_current_run[topic] = run_id
else:
_current_run.pop(topic, None)
async def add_event(topic: str, kind: str, key: str = "", label: str = "", status: str = "",
dur_ms: int | None = None, wait_ms: int | None = None, meta: dict | None = None) -> None:
"""One pipeline-history row, own commit. Callers treat this as fire-and-forget."""
db = await get_db()
await db.execute(
"INSERT INTO events (topic, ts, kind, key, label, status, dur_ms, wait_ms, meta) VALUES (?,?,?,?,?,?,?,?,?)",
"INSERT INTO events (topic, ts, kind, key, label, status, dur_ms, wait_ms, meta, run_id) VALUES (?,?,?,?,?,?,?,?,?,?)",
(topic, _now(), kind, key, label, status, dur_ms, wait_ms,
json.dumps(meta or {}, ensure_ascii=False)))
json.dumps(meta or {}, ensure_ascii=False), _current_run.get(topic, "")))
await db.commit()
@@ -737,9 +755,10 @@ async def _add_events_many(db, topic: str, rows: list[tuple]) -> None:
"""Batch insert WITHOUT commit — must run inside the caller's transaction
(kanban_advance_many) so the event batch stays atomic with the moves."""
now = _now()
rid = _current_run.get(topic, "")
await db.executemany(
"INSERT INTO events (topic, ts, kind, key, label, status, dur_ms, wait_ms, meta) VALUES (?,?,?,?,?,?,?,?,?)",
[(topic, now, kind, key, label, status, None, None, "{}")
"INSERT INTO events (topic, ts, kind, key, label, status, dur_ms, wait_ms, meta, run_id) VALUES (?,?,?,?,?,?,?,?,?,?)",
[(topic, now, kind, key, label, status, None, None, "{}", rid)
for kind, key, label, status in rows])
@@ -828,13 +847,39 @@ async def kanban_fail_card(topic: str, board: str, card_id: str, error: str,
(retries, _now_plus(backoff_base * (2 ** (retries - 1))), error[:500], _now(),
topic, board, card_id))
await db.execute(
"INSERT INTO events (topic, ts, kind, key, label, status, dur_ms, wait_ms, meta) VALUES (?,?,?,?,?,?,?,?,?)",
"INSERT INTO events (topic, ts, kind, key, label, status, dur_ms, wait_ms, meta, run_id) VALUES (?,?,?,?,?,?,?,?,?,?)",
(topic, _now(), "fail", f"{board}:{card_id}", "", "dead" if dead else f"retry{retries}",
None, None, json.dumps({"error": error[:200]}, ensure_ascii=False)))
None, None, json.dumps({"error": error[:200]}, ensure_ascii=False), _current_run.get(topic, "")))
await db.commit()
return dead
async def events_run_summary(topic: str, run_id: str) -> dict:
"""Agent/token aggregate of ONE run — the numbers block of lauf-summary.json."""
db = await get_db()
cursor = await db.execute(
"""SELECT status, COUNT(*), SUM(dur_ms),
SUM(json_extract(meta,'$.tokens.input')), SUM(json_extract(meta,'$.tokens.output')),
SUM(json_extract(meta,'$.tokens.cache_read')), SUM(json_extract(meta,'$.tokens.cache_write'))
FROM events WHERE topic = ? AND run_id = ? AND kind = 'agent' GROUP BY status""",
(topic, run_id))
agents = {"gesamt": 0, "ok": 0, "timeout": 0, "cancelled": 0, "sonstige": 0, "verlorene_min": 0}
tokens = {"input": 0, "output": 0, "cache_read": 0, "cache_write": 0}
for status, n, dur, ti, to, cr, cw in await cursor.fetchall():
agents["gesamt"] += n
if status in ("ok", "timeout", "cancelled"):
agents[status] += n
else:
agents["sonstige"] += n
if status != "ok":
agents["verlorene_min"] += round((dur or 0) / 60000)
tokens["input"] += ti or 0
tokens["output"] += to or 0
tokens["cache_read"] += cr or 0
tokens["cache_write"] += cw or 0
return {"agents": agents, "tokens": tokens}
async def kanban_dead(topic: str) -> list[dict]:
"""Dead-letter cards across boards (for the board UI + requeue)."""
return await kanban_cards(topic, stage="dead")
@@ -873,6 +918,14 @@ async def kanban_stage_cards(topic: str, board: str, stage: str, limit: int = 20
return [_card(row, cursor) for row in await cursor.fetchall()]
async def kanban_delete_card(topic: str, board: str, card_id: str) -> None:
"""Delete ONE card (repair: the merged-away/removed block's board-2 card)."""
db = await get_db()
await db.execute("DELETE FROM kanban_cards WHERE topic = ? AND board = ? AND card_id = ?",
(topic, board, card_id))
await db.commit()
async def kanban_delete_cards(topic: str, board: str, kind: str | None = None) -> None:
"""Delete derived cards (board reset) — kind=None wipes the whole board."""
db = await get_db()
@@ -928,11 +981,16 @@ async def upsert_guide_card(topic: str, format: str, block_norm: str, block: str
await db.commit()
async def list_guide_cards(topic: str, format: str) -> list[dict]:
async def list_guide_cards(topic: str, format: str | None = None) -> list[dict]:
"""format=None: alle Formate — das Guide-QA misst den Bestand topic-weit."""
db = await get_db()
cursor = await db.execute(
"SELECT * FROM guide_cards WHERE topic = ? AND format = ? ORDER BY ord, block_norm",
(topic, format))
if format is None:
cursor = await db.execute(
"SELECT * FROM guide_cards WHERE topic = ? ORDER BY format, ord, block_norm", (topic,))
else:
cursor = await db.execute(
"SELECT * FROM guide_cards WHERE topic = ? AND format = ? ORDER BY ord, block_norm",
(topic, format))
return [_row_to_dict(row, cursor) for row in await cursor.fetchall()]
@@ -1384,6 +1442,22 @@ async def subs_per_level_norm(topic: str) -> dict[str, dict[int, int]]:
return out
async def delete_artefakt_row(topic: str, block_norm: str, sub_norm: str, type: str) -> None:
"""Remove ONE artefact row (repair: dead target — sub discarded or gone)."""
db = await get_db()
await db.execute("DELETE FROM sub_artefakte WHERE topic = ? AND block_norm = ? AND sub_norm = ? AND type = ?",
(topic, block_norm, sub_norm, type))
await db.commit()
async def delete_frage_row(topic: str, block_norm: str, sub_norm: str) -> None:
"""Remove ONE question_pattern row (repair: dead target)."""
db = await get_db()
await db.execute("DELETE FROM question_pattern WHERE topic = ? AND block_norm = ? AND sub_norm = ?",
(topic, block_norm, sub_norm))
await db.commit()
async def delete_sub_artefakte(topic: str, block_norm: str | None = None) -> None:
db = await get_db()
if block_norm is None: