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>; } export interface HeatmapPayload { citySlug: string; travelMode: TravelMode; thresholdMin: number; weights: Record; 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; 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; }