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
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
---
|
||||
phase: 13-scraping-adapters-cross-source-dedup
|
||||
kind: befund-behebung
|
||||
date: 2026-09-07
|
||||
windows_ref: 11
|
||||
quick_task: 260907-efh
|
||||
result: 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`.
|
||||
Reference in New Issue
Block a user