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 <noreply@anthropic.com>
This commit is contained in:
david kiania 2026-06-16 00:03:55 +03:00
parent e54e2b7c56
commit 8e119e2328
2 changed files with 61 additions and 0 deletions

View file

@ -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/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/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/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/<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,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$;