← back to Charge And Explore

backend/src/providers/provider.ts

83 lines

// Provider-adapter interfaces (TypeScript port of the Swift protocols).
//
// The whole external-data strategy hinges on this seam: NO provider secret ever
// reaches the iOS binary — the app calls only the first-party backend, and the
// backend fans out to whichever charger/place/routing provider is licensed.
// Swapping Apple MapKit -> Google Places, or NREL -> HERE, must not require a
// product rewrite. Every normalized record MUST keep its provenance.

import type { GeoPoint, PlaceCategory } from "../core/types.ts";

/** Field-level provenance carried on every canonical value (legal requirement). */
export interface Provenance {
  providerId: string;
  providerRecordId: string;
  sourceRetrievedAt: string; // ISO-8601
  sourceEffectiveAt?: string;
  lastVerifiedAt?: string;
  confidence: number; // 0–1
  licenseCode: string;
  attributionText: string;
  redistributionAllowed: boolean;
  cacheExpiresAt?: string;
}

export interface ProviderStation {
  id: string;
  name: string;
  location: GeoPoint;
  maxKw?: number;
  stallCount?: number;
  network?: string;
  provenance: Provenance;
}

export interface ProviderPlace {
  id: string;
  name: string;
  location: GeoPoint;
  categories: PlaceCategory[];
  provenance: Provenance;
}

export interface RouteCorridor {
  polyline: GeoPoint[];
  bufferMeters: number;
}

export interface ChargingStationProvider {
  stationsNear(point: GeoPoint, radiusMeters: number): Promise<ProviderStation[]>;
  stationsAlong(corridor: RouteCorridor): Promise<ProviderStation[]>;
}

export interface PlacesProvider {
  placesNear(
    point: GeoPoint,
    categories: Set<PlaceCategory>,
    radiusMeters: number,
  ): Promise<ProviderPlace[]>;
}

export interface RouteRequest {
  origin: GeoPoint;
  destination: GeoPoint;
}

export interface RouteResult {
  corridor: RouteCorridor;
  distanceMeters: number;
  durationMinutes: number;
}

export interface WalkingRoute {
  durationMinutes: number;
  distanceMeters: number;
  /** True when a real pedestrian path exists (not a freeway-severed straight line). */
  isRoutable: boolean;
}

export interface RoutingProvider {
  route(request: RouteRequest): Promise<RouteResult>;
  walkingRoute(from: GeoPoint, to: GeoPoint): Promise<WalkingRoute>;
}