gwoe-antragspruefer/site/adr/0003-citation-property-tests/index.html

1036 lines
28 KiB
HTML
Raw Permalink 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.

<!doctype html>
<html lang="de" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="canonical" href="https://docs.gwoe.toppyr.de/adr/0003-citation-property-tests/">
<link rel="prev" href="../0002-adapter-architecture/">
<link rel="next" href="../0004-deployment-workflow/">
<link rel="icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.6">
<title>0003 Citation-Property-Tests - GWÖ-Antragsprüfer Docs</title>
<link rel="stylesheet" href="../../assets/stylesheets/main.484c7ddc.min.css">
<link rel="stylesheet" href="../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="teal" data-md-color-accent="light-green">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#0003-sub-d-property-verification-zitate-als-substring-der-zitierten-pdf-seite" class="md-skip">
Zum Inhalt
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Kopfzeile">
<a href="../.." title="GWÖ-Antragsprüfer Docs" class="md-header__button md-logo" aria-label="GWÖ-Antragsprüfer Docs" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
GWÖ-Antragsprüfer Docs
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
0003 Citation-Property-Tests
</span>
</div>
</div>
</div>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Suche" placeholder="Suche" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Suche">
<button type="reset" class="md-search__icon md-icon" title="Zurücksetzen" aria-label="Zurücksetzen" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Suche wird initialisiert
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://repo.toppyr.de/tobias/gwoe-antragspruefer" title="Zum Repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
tobias/gwoe-antragspruefer
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../.." title="GWÖ-Antragsprüfer Docs" class="md-nav__button md-logo" aria-label="GWÖ-Antragsprüfer Docs" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
GWÖ-Antragsprüfer Docs
</label>
<div class="md-nav__source">
<a href="https://repo.toppyr.de/tobias/gwoe-antragspruefer" title="Zum Repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
tobias/gwoe-antragspruefer
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." class="md-nav__link">
<span class="md-ellipsis">
Start
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" checked>
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="">
<span class="md-ellipsis">
Architecture Decision Records
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
Architecture Decision Records
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../" class="md-nav__link">
<span class="md-ellipsis">
Übersicht
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../0001-llm-citation-binding/" class="md-nav__link">
<span class="md-ellipsis">
0001 LLM-Citation-Binding
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../0002-adapter-architecture/" class="md-nav__link">
<span class="md-ellipsis">
0002 Adapter-Architektur
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
0003 Citation-Property-Tests
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
0003 Citation-Property-Tests
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Inhaltsverzeichnis">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Inhaltsverzeichnis
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#kontext" class="md-nav__link">
<span class="md-ellipsis">
Kontext
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#optionen" class="md-nav__link">
<span class="md-ellipsis">
Optionen
</span>
</a>
<nav class="md-nav" aria-label="Optionen">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#option-a-schema-only-tests-was-wir-vorher-hatten" class="md-nav__link">
<span class="md-ellipsis">
Option A — Schema-only Tests (was wir vorher hatten)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#option-b-property-test-gegen-die-echten-pdfs" class="md-nav__link">
<span class="md-ellipsis">
Option B — Property-Test gegen die echten PDFs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#option-c-online-verifikation-pro-assessment-insert" class="md-nav__link">
<span class="md-ellipsis">
Option C — Online-Verifikation pro Assessment-Insert
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#entscheidung" class="md-nav__link">
<span class="md-ellipsis">
Entscheidung
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#konsequenzen" class="md-nav__link">
<span class="md-ellipsis">
Konsequenzen
</span>
</a>
<nav class="md-nav" aria-label="Konsequenzen">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#positiv" class="md-nav__link">
<span class="md-ellipsis">
Positiv
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#negativ" class="md-nav__link">
<span class="md-ellipsis">
Negativ
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#folgen-fur-andere-adrs" class="md-nav__link">
<span class="md-ellipsis">
Folgen für andere ADRs
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../0004-deployment-workflow/" class="md-nav__link">
<span class="md-ellipsis">
0004 Deployment-Workflow
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_3" >
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="">
<span class="md-ellipsis">
Archiv
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_3">
<span class="md-nav__icon md-icon"></span>
Archiv
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../archive/" class="md-nav__link">
<span class="md-ellipsis">
Übersicht
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Inhaltsverzeichnis">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Inhaltsverzeichnis
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#kontext" class="md-nav__link">
<span class="md-ellipsis">
Kontext
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#optionen" class="md-nav__link">
<span class="md-ellipsis">
Optionen
</span>
</a>
<nav class="md-nav" aria-label="Optionen">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#option-a-schema-only-tests-was-wir-vorher-hatten" class="md-nav__link">
<span class="md-ellipsis">
Option A — Schema-only Tests (was wir vorher hatten)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#option-b-property-test-gegen-die-echten-pdfs" class="md-nav__link">
<span class="md-ellipsis">
Option B — Property-Test gegen die echten PDFs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#option-c-online-verifikation-pro-assessment-insert" class="md-nav__link">
<span class="md-ellipsis">
Option C — Online-Verifikation pro Assessment-Insert
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#entscheidung" class="md-nav__link">
<span class="md-ellipsis">
Entscheidung
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#konsequenzen" class="md-nav__link">
<span class="md-ellipsis">
Konsequenzen
</span>
</a>
<nav class="md-nav" aria-label="Konsequenzen">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#positiv" class="md-nav__link">
<span class="md-ellipsis">
Positiv
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#negativ" class="md-nav__link">
<span class="md-ellipsis">
Negativ
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#folgen-fur-andere-adrs" class="md-nav__link">
<span class="md-ellipsis">
Folgen für andere ADRs
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="0003-sub-d-property-verification-zitate-als-substring-der-zitierten-pdf-seite">0003 — Sub-D Property-Verification: Zitate als Substring der zitierten PDF-Seite<a class="headerlink" href="#0003-sub-d-property-verification-zitate-als-substring-der-zitierten-pdf-seite" title="Permanent link">&para;</a></h1>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Status</strong></td>
<td>accepted</td>
</tr>
<tr>
<td><strong>Datum</strong></td>
<td>2026-04-10</td>
</tr>
<tr>
<td><strong>Refs</strong></td>
<td>Issues #50, #54, #60; tests/integration/test_citations_substring.py</td>
</tr>
</tbody>
</table>
<h2 id="kontext">Kontext<a class="headerlink" href="#kontext" title="Permanent link">&para;</a></h2>
<p>Der LLM-Output enthält pro Assessment N Zitate, jedes mit <code>text</code>, <code>quelle</code>
(z.B. "GRÜNE NRW Wahlprogramm 2022, S. 58") und <code>url</code>. Wahrscheinlich
korrekt — aber wie verifizieren wir das, ohne jedes einzeln händisch
nachzuschlagen?</p>
<p>Die naheliegenden Test-Optionen sind alle unbefriedigend:</p>
<ul>
<li><strong>Mock-LLM-Tests</strong>: prüfen das Schema, sagen aber nichts über die
inhaltliche Korrektheit.</li>
<li><strong>Snapshot-Tests</strong> der LLM-Outputs: drift mit jedem Modell-Update.</li>
<li><strong>Manuelles Stichprobenchecken</strong>: skaliert nicht über mehrere BLs.</li>
</ul>
<h2 id="optionen">Optionen<a class="headerlink" href="#optionen" title="Permanent link">&para;</a></h2>
<h3 id="option-a-schema-only-tests-was-wir-vorher-hatten">Option A — Schema-only Tests (was wir vorher hatten)<a class="headerlink" href="#option-a-schema-only-tests-was-wir-vorher-hatten" title="Permanent link">&para;</a></h3>
<p>Pydantic validiert dass jedes Zitat die Felder <code>text</code>, <code>quelle</code>, <code>url</code> hat
und <code>url</code> mit <code>/static/referenzen/</code> beginnt. Erkennt syntaktische
Korruption, aber keine Halluzinationen.</p>
<h3 id="option-b-property-test-gegen-die-echten-pdfs">Option B — Property-Test gegen die echten PDFs<a class="headerlink" href="#option-b-property-test-gegen-die-echten-pdfs" title="Permanent link">&para;</a></h3>
<p>Pro Zitat in der Prod-DB:
1. <code>quelle</code> per Token-Coverage-Match auf den <code>PROGRAMME</code>-Eintrag mappen.
2. Seitennummer aus <code>quelle</code> extrahieren.
3. Per <code>fitz</code> die PDF-Seite lesen, Whitespace + Soft-Hyphen normalisieren.
4. <code>text</code> 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.</p>
<p><strong>Vorteile:</strong> prüft die einzige Eigenschaft die wirklich zählt — "war
das was zitiert wird auch wirklich da". Findet Halluzinationen direkt.</p>
<p><strong>Nachteile:</strong> braucht eine lokale Kopie der <code>gwoe-antraege.db</code> und der
Wahlprogramm-PDFs. Test ist Pydantic-Schema-übergreifend (Integration,
nicht Unit). Skipped sauber wenn DB nicht gemounted ist.</p>
<h3 id="option-c-online-verifikation-pro-assessment-insert">Option C — Online-Verifikation pro Assessment-Insert<a class="headerlink" href="#option-c-online-verifikation-pro-assessment-insert" title="Permanent link">&para;</a></h3>
<p>Im <code>analyze_antrag</code>-Flow direkt nach LLM-Call jedes Zitat verifizieren
und bei Failure abbrechen oder retry.</p>
<p><strong>Vorteile:</strong> kein "stale data in DB"-Risiko.</p>
<p><strong>Nachteile:</strong> fügt Latenz und Komplexität in den Hot-Path. Die
Verifikation ist O(N×M), wo N=Zitate und M=Wahlprogramm-Pages.</p>
<h2 id="entscheidung">Entscheidung<a class="headerlink" href="#entscheidung" title="Permanent link">&para;</a></h2>
<p><strong>Option B als pytest-Integration-Test</strong><code>tests/integration/test_citations_substring.py</code>,
parametrisiert per <code>_load_recent_assessments(limit_per_bl=5)</code> × <code>_flat_zitate()</code>.</p>
<p><strong>Strict substring</strong> als Default-Match (Whitespace + Soft-Hyphen normalisiert,
LLM-Truncation-Marker <code>...</code> toleriert), <strong>5-Wort-Anker als Fallback</strong> 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.</p>
<p>Marker <code>pytestmark = pytest.mark.integration</code> — der Test läuft nicht in
der Default-Suite, sondern explizit per <code>pytest -m integration</code>. Skipped
wenn <code>webapp/data/gwoe-antraege.db</code> nicht existiert (Dev-Setup ohne
DB-Kopie).</p>
<p>Match-Helpers (<code>_normalize</code>, <code>_is_substring</code>, <code>_resolve_quelle_to_programm_id</code>,
<code>_extract_page_number</code>) sind eigene Unit-Tests in <code>TestHelpers</code> — die Match-
Logik selbst ist nicht-trivial und braucht ihre Eigenkontrolle.</p>
<h2 id="konsequenzen">Konsequenzen<a class="headerlink" href="#konsequenzen" title="Permanent link">&para;</a></h2>
<h3 id="positiv">Positiv<a class="headerlink" href="#positiv" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>Findet Halluzinationen direkt</strong>: Issue #60 wurde durch den ersten
Live-Lauf dieses Tests entdeckt (3 von 36 Citations failed), ohne
dass ein Mensch Wahlprogramm-PDFs aufmachen musste.</li>
<li><strong>Re-runnable als Regression-Gate</strong>: nach jedem Deploy einmal <code>pytest -m
integration</code> gegen die DB → 0 Failures = OK.</li>
<li><strong>Test-Logik = Production-Logik</strong>: ADR 0001 Option B (<code>reconstruct_zitate</code>)
nutzt <strong>identische</strong> Match-Heuristiken (<code>find_chunk_for_text</code>,
<code>_normalize_for_match</code>). Damit kann der Test nichts fangen, was die
Production nicht auch fangen würde, und umgekehrt — kein Test-/Prod-Drift.</li>
</ul>
<h3 id="negativ">Negativ<a class="headerlink" href="#negativ" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>Lokale DB-Kopie nötig</strong>: vor jedem Sub-D-Run muss <code>data/gwoe-antraege.db</code>
vom Container gepullt werden. CI-Integration steht aus.</li>
<li><strong>Test ist langsam-ish</strong>: ~50 Citations × ein PDF-Open pro Programm ist
bei den ~30 indexierten Programmen ~250ms im Ganzen, nicht trivial aber
nicht prohibitiv.</li>
<li><strong>Token-Coverage-Heuristik für Quelle-zu-Programm-Mapping</strong> kann false-
positive bei sehr ähnlichen Programmen werden (z.B. CDU NRW 2022 vs. CDU
Niedersachsen 2022 — würde durch Bundesland-Bonus-Check abgefangen).</li>
</ul>
<h3 id="folgen-fur-andere-adrs">Folgen für andere ADRs<a class="headerlink" href="#folgen-fur-andere-adrs" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>ADR 0001</strong> 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.</li>
</ul>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "../..", "features": ["navigation.sections", "navigation.expand", "search.highlight"], "search": "../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "In Zwischenablage kopiert", "clipboard.copy": "In Zwischenablage kopieren", "search.result.more.one": "1 weiteres Suchergebnis auf dieser Seite", "search.result.more.other": "# weitere Suchergebnisse auf dieser Seite", "search.result.none": "Keine Suchergebnisse", "search.result.one": "1 Suchergebnis", "search.result.other": "# Suchergebnisse", "search.result.placeholder": "Suchbegriff eingeben", "search.result.term.missing": "Es fehlt", "select.version": "Version ausw\u00e4hlen"}, "version": null}</script>
<script src="../../assets/javascripts/bundle.79ae519e.min.js"></script>
</body>
</html>