Vollständige Pipeline zur Analyse kommunaler Vorlagen aus ALLRIS: - OParl-Import: 20.149 Vorlagen - PDF-Extraktion: 10.045 Volltexte (adaptives Throttling) - KI-Zusammenfassungen: 10.026 via Qwen Plus (parallelisiert) - Beratungsfolge-Scraper: Beschlusstexte + Wortprotokolle - Abstimmungs-Analyse mit Koalitionsmatrix - Georeferenzierung (Nominatim) Stack: FastAPI + SvelteKit + SQLite Deployment: Docker + Traefik auf VServer Daten (DB, Logs) nicht im Repo — siehe Restic-Backup. Repo-Setup: scripts/setup.sh für Neuaufbau aus OParl-API.
90 lines
3.0 KiB
Python
90 lines
3.0 KiB
Python
"""Tests for the Ketten-Builder and Status-Engine."""
|
|
|
|
import pytest
|
|
|
|
from tracker.core.chains import build_suffix_references, build_chains
|
|
from tracker.db.session import get_connection
|
|
|
|
|
|
@pytest.fixture
|
|
def db():
|
|
conn = get_connection()
|
|
yield conn
|
|
conn.close()
|
|
|
|
|
|
class TestSuffixReferences:
|
|
def test_build_suffix_references(self, db):
|
|
"""Should create referenzen for suffix-linked Vorlagen."""
|
|
count = build_suffix_references(db)
|
|
# We know there are 166 suffix Vorlagen, so we should get references
|
|
total = db.execute(
|
|
"SELECT COUNT(*) as cnt FROM referenzen WHERE typ = 'suffix'"
|
|
).fetchone()["cnt"]
|
|
assert total > 0, "No suffix references created"
|
|
|
|
def test_suffix_reference_links_correct(self, db):
|
|
"""Verify that suffix references link child -> parent correctly."""
|
|
build_suffix_references(db)
|
|
# Check a known suffix pair
|
|
row = db.execute("""
|
|
SELECT r.quelle_id, r.ziel_id,
|
|
q.aktenzeichen as q_az, z.aktenzeichen as z_az
|
|
FROM referenzen r
|
|
JOIN vorlagen q ON r.quelle_id = q.id
|
|
JOIN vorlagen z ON r.ziel_id = z.id
|
|
WHERE r.typ = 'suffix'
|
|
LIMIT 5
|
|
""").fetchall()
|
|
|
|
for r in row:
|
|
# The quelle (child) should have a suffix, the ziel (parent) should not or have lower suffix
|
|
assert r["q_az"] is not None
|
|
|
|
|
|
class TestChainBuilder:
|
|
def test_build_chains(self, db):
|
|
"""Should create ketten for Anträge and Anfragen."""
|
|
build_suffix_references(db)
|
|
count = build_chains(db)
|
|
assert count > 0, "No chains built"
|
|
|
|
# Verify chains exist in DB
|
|
total = db.execute("SELECT COUNT(*) as cnt FROM ketten").fetchone()["cnt"]
|
|
assert total > 0
|
|
|
|
def test_chain_has_glieder(self, db):
|
|
"""Each chain should have at least one Glied (the Ursprung)."""
|
|
build_suffix_references(db)
|
|
build_chains(db)
|
|
|
|
orphans = db.execute("""
|
|
SELECT k.id FROM ketten k
|
|
LEFT JOIN ketten_glieder kg ON k.id = kg.kette_id
|
|
WHERE kg.kette_id IS NULL
|
|
""").fetchall()
|
|
|
|
assert len(orphans) == 0, f"Found {len(orphans)} chains without Glieder"
|
|
|
|
def test_chain_status_is_set(self, db):
|
|
"""Every chain should have a status."""
|
|
build_suffix_references(db)
|
|
build_chains(db)
|
|
|
|
no_status = db.execute(
|
|
"SELECT COUNT(*) as cnt FROM ketten WHERE status IS NULL"
|
|
).fetchone()["cnt"]
|
|
|
|
assert no_status == 0, f"Found {no_status} chains without status"
|
|
|
|
def test_chain_types(self, db):
|
|
"""Chains should only be antrag or anfrage."""
|
|
build_suffix_references(db)
|
|
build_chains(db)
|
|
|
|
types = db.execute(
|
|
"SELECT DISTINCT typ FROM ketten"
|
|
).fetchall()
|
|
type_names = {r["typ"] for r in types}
|
|
assert type_names <= {"antrag", "anfrage"}, f"Unexpected chain types: {type_names}"
|