# @suaveplan/tides — Tidal Prediction Capability Universal-tier tidal prediction surface exposing the unified `TidalProvider` contract from `@suaveplan/types`, three concrete provider factories (harmonic / equilibrium-physics / ADMIRALTY API), harmonic-constituent math with nodal corrections, equilibrium-tide physics, and tide-station search. ## Requirements ### Requirement: Harmonic prediction engine The package SHALL expose `predictHeight(constituents, time, meanSeaLevel?)` and `findExtremes(constituents, from, to, meanSeaLevel?)` computing tide height per the harmonic formula `h(t) = H₀ + Σ f(t)·Hₙ·cos(ωₙ·t + V₀ₙ(t) − Gₙ)` using the J2000 epoch (`2000-01-01T12:00:00Z`) as the time base. #### Scenario: Empty constituent list rejected - **WHEN** `predictHeight([], time)` is called with zero constituents - **THEN** it MUST throw `TideError` with code `NO_CONSTITUENTS`. #### Scenario: Nodal corrections applied - **WHEN** `predictHeight` runs over named constituents (M2, S2, K1, …) - **THEN** the per-constituent node factor `f` and astronomical argument `V₀` MUST be derived from the time argument via `nodeFactor(name, time)` / `astronomicalArgument(name, time)` rather than treated as static. ### Requirement: Harmonic-constituent registry The package SHALL ship `CONSTITUENT_SPEEDS` (degrees/hour angular speeds) and `DOODSON_NUMBERS` tables for the canonical major constituents (M2, S2, N2, K2, K1, O1, P1, Q1, M4, …) re-exported from `./harmonic/harmonic-constituents`. ### Requirement: Equilibrium-tide physics The package SHALL expose `equilibriumTideHeight(planet, coord, time)` and `findEquilibriumExtremes(planet, coord, date, signal?)` modelling the solid-body equilibrium tide on a `PlanetConfig` ellipsoid (consumes `@suaveplan/geo-planet`). ### Requirement: ADMIRALTY UK Hydrographic Office client The package SHALL expose `AdmiraltyProvider` and `createAdmiraltyProvider` binding the ADMIRALTY UK Tidal API (`https://admiraltyapi.azure-api.net/uktidalapi/api/V1`) with: - `Ocp-Apim-Subscription-Key` header taken from constructor option `apiKey`. - Injectable `fetchFn` (defaults to `globalThis.fetch`). - Configurable `cacheTtlMs` (default `3_600_000`). #### Scenario: ADMIRALTY API key absent - **WHEN** `createAdmiraltyProvider({ apiKey: "" })` is called - **THEN** the resulting provider MUST throw `TideError` (`MISSING_API_KEY`) on first network call. ### Requirement: Provider factory surface The package SHALL expose three factories returning the unified `TidalProvider` shape from `@suaveplan/types`: - `createTiconProvider(constituents, meanSeaLevel?)` — offline harmonic. - `createPhysicsBasedProvider(planet)` — equilibrium-tide approximation. - `createAdmiraltyProvider(options)` — ADMIRALTY UK API. All three MUST satisfy `predictHeight(planet, coord, time)` and `getExtremes(planet, coord, date, signal?)`. ### Requirement: Tide-station search The package SHALL expose `findNearestStation(stations, latitude, longitude)` returning `NearestStationResult` with great-circle `distanceKm` computed via `@suaveplan/geo` haversine math. ### Requirement: Typed error surface The package SHALL expose a frozen `TideError` class with stable codes covering `NO_CONSTITUENTS`, `MISSING_API_KEY`, `STATION_NOT_FOUND`, and network/parse failure paths surfaced by the ADMIRALTY provider. ### Requirement: AbortSignal threading Every async provider call (`predictHeight`, `getExtremes`, `getNearestStation`, `getStations`) SHALL accept and honour an optional `AbortSignal`, aborting in-flight ADMIRALTY HTTP requests via the underlying `fetchFn`. ### Requirement: Universal-tier purity The package MUST NOT import browser-only or Node-only APIs at module top level — `fetch` is consumed only through the injectable `fetchFn` so the package runs unchanged in browser, Bun, and Node environments. ### Requirement: Test + coverage gates `bunx turbo lint typecheck test --filter=@suaveplan/tides` MUST be green and `bunx turbo test:coverage --filter=@suaveplan/tides` MUST hit 100% on touched modules before any release.