feat(industrial): add OSM provenance + sync state to IndustrialPark (PR 1/4)
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 4s
CI / AI Services (Python) — Smoke (push) Failing after 4s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 48s
Deploy / Build API Image (push) Failing after 5s
Deploy / Build Web Image (push) Failing after 4s
Deploy / Build AI Services Image (push) Failing after 5s
E2E Tests / Playwright E2E (push) Failing after 7s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 3s
Security Scanning / Trivy Scan — API Image (push) Failing after 31s
Security Scanning / Trivy Scan — Web Image (push) Failing after 40s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 40s
Security Scanning / Trivy Filesystem Scan (push) Failing after 25s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 1s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 4s
CI / AI Services (Python) — Smoke (push) Failing after 4s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 48s
Deploy / Build API Image (push) Failing after 5s
Deploy / Build Web Image (push) Failing after 4s
Deploy / Build AI Services Image (push) Failing after 5s
E2E Tests / Playwright E2E (push) Failing after 7s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 3s
Security Scanning / Trivy Scan — API Image (push) Failing after 31s
Security Scanning / Trivy Scan — Web Image (push) Failing after 40s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 40s
Security Scanning / Trivy Filesystem Scan (push) Failing after 25s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 1s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
First PR of the OSM-sync project. Adds the schema scaffolding so the
follow-up bulk-import PR can write OSM-sourced rows alongside the 50
hand-curated industrial parks already in the table without disturbing
public list/detail/map flows or the IndustrialListing FK relationship.
New enums:
- IndustrialParkOsmType: NODE | WAY | RELATION
- IndustrialParkDataSource:
MANUAL existing curated rows (default for the 50 backfilled)
OSM raw OSM import, hidden from public until promoted
OSM_PROMOTED admin-reviewed OSM row visible on the public list
New columns on IndustrialPark:
- dataSource — drives public visibility + sync policy
- isPublic — true for MANUAL, false for raw OSM
- osmType, osmId — link to OSM entity (osmId UNIQUE)
- osmVersion, osmTags — incremental sync state + raw tag bag (JSONB)
- boundary — PostGIS MultiPolygon for park outline (Point
centroid stays in `location` for low-zoom render)
- osmLocked — admin freeze flag; sync skips this row entirely
- lockedFields — per-field freeze list; sync preserves listed cols
- lastSyncedAt — last reconcile pass timestamp
New indexes:
- osmId — sync upsert lookup
- (dataSource, isPublic) — public list filter
- boundary GiST — viewport / bbox spatial queries
- lastSyncedAt — cron staleness scan
Backfill behaviour: existing 20 rows automatically get
dataSource=MANUAL, isPublic=true via column defaults — no breaking
change for current consumers (frontend list, detail, map, admin
moderation, IndustrialListing FK).
Manually written migration SQL because Prisma cannot manage the
PostGIS Geometry type — `boundary` is added via AddGeometryColumn().
Next PRs:
- PR 2: bulk-import script (Overpass + osmium fallback)
- PR 3: bbox spatial API + frontend Mapbox layer (cluster + outlines)
- PR 4: monthly sync cron + admin diff/promote UI
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────
|
||||
-- PR 1 of OSM-sync project: add provenance fields to IndustrialPark so the
|
||||
-- next PR can bulk-import all `landuse=industrial` features from OSM
|
||||
-- without disturbing the 50 hand-curated rows already in the table.
|
||||
--
|
||||
-- Design notes:
|
||||
-- • `dataSource` distinguishes MANUAL (existing seeded rows) from OSM
|
||||
-- (raw imports) and OSM_PROMOTED (admin-reviewed). Public list filters
|
||||
-- to MANUAL + OSM_PROMOTED via `isPublic`.
|
||||
-- • `boundary` is a PostGIS MultiPolygon — most KCN OSM entities are
|
||||
-- `way` polygons; some are `relation` multipolygons. Both fit here.
|
||||
-- • `osmId` is unique. OSM ids are per-type but collisions across types
|
||||
-- are improbable given our query restricts to `landuse=industrial`.
|
||||
-- • `lockedFields` + `osmLocked` give admins escape hatches when OSM
|
||||
-- data is noisier than the curated value.
|
||||
-- ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
-- Enums first (CREATE TYPE is not idempotent — wrap in DO block for safety).
|
||||
CREATE TYPE "IndustrialParkOsmType" AS ENUM ('NODE', 'WAY', 'RELATION');
|
||||
CREATE TYPE "IndustrialParkDataSource" AS ENUM ('MANUAL', 'OSM', 'OSM_PROMOTED');
|
||||
|
||||
-- Add the columns. Existing rows get sensible defaults: they are
|
||||
-- human-curated and publicly visible.
|
||||
ALTER TABLE "IndustrialPark"
|
||||
ADD COLUMN "dataSource" "IndustrialParkDataSource" NOT NULL DEFAULT 'MANUAL',
|
||||
ADD COLUMN "isPublic" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "osmType" "IndustrialParkOsmType",
|
||||
ADD COLUMN "osmId" BIGINT,
|
||||
ADD COLUMN "osmVersion" INTEGER,
|
||||
ADD COLUMN "osmTags" JSONB,
|
||||
ADD COLUMN "osmLocked" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "lockedFields" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
ADD COLUMN "lastSyncedAt" TIMESTAMP(3);
|
||||
|
||||
-- PostGIS MultiPolygon column. Prisma uses `Unsupported(...)` for this so
|
||||
-- it never tries to manage it; we keep ownership in raw SQL.
|
||||
SELECT AddGeometryColumn('public', 'IndustrialPark', 'boundary', 4326, 'MULTIPOLYGON', 2);
|
||||
|
||||
-- Unique constraint on osmId (NULLs allowed → unique among non-null values).
|
||||
CREATE UNIQUE INDEX "IndustrialPark_osmId_key" ON "IndustrialPark"("osmId");
|
||||
|
||||
-- Indexes for the new access patterns:
|
||||
-- • osmId lookup during sync upsert
|
||||
-- • public-list filter (dataSource + isPublic)
|
||||
-- • spatial bbox queries (boundary GiST)
|
||||
-- • find rows that haven't been synced recently (cron)
|
||||
CREATE INDEX "IndustrialPark_osmId_idx"
|
||||
ON "IndustrialPark"("osmId");
|
||||
|
||||
CREATE INDEX "IndustrialPark_dataSource_isPublic_idx"
|
||||
ON "IndustrialPark"("dataSource", "isPublic");
|
||||
|
||||
CREATE INDEX "IndustrialPark_boundary_idx"
|
||||
ON "IndustrialPark" USING GIST ("boundary");
|
||||
|
||||
CREATE INDEX "IndustrialPark_lastSyncedAt_idx"
|
||||
ON "IndustrialPark"("lastSyncedAt");
|
||||
Reference in New Issue
Block a user