From 6fe0dd07fefcf094ba41f1fd92c4d57eae717089 Mon Sep 17 00:00:00 2001 From: Schalli Date: Thu, 23 Jul 2026 09:49:19 +0200 Subject: [PATCH] feat(13-05): register CosinexAdapter as tenders module provider Adds CosinexAdapter to TendersModule's providers and registers it with SourceRegistry at DI boot, alongside DoeOpenDataAdapter/NetServerAdapter (cosinex-dtvp is not AGB-denylisted, so registration succeeds). Seeds a cosinex-dtvp TenderSourcePollConfig row with isActive: false, matching the ai-netserver "framework ready, activation deferred" stance (D-02). tsc --noEmit clean; src/tenders slice: 21 files, 221/221 tests pass (205 pre-existing + 16 new cosinex.adapter.spec.ts). Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/api/src/tenders/tenders.module.ts | 37 +++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/apps/api/src/tenders/tenders.module.ts b/apps/api/src/tenders/tenders.module.ts index ecaf466..7acd35c 100644 --- a/apps/api/src/tenders/tenders.module.ts +++ b/apps/api/src/tenders/tenders.module.ts @@ -3,6 +3,7 @@ import { ModuleRegistryModule } from '../module-registry/module-registry.module' import { ModuleRegistryService } from '../module-registry/module-registry.service'; import { PrismaService } from '../prisma/prisma.service'; import { SettingsModule } from '../settings/settings.module'; +import { CosinexAdapter } from './adapters/cosinex.adapter'; import { DoeOpenDataAdapter } from './adapters/doe-opendata.adapter'; import { NetServerAdapter } from './adapters/netserver.adapter'; import { SourceRegistry } from './source-registry'; @@ -84,6 +85,14 @@ import { TendersController } from './tenders.controller'; * plan does not force-activate the new source; activation is an * admin/seed decision (Phase 14 UI), matching D-02's "framework ready, * activation deferred" stance. + * + * Phase 13, Plan 05 (INGEST-03): adds `CosinexAdapter` — a SEPARATE + * single-portal HTML adapter for the cosinex/DTVP satellite (cosinex-dtvp + * is not denylisted) — as a provider and registers it alongside + * `DoeOpenDataAdapter`/`NetServerAdapter` in `onModuleInit`, the third and + * final Wave-4 additive `registry.register(...)` call. Its + * `TenderSourcePollConfig` row is likewise seeded with `isActive: false` + * (D-02: activation is a later admin/seed decision, Phase 14 UI). */ @Module({ imports: [ModuleRegistryModule, SettingsModule], @@ -91,6 +100,7 @@ import { TendersController } from './tenders.controller'; providers: [ DoeOpenDataAdapter, NetServerAdapter, + CosinexAdapter, SourceRegistry, TenderNormalizerService, TenderDedupService, @@ -113,17 +123,19 @@ export class TendersModule implements OnModuleInit { private readonly sourceRegistry: SourceRegistry, private readonly doeAdapter: DoeOpenDataAdapter, private readonly netServerAdapter: NetServerAdapter, + private readonly cosinexAdapter: CosinexAdapter, ) {} async onModuleInit(): Promise { try { // D-06/INGEST-07: registration itself is the denylist-gate enforcement // point — SourceRegistry.register() throws for any adapter serving a - // denylisted portal. DoeOpenDataAdapter and NetServerAdapter are both - // legitimate (tender24/lhs-vpbw/vergabe.landbw are not on the - // denylist); this call is additive-only for 13-05's future adapter. + // denylisted portal. DoeOpenDataAdapter, NetServerAdapter, and + // CosinexAdapter are all legitimate (none of tender24/lhs-vpbw/ + // vergabe.landbw/cosinex-dtvp are on the denylist). this.sourceRegistry.register(this.doeAdapter); this.sourceRegistry.register(this.netServerAdapter); + this.sourceRegistry.register(this.cosinexAdapter); this.logger.log( `Registered source adapters: ${this.sourceRegistry .activeAdapters() @@ -180,5 +192,24 @@ export class TendersModule implements OnModuleInit { } catch (error) { this.logger.error('Failed to seed ai-netserver poll config', error); } + + try { + // Phase 13, Plan 05 (INGEST-03): seed the cosinex-dtvp poll config + // row so it is admin-drivable, but isActive defaults false — same + // "framework ready, activation deferred" stance as ai-netserver + // (D-02, Phase 14 UI). + await this.prisma.tenderSourcePollConfig.upsert({ + where: { sourceType: 'cosinex-dtvp' }, + update: {}, + create: { + sourceType: 'cosinex-dtvp', + pollIntervalMin: 60, + isActive: false, + }, + }); + this.logger.log('cosinex-dtvp poll config seeded (inactive)'); + } catch (error) { + this.logger.error('Failed to seed cosinex-dtvp poll config', error); + } } }