/** * Service for the keyless UK Environment Agency flood-monitoring API * (https://environment.data.gov.uk/flood-monitoring/), which publishes * observed 15-minute river levels from a dense real gauge network in England * and across the English border, under the Open Government Licence v3. * * **Routing position.** `get_river_conditions` routes US points to NOAA NWPS * and everything else to Open-Meteo Flood (GloFAS). This service is the third * arm: `auto` selects it inside the Great Britain routing box once the country * code resolves to `gb`, and `source: "ea"` forces it anywhere. There is no * cross-fallback — an observed level in metres and a modeled discharge in m³/s * are different claims about different quantities. * * **Contract, not garnish.** This is river-safety output. A failed EA fetch * **propagates** with a fixed sanitized message; it must never degrade to an * empty gauge list, because an empty gauge list reads as "no flooding here". * The *typical range* alone is garnish **within** that contract: a station * whose detail fetch fails renders its level without a range and says nothing * more, with no retry and no added latency on failure. * * **G7 — the threshold projection, and why it is the sharpest rule here.** * `/id/stations/{ref}?_view=full` returns the 24-hour-stable `stageScale` * **and** each measure's 15-minute-volatile `latestReading` in the same * response body. Caching that response whole at 24 h would serve a day-old * river level as current on a safety-critical surface. So `getStationDetail` * caches a **projected object carrying the threshold numbers only** — never * the raw response and never a spread of it. Nothing this method returns can * carry a reading, by construction rather than by discipline. * * The station *list* endpoint is safe to cache at 24 h for the same reason * inverted: its `measures` carry `@id`, `qualifier`, `unitName` and `period` * but **no** `latestReading` at all (verified live 2026-09-02). Levels come * from the separately-cached 15-minute bulk pull, joined on the measure URL. * * **One national bulk pull, never one request per station.** Measured * 2026-09-02: `/data/readings?latest¶meter=level` returns every latest * level reading in the network — 4,106 items, 1,305,560 bytes — in a single * request costing about the same as the station-list request it accompanies, * and one cached copy serves every British query. The geographic filters do * not exist: `/id/measures?lat&long&dist` and `/data/readings?latest&lat&long&dist` * both answer HTTP 400, "Did not recognize request parameters [dist, lat, long] * as valid for this endpoint". * * **G6 — cache unfiltered, filter at read.** Both the station list and the * bulk readings map are cached complete; the `riverName` filter and the * per-query selection run at read time in `src/utils/eaGauges.ts`. * * **Errors.** Plain fixed-message `Error`s, never `ApiError`: `ApiServiceName` * is a closed union and this is a peripheral service. No message or log * argument ever carries a URL or a raw axios error; logs carry `{ status, code }` * only. The API is keyless, so there is no secret to leak — the house pattern * is uniform and cheap to keep. */ import type { EAStation } from '../types/environmentAgency.js'; /** One latest reading, reduced to the two fields the render path uses. */ export interface EALatestReading { /** ISO 8601 observation time, as published. */ dateTime: string; /** Level in the measure's own `unitName`. */ value: number; } /** * The **only** thing `getStationDetail` returns, and therefore the only thing * that reaches the 24-hour `eaStationDetail` cache entry. It is a projection, * not a subset view of the response: there is no reading field on this type to * populate, so a future edit cannot accidentally cache one (G7). */ export interface EAStationThresholds { /** Datum the stage is measured against, metres. */ datum?: number; /** Top of the gauge's published typical range, metres. */ typicalRangeHigh?: number; /** Bottom of the gauge's published typical range, metres. */ typicalRangeLow?: number; /** Top of the gauge's published scale, metres. */ scaleMax?: number; } /** Station list plus whether the defensive cap trimmed it. */ export interface EAStationsResult { stations: EAStation[]; /** True when `MAX_STATIONS` trimmed the parsed list; the caller must disclose it. */ truncated: boolean; } /** Latest-readings map (keyed by measure URL) plus whether the cap trimmed it. */ export interface EALatestReadingsResult { readings: Map; /** True when `MAX_READINGS` trimmed the parsed list; the caller must disclose it. */ truncated: boolean; } export interface EnvironmentAgencyServiceConfig { timeout?: number; /** Base URL override, so tests can point at a fixture host. */ baseUrl?: string; } export declare class EnvironmentAgencyService { private client; private cache; private baseUrl; /** * Concurrent same-key pulls collapse onto one promise, deleted in `finally` * so a rejected pull is neither cached nor left behind for the next caller. */ private inFlight; constructor(config?: EnvironmentAgencyServiceConfig); /** * Map a request failure to a fixed message. Never includes a URL, a response * body, or a raw axios error. */ private toEAError; /** Log a failure with status/code only — never a URL, a body, or a message. */ private logFailure; /** * Cache-then-in-flight-then-fetch. The single-flight map is keyed by the same * cache key, so N concurrent callers make one request; the entry is removed in * `finally`, so a rejection is retried rather than remembered. */ private pull; /** * Every level-monitoring station within `distKm` of a point, **unfiltered**. * The `riverName` filter that establishes the tool's coverage claim runs at * read time, not here (G6). * * Cached at the 24-hour `stations` TTL, which is safe because this endpoint's * `measures` carry no `latestReading` — levels come from the 15-minute bulk * pull instead. */ getStationsNear(latitude: number, longitude: number, distKm: number): Promise; /** * The national bulk latest-level pull, cached whole and keyed by measure URL. * * One request serves every British query for the life of the 15-minute entry. * The map's key is the measure `@id`, which is exactly the value a station's * `measures[].@id` carries — verified live 2026-09-02, so the join is an * equality on a published identifier rather than a parsed convention. */ getLatestLevelReadings(): Promise; /** * The published typical range for one station, and **nothing else**. * * The upstream response also carries every measure's `latestReading`. That is * deliberately dropped here rather than returned and ignored by the caller: * this method's return type has no field a reading could occupy, so the * 24-hour cache entry cannot hold one (G7). * * `stageScale` is `string | object` across the two endpoints — a URL on the * station list, an object here. It is narrowed on `typeof === 'object'`; a * string yields no range rather than throwing. * * Returns `null` when the station publishes no usable `stageScale`. That is a * legitimate answer, not a failure: the caller renders the level without a * range. */ getStationDetail(stationReference: string): Promise; } //# sourceMappingURL=environmentAgency.d.ts.map