Files
tessera-ctl/.planning/phases/13-scraping-adapters-cross-source-dedup/13-DEDUP-FIX-2026-09-07.md
T
schalli dba261de38
Tessera CI/CD / Lint & Type Check (push) Successful in 49s
Tessera CI/CD / Tests (push) Successful in 48s
Tessera CI/CD / Build & Publish Images (push) Successful in 4m1s
docs(13): Dedup-Fix dokumentiert und am laufenden System gegengeprueft
Die Fingerabdruck-Stufe legte vor dem Fix bei 16.255 Zeilen null Duplikate
zusammen — nicht zu wenige, sondern gar keine. Nach der Umstellung auf die
quellstabilen Felder sind es 26 Gruppen mit 52 Zeilen, davon 2 durch das neue
Wert-Veto getrennt. 374/374 API-Tests unabhaengig nachgelaufen.

Die automatische Nachrechnung wurde scharf geprueft statt nur beobachtet: 500
Zeilen wurden kuenstlich auf ein altes Fingerabdruck-Format zurueckgesetzt, dann
die API neu gebaut und gestartet. Der Start um 08:46:25 meldete "500 Zeile(n) auf
die neue Formel gebracht" und hinterliess null veraltete Zeilen; der Neustart um
08:46:49 erzeugte keine Meldung mehr. Damit ist beides belegt — dass sie repariert
und dass sie nicht bei jedem Start erneut laeuft.

Der Bericht haelt die drei bewusst getragenen Grenzen fest (zweites Los gleicher
Groessenordnung wird falsch zusammengelegt; RSS/E-Mail matchen nur ueber den Titel;
der eine bereits messbare Fall LSA242) und begruendet, warum ein Riegel dagegen
verworfen wurde: er wuerde RSS dauerhaft von der Dublettenerkennung ausschliessen.
Ausserdem festgehalten, dass die Stufe nur beim Einlesen wirkt — der Altbestand
wird nicht rueckwirkend zusammengefuehrt.

Phase 13 steht damit auf Complete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:48:29 +02:00

6.3 KiB

phase, kind, date, windows_ref, quick_task, result
phase kind date windows_ref quick_task result
13-scraping-adapters-cross-source-dedup befund-behebung 2026-09-07 11 260907-efh behoben und am laufenden System gegengeprueft

Phase 13 — die Dublettenerkennung greift wieder (WINDOWS #11)

Der Verifikationsbericht vom 2026-07-23 schloss Phase 13 mit gaps_found ab: die dritte Stufe der Dublettenerkennung, der Fingerabdruck, legte die Mehrzahl echter Duplikate nicht zusammen. Der Befund stand seither nur in jenem Bericht und war praktisch unsichtbar, bis er am 2026-09-07 als WINDOWS.md #11 erfasst und am selben Tag behoben wurde.

Wie schlimm es wirklich war

Vor dem Fix, gemessen an der lokalen Datenbank mit 16.255 Ausschreibungen:

Messung Wert
Zeilenpaare, die sich einen gespeicherten Fingerabdruck teilen 0

Nicht „zu wenige" — null. Die Stufe legte nichts zusammen, bei keiner einzigen Zeile. Sie war nicht ungenau, sondern wirkungslos.

Warum

Der Fingerabdruck hashte fünf Angaben: Vergabestelle, Titel, CPV-Division, Frist und geschätzten Wert. Zwei davon liefert nur eine Quellenart. normalizeBag() — der Weg für ai-netserver, cosinex, RSS und E-Mail-Alarm — setzt cpvDivisions fest auf leer und estimatedValue fest auf null; der DÖE-Weg füllt beide.

Quelle Zeilen mit CPV mit Wert mit Frist mit Vergabestelle
doe-opendata 14.432 13.925 1.029 12.534 14.426
service-bund (RSS) 1.823 0 0 0 0

Dieselbe Ausschreibung ergab aus zwei Quellen zwangsläufig zwei verschiedene Fingerabdrücke. Die Stufe konnte genau dort nicht greifen, wofür sie gebaut wurde.

Ein zweiter, unabhängiger Defekt kam beim Messen dazu: Der Aktualisierungszweig von TenderDedupService.resolve() frischte bei geänderter Änderungssignatur Titel, Vergabestelle, Frist und Wert einer bestehenden Zeile auf — schrieb den Fingerabdruck aber nie mit. Der gespeicherte Wert driftete damit vom Inhalt der Zeile weg. Schon eine reine Neuberechnung ohne jede Formeländerung hätte 3 Gruppen zusammenfallen lassen.

Warum CPV raus und der Wert nicht einfach mit

Beide Felder sind quellasymmetrisch, aber sie verhalten sich bei echten Daten gegensätzlich. Von 26 Gruppen, die sich Vergabestelle, Titel und Frist teilen:

  • 21 Gruppen trennte bisher allein das CPV. Angesehen zeigt sich immer dasselbe Muster: eine Zeile trägt die Sachdivision (34 Fahrzeuge, 45 Bau, 48 Software, 44 Material, 22 Druck, 71 Ingenieurwesen), die andere die Division 50 (Reparatur- und Wartungsdienste), teils ein bis zwei Tage später veröffentlicht. Das sind Erst- und Folgebekanntmachung derselben Sache — 21 übersehene Zusammenlegungen, keine geretteten Trennungen. Dass die früheren Stufen sie nicht fangen, ist geprüft: die 42 Zeilen tragen 41 verschiedene OCIDs und 42 verschiedene Bekanntmachungsnummern.
  • 3 Gruppen trennte allein der Wert — und die sind echt verschieden: zwei Lose desselben Bauvorhabens unter einem Titel (Neubau Radiologie mit 387.291 und 54.471; Sanierung Sport- und Mehrzweckhalle mit 200.000 und 80.000, beide CPV 71).

Deshalb: CPV ersatzlos aus dem Hash, der Wert dagegen als Veto nach dem Treffer. Findet die Stufe einen Kandidaten und tragen beide Seiten einen Wert unterschiedlicher Größenordnung, wird nicht zusammengelegt. Fehlt der Wert auf einer Seite, schweigt das Veto. Das bleibt ein exakter Vergleich zweier Größenordnungsstufen — die Warnung im Dateikommentar gegen Ähnlichkeits- und Schwellenwertsuche steht unverändert.

Ergebnis, am laufenden System gemessen

Messung vorher nachher
Fingerabdruck-Gruppen mit mehr als einer Zeile 0 26 (52 Zeilen)
davon durch das Wert-Veto getrennt — 2
Zeilen ohne die neue Versionsmarke 16.255 0
API-Tests src/tenders — 374/374 grün (unabhängig nachgelaufen)

Die automatische Nachrechnung wurde scharf geprüft

Die Formeländerung entwertet jeden gespeicherten Fingerabdruck. Damit das auf einer Bestandsdatenbank niemand von Hand nachziehen muss, trägt der Wert jetzt die Versionsmarke v2:, und ein Dienst rechnet beim Start alle Zeilen ohne diese Marke seitenweise nach.

Geprüft wurde nicht nur, dass beim Start nichts passiert, sondern dass tatsächlich repariert wird: 500 Zeilen wurden künstlich auf ein altes Format zurückgesetzt, dann die API neu gebaut und gestartet.

Zeitpunkt Beobachtung
Start 08:46:25 Log: „Fingerabdruck-Nachrechnung: 500 Zeile(n) auf die neue Formel gebracht"; danach 0 veraltete Zeilen
Neustart 08:46:49 keine Meldung — der zweite Start ist folgenlos

Was bewusst offen bleibt

Diese drei Grenzen stehen wörtlich im Dateikommentar von tender-fingerprint.ts und werden hier nicht weichgespült:

  1. Ein zweites Los gleicher Größenordnung wird weiterhin falsch zusammengelegt. Das Veto trennt nur bei unterschiedlicher Größenordnung. Eines der drei gemessenen Los-Paare (117.647 gegen 386.554, beide in der Größenordnung 10⁵) fällt darunter. Bewusst getragen: eine falsche Zusammenlegung ist sichtbar und reparierbar, 21 übersehene waren der stille Totalausfall der Stufe.
  2. RSS und E-Mail-Alarm matchen nur noch über den Titel, weil ihnen Vergabestelle und Frist fehlen. Im Normalfall heißt das, sie treffen weiterhin keine DÖE-Zeile. Fehlen der DÖE-Zeile aber ausnahmsweise beide Angaben auch, genügt der Titel.
  3. Genau ein solcher Fall liegt heute in der Datenbank: Titel „LSA242", einmal über service-bund und einmal über doe-opendata eingegangen, beide am selben Tag und beide ohne Vergabestelle und Frist. Er wird nach dieser Änderung zusammengelegt. Ein Riegel dagegen wurde erwogen und verworfen: „keine Zusammenlegung ohne Vergabestelle und Frist" würde RSS dauerhaft von der Dublettenerkennung ausschließen, weil RSS diese Felder nie liefert.

Kein Rückwirken auf den Bestand: Die Stufe entscheidet beim Einlesen, ob eine neue Zeile entsteht. Die jetzt sichtbaren 26 Gruppen bleiben als getrennte Zeilen bestehen; die Wirkung setzt beim nächsten Abruf ein. Wer den Altbestand zusammenführen will, braucht dafür einen eigenen Lauf — der ist hier nicht Teil des Fixes.

Ausführung

Quick-Task 260907-efh, Commits cb23e3b, 9e5445e, ee3b28e, 389a1fd.