dba261de38
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
127 lines
6.3 KiB
Markdown
127 lines
6.3 KiB
Markdown
---
|
|
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`.
|