Files
2026-05-27 23:29:53 +01:00

5.3 KiB
Raw Permalink Blame History

OpenSpec Change: enterprise-ports

  • Change ID: enterprise-ports
  • Package: @suaveplan/ports
  • Category / plugin: geospatial / logistics
  • Target path: packages/geospatial/logistics/ports/
  • Status: PROPOSED
  • Date: 2026-05-25
  • Reference spec: /mnt/s/web/suaveplan/enterprise/ports.md
  • Main-branch source: /mnt/s/web/suaveplan/packages/universal/ports

Why

@suaveplan/ports is the canonical universal-tier global port database on the main branch. It pairs a curated dataset of sea ports, airports, and rail terminals (UN/LOCODE addressed) with shipping-lane waypoint graphs and ETA estimation. The genesis branch starts empty; landing this package unblocks every logistics/shipping consumer (port-tracking, logistics, customs, procurement, warehouse-ops, and the matching api-plugin-* packages) which all transitively depend on port lookups and lane-based routing.

The reference implementation on main shipped at 95%+ coverage but without the cross-cutting concerns mandated by the enterprise checklist in enterprise/ports.md: stable typed error codes via @suaveplan/error, Zod schemas at the public boundary, OpenTelemetry instrumentation, injectable Clock, and 100% test coverage. This change rebuilds the package from scratch on genesis with those additions while preserving the full main-branch public surface so direct consumers do not need migration shims.

What changes

Preserved (from main-branch surface)

  • Curated port dataset split by region: seaPortsAsia, seaPortsEurope, seaPortsAmericas, seaPortsAfricaMena, seaPortsOceania, aggregated into seaPorts; plus airports and railTerminals. allPorts combines every category.
  • Curated shippingLanes waypoint graph spanning the major global routes (Asia–Europe via Suez, Asia–Europe via Cape, Transpacific, Transatlantic, rail and air lanes).
  • Port lookups: findPort(locode), findPortsByCountry(country, ports?), findPortsByType(type, ports?), searchPorts(query, ports?).
  • Spatial lookups: findNearestPort(coord, ports?), findPortsInRadius(coord, radiusKm, ports?).
  • Lane lookups: getShippingLane(id), getShippingLanesByMode(mode, lanes?), getAllShippingLanes().
  • Lane distance: estimateLaneDistance(lane), cumulativeDistances(lane) (nautical miles, great-circle).
  • Lane progress: findPositionOnLane(coord, lane) → { progress, remainingDistanceNm, distanceTravelledNm, nearestWaypointIndex }.
  • ETA: estimateETA(currentCoord, lane, avgSpeedKnots?, departureTime?) → { remainingDistanceNm, etaHours, etaDate, confidence }.
  • Types: Port, PortType, PortFacility, LaneMode, ShippingLane, LaneProgress, ETAEstimate, NearestPortResult.

Added (genesis enterprise additions)

  1. Typed error registry — PORTS_CODES via defineCodes("PORTS", ["NO_PORTS", "INVALID_RADIUS", "INSUFFICIENT_WAYPOINTS", "INVALID_SPEED", "MISSING_SPEED", "INVALID_LANE", "PORT_NOT_FOUND", "LANE_NOT_FOUND"]). Each code surfaces a dedicated PortsError subclass extending SuaveplanError.
  2. Zod schemas for Port, ShippingLane, SphericalCoord, LaneProgress, ETAEstimate, NearestPortResult exposed under ./schemas/schemas for boundary validation by API consumers.
  3. Injectable Clock — estimateETA(coord, lane, speed?, opts?) now accepts { departureTime?, clock? } so callers (jobs, simulation, tests) can supply a deterministic clock. Date.now() is never read at module scope.
  4. Vessel-class speed defaults — VESSEL_CLASS_SPEEDS constants for ulcv, panamax, feeder, coastal, freighter-air, rail-freight used as fallbacks when neither the lane nor caller specifies a speed.
  5. OTel instrumentation — counters ports.lookup_total{kind}, ports.lane_lookup_total{mode}, ports.eta_total{confidence}; histograms ports.find_nearest_duration_ms, ports.lane_distance_duration_ms, ports.eta_duration_ms. refreshPortsMeters() for test isolation.
  6. Spatial index — PortSpatialIndex bucketed by 5-degree lat/lng cells, providing O(neighbours) findNearest/findInRadius instead of scanning all ~120 ports. Used internally by the public functions.
  7. Property-based tests — fast-check invariants for ETA monotonicity (speed↑ ⇒ ETA earlier), distance triangle inequality on great-circle segments, and radius-containment consistency (findPortsInRadius never returns a port whose haversine distance exceeds radiusKm).

Removed

Nothing. The previous proposal's PortDatabase class and JSON-backed "6000-port" dataset are not part of the main-branch surface and were aspirational; this change ships the actual surface main shipped.

Impact

  • Affected specs: new ports capability spec.
  • Affected code: new package at packages/geospatial/logistics/ports/.
  • Downstream: unblocks port-tracking, logistics, customs, procurement, warehouse-ops, and their api-plugin-* companions.
  • Manifest: MANIFEST.md regenerated; STATUS.md flips – → ✅.
  • Category mapping: adds plugin logistics to super-category geospatial in scripts/folder-reorg/assignment.yml (logistics was previously a plugin without a category assignment). The 18 closed super-categories are unchanged.