Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
# `logistics`
Port data and logistics primitives.
<!-- begin:auto-docs -->
## Packages in `logistics`
1 package.
| Package | Description |
|---|---|
| [`@suaveplan/ports`](./ports/README.md) | Global port database with shipping-lane routing, ETA estimation, and UN/LOCODE port lookups for sea ports, airports, and rail terminals. |
<!-- end:auto-docs -->

View File

@@ -0,0 +1 @@
Port data and logistics primitives.

View File

@@ -0,0 +1,104 @@
# @suaveplan/ports - Global port database with shipping-lane routing, ETA estimation, and UN/LOCODE port lookups for sea ports, airports, and rail terminals.
Global port database with shipping-lane routing, ETA estimation, and
UN/LOCODE port lookups for sea ports, airports, and rail terminals.
<!-- begin:auto-docs -->
## Documentation tree
- [airports](./src/data/airports/airports.md) — Curated dataset of major international air-cargo airports, exported as the readonly `airports` array. Each record confo…
- [airports](./src/data/airports/airports.md) — Curated dataset of major international air-cargo airports, exported as the readonly `airports` array. Each record confo…
- [all-ports](./src/data/all-ports/all-ports.md) — Aggregates every curated dataset — sea ports, airports, and rail terminals — into a single readonly `allPorts` array an…
- [all-ports](./src/data/all-ports/all-ports.md) — Aggregates every curated dataset — sea ports, airports, and rail terminals — into a single readonly `allPorts` array an…
- [rail-terminals](./src/data/rail-terminals/rail-terminals.md) — Curated dataset of major rail freight terminals — primarily the inland container hubs on the New Silk Road and Trans-Eu…
- [rail-terminals](./src/data/rail-terminals/rail-terminals.md) — Curated dataset of major rail freight terminals — primarily the inland container hubs on the New Silk Road and Trans-Eu…
- [sea-ports](./src/data/sea-ports/sea-ports.md) — Aggregates every regional sea-port dataset (`seaPortsAsia`, `seaPortsEurope`, `seaPortsAmericas`, `seaPortsAfricaMena`,…
- [sea-ports](./src/data/sea-ports/sea-ports.md) — Aggregates every regional sea-port dataset (`seaPortsAsia`, `seaPortsEurope`, `seaPortsAmericas`, `seaPortsAfricaMena`,…
- [sea-ports-africa-mena](./src/data/sea-ports-africa-mena/sea-ports-africa-mena.md) — Curated dataset of major sea ports across Africa and the Middle East / North Africa region, exported as the readonly `s…
- [sea-ports-africa-mena](./src/data/sea-ports-africa-mena/sea-ports-africa-mena.md) — Curated dataset of major sea ports across Africa and the Middle East / North Africa region, exported as the readonly `s…
- [sea-ports-americas](./src/data/sea-ports-americas/sea-ports-americas.md) — Curated dataset of major sea ports across the Americas, exported as the readonly `seaPortsAmericas` array. Every record…
- [sea-ports-americas](./src/data/sea-ports-americas/sea-ports-americas.md) — Curated dataset of major sea ports across the Americas, exported as the readonly `seaPortsAmericas` array. Every record…
- [sea-ports-asia](./src/data/sea-ports-asia/sea-ports-asia.md) — Curated dataset of major sea ports across Asia, exported as the readonly `seaPortsAsia` array. Every record conforms to…
- [sea-ports-asia](./src/data/sea-ports-asia/sea-ports-asia.md) — Curated dataset of major sea ports across Asia, exported as the readonly `seaPortsAsia` array. Every record conforms to…
- [sea-ports-europe](./src/data/sea-ports-europe/sea-ports-europe.md) — Curated dataset of major sea ports across Europe, exported as the readonly `seaPortsEurope` array. Every record conform…
- [sea-ports-europe](./src/data/sea-ports-europe/sea-ports-europe.md) — Curated dataset of major sea ports across Europe, exported as the readonly `seaPortsEurope` array. Every record conform…
- [sea-ports-oceania](./src/data/sea-ports-oceania/sea-ports-oceania.md) — Curated dataset of major sea ports across Oceania, exported as the readonly `seaPortsOceania` array. Every record confo…
- [sea-ports-oceania](./src/data/sea-ports-oceania/sea-ports-oceania.md) — Curated dataset of major sea ports across Oceania, exported as the readonly `seaPortsOceania` array. Every record confo…
- [shipping-lanes](./src/data/shipping-lanes/shipping-lanes.md) — Curated registry of major global shipping lanes — Asia–Europe via Suez, Asia–Europe via Cape, Transpacific, Transatlant…
- [shipping-lanes](./src/data/shipping-lanes/shipping-lanes.md) — Curated registry of major global shipping lanes — Asia–Europe via Suez, Asia–Europe via Cape, Transpacific, Transatlant…
- [distance](./src/distance/distance.md) — Wraps `@suaveplan/geo`'s `greatCircleDistance` against a sphere whose radius matches the IUGG mean Earth radius (6 371…
- [distance](./src/distance/distance.md) — Wraps `@suaveplan/geo`'s `greatCircleDistance` against a sphere whose radius matches the IUGG mean Earth radius (6 371…
- [error-codes](./src/error-codes/error-codes.md) — Declares the eight stable error codes emitted by `@suaveplan/ports` and the matching typed `PortsError` subclasses. Eve…
- [error-codes](./src/error-codes/error-codes.md) — Declares the eight stable error codes emitted by `@suaveplan/ports` and the matching typed `PortsError` subclasses. Eve…
- [eta](./src/eta/eta.md) — Estimates the time of arrival for a vessel travelling along a `ShippingLane`. Resolves an effective cruise speed from (…
- [eta](./src/eta/eta.md) — Estimates the time of arrival for a vessel travelling along a `ShippingLane`. Resolves an effective cruise speed from (…
- [find-port](./src/find-port/find-port.md) — Provides the lookup surface for `@suaveplan/ports`. Every public function consumes the curated `allPorts` registry by d…
- [find-port](./src/find-port/find-port.md) — Provides the lookup surface for `@suaveplan/ports`. Every public function consumes the curated `allPorts` registry by d…
- [lane-distance](./src/lane-distance/lane-distance.md) — Computes great-circle distance metrics for a `ShippingLane`'s waypoint chain. `estimateLaneDistance` sums every segment…
- [lane-distance](./src/lane-distance/lane-distance.md) — Computes great-circle distance metrics for a `ShippingLane`'s waypoint chain. `estimateLaneDistance` sums every segment…
- [lane-lookup](./src/lane-lookup/lane-lookup.md) — Public lookup surface for the curated `shippingLanes` registry. Mirrors the port-lookup module in style: an O(1) `Map`…
- [lane-lookup](./src/lane-lookup/lane-lookup.md) — Public lookup surface for the curated `shippingLanes` registry. Mirrors the port-lookup module in style: an O(1) `Map`…
- [lane-progress](./src/lane-progress/lane-progress.md) — Projects a vessel position onto a shipping lane and reports how much of the lane has been traversed, how much remains,…
- [lane-progress](./src/lane-progress/lane-progress.md) — Projects a vessel position onto a shipping lane and reports how much of the lane has been traversed, how much remains,…
- [nearest-port](./src/nearest-port/nearest-port.md) — Public proximity-search API. `findNearestPort` returns the single closest port to a coordinate; `findPortsInRadius` ret…
- [nearest-port](./src/nearest-port/nearest-port.md) — Public proximity-search API. `findNearestPort` returns the single closest port to a coordinate; `findPortsInRadius` ret…
- [otel](./src/otel/otel.md) — Wires `@suaveplan/ports`' OpenTelemetry instrumentation: three counters that track lookup volume per kind, lane lookup…
- [otel](./src/otel/otel.md) — Wires `@suaveplan/ports`' OpenTelemetry instrumentation: three counters that track lookup volume per kind, lane lookup…
- [schemas](./src/schemas/schemas.md) — Defines Zod schemas for every public type emitted by `@suaveplan/ports`. API plugins, message-queue handlers, and any o…
- [schemas](./src/schemas/schemas.md) — Defines Zod schemas for every public type emitted by `@suaveplan/ports`. API plugins, message-queue handlers, and any o…
- [spatial-index](./src/spatial-index/spatial-index.md) — `PortSpatialIndex` buckets a port set by 5-degree latitude/longitude cells at construction time so `findNearest` and `f…
- [spatial-index](./src/spatial-index/spatial-index.md) — `PortSpatialIndex` buckets a port set by 5-degree latitude/longitude cells at construction time so `findNearest` and `f…
<!-- end:auto-docs -->
## Install
```bash
bun add @suaveplan/ports
```
## What's in the box
- Curated `Port` registry: ~100 sea ports across five regions, ~30
international cargo airports, ~30 rail freight terminals.
- Lookups: by UN/LOCODE (`findPort` / `findPortStrict`), by ISO country
(`findPortsByCountry`), by `PortType` (`findPortsByType`), by name
substring (`searchPorts`).
- Proximity: `findNearestPort` and `findPortsInRadius` (5°-bucketed
`PortSpatialIndex` under the hood).
- Curated `shippingLanes` waypoint graph for the major sea, rail, and
air freight routes.
- Lane distance (`estimateLaneDistance`, `cumulativeDistances`), lane
projection (`findPositionOnLane`), and ETA estimation (`estimateETA`).
- 8-code typed error registry under the `PORTS.*` namespace via
`@suaveplan/error`.
- Zod schemas for every public type under `./schemas/schemas`.
- OpenTelemetry instrumentation: three counters and three histograms
via `./otel/otel`.
## Quickstart
```ts
import {
findPort,
findNearestPort,
getShippingLaneStrict,
estimateETA,
} from "@suaveplan/ports";
const shanghai = findPort("CNSHA");
const nearestSea = findNearestPort({ latitude: 1.26, longitude: 103.82 });
const suez = getShippingLaneStrict("asia-europe-suez");
const eta = estimateETA(
{ latitude: 12.5, longitude: 43.3 },
suez,
14,
{ departureTime: new Date() }
);
console.log(eta.etaDate, eta.confidence);
```
## Capability spec
See the canonical capability
specification (requirements + scenarios + `**Verified by:**` links).

View File

@@ -0,0 +1,56 @@
# airports
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Curated dataset of major international air-cargo airports, exported as
the readonly `airports` array. Each record conforms to the `Port`
interface (with `type: "air"`) and carries the IATA code, runway
length, and freight-throughput figures used by ETA estimation on air
shipping lanes and proximity searches in `findNearestPort`.
## Features
- Coverage of the world's busiest cargo hubs across Asia, Europe, North
America, the Middle East, and Latin America.
- Each record carries `iataCode`, `runwayLengthM`, and
`annualThroughputTonnes` so consumers can filter by capacity.
- Facility tags reflect cargo-handling capability (`cargo`,
`intermodal`, `bonded_warehouse`, `free_trade_zone`, `passenger`,
`reefer`).
- IANA timezones populated for every record.
## Usage: Basic
```ts
import { airports } from "@suaveplan/ports/data/airports";
const memphis = airports.find((p) => p.iataCode === "MEM");
```
## Usage: Advanced
```ts
import { airports, findNearestPort } from "@suaveplan/ports";
const nearest = findNearestPort(
{ latitude: 25.2532, longitude: 55.3657 },
airports
);
```
## API Reference
- `airports: readonly Port[]` — curated international air-cargo airport set.
## Implementation Notes
Every entry uses the IATA-prefixed UN/LOCODE convention (e.g. `USMEM`
for Memphis). Throughput figures use underscores as digit separators
for readability. The dataset is curated rather than exhaustive — it
targets the cargo network's primary hubs and the regional gateways
most commonly used in international logistics workflows, not every
licensed airfield.

View File

@@ -0,0 +1,60 @@
# all-ports
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Aggregates every curated dataset — sea ports, airports, and rail
terminals — into a single readonly `allPorts` array and re-exports each
sub-list. This is the default candidate set consumed by `findPort`,
`findNearestPort`, and `findPortsInRadius` when callers do not supply a
custom port set.
## Features
- Single import point for the full curated port universe.
- Each sub-list is re-exported with its original name so consumers can
scope lookups by transport mode.
- Order is `seaPorts → airports → railTerminals`, matching the
main-branch convention; consumers rely on `locode` as the identity.
- All entries conform to the `Port` interface and validate against
`PortSchema`.
## Usage: Basic
```ts
import { allPorts } from "@suaveplan/ports";
console.log("total curated ports:", allPorts.length);
```
## Usage: Advanced
```ts
import {
allPorts,
findPortsByType,
findNearestPort,
} from "@suaveplan/ports";
const air = findPortsByType("air");
const nearestSeaToCargoHub = findNearestPort(
{ latitude: 35.61, longitude: 139.73 },
allPorts
);
```
## API Reference
- `allPorts: readonly Port[]` — combined sea + air + rail set.
- `seaPorts`, `airports`, `railTerminals` — sub-lists re-exported.
## Implementation Notes
Aggregation is computed once at module load via spread. Every lookup
path keys on `locode` so callers can treat the aggregate as a flat
namespace without worrying about cross-modal collisions (UN/LOCODE
allocations are unique per terminal). Sub-list re-exports preserve
referential identity so identity comparisons keep working.

View File

@@ -0,0 +1,57 @@
# rail-terminals
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Curated dataset of major rail freight terminals — primarily the
inland container hubs on the New Silk Road and Trans-European rail
freight corridors — exported as the readonly `railTerminals` array.
Each record conforms to the `Port` interface (with `type: "rail"`) and
carries a UIC railway code for cross-system identification.
## Features
- Coverage spans China's interior rail hubs, the Trans-Siberian and
Trans-Mongolian routes, the European TEN-T corridors, and select
North American intermodal centres.
- Each record carries `uicCode` (where assigned) and `iataCode`
(intermodal interchange terminals).
- Facility tags reflect rail-specific capabilities (`intermodal`,
`bonded_warehouse`, `container`, `bulk`, `roro`).
- IANA timezones populated for every record so consumers can derive
local times for cross-border schedules.
## Usage: Basic
```ts
import { railTerminals } from "@suaveplan/ports/data/rail-terminals";
console.log("rail terminal count:", railTerminals.length);
```
## Usage: Advanced
```ts
import { railTerminals, findNearestPort } from "@suaveplan/ports";
const nearest = findNearestPort(
{ latitude: 50.1, longitude: 8.7 }, // Frankfurt
railTerminals
);
```
## API Reference
- `railTerminals: readonly Port[]` — curated rail-freight terminal set.
## Implementation Notes
Every entry uses a UN/LOCODE primary key. `uicCode` is populated only
where the terminal has an assigned UIC code — pure intermodal yards
without a UIC registration leave the field undefined. The dataset is
curated rather than exhaustive: it captures the rail-freight hubs most
likely to appear in cross-modal ETA calculations against this package's
shipping-lane registry.

View File

@@ -0,0 +1,60 @@
# sea-ports-africa-mena
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Curated dataset of major sea ports across Africa and the Middle East / North Africa region, exported as the
readonly `seaPortsAfricaMena` array. Every record conforms to the `Port`
interface and the runtime `PortSchema`. The list focuses on
container-handling ports ranked by TEU throughput plus regional
dry-bulk, tanker, and roll-on/roll-off hubs to give realistic coverage
for shipping-lane calculations and proximity searches.
## Features
- ~14 entries covering the major maritime nations of Africa and the Middle East / North Africa region.
- Container throughput hints in TEU (latest published figures at time
of curation).
- Maximum-vessel-size hints in TEU where available.
- Facility tags (container, bulk, tanker, reefer, roro, passenger,
free_trade_zone, bonded_warehouse) reflect real operational
capabilities.
- IANA timezones populated for every record so consumers can derive
local times without lookup tables of their own.
## Usage: Basic
```ts
import { seaPortsAfricaMena } from "@suaveplan/ports/data/sea-ports";
console.log(seaPortsAfricaMena.length, "Africa and the Middle East / North Africa region sea ports");
```
## Usage: Advanced
```ts
import { seaPortsAfricaMena, findNearestPort } from "@suaveplan/ports";
const nearest = findNearestPort(
{ latitude: 51.9, longitude: 4.49 },
seaPortsAfricaMena
);
```
## API Reference
- `seaPortsAfricaMena: readonly Port[]` — curated Africa and the Middle East / North Africa region sea-port set.
## Implementation Notes
Every entry uses a UN/LOCODE primary key. Throughput figures use
underscores as digit separators for readability. The dataset is
intentionally append-only — re-ordering entries would not break
consumers but would generate a misleading diff against the main-branch
source. This is a curated reference set, not a complete UN/LOCODE
mirror; it targets the busiest container ports and the regional hubs
most likely to appear in shipping-lane calculations against this
Africa and the Middle East / North Africa region region.

View File

@@ -0,0 +1,60 @@
# sea-ports-americas
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Curated dataset of major sea ports across the Americas, exported as the
readonly `seaPortsAmericas` array. Every record conforms to the `Port`
interface and the runtime `PortSchema`. The list focuses on
container-handling ports ranked by TEU throughput plus regional
dry-bulk, tanker, and roll-on/roll-off hubs to give realistic coverage
for shipping-lane calculations and proximity searches.
## Features
- ~20 entries covering the major maritime nations of the Americas.
- Container throughput hints in TEU (latest published figures at time
of curation).
- Maximum-vessel-size hints in TEU where available.
- Facility tags (container, bulk, tanker, reefer, roro, passenger,
free_trade_zone, bonded_warehouse) reflect real operational
capabilities.
- IANA timezones populated for every record so consumers can derive
local times without lookup tables of their own.
## Usage: Basic
```ts
import { seaPortsAmericas } from "@suaveplan/ports/data/sea-ports";
console.log(seaPortsAmericas.length, "the Americas sea ports");
```
## Usage: Advanced
```ts
import { seaPortsAmericas, findNearestPort } from "@suaveplan/ports";
const nearest = findNearestPort(
{ latitude: 51.9, longitude: 4.49 },
seaPortsAmericas
);
```
## API Reference
- `seaPortsAmericas: readonly Port[]` — curated the Americas sea-port set.
## Implementation Notes
Every entry uses a UN/LOCODE primary key. Throughput figures use
underscores as digit separators for readability. The dataset is
intentionally append-only — re-ordering entries would not break
consumers but would generate a misleading diff against the main-branch
source. This is a curated reference set, not a complete UN/LOCODE
mirror; it targets the busiest container ports and the regional hubs
most likely to appear in shipping-lane calculations against this
the Americas region.

View File

@@ -0,0 +1,59 @@
# sea-ports-asia
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Curated dataset of major sea ports across Asia, exported as the readonly
`seaPortsAsia` array. Every record conforms to the `Port` interface and
the runtime `PortSchema`. The list focuses on container-handling ports
ranked by TEU throughput plus regional dry-bulk and tanker hubs to give
realistic coverage for shipping-lane calculations.
## Features
- ~30 entries covering China, Japan, South Korea, Hong Kong, Taiwan,
Singapore, Malaysia, Vietnam, Thailand, Indonesia, the Philippines,
India, Sri Lanka, Bangladesh, and Pakistan.
- Container throughput hints in TEU (latest published figures at time
of curation).
- Maximum-vessel-size hints in TEU.
- Facility tags (container, bulk, tanker, reefer, free_trade_zone,
passenger, roro) reflect real operational capabilities.
- IANA timezones populated for every record so consumers can derive
local times without lookup tables of their own.
## Usage: Basic
```ts
import { seaPortsAsia } from "@suaveplan/ports/data/sea-ports";
console.log(seaPortsAsia.length, "Asia sea ports");
```
## Usage: Advanced
```ts
import { seaPortsAsia, findNearestPort } from "@suaveplan/ports";
const nearest = findNearestPort(
{ latitude: 22.32, longitude: 114.17 },
seaPortsAsia
);
```
## API Reference
- `seaPortsAsia: readonly Port[]` — curated Asia sea-port set.
## Implementation Notes
Every entry uses a UN/LOCODE primary key. Some throughput figures use
underscores as digit separators for readability. The dataset is
intentionally append-only: re-ordering entries would not break
consumers but would generate a misleading diff against the main-branch
source. Curated rather than complete — this is not a substitute for a
live UN/LOCODE database, it's a fixed reference set for ETA and
proximity calculations against the world's busiest container ports.

View File

@@ -0,0 +1,60 @@
# sea-ports-europe
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Curated dataset of major sea ports across Europe, exported as the
readonly `seaPortsEurope` array. Every record conforms to the `Port`
interface and the runtime `PortSchema`. The list focuses on
container-handling ports ranked by TEU throughput plus regional
dry-bulk, tanker, and roll-on/roll-off hubs to give realistic coverage
for shipping-lane calculations and proximity searches.
## Features
- ~24 entries covering the major maritime nations of Europe.
- Container throughput hints in TEU (latest published figures at time
of curation).
- Maximum-vessel-size hints in TEU where available.
- Facility tags (container, bulk, tanker, reefer, roro, passenger,
free_trade_zone, bonded_warehouse) reflect real operational
capabilities.
- IANA timezones populated for every record so consumers can derive
local times without lookup tables of their own.
## Usage: Basic
```ts
import { seaPortsEurope } from "@suaveplan/ports/data/sea-ports";
console.log(seaPortsEurope.length, "Europe sea ports");
```
## Usage: Advanced
```ts
import { seaPortsEurope, findNearestPort } from "@suaveplan/ports";
const nearest = findNearestPort(
{ latitude: 51.9, longitude: 4.49 },
seaPortsEurope
);
```
## API Reference
- `seaPortsEurope: readonly Port[]` — curated Europe sea-port set.
## Implementation Notes
Every entry uses a UN/LOCODE primary key. Throughput figures use
underscores as digit separators for readability. The dataset is
intentionally append-only — re-ordering entries would not break
consumers but would generate a misleading diff against the main-branch
source. This is a curated reference set, not a complete UN/LOCODE
mirror; it targets the busiest container ports and the regional hubs
most likely to appear in shipping-lane calculations against this
Europe region.

View File

@@ -0,0 +1,60 @@
# sea-ports-oceania
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Curated dataset of major sea ports across Oceania, exported as the
readonly `seaPortsOceania` array. Every record conforms to the `Port`
interface and the runtime `PortSchema`. The list focuses on
container-handling ports ranked by TEU throughput plus regional
dry-bulk, tanker, and roll-on/roll-off hubs to give realistic coverage
for shipping-lane calculations and proximity searches.
## Features
- ~10 entries covering the major maritime nations of Oceania.
- Container throughput hints in TEU (latest published figures at time
of curation).
- Maximum-vessel-size hints in TEU where available.
- Facility tags (container, bulk, tanker, reefer, roro, passenger,
free_trade_zone, bonded_warehouse) reflect real operational
capabilities.
- IANA timezones populated for every record so consumers can derive
local times without lookup tables of their own.
## Usage: Basic
```ts
import { seaPortsOceania } from "@suaveplan/ports/data/sea-ports";
console.log(seaPortsOceania.length, "Oceania sea ports");
```
## Usage: Advanced
```ts
import { seaPortsOceania, findNearestPort } from "@suaveplan/ports";
const nearest = findNearestPort(
{ latitude: 51.9, longitude: 4.49 },
seaPortsOceania
);
```
## API Reference
- `seaPortsOceania: readonly Port[]` — curated Oceania sea-port set.
## Implementation Notes
Every entry uses a UN/LOCODE primary key. Throughput figures use
underscores as digit separators for readability. The dataset is
intentionally append-only — re-ordering entries would not break
consumers but would generate a misleading diff against the main-branch
source. This is a curated reference set, not a complete UN/LOCODE
mirror; it targets the busiest container ports and the regional hubs
most likely to appear in shipping-lane calculations against this
Oceania region.

View File

@@ -0,0 +1,65 @@
# sea-ports
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Aggregates every regional sea-port dataset (`seaPortsAsia`,
`seaPortsEurope`, `seaPortsAmericas`, `seaPortsAfricaMena`,
`seaPortsOceania`) into a single readonly `seaPorts` array and re-exports
each regional sub-list. This is the curated set consumed by
`@suaveplan/ports`' `findPort`, `findNearestPort`, and
`findPortsInRadius` lookups when callers do not pass a custom port set.
## Features
- Single import point for the global sea-port set.
- Every regional sub-list is re-exported with the original name so
callers can scope a search to a region.
- Order is `Asia → Europe → Americas → Africa/MENA → Oceania`, matching
the main-branch convention; consumers rely on `locode` as the
identity rather than array index.
- All entries conform to the `Port` interface and validate against
`PortSchema`.
## Usage: Basic
```ts
import { seaPorts } from "@suaveplan/ports/data/sea-ports";
console.log("global sea-port count:", seaPorts.length);
```
## Usage: Advanced
```ts
import {
seaPortsAsia,
seaPortsEurope,
findPortsInRadius,
} from "@suaveplan/ports";
const pair = [...seaPortsAsia, ...seaPortsEurope];
const candidates = findPortsInRadius(
{ latitude: 36, longitude: 30 },
1500,
pair
);
```
## API Reference
- `seaPorts: readonly Port[]` — global aggregate.
- `seaPortsAsia`, `seaPortsEurope`, `seaPortsAmericas`,
`seaPortsAfricaMena`, `seaPortsOceania` — regional sub-lists
re-exported.
## Implementation Notes
Aggregation is computed once at module load via spread. The cost is
linear in the total port count and pays off immediately because every
lookup-by-locode path hits the merged set. Regional sub-lists remain
referentially distinct from their aggregate appearance, so identity
checks (`port === seaPortsAsia[0]`) keep working after aggregation.

View File

@@ -0,0 +1,57 @@
# shipping-lanes
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Curated registry of major global shipping lanes — Asia–Europe via Suez,
Asia–Europe via Cape, Transpacific, Transatlantic, Europe–MENA, plus
rail and air freight lanes — exported as the readonly `shippingLanes`
array. Each lane carries an ordered waypoint chain that ETA, lane
distance, and lane-progress calculations project vessel positions
against.
## Features
- Sea lanes: Asia–Europe via Suez, Asia–Europe via Cape, Transpacific
Asia–US West Coast, Transatlantic Europe–US East Coast.
- Rail lane: China–Europe via Trans-Siberian / Trans-Mongolian.
- Air lane: Asia–Europe.
- Each lane carries `typicalTransitDays` and `typicalSpeedKnots`
(sea/rail) or `typicalSpeedKmh` (air/rail metric).
- Waypoint chains capture strait passages (Malacca, Bab el-Mandeb,
Gibraltar, Suez Canal) so lane-progress reporting reflects real
navigational milestones.
## Usage: Basic
```ts
import { shippingLanes } from "@suaveplan/ports/data/shipping-lanes";
console.log("curated lane count:", shippingLanes.length);
```
## Usage: Advanced
```ts
import { shippingLanes, estimateLaneDistance } from "@suaveplan/ports";
for (const lane of shippingLanes) {
console.log(lane.id, estimateLaneDistance(lane), "nm");
}
```
## API Reference
- `shippingLanes: readonly ShippingLane[]` — curated lane set.
## Implementation Notes
Waypoint chains are coarse — they capture the strategic geography of
the route, not turn-by-turn navigation. Distance and ETA computations
are therefore representative of typical transit, not exact in-water
distance with weather routing. Each lane id is stable across versions
so external systems can reference lanes by string id without worrying
about array reorder.

View File

@@ -0,0 +1,69 @@
# distance
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Wraps `@suaveplan/geo`'s `greatCircleDistance` against a sphere whose
radius matches the IUGG mean Earth radius (6 371 km), bound once as the
exported `EARTH_SPHERE` ellipsoid. The package's lane-distance,
lane-progress, ETA, and nearest-port modules all consume these wrappers
so the entire package agrees on a single radius and the rounding policy
is centralised.
## Features
- `distanceNm(a, b)` — great-circle distance in nautical miles, rounded
to two decimals.
- `distanceKm(a, b)` — same in kilometres, rounded to two decimals.
- `distanceMetresRaw(a, b)` — unrounded metres, useful for accumulating
segment sums without compounding rounding error.
- Symmetric: `distanceNm(a, b) === distanceNm(b, a)` to floating
precision.
- Non-negative for any valid coordinate pair.
- Coordinate inputs use `@suaveplan/geo`'s `SphericalCoord` shape so the
wrappers are interoperable with the entire geospatial stack.
## Usage: Basic
```ts
import { distanceNm } from "@suaveplan/ports";
const nm = distanceNm(
{ latitude: 1.26, longitude: 103.82 },
{ latitude: 51.9, longitude: 4.49 }
);
console.log(nm); // ~5800 nautical miles
```
## Usage: Advanced
```ts
import { distanceMetresRaw } from "@suaveplan/ports";
function totalMetres(coords: { latitude: number; longitude: number }[]): number {
let total = 0;
for (let i = 0; i < coords.length - 1; i++) {
total += distanceMetresRaw(coords[i], coords[i + 1]);
}
return total;
}
```
## API Reference
- `distanceNm(a, b)` → `number`.
- `distanceKm(a, b)` → `number`.
- `distanceMetresRaw(a, b)` → `number` (unrounded).
## Implementation Notes
All three wrappers route through the single `EARTH_SPHERE` constant so
the cost of ellipsoid construction is paid once at module load. Rounded
helpers multiply by 100, round, then divide — this matches the
main-branch rounding convention so cross-version diffs remain
byte-for-byte stable on the rounded values. `distanceMetresRaw` is
deliberately exposed for callers that need full precision (segment
accumulation in lane-distance, deviation-ratio computation in ETA).

View File

@@ -0,0 +1,96 @@
# error-codes
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Declares the eight stable error codes emitted by `@suaveplan/ports` and
the matching typed `PortsError` subclasses. Every public function in the
package throws one of these subclasses when a precondition fails, so
downstream consumers — loggers, problem+json responders, retry middleware
— can switch on `.code` without re-implementing the classifier. Codes
flow through `defineCodes("PORTS", [...])` from `@suaveplan/error` so the
full namespace is `PORTS.NO_PORTS`, `PORTS.INVALID_RADIUS`, and so on.
## Features
- Eight codes: `NO_PORTS`, `INVALID_RADIUS`,
`INSUFFICIENT_WAYPOINTS`, `INVALID_SPEED`, `MISSING_SPEED`,
`PORT_NOT_FOUND`, `LANE_NOT_FOUND`, `INVALID_LANE`.
- Eight typed subclasses, each extending `PortsError` which itself
extends `SuaveplanError<PortsErrorCode>`.
- Context carriers — `PortsPortNotFoundError` exposes `locode`,
`PortsLaneNotFoundError` exposes `laneId`, `PortsInvalidLaneError`
exposes `issues` (Zod issue array).
- i18n-ready: every constructor accepts a `message` override defaulting
to an i18n key (`ports.errors.<camelCase>`).
- Cause-chain pass-through (`cause`, `traceId`, `context`) honours the
full `SuaveplanErrorOptions` shape.
## Usage: Basic
```ts
import {
PortsNoPortsError,
PortsInvalidRadiusError,
} from "@suaveplan/ports";
function safeFind(): void {
try {
// ...
} catch (err) {
if (err instanceof PortsInvalidRadiusError) {
// 400 Bad Request
} else if (err instanceof PortsNoPortsError) {
// 503 Service Unavailable
}
}
}
```
## Usage: Advanced
```ts
import {
PORTS_CODES,
PortsPortNotFoundError,
} from "@suaveplan/ports";
const err = new PortsPortNotFoundError({
locode: "XXNON",
traceId: "01HXYZ",
context: { source: "rest" },
});
switch (err.code) {
case PORTS_CODES.PORT_NOT_FOUND:
// Render i18n-aware 404 message with locode in context.
break;
default:
throw err;
}
```
## API Reference
- `PORTS_CODES` — frozen registry: `{ NO_PORTS, INVALID_RADIUS,
INSUFFICIENT_WAYPOINTS, INVALID_SPEED, MISSING_SPEED, PORT_NOT_FOUND,
LANE_NOT_FOUND, INVALID_LANE }`.
- `PortsErrorCode` — union type of every code.
- `PortsError` — abstract base.
- `PortsNoPortsError`, `PortsInvalidRadiusError`,
`PortsInsufficientWaypointsError`, `PortsInvalidSpeedError`,
`PortsMissingSpeedError`, `PortsPortNotFoundError`,
`PortsLaneNotFoundError`, `PortsInvalidLaneError`.
## Implementation Notes
The registry is registered eagerly at module load via `defineCodes` so
collisions across packages fail fast. Each subclass forwards the
`pickOpts` filtered options bundle to `SuaveplanError`'s protected
constructor so context, cause, and traceId remain optional but
correctly typed. Default message keys follow `ports.errors.<camelCase>`
so an i18n resolver can translate them at the boundary; consumers that
prefer literal messages pass `{ message: "..." }`.

View File

@@ -0,0 +1,70 @@
# eta
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Estimates the time of arrival for a vessel travelling along a
`ShippingLane`. Resolves an effective cruise speed from (in order) an
explicit override, the lane's `typicalSpeedKnots`, or the km/h
equivalent, then divides the remaining-distance figure produced by
`findPositionOnLane` to compute `etaHours` and projects to an
`etaDate`. The result includes a `confidence` band derived from
deviation between travelled+remaining and the lane's total length so
operators can flag vessels that have drifted off the planned route.
## Features
- Three-tier speed resolution: explicit override → lane knots → lane
km/h (converted at 1.852 km/nm).
- Injectable wall-clock — `opts.clock?: Clock` defaults to `Date.now`.
- Explicit `opts.departureTime?` overrides the clock.
- Confidence band: `high` (off-lane ≤3% of total length), `medium`
(≤10%), `low` (more than 10%).
- Throws `PortsInvalidSpeedError` on non-positive speed, and
`PortsMissingSpeedError` when no speed source resolves.
- Emits `ports.eta_total{confidence}` counter and
`ports.eta_duration_ms` histogram per call.
- `etaHours` rounded to two decimals; `etaDate` uses unrounded hours
for sub-minute precision.
## Usage: Basic
```ts
import { estimateETA, getShippingLaneStrict } from "@suaveplan/ports";
const suez = getShippingLaneStrict("asia-europe-suez");
const result = estimateETA({ latitude: 12.5, longitude: 43.3 }, suez);
console.log(result.etaDate, result.confidence);
```
## Usage: Advanced
```ts
import { estimateETA } from "@suaveplan/ports";
import { createClock } from "@suaveplan/testing";
const clock = createClock(new Date("2026-05-25T00:00:00Z"));
const eta = estimateETA(coord, lane, 18, {
clock: () => clock.now(),
});
```
## API Reference
- `estimateETA(currentCoord, lane, avgSpeedKnots?, opts?)` → `ETAEstimate`.
- `EstimateETAOptions`:
- `departureTime?: Date` — explicit override.
- `clock?: Clock` — wall-clock source (`() => number`).
## Implementation Notes
Speed resolution is centralised in a private helper so the order of
fallbacks stays explicit. The `etaDate` is computed from the unrounded
hour figure — rounding to two decimals before `* 3_600_000` would
quantise arrival to 36-second buckets. The deviation ratio uses the
lane's full great-circle length as the denominator so the band stays
calibrated independent of vessel position. `performance.now()` is
preferred over `Date.now()` for the duration histogram when available.

View File

@@ -0,0 +1,78 @@
# find-port
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides the lookup surface for `@suaveplan/ports`. Every public
function consumes the curated `allPorts` registry by default but accepts
an explicit `ports` argument so consumers can scope a search to a
subset (a tenant-specific allowlist, an environment-restricted set, or
test fixtures). UN/LOCODE lookups go through a lazily-built `Map`
index keyed on the uppercased locode so the call cost is O(1) once the
index is initialised.
## Features
- `findPort(locode)` — O(1) lookup by UN/LOCODE, case-insensitive,
returns `undefined` on miss.
- `findPortStrict(locode)` — throws `PortsPortNotFoundError`
(`PORTS.PORT_NOT_FOUND`) on miss, carrying the locode in error
context.
- `findPortsByCountry(country, ports?)` — ISO 3166-1 alpha-2 filter
(input uppercased).
- `findPortsByType(type, ports?)` — `PortType` filter.
- `searchPorts(query, ports?)` — case-insensitive substring match on
`name`.
- Every call emits the OTel counter `ports.lookup_total{kind}`.
## Usage: Basic
```ts
import { findPort, findPortsByCountry } from "@suaveplan/ports";
const shanghai = findPort("cnsha");
const dutchPorts = findPortsByCountry("nl");
```
## Usage: Advanced
```ts
import {
findPortStrict,
findPortsByType,
searchPorts,
PortsPortNotFoundError,
} from "@suaveplan/ports";
try {
const port = findPortStrict("ZZZZZ");
} catch (err) {
if (err instanceof PortsPortNotFoundError) {
// 404
}
}
const sea = findPortsByType("sea");
const named = searchPorts("rotterdam", sea); // scoped search
```
## API Reference
- `findPort(locode: string): Port | undefined`.
- `findPortStrict(locode: string): Port` — throws on miss.
- `findPortsByCountry(country: string, ports?: readonly Port[]): Port[]`.
- `findPortsByType(type: PortType, ports?: readonly Port[]): Port[]`.
- `searchPorts(query: string, ports?: readonly Port[]): Port[]`.
## Implementation Notes
The locode index is built lazily on first lookup and cached at module
scope for the lifetime of the process. Re-importing the module never
duplicates the index because ES modules are singletons within a realm.
Filtering helpers return fresh arrays so callers can mutate without
affecting the curated registry. The substring match is allocator-light
— it lowercases the query once and the port name once per record, no
regex compilation in the hot path.

View File

@@ -0,0 +1,65 @@
# lane-distance
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Computes great-circle distance metrics for a `ShippingLane`'s waypoint
chain. `estimateLaneDistance` sums every segment for the lane total;
`cumulativeDistances` returns the per-waypoint cumulative array used by
`findPositionOnLane` and `estimateETA`. Both functions reject lanes
with fewer than two waypoints because any distance metric over a
single-point chain is undefined.
## Features
- `estimateLaneDistance(lane)` — total nautical miles, rounded to two
decimals.
- `cumulativeDistances(lane)` — `number[]` of length `waypoints.length`
where entry 0 is 0 and entry i is the cumulative nm distance from
`waypoints[0]` to `waypoints[i]`.
- Throws `PortsInsufficientWaypointsError`
(`PORTS.INSUFFICIENT_WAYPOINTS`) for lanes shorter than two
waypoints.
- Emits the `ports.lane_distance_duration_ms` histogram.
- Uses the package's shared `distanceNm` helper so the rounding policy
is centralised.
## Usage: Basic
```ts
import { estimateLaneDistance, getShippingLaneStrict } from "@suaveplan/ports";
const suez = getShippingLaneStrict("asia-europe-suez");
const totalNm = estimateLaneDistance(suez);
```
## Usage: Advanced
```ts
import { cumulativeDistances, getShippingLaneStrict } from "@suaveplan/ports";
const lane = getShippingLaneStrict("asia-europe-suez");
const cum = cumulativeDistances(lane);
// cum[i] - cum[i-1] gives segment length between waypoints i-1 and i.
for (let i = 1; i < cum.length; i++) {
console.log("segment", i, cum[i] - cum[i - 1], "nm");
}
```
## API Reference
- `estimateLaneDistance(lane: ShippingLane): number`.
- `cumulativeDistances(lane: ShippingLane): number[]`.
## Implementation Notes
Each segment runs through `distanceNm` which itself routes through
`@suaveplan/geo`'s `greatCircleDistance` against the
`EARTH_SPHERE` constant. The cumulative function recomputes segment
distances rather than reusing the running sum from
`estimateLaneDistance` — this keeps the two functions independent so
callers can use either in isolation without paying for both. The
trivial perf cost is acceptable at curated-dataset scale.

View File

@@ -0,0 +1,67 @@
# lane-lookup
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Public lookup surface for the curated `shippingLanes` registry. Mirrors
the port-lookup module in style: an O(1) `Map` index by lane id, mode
filtering, and a "get all" accessor for downstream consumers that want
to render lists or build their own indices. The strict variant of
`getShippingLane` throws a typed `PortsLaneNotFoundError` so consumers
can branch on `.code` rather than nullable returns.
## Features
- `getShippingLane(id)` — lazy `Map<string, ShippingLane>` lookup.
- `getShippingLaneStrict(id)` — throws on miss with `laneId` in error
context.
- `getShippingLanesByMode(mode, lanes?)` — `LaneMode` filter.
- `getAllShippingLanes()` — returns the curated registry directly
(readonly).
- Every call emits `ports.lane_lookup_total{mode}` (mode is `all` for
`getAllShippingLanes` and miss-paths).
## Usage: Basic
```ts
import { getShippingLane, getShippingLanesByMode } from "@suaveplan/ports";
const suez = getShippingLane("asia-europe-suez");
const seaLanes = getShippingLanesByMode("sea");
```
## Usage: Advanced
```ts
import {
getShippingLaneStrict,
PortsLaneNotFoundError,
} from "@suaveplan/ports";
try {
const lane = getShippingLaneStrict("missing-lane");
} catch (err) {
if (err instanceof PortsLaneNotFoundError) {
// err.context.laneId === "missing-lane"
}
}
```
## API Reference
- `getShippingLane(id: string): ShippingLane | undefined`.
- `getShippingLaneStrict(id: string): ShippingLane`.
- `getShippingLanesByMode(mode: LaneMode, lanes?: readonly ShippingLane[]):
ShippingLane[]`.
- `getAllShippingLanes(): readonly ShippingLane[]`.
## Implementation Notes
The lane id index is built lazily on first call and cached at module
scope for the lifetime of the process. `getAllShippingLanes` returns
the curated registry directly rather than a copy — callers must not
mutate it. Filtering helpers return fresh arrays so callers retain
mutation safety on derived sets.

View File

@@ -0,0 +1,71 @@
# lane-progress
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Projects a vessel position onto a shipping lane and reports how much of
the lane has been traversed, how much remains, and which waypoint is
nearest. Used by `estimateETA` to derive the remaining-distance figure
that feeds the ETA computation, and exposed publicly so downstream UIs
can render lane-progress bars without re-implementing the geometry.
## Features
- Finds the nearest waypoint by great-circle distance.
- Estimates fractional progress within the nearest waypoint's adjacent
segment (prev or next, whichever is closer).
- Returns `{ progress, remainingDistanceNm, distanceTravelledNm,
nearestWaypointIndex }` with `progress` clamped to `[0, 1]` and
rounded to four decimals; distances clamped to `>= 0` and rounded to
two decimals.
- Throws `PortsInsufficientWaypointsError` for lanes with fewer than
two waypoints.
- Handles degenerate zero-length lanes (every waypoint identical) by
returning zero progress.
## Usage: Basic
```ts
import { findPositionOnLane, getShippingLaneStrict } from "@suaveplan/ports";
const lane = getShippingLaneStrict("asia-europe-suez");
const progress = findPositionOnLane(
{ latitude: 12.5, longitude: 43.3 },
lane
);
```
## Usage: Advanced
```ts
import { findPositionOnLane, type ShippingLane } from "@suaveplan/ports";
function eta(
coord: { latitude: number; longitude: number },
lane: ShippingLane,
knots: number,
departure: Date
): Date {
const { remainingDistanceNm } = findPositionOnLane(coord, lane);
const hours = remainingDistanceNm / knots;
return new Date(departure.getTime() + hours * 3_600_000);
}
```
## API Reference
- `findPositionOnLane(coord, lane): LaneProgress`.
## Implementation Notes
The "nearest waypoint" pass is a linear scan. The adjacent-segment
projection uses a 1D ratio (distance-to-nearest / segment-length)
clamped to `[0, 1]`; this is an approximation that works well when the
coord is reasonably close to the lane and degrades gracefully when the
coord is far off-lane (the ETA `confidence` band downgrades to
`medium`/`low`). The function never throws on out-of-lane coords —
callers receive a clamped progress fraction and a non-negative
remaining distance regardless of how far off-lane the vessel is.

View File

@@ -0,0 +1,65 @@
# nearest-port
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Public proximity-search API. `findNearestPort` returns the single
closest port to a coordinate; `findPortsInRadius` returns every port
within a great-circle radius, sorted ascending by distance. When called
against the default `allPorts` registry the work delegates to a
pre-built `PortSpatialIndex`; explicit overrides fall through to a
linear scan so the caller retains full control over the candidate set.
## Features
- O(neighbours) on the default registry via `PortSpatialIndex`.
- Linear scan fallback for custom port sets.
- `findNearestPort` throws `PortsNoPortsError` (`PORTS.NO_PORTS`) on an
empty input.
- `findPortsInRadius` throws `PortsInvalidRadiusError`
(`PORTS.INVALID_RADIUS`) on `radiusKm <= 0`.
- Distance returned in kilometres, rounded to two decimals.
- Emits `ports.lookup_total{kind}` per call and the
`ports.find_nearest_duration_ms` histogram for `findNearestPort`.
## Usage: Basic
```ts
import { findNearestPort, findPortsInRadius } from "@suaveplan/ports";
const nearest = findNearestPort({ latitude: 1.26, longitude: 103.82 });
const within500 = findPortsInRadius(
{ latitude: 1.26, longitude: 103.82 },
500
);
```
## Usage: Advanced
```ts
import { findNearestPort, findPortsByType } from "@suaveplan/ports";
const onlySea = findPortsByType("sea");
const nearestSea = findNearestPort(
{ latitude: 51.9, longitude: 4.49 },
onlySea
);
```
## API Reference
- `findNearestPort(coord, ports?)` — `NearestPortResult`.
- `findPortsInRadius(coord, radiusKm, ports?)` — `NearestPortResult[]`,
sorted ascending by `distanceKm`.
## Implementation Notes
The spatial index is built lazily on first call against the default
registry. The duration histogram uses `performance.now()` when
available, falling back to `Date.now()` so the package stays usable in
runtimes without a high-resolution timer. Custom port sets bypass the
index because building a fresh index per call would dominate the
linear-scan cost we are trying to avoid.

View File

@@ -0,0 +1,72 @@
# otel
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Wires `@suaveplan/ports`' OpenTelemetry instrumentation: three counters
that track lookup volume per kind, lane lookup volume per mode, and ETA
estimations per confidence band; plus three histograms recording the
duration of `findNearestPort`, `estimateLaneDistance`, and `estimateETA`.
Every public function in the package emits exactly one counter
increment and (where relevant) one histogram sample on each call so
operators can build dashboards without re-instrumenting consumers.
## Features
- Three counters: `ports.lookup_total{kind}`,
`ports.lane_lookup_total{mode}`, `ports.eta_total{confidence}`.
- Three histograms: `ports.find_nearest_duration_ms`,
`ports.lane_distance_duration_ms`, `ports.eta_duration_ms`, all in
millisecond units.
- `refreshPortsMeters()` rebinds every instrument against the current
global `MeterProvider` — tests call this after wiring an in-memory
exporter so subsequent record calls land on the test provider rather
than the module-load meter.
- Recorder helpers (`recordPortLookup`, `recordLaneLookup`,
`recordEta`, `record*Duration`) are 1:1 with the named instruments
and accept the literal-typed label values directly.
- Meter is named `@suaveplan/ports` at version `0.1.0`.
## Usage: Basic
```ts
import { recordPortLookup } from "@suaveplan/ports";
recordPortLookup("findPort");
```
## Usage: Advanced
```ts
import { MeterProvider } from "@opentelemetry/sdk-metrics";
import { metrics } from "@opentelemetry/api";
import { refreshPortsMeters } from "@suaveplan/ports";
const provider = new MeterProvider();
metrics.setGlobalMeterProvider(provider);
refreshPortsMeters();
// Now every subsequent ports operation lands on `provider`.
```
## API Reference
- `recordPortLookup(kind)` — kind ∈ `findPort | byCountry | byType |
search | findNearest | inRadius`.
- `recordLaneLookup(mode)` — mode ∈ `sea | rail | air | all`.
- `recordEta(confidence)` — confidence ∈ `high | medium | low`.
- `recordFindNearestDuration(ms)`,
`recordLaneDistanceDuration(ms)`, `recordEtaDuration(ms)`.
- `refreshPortsMeters()` — re-resolve every instrument.
## Implementation Notes
Instruments are module-scope `let` bindings so `refreshPortsMeters` can
swap them in place; otherwise the OTel API would route emissions to the
original meter even after `setGlobalMeterProvider`. The histograms use
the `ms` unit hint so exporters render them as durations. The package
deliberately does not start its own spans — span management is the
caller's responsibility, and instruments stay focused on aggregate
behaviour rather than per-call tracing.

View File

@@ -0,0 +1,74 @@
# schemas
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines Zod schemas for every public type emitted by `@suaveplan/ports`.
API plugins, message-queue handlers, and any other consumer that
receives a `Port` or `ShippingLane` over the wire passes the payload
through `PortSchema.parse(...)` / `ShippingLaneSchema.parse(...)` to
guarantee shape before the curated functions touch the data. The
schemas mirror the TypeScript interfaces in `types/port.types.ts`
exactly so a runtime success implies a sound compile-time `Port` or
`ShippingLane`.
## Features
- Every TS interface (`Port`, `ShippingLane`, `LaneProgress`,
`ETAEstimate`, `NearestPortResult`, `SphericalCoord`) has a matching
Zod schema.
- Locode is regex-validated as five uppercase A–Z characters.
- Country is regex-validated as ISO 3166-1 alpha-2 (two uppercase).
- IATA code (when present) is regex-validated as three uppercase A–Z.
- Latitude clamped to `[-90, 90]`; longitude to `[-180, 180]`.
- `ShippingLaneSchema` enforces at least two waypoints — matching the
precondition of every lane-distance / lane-progress / ETA function.
- Throughput and capacity fields are non-negative integers.
## Usage: Basic
```ts
import { PortSchema } from "@suaveplan/ports";
const port = PortSchema.parse(payload); // throws on shape mismatch
```
## Usage: Advanced
```ts
import { ShippingLaneSchema, PortsInvalidLaneError } from "@suaveplan/ports";
const result = ShippingLaneSchema.safeParse(payload);
if (!result.success) {
throw new PortsInvalidLaneError({
issues: result.error.issues,
});
}
const lane = result.data;
```
## API Reference
- `SphericalCoordSchema` — `{ latitude, longitude, altitude? }`.
- `PortSchema` — full `Port` shape.
- `PortTypeSchema` — `z.enum(["sea","air","rail","multimodal"])`.
- `PortFacilitySchema` — 10-value enum.
- `ShippingLaneSchema` — `id, name, mode, waypoints≥2, typical*?`.
- `LaneModeSchema` — `z.enum(["sea","rail","air"])`.
- `LaneProgressSchema` — clamped-progress object.
- `ETAEstimateSchema` — `{ remainingDistanceNm, etaHours, etaDate,
confidence }`.
- `NearestPortResultSchema` — `{ port, distanceKm }`.
## Implementation Notes
Schemas use Zod's default-import form (`import z from "zod"`) as
mandated by project rules. Optional fields are declared with
`.optional()` so undefined and absence are interchangeable on the wire.
The schemas do not enforce cross-field invariants (e.g. that
`distanceTravelledNm + remainingDistanceNm <= totalLaneDistance`) —
those invariants are guaranteed by the producing functions, not the
inbound parse step.

View File

@@ -0,0 +1,76 @@
# spatial-index
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`PortSpatialIndex` buckets a port set by 5-degree latitude/longitude
cells at construction time so `findNearest` and `findInRadius` can run
against a small candidate set instead of the full registry. The 3×3
cell neighbourhood is ~600 km wide, so a 500-km radius query (the
common case) visits at most nine cells. For the curated ~120-port
dataset the absolute saving is small, but the API stays O(neighbours)
as the dataset grows.
## Features
- 5° lat/lng cell bucketing — bucket key is
`floor(lat / 5) + ":" + floor(lng / 5)`.
- O(neighbours) `findNearest` that falls back to a full scan when the
3×3 neighbourhood is empty (Pacific gyre, polar regions).
- `findInRadius(coord, radiusKm)` returns ports within `radiusKm`
sorted ascending by distance, switching to a full scan when the
requested radius exceeds the half-width of the 3×3 neighbourhood
(~830 km) to guarantee correctness.
- Immutable after construction — no add/remove API, so callers rebuild
to reflect updates and the index never falls out of sync with its
source set.
- `size` exposes the total indexed port count.
## Usage: Basic
```ts
import { PortSpatialIndex, allPorts } from "@suaveplan/ports";
const index = new PortSpatialIndex(allPorts);
const nearest = index.findNearest({ latitude: 1.26, longitude: 103.82 });
```
## Usage: Advanced
```ts
import { PortSpatialIndex } from "@suaveplan/ports";
// Tenant-scoped index built once per request.
function buildIndex(allowed: Port[]): PortSpatialIndex {
return new PortSpatialIndex(allowed);
}
const tenant = buildIndex(allowedPortsForTenant);
const nearby = tenant.findInRadius(
{ latitude: 35.4, longitude: 139.6 },
250
);
```
## API Reference
- `new PortSpatialIndex(ports)` — pre-bucket the input.
- `index.size` — total indexed port count.
- `index.neighbourhood(coord)` — every port in the 3×3 cell around
`coord`.
- `index.findNearest(coord)` — nearest `NearestPortResult` or
`undefined` when the index is empty.
- `index.findInRadius(coord, radiusKm)` — sorted `NearestPortResult[]`.
## Implementation Notes
The 5° cell width was chosen because individual seas (the Med, the
Caribbean, the Sea of Japan) span 2–4 cells, which keeps the
neighbourhood candidate set tight without artificially excluding ports
on cell boundaries. The radius branch threshold (`> 1.5 × 5° × 111`)
ensures we never miss a port outside the neighbourhood: a circle of
radius greater than half the neighbourhood width must be tested
against the full set.

View File

@@ -0,0 +1,80 @@
# port.types
## Purpose
Defines every type emitted by `@suaveplan/ports`: the curated `Port`
record, the `ShippingLane` waypoint graph, the result types for lane
projection (`LaneProgress`) and ETA estimation (`ETAEstimate`), and the
discriminating unions for port type and facility tags. Every public
function in the package consumes or returns one of these types so the
file is the single source of truth for the package's data contract.
## Features
- `Port` carries a UN/LOCODE primary key plus geographic coordinate,
country, type, timezone, and facility tags.
- Optional capacity hints (`maxVesselSizeTEU`, `maxVesselSizeDWT`,
`runwayLengthM`, `annualThroughputTEU`, `annualThroughputTonnes`).
- `PortType` discriminates between `sea | air | rail | multimodal`.
- `ShippingLane.waypoints` is an ordered `SphericalCoord` array.
- `LaneMode` is `sea | rail | air`.
- `ETAEstimate.confidence` is `high | medium | low` derived from
deviation between travelled+remaining and the lane's total distance.
- Re-exports `SphericalCoord` from `@suaveplan/geo` so consumers only
import from a single canonical location.
## Usage: Basic
```ts
import type { Port } from "@suaveplan/ports";
const port: Port = {
locode: "SGSIN",
name: "Singapore",
country: "SG",
coordinate: { latitude: 1.26, longitude: 103.82 },
type: "sea",
timezone: "Asia/Singapore",
facilities: ["container", "bulk"],
};
```
## Usage: Advanced
```ts
import type { ETAEstimate, ShippingLane } from "@suaveplan/ports";
function logEta(eta: ETAEstimate, lane: ShippingLane): void {
console.info({
lane: lane.id,
etaHours: eta.etaHours,
confidence: eta.confidence,
remainingNm: eta.remainingDistanceNm,
});
}
```
## API Reference
- `Port` — curated port record with optional capacity hints.
- `PortType` — `"sea" | "air" | "rail" | "multimodal"`.
- `PortFacility` — 10-value union: container, bulk, roro, tanker,
passenger, cargo, reefer, intermodal, bonded_warehouse,
free_trade_zone.
- `ShippingLane` — id, name, mode, waypoints, optional typical transit
days and speeds (knots or km/h).
- `LaneMode` — `"sea" | "rail" | "air"`.
- `LaneProgress` — `{ progress, remainingDistanceNm,
distanceTravelledNm, nearestWaypointIndex }`.
- `ETAEstimate` — `{ remainingDistanceNm, etaHours, etaDate, confidence }`.
- `NearestPortResult` — `{ port, distanceKm }`.
- `SphericalCoord` — re-exported from `@suaveplan/geo`.
## Implementation Notes
Every interface is `readonly` end-to-end so curated records remain
immutable across consumers. Optional fields are non-nullable
unioned with `undefined` via TypeScript's exact-optional semantics —
schemas in `schemas/schemas.ts` enforce the same shape at runtime for
incoming payloads. The file deliberately contains no runtime code so
type-only imports do not pay any bundle cost.