197 lines
6.6 KiB
SQL
197 lines
6.6 KiB
SQL
-- BikepackPilot database draft
|
|
-- Requires PostgreSQL + PostGIS.
|
|
|
|
CREATE EXTENSION IF NOT EXISTS postgis;
|
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
|
|
CREATE TYPE confidence_level AS ENUM (
|
|
'known_good', 'high', 'medium', 'low', 'unknown', 'restricted', 'conflicting'
|
|
);
|
|
|
|
CREATE TYPE warning_severity AS ENUM ('info', 'notice', 'warning', 'critical');
|
|
|
|
CREATE TYPE poi_category AS ENUM (
|
|
'water', 'food', 'sleep', 'repair', 'bailout', 'charging', 'emergency'
|
|
);
|
|
|
|
CREATE TABLE users (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email TEXT UNIQUE,
|
|
display_name TEXT,
|
|
default_profile TEXT NOT NULL DEFAULT 'loaded_gravel',
|
|
privacy_mode TEXT NOT NULL DEFAULT 'private',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE trips (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
owner_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
title TEXT NOT NULL,
|
|
start_point GEOGRAPHY(Point, 4326) NOT NULL,
|
|
end_point GEOGRAPHY(Point, 4326) NOT NULL,
|
|
start_date DATE,
|
|
end_date DATE,
|
|
profile TEXT NOT NULL,
|
|
constraints JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
status TEXT NOT NULL DEFAULT 'draft',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE routes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
trip_id UUID NOT NULL REFERENCES trips(id) ON DELETE CASCADE,
|
|
provider TEXT NOT NULL,
|
|
geometry GEOGRAPHY(LineString, 4326) NOT NULL,
|
|
distance_m INTEGER NOT NULL,
|
|
ascent_m INTEGER DEFAULT 0,
|
|
descent_m INTEGER DEFAULT 0,
|
|
surface_breakdown JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
suitability_score NUMERIC(5,2),
|
|
warnings JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
attribution TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE route_segments (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
route_id UUID NOT NULL REFERENCES routes(id) ON DELETE CASCADE,
|
|
seq INTEGER NOT NULL,
|
|
start_m INTEGER NOT NULL,
|
|
end_m INTEGER NOT NULL,
|
|
geometry GEOGRAPHY(LineString, 4326) NOT NULL,
|
|
distance_m INTEGER NOT NULL,
|
|
ascent_m INTEGER DEFAULT 0,
|
|
descent_m INTEGER DEFAULT 0,
|
|
avg_grade_percent NUMERIC(5,2),
|
|
max_grade_percent NUMERIC(5,2),
|
|
surface TEXT,
|
|
smoothness TEXT,
|
|
highway TEXT,
|
|
bicycle_access TEXT,
|
|
legal_access_confidence confidence_level NOT NULL DEFAULT 'unknown',
|
|
source_confidence confidence_level NOT NULL DEFAULT 'unknown',
|
|
traffic_stress NUMERIC(4,2),
|
|
official_cycle_route BOOLEAN DEFAULT false,
|
|
protected_area_overlap BOOLEAN DEFAULT false,
|
|
scores JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE INDEX route_segments_route_seq_idx ON route_segments(route_id, seq);
|
|
CREATE INDEX route_segments_geom_idx ON route_segments USING GIST(geometry);
|
|
|
|
CREATE TABLE pois (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
category poi_category NOT NULL,
|
|
name TEXT,
|
|
location GEOGRAPHY(Point, 4326) NOT NULL,
|
|
source TEXT NOT NULL,
|
|
source_id TEXT,
|
|
confidence confidence_level NOT NULL DEFAULT 'unknown',
|
|
opening_hours TEXT,
|
|
last_seen_at TIMESTAMPTZ,
|
|
last_confirmed_by_user_at TIMESTAMPTZ,
|
|
tags JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX pois_category_idx ON pois(category);
|
|
CREATE INDEX pois_location_idx ON pois USING GIST(location);
|
|
CREATE UNIQUE INDEX pois_source_source_id_idx ON pois(source, source_id) WHERE source_id IS NOT NULL;
|
|
|
|
CREATE TABLE route_pois (
|
|
route_id UUID NOT NULL REFERENCES routes(id) ON DELETE CASCADE,
|
|
poi_id UUID NOT NULL REFERENCES pois(id) ON DELETE CASCADE,
|
|
meters_from_start INTEGER,
|
|
distance_from_route_m INTEGER,
|
|
detour_distance_m INTEGER,
|
|
relevance_score NUMERIC(5,2),
|
|
PRIMARY KEY (route_id, poi_id)
|
|
);
|
|
|
|
CREATE TABLE stages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
route_id UUID NOT NULL REFERENCES routes(id) ON DELETE CASCADE,
|
|
day_index INTEGER NOT NULL,
|
|
start_m INTEGER NOT NULL,
|
|
end_m INTEGER NOT NULL,
|
|
distance_m INTEGER NOT NULL,
|
|
ascent_m INTEGER DEFAULT 0,
|
|
descent_m INTEGER DEFAULT 0,
|
|
endpoint GEOGRAPHY(Point, 4326) NOT NULL,
|
|
sleep_candidates JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
water_pois JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
food_pois JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
repair_pois JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
bailout_pois JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
warnings JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
plan_b JSONB,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE service_gaps (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
route_id UUID NOT NULL REFERENCES routes(id) ON DELETE CASCADE,
|
|
category poi_category NOT NULL,
|
|
start_m INTEGER NOT NULL,
|
|
end_m INTEGER NOT NULL,
|
|
distance_m INTEGER NOT NULL,
|
|
severity warning_severity NOT NULL,
|
|
explanation TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE rule_cards (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
rule_type TEXT NOT NULL,
|
|
jurisdiction TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
confidence confidence_level NOT NULL DEFAULT 'unknown',
|
|
summary TEXT NOT NULL,
|
|
source_url TEXT,
|
|
source_name TEXT,
|
|
last_reviewed_at DATE,
|
|
valid_from DATE,
|
|
valid_to DATE,
|
|
geometry GEOGRAPHY(Geometry, 4326),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX rule_cards_geom_idx ON rule_cards USING GIST(geometry);
|
|
|
|
CREATE TABLE local_reports (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
route_id UUID REFERENCES routes(id) ON DELETE SET NULL,
|
|
route_segment_id UUID REFERENCES route_segments(id) ON DELETE SET NULL,
|
|
report_type TEXT NOT NULL,
|
|
location GEOGRAPHY(Point, 4326) NOT NULL,
|
|
meters_from_start INTEGER,
|
|
observed_at TIMESTAMPTZ NOT NULL,
|
|
details TEXT,
|
|
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
trust_score NUMERIC(5,2) DEFAULT 0,
|
|
expires_at TIMESTAMPTZ,
|
|
moderation_status TEXT NOT NULL DEFAULT 'pending',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX local_reports_location_idx ON local_reports USING GIST(location);
|
|
CREATE INDEX local_reports_type_idx ON local_reports(report_type);
|
|
|
|
CREATE TABLE offline_packs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
trip_id UUID NOT NULL REFERENCES trips(id) ON DELETE CASCADE,
|
|
route_id UUID NOT NULL REFERENCES routes(id) ON DELETE CASCADE,
|
|
bbox GEOGRAPHY(Polygon, 4326),
|
|
route_buffer_km NUMERIC(6,2) NOT NULL DEFAULT 5,
|
|
included_layers JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
data_versions JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
files JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
warnings JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
status TEXT NOT NULL DEFAULT 'queued',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
expires_at TIMESTAMPTZ
|
|
);
|