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:
@@ -605,7 +605,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
|
||||
| 10. Ausschreibungs-Radar Foundation & DÖE Ingestion | 6/6 | Complete | 2026-07-21 |
|
||||
| 11. Filter Engine, Results UI & Saved Searches | 6/6 | Complete | 2026-07-22 (Verifikation: passed) |
|
||||
| 12. Tender Notifications | 4/4 | Complete | 2026-07-23 (Verifikation: passed) |
|
||||
| 13. Scraping Adapters & Cross-Source Deduplication | 6/6 | Offener Befund | Alle Plaene fertig, Verifikation 3/4 — Fingerprint-Dedup greift wegen leerer cpvDivisions beim Scraper nicht (WINDOWS.md #11) |
|
||||
| 13. Scraping Adapters & Cross-Source Deduplication | 6/6 | Complete | 2026-07-23, Dedup-Befund am 2026-09-07 behoben und am laufenden System gegengeprueft (13-DEDUP-FIX-2026-09-07.md) |
|
||||
| 14. RSS, Email-Alert Ingestion & Module Rollout | 5/5 | Wartet auf Live-Test | Alle Plaene fertig, Verifikation 4/5 — echter Exchange/EWS-Durchlauf steht aus, bewusst zurueckgestellt (WINDOWS.md #12) |
|
||||
| 15. Modul-Berechtigungen: Gruppen & User-Grants | 8/8 | Complete | 2026-08-04 (Bericht 15-04 am 2026-08-11 nachgezogen) |
|
||||
| 16. AD-Gruppen-Synchronisation | 5/5 | Complete | 2026-08-11 |
|
||||
|
||||
+5
-5
@@ -5,10 +5,10 @@ milestone_name: Ausschreibungs-Radar
|
||||
current_phase: 17
|
||||
current_phase_name: eigene-ausschreibungs-quellen-je-nutzer
|
||||
status: verified
|
||||
stopped_at: "Phase 17 auf passed. Zusaetzlich die alten Browser-Gegenproben nachgeholt: WINDOWS #1, #2, #5 geschlossen, #3 durchgefuehrt. Dabei ein Defekt gefunden und als #10 erfasst — der Zugriffs-Guard greift nicht auf den modul-eigenen Routen (PERM-04 unvollstaendig). Offen bleiben #4 und #6 (brauchen echtes AD) sowie #10."
|
||||
last_updated: "2026-09-07T08:10:00.000Z"
|
||||
stopped_at: "Phase 17 passed. Alte Gegenproben nachgeholt (#1,#2,#3,#5). Zwei Defekte behoben: #10 Zugriffs-Guard auf modul-eigenen Routen (Quick 260907-e8k), #11 Fingerabdruck-Dedup war wirkungslos, 0 statt 26 Gruppen (Quick 260907-efh). Beide im Browser bzw. am laufenden System gegengeprueft. Offen: #4 und #6 (brauchen echtes AD), #12 (Exchange-Live-Test)."
|
||||
last_updated: "2026-09-07T08:50:00.000Z"
|
||||
last_activity: 2026-09-07
|
||||
last_activity_desc: Alle nachholbaren Browser-Gegenproben erledigt (Phase 15/16/17); ein Defekt gefunden (WINDOWS #10)
|
||||
last_activity_desc: WINDOWS #10 und #11 behoben und gegengeprueft; Ledger auf 3 offene Punkte
|
||||
progress:
|
||||
total_phases: 17
|
||||
completed_phases: 17
|
||||
@@ -336,7 +336,7 @@ Items acknowledged and carried forward from previous milestone close:
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-09-07T08:10:00.000Z
|
||||
Stopped at: Naechster Schritt: WINDOWS #10 beheben (Guard ins gemeinsame Modul-Layout) oder /gsd-ship fuer Phase 17.
|
||||
Last session: 2026-09-07T08:50:00.000Z
|
||||
Stopped at: Alle drei verbleibenden Ledger-Punkte brauchen Fremdsysteme (AD, Exchange). Naechster moeglicher Schritt: /gsd-ship.
|
||||
Resume file: None
|
||||
Last activity: 2026-07-14 - Built LDAP per-user exclude/denylist filter (9d1323f), migration applied on live DB, verified via Playwright: sync deactivated 4 excluded service accounts (administrator/krbtgt/guest/dns-ldap), 2 real LDAP users stay active, 0 wrongly created
|
||||
|
||||
@@ -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