antragstracker/scripts/fix_orphan_chains.py
Dotty Dotter 401cd3acb0 fix: 43 verwaiste Suffix-Vorlagen in Ketten zusammengeführt
- fix_orphan_chains.py: Findet Suffix-Dokumente (z.B. 0010/2025-1) ohne Kette
- Matcht automatisch mit Basis-Vorlage (0010/2025) → erstellt Kette
- 43 neue Ketten, 5 bestehende erweitert, 3 übersprungen (Basis fehlt)
- Status-Engine auf alle neuen Ketten angewendet
- Beispiel: Rüggeweg 0010/2025 + 0010/2025-1 → Kette 6313
2026-04-01 14:07:01 +02:00

188 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Fix orphaned suffix documents: match to base and create/extend chains.
Finds suffix documents (e.g. 0010/2025-1) not in any chain,
looks up their base document (0010/2025), and creates or extends chains.
"""
import sqlite3
import sys
from pathlib import Path
from datetime import datetime
def fix(db_path: str):
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode = WAL")
conn.execute("PRAGMA foreign_keys = ON")
# Find suffix documents not in any chain
orphans = conn.execute("""
SELECT v.id, v.aktenzeichen, v.aktenzeichen_basis, v.aktenzeichen_suffix,
v.typ, v.datum_eingang
FROM vorlagen v
WHERE v.aktenzeichen_suffix IS NOT NULL
AND v.id NOT IN (SELECT vorlage_id FROM ketten_glieder)
ORDER BY v.aktenzeichen_basis, v.aktenzeichen_suffix
""").fetchall()
print(f"🔍 {len(orphans)} Suffix-Vorlagen ohne Kette gefunden")
created = 0
extended = 0
skipped = 0
for orphan in orphans:
basis_az = orphan["aktenzeichen_basis"]
# Find base document
basis = conn.execute(
"SELECT id, aktenzeichen, typ, betreff, datum_eingang FROM vorlagen "
"WHERE aktenzeichen_basis = ? AND (aktenzeichen_suffix IS NULL OR aktenzeichen_suffix = '')",
(basis_az,)
).fetchone()
if not basis:
# No base document found - skip
print(f" ⚠️ {orphan['aktenzeichen']}: Basis {basis_az} nicht gefunden — übersprungen")
skipped += 1
continue
# Is the base already in a chain?
existing_chain = conn.execute(
"SELECT kette_id FROM ketten_glieder WHERE vorlage_id = ?",
(basis["id"],)
).fetchone()
if existing_chain:
# Extend existing chain
kette_id = existing_chain["kette_id"]
max_pos = conn.execute(
"SELECT MAX(position) as mp FROM ketten_glieder WHERE kette_id = ?",
(kette_id,)
).fetchone()["mp"] or 0
# Check suffix isn't already in this chain
already = conn.execute(
"SELECT 1 FROM ketten_glieder WHERE kette_id = ? AND vorlage_id = ?",
(kette_id, orphan["id"])
).fetchone()
if already:
continue
conn.execute(
"INSERT INTO ketten_glieder (kette_id, vorlage_id, position, rolle) VALUES (?, ?, ?, ?)",
(kette_id, orphan["id"], max_pos + 1, _rolle(orphan["typ"]))
)
extended += 1
print(f" {orphan['aktenzeichen']} → Kette {kette_id} (Position {max_pos + 1})")
else:
# Create new chain with base + suffix
# Determine chain type from base
typ = _chain_type(basis["typ"])
conn.execute(
"INSERT INTO ketten (ursprung_id, typ, thema, status, begruendung) VALUES (?, ?, ?, ?, ?)",
(basis["id"], typ, basis["betreff"], "in_beratung",
f"Automatisch erstellt: {basis['aktenzeichen']} + Suffix-Dokument(e)")
)
kette_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
# Add base as position 0
conn.execute(
"INSERT INTO ketten_glieder (kette_id, vorlage_id, position, rolle) VALUES (?, ?, ?, ?)",
(kette_id, basis["id"], 0, "ursprung")
)
# Add suffix as position 1
conn.execute(
"INSERT INTO ketten_glieder (kette_id, vorlage_id, position, rolle) VALUES (?, ?, ?, ?)",
(kette_id, orphan["id"], 1, _rolle(orphan["typ"]))
)
created += 1
print(f" 🆕 Kette {kette_id}: {basis['aktenzeichen']} + {orphan['aktenzeichen']}")
conn.commit()
# Also find base documents whose suffixes ARE in chains but the base itself isn't
base_orphans = conn.execute("""
SELECT v.id, v.aktenzeichen, v.aktenzeichen_basis
FROM vorlagen v
WHERE v.aktenzeichen_suffix IS NULL
AND v.aktenzeichen_basis IN (
SELECT DISTINCT v2.aktenzeichen_basis
FROM vorlagen v2
JOIN ketten_glieder kg ON kg.vorlage_id = v2.id
WHERE v2.aktenzeichen_suffix IS NOT NULL
)
AND v.id NOT IN (SELECT vorlage_id FROM ketten_glieder)
""").fetchall()
base_added = 0
for bv in base_orphans:
# Find which chain the suffix is in
chain = conn.execute("""
SELECT kg.kette_id FROM ketten_glieder kg
JOIN vorlagen v ON kg.vorlage_id = v.id
WHERE v.aktenzeichen_basis = ? AND v.aktenzeichen_suffix IS NOT NULL
LIMIT 1
""", (bv["aktenzeichen_basis"],)).fetchone()
if chain:
already = conn.execute(
"SELECT 1 FROM ketten_glieder WHERE kette_id = ? AND vorlage_id = ?",
(chain["kette_id"], bv["id"])
).fetchone()
if not already:
# Insert base at position 0, shift others up
conn.execute(
"UPDATE ketten_glieder SET position = position + 1 WHERE kette_id = ?",
(chain["kette_id"],)
)
conn.execute(
"INSERT INTO ketten_glieder (kette_id, vorlage_id, position, rolle) VALUES (?, ?, 0, 'ursprung')",
(chain["kette_id"], bv["id"])
)
conn.execute(
"UPDATE ketten SET ursprung_id = ? WHERE id = ?",
(bv["id"], chain["kette_id"])
)
base_added += 1
print(f" 📎 Basis {bv['aktenzeichen']} → Kette {chain['kette_id']} (Position 0)")
conn.commit()
print(f"\n✅ Fertig:")
print(f" {created} neue Ketten erstellt")
print(f" {extended} Ketten erweitert")
print(f" {base_added} Basis-Dokumente nachgetragen")
print(f" {skipped} übersprungen (Basis nicht gefunden)")
# Run status engine on new chains
new_chain_count = created + extended + base_added
if new_chain_count > 0:
print(f"\n🔄 Status-Engine für betroffene Ketten wird NICHT automatisch ausgeführt.")
print(f" Bitte manuell: python -c \"from tracker.core.status import update_all_chains; ...\"")
conn.close()
def _rolle(typ):
if typ in ("stellungnahme", "bericht"):
return "antwort"
return "folge"
def _chain_type(typ):
if typ == "antrag":
return "antrag"
elif typ == "anfrage":
return "anfrage"
return "sonstig"
if __name__ == "__main__":
db = sys.argv[1] if len(sys.argv) > 1 else str(
Path(__file__).resolve().parents[1] / "data" / "tracker.db"
)
print(f"🔧 Orphan-Chain-Fix: {db}")
fix(db)