-- Location tracking schema for 2.4G gateway based wearable positioning -- Run this script in Supabase/PostgreSQL environment after ensuring uuid-ossp extension is available. CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- ============================================================= -- 1. 2.4G Gateways registry -- ============================================================= CREATE TABLE IF NOT EXISTS location_gateways ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), organization_id UUID, -- optional, reference to owning org / school (wire manually) campus_code TEXT, -- logical campus/region grouping code area_name TEXT, -- eg. East Campus, Zone B building_name TEXT, floor_label TEXT, room_label TEXT, name TEXT NOT NULL, -- human friendly name system_code TEXT NOT NULL UNIQUE, -- system/asset identifier serial_number TEXT, description TEXT, latitude DECIMAL(10, 8) NOT NULL, longitude DECIMAL(11, 8) NOT NULL, altitude_m NUMERIC(8, 2), coordinate_system TEXT DEFAULT 'WGS84', -- WGS84 / GCJ02 / BD09 etc install_height_m NUMERIC(6, 2), orientation_deg NUMERIC(5, 2), coverage_radius_m NUMERIC(6, 2), coverage_notes TEXT, ip_address INET, lan_mac MACADDR, upstream_mac MACADDR, firmware_version TEXT, hardware_version TEXT, heartbeat_interval_s INTEGER, status TEXT DEFAULT 'active' CHECK (status IN ('active', 'maintenance', 'inactive', 'retired')), last_online_at TIMESTAMPTZ, last_maintenance_at TIMESTAMPTZ, tags TEXT[], extra JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ ); COMMENT ON TABLE location_gateways IS 'Registry of 2.4G gateways installed across campuses.'; COMMENT ON COLUMN location_gateways.extra IS 'Extensible JSON payload for vendor-specific metadata.'; CREATE INDEX IF NOT EXISTS idx_location_gateways_org ON location_gateways(organization_id); CREATE INDEX IF NOT EXISTS idx_location_gateways_geo ON location_gateways(latitude, longitude); CREATE INDEX IF NOT EXISTS idx_location_gateways_status ON location_gateways(status); -- Auto update trigger for updated_at CREATE OR REPLACE FUNCTION trg_location_gateways_touch() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at := NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trg_location_gateways_touch ON location_gateways; CREATE TRIGGER trg_location_gateways_touch BEFORE UPDATE ON location_gateways FOR EACH ROW EXECUTE FUNCTION trg_location_gateways_touch(); -- ============================================================= -- 2. Wearable location snapshots (per time slice, per device) -- ============================================================= CREATE TABLE IF NOT EXISTS wearable_location_snapshots ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), device_id UUID NOT NULL REFERENCES public.ak_devices(id) ON DELETE CASCADE, user_id UUID REFERENCES public.ak_users(id) ON DELETE SET NULL, estimated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), window_started_at TIMESTAMPTZ, window_ended_at TIMESTAMPTZ, latitude DECIMAL(10, 8), longitude DECIMAL(11, 8), altitude_m NUMERIC(8, 2), coordinate_system TEXT DEFAULT 'WGS84', uncertainty_radius_m NUMERIC(6, 2), -- estimated accuracy / radius (meters) confidence NUMERIC(4, 2), -- 0.00 - 1.00 method TEXT, -- e.g. WCL, Trilateration, Kalman algorithm_version TEXT, observation_count INTEGER DEFAULT 0, campus_code TEXT, area_name TEXT, building_name TEXT, floor_label TEXT, room_label TEXT, observations JSONB, -- optional denormalized structure of gateway RSSI readings diagnostics JSONB, -- troubleshooting info (residuals, covariance etc) created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); COMMENT ON TABLE wearable_location_snapshots IS 'Estimated wearable locations derived from multi-gateway RSSI aggregation.'; COMMENT ON COLUMN wearable_location_snapshots.observations IS 'Optional JSON containing gateway RSSI array for quick lookup.'; CREATE INDEX IF NOT EXISTS idx_wearable_snapshots_device_time ON wearable_location_snapshots(device_id, estimated_at DESC); CREATE INDEX IF NOT EXISTS idx_wearable_snapshots_user_time ON wearable_location_snapshots(user_id, estimated_at DESC); CREATE INDEX IF NOT EXISTS idx_wearable_snapshots_geo ON wearable_location_snapshots(latitude, longitude); CREATE OR REPLACE FUNCTION trg_wearable_snapshots_touch() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at := NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trg_wearable_snapshots_touch ON wearable_location_snapshots; CREATE TRIGGER trg_wearable_snapshots_touch BEFORE UPDATE ON wearable_location_snapshots FOR EACH ROW EXECUTE FUNCTION trg_wearable_snapshots_touch(); -- ============================================================= -- 3. Raw gateway RSSI observations per snapshot -- ============================================================= CREATE TABLE IF NOT EXISTS wearable_location_observations ( id BIGSERIAL PRIMARY KEY, snapshot_id UUID NOT NULL REFERENCES wearable_location_snapshots(id) ON DELETE CASCADE, gateway_id UUID NOT NULL REFERENCES location_gateways(id) ON DELETE CASCADE, received_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gateway_time TIMESTAMPTZ, rssi_dbm NUMERIC(5, 2) NOT NULL, snr_db NUMERIC(5, 2), distance_m NUMERIC(7, 2), -- computed from RSSI if available channel INTEGER, frequency_mhz NUMERIC(6, 2), packet_sequence INTEGER, latency_ms INTEGER, firmware_version TEXT, payload JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); COMMENT ON TABLE wearable_location_observations IS 'Raw 2.4G gateway readings contributing to a wearable location snapshot.'; CREATE UNIQUE INDEX IF NOT EXISTS idx_observations_unique_sample ON wearable_location_observations(snapshot_id, gateway_id); CREATE INDEX IF NOT EXISTS idx_observations_gateway_time ON wearable_location_observations(gateway_id, received_at DESC); CREATE INDEX IF NOT EXISTS idx_observations_snapshot ON wearable_location_observations(snapshot_id); -- Optional view for latest snapshot per device CREATE OR REPLACE VIEW wearable_latest_location AS SELECT DISTINCT ON (device_id) device_id, id AS snapshot_id, estimated_at, latitude, longitude, altitude_m, uncertainty_radius_m, confidence, method, algorithm_version, created_at FROM wearable_location_snapshots ORDER BY device_id, estimated_at DESC; COMMENT ON VIEW wearable_latest_location IS 'Convenience view returning the latest computed position per wearable device.';