fix(tenders): link DOE notices to the notice page, not the API
The doe-opendata adapter stored the OCDS document's own `uri` as sourceUrl. That is the API address of the record and serves OCDS JSON by design, so anyone following the link from the results list, the detail view, or an alert mail landed on raw JSON instead of the notice. Build the human-readable page from the notice id the row already carries instead. `/ui/de/search/details?noticeId=...` is the redirect target of `/ui/de/notices/...`, so it needs no redirect. Verified in a browser for both id shapes the feed uses -- numeric (25673764 -> "Feuerwehr-Geraetehaus Miehlen Fliesenarbeiten") and UUID (7085ba12-... -> "Holzfassade"). The page is a single-page app that answers 200 with an identical shell for any id, so this had to be checked on rendered content; a status code proves nothing. The adapter alone only fixes new ingests, so a backfill migration rewrites the rows already stored -- in Tender and in TenderSource, since the detail view lists per-source links separately. It touches only rows still pointing at /api/notices/ and only ids of a shape that was actually verified, which makes it idempotent and keeps an unexpected id from being pasted into a URL. Counted read-only against the live database beforehand: 2846 DOE rows affected, none skipped. Closes the 2026-08-05 backlog item, which was deliberately held until Phase 16 was done. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
-- Backfill for the DÖE notice links.
|
||||
--
|
||||
-- Until 2026-08-11 the doe-opendata adapter stored the OCDS document's own
|
||||
-- `uri` as Tender.sourceUrl. That is the API address of the record: it serves
|
||||
-- OCDS JSON by design, so every user who followed the link from the results
|
||||
-- list, the detail view, or an alert mail landed on raw JSON instead of the
|
||||
-- notice. The adapter now builds the human-readable notice page instead, but
|
||||
-- rows already ingested keep the broken URL until this migration rewrites
|
||||
-- them.
|
||||
--
|
||||
-- The id inside the API URL is the same value the row already carries in
|
||||
-- sourceNoticeId (verified against the live feed on 2026-08-11: for a single
|
||||
-- notice, `uri`'s id and `releases[0].id` are identical). The page address is
|
||||
-- therefore derivable from stored data alone -- no refetch, no user input
|
||||
-- interpolated.
|
||||
--
|
||||
-- Two shapes of notice id occur in the feed and both were verified to render
|
||||
-- the correct notice in a browser: numeric ("25673764") and UUID
|
||||
-- ("7085ba12-c7f4-4f8c-8599-2fe9e4e2737c"). The WHERE clause restricts the
|
||||
-- rewrite to ids made of those characters, so a row with an unexpected id
|
||||
-- shape keeps its old value rather than getting a URL built from something
|
||||
-- that was never checked.
|
||||
--
|
||||
-- Idempotent: only rows still pointing at /api/notices/ are touched. Running
|
||||
-- it twice changes nothing the second time.
|
||||
|
||||
UPDATE "Tender"
|
||||
SET "sourceUrl" =
|
||||
'https://oeffentlichevergabe.de/ui/de/search/details?noticeId=' || "sourceNoticeId"
|
||||
WHERE "sourcePortal" = 'doe-opendata'
|
||||
AND "sourceUrl" LIKE 'https://oeffentlichevergabe.de/api/notices/%'
|
||||
AND "sourceNoticeId" ~ '^[A-Za-z0-9-]+$';
|
||||
|
||||
-- Same rewrite for the per-source rows behind a deduplicated tender. The
|
||||
-- detail view lists these separately (tenders.controller.ts), so leaving them
|
||||
-- untouched would keep broken links visible even after the primary column is
|
||||
-- fixed.
|
||||
UPDATE "TenderSource"
|
||||
SET "sourceUrl" =
|
||||
'https://oeffentlichevergabe.de/ui/de/search/details?noticeId=' || "sourceNoticeId"
|
||||
WHERE "sourcePortal" = 'doe-opendata'
|
||||
AND "sourceUrl" LIKE 'https://oeffentlichevergabe.de/api/notices/%'
|
||||
AND "sourceNoticeId" ~ '^[A-Za-z0-9-]+$';
|
||||
Reference in New Issue
Block a user