feat(14-03): add EmailAlertAdapter generic extraction + 'email-alert' normalizer dispatch
GREEN phase (TDD) for Task 1: extractCandidateLinks (cheerio a[href] + footer-noise filter + MAX_LINKS_PER_EMAIL cap, plaintext regex fallback), titleFromEmail (subject -> first body line -> fallback), and sourceNoticeIdFor (sha256 link hash) implement D-04's generic, no-portal- specific-parser evaluation of alert emails. SourceType gains 'email-alert'; TenderNormalizerService routes it through the existing normalizeBag() path (same as ai-netserver/cosinex-dtvp/rss). EmailAlertAdapter.fetchTenders() is a Task-1 placeholder — Task 2 wires the real per-tenant fan-out. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -55,6 +55,7 @@ export class TenderNormalizerService {
|
||||
case 'ai-netserver':
|
||||
case 'cosinex-dtvp':
|
||||
case 'rss':
|
||||
case 'email-alert':
|
||||
return this.normalizeBag(raw);
|
||||
case 'doe-opendata':
|
||||
default:
|
||||
@@ -118,13 +119,14 @@ export class TenderNormalizerService {
|
||||
|
||||
/**
|
||||
* Generic-bag path (gap closure): NetServer/cosinex-DTVP scraper adapters
|
||||
* (and, since Phase 14 Plan 02, the RssAdapter, INGEST-04) carry a flat
|
||||
* (and, since Phase 14 Plan 02, the RssAdapter, INGEST-04; and since
|
||||
* Phase 14 Plan 03, the EmailAlertAdapter, INGEST-05) carry a flat
|
||||
* `{title, buyerName, procedureType, legalFramework, deadlineAt}` bag in
|
||||
* `raw.ocdsPayload` — there is no eForms/OCDS structure to inspect.
|
||||
* `legalFramework` is deliberately NOT mapped: NormalizedTenderFields has
|
||||
* no target field for it. RSS records always have `buyerName`/
|
||||
* `procedureType`/`deadlineAt` null (baseline title/link/guid mapping
|
||||
* only, D-04/D-05 thin-fields deferral).
|
||||
* no target field for it. RSS/email-alert records always have
|
||||
* `buyerName`/`procedureType`/`deadlineAt` null (baseline title/link/guid
|
||||
* mapping only, D-04/D-05 thin-fields deferral).
|
||||
*/
|
||||
private normalizeBag(raw: RawTenderRecord): NormalizedTenderFields {
|
||||
const bag = getBagPayload(raw.ocdsPayload);
|
||||
|
||||
Reference in New Issue
Block a user