From 8e119e2328cec72b878854b066f3875021de8ded Mon Sep 17 00:00:00 2001 From: david kiania Date: Tue, 16 Jun 2026 00:03:55 +0300 Subject: [PATCH] feat: tickets.inc_open_sla view + lowercase legacy region - Add tickets.inc_open_sla: open tickets with derived SLA (hours_open, sla_state vs 48h; clock = created_at_service or first_seen_at fallback) + team/cluster/geog for dispatch. (One-time legacy region->lowercase backfill applied to live data.) Co-Authored-By: Claude Opus 4.8 --- README.md | 1 + migrations/08_inc_open_sla_view.sql | 60 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 migrations/08_inc_open_sla_view.sql diff --git a/README.md b/README.md index 8c8ad6c..6d8013d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Field-ops **INC ticket** ingestion, geocoding, and read-schema that powers the | `migrations/05_inc_geography.sql` | Adds `geog geography(Point,4326)` (= `geom::geography`) + GiST index for **routing** — `ST_Distance`/`ST_DWithin`/KNN in real metres (nearest-vehicle, radius search) | | `migrations/06_inc_mttr_minutes.sql` | `mttr` generated column → integer **minutes** (source is decimal hours); drops the constant `is_alarm`/`is_auto_created`/`is_auto_closed` columns (kept in `raw`). `is_actionable` retained | | `migrations/07_inc_drop_service_type.sql` | Drops the constant `service_type` column (always `inc`; kept in `raw`) | +| `migrations/08_inc_open_sla_view.sql` | `tickets.inc_open_sla` view — open (`is_actionable`) tickets with **derived SLA** (`hours_open`, `sla_state` vs 48h; clock = `created_at_service` ∥ `first_seen_at`), plus team/cluster/`geog` for dispatch | | `import_tickets.py` | Ingests the **newest INC CSV** from the rustfs `tickets` bucket (`automations/inc/.csv`) and upserts on `ticket_id`; geocodes clusters + INC locations | | `run_migrations.py` | Applies `migrations/*.sql` in order (ledger: `tickets.schema_migrations`) | | `shared.py` | Minimal DB/logging helpers (self-contained — no tracksolid dependency) | diff --git a/migrations/08_inc_open_sla_view.sql b/migrations/08_inc_open_sla_view.sql new file mode 100644 index 0000000..7235ec6 --- /dev/null +++ b/migrations/08_inc_open_sla_view.sql @@ -0,0 +1,60 @@ +-- 08_inc_open_sla_view.sql — fleettickets · open-ticket SLA view +-- ───────────────────────────────────────────────────────────────────────────── +-- reporting view over the open (is_actionable) INC tickets with a DERIVED SLA +-- state, since the source sla_status is only meaningful once a ticket is closed. +-- +-- SLA clock = created_at_service, falling back to first_seen_at (the export's +-- first-sighting) when creation is missing (~70% of open rows lack +-- created_at_service); sla_clock_source flags which was used. Contract rule: 48h. +-- hours_open / sla_state use now(), so the view is always current. Includes the +-- routing-grade geog so dispatch can join straight off it. +-- ───────────────────────────────────────────────────────────────────────────── + +SET search_path = tickets, public; + +CREATE OR REPLACE VIEW tickets.inc_open_sla AS +SELECT + ticket_id, + normalized_status, + bucket, + cluster, + region, + location_name, + assigned_team, + owner, + sla_status AS source_sla_status, + mttr, -- minutes (null until closed) + COALESCE(created_at_service, first_seen_at) AS sla_clock, + CASE WHEN created_at_service IS NOT NULL THEN 'service' ELSE 'first_seen' END AS sla_clock_source, + round((EXTRACT(EPOCH FROM now() - COALESCE(created_at_service, first_seen_at)) / 3600)::numeric, 1) AS hours_open, + CASE + WHEN COALESCE(created_at_service, first_seen_at) IS NULL THEN 'unknown' + WHEN now() - COALESCE(created_at_service, first_seen_at) >= interval '48h' THEN 'breached' + WHEN now() - COALESCE(created_at_service, first_seen_at) >= interval '36h' THEN 'at_risk' + ELSE 'ok' + END AS sla_state, + created_at_service, + first_seen_at, + scheduled_at, + latitude, + longitude, + geo_source, + geom, + geog +FROM tickets.inc +WHERE is_actionable; + +COMMENT ON VIEW tickets.inc_open_sla IS + 'Open (is_actionable) INC tickets with derived SLA (48h rule; clock = created_at_service ' + 'or first_seen_at fallback). fleettickets 08.'; + +-- grants (guarded: roles may not exist on a fresh DB) +DO $grants$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'dashboard_ro') THEN + GRANT SELECT ON tickets.inc_open_sla TO dashboard_ro; + END IF; + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro') THEN + GRANT SELECT ON tickets.inc_open_sla TO grafana_ro; + END IF; +END $grants$;