"""Ketten-Builder: groups Vorlagen into chains based on Aktenzeichen-Suffix references.""" import sqlite3 from collections import defaultdict from tracker.core.status import compute_status def build_suffix_references(conn: sqlite3.Connection) -> int: """Create referenzen entries for Aktenzeichen-Suffix relations. E.g. 0362/2025-1 references 0362/2025 via suffix relation. Returns the number of new references created. """ cursor = conn.execute(""" INSERT OR IGNORE INTO referenzen (quelle_id, ziel_id, typ, konfidenz) SELECT child.id, parent.id, 'suffix', 1.0 FROM vorlagen child JOIN vorlagen parent ON child.aktenzeichen_basis = parent.aktenzeichen_basis WHERE child.aktenzeichen_suffix IS NOT NULL AND parent.aktenzeichen_suffix IS NULL AND child.id != parent.id """) # Also link sequential suffixes: -2 -> -1, -3 -> -2, etc. conn.execute(""" INSERT OR IGNORE INTO referenzen (quelle_id, ziel_id, typ, konfidenz) SELECT later.id, earlier.id, 'suffix', 1.0 FROM vorlagen later JOIN vorlagen earlier ON later.aktenzeichen_basis = earlier.aktenzeichen_basis AND later.aktenzeichen_suffix IS NOT NULL AND earlier.aktenzeichen_suffix IS NOT NULL AND CAST(REPLACE(later.aktenzeichen_suffix, '-', '') AS INTEGER) = CAST(REPLACE(earlier.aktenzeichen_suffix, '-', '') AS INTEGER) + 1 WHERE later.id != earlier.id """) conn.commit() return cursor.rowcount def build_chains(conn: sqlite3.Connection) -> int: """Build ketten from Vorlagen that share the same aktenzeichen_basis. A chain's Ursprung is the Vorlage without suffix (the original). Chain members are ordered by suffix number. Returns the number of chains created/updated. """ # Find all aktenzeichen_basis values that have at least one entry # and where the base vorlage is an antrag, anfrage, or stellungnahme rows = conn.execute(""" SELECT aktenzeichen_basis, COUNT(*) as cnt FROM vorlagen WHERE aktenzeichen_basis IS NOT NULL GROUP BY aktenzeichen_basis HAVING cnt >= 1 """).fetchall() count = 0 for row in rows: basis = row["aktenzeichen_basis"] # Get all Vorlagen in this chain, ordered by suffix members = conn.execute(""" SELECT id, aktenzeichen, aktenzeichen_suffix, typ, datum_eingang, betreff FROM vorlagen WHERE aktenzeichen_basis = ? ORDER BY CASE WHEN aktenzeichen_suffix IS NULL THEN 0 ELSE CAST(REPLACE(aktenzeichen_suffix, '-', '') AS INTEGER) END """, (basis,)).fetchall() if not members: continue ursprung = members[0] # Only create chains for antrag/anfrage types (the base should be one) chain_typ = ursprung["typ"] if chain_typ not in ("antrag", "anfrage"): continue # Compute status status_info = compute_status(conn, ursprung["id"], chain_typ, members) # Determine letzte_aktivitaet dates = [m["datum_eingang"] for m in members if m["datum_eingang"]] letzte_aktivitaet = max(dates) if dates else ursprung["datum_eingang"] # Check if chain already exists existing = conn.execute( "SELECT id FROM ketten WHERE ursprung_id = ?", (ursprung["id"],) ).fetchone() if existing: kette_id = existing["id"] conn.execute(""" UPDATE ketten SET typ = ?, thema = ?, status = ?, status_seit = ?, letzte_aktivitaet = ?, vertagungen_count = ? WHERE id = ? """, ( chain_typ, ursprung["betreff"], status_info["status"], status_info.get("status_seit"), letzte_aktivitaet, status_info.get("vertagungen_count", 0), kette_id, )) conn.execute("DELETE FROM ketten_glieder WHERE kette_id = ?", (kette_id,)) else: cursor = conn.execute(""" INSERT INTO ketten (ursprung_id, typ, thema, status, status_seit, letzte_aktivitaet, vertagungen_count) VALUES (?, ?, ?, ?, ?, ?, ?) """, ( ursprung["id"], chain_typ, ursprung["betreff"], status_info["status"], status_info.get("status_seit"), letzte_aktivitaet, status_info.get("vertagungen_count", 0), )) kette_id = cursor.lastrowid # Insert chain members for pos, member in enumerate(members): rolle = _determine_rolle(member, pos) conn.execute(""" INSERT OR REPLACE INTO ketten_glieder (kette_id, vorlage_id, position, rolle) VALUES (?, ?, ?, ?) """, (kette_id, member["id"], pos, rolle)) count += 1 conn.commit() return count def _determine_rolle(member: sqlite3.Row, position: int) -> str: if position == 0: return "ursprung" typ = member["typ"] if typ == "stellungnahme": return "stellungnahme" if typ == "bericht": return "bericht" if typ in ("antrag", "anfrage"): return "aenderung" return "ergaenzung"