ADR 0015 fixiert die zwei strukturellen Entscheidungen vom 2.0-Cut: - Prod-Deploy ueber sauberen git-Checkout statt Tar-Upload (loest ADR 0004 in Teilen ab) - Reconstruct_zitate-Zwei-Pass: Zitate werden ueber beide Bloecke hinweg klassifiziert, dann erst geschrieben — Cross-Block-Move statt nur quelle-Korrektur scripts/migrate-zitate-blocks.py: idempotentes String-basiertes Migrations-Skript fuer bestehende Records mit altem Bug-Stand. Nicht LLM-abhaengig, deterministisch. Beim 2.0-Cut auf 22 Assessments angewendet (26 Zitate verschoben).
92 lines
2.9 KiB
Python
Executable File
92 lines
2.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Retro-Migration: Zitate, deren `quelle`-Label auf das jeweils andere
|
|
Programm verweist, wandern in den passenden Block.
|
|
|
|
Hintergrund: Vor Commit 4b9c65c hat ``reconstruct_zitate`` bei einem
|
|
Cross-Kind-Fallback-Match nur die ``quelle`` korrigiert, das Zitat aber
|
|
im urspruenglichen Block belassen. Folge: Im wahlprogramm-Block standen
|
|
auch Zitate aus dem Grundsatzprogramm. Der Code-Fix korrigiert das fuer
|
|
neue Bewertungen — dieses Skript korrigiert die bestehenden Records.
|
|
|
|
Heuristik (string-basiert, ohne LLM/Re-Bewertung):
|
|
- quelle enthaelt 'Grundsatzprogramm' (case-insensitive) → parteiprogramm-Block
|
|
- quelle enthaelt 'Wahlprogramm' (ohne 'Grundsatz') → wahlprogramm-Block
|
|
- sonst: bleibt wo es ist
|
|
|
|
Idempotent: doppelter Lauf bewegt nichts mehr.
|
|
|
|
Usage (aus dem Container):
|
|
docker exec gwoe-antragspruefer python /app/scripts/migrate-zitate-blocks.py # dry-run
|
|
docker exec gwoe-antragspruefer python /app/scripts/migrate-zitate-blocks.py --apply # commit
|
|
"""
|
|
import json
|
|
import sqlite3
|
|
import sys
|
|
|
|
DRY_RUN = "--apply" not in sys.argv
|
|
|
|
db = sqlite3.connect("/app/data/gwoe-antraege.db")
|
|
db.row_factory = sqlite3.Row
|
|
moved = 0
|
|
touched_assessments = 0
|
|
|
|
rows = db.execute(
|
|
"SELECT drucksache, bundesland, wahlprogramm_scores FROM assessments "
|
|
"WHERE wahlprogramm_scores IS NOT NULL"
|
|
).fetchall()
|
|
|
|
for r in rows:
|
|
raw = r["wahlprogramm_scores"]
|
|
if not raw:
|
|
continue
|
|
try:
|
|
wps = json.loads(raw)
|
|
except Exception:
|
|
continue
|
|
|
|
changed = False
|
|
for wp in (wps or []):
|
|
wp_blk = wp.get("wahlprogramm") or {}
|
|
pp_blk = wp.get("parteiprogramm") or {}
|
|
wp_zitate = list(wp_blk.get("zitate") or [])
|
|
pp_zitate = list(pp_blk.get("zitate") or [])
|
|
|
|
new_wp, new_pp = [], []
|
|
for z in wp_zitate:
|
|
q = (z.get("quelle") or "").lower()
|
|
if "grundsatzprogramm" in q:
|
|
new_pp.append(z)
|
|
moved += 1
|
|
else:
|
|
new_wp.append(z)
|
|
for z in pp_zitate:
|
|
q = (z.get("quelle") or "").lower()
|
|
if "wahlprogramm" in q and "grundsatz" not in q:
|
|
new_wp.append(z)
|
|
moved += 1
|
|
else:
|
|
new_pp.append(z)
|
|
|
|
if new_wp != wp_zitate or new_pp != pp_zitate:
|
|
wp_blk["zitate"] = new_wp
|
|
wp["wahlprogramm"] = wp_blk
|
|
pp_blk["zitate"] = new_pp
|
|
wp["parteiprogramm"] = pp_blk
|
|
changed = True
|
|
|
|
if changed:
|
|
touched_assessments += 1
|
|
if not DRY_RUN:
|
|
db.execute(
|
|
"UPDATE assessments SET wahlprogramm_scores=? WHERE drucksache=?",
|
|
(json.dumps(wps, ensure_ascii=False), r["drucksache"]),
|
|
)
|
|
|
|
if not DRY_RUN:
|
|
db.commit()
|
|
|
|
print(f"DRY_RUN={DRY_RUN}")
|
|
print(f"Zitate verschoben: {moved}")
|
|
print(f"Assessments betroffen: {touched_assessments}")
|
|
db.close()
|