fifteen/shared/src/types.ts

147 lines
4.5 KiB
TypeScript

import type { CategoryId, TravelMode } from "./osm-tags.js";
// ─── Cities ──────────────────────────────────────────────────────────────────
export type CityStatus = "pending" | "processing" | "ready" | "error" | "empty";
export interface City {
slug: string;
name: string;
countryCode: string;
geofabrikUrl: string;
bbox: [number, number, number, number]; // [minLng, minLat, maxLng, maxLat]
boundary?: object | null;
status: CityStatus;
lastIngested: string | null;
}
// ─── POIs ────────────────────────────────────────────────────────────────────
export interface Poi {
osmId: string;
osmType: "N" | "W" | "R";
category: CategoryId;
subcategory: string;
name: string | null;
lng: number;
lat: number;
}
// ─── Grid / Heatmap ──────────────────────────────────────────────────────────
export interface GridCell {
gridX: number;
gridY: number;
lng: number;
lat: number;
score: number;
categoryScores: Partial<Record<CategoryId, number>>;
}
export interface HeatmapPayload {
citySlug: string;
travelMode: TravelMode;
thresholdMin: number;
weights: Record<CategoryId, number>;
gridSpacingM: number;
cells: GridCell[];
generatedAt: string;
}
// ─── Stats ───────────────────────────────────────────────────────────────────
export interface CategoryStats {
category: CategoryId;
poiCount: number;
p10: number;
p25: number;
p50: number;
p75: number;
p90: number;
coveragePct: number;
}
export interface CityStats {
citySlug: string;
totalPois: number;
gridPointCount: number;
categoryStats: CategoryStats[];
}
// ─── Isochrones ──────────────────────────────────────────────────────────────
export interface IsochroneRequest {
lng: number;
lat: number;
travelMode: TravelMode;
contourMinutes: number[];
}
// ─── Job / Pipeline ──────────────────────────────────────────────────────────
export interface JobProgress {
stage: string;
pct: number;
message: string;
bytesDownloaded?: number;
totalBytes?: number;
}
export type JobState =
| "waiting"
| "active"
| "completed"
| "failed"
| "delayed"
| "waiting-children";
export interface JobSummary {
id: string;
type: string;
citySlug: string;
state: JobState;
progress: JobProgress | null;
failedReason: string | null;
createdAt: number;
finishedAt: number | null;
duration: number | null;
}
// ─── SSE Events ──────────────────────────────────────────────────────────────
export type RoutingDetail = Record<string, { done: number; total: number }>;
export type SSEEvent =
| { type: "progress"; stage: string; pct: number; message: string; bytesDownloaded?: number; totalBytes?: number; routingDetail?: RoutingDetail }
| { type: "completed"; jobId: string }
| { type: "failed"; jobId: string; error: string }
| { type: "heartbeat" };
// ─── Geofabrik ───────────────────────────────────────────────────────────────
export interface GeofabrikFeature {
type: "Feature";
properties: {
id: string;
parent?: string;
name: string;
urls: {
pbf: string;
"pbf.md5": string;
};
poly?: string;
};
geometry: GeoJSON.Polygon | GeoJSON.MultiPolygon | null;
}
export interface GeofabrikIndex {
type: "FeatureCollection";
features: GeofabrikFeature[];
}
// ─── API helpers ─────────────────────────────────────────────────────────────
export interface ApiError {
error: string;
code: string;
}