Spaces:
Sleeping
Sleeping
| -- PotholeIQ β Supabase schema (PostgreSQL + PostGIS) | |
| -- Run this once in the Supabase SQL editor (Project β SQL Editor β New query β paste β Run). | |
| -- 1. Geospatial extension | |
| create extension if not exists postgis; | |
| -- 2. Cases table. `geom` is a geography point so radius queries are in METERS. | |
| create table if not exists cases ( | |
| case_id text primary key, | |
| status text, | |
| submitted_at timestamptz, | |
| resolved_at timestamptz, | |
| address text, | |
| ward text, | |
| district text, | |
| maintenance_zone text, | |
| latitude double precision, | |
| longitude double precision, | |
| geom geography(Point, 4326), | |
| severity_score numeric, | |
| severity_level text, | |
| pothole_size text, | |
| duplicate_status text, | |
| duplicate_count integer default 0, | |
| linked_case_ids text, | |
| weather_risk text, | |
| ai_review_status text, | |
| reporter_contact text, | |
| photo_url text, | |
| box_folder_id text, | |
| -- crew closeout | |
| assigned_crew text, | |
| repair_method text, | |
| materials_used text, | |
| labor_hours numeric, | |
| crew_notes text, | |
| after_photo_url text, | |
| signed_by text, | |
| signed_at timestamptz, | |
| raw jsonb, | |
| created_at timestamptz default now(), | |
| updated_at timestamptz default now() | |
| ); | |
| -- 3. Indexes β spatial index makes "any pothole within 50m?" an instant lookup | |
| create index if not exists cases_geom_gix on cases using gist (geom); | |
| create index if not exists cases_status_idx on cases (status); | |
| create index if not exists cases_ward_idx on cases (ward); | |
| create index if not exists cases_submitted_idx on cases (submitted_at desc); | |
| -- 4. Keep geom in sync with lat/lng automatically | |
| create or replace function cases_set_geom() returns trigger language plpgsql as $$ | |
| begin | |
| if new.latitude is not null and new.longitude is not null then | |
| new.geom := ST_SetSRID(ST_MakePoint(new.longitude, new.latitude), 4326)::geography; | |
| end if; | |
| new.updated_at := now(); | |
| return new; | |
| end $$; | |
| drop trigger if exists trg_cases_set_geom on cases; | |
| create trigger trg_cases_set_geom before insert or update on cases | |
| for each row execute function cases_set_geom(); | |
| -- 5. Duplicate finder: every case within `radius_m` meters of a point (excluding optional case_id) | |
| create or replace function find_nearby_cases( | |
| p_lat double precision, p_lng double precision, | |
| p_radius_m double precision, p_exclude text default null | |
| ) returns table(case_id text, distance_m double precision, | |
| severity_level text, status text, submitted_at timestamptz) | |
| language sql stable as $$ | |
| select case_id, | |
| ST_Distance(geom, ST_SetSRID(ST_MakePoint(p_lng, p_lat),4326)::geography) as distance_m, | |
| severity_level, status, submitted_at | |
| from cases | |
| where geom is not null | |
| and (p_exclude is null or case_id <> p_exclude) | |
| and ST_DWithin(geom, ST_SetSRID(ST_MakePoint(p_lng, p_lat),4326)::geography, p_radius_m) | |
| order by distance_m asc; | |
| $$; | |