-- QUIXZOOM PostgreSQL + PostGIS Schema -- -- Ansvarsområden: -- - Observationer -- - GPS -- - Tidsserier -- - Uppdrag -- - Zoomers -- - Videor -- - Sensorloggar -- - Spatiala frågor -- Aktivera PostGIS CREATE EXTENSION IF NOT EXISTS postgis; -- ============================================================ -- ZOOMERS -- ============================================================ CREATE TABLE zoomers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE, phone VARCHAR(50), status VARCHAR(50) DEFAULT 'active', -- active, inactive, suspended capabilities JSONB DEFAULT '[]', -- ["video", "photo", "lidar"] created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- ============================================================ -- AREAS -- ============================================================ CREATE TABLE areas ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, bounds GEOMETRY(POLYGON, 4326), -- PostGIS country VARCHAR(100), city VARCHAR(100), priority FLOAT DEFAULT 0.5, status VARCHAR(50) DEFAULT 'active', metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- ============================================================ -- OBSERVATIONS -- ============================================================ CREATE TABLE observations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), observation_id VARCHAR(255) UNIQUE NOT NULL, -- Extern ID (obs_xxx) -- Källa source_type VARCHAR(50) NOT NULL, -- quixzoom, youtube_cc, inspector source_id VARCHAR(255), zoomer_id UUID REFERENCES zoomers(id), -- Media media_type VARCHAR(50), -- video, image media_url TEXT, frame_number INTEGER, media_timestamp TIMESTAMP WITH TIME ZONE, -- Plats location GEOMETRY(POINT, 4326), -- PostGIS gps_accuracy FLOAT, altitude FLOAT, -- Kvalitet quality JSONB DEFAULT '{}', -- {resolution, sharpness, exposure} -- AI-analys ai_analysis JSONB DEFAULT '{}', -- {objectDetection, sceneClassification} -- Metadata metadata JSONB DEFAULT '{}', -- Status status VARCHAR(50) DEFAULT 'pending', -- pending, validated, rejected validated_by VARCHAR(255), validated_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Spatialt index CREATE INDEX idx_observations_location ON observations USING GIST(location); CREATE INDEX idx_observations_created_at ON observations(created_at); CREATE INDEX idx_observations_source ON observations(source_type, source_id); -- ============================================================ -- OBJECTS (Urban Objects) -- ============================================================ CREATE TABLE objects ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), object_id VARCHAR(255) UNIQUE NOT NULL, -- Extern ID (street_lamp_xxx) -- Typ type VARCHAR(100) NOT NULL, -- street_lamp, pothole, crosswalk subtype VARCHAR(100), -- Plats location GEOMETRY(POINT, 4326), -- PostGIS location_accuracy FLOAT, -- Attribut (kan ändras över tid) attributes JSONB DEFAULT '{}', -- Status status VARCHAR(50) DEFAULT 'active', -- active, removed, modified verification_status VARCHAR(50) DEFAULT 'unverified', -- unverified, verified, disputed -- Konfidens confidence FLOAT DEFAULT 0, uncertainty FLOAT DEFAULT 1.0, -- Metadata first_seen_at TIMESTAMP WITH TIME ZONE, last_seen_at TIMESTAMP WITH TIME ZONE, observation_count INTEGER DEFAULT 0, metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Spatialt index CREATE INDEX idx_objects_location ON objects USING GIST(location); CREATE INDEX idx_objects_type ON objects(type); CREATE INDEX idx_objects_verification ON objects(verification_status); -- ============================================================ -- EVIDENCE -- ============================================================ CREATE TABLE evidence ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), evidence_id VARCHAR(255) UNIQUE NOT NULL, -- Extern ID (ev_xxx) -- Referenser object_id UUID REFERENCES objects(id), observation_id UUID REFERENCES observations(id), -- Bevis proof_type VARCHAR(50), -- video, image, lidar, manual_inspection proof_url TEXT, frame_number INTEGER, -- Källa source_type VARCHAR(50), source_id VARCHAR(255), operator VARCHAR(255), -- Kvalitet quality JSONB DEFAULT '{}', reliability FLOAT DEFAULT 0, -- Metadata timestamp TIMESTAMP WITH TIME ZONE, metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_evidence_object ON evidence(object_id); CREATE INDEX idx_evidence_observation ON evidence(observation_id); -- ============================================================ -- TEMPORAL (Object History) -- ============================================================ CREATE TABLE temporal_records ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), temporal_id VARCHAR(255) UNIQUE NOT NULL, -- Extern ID (tmp_xxx) -- Referens object_id UUID REFERENCES objects(id), -- Tillstånd state JSONB NOT NULL, -- {attributes, verificationStatus, confidence} -- Ändringar changes JSONB DEFAULT '[]', -- ["lean: 0 -> 2"] -- Orsak reason VARCHAR(255), source VARCHAR(255), -- Tid recorded_at TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_temporal_object ON temporal_records(object_id); CREATE INDEX idx_temporal_recorded_at ON temporal_records(recorded_at); -- ============================================================ -- MISSIONS -- ============================================================ CREATE TABLE missions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), mission_id VARCHAR(255) UNIQUE NOT NULL, -- Extern ID (mission_xxx) -- Typ type VARCHAR(50) NOT NULL, -- verify, explore, document, diversify -- Område area_id UUID REFERENCES areas(id), -- Mål target_location GEOMETRY(POINT, 4326), -- PostGIS target_object_id UUID REFERENCES objects(id), -- Tilldelning assigned_to UUID REFERENCES zoomers(id), assigned_at TIMESTAMP WITH TIME ZONE, -- Status status VARCHAR(50) DEFAULT 'pending', -- pending, assigned, active, completed, failed -- Resultat result JSONB DEFAULT '{}', completed_at TIMESTAMP WITH TIME ZONE, failed_at TIMESTAMP WITH TIME ZONE, failure_reason TEXT, -- Prioritet priority FLOAT DEFAULT 0.5, -- Metadata instructions JSONB DEFAULT '[]', metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_missions_area ON missions(area_id); CREATE INDEX idx_missions_status ON missions(status); CREATE INDEX idx_missions_assigned ON missions(assigned_to); -- ============================================================ -- EVENTS (Event Sourcing) -- ============================================================ CREATE TABLE events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), sequence_number BIGINT NOT NULL UNIQUE, -- Event-typ type VARCHAR(100) NOT NULL, -- Stream stream_id VARCHAR(255) NOT NULL, aggregate_id VARCHAR(255) NOT NULL, -- Payload payload JSONB NOT NULL, -- Metadata metadata JSONB DEFAULT '{}', -- Tid timestamp TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_events_stream ON events(stream_id); CREATE INDEX idx_events_type ON events(type); CREATE INDEX idx_events_timestamp ON events(timestamp); CREATE INDEX idx_events_sequence ON events(sequence_number); -- ============================================================ -- SENSOR LOGS -- ============================================================ CREATE TABLE sensor_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Källa zoomer_id UUID REFERENCES zoomers(id), observation_id UUID REFERENCES observations(id), -- Sensorer gps_location GEOMETRY(POINT, 4326), -- PostGIS gps_accuracy FLOAT, altitude FLOAT, imu_accelerometer JSONB, -- {x, y, z} imu_gyroscope JSONB, -- {x, y, z} imu_magnetometer JSONB, -- {x, y, z} compass FLOAT, -- Tid recorded_at TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_sensor_logs_zoomer ON sensor_logs(zoomer_id); CREATE INDEX idx_sensor_logs_observation ON sensor_logs(observation_id); CREATE INDEX idx_sensor_logs_recorded_at ON sensor_logs(recorded_at); -- ============================================================ -- VIEWS (För snabb läsning) -- ============================================================ CREATE VIEW object_summary AS SELECT o.id, o.object_id, o.type, o.subtype, ST_X(o.location::geometry) as longitude, ST_Y(o.location::geometry) as latitude, o.verification_status, o.confidence, o.observation_count, o.first_seen_at, o.last_seen_at, COUNT(e.id) as evidence_count, COUNT(t.id) as temporal_records_count FROM objects o LEFT JOIN evidence e ON e.object_id = o.id LEFT JOIN temporal_records t ON t.object_id = o.id GROUP BY o.id; CREATE VIEW mission_summary AS SELECT m.id, m.mission_id, m.type, m.status, m.priority, a.name as area_name, z.name as assigned_to_name, m.created_at, m.completed_at FROM missions m LEFT JOIN areas a ON a.id = m.area_id LEFT JOIN zoomers z ON z.id = m.assigned_to; -- ============================================================ -- TRIGGERS (Uppdatera updated_at) -- ============================================================ CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ language 'plpgsql'; CREATE TRIGGER update_zoomers_updated_at BEFORE UPDATE ON zoomers FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_areas_updated_at BEFORE UPDATE ON areas FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_objects_updated_at BEFORE UPDATE ON objects FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_missions_updated_at BEFORE UPDATE ON missions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();