feat(12-01): wire matchDelta into pollDueSources; register TenderMatchingService

pollDueSources now collects genuinely-new tender IDs via an indexed
dedupKey pre-check (existing upsert doesn't report create-vs-update),
and calls TenderMatchingService.matchDelta(newTenderIds) once at the
end of the tick — the delta-only matching boundary (D-07). Changed/
re-seen rows are excluded, only genuinely new rows trigger matching.

TenderMatchingService registered as a provider in TendersModule and
injected into TenderIngestionService. Ingestion spec extended to
assert matchDelta receives only the new IDs, and is not called when
no new tenders were ingested this tick.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-22 09:05:41 +02:00
parent 92d4c969de
commit b45047f0b3
3 changed files with 111 additions and 3 deletions
+8
View File
@@ -5,6 +5,7 @@ import { PrismaService } from '../prisma/prisma.service';
import { DoeOpenDataAdapter } from './adapters/doe-opendata.adapter';
import { seedTendersModule } from './tenders.seed';
import { TenderIngestionService } from './tender-ingestion.service';
import { TenderMatchingService } from './tender-matching.service';
import { TenderNormalizerService } from './tender-normalizer.service';
import { TenderSavedSearchService } from './tender-saved-search.service';
import { TenderSchedulerService } from './tender-scheduler.service';
@@ -31,6 +32,12 @@ import { TendersController } from './tenders.controller';
* as a further provider — same userId-scoping convention as
* TenderTriageService, no forTenant()/RLS (Pitfall 4).
*
* Phase 12, Plan 01 (NOTIFY-03) adds TenderMatchingService: injected into
* TenderIngestionService and called at the end of pollDueSources with only
* the genuinely-new tender IDs of the current tick (delta-only matching,
* D-07) — reuses buildTenderWhere against every active TenderSavedSearch
* profile and upserts TenderMatch rows (matched-vs-notified state, D-06).
*
* Seeds itself into the module registry on application startup via
* OnModuleInit lifecycle hook — same pattern as DkvModule.
*/
@@ -44,6 +51,7 @@ import { TendersController } from './tenders.controller';
TenderSchedulerService,
TenderTriageService,
TenderSavedSearchService,
TenderMatchingService,
],
})
export class TendersModule implements OnModuleInit {