diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..f9d0b00 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,48 @@ +# GWÖ-Antragsprüfer — Dokumentation + +Diese Dokumentation folgt dem [Diátaxis-Framework](https://diataxis.fr) und ist +nach Funktion (nicht nach Themen) organisiert. Drift-immun durch klare Trennung: +Reference wird aus dem Code generiert, ADRs sind immutable, Tutorials/How-to +sind manuell gepflegt aber knapp. + +## Struktur + +``` +docs/ +├── README.md ← du bist hier +├── adr/ Architecture Decision Records (immutable) +│ ├── README.md ADR-Index + Workflow +│ ├── template.md Template für neue ADRs +│ └── NNNN-titel.md Eine Datei pro Entscheidung +└── archive/ Historische Snapshots, nicht autoritativ +``` + +Geplant für später (siehe Issue #62): + +``` +docs/ +├── reference/ ← mkdocs autodoc-Output (nicht eingecheckt) +├── tutorials/ ← Erst-Schritt-Anleitungen +├── how-to/ ← Aufgaben-orientiert, "wie deploye ich" +└── explanation/ ← Hintergründe, Konzepte +``` + +## Nicht hier dokumentiert (bewusst) + +| Was | Wo | +|---|---| +| API-Reference (Endpoints, Models, Schemas) | Auto-generiert aus FastAPI/Pydantic, nicht in docs/ einchecken | +| Code-Reference (Klassen, Funktionen) | mkdocstrings aus den Docstrings (geplant Phase 2) | +| Aktueller Projekt-Stand / Issues | Gitea Issues — `repo.toppyr.de/tobias/gwoe-antragspruefer/issues` | +| Onboarding für KI-assisted Coder | `CLAUDE.md` im Repo-Root | +| Live-System-Status | `https://gwoe.toppyr.de/auswertungen` (dynamisches Dashboard) | +| Memory der KI-Sessions | `~/.claude/projects//memory/` (privat) | + +## Gegen Drift + +- ADRs sind **immutable**: nie überschreiben, sondern bei Änderung mit einem + neuen ADR superseden, der den alten in seinem Header referenziert. +- Reference wird aus dem Code generiert, **nie** von Hand gepflegt. +- `docs/archive/` enthält historische Status-Files. Werden gelesen aber **nicht + aktualisiert**. Wenn etwas davon noch wahr ist, gehört es in einen ADR oder + in die generierte Reference, nicht in einen neuen Status-Snapshot. diff --git a/docs/adr/0001-llm-citation-binding.md b/docs/adr/0001-llm-citation-binding.md new file mode 100644 index 0000000..7562c6c --- /dev/null +++ b/docs/adr/0001-llm-citation-binding.md @@ -0,0 +1,128 @@ +# 0001 — LLM-Citations server-seitig binden statt prompt-seitig + +| | | +|---|---| +| **Status** | accepted | +| **Datum** | 2026-04-10 | +| **Refs** | Issue #60, Commits eb045d0/ed64399/db3ada9/6ced7ae, Sub-D Tests | + +## Kontext + +Der GWÖ-Antragsprüfer lässt ein LLM (Qwen Plus via DashScope) parlamentarische +Anträge bewerten. Pro Antrag werden via Embedding-Retrieval relevante Chunks +aus den Wahl- und Grundsatzprogrammen der involvierten Parteien zugespielt. +Das LLM gibt im JSON-Output `wahlprogramm_scores[*].wahlprogramm.zitate[*]` +mit jeweils `text`, `quelle` (Source-Label, z.B. "GRÜNE NRW Wahlprogramm +2022, S. 58") und `url` zurück. + +Beim ersten Live-Lauf des Sub-D Citation-Property-Tests (siehe ADR 0003) +gegen die Prod-DB wurden drei Halluzinations-Cases gefunden: das LLM hatte +Snippets erfunden und unter realen Source-Labels zitiert. Issue #60 wurde +geöffnet. + +## Optionen + +Drei strukturell unterschiedliche Wege, das LLM zu binden: + +### Option A — Schärferer Prompt + +Die ZITATEREGEL im User-Prompt wird verschärft. Konkret: + +1. Jeder retrievte Chunk wird mit einer ENUM-ID `[Q1]`, `[Q2]`, … getaggt. +2. Das LLM wird angewiesen, jedes Zitat per `[Qn]`-Tag an einen Chunk zu + binden, den `text` wörtlich aus diesem Chunk zu kopieren und das + Source-Label exakt zu übernehmen. +3. Top-K wird von 2 → 5 erhöht, damit die "richtige" Seite häufiger im + Retrieval-Window landet. + +**Vorteile:** prompt-only, kleiner Diff, keine Server-Pipeline-Änderung. + +**Nachteile:** ENUM-Anker im Prompt ist ein **weicher** Hint. Das LLM darf +zwar den Snippet aus Chunk Qn nehmen, aber es gibt nichts, was es daran +hindert, die Seite aus Chunk Qm in `quelle` zu schreiben. Cross-Mix bleibt +möglich. + +### Option B — Server-seitige Quellen-Rekonstruktion + +Nach dem LLM-Call werden die emittierten Zitate Server-seitig nachgearbeitet: + +1. Für jedes Zitat im Output-JSON wird der `text` per Substring oder + 5-Wort-Anker gegen **alle** retrievten Chunks gematcht. +2. Bei Match: `quelle` und `url` werden aus dem matchenden Chunk **konstruiert** + und der LLM-Output für diese Felder verworfen. +3. Bei kein-Match: das ganze Zitat wird verworfen. + +**Vorteile:** strukturell — der LLM hat keine Möglichkeit mehr, eine falsche +Quelle anzugeben. Die einzigen zwei Outcomes sind "korrekt zitiert" oder +"verworfen". + +**Nachteile:** zusätzlicher Server-seitiger Schritt nach `json.loads` und +vor Pydantic-Validation. Match-Logik muss konsistent mit Sub-D bleiben. + +### Option C — Schema-Änderung mit `quote_id`-Feld + +Statt `quelle` direkt zu emittieren, soll das LLM ein neues Feld `quote_id` +(z.B. `"Q3"`) liefern, aus dem der Server `quelle`/`url` rekonstruiert. + +**Vorteile:** explizit schema-modelliert, keine Heuristik. + +**Nachteile:** invasiv (Pydantic-Modell, JSON-Schema, Frontend, DB-Migration); +verlässt sich darauf, dass das LLM die ID korrekt setzt — wenn es lügt, +ist die Bindung futsch. + +## Entscheidung + +**A + B kombiniert.** Option A liefert die Vorab-Härtung im Prompt — sie +fängt den Großteil der Cases (etwa 80%). Option B ist die strukturelle +Backstop, die die restlichen 20% (echter Snippet aus Qn, falsche Seite +aus Qm) verlässlich abdeckt. Option C wurde verworfen, weil der Schema- +Eingriff den Aufwand nicht rechtfertigt, solange B die LLM-Eingabe ohnehin +ignoriert. + +Konkret in dieser Reihenfolge implementiert: + +1. **`db3ada9`** — Option A: ENUM-Anker `[Q1]/[Q2]/…` im Prompt + Top-K 2→5. + Live-Verifikation der drei Original-Cases aus #60: 13/13 ok. +2. **Sub-D Live-Run gegen Prod-DB**: 45/46 grün, ein neuer Case (BB 8/673) + gefunden mit "echter Snippet, falsche Seite". Beweis dass A allein nicht + reicht. +3. **`6ced7ae`** — Option B: `embeddings.reconstruct_zitate(data, semantic_quotes)` + nach `json.loads` und vor Pydantic-Validation. Helpers `find_chunk_for_text` + und `_normalize_for_match` mit identischer Logik wie Sub-D. Bei Match wird + `_chunk_source_label` und `_chunk_pdf_url` angewendet, bei No-Match wird + das Zitat gedroppt. +4. Sub-D Re-Run nach B: **52/52 grün** über NRW/LSA/BE/MV/BB/BUND. + +## Konsequenzen + +### Positiv + +- **Strukturelle Garantie**: nach `reconstruct_zitate` kann ein im DB + gespeichertes Zitat nur noch eines von zwei Dingen sein: korrekt zitiert + aus einem real retrievten Chunk, oder gar nicht da. +- **Sub-D als CI-Gate**: das Property-Test-Pattern fängt jede künftige + Regression dieser Bug-Klasse. +- **Server vertraut LLM nicht mehr** für `quelle`/`url`. Künftige Modell- + Wechsel oder Prompt-Drift können die Quellen-Korrektheit nicht mehr brechen. + +### Negativ + +- **Fehlende Match-Heuristik = stilles Verschwinden**: Wenn der LLM einen + echten Chunk leicht paraphrasiert (z.B. Komma anders gesetzt), verwirft + Option B das Zitat. Die 5-Wort-Anker-Fallback fängt das in den meisten + Fällen, aber Edge-Cases bleiben. Mitigation: doctest-style Sub-D-Run pro + Deploy + Monitor der `wahlprogramm_scores[*].zitate`-Counts pro Assessment. +- **Code-Pfad-Komplexität**: drei Schichten (Prompt-ENUM, Server-Postprocess, + Sub-D-Test) statt einer. Dafür ist jede Schicht isoliert testbar. +- **`embeddings.py` hat jetzt mehr Verantwortung** als nur Embedding-Retrieval + — auch Match-Helpers und Source-Label-Konstruktion. Akzeptabel solange das + Modul thematisch geschlossen bleibt; bei weiterem Wachstum als + `citations.py` extrahieren. + +### Folgen für andere ADRs + +- **ADR 0003** (Sub-D Citation-Property-Tests) ist die Test-Säule dieser + Entscheidung — wenn 0003 jemals supersedet wird, bricht 0001 ohne den + Test-Backstop möglicherweise still. +- Neue Adapter (zukünftige Bundesländer) müssen sich nicht extra um + Citation-Korrektheit kümmern; der Server-Postprocess ist adapter-agnostisch. diff --git a/docs/adr/0002-adapter-architecture.md b/docs/adr/0002-adapter-architecture.md new file mode 100644 index 0000000..7d7aab5 --- /dev/null +++ b/docs/adr/0002-adapter-architecture.md @@ -0,0 +1,119 @@ +# 0002 — Adapter-Pattern mit ParlamentAdapter-Basisklasse + Registry + +| | | +|---|---| +| **Status** | accepted | +| **Datum** | 2026-04-10 | +| **Refs** | Issues #2, #3, #4, #19, #21, #23, #24, #26, #56, parlamente.py | + +## Kontext + +Der GWÖ-Antragsprüfer soll Anträge aus 16 Bundesländern + dem Bundestag +analysieren können. Jedes Parlament hat ein eigenes Doku-System mit +unterschiedlichen Endpoints, Schemas, Auth-Anforderungen und Quirks: + +| Familie | Mitglieder | Charakter | +|---|---|---| +| portala/eUI | LSA, BE, BB, RP | JSON-Tree-Search mit `parsed`-Strings, HTML-Hits in efxRecordRepeater | +| StarWeb (eUI v2) | HE | `browse.tt.json` 2-step + Perl-Dump-Comments | +| PARiS (Java-Servlet) | HB | älterer StarWeb-Vorgänger, eigenes Hit-Format | +| ParlDok 8.x | MV, HH, TH | SPA mit Fulltext/Search + Resultpage Pagination | +| StarFinder-CGI | SH | `lissh.lvn.parlanet.de/cgi-bin/starfinder/0` | +| OPAL native | NRW | älteres Eigensystem | +| PARLIS (eUI-Variante) | BW | polling, JSON-in-HTML-Comments | +| Eigensysteme | BY (TYPO3-Solr), SL (Umbraco/.NET), SN (ASP.NET-Webforms), BUND (DIP-API) | jeweils komplett unique | + +## Optionen + +### Option A — Eine Mega-Klasse mit Strategy-Pattern + +Eine `ParlamentAdapter`-Klasse mit Strategy-Object pro Doku-System, +gewählt über Konfiguration. + +**Vorteile:** zentral, ein Eintrittspunkt für gemeinsame Logik wie +Fraktion-Normalisierung. + +**Nachteile:** kompliziert sich schnell, wenn jede Strategy ihre eigenen +Quirks hat (z.B. BB hat Datum-vor-Drucksachennummer im h6, BE hat +730d-Window-Hack, HE hat Perl-Hex-Decoder). Die Trennung wäre +"Strategy mit Subclasses" — und damit sind wir bei B. + +### Option B — Abstract Base + Eine konkrete Klasse pro Familie + +`ParlamentAdapter(ABC)` mit `search/get_document/download_text`-Methoden. +Pro Doku-System eine konkrete Klasse: +`PortalaAdapter`, `ParLDokAdapter`, `StarFinderCGIAdapter`, +`StarWebHEAdapter`, `PARiSHBAdapter`, `PARLISAdapter`, `BayernAdapter`, +`SaarlandAdapter`, `BundestagAdapter`, `SNEdasXmlAdapter`, `NRWAdapter`. + +Wo möglich, wird die Klasse via Konstruktor-Parameter parametrisiert +(`PortalaAdapter` nimmt 6 BLs auf, `ParLDokAdapter` 3) statt jede +Wiederverwendung als Subklasse. + +Registriert in einem `ADAPTERS: dict[str, ParlamentAdapter]` am Datei-Ende, +indexiert per BL-Code (`"NRW"`, `"BUND"`, …). + +**Vorteile:** +- Klare Verantwortlichkeit: ein Test-Lauf gegen einen LT trifft genau + eine Klasse. +- Reverse-Engineering-Findings können als Class-Docstring dokumentiert + werden, wo sie hingehören. +- Parametrisierung erlaubt Wiederverwendung ohne Vererbungs-Hierarchie. +- `get_adapter(bundesland)` ist O(1). + +**Nachteile:** +- Cross-Cutting-Concerns (Fraktion-Normalisierung) müssen separat gelöst + werden — siehe ADR-Folge zur `parteien`-Zentralisierung in #55. + +### Option C — Separates Modul pro Adapter + +Ein Python-File pro Adapter (z.B. `app/adapters/lsa.py`). + +**Vorteile:** maximale Datei-Granularität. + +**Nachteile:** Import-Overhead, der Registry-Eintrag muss in einer dritten +Datei liegen, kein nennenswerter Vorteil gegenüber B solange die +Adapter-Klassen unter ~500 Zeilen bleiben (PortalaAdapter ist mit ~520 +Zeilen der größte). + +## Entscheidung + +**Option B**. Eine `parlamente.py` mit einer Klasse pro Doku-System, +parametrisierbar wo sinnvoll, registriert in `ADAPTERS`. Cross-Cutting-Logik +wie Fraktion-Normalisierung wandert in separate Module (`parteien.py`, +`bundeslaender.py`) und wird per Import oder Funktions-Argument injiziert. + +## Konsequenzen + +### Positiv + +- **17 Adapter sauber separierbar** in ~3000 Zeilen `parlamente.py`. Jeder + Adapter kommt mit seinem Reverse-Engineering-Kommentar als Docstring, + was bei Re-Reads nach Wochen die Wiedereinarbeit auf Minuten bringt. +- **Tests parametrisieren über `ADAPTERS`-Registry**: ein einzelner + Test-Helper iteriert alle aktiven Adapter und prüft die Akzeptanz- + Kriterien (search liefert ≥3 Hits, get_document funktioniert, + download_text extrahiert Text). +- **Neue Bundesländer** kommen als 100-300-Zeilen-Klasse + 1 Eintrag in + `ADAPTERS` rein, ohne Berührung anderer Adapter. + +### Negativ + +- **`parlamente.py` ist groß** (~3000 Zeilen, 17 Klassen). Code-Navigation + via Grep statt Auto-Imports. +- **Reverse-Engineering-Findings sind in Kommentaren**, nicht in einer + reference-Sektion. Wenn sich das System ändert, kann der Kommentar + veralten — Mitigation: jede Reverse-Engineering-Session legt einen + HAR-Trace in `TEMP/` und referenziert ihn im Class-Docstring. +- **Adapter-Bugs leak nicht in andere Adapter**, aber **gemeinsame + Fraktion-Disambiguation** muss manuell konsistent gehalten werden + (siehe #55-Refactor und seine Regression in Issue #60-Side-Befund: + ein Refactor-Rest in `embeddings.py:528` wurde durch ein anderes + Modul verbreitet). + +### Folgen für andere ADRs + +- ADR 0001 (LLM-Citation-Binding) ist davon unabhängig — der Postprocess + läuft adapter-agnostisch nach jedem `analyze_antrag`. +- Eine künftige Module-Splitter-Entscheidung (`adapters/` als Folder) + würde den ADR superseden, wenn `parlamente.py` über ~5000 Zeilen wächst. diff --git a/docs/adr/0003-citation-property-tests.md b/docs/adr/0003-citation-property-tests.md new file mode 100644 index 0000000..878bf80 --- /dev/null +++ b/docs/adr/0003-citation-property-tests.md @@ -0,0 +1,106 @@ +# 0003 — Sub-D Property-Verification: Zitate als Substring der zitierten PDF-Seite + +| | | +|---|---| +| **Status** | accepted | +| **Datum** | 2026-04-10 | +| **Refs** | Issues #50, #54, #60; tests/integration/test_citations_substring.py | + +## Kontext + +Der LLM-Output enthält pro Assessment N Zitate, jedes mit `text`, `quelle` +(z.B. "GRÜNE NRW Wahlprogramm 2022, S. 58") und `url`. Wahrscheinlich +korrekt — aber wie verifizieren wir das, ohne jedes einzeln händisch +nachzuschlagen? + +Die naheliegenden Test-Optionen sind alle unbefriedigend: + +- **Mock-LLM-Tests**: prüfen das Schema, sagen aber nichts über die + inhaltliche Korrektheit. +- **Snapshot-Tests** der LLM-Outputs: drift mit jedem Modell-Update. +- **Manuelles Stichprobenchecken**: skaliert nicht über mehrere BLs. + +## Optionen + +### Option A — Schema-only Tests (was wir vorher hatten) + +Pydantic validiert dass jedes Zitat die Felder `text`, `quelle`, `url` hat +und `url` mit `/static/referenzen/` beginnt. Erkennt syntaktische +Korruption, aber keine Halluzinationen. + +### Option B — Property-Test gegen die echten PDFs + +Pro Zitat in der Prod-DB: +1. `quelle` per Token-Coverage-Match auf den `PROGRAMME`-Eintrag mappen. +2. Seitennummer aus `quelle` extrahieren. +3. Per `fitz` die PDF-Seite lesen, Whitespace + Soft-Hyphen normalisieren. +4. `text` muss als Substring (oder 5-Wort-Anker) in der Seite vorkommen. +5. Bug-Klasse 17 (Cross-Bundesland-Zitat): das aufgelöste Programm muss + zum Bundesland des Antrags passen, oder ein Grundsatzprogramm sein. + +**Vorteile:** prüft die einzige Eigenschaft die wirklich zählt — "war +das was zitiert wird auch wirklich da". Findet Halluzinationen direkt. + +**Nachteile:** braucht eine lokale Kopie der `gwoe-antraege.db` und der +Wahlprogramm-PDFs. Test ist Pydantic-Schema-übergreifend (Integration, +nicht Unit). Skipped sauber wenn DB nicht gemounted ist. + +### Option C — Online-Verifikation pro Assessment-Insert + +Im `analyze_antrag`-Flow direkt nach LLM-Call jedes Zitat verifizieren +und bei Failure abbrechen oder retry. + +**Vorteile:** kein "stale data in DB"-Risiko. + +**Nachteile:** fügt Latenz und Komplexität in den Hot-Path. Die +Verifikation ist O(N×M), wo N=Zitate und M=Wahlprogramm-Pages. + +## Entscheidung + +**Option B als pytest-Integration-Test** — `tests/integration/test_citations_substring.py`, +parametrisiert per `_load_recent_assessments(limit_per_bl=5)` × `_flat_zitate()`. + +**Strict substring** als Default-Match (Whitespace + Soft-Hyphen normalisiert, +LLM-Truncation-Marker `...` toleriert), **5-Wort-Anker als Fallback** für +geringfügige Wort-Drift wie "LLM hat mittendrin gekürzt". Min-Length-Guard +von 20 Zeichen verhindert false-positive Matches auf trivialen Snippets. + +Marker `pytestmark = pytest.mark.integration` — der Test läuft nicht in +der Default-Suite, sondern explizit per `pytest -m integration`. Skipped +wenn `webapp/data/gwoe-antraege.db` nicht existiert (Dev-Setup ohne +DB-Kopie). + +Match-Helpers (`_normalize`, `_is_substring`, `_resolve_quelle_to_programm_id`, +`_extract_page_number`) sind eigene Unit-Tests in `TestHelpers` — die Match- +Logik selbst ist nicht-trivial und braucht ihre Eigenkontrolle. + +## Konsequenzen + +### Positiv + +- **Findet Halluzinationen direkt**: Issue #60 wurde durch den ersten + Live-Lauf dieses Tests entdeckt (3 von 36 Citations failed), ohne + dass ein Mensch Wahlprogramm-PDFs aufmachen musste. +- **Re-runnable als Regression-Gate**: nach jedem Deploy einmal `pytest -m + integration` gegen die DB → 0 Failures = OK. +- **Test-Logik = Production-Logik**: ADR 0001 Option B (`reconstruct_zitate`) + nutzt **identische** Match-Heuristiken (`find_chunk_for_text`, + `_normalize_for_match`). Damit kann der Test nichts fangen, was die + Production nicht auch fangen würde, und umgekehrt — kein Test-/Prod-Drift. + +### Negativ + +- **Lokale DB-Kopie nötig**: vor jedem Sub-D-Run muss `data/gwoe-antraege.db` + vom Container gepullt werden. CI-Integration steht aus. +- **Test ist langsam-ish**: ~50 Citations × ein PDF-Open pro Programm ist + bei den ~30 indexierten Programmen ~250ms im Ganzen, nicht trivial aber + nicht prohibitiv. +- **Token-Coverage-Heuristik für Quelle-zu-Programm-Mapping** kann false- + positive bei sehr ähnlichen Programmen werden (z.B. CDU NRW 2022 vs. CDU + Niedersachsen 2022 — würde durch Bundesland-Bonus-Check abgefangen). + +### Folgen für andere ADRs + +- **ADR 0001** ist von ADR 0003 abhängig — wenn dieser Test entfernt würde, + hätte der LLM-Citation-Postprocess keinen Backstop und neue Halluzinations- + Bug-Klassen würden still durchrutschen. diff --git a/docs/adr/0004-deployment-workflow.md b/docs/adr/0004-deployment-workflow.md new file mode 100644 index 0000000..39192e8 --- /dev/null +++ b/docs/adr/0004-deployment-workflow.md @@ -0,0 +1,135 @@ +# 0004 — Docker Compose Deploy mit DB-/Reports-Volume und SN-XML-Sonderpfad + +| | | +|---|---| +| **Status** | accepted | +| **Datum** | 2026-04-10 | +| **Refs** | CLAUDE.md "Deployment", `docker-compose.yml`, Issue #5, project_sn_xml_export | + +## Kontext + +Der GWÖ-Antragsprüfer läuft als Docker-Container `gwoe-antragspruefer` auf +einem VServer hinter Traefik (Let's Encrypt SSL, Domain +`gwoe.toppyr.de`). Code wird via Git aus `repo.toppyr.de` gezogen. + +Drei Subsysteme haben unterschiedliche Lebenszyklen: + +1. **Code** (`app/`) — wird bei jedem Deploy neu kopiert. +2. **SQLite-Daten** (`data/gwoe-antraege.db`, `data/embeddings.db`) — + Source of Truth, MUSS persistent über Deploys hinweg. +3. **PDF-Reports** (`reports/*.pdf`) — sind generierte Artefakte, könnten + theoretisch regeneriert werden, sind aber teuer (LLM-Calls + WeasyPrint), + also auch persistent. + +Issue #5 hatte einen frühen Schmerzpunkt: der Container-Build hat die +DB überschrieben, weil `data/` nicht aus dem Build-Context exkludiert war. + +## Optionen + +### Option A — Alles im Image, manuelle Backups + +Code, DB, Reports zusammen im Image. Backups via `docker cp` vor jedem +Deploy. + +**Nachteile:** Image wird gigantisch, jeder Deploy ist ein Risk-Event, +kein automatischer Persistenz-Mechanismus. + +### Option B — Docker-Volumes für DB und Reports + +`docker-compose.yml` mountet `./data` und `./reports` vom Host als Volumes. +Build kopiert nur `app/` und `requirements.txt`. + +**Vorteile:** Host-Volumes überleben Container-Restart und Image-Rebuild. +Backups via Standard-Linux-Tools auf dem Host. + +**Nachteile:** Build-Context muss `data/`, `reports/`, `.env` exkludieren +(siehe `.dockerignore`), sonst werden sie versehentlich ins Image kopiert +und überschreiben das gemountete Volume bei Container-Start. + +### Option C — Externe DB (Postgres) + +Postgres als separater Container, gwoe-antragspruefer verbindet per asyncpg. + +**Vorteile:** standard, robust, Backups via pg_dump. + +**Nachteile:** Migration aller existierenden Queries, neue Abhängigkeit, +mehr Operational Surface. SQLite reicht für die aktuelle Last (~30 Anträge, +selten parallele Writes). + +## Entscheidung + +**Option B**. Docker-Compose mit Host-Volumes für `data/` und `reports/`. +`.dockerignore` exkludiert beide aus dem Build-Context. + +### Standard-Deploy + +```bash +ssh vserver 'cd /opt/gwoe-antragspruefer && git pull && docker compose up -d --build' +``` + +### Manueller Tar-Upload (falls Git-Workflow blockiert ist) + +```bash +cd webapp +tar czf /tmp/gwoe-webapp.tar.gz \ + --exclude='venv' --exclude='__pycache__' \ + --exclude='data' --exclude='reports' --exclude='.env' . +scp /tmp/gwoe-webapp.tar.gz vserver:/tmp/ +ssh vserver 'cd /opt/gwoe-antragspruefer && tar xzf /tmp/gwoe-webapp.tar.gz && docker compose up -d --build' +``` + +Beachte: `--exclude='data'` ist **zwingend**, sonst überschreibt der Tar +die Live-DB. + +### SN-XML-Sonderpfad + +Sachsen hat keinen scrape-baren Endpoint und liest stattdessen wöchentlich +manuell exportierte XML-Dumps aus EDAS. Workflow: + +1. User exportiert XML aus EDAS (manuell, im Browser). +2. `cp dokumente_export.xml gwoe-antragspruefer:/app/data/sn-edas/` +3. `scp` aus dem Container ins Host-Volume — kein Container-Restart nötig. +4. SNEdasXmlAdapter liest die XML beim nächsten Search-Call. + +Details in `~/.claude/projects//memory/project_sn_xml_export.md`. + +### Container-Zeitzone + +Der Container läuft **UTC**, nicht CEST. DB-Timestamps in `assessments.created_at` +sind UTC (kein TZ-Suffix, aber UTC). Beim Korrelieren mit Commit-Zeiten +(lokal CEST = UTC+2) muss konvertiert werden, sonst fließen falsche +Schlussfolgerungen ein. Detailliert in +`~/.claude/projects//memory/reference_container_utc.md`. + +## Konsequenzen + +### Positiv + +- **DB überlebt jeden Deploy** — verifiziert seit Issue #5 (Fix in + `.dockerignore`-Update). +- **Backups sind trivial**: `tar czf gwoe-data-$(date +%F).tar.gz data/` + auf dem Host. +- **Build ist schnell** (~30s für nicht-cached Layers), weil das Image + nur Code + Dependencies enthält. + +### Negativ + +- **`.dockerignore` ist ein Foot-Gun** — wenn jemand vergisst, `data/` neu + hinzuzufügen nach einem Refactor, kann es passieren dass der Build die + Live-DB überschreibt. Mitigation: ein dedicated Sub-D-style Test, der + nach dem Build prüft, dass die DB die erwarteten Tabellen hat — steht + noch aus. +- **SN-XML braucht manuelle Pflege** wöchentlich. Akzeptiert weil es kein + scrape-baren Endpoint gibt. +- **SQLite skaliert nicht über parallele Writes** — bei mehr als ~5 + gleichzeitigen Analyses würde es Lock-Contention geben. Aktuell läuft + alles seriell durch den Background-Task-Mechanismus, also kein Problem. + Bei Wachstum auf >50 Analyses/Tag wäre ein Postgres-Migration ein + separater ADR. + +### Folgen für andere ADRs + +- ADR 0002 (Adapter-Architektur) ist davon unabhängig — Adapter sind reine + Code-Klassen ohne State. +- Ein zukünftiger Postgres-Migration-ADR würde diesen ADR partial superseden + (DB-Persistenz, nicht Reports). diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..436ae3c --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,36 @@ +# Architecture Decision Records (ADRs) + +ADRs dokumentieren signifikante Architektur-Entscheidungen mit Kontext, Optionen +und Konsequenzen. Format inspiriert von [Michael Nygard](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions). + +## Workflow + +1. Neue Entscheidung steht an → Kopie von `template.md` mit nächster freier + Nummer (`NNNN-kebap-titel.md`). +2. Status `proposed` → diskutiert in Issue/PR → bei Akzeptanz auf `accepted`. +3. **Niemals editieren nach `accepted`.** Wenn eine Entscheidung sich ändert, + neuer ADR mit `Supersedes: NNNN-…` im Header und der alte ADR bekommt + `Superseded by: MMMM-…`. +4. Status `deprecated` für Entscheidungen, die ohne Nachfolger auslaufen. + +## Index + +| ID | Titel | Status | Datum | +|---|---|---|---| +| [0001](0001-llm-citation-binding.md) | LLM-Citations server-seitig binden statt prompt-seitig | accepted | 2026-04-10 | +| [0002](0002-adapter-architecture.md) | Adapter-Pattern mit ParlamentAdapter-Basisklasse + Registry | accepted | 2026-04-10 | +| [0003](0003-citation-property-tests.md) | Sub-D Property-Verification: Zitate als Substring der zitierten PDF-Seite | accepted | 2026-04-10 | +| [0004](0004-deployment-workflow.md) | Docker Compose Deploy mit DB-/Reports-Volume und SN-XML-Sonderpfad | accepted | 2026-04-10 | + +## Wann ADR, wann nicht + +| ADR-würdig | nicht ADR-würdig | +|---|---| +| Wahl zwischen mehreren plausiblen Architekturen mit Trade-offs | Bug-Fix | +| Strukturelle Konsequenzen für mehrere Module | Refactoring innerhalb eines Moduls | +| Reverse-Engineering-Findings die andere Adapter beeinflussen | Stiländerungen, Linting-Konventionen | +| Neue externe Abhängigkeiten oder APIs | Dependency-Bumps ohne API-Änderung | +| Workflow-Konventionen die mehrere Sessions überdauern müssen | Tagesgeschäft, Issue-Tracking | + +Faustregel: Wenn ein neuer Kollege (oder eine neue Session) die Entscheidung +sonst rückgängig machen würde, gehört sie in einen ADR. diff --git a/docs/adr/template.md b/docs/adr/template.md new file mode 100644 index 0000000..eda9009 --- /dev/null +++ b/docs/adr/template.md @@ -0,0 +1,48 @@ +# NNNN — Titel der Entscheidung + +| | | +|---|---| +| **Status** | proposed / accepted / deprecated / superseded | +| **Datum** | YYYY-MM-DD | +| **Supersedes** | (optional) link auf vorherigen ADR | +| **Superseded by** | (optional) link auf neueren ADR | +| **Refs** | Issues, PRs, Commits | + +## Kontext + +Welches Problem hat zu der Entscheidung geführt? Was ist der Stand vorher, +welche Constraints sind im Spiel, welche Stakeholder sind betroffen? + +## Optionen + +Welche Alternativen wurden ernsthaft erwogen? Mindestens 2, gerne mehr. + +### Option A — … + +Beschreibung. Vor- und Nachteile. + +### Option B — … + +Beschreibung. Vor- und Nachteile. + +## Entscheidung + +Welche Option wurde gewählt und warum? Konkret und unmissverständlich, sodass +ein neuer Kollege ohne Diskussion weitermachen kann. + +## Konsequenzen + +Was wird leichter / schwerer durch diese Entscheidung? Welche Folge-Arbeiten +fallen an? Welche Teile des Systems werden berührt? + +### Positiv + +- … + +### Negativ + +- … + +### Folgen für andere ADRs + +- … diff --git a/docs/archive/DOKUMENTATION-2026-03-24.md b/docs/archive/DOKUMENTATION-2026-03-24.md new file mode 100644 index 0000000..c885aef --- /dev/null +++ b/docs/archive/DOKUMENTATION-2026-03-24.md @@ -0,0 +1,381 @@ +# GWÖ-Antragsprüfer — Dokumentation + +*Stand: 24. März 2026, 22:15* + +--- + +## 0. Datenbank + +Alle Bewertungen werden in einer SQLite-Datenbank gespeichert: `gwoe-antraege.db` + +```bash +# DB initialisieren (einmalig) +sqlite3 gwoe-antraege.db < scripts/init-db.sql + +# Übersicht aller Anträge +sqlite3 -header -column gwoe-antraege.db "SELECT * FROM v_antraege_uebersicht;" + +# Statistik nach Fraktion +sqlite3 -header -column gwoe-antraege.db "SELECT * FROM v_statistik_fraktionen;" + +# Themen-Ranking +sqlite3 -header -column gwoe-antraege.db "SELECT * FROM v_themen_ranking;" +``` + +Schema: siehe `scripts/init-db.sql` und `prompt-gwoe-antragspruefer-v4.md` + +--- + +## 1. Batch-Workflow + +### Vollautomatisiert +```bash +# Einzelner Antrag +./scripts/process_single.sh 18-18102 + +# Batch aus Datei +./scripts/gwoe-batch.sh --batch neue-antraege.txt + +# Alle neu rendern +./scripts/gwoe-batch.sh --render-all +``` + +### Manuell (Schritt für Schritt) +```bash +# 1. PDF herunterladen +curl -sL "https://www.landtag.nrw.de/portal/WWW/dokumentenarchiv/Dokument/MMD18-18102.pdf" \ + -o antraege/18-18102.pdf + +# 2. Text extrahieren +pdftotext -layout antraege/18-18102.pdf antraege/18-18102.txt + +# 3. LLM analysieren (manuell oder via API) +# → JSON in assessments.json einfügen + +# 4. data.ts aktualisieren +cat assessments.json > quarto-report/src/data.ts # vereinfacht + +# 5. Quarto rendern +cd quarto-report && npm run generate +cd reports && quarto render gwoe-18-18102.qmd --to pdf + +# 6. Mit Original mergen +pdftk quarto-report/reports/gwoe-18-18102.pdf antraege/18-18102.pdf \ + cat output output/gwoe-bericht-18-18102.pdf +``` + +### API-Aufruf (Claude) +```bash +curl -sS https://api.anthropic.com/v1/messages \ + -H "Content-Type: application/json" \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -d '{ + "model": "claude-sonnet-4-20250514", + "max_tokens": 4096, + "system": "", + "messages": [{"role": "user", "content": ""}] + }' +``` + +--- + +## 1. Überblick + +Der GWÖ-Antragsprüfer bewertet parlamentarische Anträge (aktuell: NRW Landtag) systematisch nach drei Dimensionen: + +1. **GWÖ-Treue (0-10):** Übereinstimmung mit der Gemeinwohl-Ökonomie Matrix 2.0 +2. **Wahlprogrammtreue (0-10):** Konsistenz mit dem NRW-Wahlprogramm 2022 der einreichenden Fraktion(en) und der Fraktionen in Regierungsverantwortung +3. **Parteiprogrammtreue (0-10):** Konsistenz mit dem Grundsatzprogramm der einreichenden Fraktion(en) und der Fraktionen in Regierungsverantwortung + +Das Tool generiert professionelle PDF-Berichte mit dem offiziellen ECOnGOOD-Branding. + +--- + +## 2. Gewählte Matrix: V2.0 für Gemeinden + +### Warum nicht Matrix 5.1? + +Die **GWÖ-Matrix 5.1** ist für Unternehmen konzipiert mit Berührungsgruppen wie "Kund:innen", "Eigentümer:innen", "Mitarbeitende". Diese passen nicht zu parlamentarischen Anträgen. + +### Warum nicht Matrix 2.1.A? + +Die Matrix 2.1.A ist für die "öffentliche Hand" allgemein. Die **Matrix 2.0 für Gemeinden** bietet einen etwas weiteren Blick und ist kompakter formuliert — ideal für parlamentarische Analyse. + +### Die Matrix 2.0 für Gemeinden + +| Aspekt | Beschreibung | +|--------|--------------| +| **Struktur** | 5×5 (25 Felder) | +| **Fokus** | Kommunale Gebietskörperschaften | +| **Zielgruppe** | Kommunalpolitik, Landesanträge | +| **Staatsprinzipien** | ja (verfassungsrechtlich verankert) | + +### Die 5×5-Struktur + +**Spalten (Werte des Gemeinwohls + Staatsprinzipien):** + +| Nr | Wert | Staatsprinzip | +|----|------|---------------| +| 1 | Menschenwürde | Rechtsstaatsprinzip | +| 2 | Solidarität | Gemeinnutz | +| 3 | Ökologische Nachhaltigkeit | Umwelt-Verantwortung | +| 4 | Soziale Gerechtigkeit | Sozialstaatsprinzip | +| 5 | Transparenz & Mitbestimmung | Demokratie | + +**Zeilen (Berührungsgruppen):** + +| Code | Gruppe | Beschreibung | +|------|--------|--------------| +| A | Ausgelagerte Betriebe, Lieferant:innen, Dienstleister:innen | Externe Beschaffung, Lieferketten | +| B | Finanzpartner:innen, Geldgeber:innen, Steuerzahler:innen | Haushalt, Finanzpolitik | +| C | Politische Führung, Verwaltung, Ehrenamtliche | Mandatsträger:innen, Mitarbeitende | +| D | Bürger:innen und Wirtschaft | Interne Wirkung, Daseinsvorsorge | +| E | Staat, Gesellschaft und Natur | Überregionale/langfristige Wirkung | + +### Relevanz für Landesanträge + +Die meisten Parlamentsanträge betreffen: +- **D-Zeile:** Wirkung auf Bürger:innen und Wirtschaft im Land +- **E-Zeile:** Überregionale oder langfristige Auswirkungen + +**Prinzip:** D (intern) hat Vorrang vor E (extern). + +--- + +## 3. Programmtreue-Bewertung + +### Zwei Dimensionen + +Anträge werden sowohl gegen das **Wahlprogramm** als auch gegen das **Grundsatzprogramm** der Fraktionen geprüft: + +| Partei | Wahlprogramm | Grundsatzprogramm | +|--------|--------------|-------------------| +| CDU | NRW 2022 | "In Freiheit leben" (2024) | +| SPD | NRW 2022 | Hamburger Programm (2007) | +| GRÜNE | NRW 2022 | "...zu achten und zu schützen..." (2020) | +| FDP | NRW 2022 | "Verantwortung für die Freiheit" (2012) | +| AfD | NRW 2022 | "Programm für Deutschland" (2016) | + +### Bewertungsskala + +| Score | Bedeutung | +|-------|-----------| +| 9-10 | Vollständige Übereinstimmung | +| 7-8 | Hohe Übereinstimmung | +| 5-6 | Partielle Übereinstimmung | +| 3-4 | Geringe Übereinstimmung | +| 1-2 | Widerspricht Teilaspekten | +| 0 | Vollständiger Widerspruch | + +--- + +## 4. Bewertungsskala GWÖ + +| Punkte | Stufe | Beschreibung | +|--------|-------|--------------| +| 7-10 | **Vorbildlich** | Innovative Maßnahmen, weitreichende Verbesserungen | +| 4-6 | **Erfahren** | Erkennbare Verbesserungen, gute Ergebnisse | +| 2-3 | **Fortgeschritten** | Erste Maßnahmen, erste Erfolge | +| 1 | **Erste Schritte** | Erstes Engagement | +| 0 | **Basislinie** | Nur gesetzliche Anforderungen | +| negativ | **Widerspruch** | Aktiver Widerspruch zu GWÖ-Werten | + +### Matrix-Feldwertung + +Für jedes berührte Feld im Antrag: +- `++` (+2/+3): Stark fördernd +- `+` (+1): Fördernd +- `○` (0): Neutral +- `−` (-1): Widersprechend +- `−−` (-2/-3): Stark widersprechend + +--- + +## 5. ECOnGOOD Corporate Design + +### Branding (aus CD Manual 2024) + +Das Layout folgt strikt dem offiziellen ECOnGOOD Corporate Design Manual (Juni 2024). + +**Farbpalette (Primärfarben aus Logo):** + +| Farbe | Hex | CMYK | Pantone | Verwendung | +|-------|-----|------|---------|------------| +| **Dunkelgrau** | `#5a5a5a` | 0-0-0-80 | 425 U | Fließtext, H1-Überschriften | +| **Grün** | `#889e33` | 40-10-90-20 | 583 U | Positive Werte, H3, Akzente | +| **Blau** | `#009da5` | 100-10-40-00 | 320 U | Hauptfarbe, H2, Links, Linien | +| **Hellgrau** | `#bfbfbf` | 0-0-0-30 | 421 U | Hintergründe, Fußzeilen | + +**Typografie:** +- **Primär:** Avenir (light, roman, black) +- **Fallback:** Arial +- **Web:** Avenir +- **Überschriften:** + - H1: Avenir black, 22pt, Dunkelgrau + - H2: Avenir black, 16pt, Blau oder Grün + - H3: Avenir black, 12pt, Blau oder Grün +- **Fließtext:** Avenir roman, 10pt, Dunkelgrau +- **Hervorhebungen:** Avenir black + +**Logo:** ECOnGOOD-Logo (`econgood-logo.png`) — zwei stilisierte Pusteblumen (Blau + Grün) + +**Boxen (CD-konform):** +- Flächig farbig (Grün oder Blau) mit weißem Text +- Oder mit farbiger Outline und farbigem Text +- Keine abgerundeten Ecken (arc=0pt) + +**Tabellen:** +- Titelleiste in Markenfarbe (Blau) +- Weißer Text in Titelleiste, fett, Großbuchstaben +- Innere horizontale Linien max. 1pt +- 3mm Abstand zwischen Text und Linie + +**Layout:** +- Offenes, leichtes Design +- Reinweißer Hintergrund +- Kräftige Farben (aktiv) +- Genug Weißraum zwischen Elementen + +### Empfehlungs-Symbole + +| Empfehlung | Symbol | Farbe | Hex | +|------------|--------|-------|-----| +| Uneingeschränkt unterstützen | `[++]` | Grün | `#889e33` | +| Unterstützen mit Änderungen | `[+]` | Blau | `#009da5` | +| Überarbeiten | `[!]` | Orange | `#F7941D` | +| Ablehnen | `[X]` | Rot | `#d00000` | + +--- + +## 6. Technische Umsetzung + +### Stack + +- **TypeScript** — Datentypen, Generator +- **Quarto** — Markdown → PDF (via LuaLaTeX) +- **TikZ** — Grafische Elemente (Score-Balken) +- **pdftk** — PDF-Merge (Bewertung + Original-Antrag) + +### Projektstruktur + +``` +quarto-report/ +├── src/ +│ ├── types.ts # Datentypen, Matrix-Labels +│ ├── data.ts # Testbewertungen +│ └── generate-qmd.ts # Quarto-Generator +├── reports/ # Generierte .qmd + .pdf +├── econgood-logo.png # Logo (aus CD Manual) +└── package.json +``` + +### Workflow + +1. LLM analysiert Antrag → JSON-Ausgabe +2. JSON → TypeScript Assessment-Objekt +3. Generator erzeugt `.qmd` (Quarto Markdown) +4. Quarto rendert → PDF +5. pdftk merged Bewertung + Original-Antrag + +--- + +## 7. Kontext-Dateien + +Im Ordner `kontext/`: + +| Datei | Beschreibung | +|-------|--------------| +| `gwoe-matrix-2.0.md` | Matrix 2.0 für Gemeinden — vollständige Dokumentation | +| `gwoe-matrix-2.0-gemeinden.pdf` | Offizielles Matrix-PDF | +| `gwoe-arbeitsbuch-gemeinden-2.0.pdf` | Arbeitsbuch zur Matrix | +| `parteiprogramme.md` | Kurzreferenz Wahlprogramme + Grundsatzprogramme | +| `cdu-grundsatzprogramm-2024.pdf` | CDU "In Freiheit leben" | +| `spd-hamburger-programm-2007.pdf` | SPD Hamburger Programm | +| `gruene-grundsatzprogramm-2020.pdf` | Grüne Grundsatzprogramm | +| `fdp-grundsatzprogramm-2012.pdf` | FDP "Verantwortung für die Freiheit" | +| `afd-grundsatzprogramm-2016.pdf` | AfD "Programm für Deutschland" | +| `wahlprogramme-nrw-2022.md` | NRW Wahlprogramme 2022 | + +--- + +## 8. Getestete Anträge + +| Drucksache | Titel | GWÖ | Empfehlung | +|------------|-------|-----|------------| +| 18/18081 | Schlechte-Kita-Gesetz stoppen (SPD) | 8/10 | Uneingeschränkt unterstützen | +| 18/18088 | Iran-Solidarität (alle) | 9/10 | Uneingeschränkt unterstützen | +| 18/18094 | Versorgungssicherheit/Fracking (AfD) | 1/10 | Ablehnen | +| 18/18099 | Kultur-Schecks (CDU/Grüne) | 6/10 | Unterstützen mit Änderungen | +| 18/18104 | KI-Korrektur (FDP) | 6/10 | Unterstützen mit Änderungen | + +--- + +## 9. Gendering + +**Standard:** `:` (Doppelpunkt) statt `*` (Sternchen) + +Beispiele: +- Bürger:innen +- Lieferant:innen +- Mitarbeiter:innen + +--- + +## 10. Quellen + +- **GWÖ-Matrix 2.0:** https://germany.econgood.org/wp-content/uploads/sites/8/2024/04/Matrix-Gemeinwohl-Bilanzierung-Gemeinden-V2.0.pdf +- **Arbeitsbuch 2.0:** https://austria.econgood.org/wp-content/uploads/sites/7/2022/01/Arbeitsbuch-Gemeinden_2.pdf +- **ECOnGOOD CD Manual:** Corporate Design/2024_ECOnGOOD_Manual_DE_Juni_02.pdf +- **GWÖ Deutschland:** https://germany.econgood.org +- **Kontakt:** Gemeinde@ecogood.org + +--- + +## 11. Changelog + +| Datum | Version | Änderung | +|-------|---------|----------| +| 24.03.2026 | v4 | Matrix 2.0 statt 2.1.A, Parteiprogrammtreue ergänzt, `:` beim Gendern, ECOnGOOD-Logo | +| 23.03.2026 | v3 | Umstellung auf Matrix 2.1.A (5×5), GWÖ-Branding | +| 23.03.2026 | v2 | Vollständiger Kontext (Matrix + Wahlprogramme) | +| 23.03.2026 | v1 | Initiale Version mit Matrix 5.1 | + +--- + +*Projektordner:* `~/Nextcloud/dotty/projekte/2026-03-23 GWÖ-Antragsprüfer _WIP_/` +*Telegram:* https://t.me/c/3823618505/4247 + +--- + +## Session 28.03.2026 (Nachmittag/Abend) + +### Security +- CSP, X-Frame-Options, X-XSS-Protection etc. als Middleware +- /docs deaktiviert + +### v5-Prompt mit Verbesserungsvorschlägen +Neues Format: +```json +{ + "original": "Zitat aus Antrag", + "vorschlag": "Verbesserter Text mit **Markierungen**", + "begruendung": "GWÖ-Begründung" +} +``` + +### UI-Erweiterungen +- Partei-Filter (Dropdown) +- Tag-Wolke mit Multi-Select +- Partei-Durchschnitte in Stats-Bar +- Kombinierte Filter (Score + Partei) + +### Infrastruktur-Fixes +- DB-Persistenz: Dockerfile kopiert data/ nicht mehr +- JSON-Import deaktiviert +- Retry-Logik für LLM (3 Versuche) + +### Repository +- https://repo.toppyr.de/tobias/gwoe-antragspruefer +- MIT License +- Vollständige Dokumentation diff --git a/docs/archive/README-2026-03-28.md b/docs/archive/README-2026-03-28.md new file mode 100644 index 0000000..9b538e1 --- /dev/null +++ b/docs/archive/README-2026-03-28.md @@ -0,0 +1,293 @@ +# GWÖ-Antragsprüfer — Projektdokumentation + +**Stand:** 28.03.2026, 23:58 Uhr +**Telegram-Topic:** [🌱 GWÖ-Antragsprüfer](https://t.me/c/3823618505/4247) (thread_id 4247) + +## 🎯 Projektziel + +Automatische Bewertung von Parlamentsanträgen nach der **Gemeinwohl-Ökonomie (GWÖ) Matrix 2.0 für Gemeinden**. Das Tool analysiert Anträge aus Landesparlamenten und bewertet sie nach GWÖ-Kriterien, vergleicht mit Wahl- und Parteiprogrammen und schlägt konkrete Textverbesserungen vor. + +--- + +## 🌐 Live-System + +| Was | URL | +|-----|-----| +| **Webapp** | https://gwoe.toppyr.de | +| **Git-Repository** | https://repo.toppyr.de/tobias/gwoe-antragspruefer | +| **Server** | VServer 152.53.119.77 | +| **Container** | `/opt/gwoe-antragspruefer/` | + +--- + +## 🏗️ Architektur + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Frontend │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ Durchsuchen │ │ 🏷️ Tags │ │ Prüfen │ │ +│ │ (Liste) │ │ (Wolke) │ │ (Upload/Analyse) │ │ +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ FastAPI Backend │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │ +│ │ /api/ │ │ /api/ │ │ /api/ │ │ /api/ │ │ +│ │assessments│ │search- │ │analyze- │ │assessment/ │ │ +│ │ │ │landtag │ │drucksache│ │pdf │ │ +│ └──────────┘ └──────────┘ └──────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ +│ SQLite │ │ OPAL API │ │ DashScope (Qwen) │ +│ gwoe-antraege│ │ (Landtag │ │ qwen-plus-latest │ +│ .db │ │ NRW) │ │ (LLM Analyse) │ +└─────────────┘ └─────────────┘ └─────────────────────────┘ +``` + +--- + +## 📁 Projektstruktur + +``` +webapp/ +├── app/ +│ ├── main.py # FastAPI-Endpoints + Security Middleware +│ ├── analyzer.py # LLM-Analyse mit v5-Prompt + Retry-Logik +│ ├── database.py # SQLite (aiosqlite) Persistenz +│ ├── models.py # Pydantic Assessment-Model +│ ├── parlamente.py # OPAL-Adapter für NRW Landtag +│ ├── report.py # PDF-Generierung (WeasyPrint) +│ ├── config.py # Settings + Environment +│ ├── embeddings.py # Wahlprogramm-Embeddings (optional) +│ ├── kontext/ +│ │ ├── gwoe-matrix-2.0.md # GWÖ-Matrix Referenz +│ │ ├── wahlprogramme-nrw-2022.md # Zusammenfassungen +│ │ ├── parteiprogramme.md # Grundsatzprogramme +│ │ └── *-nrw-2022-paged.txt # Volltext (paginiert) +│ ├── templates/ +│ │ ├── index.html # Haupt-UI (Jinja2) +│ │ └── quellen.html # Quellenübersicht +│ └── static/ +│ └── referenzen/ # Original-PDFs der Programme +├── data/ # SQLite-DBs (Docker Volume) +│ └── gwoe-antraege.db # Haupt-Datenbank +├── reports/ # Generierte PDFs (Docker Volume) +├── docker-compose.yml # Traefik + Let's Encrypt +├── Dockerfile # Python 3.12 + WeasyPrint +├── requirements.txt +├── .env.example +├── .gitignore +├── .dockerignore +├── LICENSE (MIT) +└── README.md +``` + +--- + +## ✨ Features (Stand 28.03.2026) + +### Analyse +- [x] GWÖ-Score (0-10) mit Matrix-Zuordnung +- [x] Bewertungssymbole (++/+/○/−/−−) pro Matrix-Feld +- [x] Wahlprogrammtreue (0-10) pro Fraktion +- [x] Parteiprogrammtreue (0-10) pro Fraktion +- [x] **Verbesserungsvorschläge im Redline-Format** + - Original-Zitat aus dem Antrag + - Konkreter Verbesserungsvorschlag + - GWÖ-Begründung +- [x] Themen-Tags für Kategorisierung +- [x] Retry-Logik (3 Versuche) bei JSON-Parse-Fehlern + +### UI +- [x] Score-Filter (Alle / 8-10 / 5-7 / 0-4) +- [x] **Partei-Filter** (Dropdown) +- [x] **Durchschnittswerte pro Partei** (kompakte Stats-Bar) +- [x] **Tag-Wolke** mit Multi-Select (Schnittmenge) +- [x] Landtag-Suche via OPAL-API +- [x] "Jetzt prüfen"-Button für neue Anträge +- [x] Detail-Ansicht mit allen Bewertungen +- [x] PDF-Download pro Antrag + +### Security +- [x] Content-Security-Policy +- [x] X-Frame-Options: DENY +- [x] X-Content-Type-Options: nosniff +- [x] X-XSS-Protection +- [x] Referrer-Policy +- [x] Permissions-Policy +- [x] /docs, /redoc, /openapi.json deaktiviert + +### Infrastruktur +- [x] Docker Compose mit Traefik Reverse Proxy +- [x] Let's Encrypt SSL +- [x] **Persistente SQLite-DB** (Volume, überlebt Container-Neustarts) +- [x] Git-Repository auf repo.toppyr.de + +--- + +## 🔧 Konfiguration + +### Environment-Variablen (.env) + +```bash +DASHSCOPE_API_KEY=sk-... # Alibaba DashScope API +KEYCLOAK_URL=https://sso.toppyr.de # Optional: SSO +KEYCLOAK_REALM=collaboration +KEYCLOAK_CLIENT_ID=gwoe-antragspruefer +``` + +### LLM-Modelle + +| Modell | Verwendung | Kosten | +|--------|------------|--------| +| `qwen-plus-latest` | Standard (Free Tier) | Kostenlos | +| `qwen-plus` | Fallback (Paid) | ~$0.80/MTok | +| `qwen-max` | Premium | ~$2.40/MTok | + +--- + +## 📊 API-Endpoints + +| Methode | Pfad | Beschreibung | +|---------|------|--------------| +| GET | `/` | Web-UI | +| GET | `/health` | Health Check | +| GET | `/quellen` | Quellenübersicht | +| GET | `/api/assessments` | Alle Bewertungen (JSON) | +| GET | `/api/assessment?drucksache=18/12345` | Einzelne Bewertung | +| GET | `/api/search?q=Klima` | Interne DB-Suche | +| GET | `/api/search-landtag?q=Klima` | Landtag OPAL-Suche | +| POST | `/api/analyze-drucksache` | Neue Analyse starten | +| GET | `/api/assessment/pdf?drucksache=18/12345` | PDF-Download | +| GET | `/api/bundeslaender` | Verfügbare Bundesländer | +| GET | `/status/{job_id}` | Job-Status abfragen | + +--- + +## 🧠 Prompt v5 — Kern-Features + +Der Analyse-Prompt fordert: + +1. **GWÖ-Treue (0-10)** mit Matrix-Zuordnung: + - Matrix-Feld (z.B. "D1 Menschenwürde") + - Symbol (++/+/○/−/−−) + - Kurzbegründung + +2. **Programmtreue** für Antragsteller UND Regierungsfraktionen: + - Wahlprogramm-Score (0-10) + - Parteiprogramm-Score (0-10) + - Begründungen + +3. **Verbesserungsvorschläge** (max. 3): + ```json + { + "original": "Zitat aus dem Antrag", + "vorschlag": "Konkret **verbesserter** Text", + "begruendung": "Stärkt GWÖ-Wert X durch Y" + } + ``` + +4. **Themen-Tags** für Kategorisierung + +--- + +## 🚀 Deployment + +### Erstinstallation + +```bash +ssh vserver +cd /opt +git clone https://repo.toppyr.de/tobias/gwoe-antragspruefer.git +cd gwoe-antragspruefer + +# .env erstellen +cp .env.example .env +nano .env # DASHSCOPE_API_KEY eintragen + +# Starten +docker compose up -d +``` + +### Update + +```bash +# Lokal +cd ~/Nextcloud/dotty/projekte/2026-03-23\ GWÖ-Antragsprüfer\ _WIP_/webapp +git add . && git commit -m "Update" && git push + +# Auf Server +ssh vserver 'cd /opt/gwoe-antragspruefer && git pull && docker compose up -d --build' +``` + +### Manuelles Deploy (ohne Git auf Server) + +```bash +cd ~/Nextcloud/dotty/projekte/2026-03-23\ GWÖ-Antragsprüfer\ _WIP_/webapp +tar czf /tmp/gwoe-webapp.tar.gz --exclude='venv' --exclude='__pycache__' --exclude='data' --exclude='reports' --exclude='.env' . +scp /tmp/gwoe-webapp.tar.gz vserver:/tmp/ +ssh vserver 'cd /opt/gwoe-antragspruefer && tar xzf /tmp/gwoe-webapp.tar.gz && docker compose up -d --build' +``` + +--- + +## 📈 Statistiken (28.03.2026) + +| Metrik | Wert | +|--------|------| +| Analysierte Anträge | 20 | +| Ø GWÖ-Score gesamt | 4.6 | +| Ø SPD | 7.7 | +| Ø GRÜNE | 6.0 | +| Ø CDU | 6.0 | +| Ø FDP | 4.8 | +| Ø AfD | 1.5 | + +--- + +## 🐛 Bekannte Issues / TODOs + +### Offen +- [ ] Keycloak SSO-Integration aktivieren +- [ ] Weitere Bundesländer (BY, BW) anbinden +- [ ] Batch-Analyse für viele Anträge +- [ ] Wahlprogramm-Zitate mit Seitenzahlen +- [ ] Export als CSV/Excel + +### Gelöst (28.03.2026) +- [x] JSON-Parse-Fehler bei LLM-Output → Retry-Logik +- [x] DB nicht persistent → Dockerfile gefixt, data/ als Volume +- [x] Alte Assessments überschreiben neue → JSON-Import deaktiviert +- [x] Partei-Filter zeigt "Suchfehler" → JS-Bug gefixt + +--- + +## 📝 Changelog + +### v1.0.0 (28.03.2026) +- Initial Release +- GWÖ-Matrix 2.0 Analyse für NRW +- Verbesserungsvorschläge im Redline-Format +- Tag-Wolke mit Multi-Select +- Partei-Filter + Durchschnittswerte +- Security Headers +- Docker Deployment + +--- + +## 📚 Quellen + +- [GWÖ-Matrix 2.0 für Gemeinden (Arbeitsbuch)](https://econgood.org) +- [ECOnGOOD Corporate Design Manual 2024](https://econgood.org) +- [NRW OPAL Parlamentsdokumentation](https://www.landtag.nrw.de) +- Wahlprogramme NRW 2022 (CDU, GRÜNE, SPD, FDP, AfD) +- Grundsatzprogramme der Parteien + +--- + +**Entwickelt von Tobias Rödel mit Unterstützung von Dotty** 👻 diff --git a/docs/archive/README.md b/docs/archive/README.md new file mode 100644 index 0000000..a696074 --- /dev/null +++ b/docs/archive/README.md @@ -0,0 +1,26 @@ +# Archiv + +Hier liegen historische Doku-Snapshots, die **nicht mehr autoritativ** sind. +Sie beschreiben jeweils einen Zustand zu einem bestimmten Zeitpunkt; vieles +davon ist heute überholt. + +## Warum nicht löschen? + +- Sie sind als historische Quelle nützlich, wenn man verstehen will, wie + das Projekt zu einem früheren Zeitpunkt strukturiert war. +- Manche Findings sind in die ADRs (`../adr/`) übernommen, andere bewusst + nicht — weil sie tagesaktueller Stand waren und nie ADR-würdig. + +## Inhalt + +| Datei | Stand | Beschreibung | +|---|---|---| +| `DOKUMENTATION-2026-03-24.md` | 2026-03-24 | Frühe Skript-basierte Architektur, vor dem Webapp-Migrate. Beschreibt `scripts/process_single.sh`-Workflow, der heute durch FastAPI-Backend ersetzt ist. | +| `STATUS-2026-03-28.md` | 2026-03-28 | Tagesstand-Snapshot. Beschreibt v5-Prompt, Persistenz-Volume, Security-Headers — alle drei sind heute Standard. | +| `README-2026-03-28.md` | 2026-03-28 | Erste Repo-README. Listet nur den NRW-Adapter (vor #2/#3/#4 und 13 weiteren BLs). Architektur-Diagramm ist veraltet. | + +## Wenn etwas davon noch wahr ist… + +…gehört es in einen ADR (`../adr/`) oder die generierte Reference (geplant +für #62 Phase 2). Bitte **nicht** in einen neuen Status-Snapshot ziehen. +Status-Snapshots sind die Drift-Falle, die wir hier vermeiden wollen. diff --git a/docs/archive/STATUS-2026-03-28.md b/docs/archive/STATUS-2026-03-28.md new file mode 100644 index 0000000..15fb7e4 --- /dev/null +++ b/docs/archive/STATUS-2026-03-28.md @@ -0,0 +1,182 @@ +# GWÖ-Antragsprüfer — Status 28.03.2026 (Final) + +## ✅ Heute erledigt (28.03.2026) + +### Vormittag +- [x] "Jetzt prüfen" aus Landtag-Suche implementiert +- [x] Zitat-System mit Wahlprogramm-Seitenreferenzen +- [x] 5 Original-Wahlprogramme integriert (CDU, SPD, Grüne, FDP, AfD) + +### Nachmittag/Abend +- [x] **Security Headers** eingebaut (CSP, X-Frame-Options, etc.) +- [x] /docs, /redoc, /openapi.json deaktiviert +- [x] **v5-Prompt** mit Verbesserungsvorschlägen im Redline-Format +- [x] **Retry-Logik** (3 Versuche) bei JSON-Parse-Fehlern +- [x] **Partei-Filter** (Dropdown) +- [x] **Tag-Wolke** mit Multi-Select (Schnittmenge-Filter) +- [x] **Partei-Durchschnittswerte** in kompakter Stats-Bar +- [x] **Persistente DB** — Dockerfile gefixt, data/ als Volume +- [x] JSON-Import deaktiviert (DB ist Source of Truth) +- [x] 20 Test-Anträge neu analysiert mit v5-Prompt +- [x] **Git-Repository** auf repo.toppyr.de gepusht +- [x] **Dokumentation** komplett + +--- + +## 🌐 Live-System + +| Was | URL | +|-----|-----| +| **Webapp** | https://gwoe.toppyr.de | +| **Repository** | https://repo.toppyr.de/tobias/gwoe-antragspruefer | +| **Server** | VServer 152.53.119.77 (`/opt/gwoe-antragspruefer/`) | + +--- + +## 📊 Aktueller Stand + +| Metrik | Wert | +|--------|------| +| Analysierte Anträge | 20 | +| Alle mit Verbesserungsvorschlägen | ✅ | +| Ø GWÖ-Score gesamt | 4.6 | +| Ø SPD | 7.7 | +| Ø CDU/GRÜNE | 6.0 | +| Ø FDP | 4.8 | +| Ø AfD | 1.5 | + +--- + +## 🔧 Technische Änderungen heute + +### Security (main.py) +```python +# Middleware hinzugefügt: +- X-Content-Type-Options: nosniff +- X-Frame-Options: DENY +- X-XSS-Protection: 1; mode=block +- Content-Security-Policy +- Referrer-Policy: strict-origin-when-cross-origin +- Permissions-Policy: geolocation=(), microphone=(), camera=() + +# FastAPI Docs deaktiviert: +docs_url=None, redoc_url=None, openapi_url=None +``` + +### Retry-Logik (analyzer.py) +```python +# 3 Versuche bei JSON-Parse-Fehlern +# Temperatur steigt pro Versuch (0.3 → 0.4 → 0.5) +max_retries = 3 +for attempt in range(max_retries): + # ... LLM Call ... + try: + data = json.loads(content) + return Assessment.model_validate(data) + except json.JSONDecodeError: + continue # Retry +``` + +### Persistente DB (Dockerfile) +```dockerfile +# VORHER (kaputt): +COPY data/ ./data/ +COPY reports/ ./reports/ + +# NACHHER (korrekt): +# data/ und reports/ werden als Volumes gemountet +RUN mkdir -p /app/data /app/reports +``` + +### Deploy-Workflow +```bash +# .tarignore und --exclude verhindern Überschreiben der Server-DB +tar czf ... --exclude='data' --exclude='reports' ... +``` + +--- + +## 📁 Repository-Struktur + +``` +gwoe-antragspruefer/ +├── app/ +│ ├── main.py # FastAPI + Security +│ ├── analyzer.py # LLM + Retry +│ ├── database.py # SQLite +│ ├── models.py # Pydantic +│ ├── parlamente.py # OPAL-Adapter +│ ├── report.py # PDF +│ ├── kontext/ # GWÖ-Matrix, Programme +│ ├── templates/ # Jinja2 UI +│ └── static/referenzen/ # Original-PDFs +├── docker-compose.yml +├── Dockerfile +├── requirements.txt +├── .env.example +├── .gitignore +├── LICENSE (MIT) +└── README.md +``` + +--- + +## 📋 Offene TODOs + +### Prio 1 +- [ ] Keycloak SSO aktivieren +- [ ] Batch-Analyse für viele Anträge optimieren + +### Prio 2 +- [ ] Weitere Bundesländer (BY, BW) +- [ ] CSV/Excel-Export +- [ ] Zitat-Highlighting in PDFs + +### Nice to have +- [ ] Historische Trend-Analyse +- [ ] Newsletter-Integration +- [ ] API-Rate-Limiting für öffentliche Nutzung + +--- + +## 🚀 Deployment-Befehle + +### Standard-Update +```bash +# Lokal committen +cd ~/Nextcloud/dotty/projekte/2026-03-23\ GWÖ-Antragsprüfer\ _WIP_/webapp +git add . && git commit -m "..." && git push + +# Server aktualisieren +ssh vserver 'cd /opt/gwoe-antragspruefer && git pull && docker compose up -d --build' +``` + +### Manuell (ohne Server-Git) +```bash +cd ~/Nextcloud/dotty/projekte/2026-03-23\ GWÖ-Antragsprüfer\ _WIP_/webapp +tar czf /tmp/gwoe-webapp.tar.gz --exclude='venv' --exclude='__pycache__' --exclude='data' --exclude='reports' --exclude='.env' . +scp /tmp/gwoe-webapp.tar.gz vserver:/tmp/ +ssh vserver 'cd /opt/gwoe-antragspruefer && tar xzf /tmp/gwoe-webapp.tar.gz && docker compose up -d --build' +``` + +### Logs prüfen +```bash +ssh vserver 'docker logs gwoe-antragspruefer --tail 50' +``` + +### DB-Status +```bash +ssh vserver 'docker exec gwoe-antragspruefer python -c " +import sqlite3 +conn = sqlite3.connect(\"/app/data/gwoe-antraege.db\") +cur = conn.cursor() +cur.execute(\"SELECT COUNT(*) FROM assessments\") +print(f\"Assessments: {cur.fetchone()[0]}\") +"' +``` + +--- + +**Projekt-Status: FUNKTIONSFÄHIG** ✅ + +*Dokumentiert von Dotty, 28.03.2026, 23:58 Uhr*