feat: populate inc latitude/longitude from geocoded geom (migration 04)

Feed coords are always empty; redefine the latitude/longitude generated columns to
COALESCE(feed, ST_Y/ST_X(geom)) so they carry the resolved/geocoded position for
every geocoded ticket (precision indicated by geo_source). STORED, recomputes when
geom changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
david kiania 2026-06-15 23:26:39 +03:00
parent 073db9b5b8
commit b323e8a1ac
2 changed files with 25 additions and 0 deletions

View file

@ -14,6 +14,7 @@ Field-ops **INC ticket** ingestion, geocoding, and read-schema that powers the
| `migrations/01_tickets_schema.sql` | The `tickets` schema: `tickets.inc` / `tickets.crq` (raw-jsonb-first), `tickets.geo_clusters` + `tickets.geo_locations` gazetteers, geom-resolution trigger, and `reporting.fn_tickets_for_map` (the GeoJSON read function) | | `migrations/01_tickets_schema.sql` | The `tickets` schema: `tickets.inc` / `tickets.crq` (raw-jsonb-first), `tickets.geo_clusters` + `tickets.geo_locations` gazetteers, geom-resolution trigger, and `reporting.fn_tickets_for_map` (the GeoJSON read function) |
| `migrations/02_import_meta.sql` | `tickets.import_meta` (per-dataset snapshot envelope metadata) + `fn_tickets_for_map` re-defined to expose it as `summary.freshness` (same signature — dashboard_api unchanged) | | `migrations/02_import_meta.sql` | `tickets.import_meta` (per-dataset snapshot envelope metadata) + `fn_tickets_for_map` re-defined to expose it as `summary.freshness` (same signature — dashboard_api unchanged) |
| `migrations/03_inc_columns.sql` | Unpacks `tickets.inc.raw` into **typed STORED generated columns** (status, cluster, region, team, owner, sla_status, mttr, lat/lng, is_* booleans, and EAT→`timestamptz` timestamps via `tickets.eat_ts()`). Computed for all rows + auto-populated on every ingest; `raw` stays the source of truth | | `migrations/03_inc_columns.sql` | Unpacks `tickets.inc.raw` into **typed STORED generated columns** (status, cluster, region, team, owner, sla_status, mttr, lat/lng, is_* booleans, and EAT→`timestamptz` timestamps via `tickets.eat_ts()`). Computed for all rows + auto-populated on every ingest; `raw` stays the source of truth |
| `migrations/04_inc_latlng.sql` | Redefines `latitude`/`longitude` to `COALESCE(feed, ST_Y/ST_X(geom))` so they're **populated from the geocoded position** (feed is always empty); precision per `geo_source` (`location` vs `cluster` centroid) |
| `import_tickets.py` | Ingests the **newest INC CSV** from the rustfs `tickets` bucket (`automations/inc/<EAT-timestamp>.csv`) and upserts on `ticket_id`; geocodes clusters + INC locations | | `import_tickets.py` | Ingests the **newest INC CSV** from the rustfs `tickets` bucket (`automations/inc/<EAT-timestamp>.csv`) and upserts on `ticket_id`; geocodes clusters + INC locations |
| `run_migrations.py` | Applies `migrations/*.sql` in order (ledger: `tickets.schema_migrations`) | | `run_migrations.py` | Applies `migrations/*.sql` in order (ledger: `tickets.schema_migrations`) |
| `shared.py` | Minimal DB/logging helpers (self-contained — no tracksolid dependency) | | `shared.py` | Minimal DB/logging helpers (self-contained — no tracksolid dependency) |

View file

@ -0,0 +1,24 @@
-- 04_inc_latlng.sql — fleettickets · populate lat/lng from the resolved geom
-- ─────────────────────────────────────────────────────────────────────────────
-- The source feed never carries coordinates (latitude/longitude are always empty),
-- but geocoding resolves a position into `geom` (feed -> location -> cluster). This
-- redefines the latitude/longitude generated columns to fall back to that geom when
-- the feed is empty, so they are actually populated for every geocoded ticket.
--
-- Precision still varies by geo_source ('location' = street-level, 'cluster' =
-- cluster centroid). geom is a regular column (set by the geom trigger / resolve),
-- which a generated column may reference; ST_X/ST_Y are IMMUTABLE. STORED, so it
-- recomputes whenever geom changes (e.g. after a geocode pass). Idempotent.
-- ─────────────────────────────────────────────────────────────────────────────
SET search_path = tickets, public;
-- redefine (drop + re-add) the two generated columns added in migration 03
ALTER TABLE tickets.inc DROP COLUMN IF EXISTS latitude;
ALTER TABLE tickets.inc DROP COLUMN IF EXISTS longitude;
ALTER TABLE tickets.inc
ADD COLUMN IF NOT EXISTS latitude double precision GENERATED ALWAYS AS
(COALESCE(NULLIF(raw->>'latitude','')::double precision, ST_Y(geom))) STORED,
ADD COLUMN IF NOT EXISTS longitude double precision GENERATED ALWAYS AS
(COALESCE(NULLIF(raw->>'longitude','')::double precision, ST_X(geom))) STORED;