Alpha stage commit
This commit is contained in:
77
schemas/bikepacking_profile.schema.json
Normal file
77
schemas/bikepacking_profile.schema.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "Bikepacking Profile",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"label",
|
||||
"thresholds",
|
||||
"weights"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"thresholds": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"targetDistanceKm": {
|
||||
"type": "number"
|
||||
},
|
||||
"minDistanceKm": {
|
||||
"type": "number"
|
||||
},
|
||||
"maxDistanceKm": {
|
||||
"type": "number"
|
||||
},
|
||||
"maxLoadedGradePercent": {
|
||||
"type": "number"
|
||||
},
|
||||
"maxWaterGapKm": {
|
||||
"type": "number"
|
||||
},
|
||||
"maxFoodGapKm": {
|
||||
"type": "number"
|
||||
},
|
||||
"maxBailoutGapKm": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"weights": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"distance": {
|
||||
"type": "number"
|
||||
},
|
||||
"climb": {
|
||||
"type": "number"
|
||||
},
|
||||
"steepness": {
|
||||
"type": "number"
|
||||
},
|
||||
"poorSurface": {
|
||||
"type": "number"
|
||||
},
|
||||
"trafficStress": {
|
||||
"type": "number"
|
||||
},
|
||||
"accessUncertainty": {
|
||||
"type": "number"
|
||||
},
|
||||
"hikeABikeRisk": {
|
||||
"type": "number"
|
||||
},
|
||||
"officialCycleRouteBonus": {
|
||||
"type": "number"
|
||||
},
|
||||
"confirmedGoodBonus": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
196
schemas/database.sql
Normal file
196
schemas/database.sql
Normal file
@@ -0,0 +1,196 @@
|
||||
-- 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
|
||||
);
|
||||
150
schemas/domain_types.ts
Normal file
150
schemas/domain_types.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
export type Coordinate = {
|
||||
lat: number;
|
||||
lon: number;
|
||||
elevationM?: number;
|
||||
};
|
||||
|
||||
export type BikepackingProfileId =
|
||||
| 'loaded_gravel'
|
||||
| 'hardtail_bikepacking'
|
||||
| 'road_touring'
|
||||
| 'ebikepacking'
|
||||
| 'beginner_safe';
|
||||
|
||||
export type SleepPreference =
|
||||
| 'official_campsites'
|
||||
| 'mixed_budget'
|
||||
| 'indoor_only'
|
||||
| 'wild_where_legal'
|
||||
| 'any_safe_option';
|
||||
|
||||
export type PoiCategory =
|
||||
| 'water'
|
||||
| 'food'
|
||||
| 'sleep'
|
||||
| 'repair'
|
||||
| 'bailout'
|
||||
| 'charging'
|
||||
| 'emergency';
|
||||
|
||||
export type Confidence =
|
||||
| 'known_good'
|
||||
| 'high'
|
||||
| 'medium'
|
||||
| 'low'
|
||||
| 'unknown'
|
||||
| 'restricted'
|
||||
| 'conflicting';
|
||||
|
||||
export type WarningSeverity = 'info' | 'notice' | 'warning' | 'critical';
|
||||
|
||||
export type TripRequest = {
|
||||
start: Coordinate;
|
||||
end: Coordinate;
|
||||
viaPoints?: Coordinate[];
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
profile: BikepackingProfileId;
|
||||
sleepPreference: SleepPreference;
|
||||
dailyDistanceKm: { min: number; target: number; max: number };
|
||||
dailyAscentM?: { target?: number; max?: number };
|
||||
maxLoadedGradePercent?: number;
|
||||
preferredUnpavedPercent?: { min?: number; max?: number };
|
||||
waterGapMaxKm?: number;
|
||||
foodGapMaxKm?: number;
|
||||
requireBailoutEveryKm?: number;
|
||||
};
|
||||
|
||||
export type RouteSegment = {
|
||||
id: string;
|
||||
geometry: Coordinate[];
|
||||
distanceM: number;
|
||||
ascentM?: number;
|
||||
descentM?: number;
|
||||
avgGradePercent?: number;
|
||||
maxGradePercent?: number;
|
||||
surface?: string;
|
||||
smoothness?: string;
|
||||
highway?: string;
|
||||
bicycleAccess?: string;
|
||||
legalAccessConfidence: Confidence;
|
||||
trafficStress?: number;
|
||||
officialCycleRoute?: boolean;
|
||||
protectedAreaOverlap?: boolean;
|
||||
communityScore?: number;
|
||||
sourceConfidence: Confidence;
|
||||
};
|
||||
|
||||
export type RouteWarning = {
|
||||
code: string;
|
||||
severity: WarningSeverity;
|
||||
title: string;
|
||||
message: string;
|
||||
metersFromStart?: number;
|
||||
segmentId?: string;
|
||||
};
|
||||
|
||||
export type RoutePlan = {
|
||||
id: string;
|
||||
tripId: string;
|
||||
geometry: Coordinate[];
|
||||
distanceM: number;
|
||||
ascentM: number;
|
||||
descentM: number;
|
||||
segments: RouteSegment[];
|
||||
surfaceBreakdown: Record<string, number>;
|
||||
suitabilityScore: number;
|
||||
warnings: RouteWarning[];
|
||||
provider: string;
|
||||
};
|
||||
|
||||
export type Poi = {
|
||||
id: string;
|
||||
category: PoiCategory;
|
||||
name: string;
|
||||
location: Coordinate;
|
||||
source: string;
|
||||
confidence: Confidence;
|
||||
distanceFromRouteM?: number;
|
||||
metersFromStart?: number;
|
||||
detourDistanceM?: number;
|
||||
openingHours?: string;
|
||||
lastConfirmedAt?: string;
|
||||
tags?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type StagePlan = {
|
||||
id: string;
|
||||
routeId: string;
|
||||
dayIndex: number;
|
||||
startMeters: number;
|
||||
endMeters: number;
|
||||
distanceM: number;
|
||||
ascentM?: number;
|
||||
descentM?: number;
|
||||
expectedRideTimeMinutes?: { low: number; high: number };
|
||||
sleepCandidates: Poi[];
|
||||
waterPois: Poi[];
|
||||
foodPois: Poi[];
|
||||
repairPois: Poi[];
|
||||
bailoutPois: Poi[];
|
||||
warnings: RouteWarning[];
|
||||
planB?: {
|
||||
earlierEndMeters?: number;
|
||||
laterEndMeters?: number;
|
||||
reason: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type OfflinePackManifest = {
|
||||
packId: string;
|
||||
tripId: string;
|
||||
routeId: string;
|
||||
createdAt: string;
|
||||
bbox: [number, number, number, number];
|
||||
routeBufferKm: number;
|
||||
includedLayers: string[];
|
||||
dataVersions: Record<string, string>;
|
||||
files: Array<{ type: string; url?: string; path?: string; bytes?: number }>;
|
||||
warnings: RouteWarning[];
|
||||
};
|
||||
423
schemas/openapi.yaml
Normal file
423
schemas/openapi.yaml
Normal file
@@ -0,0 +1,423 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: BikepackPilot API
|
||||
version: 0.1.0
|
||||
description: MVP API contract for bikepacking route planning, staging, POI corridor, offline packs, exports, and local reports.
|
||||
servers:
|
||||
- url: http://localhost:8000
|
||||
paths:
|
||||
/v1/trips:
|
||||
post:
|
||||
summary: Create trip
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TripRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Trip created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Trip'
|
||||
/v1/routes/plan:
|
||||
post:
|
||||
summary: Plan a bikepacking route
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RoutePlanRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Route plan
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RoutePlan'
|
||||
/v1/stages/plan:
|
||||
post:
|
||||
summary: Split a route into daily stages
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StagePlanRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Stage plans
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
stages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/StagePlan'
|
||||
/v1/routes/{routeId}/pois:
|
||||
get:
|
||||
summary: Get route-corridor POIs
|
||||
parameters:
|
||||
- in: path
|
||||
name: routeId
|
||||
required: true
|
||||
schema: { type: string }
|
||||
- in: query
|
||||
name: category
|
||||
schema: { $ref: '#/components/schemas/PoiCategory' }
|
||||
- in: query
|
||||
name: buffer_km
|
||||
schema: { type: number, default: 5 }
|
||||
responses:
|
||||
'200':
|
||||
description: POIs
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
pois:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Poi'
|
||||
attribution:
|
||||
type: array
|
||||
items: { type: string }
|
||||
/v1/routes/{routeId}/gaps:
|
||||
get:
|
||||
summary: Get critical service gaps
|
||||
parameters:
|
||||
- in: path
|
||||
name: routeId
|
||||
required: true
|
||||
schema: { type: string }
|
||||
responses:
|
||||
'200':
|
||||
description: Service gaps
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
gaps:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ServiceGap'
|
||||
/v1/rules:
|
||||
get:
|
||||
summary: Get local rule cards
|
||||
parameters:
|
||||
- in: query
|
||||
name: bbox
|
||||
schema:
|
||||
type: string
|
||||
description: minLon,minLat,maxLon,maxLat
|
||||
- in: query
|
||||
name: route_id
|
||||
schema: { type: string }
|
||||
responses:
|
||||
'200':
|
||||
description: Rule cards
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RuleCard'
|
||||
disclaimer:
|
||||
type: string
|
||||
/v1/offline-packs:
|
||||
post:
|
||||
summary: Create offline pack manifest
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [tripId, routeId]
|
||||
properties:
|
||||
tripId: { type: string }
|
||||
routeId: { type: string }
|
||||
routeBufferKm: { type: number, default: 5 }
|
||||
responses:
|
||||
'200':
|
||||
description: Offline pack manifest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OfflinePackManifest'
|
||||
/v1/routes/{routeId}/export:
|
||||
get:
|
||||
summary: Export route
|
||||
parameters:
|
||||
- in: path
|
||||
name: routeId
|
||||
required: true
|
||||
schema: { type: string }
|
||||
- in: query
|
||||
name: format
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum: [gpx, tcx, fit]
|
||||
responses:
|
||||
'200':
|
||||
description: Route export file
|
||||
content:
|
||||
application/gpx+xml:
|
||||
schema: { type: string }
|
||||
application/json:
|
||||
schema: { type: object }
|
||||
/v1/reports:
|
||||
post:
|
||||
summary: Submit local report
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LocalReportCreate'
|
||||
responses:
|
||||
'201':
|
||||
description: Report submitted
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LocalReport'
|
||||
components:
|
||||
schemas:
|
||||
Coordinate:
|
||||
type: object
|
||||
required: [lat, lon]
|
||||
properties:
|
||||
lat: { type: number }
|
||||
lon: { type: number }
|
||||
elevationM:
|
||||
type: number
|
||||
description: Optional elevation in meters above sea level when supplied by the active provider.
|
||||
TripRequest:
|
||||
type: object
|
||||
required: [start, end, profile, sleepPreference, dailyDistanceKm]
|
||||
properties:
|
||||
start: { $ref: '#/components/schemas/Coordinate' }
|
||||
end: { $ref: '#/components/schemas/Coordinate' }
|
||||
viaPoints:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/Coordinate' }
|
||||
maxItems: 24
|
||||
startDate: { type: string, format: date }
|
||||
endDate: { type: string, format: date }
|
||||
profile: { type: string }
|
||||
sleepPreference: { type: string }
|
||||
dailyDistanceKm:
|
||||
type: object
|
||||
required: [min, target, max]
|
||||
properties:
|
||||
min: { type: number }
|
||||
target: { type: number }
|
||||
max: { type: number }
|
||||
waterGapMaxKm: { type: number }
|
||||
foodGapMaxKm: { type: number }
|
||||
Trip:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
title: { type: string }
|
||||
request: { $ref: '#/components/schemas/TripRequest' }
|
||||
RoutePlanRequest:
|
||||
type: object
|
||||
required: [tripRequest]
|
||||
properties:
|
||||
tripRequest: { $ref: '#/components/schemas/TripRequest' }
|
||||
provider: { type: string, default: mock }
|
||||
RoutePlan:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
tripId: { type: string }
|
||||
geometry:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/Coordinate' }
|
||||
distanceM: { type: integer }
|
||||
ascentM: { type: integer }
|
||||
descentM: { type: integer }
|
||||
provider: { type: string }
|
||||
segments:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/RouteSegment' }
|
||||
surfaceBreakdown:
|
||||
type: object
|
||||
additionalProperties: { type: number }
|
||||
suitabilityScore: { type: number }
|
||||
warnings:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/RouteWarning' }
|
||||
attribution:
|
||||
type: array
|
||||
items: { type: string }
|
||||
RouteSegment:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
geometry:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/Coordinate' }
|
||||
distanceM: { type: integer }
|
||||
startMeters: { type: integer }
|
||||
endMeters: { type: integer }
|
||||
ascentM: { type: integer }
|
||||
descentM: { type: integer }
|
||||
avgGradePercent: { type: number }
|
||||
maxGradePercent: { type: number }
|
||||
surface: { type: string }
|
||||
smoothness: { type: string }
|
||||
highway: { type: string }
|
||||
bicycleAccess: { type: string }
|
||||
legalAccessConfidence: { type: string }
|
||||
trafficStress: { type: number }
|
||||
officialCycleRoute: { type: boolean }
|
||||
protectedAreaOverlap: { type: boolean }
|
||||
sourceConfidence: { type: string }
|
||||
StagePlanRequest:
|
||||
type: object
|
||||
required: [routeId, tripRequest]
|
||||
properties:
|
||||
routeId: { type: string }
|
||||
tripRequest: { $ref: '#/components/schemas/TripRequest' }
|
||||
StagePlan:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
dayIndex: { type: integer }
|
||||
startMeters: { type: integer }
|
||||
endMeters: { type: integer }
|
||||
distanceM: { type: integer }
|
||||
ascentM: { type: integer }
|
||||
descentM: { type: integer }
|
||||
expectedRideTimeMinutes:
|
||||
type: object
|
||||
properties:
|
||||
low: { type: integer }
|
||||
high: { type: integer }
|
||||
sleepCandidates:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/Poi' }
|
||||
waterPois:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/Poi' }
|
||||
foodPois:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/Poi' }
|
||||
repairPois:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/Poi' }
|
||||
bailoutPois:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/Poi' }
|
||||
warnings:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/RouteWarning' }
|
||||
planB:
|
||||
type: object
|
||||
properties:
|
||||
earlierEndMeters: { type: integer }
|
||||
laterEndMeters: { type: integer }
|
||||
reason: { type: string }
|
||||
PoiCategory:
|
||||
type: string
|
||||
enum: [water, food, sleep, repair, bailout, charging, emergency]
|
||||
Poi:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
category: { $ref: '#/components/schemas/PoiCategory' }
|
||||
name: { type: string }
|
||||
location: { $ref: '#/components/schemas/Coordinate' }
|
||||
source: { type: string }
|
||||
confidence: { type: string }
|
||||
metersFromStart: { type: integer }
|
||||
distanceFromRouteM: { type: integer }
|
||||
detourDistanceM: { type: integer }
|
||||
RouteWarning:
|
||||
type: object
|
||||
properties:
|
||||
code: { type: string }
|
||||
severity: { type: string, enum: [info, notice, warning, critical] }
|
||||
title: { type: string }
|
||||
message: { type: string }
|
||||
metersFromStart: { type: integer }
|
||||
segmentId: { type: string }
|
||||
ServiceGap:
|
||||
type: object
|
||||
properties:
|
||||
category: { $ref: '#/components/schemas/PoiCategory' }
|
||||
startM: { type: integer }
|
||||
endM: { type: integer }
|
||||
distanceM: { type: integer }
|
||||
severity: { type: string }
|
||||
explanation: { type: string }
|
||||
RuleCard:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
ruleType: { type: string }
|
||||
jurisdiction: { type: string }
|
||||
status: { type: string }
|
||||
confidence: { type: string }
|
||||
summary: { type: string }
|
||||
sourceUrl: { type: string }
|
||||
lastReviewedAt: { type: string, format: date }
|
||||
OfflinePackManifest:
|
||||
type: object
|
||||
properties:
|
||||
packId: { type: string }
|
||||
tripId: { type: string }
|
||||
routeId: { type: string }
|
||||
createdAt: { type: string, format: date-time }
|
||||
expiresAt: { type: string, format: date-time }
|
||||
bbox:
|
||||
type: array
|
||||
minItems: 4
|
||||
maxItems: 4
|
||||
items: { type: number }
|
||||
routeBufferKm: { type: number }
|
||||
includedLayers:
|
||||
type: array
|
||||
items: { type: string }
|
||||
dataVersions: { type: object }
|
||||
files:
|
||||
type: array
|
||||
items: { type: object }
|
||||
warnings:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/RouteWarning' }
|
||||
LocalReportCreate:
|
||||
type: object
|
||||
required: [reportType, location, observedAt]
|
||||
properties:
|
||||
reportType: { type: string }
|
||||
location: { $ref: '#/components/schemas/Coordinate' }
|
||||
routeId: { type: string }
|
||||
metersFromStart: { type: integer }
|
||||
observedAt: { type: string, format: date-time }
|
||||
details: { type: string }
|
||||
payload: { type: object }
|
||||
offlineClientId: { type: string }
|
||||
LocalReport:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/LocalReportCreate'
|
||||
- type: object
|
||||
properties:
|
||||
id: { type: string }
|
||||
moderationStatus: { type: string }
|
||||
trustScore: { type: number }
|
||||
queuedOffline: { type: boolean }
|
||||
Reference in New Issue
Block a user