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:
@@ -85,7 +85,9 @@ function bagRecord(
|
||||
? 'cosinex-dtvp'
|
||||
: sourceType === 'rss'
|
||||
? 'service-bund'
|
||||
: 'tender24';
|
||||
: sourceType === 'email-alert'
|
||||
? 'email-alert'
|
||||
: 'tender24';
|
||||
return {
|
||||
sourceType,
|
||||
sourcePortal,
|
||||
@@ -355,4 +357,40 @@ describe('TenderNormalizerService', () => {
|
||||
expect(normalized.title).toBe('Unbenannte Ausschreibung');
|
||||
});
|
||||
});
|
||||
|
||||
describe("'email-alert' sourceType (Phase 14, Plan 03, INGEST-05)", () => {
|
||||
it('maps an email-alert bag through normalizeBag(): title passes through, cpvDivisions stays empty', () => {
|
||||
const raw = bagRecord('email-alert', {
|
||||
title: 'Vergabebekanntmachung: Straßenbauarbeiten Musterstadt',
|
||||
buyerName: null,
|
||||
procedureType: null,
|
||||
legalFramework: null,
|
||||
deadlineAt: null,
|
||||
});
|
||||
|
||||
const normalized = service.normalize(raw);
|
||||
|
||||
expect(normalized.title).toBe(
|
||||
'Vergabebekanntmachung: Straßenbauarbeiten Musterstadt',
|
||||
);
|
||||
expect(normalized.cpvDivisions).toEqual([]);
|
||||
expect(normalized.cpvCodes).toEqual([]);
|
||||
expect(normalized.region).toBeNull();
|
||||
expect(normalized.bundesland).toBeNull();
|
||||
expect(normalized.buyerName).toBeNull();
|
||||
expect(normalized.procedureType).toBeNull();
|
||||
expect(normalized.deadlineAt).toBeNull();
|
||||
expect(normalized.dedupKey).toBe(
|
||||
`${raw.sourcePortal}:${raw.sourceNoticeId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('email-alert title falls back to "Unbenannte Ausschreibung" when empty', () => {
|
||||
const raw = bagRecord('email-alert', { title: '' });
|
||||
|
||||
const normalized = service.normalize(raw);
|
||||
|
||||
expect(normalized.title).toBe('Unbenannte Ausschreibung');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user