feat(10-01): add Tender/TenderSourcePollConfig models + ingestion packages
- Install fast-xml-parser, adm-zip, csv-parse in @tessera/api - Add global Tender model (no tenantId — D-03 platform-global data) - Add singleton TenderSourcePollConfig (sourceType @unique) - Handwritten additive migration for both tables - Regenerate Prisma client
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "Tender" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sourcePortal" TEXT NOT NULL,
|
||||
"sourceNoticeId" TEXT NOT NULL,
|
||||
"ocid" TEXT,
|
||||
"dedupKey" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"buyerName" TEXT,
|
||||
"cpvCodes" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"region" TEXT,
|
||||
"plz" TEXT,
|
||||
"bundesland" TEXT,
|
||||
"deadlineAt" TIMESTAMP(3),
|
||||
"estimatedValue" DECIMAL(14,2),
|
||||
"procedureType" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'active',
|
||||
"sourceUrl" TEXT,
|
||||
"contentHash" TEXT NOT NULL,
|
||||
"rawPayload" JSONB,
|
||||
"publishedAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Tender_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "TenderSourcePollConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sourceType" TEXT NOT NULL,
|
||||
"pollIntervalMin" INTEGER NOT NULL DEFAULT 60,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT false,
|
||||
"lastIngestedDay" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "TenderSourcePollConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "Tender_dedupKey_key" ON "Tender"("dedupKey");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "Tender_status_idx" ON "Tender"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "Tender_deadlineAt_idx" ON "Tender"("deadlineAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "Tender_publishedAt_idx" ON "Tender"("publishedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "TenderSourcePollConfig_sourceType_key" ON "TenderSourcePollConfig"("sourceType");
|
||||
@@ -256,3 +256,46 @@ model FavoriteLink {
|
||||
@@index([tenantId])
|
||||
@@index([widgetId])
|
||||
}
|
||||
|
||||
// Ausschreibungs-Radar (Phase 10) — platform-global tender reference data.
|
||||
// D-03: Tender carries NO tenantId and is NOT wrapped by forTenant()/RLS —
|
||||
// per-tenant scoping (saved searches) lives one layer up in Phase 11.
|
||||
model Tender {
|
||||
id String @id @default(uuid())
|
||||
sourcePortal String // e.g. 'doe-opendata'
|
||||
sourceNoticeId String
|
||||
ocid String? // OCDS contracting id, when present
|
||||
dedupKey String @unique // ocid, or fallback sourcePortal:sourceNoticeId — SCHEMA-02 upsert target
|
||||
title String
|
||||
buyerName String?
|
||||
cpvCodes String[] @default([])
|
||||
region String?
|
||||
plz String?
|
||||
bundesland String?
|
||||
deadlineAt DateTime? // frequently null (RESEARCH Pattern 4) — nullable is mandatory
|
||||
estimatedValue Decimal? @db.Decimal(14, 2)
|
||||
procedureType String?
|
||||
status String @default("active") // 'active' | 'expired' (D-05 retention marking)
|
||||
sourceUrl String?
|
||||
contentHash String // SCHEMA-02 change detection
|
||||
rawPayload Json? // debugging / re-normalization
|
||||
publishedAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([status])
|
||||
@@index([deadlineAt])
|
||||
@@index([publishedAt])
|
||||
// Deliberately NO tenant column and NO tenant index — this is global data (D-03)
|
||||
}
|
||||
|
||||
// Singleton-per-source admin poll config (INGEST-06 foundation).
|
||||
model TenderSourcePollConfig {
|
||||
id String @id @default(uuid())
|
||||
sourceType String @unique // fixed slug 'doe-opendata' — @unique makes singleton intent explicit
|
||||
pollIntervalMin Int @default(60) // D-04 default hourly
|
||||
isActive Boolean @default(false)
|
||||
lastIngestedDay DateTime? // day-cursor, NOT a timestamp (RESEARCH Pattern 1)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user