Files
suaveplan/openspec/archive/2026-05-11-enterprise-tides/proposal.md
2026-05-27 23:29:53 +01:00

5.5 KiB

OpenSpec Change: enterprise-tides

  • Change ID: enterprise-tides
  • Package: @suaveplan/tides
  • Category: universal
  • Target path: universal/geo/tides
  • Status: IN PROGRESS
  • Date: 2026-04-24
  • Reference spec: /mnt/s/web/suaveplan/enterprise/tides.md

Summary

Deliver @suaveplan/tides on the genesis branch from scratch at enterprise quality. The package provides UK tidal prediction using offline TICON harmonic constituent data and ADMIRALTY-compatible algorithms: load harmonic data for any UK port, compute water height at any minute over any date range, find high/low-water times within a window, compute tidal range and mean water, generate 24-hour or multi-day tide tables, and a streaming prediction generator. Fully offline — no network calls. The main-branch version sat at 97.82% coverage. Genesis adds structured errors, Zod schemas, OTel instrumentation, injectable clock, and property-based tests to reach 100%.


Current-state audit (genesis)

The package does not exist on genesis yet — this is a fresh build.


Available workspace deps on genesis

Package Role
@suaveplan/error (workspace:^) TidesError; TIDES.* error codes
@suaveplan/types (workspace:^) LoggerLike, Clock
@suaveplan/geo (workspace:^) LatLng type for port location
zod (catalog:forms) Schema validation
@opentelemetry/api (catalog:observability) Spans + counters

Dev deps: vitest, @vitest/coverage-istanbul, fast-check (all catalog:testing).


Public API surface

Core types

interface TidalPort {
  id: string                    // ADMIRALTY port code e.g. '0113'
  name: string
  location: LatLng
  constituents: TidalConstituent[]
  timezone: string              // IANA timezone
  meanHighWaterSprings: number  // metres
  meanLowWaterSprings: number
  chartDatum: number            // LAT in metres
}

interface TidalConstituent {
  name: string      // e.g. 'M2', 'S2', 'N2'
  amplitude: number // metres
  phase: number     // degrees
  speed: number     // degrees/hour
}

interface TidePrediction {
  time: Date
  height: number    // metres above chart datum
  type?: 'high' | 'low'
}

interface TideTable {
  portId: string
  date: string      // ISO date
  predictions: TidePrediction[]
  highWaters: TidePrediction[]
  lowWaters: TidePrediction[]
  range: number
  meanLevel: number
}

TidePredictor

class TidePredictor {
  constructor(options?: TidePredictorOptions)

  loadPort(port: TidalPort): void
  listPorts(): string[]

  predict(portId: string, time: Date): number     // metres at single instant
  predictRange(portId: string, from: Date, to: Date, intervalMinutes?: number): TidePrediction[]
  findExtrema(portId: string, from: Date, to: Date): TidePrediction[]
  generateTable(portId: string, date: Date): TideTable
  generateTables(portId: string, from: Date, to: Date): TideTable[]
  stream(portId: string, from: Date, to: Date, intervalMinutes?: number): AsyncGenerator<TidePrediction>
}

interface TidePredictorOptions {
  clock?: Clock
  logger?: LoggerLike
}

Data loaders

function loadBuiltinPorts(): TidalPort[]    // bundled TICON data for UK ports
function parseTiconData(raw: string): TidalPort[]
function harmonicHeight(constituents: TidalConstituent[], time: Date): number

Error codes

type TidesErrorCode =
  | 'TIDES_PORT_NOT_FOUND'
  | 'TIDES_INVALID_DATE_RANGE'
  | 'TIDES_INVALID_CONSTITUENT'
  | 'TIDES_PARSE_FAILED'
  | 'TIDES_PREDICTION_FAILED'

Genesis additions

  1. TidesError extends SuaveplanError with five stable codes.
  2. Zod schemas for TidalPort, TidalConstituent, TidePrediction, TideTable.
  3. Streaming stream() method — returns an AsyncGenerator<TidePrediction> for memory-efficient prediction over long date ranges.
  4. Injectable clock — TidePredictorOptions.clock allows time-travel in tests; predict() without a time argument uses clock.now().
  5. OTel instrumentation — span tides.predict_range with attributes tides.port_id, tides.interval_minutes; counter tides.table_generate_total; histogram tides.predict_duration_ms.
  6. Property-based tests — harmonic height at known published times (deterministic); extrema always come in alternating high/low order; prediction is a continuous function (no discontinuities > 0.5m per minute).

File layout

universal/geo/tides/
  package.json
  tsconfig.json
  vitest.config.ts
  src/
    index.ts
    error-codes.ts
    constants/
      index.ts              # DEGREES_PER_HOUR, G (gravitational const)
    types/
      common.ts
      common.md
    core/
      tide-predictor/
        tide-predictor.ts
        tide-predictor.md
        tide-predictor.types.ts
        tide-predictor.test.ts
        index.ts
      harmonic-engine/
        harmonic-engine.ts
        harmonic-engine.md
        harmonic-engine.test.ts
        index.ts
      extrema-finder/
        extrema-finder.ts
        extrema-finder.md
        extrema-finder.test.ts
        index.ts
    data/
      ticon-loader/
        ticon-loader.ts
        ticon-loader.md
        ticon-loader.test.ts
        index.ts
      builtin-ports.json    # bundled TICON data for UK ports
    __benchmarks__/
      tides.bench.ts
  tests/
    integration.test.ts
    property.test.ts

Impact

Downstream consumers: @suaveplan/ports (tidal windows for port scheduling).

Workspace dep prerequisites: @suaveplan/error, @suaveplan/types, @suaveplan/geo.