feat(10-03): implement DoeOpenDataAdapter — fetch, extract, D-02 filter

- Native fetch + AbortController 15s timeout (icon-discovery idiom, no
  axios); URL host hardcoded, only the internally-computed dayCursor is
  interpolated (T-10-06)
- HTTP 400 treated as an expected no-op (pubDay today/future) -> []
- adm-zip extraction with a pre-extraction decompression-bomb ceiling
  check (sum entry.header.size vs ~50MB) before any entry buffer is read
  (T-10-07); entries are never written to disk
- D-02 open-tender filter: positive tag.includes('tender') match only —
  award/planning/untagged-with-awards excluded (Pitfall C)
- Register DoeOpenDataAdapter in TendersModule.providers
- All doe-opendata.adapter.spec.ts tests green (6/6)
This commit is contained in:
2026-07-21 10:57:29 +02:00
parent f88e2a8141
commit 3764feb50b
2 changed files with 191 additions and 5 deletions
+7 -5
View File
@@ -2,15 +2,17 @@ import { Logger, Module, OnModuleInit } from '@nestjs/common';
import { ModuleRegistryModule } from '../module-registry/module-registry.module';
import { ModuleRegistryService } from '../module-registry/module-registry.service';
import { PrismaService } from '../prisma/prisma.service';
import { DoeOpenDataAdapter } from './adapters/doe-opendata.adapter';
import { seedTendersModule } from './tenders.seed';
/**
* NestJS module for the Ausschreibungs-Radar feature.
*
* Wave 2 (this plan) only registers the module in the marketplace and
* seeds the singleton DÖE poll config so the shared poll is
* admin-drivable. Ingestion/adapter/scheduler/controller wiring is
* added in Plans 03-05.
* Wave 2 registered the module in the marketplace and seeded the
* singleton DÖE poll config so the shared poll is admin-drivable. This
* plan (03) adds the DÖE source adapter + normalizer (parse+map core of
* INGEST-01/SCHEMA-01). Ingestion orchestration/scheduler/controller
* wiring is added in Plans 04-05.
*
* PrismaModule is global (no explicit import needed).
*
@@ -20,7 +22,7 @@ import { seedTendersModule } from './tenders.seed';
@Module({
imports: [ModuleRegistryModule],
controllers: [],
providers: [],
providers: [DoeOpenDataAdapter],
})
export class TendersModule implements OnModuleInit {
private readonly logger = new Logger(TendersModule.name);