Files
suaveplan/openspec/archive/2026-05-25-enterprise-ports/design.md
2026-05-27 23:29:53 +01:00

152 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Design: enterprise-ports
## Module layout
```
packages/geospatial/logistics/ports/
package.json
tsconfig.lib.json
vite.config.ts
vitest.config.ts
README.md
src/
index.ts
constants/index.ts # EARTH_RADIUS_NM, DEFAULT_SPEED_KNOTS, VESSEL_CLASS_SPEEDS
types/
port.types.ts
port.types.md
error-codes/
error-codes.ts
error-codes.md
error-codes.test.ts
index.ts
schemas/
schemas.ts
schemas.md
schemas.test.ts
index.ts
otel/
otel.ts
otel.md
otel.test.ts
index.ts
data/
sea-ports-asia/
sea-ports-europe/
sea-ports-americas/
sea-ports-africa-mena/
sea-ports-oceania/
sea-ports/
airports/
rail-terminals/
all-ports/
shipping-lanes/
distance/
distance.ts # great-circle wrapper, nm/km conversions
distance.md
distance.test.ts
index.ts
find-port/
find-port.ts
find-port.md
find-port.test.ts
index.ts
nearest-port/
nearest-port.ts
nearest-port.md
nearest-port.test.ts
index.ts
spatial-index/
spatial-index.ts
spatial-index.md
spatial-index.test.ts
index.ts
lane-lookup/
lane-lookup.ts
lane-lookup.md
lane-lookup.test.ts
index.ts
lane-distance/
lane-distance.ts
lane-distance.md
lane-distance.test.ts
index.ts
lane-progress/
lane-progress.ts
lane-progress.md
lane-progress.test.ts
index.ts
eta/
eta.ts
eta.md
eta.test.ts
index.ts
property.test.ts
```
## Distance helper
`distanceNm(a, b)` and `distanceKm(a, b)` wrap
`@suaveplan/geo`'s `greatCircleDistance` against a WGS84-radius
spherical earth (semi-major axis 6_371_000 m) via `defineEllipsoid`. The
genesis `greatCircleDistance` returns metres by default; this package
converts via the geo `convertDistance` utility. We bind a single
`EARTH_SPHERE` ellipsoid once at module load — no allocation per call.
## Error mapping
Each main-branch `new PortError("msg", "CODE")` call maps to a typed
subclass:
| main code | genesis class |
|---|---|
| `NO_PORTS` | `PortsNoPortsError` |
| `INVALID_RADIUS` | `PortsInvalidRadiusError` |
| `INSUFFICIENT_WAYPOINTS` | `PortsInsufficientWaypointsError` |
| `INVALID_SPEED` | `PortsInvalidSpeedError` |
| `MISSING_SPEED` | `PortsMissingSpeedError` |
| _(new)_ | `PortsPortNotFoundError` — strict variant of `findPort` |
| _(new)_ | `PortsLaneNotFoundError` — strict variant of `getShippingLane` |
| _(new)_ | `PortsInvalidLaneError` — Zod schema rejected |
All extend `PortsError extends SuaveplanError<PortsCode>` with i18n
message keys.
## Spatial index
`PortSpatialIndex` keys ports by `floor(latitude / 5)` and
`floor(longitude / 5)` into a `Map<string, Port[]>`. Query for a
coordinate fetches the 3×3 cell neighbourhood and runs exact great-circle
distance on the candidate set. For ~120 curated ports this is overkill in
absolute terms but keeps the public API stable as the dataset grows.
## OTel instruments
Module-scope meter `@suaveplan/ports@0.1.0`:
- Counter `ports.lookup_total` — labels `kind=findPort|byCountry|byType|search`.
- Counter `ports.lane_lookup_total` — labels `mode=sea|rail|air|all`.
- Counter `ports.eta_total` — labels `confidence=high|medium|low`.
- Histogram `ports.find_nearest_duration_ms`.
- Histogram `ports.lane_distance_duration_ms`.
- Histogram `ports.eta_duration_ms`.
`refreshPortsMeters()` re-resolves every instrument against the current
global `MeterProvider` after tests wire in-memory exporters.
## Clock injection
`estimateETA(coord, lane, speedKnotsOverride?, opts?)`:
`opts = { departureTime?: Date; clock?: Clock }`. If `departureTime` is
omitted and `clock` is supplied → `new Date(clock.now())`; else `new
Date()`. Tests pass a `FakeClock` from `@suaveplan/testing` for
determinism.
## Data shape stability
The migrated dataset preserves every record from the main branch
(coordinates, throughputs, vessel limits, IATA/UIC codes) — adding any
new fields would break consumer expectations. The data is split into
per-region files exactly mirroring the main layout so future curated
updates can be diffed cleanly.