docs(260907-efh): complete Cross-Source-Dedup-Fingerabdruck-Fix
WINDOWS.md #11 als fixed markiert — Fingerprint-Stufe hasht nur noch [buyerName, title, deadlineAt], Wert kehrt als exakter Widerspruchs-Veto zurueck, 16.255 Bestandszeilen wurden gegen die lokale DB nachgerechnet (0 -> 26 Duplikat-Gruppen, davon 2 durch den Veto geschuetzt). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
schema_version: 1
|
schema_version: 1
|
||||||
open_count: 4
|
open_count: 3
|
||||||
waived_count: 0
|
waived_count: 0
|
||||||
fixed_count: 8
|
fixed_count: 9
|
||||||
total_count: 12
|
total_count: 12
|
||||||
last_updated: 2026-09-07T08:40:04.951Z
|
last_updated: 2026-09-07T08:42:34.794Z
|
||||||
---
|
---
|
||||||
|
|
||||||
# Broken Windows Ledger
|
# Broken Windows Ledger
|
||||||
@@ -25,7 +25,7 @@ last_updated: 2026-09-07T08:40:04.951Z
|
|||||||
| 8 | 17 | unrun-verify | apps/web/src/app/(portal)/modules/tender-radar/my-sources/page.tsx | | Browser-Gegenprobe Plan 17-03 Task 1 (Tracer-Feedback-Gate): Meine Quellen mit drei Abschnitten, neu angelegter eigener Feed sofort sichtbar, plattformweiter Feed ohne Entfernen-Knopf, Digest-Intervall speichert nach Reload — kein Browser-Tool in dieser Sitzung verfuegbar | fixed | | 2026-08-12T10:02:49.122Z | 2026-09-07T07:50:56.693Z |
|
| 8 | 17 | unrun-verify | apps/web/src/app/(portal)/modules/tender-radar/my-sources/page.tsx | | Browser-Gegenprobe Plan 17-03 Task 1 (Tracer-Feedback-Gate): Meine Quellen mit drei Abschnitten, neu angelegter eigener Feed sofort sichtbar, plattformweiter Feed ohne Entfernen-Knopf, Digest-Intervall speichert nach Reload — kein Browser-Tool in dieser Sitzung verfuegbar | fixed | | 2026-08-12T10:02:49.122Z | 2026-09-07T07:50:56.693Z |
|
||||||
| 9 | 17 | unrun-verify | apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx | | Browser-Gegenprobe Plan 17-03 Task 2: USER-Konto sieht keine Bedienelemente auf /settings (nur Hinweis+Verweis), ADMIN-Konto sieht Abrufintervall+plattformweite Feeds ohne Postfach/Benachrichtigung, Zahnrad fuehrt in beiden Faellen nach Meine Quellen — kein Browser-Tool in dieser Sitzung verfuegbar | fixed | | 2026-08-12T10:02:49.291Z | 2026-09-07T07:50:56.910Z |
|
| 9 | 17 | unrun-verify | apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx | | Browser-Gegenprobe Plan 17-03 Task 2: USER-Konto sieht keine Bedienelemente auf /settings (nur Hinweis+Verweis), ADMIN-Konto sieht Abrufintervall+plattformweite Feeds ohne Postfach/Benachrichtigung, Zahnrad fuehrt in beiden Faellen nach Meine Quellen — kein Browser-Tool in dieser Sitzung verfuegbar | fixed | | 2026-08-12T10:02:49.291Z | 2026-09-07T07:50:56.910Z |
|
||||||
| 10 | 15 | unmet-truth | apps/web/src/app/(portal)/modules/tender-radar/page.tsx | | PERM-04 greift nicht auf modul-eigenen Routen: der Zugriffs-Guard sitzt nur in modules/[category]/[moduleSlug]/page.tsx. Die vier fest verdrahteten Modulrouten (tender-radar, dkv-fleet, cert-manager, domaincheck) samt Unterseiten (z.B. tender-radar/my-sources) laufen daran vorbei und rendern fuer einen USER OHNE Modulfreigabe vollstaendig, inklusive bedienbarer Knoepfe. Im Browser gemessen am 2026-09-07 mit nutzer2 (keine Freigabe): /modules/procurement/tender-radar zeigt korrekt die 403-Seite, /modules/tender-radar und /modules/tender-radar/my-sources und /modules/dkv-fleet zeigen die volle Seite. Keine Datenpreisgabe - die API antwortet auf allen Endpunkten mit 403 - aber der Nutzer sieht Bedienelemente, die er nicht benutzen darf, und bekommt rohe englische Techniktexte statt einer verstaendlichen Meldung. Beleg: .planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/uat-2026-09-07/befund-modul-eigene-route-ohne-freigabe.png | fixed | | 2026-09-07T08:03:45.978Z | 2026-09-07T08:40:04.951Z |
|
| 10 | 15 | unmet-truth | apps/web/src/app/(portal)/modules/tender-radar/page.tsx | | PERM-04 greift nicht auf modul-eigenen Routen: der Zugriffs-Guard sitzt nur in modules/[category]/[moduleSlug]/page.tsx. Die vier fest verdrahteten Modulrouten (tender-radar, dkv-fleet, cert-manager, domaincheck) samt Unterseiten (z.B. tender-radar/my-sources) laufen daran vorbei und rendern fuer einen USER OHNE Modulfreigabe vollstaendig, inklusive bedienbarer Knoepfe. Im Browser gemessen am 2026-09-07 mit nutzer2 (keine Freigabe): /modules/procurement/tender-radar zeigt korrekt die 403-Seite, /modules/tender-radar und /modules/tender-radar/my-sources und /modules/dkv-fleet zeigen die volle Seite. Keine Datenpreisgabe - die API antwortet auf allen Endpunkten mit 403 - aber der Nutzer sieht Bedienelemente, die er nicht benutzen darf, und bekommt rohe englische Techniktexte statt einer verstaendlichen Meldung. Beleg: .planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/uat-2026-09-07/befund-modul-eigene-route-ohne-freigabe.png | fixed | | 2026-09-07T08:03:45.978Z | 2026-09-07T08:40:04.951Z |
|
||||||
| 11 | 13 | unmet-truth | apps/api/src/tenders/tender-fingerprint.ts | | Cross-Source-Dedup kollabiert die Mehrzahl echter Duplikate nicht: tenderFingerprint() haengt [buyerName, title, cpvDivisionKey, deadlineKey, valueBucket] zu einem String und hasht ihn. Die Scraper-Adapter liefern cpvDivisions immer leer, DOE dagegen meist gefuellt - dieselbe Ausschreibung ergibt aus beiden Quellen verschiedene Fingerabdruecke, die Fingerprint-Stufe greift also gerade dort nicht, wo sie greifen soll. Steht als offener Rest in 13-VERIFICATION.md (status gaps_found, 3/4 Wahrheiten), war aber bisher nicht im Ledger und damit unsichtbar. Die frueher vermutete Ursache (Normalizer-Defaults) ist mit 9881005 geschlossen und nicht gemeint. | open | | 2026-09-07T08:09:04.188Z | |
|
| 11 | 13 | unmet-truth | apps/api/src/tenders/tender-fingerprint.ts | | Cross-Source-Dedup kollabiert die Mehrzahl echter Duplikate nicht: tenderFingerprint() haengt [buyerName, title, cpvDivisionKey, deadlineKey, valueBucket] zu einem String und hasht ihn. Die Scraper-Adapter liefern cpvDivisions immer leer, DOE dagegen meist gefuellt - dieselbe Ausschreibung ergibt aus beiden Quellen verschiedene Fingerabdruecke, die Fingerprint-Stufe greift also gerade dort nicht, wo sie greifen soll. Steht als offener Rest in 13-VERIFICATION.md (status gaps_found, 3/4 Wahrheiten), war aber bisher nicht im Ledger und damit unsichtbar. Die frueher vermutete Ursache (Normalizer-Defaults) ist mit 9881005 geschlossen und nicht gemeint. | fixed | | 2026-09-07T08:09:04.188Z | 2026-09-07T08:42:34.794Z |
|
||||||
| 12 | 14 | unrun-verify | .planning/phases/14-rss-email-alert-ingestion-module-rollout/14-03-PLAN.md | | 14-03 Task 4 (checkpoint human-verify, gate blocking): echtes Portal-Alert-Postfach ueber den Exchange/EWS-Weg anbinden und eine echte Alarm-Mail ingestieren. Zu belegen sind (a) die Mail erzeugt eine mandantenprivate Ausschreibung, (b) ein anderer Mandant sieht sie nicht, (c) der EWS-Body-Abruf liefert nicht-leeren, nicht-verstuemmelten Inhalt gegen einen echten Exchange-Server. Der handgeschriebene NTLM/SOAP-Weg ist nur gegen gemocktes httpntlm.post geprueft, nie gegen einen echten Server. Braucht ein echtes Postfach - vom User am 2026-07-24 bewusst zurueckgestellt. Stand bisher nur in 14-VERIFICATION.md (status human_needed), nicht im Ledger. | open | | 2026-09-07T08:09:04.406Z | |
|
| 12 | 14 | unrun-verify | .planning/phases/14-rss-email-alert-ingestion-module-rollout/14-03-PLAN.md | | 14-03 Task 4 (checkpoint human-verify, gate blocking): echtes Portal-Alert-Postfach ueber den Exchange/EWS-Weg anbinden und eine echte Alarm-Mail ingestieren. Zu belegen sind (a) die Mail erzeugt eine mandantenprivate Ausschreibung, (b) ein anderer Mandant sieht sie nicht, (c) der EWS-Body-Abruf liefert nicht-leeren, nicht-verstuemmelten Inhalt gegen einen echten Exchange-Server. Der handgeschriebene NTLM/SOAP-Weg ist nur gegen gemocktes httpntlm.post geprueft, nie gegen einen echten Server. Braucht ein echtes Postfach - vom User am 2026-07-24 bewusst zurueckgestellt. Stand bisher nur in 14-VERIFICATION.md (status human_needed), nicht im Ledger. | open | | 2026-09-07T08:09:04.406Z | |
|
||||||
|
|
||||||
````json
|
````json
|
||||||
@@ -157,10 +157,10 @@ last_updated: 2026-09-07T08:40:04.951Z
|
|||||||
"file": "apps/api/src/tenders/tender-fingerprint.ts",
|
"file": "apps/api/src/tenders/tender-fingerprint.ts",
|
||||||
"line": null,
|
"line": null,
|
||||||
"description": "Cross-Source-Dedup kollabiert die Mehrzahl echter Duplikate nicht: tenderFingerprint() haengt [buyerName, title, cpvDivisionKey, deadlineKey, valueBucket] zu einem String und hasht ihn. Die Scraper-Adapter liefern cpvDivisions immer leer, DOE dagegen meist gefuellt - dieselbe Ausschreibung ergibt aus beiden Quellen verschiedene Fingerabdruecke, die Fingerprint-Stufe greift also gerade dort nicht, wo sie greifen soll. Steht als offener Rest in 13-VERIFICATION.md (status gaps_found, 3/4 Wahrheiten), war aber bisher nicht im Ledger und damit unsichtbar. Die frueher vermutete Ursache (Normalizer-Defaults) ist mit 9881005 geschlossen und nicht gemeint.",
|
"description": "Cross-Source-Dedup kollabiert die Mehrzahl echter Duplikate nicht: tenderFingerprint() haengt [buyerName, title, cpvDivisionKey, deadlineKey, valueBucket] zu einem String und hasht ihn. Die Scraper-Adapter liefern cpvDivisions immer leer, DOE dagegen meist gefuellt - dieselbe Ausschreibung ergibt aus beiden Quellen verschiedene Fingerabdruecke, die Fingerprint-Stufe greift also gerade dort nicht, wo sie greifen soll. Steht als offener Rest in 13-VERIFICATION.md (status gaps_found, 3/4 Wahrheiten), war aber bisher nicht im Ledger und damit unsichtbar. Die frueher vermutete Ursache (Normalizer-Defaults) ist mit 9881005 geschlossen und nicht gemeint.",
|
||||||
"status": "open",
|
"status": "fixed",
|
||||||
"reason": "",
|
"reason": "",
|
||||||
"recorded_at": "2026-09-07T08:09:04.188Z",
|
"recorded_at": "2026-09-07T08:09:04.188Z",
|
||||||
"resolved_at": null
|
"resolved_at": "2026-09-07T08:42:34.794Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 12,
|
"id": 12,
|
||||||
|
|||||||
+188
@@ -0,0 +1,188 @@
|
|||||||
|
---
|
||||||
|
phase: quick-260907-efh
|
||||||
|
plan: 01
|
||||||
|
subsystem: api
|
||||||
|
tags: [dedup, fingerprint, tenders, prisma, nestjs]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: "13-scraping-adapters-cross-source-dedup"
|
||||||
|
provides: "TenderDedupService three-tier resolver, tenderFingerprint() v1, backfill-tender-source.ts"
|
||||||
|
provides:
|
||||||
|
- "tenderFingerprint() v2 (buyerName, title, deadlineAt only) with FINGERPRINT_VERSION_PREFIX"
|
||||||
|
- "valueBucketsContradict() exported veto function"
|
||||||
|
- "Value veto wired into TenderDedupService's fingerprint tier"
|
||||||
|
- "Fingerprint refresh on TenderDedupService's change-detection update branch"
|
||||||
|
- "TenderFingerprintBackfillService: automatic startup recompute of outdated fingerprints"
|
||||||
|
affects: ["13-scraping-adapters-cross-source-dedup", "any future tender dedup work"]
|
||||||
|
|
||||||
|
actuals:
|
||||||
|
tokens: 9035
|
||||||
|
tasks: 3
|
||||||
|
commits: 3
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "Version-prefixed hash output (FINGERPRINT_VERSION_PREFIX) so a startup backfill can detect stale rows without a separate schema flag"
|
||||||
|
- "Post-match veto function applied after a fingerprint hit, kept separate from the hash itself (exact bucket comparison, no similarity/threshold logic)"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- apps/api/src/tenders/tender-fingerprint-backfill.service.ts
|
||||||
|
- apps/api/src/tenders/tender-fingerprint-backfill.service.spec.ts
|
||||||
|
modified:
|
||||||
|
- apps/api/src/tenders/tender-fingerprint.ts
|
||||||
|
- apps/api/src/tenders/tender-fingerprint.spec.ts
|
||||||
|
- apps/api/src/tenders/backfill-tender-source.ts
|
||||||
|
- apps/api/src/tenders/tender-dedup.service.ts
|
||||||
|
- apps/api/src/tenders/tender-dedup.service.spec.ts
|
||||||
|
- apps/api/src/tenders/tenders.module.ts
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "CPV and estimatedValue removed from the hash with no replacement inside the hash — CPV is not compared anywhere anymore."
|
||||||
|
- "estimatedValue returns as a post-match veto (valueBucketsContradict), not a hash input — exact order-of-magnitude comparison, never a similarity threshold."
|
||||||
|
- "TenderDedupService's change-detection update branch now writes fingerprint alongside the other refreshed fields, fixing a second, independently discovered defect (fingerprint drift on update)."
|
||||||
|
- "Startup backfill (TenderFingerprintBackfillService) recomputes only rows failing the FINGERPRINT_VERSION_PREFIX check, paged at 500 rows, so a second start is a no-op."
|
||||||
|
|
||||||
|
requirements-completed: [SCHEMA-03]
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
- id: D1
|
||||||
|
description: "DOE-shaped and scraper-shaped records of the same tender (same buyer/title/deadline, differing CPV/value) produce the same v2 fingerprint"
|
||||||
|
requirement: "SCHEMA-03"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/api/src/tenders/tender-fingerprint.spec.ts#is the Kernregression this plan exists for"
|
||||||
|
status: pass
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/api/src/tenders/tender-fingerprint.spec.ts#gold value"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D2
|
||||||
|
description: "A fingerprint hit is vetoed when both sides carry an estimatedValue of a different order of magnitude; not vetoed when either side is null or both share the same magnitude"
|
||||||
|
requirement: "SCHEMA-03"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/api/src/tenders/tender-fingerprint.spec.ts#valueBucketsContradict"
|
||||||
|
status: pass
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/api/src/tenders/tender-dedup.service.spec.ts#Stufe-3-Wertveto (WINDOWS.md #11)"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D3
|
||||||
|
description: "The change-detection update branch writes the recomputed fingerprint alongside the other refreshed fields, closing the drift bug found while measuring"
|
||||||
|
requirement: "SCHEMA-03"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/api/src/tenders/tender-dedup.service.spec.ts#OCID match with a changed contentHash still updates the Tender mutable fields"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D4
|
||||||
|
description: "Pre-existing Tender rows are recomputed to the v2 fingerprint automatically on startup; a second startup does no work"
|
||||||
|
requirement: "SCHEMA-03"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/api/src/tenders/tender-fingerprint-backfill.service.spec.ts (4 tests: empty DB, old+null hash both caught, second run no-op, DB error swallowed)"
|
||||||
|
status: pass
|
||||||
|
- kind: other
|
||||||
|
ref: "Real local-DB recompute via backfill-tender-source.js (build + node dist/tenders/backfill-tender-source.js against tessera-ctl-db-1) — measured numbers below"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D5
|
||||||
|
description: "Next local API restart performs no further recompute (log stays silent about the backfill)"
|
||||||
|
verification: []
|
||||||
|
human_judgment: true
|
||||||
|
rationale: "The plan marks this a human-check step — the user restarts the local API themselves; the executor cannot restart it and observe the log itself under this task's constraints."
|
||||||
|
|
||||||
|
duration: 30min
|
||||||
|
completed: 2026-09-07
|
||||||
|
status: complete
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase quick-260907-efh Plan 01: Cross-Source-Dedup-Fingerabdruck korrigiert Summary
|
||||||
|
|
||||||
|
**Fingerabdruck-Formel auf [Vergabestelle, Titel, Frist] verengt, Wert kehrt als exakter Widerspruchs-Veto zurueck, 16.255 Bestandszeilen automatisch nachgerechnet — 26 statt 0 Duplikat-Gruppen gefunden, davon 2 durch den Wert-Veto vor Fehlzusammenlegung geschuetzt.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** ca. 30 min
|
||||||
|
- **Tasks:** 3/3 completed
|
||||||
|
- **Files modified:** 6 modified, 2 created
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- `tenderFingerprint()` hasht nur noch drei statt fuenf Segmente (buyerName, title, deadlineAt) — CPV-Division und geschaetzter Wert sind vollstaendig aus dem Hash entfernt, weil sie an der Live-Datenbank gemessen systematisch nur von einer Quellenart geliefert werden und die Stufe damit fuer ihren eigentlichen Zweck (Cross-Source-Match) blind machten.
|
||||||
|
- Neue Versionsmarke `FINGERPRINT_VERSION_PREFIX = "v2:"` vor dem Hash — macht veraltete Zeilen maschinell erkennbar, ohne ein zusaetzliches Schemafeld.
|
||||||
|
- Neue, exportierte Funktion `valueBucketsContradict()`: exakter Groessenordnungsvergleich, angewendet als Veto NACH einem Fingerprint-Treffer in `TenderDedupService`, nie als Bestandteil des Hashes.
|
||||||
|
- Beim Messen ein zweiter, unabhaengiger Defekt gefunden und mit repariert: der Aktualisierungszweig von `TenderDedupService.resolve()` aktualisierte Titel/Vergabestelle/Frist einer bestehenden Zeile, schrieb den Fingerabdruck dabei aber nie mit — der gespeicherte Wert driftete vom Inhalt der Zeile weg. Jetzt wird der einmal berechnete Fingerabdruck in beiden Zweigen (Anlegen und Aktualisieren) geschrieben.
|
||||||
|
- Neuer Dienst `TenderFingerprintBackfillService` (OnApplicationBootstrap, gleiche Bauform wie die LDAP-Bind-Passwort-Nachverschluesselung): rechnet beim Start seitenweise (500 Zeilen pro Transaktion) alle Zeilen ohne die neue Versionsmarke nach; ein zweiter Start tut nichts mehr. In `tenders.module.ts` bewusst VOR `TenderSchedulerService` eingetragen.
|
||||||
|
- Reale Nachrechnung gegen die lokale Datenbank gefahren (nicht nur behauptet) — Zahlen unten.
|
||||||
|
|
||||||
|
## Gemessene Zahlen (real gegen die lokale Datenbank gefahren, `backfill-tender-source.js`)
|
||||||
|
|
||||||
|
| Messung | Wert |
|
||||||
|
|---|---|
|
||||||
|
| Zeilen in Tender | 16.255 |
|
||||||
|
| Fingerabdruck-Gruppen mit mehr als einer Zeile, VOR der Nachrechnung | 0 |
|
||||||
|
| Fingerabdruck-Gruppen mit mehr als einer Zeile, NACH der Nachrechnung | 26 |
|
||||||
|
| Zeilen ohne die neue Versionsmarke NACH der Nachrechnung | 0 |
|
||||||
|
| Gruppen davon mit widersprechenden Wert-Groessenordnungen (Veto greift) | 2 |
|
||||||
|
|
||||||
|
Diese Zahlen stimmen exakt mit den in der Aufgabenstellung vorab gemessenen Werten ueberein (erwartet: 26 Gruppen, 2 Wertkonflikte).
|
||||||
|
|
||||||
|
## Die drei verbleibenden, bewusst getragenen Grenzen
|
||||||
|
|
||||||
|
Woertlich im Dateikommentar von `apps/api/src/tenders/tender-fingerprint.ts` UND hier festgehalten:
|
||||||
|
|
||||||
|
1. **Ein zweites Los gleicher Groessenordnung wird faelschlich zusammengelegt.** Zwei Lose desselben Bauvorhabens mit Werten derselben Groessenordnung teilen sich Titel, Vergabestelle und Frist und werden vom Wert-Veto nicht getrennt — der Veto greift nur bei UNTERSCHIEDLICHER Groessenordnung. An der Live-Datenbank gemessen betrifft das eines der drei urspruenglich gefundenen Los-Paare (117.647 gegen 386.554 — beide Groessenordnung 10^5). Bewusst getragen: eine falsche Zusammenlegung ist sichtbar und reparierbar, die vorherigen 21 uebersehenen Duplikate waren der stille Totalausfall der Stufe.
|
||||||
|
2. **Quellen ohne Vergabestelle und ohne Frist (RSS, E-Mail-Alarm) matchen nur noch ueber den Titel.** Im Normalfall bedeutet das: sie treffen weiterhin keine DOE-Zeile (DOE traegt fast immer beides). Fehlt aber ausnahmsweise auch der DOE-Zeile Vergabestelle und Frist, reicht der Titel allein zur Zusammenlegung.
|
||||||
|
3. **Genau ein solcher Fall existiert heute schon gemessen in der Datenbank:** Titel "LSA242", einmal ueber `service-bund`, einmal ueber `doe-opendata` eingegangen — wird nach dieser Aenderung zusammengelegt.
|
||||||
|
|
||||||
|
Zusaetzlich unveraendert gueltig, aus dem Dateikommentar uebernommen: der Wertvergleich bleibt ein exakter Vergleich zweier Groessenordnungs-Stufen, keine Aehnlichkeits- oder Schwellenwertsuche — das alte Verbot dazu ist NICHT aufgeweicht worden.
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Jede Aufgabe wurde einzeln committet:
|
||||||
|
|
||||||
|
1. **Task 1: Hash-Formel verengen, Versionsmarke, Wert-Widerspruch als eigene Funktion** — `cb23e3b` (fix, tdd) — `tender-fingerprint.ts`, `tender-fingerprint.spec.ts`, `backfill-tender-source.ts`
|
||||||
|
2. **Task 2: Wert-Veto in Stufe 3 des Resolvers, Fingerabdruck bei Update mitschreiben** — `9e5445e` (fix, tdd) — `tender-dedup.service.ts`, `tender-dedup.service.spec.ts`
|
||||||
|
3. **Task 3: Automatische Nachrechnung der Bestandszeilen beim Start** — `ee3b28e` (feat) — `tender-fingerprint-backfill.service.ts`, `tender-fingerprint-backfill.service.spec.ts`, `tenders.module.ts`
|
||||||
|
|
||||||
|
Hinweis zur Commit-Zaehlung: zwischen Task 2 und Task 3 landete auf `main` ein fremder Commit (`0645cac`, "docs(15): WINDOWS #10 geschlossen") aus einer parallel laufenden, unabhaengigen Aufgabe (Scope: `apps/web/`) — nicht Teil dieses Plans, nicht angefasst. Der Ausfuehrungsmodus dieser Aufgabe war SEQUENTIAL/ohne Worktree auf dem geteilten `main`-Branch, daher taucht dieser Commit zwischen den eigenen Commits im Log auf.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `apps/api/src/tenders/tender-fingerprint.ts` - v2-Formel, `FINGERPRINT_VERSION_PREFIX`, `valueBucketsContradict()`, ehrlicher Dateikommentar mit den drei Grenzen
|
||||||
|
- `apps/api/src/tenders/tender-fingerprint.spec.ts` - Tests auf v2-Verhalten umgeschrieben, inkl. handgerechnetem Goldwert-Test
|
||||||
|
- `apps/api/src/tenders/backfill-tender-source.ts` - Aufruf/Feldauswahl an neue Signatur angepasst, `decimalToNumberOrNull` entfernt (nicht mehr gebraucht)
|
||||||
|
- `apps/api/src/tenders/tender-dedup.service.ts` - Fingerabdruck einmal berechnet, in Anlege- UND Aktualisierungszweig geschrieben; Wert-Veto in Stufe 3
|
||||||
|
- `apps/api/src/tenders/tender-dedup.service.spec.ts` - vier neue Wert-Veto-Tests, bestehender Update-Test um Fingerabdruck-Erwartung erweitert
|
||||||
|
- `apps/api/src/tenders/tender-fingerprint-backfill.service.ts` - NEU: automatische Startup-Nachrechnung
|
||||||
|
- `apps/api/src/tenders/tender-fingerprint-backfill.service.spec.ts` - NEU: vier Tests (leere DB, alter+leerer Hash, zweiter Lauf no-op, DB-Fehler geschluckt)
|
||||||
|
- `apps/api/src/tenders/tenders.module.ts` - `TenderFingerprintBackfillService` als Provider, mit Kommentar VOR `TenderSchedulerService` platziert
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- CPV bleibt vollstaendig aus dem Fingerabdruck draussen — keine Ersatzformel, keine Wiedereinfuehrung an anderer Stelle.
|
||||||
|
- Der Wertvergleich lebt bewusst in `tender-fingerprint.ts` neben der Groessenordnungsbildung, damit es genau eine Definition von "Groessenordnung" im Code gibt.
|
||||||
|
- Der Aktualisierungszweig in `TenderDedupService` schreibt weiterhin niemals `ownerTenantId` — dieser zweite, beim Messen gefundene Defekt wurde bewusst NICHT mitrepariert, weil er ausserhalb der Aufgabenstellung dieses Plans liegt (siehe Threat Register T-EFH-02, "accept").
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None - plan executed exactly as written. Der im Objective bereits angekuendigte "zweite Defekt" (Fingerabdruck-Drift im Aktualisierungszweig) war explizit Teil von Task 2 der Aufgabenstellung, keine ungeplante Abweichung.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
None - keine externe Konfiguration noetig. Ein Punkt bleibt beim Nutzer: beim naechsten Neustart der lokalen API selbst pruefen, dass im Log KEINE Meldung ueber nachgerechnete Fingerabdruecke mehr erscheint (die lokale Datenbank wurde in diesem Lauf bereits auf v2 umgestellt — ein Log-Eintrag beim naechsten Start waere ein Zeichen, dass die Veraltet-Bedingung falsch herum greift).
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
WINDOWS.md #11 ist als `fixed` markiert (`gsd-tools windows fixed 11`). Die in 13-VERIFICATION.md offene Wahrheit 3 ist nicht mehr durch die Hash-Formel blockiert; die dort noch offene Live-Gegenprobe mit einer zweiten tatsaechlich aktiven Scraper-Quelle bleibt unveraendert offen (dedupActive erfordert `activePortalCount >= 2` — aktuell laeuft praktisch nur DOE aktiv).
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: quick-260907-efh*
|
||||||
|
*Completed: 2026-09-07*
|
||||||
Reference in New Issue
Block a user