--- phase: 10-ausschreibungs-radar-foundation-d-e-ingestion plan: 01 subsystem: database tags: [prisma, postgres, tender-radar, ingestion, adm-zip, fast-xml-parser, csv-parse, multi-tenant] # Dependency graph requires: - phase: 09 (module-registry / dkv patterns) provides: Prisma schema conventions, handwritten-migration workflow, tenant-extension (forTenant) reference provides: - Global (tenant-agnostic) Tender table with OCDS-oriented nullable deadline/value fields (SCHEMA-01) - Singleton TenderSourcePollConfig model (sourceType @unique) as INGEST-06 foundation - fast-xml-parser, adm-zip, csv-parse installed in @tessera/api - Applied migration + regenerated Prisma client types for Tender / TenderSourcePollConfig affects: [10-02 module registration, 10-03 adapter/normalizer, 10-04 ingestion/scheduler, 10-05 controller/DTOs, phase 11 saved searches] # Tech tracking tech-stack: added: [fast-xml-parser@5, adm-zip@0.6, csv-parse@7] patterns: - "Global RLS-exempt reference table (no tenantId, no forTenant()) for platform-wide data (D-03)" - "Singleton-per-source config via fixed-slug @unique column (mirrors DkvModuleConfig.tenantId @unique)" - "Handwritten timestamped migration with IF NOT EXISTS guards (repo convention)" key-files: created: - apps/api/prisma/migrations/20260721120000_add_tender_radar/migration.sql modified: - apps/api/prisma/schema.prisma - apps/api/package.json key-decisions: - "Tender carries NO tenantId column and is NOT wrapped by forTenant()/RLS — platform-global data (D-03)" - "deadlineAt / estimatedValue are nullable by mandate (RESEARCH Pattern 4), not optional hardening" - "TenderSourcePollConfig.lastIngestedDay is a day-cursor (DateTime?), not a poll timestamp (RESEARCH Pattern 1)" - "sourceType @unique makes the singleton-per-source intent explicit and enforceable at the DB level" patterns-established: - "Global reference table: no tenantId, no @@index([tenantId]), plain PrismaService client (no RLS wrap)" - "Singleton config row keyed by fixed slug via @unique" requirements-completed: [SCHEMA-01] coverage: - id: D1 description: "Global Tender table exists with OCDS-oriented nullable deadline/value fields and no tenantId (SCHEMA-01, D-03)" requirement: "SCHEMA-01" verification: - kind: automated_ui ref: "grep -c 'model Tender' schema.prisma >=1; awk Tender-block | grep -c tenantId == 0" status: pass - kind: integration ref: "prisma migrate status -> 'Database schema is up to date!'; prisma.tender.count() == 0" status: pass human_judgment: false - id: D2 description: "Singleton TenderSourcePollConfig model (sourceType @unique) present and live (INGEST-06 foundation)" verification: - kind: integration ref: "prisma.tenderSourcePollConfig.count() == 0 without error; grep -c 'model TenderSourcePollConfig' == 1" status: pass human_judgment: false - id: D3 description: "fast-xml-parser, adm-zip, csv-parse installed and resolvable in @tessera/api" verification: - kind: integration ref: "node -e require('adm-zip');require('fast-xml-parser');require('csv-parse/sync') -> packages OK" status: pass human_judgment: false # Metrics duration: ~15min (aktive Ausführung, exkl. Checkpoint-Wartezeit) completed: 2026-07-21 status: complete --- # Phase 10 Plan 01: Ausschreibungs-Radar Foundation & Dependencies Summary **Global (tenant-agnostic) `Tender`-Referenztabelle + singleton `TenderSourcePollConfig` in Prisma/Postgres, drei Ingestion-Pakete (fast-xml-parser, adm-zip, csv-parse) installiert, Migration angewendet — Datenfundament für das gesamte DÖE-Ingestion-Modul.** ## Performance - **Duration:** ~15 min aktive Ausführung (exkl. Checkpoint- und Infrastruktur-Wartezeit) - **Started:** 2026-07-21 - **Completed:** 2026-07-21 - **Tasks:** 3 (1 Checkpoint + 2 auto) - **Files modified:** 4 (schema.prisma, package.json, pnpm-lock.yaml, migration.sql) ## Accomplishments - Globale `Tender`-Tabelle (OCDS-orientiert, nullable `deadlineAt`/`estimatedValue`) ohne `tenantId` — D-03 Invariante im Schema verankert (SCHEMA-01). - Singleton `TenderSourcePollConfig` mit `sourceType @unique` als Fundament für die geteilte, mandantensichere Poll-Konfiguration (INGEST-06). - Drei Ingestion-Bibliotheken installiert und auflösbar (fast-xml-parser, adm-zip, csv-parse) — adm-zip nach blockierendem Supply-Chain-Checkpoint vom User freigegeben. - Handgeschriebene, timestamped Migration angewendet; Prisma-Client-Typen regeneriert; DB-Schema up to date. ## Task Commits 1. **Task 1: adm-zip package legitimacy verification** — kein Commit (blocking-human Checkpoint, vom User "approved": cthackers/adm-zip, MIT, seit 2012, Mio. DLs/Woche) 2. **Task 2: Install packages + Tender/TenderSourcePollConfig models** — `713b1eb` (feat) 3. **Task 3: [BLOCKING] Apply tender-radar migration** — kein separater Code-Commit; Migrations-SQL ist Teil von `713b1eb`, Anwendung auf die laufende DB durch den Coordinator (Stack lokal gestartet) **Plan metadata:** siehe finalen `docs(10-01)`-Commit. ## Files Created/Modified - `apps/api/prisma/schema.prisma` — Modelle `Tender` (global, kein tenantId) + `TenderSourcePollConfig` (singleton) ergänzt - `apps/api/prisma/migrations/20260721120000_add_tender_radar/migration.sql` — handgeschriebene additive Migration (2 Tabellen, 4 Indexe, 2 Unique-Indexe) - `apps/api/package.json` — fast-xml-parser, adm-zip, csv-parse als Dependencies - `pnpm-lock.yaml` — Lockfile-Aktualisierung ## Decisions Made - **Tender ist plattform-global (D-03):** kein `tenantId`, kein `@@index([tenantId])`, keine RLS/`forTenant()`-Umhüllung. Verhindert, dass ein späterer `where: { tenantId }`-Reflex globale Daten für einen zweiten Mandanten unsichtbar macht (Threat T-10-01, mitigiert). - **Nullable deadline/value verpflichtend:** `deadlineAt` und `estimatedValue` sind laut RESEARCH Pattern 4 häufig leer — nullable ist Korrektheitsanforderung, kein optionales Hardening. - **Day-cursor statt Timestamp:** `TenderSourcePollConfig.lastIngestedDay` ist ein Tages-Cursor (`DateTime?`), passend zum DÖE-Daily-Batch-ZIP-Modell (RESEARCH Pattern 1). - **Singleton via `sourceType @unique`:** macht die "eine Konfig pro Quelle"-Absicht auf DB-Ebene explizit und erzwingbar. ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] Kommentar-Wortlaut verletzte die tenantId-Assertion** - **Found during:** Task 2 (Schema-Verifikation) - **Issue:** Der erläuternde Kommentar im Tender-Modell enthielt das Token `tenantId` ("Deliberately NO tenantId field ..."), wodurch die Acceptance-Assertion `grep -c "tenantId"` im Tender-Block `1` statt `0` lieferte — ein False-Positive gegen die D-03-Invariante. - **Fix:** Kommentar umformuliert zu "NO tenant column and NO tenant index — this is global data (D-03)"; Aussage bleibt erhalten, Token entfernt. - **Files modified:** apps/api/prisma/schema.prisma - **Verification:** `awk Tender-block | grep -c "tenantId"` == 0; `tsc --noEmit` exit 0. - **Committed in:** 713b1eb (Task-2-Commit) --- **Total deviations:** 1 auto-fixed (1 Bug) **Impact on plan:** Reine Kommentar-Korrektur zur Erfüllung der Design-Invarianten-Assertion. Kein Scope-Creep, keine Verhaltensänderung. ## Issues Encountered - **Task 3 blockiert durch offline DB-Stack:** Der Tessera-Docker-Stack (`tessera-ctl-db-1/-api-1/-web-1`) war beendet, die Postgres-DB liegt auf einem `internal: true`-Netzwerk ohne Host-Port-Mapping, und `DATABASE_URL` war vom Host nicht zugänglich. Als blockierendes Infrastruktur-Gate an den Coordinator/User zurückgegeben. **Auflösung:** Coordinator startete den lokalen Stack und wandte `prisma migrate deploy` an. Verifikation (auch eigenständig via `DATABASE_URL=...@172.19.0.2:5432` gegengeprüft): `prisma migrate status` → "Database schema is up to date!", `Tender`-Count = 0, `TenderSourcePollConfig`-Count = 0 — beide Tabellen live, keine Fehler. ## User Setup Required None — keine externe Service-Konfiguration nötig. (Hinweis: Migrations werden in diesem Repo bei laufendem Stack angewendet; `up`/`rebuild` liegt beim User.) ## Next Phase Readiness - Datenfundament steht: `Tender` + `TenderSourcePollConfig` live, Prisma-Client-Typen generiert, Pakete installiert. - Plan 10-02 (TendersModule + seedTendersModule) kann direkt aufsetzen. - Keine offenen Blocker. ## Self-Check: PASSED - FOUND: apps/api/prisma/schema.prisma - FOUND: apps/api/prisma/migrations/20260721120000_add_tender_radar/migration.sql - FOUND: .planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/10-01-SUMMARY.md - FOUND commit: 713b1eb --- *Phase: 10-ausschreibungs-radar-foundation-d-e-ingestion* *Completed: 2026-07-21*