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
This commit is contained in:
parent
77371fafc9
commit
401cd3acb0
187
scripts/fix_orphan_chains.py
Normal file
187
scripts/fix_orphan_chains.py
Normal file
@ -0,0 +1,187 @@
|
||||
#!/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)
|
||||
Loading…
Reference in New Issue
Block a user