mostlyright 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../weather/src/index.ts","../../weather/src/_fetchers/awc.ts","../../weather/src/_fetchers/iem-cli.ts","../../core/src/internal/merge/observations.ts","../../core/src/internal/merge/climate.ts","../../weather/src/_parsers/cli.ts","../../weather/src/_fetchers/ghcnh.ts","../../weather/src/_parsers/_station_translator.ts","../../weather/src/_parsers/ghcnh.ts","../../weather/src/live/sources.ts","../../weather/src/live/_fetch.ts","../../weather/src/live/latest.ts","../../weather/src/live/stream.ts","../../weather/src/forecasts/iem-mos.ts","../../weather/src/forecasts/nwp-stub.ts","../../weather/src/forecasts/open-meteo-models.ts","../../weather/src/forecasts/open-meteo.ts","../../core/src/data/generated/stations.ts","../../core/src/discovery/availability.ts","../../core/src/discovery/international.ts","../../core/src/exceptions/index.ts","../../core/src/formats/toon.ts","../../core/src/discovery/snapshot.ts","../../core/src/discovery/data-version.ts","../../core/src/discovery/describe.ts","../../weather/src/dailyExtremes.ts","../../weather/src/obs.ts","../../markets/src/index.ts","../../markets/src/data/generated/kalshi-stations.ts","../../markets/src/data/generated/polymarket-city-stations.ts","../../markets/src/resolvers/kalshi-nhigh.ts","../../markets/src/resolvers/kalshi-nlow.ts","../../markets/src/kalshi-settlement.ts","../../../node_modules/.pnpm/idb@8.0.3/node_modules/idb/build/index.js","../../core/src/internal/cache/types.ts","../../core/src/internal/cache/memory.ts","../../core/src/internal/cache/indexeddb.ts","../../core/src/internal/cache/versionedCacheStore.ts","../../core/src/data/generated/stations.ts","../../core/src/snapshot.ts","../../core/src/internal/cache/skip-rules.ts","../../core/src/internal/cache/keys.ts","../../core/src/internal/cache/index.browser.ts","../../core/src/snapshot.ts","../../core/src/internal/pairs.ts","../src/research.types.ts","../src/research.ts","../src/mode2.ts","../../markets/src/polymarket/types.ts","../../markets/src/polymarket/resolver.ts","../../markets/src/polymarket/known-wrong-stations.ts","../src/compose.ts","../src/discover.ts","../../core/src/transforms/clip.ts","../../core/src/qc/crosscheck.ts","../src/index.ts"],"sourcesContent":["// @mostlyrightmd/weather — weather fetchers + parsers.\n//\n// TS-W1 ships AWC (Wave 3) + IEM CLI (Wave 4). TS-W2 Plan 01 adds IEM ASOS\n// (yearly-chunk historical METARs) + the IEM CSV parser. Subsequent TS-W2\n// plans add GHCNh + mergeObservations; TS-W3 adds the disk cache.\n\n/**\n * Placeholder version string from the TS-W0 Wave 1 scaffold. The\n * authoritative package version lives in `package.json#version`\n * (currently `0.1.0-rc.7`); this constant has not been bumped.\n */\nexport const version = \"0.0.0\";\n\n/**\n * Smoke-test export from the TS-W0 Wave 1 scaffold. Returns the literal\n * string `\"hello @mostlyrightmd/weather\"`. Retained so the published\n * package has at least one importable runtime export until the scaffold\n * is removed in a later phase.\n */\nexport function helloWeather(): string {\n return \"hello @mostlyrightmd/weather\";\n}\n\n// TS-W1 Wave 3 — AWC live METARs.\nexport {\n AWC_MAX_HOURS,\n AWC_METAR_URL,\n fetchAwcMetars,\n type AwcMetarRaw,\n type FetchAwcOptions,\n} from \"./_fetchers/awc.js\";\n// Shared row contract: `Observation.source` widened in TS-W2 Plan 01 to\n// `\"awc\" | \"iem\" | \"ghcnh\"`. Each parser still emits its own literal.\nexport {\n awcToObservation,\n icaoToStationCode,\n mapCloudCover,\n parseAwcVisibility,\n type Observation,\n} from \"./_parsers/awc.js\";\n\n// TS-W1 Wave 4 — IEM CLI fetcher + range + parser.\nexport {\n downloadCli,\n downloadCliRange,\n IEM_CLI_BASE_URL,\n IEM_CLI_POLITE_DELAY_MS,\n type CliRawRecord,\n type DownloadCliRangeOptions,\n} from \"./_fetchers/iem-cli.js\";\nexport {\n parseCliRecord,\n parseCliResponse,\n mergeClimate,\n inferReportType,\n HIGH_TEMP_MAX_F,\n HIGH_TEMP_MIN_F,\n LOW_TEMP_MAX_F,\n LOW_TEMP_MIN_F,\n type ClimateObservation,\n type ReportType,\n} from \"./_parsers/cli.js\";\n\n// TS-W2 Plan 01 — IEM ASOS yearly-chunk fetcher + chunker + CSV parser.\nexport {\n yearlyChunksExclusiveEnd,\n type IsoDate,\n} from \"./_fetchers/_iem_chunks.js\";\nexport {\n buildIemUrl,\n downloadIemAsos,\n IEM_BASE_URL,\n IEM_POLITE_DELAY_MS,\n type DownloadIemAsosOptions,\n type IemChunkResult,\n} from \"./_fetchers/iem-asos.js\";\nexport {\n iemToObservation,\n parseIemCsv,\n type IemCsvRow,\n type IemObservationTypeOverride,\n type IemToObservationOptions,\n} from \"./_parsers/iem.js\";\n\n// TS-W2 Plan 02 — GHCNh PSV fetcher + parser + station-id translator.\nexport {\n downloadGhcnh,\n downloadGhcnhRange,\n GHCNH_BASE_URL,\n NCEI_POLITE_DELAY_MS,\n type DownloadGhcnhRangeOptions,\n type GhcnhYearResult,\n} from \"./_fetchers/ghcnh.js\";\nexport {\n parseGhcnhPsv,\n parseGhcnhRow,\n ghcnhStationToCode,\n extractStationCode,\n SSID_COLUMNS,\n} from \"./_parsers/ghcnh.js\";\n\n// Phase 11 — `mostlyright.live` ticker surface (stream + latest).\nexport {\n POLITE_FLOORS_S,\n SOURCE_IDENTITY_TAGS,\n SUPPORTED_SOURCES,\n isLiveSource,\n latest,\n sourceTag,\n stream,\n validatePollSeconds,\n validateSource,\n type LatestOptions,\n type LiveObservation,\n type LiveSource,\n type LiveSourceTag,\n type StreamOptions,\n} from \"./live/index.js\";\nexport { LiveStreamError, NoLiveDataError } from \"@mostlyrightmd/core\";\n\n// Phase 17 PLAN-11 — IEM MOS forecast fetcher + NWP stub (v1.0 TS lane).\n// Available via the root barrel AND via the `@mostlyrightmd/weather/forecasts`\n// subpath (subpath bundle stays lean for browser callers; root pulls all).\nexport {\n forecastNwp,\n iemMosForecasts,\n openMeteoForecasts,\n OPEN_METEO_MODELS,\n OPEN_METEO_PREVIOUS_RUNS_URL,\n OPEN_METEO_SINGLE_RUNS_URL,\n OPEN_METEO_LIVE_URL,\n OPEN_METEO_SEAMLESS_URL,\n type ForecastNwpOptions,\n type IemMosModel,\n type IemMosOptions,\n type IemMosRow,\n type IemMosSource,\n type NwpModel,\n type OpenMeteoMode,\n type OpenMeteoModel,\n type OpenMeteoOptions,\n type OpenMeteoRow,\n type OpenMeteoSource,\n} from \"./forecasts/index.js\";\n\n// Phase 21 21-05 — dailyExtremes(station, from, to, opts?) wrapper matching\n// Python `mostlyright.international.daily_extremes`. Composes existing\n// fetchers + internationalDailyExtremes for the rollup; surfaces under\n// the weather barrel and `mostlyright` meta package.\nexport { dailyExtremes } from \"./dailyExtremes.js\";\nexport type {\n DailyExtremeRow,\n DailyExtremesMergeMode,\n DailyExtremesOptions,\n} from \"./dailyExtremes.types.js\";\n\n// Phase 21 21-04 — obs(station, from, to, opts?) Phase 7 ingest-planner\n// surface. Smart-routes between exact_window / warm_cache / hosted\n// strategies; matches Python `tw.weather.obs` signature.\nexport { obs, resolveAutoStrategy } from \"./obs.js\";\nexport type {\n ObsOptions,\n ObsRow,\n ObsSourceFilter,\n ObsStrategy,\n} from \"./obs.types.js\";\n","// AWC METAR HTTP fetcher — live observations from aviationweather.gov.\n//\n// Ported byte-faithfully from\n// `packages/weather/src/mostlyright/weather/_fetchers/awc.py::fetch_awc_metars`.\n//\n// =============================================================================\n// CORS posture: NONE (per .planning/research/TS-CORS-MATRIX.md)\n// =============================================================================\n// AWC's `aviationweather.gov` does NOT emit any `Access-Control-Allow-*` headers.\n// This means a pure browser-app (no extension) cannot call this endpoint via\n// `fetch()` directly — the browser blocks the response.\n//\n// Workarounds:\n// 1. Chrome extension (MV3 service worker): declare host permissions in\n// manifest.json — `\"host_permissions\": [\"https://aviationweather.gov/*\"]`.\n// MV3 service workers bypass CORS for hosts they have permission for.\n// 2. CORS proxy: deploy a thin Cloudflare Worker (or similar) that fronts\n// this endpoint and emits `Access-Control-Allow-Origin: *`. Then point\n// this fetcher at the proxy URL via the `urlOverride` option (TODO: when\n// added in a future wave).\n// 3. Node.js / Deno / Bun / Cloudflare Worker runtime: no CORS — works\n// out of the box.\n//\n// See `.planning/research/TS-CORS-MATRIX.md` §AWC and `docs/chrome-extension-integration.md`\n// for the canonical workaround guidance.\n// =============================================================================\n//\n// The AWC live endpoint serves only recent observations — at most ~168 hours\n// (7 days). For historical multi-day fetches use IEM ASOS (lands in TS-W4).\n//\n// Return contract: ReadonlyArray<AwcMetarRaw>. Empty array on 4xx, timeout, or\n// exhausted retries — NEVER throws (matches Python `fetch_awc_metars` so the\n// caller can degrade gracefully when AWC is down).\n\nimport { type FetchWithRetryOptions, TherminalError, fetchWithRetry } from \"@mostlyrightmd/core\";\n\n/** Canonical AWC METAR endpoint. */\nexport const AWC_METAR_URL = \"https://aviationweather.gov/api/data/metar\";\n\n/**\n * AWC live serves at most ~168 hours (7 days). Beyond that the endpoint\n * either silently truncates or returns an empty list — use IEM ASOS for\n * history.\n */\nexport const AWC_MAX_HOURS = 168;\n\n/**\n * Raw AWC METAR record as returned by the public JSON endpoint.\n *\n * Fields are documented loosely — the upstream payload is not formally\n * schema-versioned. We pass-through optional fields to the parser\n * (`_parsers/awc.ts`) which validates them against bounds.\n */\nexport interface AwcMetarRaw {\n /** Four-letter ICAO identifier (e.g. \"KNYC\"). REQUIRED. */\n icaoId: string;\n /** Observation time as Unix epoch seconds. REQUIRED. */\n obsTime: number;\n metarType?: \"METAR\" | \"SPECI\" | string;\n /** Wind direction in degrees, or \"VRB\" for variable. */\n wdir?: number | \"VRB\" | string;\n wspd?: number | null;\n wgst?: number | null;\n /** Altimeter setting in hPa. */\n altim?: number | null;\n /** Sea-level pressure in mb/hPa. */\n slp?: number | null;\n /** Temperature in Celsius. */\n temp?: number | null;\n /** Dewpoint in Celsius. */\n dewp?: number | null;\n /** Visibility — may be number, \"10+\", \"1/2\", \"2 1/4\", \"M1/4\", etc. */\n visib?: string | number | null;\n clouds?: ReadonlyArray<{ cover?: string; base?: number | null }>;\n /** Raw METAR text — includes remarks. */\n rawOb?: string | null;\n /** Weather codes (e.g. \"RA BR\"). */\n wxString?: string | null;\n /** Precipitation past hour (inches). \"T\" = trace. */\n precip?: number | \"T\" | string | null;\n /** QC bitmask field. */\n qcField?: number | null;\n}\n\nexport interface FetchAwcOptions extends FetchWithRetryOptions {\n /** Lookback window in hours. Default 168 (max). Values above `AWC_MAX_HOURS` are clamped. */\n hours?: number;\n}\n\n/**\n * Fetch recent METARs from AWC for one or more ICAO stations.\n *\n * Returns the raw JSON array as-is (typed as `AwcMetarRaw[]`); compose with\n * `awcToObservation` from `../_parsers/awc.ts` to map each entry to the\n * observation row schema.\n *\n * Behaviour mirrors Python `fetch_awc_metars`:\n * - empty `stationIcaos` → return `[]` immediately, no HTTP issued\n * - 4xx after retry exhaustion → `[]`\n * - 5xx / network errors after retry budget → `[]`\n * - non-array JSON body → `[]`\n * - NEVER throws (callers want graceful degradation)\n */\nexport async function fetchAwcMetars(\n stationIcaos: ReadonlyArray<string>,\n opts: FetchAwcOptions = {},\n): Promise<ReadonlyArray<AwcMetarRaw>> {\n if (stationIcaos.length === 0) {\n return [];\n }\n\n const hours = Math.min(opts.hours ?? AWC_MAX_HOURS, AWC_MAX_HOURS);\n // Encode each component (defensive — caller should already have validated\n // ICAOs via station registry, but the URL shape must be safe regardless).\n const idsCsv = stationIcaos.map((s) => encodeURIComponent(s)).join(\",\");\n const url = `${AWC_METAR_URL}?ids=${idsCsv}&format=json&taf=false&hours=${hours}`;\n\n // Strip `hours` from the options forwarded to fetchWithRetry so the type\n // is exactly `FetchWithRetryOptions`. Note: `hours` is consumed above.\n const { hours: _consumed, ...retryOpts } = opts;\n void _consumed;\n\n let response: Response;\n try {\n response = await fetchWithRetry(url, retryOpts);\n } catch (err) {\n // Any TherminalError (404, 400, 401, 403, 429-after-exhaustion, 5xx-after-\n // exhaustion) OR raw network error → return [] (mirror Python). This is\n // the \"graceful degradation\" contract; orchestration layer decides whether\n // to surface a SourceUnavailableError instead.\n if (err instanceof TherminalError) {\n return [];\n }\n // Re-raise abort errors so callers that pass AbortSignals can cancel.\n if (err instanceof DOMException && (err.name === \"AbortError\" || err.name === \"TimeoutError\")) {\n // For caller-initiated aborts we re-throw; otherwise the timeout was\n // composed in by fetchWithRetry and would already have been retried.\n if (opts.signal?.aborted) {\n throw err;\n }\n return [];\n }\n return [];\n }\n\n let data: unknown;\n try {\n data = await response.json();\n } catch {\n return [];\n }\n\n if (!Array.isArray(data)) {\n return [];\n }\n\n return data as ReadonlyArray<AwcMetarRaw>;\n}\n","// IEM CLI (NWS climate) historical fetcher — settlement-grade source.\n//\n// Ported from `packages/weather/src/mostlyright/weather/_fetchers/iem_cli.py`\n// (TS-W1 Wave 4). Uses the native `fetch` via `@mostlyrightmd/core`'s\n// `fetchWithRetry`, so this module runs in browsers, Node 20+, Cloudflare\n// Workers, and Deno.\n//\n// CORS posture: OPEN (Access-Control-Allow-Origin: *). See\n// `.planning/research/TS-CORS-MATRIX.md`.\n//\n// Granularity is whole-year (one HTTP request per station-year). Callers\n// that want a window filter the parsed records downstream — IEM's cli.py\n// endpoint does not support partial-year requests.\n\nimport { NotFoundError, fetchWithRetry } from \"@mostlyrightmd/core\";\nimport type { FetchWithRetryOptions } from \"@mostlyrightmd/core\";\n\n/** IEM cli.py JSON endpoint. Mirrors Python `IEM_CLI_BASE_URL`. */\nexport const IEM_CLI_BASE_URL = \"https://mesonet.agron.iastate.edu/json/cli.py\";\n\n/**\n * Polite delay (ms) between range requests. Mirrors Python\n * `IEM_CLI_POLITE_DELAY = 1.0` — IEM runs on a university server.\n */\nexport const IEM_CLI_POLITE_DELAY_MS = 1000;\n\n/**\n * Station code regex (3-4 uppercase letters). Mirrors\n * `STATION_CODE_RE` in `@mostlyrightmd/core/internal/bounds`. Inlined here\n * because that helper is intentionally a deep-import in core; we don't\n * want fetchers transitively pulling in the validators barrel.\n *\n * NOTE: IEM CLI expects a 4-letter ICAO (e.g. `KNYC`). The shared regex\n * also accepts 3-letter NWS codes — we tighten the check below.\n */\nconst STATION_CODE_RE = /^[A-Z]{3,4}$/;\n\n/**\n * Raw record shape returned by IEM cli.py (post-unwrap of `{results: [...]}`).\n *\n * We capture only the fields consumed by the parser; everything else is\n * preserved as `unknown` so forward-compat is automatic when IEM adds\n * new columns.\n */\nexport interface CliRawRecord {\n /** Local climate day, YYYY-MM-DD. */\n valid: string;\n /** Observed daily high °F. Sentinel: \"M\" or empty string for missing. */\n high?: number | \"M\" | \"\" | null;\n /** Observed daily low °F. Sentinel: \"M\" or empty string for missing. */\n low?: number | \"M\" | \"\" | null;\n /** Product identifier, e.g. \"202501160620-KFFC-CDUS42-CLIATL\". */\n product?: string | null;\n [key: string]: unknown;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction validateIcao(stationIcao: string): void {\n if (typeof stationIcao !== \"string\" || !STATION_CODE_RE.test(stationIcao)) {\n throw new Error(\n `station_icao=${JSON.stringify(\n stationIcao,\n )} does not match STATION_CODE_RE (3-4 uppercase letters); refusing to use as URL component`,\n );\n }\n}\n\nfunction unwrapResults(data: unknown): ReadonlyArray<CliRawRecord> {\n // IEM cli.py returns either a bare array or `{results: [...]}`. Empty\n // bodies (e.g. `[]` or `{results: []}`) are treated as \"no records\".\n if (Array.isArray(data)) {\n return data as ReadonlyArray<CliRawRecord>;\n }\n if (\n data !== null &&\n typeof data === \"object\" &&\n \"results\" in data &&\n Array.isArray((data as { results: unknown }).results)\n ) {\n return (data as { results: ReadonlyArray<CliRawRecord> }).results;\n }\n // The Python port raises ValueError here; mirror that with a plain Error\n // so callers can catch it without depending on a core exception class.\n throw new Error(\n `Unexpected IEM CLI response shape: ${\n data === null ? \"null\" : Array.isArray(data) ? \"array\" : typeof data\n }`,\n );\n}\n\n/**\n * Download IEM CLI JSON for one station-year.\n *\n * URL: `${IEM_CLI_BASE_URL}?station={icao}&year={year}`.\n *\n * Response may be wrapped as `{\"results\": [...]}` — we unwrap and return\n * the inner array so downstream parsers always see the same shape.\n *\n * Throws {@link NotFoundError} on HTTP 404 (no data for that year);\n * {@link downloadCliRange} catches and continues. Other transport errors\n * propagate as the structured exceptions defined by `fetchWithRetry`.\n */\nexport async function downloadCli(\n stationIcao: string,\n year: number,\n opts: FetchWithRetryOptions = {},\n): Promise<ReadonlyArray<CliRawRecord>> {\n validateIcao(stationIcao);\n const url = `${IEM_CLI_BASE_URL}?station=${stationIcao}&year=${year}`;\n const response = await fetchWithRetry(url, opts);\n const data = (await response.json()) as unknown;\n return unwrapResults(data);\n}\n\nexport interface DownloadCliRangeOptions extends FetchWithRetryOptions {\n /**\n * Delay (ms) between successive year requests. Defaults to\n * {@link IEM_CLI_POLITE_DELAY_MS}.\n */\n politenessMs?: number;\n}\n\n/**\n * Download CLI records for an inclusive year range.\n *\n * Skips 404s (IEM \"no data for this year\") so multi-year backfills do\n * not abort when a station has gaps — mirrors Python's\n * `download_cli_range` 404-skip behavior. Other HTTP errors propagate.\n *\n * Between requests we sleep `politenessMs` (default\n * {@link IEM_CLI_POLITE_DELAY_MS}).\n */\nexport async function downloadCliRange(\n stationIcao: string,\n startYear: number,\n endYear: number,\n opts: DownloadCliRangeOptions = {},\n): Promise<ReadonlyArray<CliRawRecord>> {\n if (endYear < startYear) {\n throw new Error(`endYear (${endYear}) must be >= startYear (${startYear})`);\n }\n validateIcao(stationIcao);\n\n const politenessMs = opts.politenessMs ?? IEM_CLI_POLITE_DELAY_MS;\n // Strip `politenessMs` so it isn't forwarded to fetchWithRetry's strict opts.\n const { politenessMs: _drop, ...fetchOpts } = opts;\n void _drop;\n\n const out: CliRawRecord[] = [];\n for (let year = startYear; year <= endYear; year++) {\n if (year > startYear && politenessMs > 0) {\n await sleep(politenessMs);\n }\n try {\n const records = await downloadCli(stationIcao, year, fetchOpts);\n out.push(...records);\n } catch (err) {\n if (err instanceof NotFoundError) {\n // No data for that year — log-equivalent silent skip. Continue.\n continue;\n }\n throw err;\n }\n }\n return out;\n}\n","// Observation source-priority dedup — AWC > IEM > GHCNh with strict-> + first-seen tiebreak.\n//\n// Byte-faithful TS port of\n// `packages/core/src/mostlyright/_internal/merge/observations.py::merge_observations`.\n//\n// Why this lives in @mostlyrightmd/core (not @mostlyrightmd/weather): the merge\n// policy is a mostlyright-wide invariant — every settlement-grade path that\n// joins multi-source observations depends on it. Putting it in core keeps\n// the dep direction clean (weather + meta → core), avoids a circular\n// import between weather/parsers and the orchestrator, and matches the\n// Python layout (`_internal/merge/`).\n//\n// Type strategy: a STRUCTURAL `ObservationKey` interface (4 fields) avoids\n// pulling the full `Observation` shape from @mostlyrightmd/weather into core.\n// The function is generic over `T extends ObservationKey` so callers can\n// pass the full row type without losing fields.\n\n/**\n * Source priority — strictly greater means \"wins on tiebreak\".\n * Verbatim from Python `SOURCE_PRIORITY` at\n * `packages/core/src/mostlyright/_internal/merge/observations.py:18`.\n *\n * Frozen so consumers cannot mutate the policy at runtime.\n */\nexport const SOURCE_PRIORITY: Readonly<Record<string, number>> = Object.freeze({\n awc: 3,\n iem: 2,\n ghcnh: 1,\n});\n\n/**\n * Subset of `Observation` that `mergeObservations` needs to dedup +\n * priority-rank. The four fields below form the dedup key (first three)\n * plus the source string that the priority map keys on. Unknown sources\n * resolve to priority 0 (lose to any known source).\n */\nexport interface ObservationKey {\n readonly station_code: string;\n readonly observed_at: string;\n readonly observation_type: string;\n readonly source: string;\n}\n\n/**\n * Deduplicate observation rows by `(station_code, observed_at,\n * observation_type)`, keeping the row whose source has the highest\n * {@link SOURCE_PRIORITY}.\n *\n * Tiebreak: **STRICT `>`** — on equal priority the FIRST row seen wins.\n * This is the byte-faithful semantics of Python v0.14.1, which preserved\n * input order through `dict.values()`. TS uses `Map.values()` with the\n * same insertion-order guarantee.\n *\n * The order-dependent tiebreak is intentional: callers can rely on a\n * canonical fetch order (AWC live → IEM yearly chunk → GHCNh yearly\n * chunk) to deterministically pick the AWC row over IEM/GHCNh on tied\n * priority (which only happens for unknown sources in practice).\n *\n * Unknown source strings resolve to priority 0 (lose to any of awc/iem/\n * ghcnh). Empty input returns an empty array.\n *\n * Output order is `Map.values()` insertion order — first row per key\n * wins both priority AND output position when no later row outranks it.\n *\n * Generic over `T extends ObservationKey` so the consumer (e.g. the\n * `research()` orchestrator in TS-W2 Plan 06) can pass the full\n * `Observation` row type without losing fields. The returned array is\n * a freshly-allocated `T[]`, not the input array.\n */\nexport function mergeObservations<T extends ObservationKey>(\n rows: ReadonlyArray<T>,\n): ReadonlyArray<T> {\n const best = new Map<string, T>();\n for (const row of rows) {\n // Null-byte separator: station_code is `[A-Z]{3,4}`, observed_at is\n // `\\d{4}-...Z`, observation_type is `METAR|SPECI` — none can carry a\n // literal `\\x00`. Defense-in-depth against upstream parser bugs that\n // might leak weird characters.\n const key = `${row.station_code}\\x00${row.observed_at}\\x00${row.observation_type}`;\n const existing = best.get(key);\n if (existing === undefined) {\n best.set(key, row);\n continue;\n }\n const priority = SOURCE_PRIORITY[row.source] ?? 0;\n const existingPriority = SOURCE_PRIORITY[existing.source] ?? 0;\n if (priority > existingPriority) {\n // STRICT `>`: on equal priority the first-seen row stays.\n best.set(key, row);\n }\n }\n return Array.from(best.values());\n}\n","// Climate-row dedup — keep highest `report_type_priority` per (station, date).\n//\n// Migrated from `packages-ts/weather/src/_parsers/cli.ts::mergeClimate`\n// (TS-W1 Wave 4) to its canonical home under @mostlyrightmd/core/internal/merge\n// in TS-W2 Plan 04. Behavior is unchanged — only the module location moves.\n//\n// Byte-faithful TS port of `mostlyright._internal.merge.climate.merge_climate`\n// (Python), itself a lift of `_dedup_climate_rows` from\n// `monorepo-v0.14.1/ingest/storage/parquet.py:477-494`.\n//\n// Type strategy: structural `ClimateKey` interface (3 fields) so this\n// module does not pull `ClimateObservation` from @mostlyrightmd/weather into\n// @mostlyrightmd/core. Callers pass the full row type and the generic\n// preserves it.\n\n/**\n * Subset of `ClimateObservation` that `mergeClimate` needs.\n * The first two fields form the dedup key; `report_type_priority` is the\n * tiebreak field (codegen-sourced from `REPORT_TYPE_PRIORITY`).\n */\nexport interface ClimateKey {\n readonly station_code: string;\n readonly observation_date: string;\n readonly report_type_priority: number;\n}\n\n/**\n * Deduplicate climate rows by `(station_code, observation_date)`.\n *\n * Keeps the row with the highest `report_type_priority` using **STRICT `>`**\n * (not `>=`). First-seen wins at equal priority — this preserves the\n * overnight `final` (which IS the Kalshi settlement value) when a\n * `preliminary` arrives first in input order.\n *\n * Generic over `T extends ClimateKey` so consumers can pass the full\n * `ClimateObservation` shape without losing fields.\n *\n * Empty input returns an empty array.\n */\nexport function mergeClimate<T extends ClimateKey>(rows: ReadonlyArray<T>): ReadonlyArray<T> {\n const best = new Map<string, T>();\n for (const row of rows) {\n // Null-byte separator — station_code is `[A-Z]{3,4}`, observation_date\n // is `YYYY-MM-DD`; neither can carry a literal `\\x00`.\n const key = `${row.station_code}\\x00${row.observation_date}`;\n const existing = best.get(key);\n if (existing === undefined) {\n best.set(key, row);\n continue;\n }\n // Strict `>`; first-seen wins on equal priority.\n if (row.report_type_priority > existing.report_type_priority) {\n best.set(key, row);\n }\n }\n return Array.from(best.values());\n}\n","// IEM CLI daily climate report parser.\n//\n// Ported from `packages/weather/src/mostlyright/weather/_climate.py` —\n// THE Kalshi settlement source. Report-type priority determines dedup:\n// `final` (3.0) overwrites `preliminary` (1.0), but a second `final`\n// never overwrites the first (strict `>`, first-seen wins at equal\n// priority). The overnight `final` IS the Kalshi settlement value.\n//\n// `CLIMATE_REPORT_TYPE_PRIORITY` is consumed from `@mostlyrightmd/core`'s\n// codegen output — do not re-define here.\n\nimport { CLIMATE_REPORT_TYPE_PRIORITY } from \"@mostlyrightmd/core\";\n\nimport type { CliRawRecord } from \"../_fetchers/iem-cli.js\";\n\n/** Climate temp bounds from `specs/climate.json`. Inclusive. */\nexport const HIGH_TEMP_MIN_F = -60;\nexport const HIGH_TEMP_MAX_F = 150;\nexport const LOW_TEMP_MIN_F = -80;\nexport const LOW_TEMP_MAX_F = 130;\n\nexport type ReportType = \"final\" | \"ncei_final\" | \"correction\" | \"preliminary\" | \"estimated\";\n\nexport interface ClimateObservation {\n /** Station code (3-letter NWS or 4-letter ICAO, caller's choice). */\n station_code: string;\n /** Local climate day, YYYY-MM-DD. */\n observation_date: string;\n /** Daily high °F, rounded to int. `null` when missing or out-of-bounds. */\n high_temp_f: number | null;\n /** Daily low °F, rounded to int. `null` when missing or out-of-bounds. */\n low_temp_f: number | null;\n /** Inferred report type. */\n report_type: ReportType;\n /**\n * Numeric priority for dedup (final=3, ncei_final=2.5, correction=2,\n * preliminary=1, estimated=0). Sourced from\n * `CLIMATE_REPORT_TYPE_PRIORITY` in `@mostlyrightmd/core` codegen.\n */\n report_type_priority: number;\n /** Always `\"iem\"` for CLI records. */\n source: \"iem\";\n /** Raw NWS product identifier when present. */\n product_id: string | null;\n /** ISO 8601 UTC issuance time parsed from `product[:12]`, else `null`. */\n issued_at: string | null;\n}\n\n// Pre-compiled regexes — mirror Python's module-level `re.compile`.\nconst PRODUCT_TS_RE = /^(\\d{12})/;\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Parse the first-12-character product timestamp to a UTC `Date`.\n *\n * Format: `\"202501160620-KFFC-CDUS42-CLIATL\"` → `2025-01-16T06:20:00Z`.\n * Returns `null` for empty, malformed, or invalid calendar timestamps.\n */\nfunction parseProductTimestamp(product: string): Date | null {\n if (!product) return null;\n const m = PRODUCT_TS_RE.exec(product);\n if (m === null) return null;\n const ts = m[1];\n if (ts === undefined) return null;\n const year = Number.parseInt(ts.slice(0, 4), 10);\n const month = Number.parseInt(ts.slice(4, 6), 10);\n const day = Number.parseInt(ts.slice(6, 8), 10);\n const hour = Number.parseInt(ts.slice(8, 10), 10);\n const minute = Number.parseInt(ts.slice(10, 12), 10);\n if (\n !Number.isFinite(year) ||\n !Number.isFinite(month) ||\n !Number.isFinite(day) ||\n !Number.isFinite(hour) ||\n !Number.isFinite(minute)\n ) {\n return null;\n }\n // Reject hour/minute out of range — `Date.UTC` would silently roll forward.\n if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59) {\n return null;\n }\n const millis = Date.UTC(year, month - 1, day, hour, minute, 0, 0);\n if (!Number.isFinite(millis)) return null;\n const d = new Date(millis);\n // Round-trip check rejects e.g. \"20250230\" (Feb 30) silently rolling to Mar 2.\n if (\n d.getUTCFullYear() !== year ||\n d.getUTCMonth() !== month - 1 ||\n d.getUTCDate() !== day ||\n d.getUTCHours() !== hour ||\n d.getUTCMinutes() !== minute\n ) {\n return null;\n }\n return d;\n}\n\n/**\n * Parse an ISO date-only string (\"YYYY-MM-DD\") as a UTC `Date`. Returns\n * `null` if the string is missing/malformed or if the calendar date does\n * not exist (e.g. \"2025-02-30\"). Mirrors Python `date.fromisoformat`.\n */\nfunction parseObservationDate(observationDate: string): Date | null {\n if (!DATE_RE.test(observationDate)) return null;\n const year = Number.parseInt(observationDate.slice(0, 4), 10);\n const month = Number.parseInt(observationDate.slice(5, 7), 10);\n const day = Number.parseInt(observationDate.slice(8, 10), 10);\n if (month < 1 || month > 12 || day < 1 || day > 31) return null;\n const millis = Date.UTC(year, month - 1, day);\n const d = new Date(millis);\n if (d.getUTCFullYear() !== year || d.getUTCMonth() !== month - 1 || d.getUTCDate() !== day) {\n return null;\n }\n return d;\n}\n\n/**\n * Infer report type from product timestamp vs observation date.\n *\n * Rules (byte-faithful port of Python `_climate.infer_report_type`):\n * - No product → `\"preliminary\"` (safe default).\n * - Unparseable product or observation date → `\"preliminary\"`.\n * - Issued same day as observation → `\"preliminary\"`.\n * - Issued the next day, 04:00–10:00 UTC → `\"final\"` (overnight CLI window).\n * - Issued the next day outside that window → `\"correction\"`.\n * - Issued >1 day later → `\"correction\"`.\n */\nexport function inferReportType(\n product: string | null | undefined,\n observationDate: string,\n): ReportType {\n if (!product) return \"preliminary\";\n\n const issued = parseProductTimestamp(product);\n if (issued === null) return \"preliminary\";\n\n const obs = parseObservationDate(observationDate);\n if (obs === null) return \"preliminary\";\n\n // Compare issued *date* vs observation date in UTC days.\n const issuedDayUtc = Date.UTC(issued.getUTCFullYear(), issued.getUTCMonth(), issued.getUTCDate());\n const obsDayUtc = obs.getTime(); // already a UTC midnight from parseObservationDate\n const deltaDays = Math.round((issuedDayUtc - obsDayUtc) / 86_400_000);\n\n if (deltaDays <= 0) return \"preliminary\";\n if (deltaDays === 1) {\n const hour = issued.getUTCHours();\n if (hour >= 4 && hour <= 10) return \"final\";\n return \"correction\";\n }\n return \"correction\";\n}\n\n/** Parse a temperature sentinel into an integer °F or `null`. */\nfunction parseTemp(val: unknown): number | null {\n if (val === null || val === undefined || val === \"M\" || val === \"\") return null;\n const n = typeof val === \"number\" ? val : Number(val);\n if (!Number.isFinite(n)) return null;\n // Python `round(float(val))` uses banker's rounding (round-half-to-even).\n // CLI temps are int-valued in practice (e.g. 45, -3); the sub-degree edge\n // case is rare enough that `Math.round` (half-up) matches expectations.\n return Math.round(n);\n}\n\n/**\n * Parse one IEM CLI record into a {@link ClimateObservation}.\n *\n * Returns `null` if:\n * - `valid` is missing, non-string, or not a real calendar date, OR\n * - **both** high and low end up `null` after bounds checks.\n *\n * Mirrors Python `parse_cli_record`.\n */\nexport function parseCliRecord(\n record: CliRawRecord,\n stationCode: string,\n): ClimateObservation | null {\n const observationDate = record.valid;\n if (typeof observationDate !== \"string\" || observationDate.length === 0) return null;\n if (parseObservationDate(observationDate) === null) return null;\n\n let high = parseTemp(record.high);\n let low = parseTemp(record.low);\n\n if (high !== null && (high < HIGH_TEMP_MIN_F || high > HIGH_TEMP_MAX_F)) {\n high = null;\n }\n if (low !== null && (low < LOW_TEMP_MIN_F || low > LOW_TEMP_MAX_F)) {\n low = null;\n }\n\n if (high === null && low === null) return null;\n\n const product =\n typeof record.product === \"string\" && record.product.length > 0 ? record.product : null;\n\n const reportType = inferReportType(product, observationDate);\n const priority = CLIMATE_REPORT_TYPE_PRIORITY[reportType];\n // `priority` should always resolve — every ReportType is keyed in\n // CLIMATE_REPORT_TYPE_PRIORITY by construction. Guard for codegen drift.\n if (priority === undefined) {\n throw new Error(\n `report type ${JSON.stringify(reportType)} missing from CLIMATE_REPORT_TYPE_PRIORITY (codegen drift)`,\n );\n }\n\n let issuedAt: string | null = null;\n if (product !== null) {\n const issuedDt = parseProductTimestamp(product);\n if (issuedDt !== null) {\n // Format `YYYY-MM-DDTHH:MM:SSZ` to match Python's strftime output.\n issuedAt = `${issuedDt.toISOString().slice(0, 19)}Z`;\n }\n }\n\n return {\n station_code: stationCode,\n observation_date: observationDate,\n high_temp_f: high,\n low_temp_f: low,\n report_type: reportType,\n report_type_priority: priority,\n source: \"iem\",\n product_id: product,\n issued_at: issuedAt,\n };\n}\n\n/**\n * Parse a full IEM CLI response (post-unwrap) into climate observations,\n * filtering out records where both temps are missing or the date is\n * invalid. Mirrors Python `parse_cli_response`.\n */\nexport function parseCliResponse(\n data: ReadonlyArray<CliRawRecord>,\n stationCode: string,\n): ReadonlyArray<ClimateObservation> {\n const out: ClimateObservation[] = [];\n for (const record of data) {\n const parsed = parseCliRecord(record, stationCode);\n if (parsed !== null) out.push(parsed);\n }\n return out;\n}\n\n// Backward-compat re-export. mergeClimate canonically lives at\n// @mostlyrightmd/core/internal/merge as of TS-W2 Plan 04. Existing imports\n// from @mostlyrightmd/weather continue to work; new code should prefer\n// `import { mergeClimate } from \"@mostlyrightmd/core/internal/merge\"`.\nexport { mergeClimate } from \"@mostlyrightmd/core/internal/merge\";\n","// NCEI GHCNh per-year PSV fetcher — single station-year and inclusive range.\n//\n// Byte-faithful TS port of Python\n// `packages/weather/src/mostlyright/weather/_fetchers/ghcnh.py::download_ghcnh`\n// and `download_ghcnh_range`, with the following deliberate adaptations:\n//\n// 1. No disk cache. Python writes per-station PSVs under\n// `dest_dir/{station_id}/GHCNh_{station}_{year}.psv`; the TS port\n// returns the PSV body in-memory via `{ stationId, year, psv }`.\n// The disk-cache layer lands in TS-W3 — out of scope here.\n// 2. Station-id validation at the URL boundary uses an inline regex\n// (`^[A-Z0-9-]{1,32}$`) matching the path-traversal guard from Python\n// `_internal/_bounds.py::validate_ghcnh_id_for_path`. There's no path\n// here in TS-W2, so the regex check is the defense-in-depth measure.\n//\n// 404-as-no-data behavior in `downloadGhcnhRange` is byte-faithful — NCEI\n// returns 404 for station-years that have no published data (typical for\n// recent partial years or pre-1973 stations), and the range function\n// silently skips them. Other HTTP errors propagate.\n//\n// CORS posture: NCEI GHCNh emits `Access-Control-Allow-Origin: *` per\n// `.planning/research/TS-CORS-MATRIX.md` §GHCNh — OPEN. Works in browsers,\n// Node 20+, Cloudflare Workers, Deno.\n\nimport { NotFoundError, fetchWithRetry } from \"@mostlyrightmd/core\";\nimport type { FetchWithRetryOptions } from \"@mostlyrightmd/core\";\n\n/** NCEI GHCNh public archive base URL (no trailing slash). Mirrors Python `GHCNH_BASE_URL`. */\nexport const GHCNH_BASE_URL =\n \"https://www.ncei.noaa.gov/oa/global-historical-climatology-network/hourly/access\";\n\n/**\n * Polite delay (ms) between consecutive NCEI HTTP requests in range mode.\n * Mirrors Python `NCEI_POLITE_DELAY = 1.0` (s). The delay fires AFTER each\n * successful response; 404s and network errors do NOT pay the tax.\n */\nexport const NCEI_POLITE_DELAY_MS = 1000;\n\n/**\n * GHCNh station-id boundary regex. Permits the USAF-WBAN form\n * (`744860-94789`), the 11-char NCEI id (`USW00094789`), and short ICAO-like\n * tokens. Rejects path separators, NUL, and any whitespace before the value\n * ever reaches a URL path segment.\n */\nconst GHCNH_STATION_ID_RE = /^[A-Z0-9-]{1,32}$/;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction validateStationId(stationId: string): void {\n if (typeof stationId !== \"string\" || !GHCNH_STATION_ID_RE.test(stationId)) {\n throw new Error(\n `station_id=${JSON.stringify(\n stationId,\n )} does not match GHCNH_STATION_ID_RE (uppercase alphanumeric + hyphen, 1-32 chars); refusing to use as URL component`,\n );\n }\n}\n\nfunction buildGhcnhUrl(stationId: string, year: number): string {\n return `${GHCNH_BASE_URL}/by-year/${year}/psv/GHCNh_${stationId}_${year}.psv`;\n}\n\n/** One downloaded station-year PSV. */\nexport interface GhcnhYearResult {\n readonly stationId: string;\n readonly year: number;\n /** Raw PSV body returned by NCEI (text/plain, pipe-delimited). */\n readonly psv: string;\n}\n\nexport interface DownloadGhcnhRangeOptions extends FetchWithRetryOptions {\n /**\n * Delay (ms) between successive year requests. Defaults to\n * {@link NCEI_POLITE_DELAY_MS}. Set to `0` in unit tests.\n */\n politenessMs?: number;\n}\n\n/**\n * Download a NOAA GHCNh PSV for one station-year.\n *\n * URL: `${GHCNH_BASE_URL}/by-year/{year}/psv/GHCNh_{stationId}_{year}.psv`.\n *\n * @throws {NotFoundError} when NCEI returns 404 (no data for this station-year).\n * Callers that want 404-as-skip behavior should use {@link downloadGhcnhRange}.\n * @throws If `stationId` does not match `GHCNH_STATION_ID_RE`.\n * @throws Whatever `fetchWithRetry` propagates on persistent network/HTTP errors.\n */\nexport async function downloadGhcnh(\n stationId: string,\n year: number,\n opts: FetchWithRetryOptions = {},\n): Promise<GhcnhYearResult> {\n validateStationId(stationId);\n const url = buildGhcnhUrl(stationId, year);\n const response = await fetchWithRetry(url, opts);\n const psv = await response.text();\n return { stationId, year, psv };\n}\n\n/**\n * Download GHCNh PSVs for an inclusive year range.\n *\n * Iterates `[startYear, endYear]`. Years that return 404 (no data for that\n * station-year) are silently skipped — they do NOT appear in the output\n * array. Other HTTP errors propagate.\n *\n * `endYear < startYear` returns `[]` with zero HTTP requests.\n *\n * Polite delay fires AFTER each successful response only.\n */\nexport async function downloadGhcnhRange(\n stationId: string,\n startYear: number,\n endYear: number,\n opts: DownloadGhcnhRangeOptions = {},\n): Promise<ReadonlyArray<GhcnhYearResult>> {\n validateStationId(stationId);\n\n if (endYear < startYear) {\n return [];\n }\n\n const politenessMs = opts.politenessMs ?? NCEI_POLITE_DELAY_MS;\n const { politenessMs: _pmDrop, ...fetchOpts } = opts;\n void _pmDrop;\n\n const out: GhcnhYearResult[] = [];\n for (let year = startYear; year <= endYear; year++) {\n let result: GhcnhYearResult;\n try {\n result = await downloadGhcnh(stationId, year, fetchOpts);\n } catch (err) {\n if (err instanceof NotFoundError) {\n // 404 → silent skip. Mirrors Python L160-166 (log + continue).\n continue;\n }\n throw err;\n }\n out.push(result);\n if (politenessMs > 0) {\n await sleep(politenessMs);\n }\n }\n return out;\n}\n","// GHCNh Source_Station_ID → station_code translator.\n//\n// Byte-faithful TS port of Python\n// `packages/weather/src/mostlyright/weather/_ghcnh.py::ghcnh_station_to_code`\n// (L133-145) and `_extract_station_code` (L148-155) + the `_SSID_COLUMNS`\n// tuple verbatim.\n//\n// GHCNh PSV rows carry up to 11 Source_Station_ID columns (per variable);\n// we walk them in priority order and return the first ICAO-prefixed value\n// that resolves to a valid station code.\n\nimport { STATION_CODE_RE } from \"@mostlyrightmd/core/internal/bounds\";\n\nimport { icaoToStationCode } from \"./awc.js\";\n\n/**\n * Source_Station_ID column priority. Order matches Python `_SSID_COLUMNS`\n * tuple at `_ghcnh.py:45-57` EXACTLY (temperature first, then dew_point,\n * wind_speed, wind_direction, sea_level_pressure, altimeter, visibility,\n * then four sky_cover_summation layers).\n */\nexport const SSID_COLUMNS = [\n \"temperature_Source_Station_ID\",\n \"dew_point_temperature_Source_Station_ID\",\n \"wind_speed_Source_Station_ID\",\n \"wind_direction_Source_Station_ID\",\n \"sea_level_pressure_Source_Station_ID\",\n \"altimeter_Source_Station_ID\",\n \"visibility_Source_Station_ID\",\n \"sky_cover_summation_1_Source_Station_ID\",\n \"sky_cover_summation_2_Source_Station_ID\",\n \"sky_cover_summation_3_Source_Station_ID\",\n \"sky_cover_summation_4_Source_Station_ID\",\n] as const;\n\n/**\n * Extract the 3-letter station code from a GHCNh Source_Station_ID value.\n *\n * `\"ICAO-KJFK\"` → `\"JFK\"` (strip prefix, apply ICAO→code conversion)\n * `\"744860-94789\"` → `null` (WMO USAF-WBAN format, no ICAO prefix)\n * `\"\"` / `\"ICAO-\"` → `null`\n *\n * Returns `null` for any input that does not resolve to a value matching\n * `STATION_CODE_RE` (3-4 uppercase letters).\n */\nexport function ghcnhStationToCode(sourceStationId: string): string | null {\n if (!sourceStationId || !sourceStationId.startsWith(\"ICAO-\")) {\n return null;\n }\n const icao = sourceStationId.slice(5);\n if (!icao) {\n return null;\n }\n const code = icaoToStationCode(icao);\n if (STATION_CODE_RE.test(code)) {\n return code;\n }\n return null;\n}\n\n/**\n * Walk `SSID_COLUMNS` in priority order and return the first non-null\n * result from `ghcnhStationToCode`. Returns `null` if every column misses.\n */\nexport function extractStationCode(row: Readonly<Record<string, string>>): string | null {\n for (const col of SSID_COLUMNS) {\n const ssid = row[col] ?? \"\";\n const code = ghcnhStationToCode(ssid);\n if (code !== null) {\n return code;\n }\n }\n return null;\n}\n","// GHCNh PSV parser — pipe-delimited body in, Observation rows out.\n//\n// Byte-faithful TS port of Python\n// `packages/weather/src/mostlyright/weather/_ghcnh.py::parse_ghcnh_row` and\n// `parse_ghcnh_file`. The TS port consumes the PSV body in-memory (a\n// string returned by `downloadGhcnh`); the Python version walks a file\n// handle but the semantics are identical.\n//\n// GHCNh ships pre-QC'd data with per-variable `*_Quality_Code` columns.\n// We keep raw observations only (`{0, 1, 4, 5, \"\"}`) and drop QC-flagged\n// variables (`{2, 3, 6, 7, I, P, R, U}`). The empty-string acceptance is\n// load-bearing — many GHCNh rows omit Quality_Code entirely and would\n// otherwise drop silently (parity case 5).\n//\n// Unit conversions: m/s → kt, km → mi, m → ft, mm → in, cm → in.\n// Sky-cover layers 1-4 + per-layer baseht; present-weather codes from\n// pres_wx_AW{1..3}; raw_metar extracted from REM column.\n//\n// CSV implementation: hand-rolled pipe-split + header-map. NO `csv` dep —\n// the parser preserves bundle-size discipline (TS Architect rubric §2).\n\nimport {\n MAX_RAW_METAR_LEN,\n MAX_VISIBILITY_MILES,\n MAX_WX_CODES_LEN,\n MAX_YEAR,\n MIN_YEAR,\n SKY_BASE_MAX_FT,\n SLP_MAX_MB,\n SLP_MIN_MB,\n TEMP_MAX_C,\n TEMP_MIN_C,\n boundedFloat,\n boundedFloatMin,\n boundedInt,\n} from \"@mostlyrightmd/core/internal/bounds\";\nimport { celsiusToFahrenheit, hpaToInhg } from \"@mostlyrightmd/core/internal/convert\";\n\nimport { extractStationCode } from \"./_station_translator.js\";\nimport { type Observation, mapCloudCover } from \"./awc.js\";\n\nexport {\n SSID_COLUMNS,\n extractStationCode,\n ghcnhStationToCode,\n} from \"./_station_translator.js\";\n\n// ---------------------------------------------------------------------------\n// Constants (byte-faithful with Python `_ghcnh.py`)\n// ---------------------------------------------------------------------------\n\nconst MS_TO_KT = 1 / 0.514444;\nconst KM_TO_MI = 1 / 1.60934;\nconst M_TO_FT = 3.28084;\nconst MM_TO_IN = 1 / 25.4;\nconst CM_TO_IN = 1 / 2.54;\n\n/** ISO 8601 form GHCNh uses in its DATE column. Optional trailing `Z`. */\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z?$/;\n\n/** Quality_Code values that pass the raw-only filter. Empty string also accepts. */\nconst ALLOWED_QC: ReadonlySet<string> = new Set([\"0\", \"1\", \"4\", \"5\"]);\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction safeFloat(val: string): number | null {\n if (!val || val === \"NA\") return null;\n const f = Number(val);\n return Number.isFinite(f) ? f : null;\n}\n\nfunction safeInt(val: string): number | null {\n const f = safeFloat(val);\n return f === null ? null : Math.round(f);\n}\n\n/**\n * Quality_Code acceptance gate. Accepts {0, 1, 4, 5} and empty string.\n * Rejects {2, 3, 6, 7} and letter flags {I, P, R, U}. Empty-string\n * acceptance is critical — without it, ungraded rows drop silently\n * (parity case 5 KMSY Hurricane Francine).\n */\nfunction isQcAccepted(qc: string): boolean {\n const stripped = qc.trim();\n if (!stripped) return true;\n return ALLOWED_QC.has(stripped);\n}\n\n/** `\"SCT:04;\"` → `\"SCT\"` via {@link mapCloudCover}. */\nfunction parseSkyCover(val: string): string | null {\n if (!val) return null;\n let code: string;\n if (val.includes(\":\")) {\n const colonIdx = val.indexOf(\":\");\n code = val.slice(0, colonIdx);\n } else {\n code = val.endsWith(\";\") ? val.slice(0, -1) : val;\n }\n return mapCloudCover(code);\n}\n\n/** sky_cover_summation_baseht_N (meters) → integer feet (rounded, bounded). */\nfunction parseSkyBaseht(val: string): number | null {\n const meters = safeFloat(val);\n if (meters === null || meters < 0) return null;\n const feet = Math.round(meters * M_TO_FT);\n return feet <= SKY_BASE_MAX_FT ? feet : null;\n}\n\n/**\n * Parse `pres_wx_AW{1..3}` columns. GHCNh format is `\"TS:90\"` / `\"+RA:02\"`\n * (METAR text + WMO AW code) — extract the leading METAR token, filter\n * bare-numeric WMO codes, honor per-column Quality_Code (`{3, P}` reject).\n */\nfunction parseWeatherCodes(row: Record<string, string>): string | null {\n const codes: string[] = [];\n for (let i = 1; i <= 3; i++) {\n const val = row[`pres_wx_AW${i}`] ?? \"\";\n if (!val) continue;\n const qc = (row[`pres_wx_AW${i}_Quality_Code`] ?? \"\").trim();\n if (qc === \"3\" || qc === \"P\") continue;\n const code = val.includes(\":\") ? val.slice(0, val.indexOf(\":\")) : val;\n if (!code) continue;\n // Skip bare-numeric WMO codes (`\"02\"`, `\"+90\"`, `\"-10\"`).\n const numericProbe = code.replace(/^[+-]/, \"\");\n if (numericProbe.length > 0 && /^\\d+$/.test(numericProbe)) continue;\n codes.push(code);\n }\n if (codes.length === 0) return null;\n const result = codes.join(\" \");\n return result.length > MAX_WX_CODES_LEN ? result.slice(0, MAX_WX_CODES_LEN) : result;\n}\n\n/**\n * Calendar-validity round-trip for `YYYY-MM-DDTHH:MM:SS[Z]`. Returns true if\n * the parts round-trip exactly through `Date.UTC` — rejects `2025-02-30`,\n * `2025-13-01`, etc. Mirrors Python's `datetime.fromisoformat` exception path.\n */\nfunction isValidCalendarDate(iso: string): boolean {\n const stripped = iso.endsWith(\"Z\") ? iso.slice(0, -1) : iso;\n // Format-guard already applied by DATE_RE — substring math is safe.\n const year = Number.parseInt(stripped.slice(0, 4), 10);\n const month = Number.parseInt(stripped.slice(5, 7), 10);\n const day = Number.parseInt(stripped.slice(8, 10), 10);\n const hour = Number.parseInt(stripped.slice(11, 13), 10);\n const minute = Number.parseInt(stripped.slice(14, 16), 10);\n const second = Number.parseInt(stripped.slice(17, 19), 10);\n if (month < 1 || month > 12) return false;\n if (day < 1 || day > 31) return false;\n if (hour > 23 || minute > 59 || second > 59) return false;\n const millis = Date.UTC(year, month - 1, day, hour, minute, second, 0);\n if (!Number.isFinite(millis)) return false;\n const d = new Date(millis);\n return (\n d.getUTCFullYear() === year &&\n d.getUTCMonth() === month - 1 &&\n d.getUTCDate() === day &&\n d.getUTCHours() === hour &&\n d.getUTCMinutes() === minute &&\n d.getUTCSeconds() === second\n );\n}\n\n// ---------------------------------------------------------------------------\n// Row parser\n// ---------------------------------------------------------------------------\n\n/**\n * Parse one GHCNh PSV row (header→cell dict) into the canonical Observation\n * row. Returns `null` if the row should be skipped:\n * - Station code unresolvable from any `Source_Station_ID` column\n * - DATE missing / malformed / calendar-invalid / out-of-year-range\n * - ALL four key vars (temperature, dew_point_temperature, wind_speed,\n * sea_level_pressure) fail Quality_Code\n *\n * Output dict-key order is preserved verbatim to keep downstream\n * `JSON.stringify` byte-stable across SDKs.\n */\nexport function parseGhcnhRow(row: Readonly<Record<string, string>>): Observation | null {\n const stationCode = extractStationCode(row);\n if (stationCode === null) return null;\n\n const dateStr = row.DATE ?? \"\";\n if (!dateStr || !DATE_RE.test(dateStr)) return null;\n if (!isValidCalendarDate(dateStr)) return null;\n\n const year = Number.parseInt(dateStr.slice(0, 4), 10);\n if (year < MIN_YEAR || year > MAX_YEAR) return null;\n\n const observedAt = dateStr.endsWith(\"Z\") ? dateStr : `${dateStr}Z`;\n\n const reportType = row.temperature_Report_Type ?? \"\";\n const observationType: \"METAR\" | \"SPECI\" = reportType === \"FM16\" ? \"SPECI\" : \"METAR\";\n\n // Per-variable Quality_Code gates.\n const tempOk = isQcAccepted(row.temperature_Quality_Code ?? \"\");\n const dewpOk = isQcAccepted(row.dew_point_temperature_Quality_Code ?? \"\");\n const wspdOk = isQcAccepted(row.wind_speed_Quality_Code ?? \"\");\n const wdirOk = isQcAccepted(row.wind_direction_Quality_Code ?? \"\");\n const wgustOk = isQcAccepted(row.wind_gust_Quality_Code ?? \"\");\n const slpOk = isQcAccepted(row.sea_level_pressure_Quality_Code ?? \"\");\n const altimOk = isQcAccepted(row.altimeter_Quality_Code ?? \"\");\n const visOk = isQcAccepted(row.visibility_Quality_Code ?? \"\");\n const precipOk = isQcAccepted(row.precipitation_Quality_Code ?? \"\");\n const snowOk = isQcAccepted(row.snow_depth_Quality_Code ?? \"\");\n\n // Skip-row gate (Python L200-201).\n if (!(tempOk || dewpOk || wspdOk || slpOk)) return null;\n\n // Temperature (no rounding, bounded; °C native).\n const tempC = tempOk\n ? boundedFloat(safeFloat(row.temperature ?? \"\"), TEMP_MIN_C, TEMP_MAX_C, { field: \"temp_c\" })\n : null;\n const dewpC = dewpOk\n ? boundedFloat(safeFloat(row.dew_point_temperature ?? \"\"), TEMP_MIN_C, TEMP_MAX_C, {\n field: \"dewpoint_c\",\n })\n : null;\n // Phase 18: GHCNh tempF conversion path deferred from Phase 18 fix.\n // NCEI publishes hourly observations with `temperature` in °C as the\n // primary field; whether the underlying ASOS source data is integer-°F\n // native (with NCEI doing the °F→°C conversion server-side) is NOT\n // documented in the NCEI data dictionary. Until NCEI native-units\n // documentation is obtained, the existing celsiusToFahrenheit path is\n // preserved here as the safe default. AWC + IEM Tgroup-recovery fixes\n // (Phase 18 PREC-01 + PREC-02) cover the two sources whose raw METAR\n // carries an unambiguous Tgroup. See\n // .planning/phases/18-precision-fix-asos-integer-fahrenheit/18-CONTEXT.md\n // for the deferral rationale.\n const tempF = celsiusToFahrenheit(tempC);\n const dewpF = celsiusToFahrenheit(dewpC);\n\n // Wind direction (degrees, bounded 0..360).\n const windDir = wdirOk ? boundedInt(safeInt(row.wind_direction ?? \"\"), 0, 360) : null;\n\n // Wind speed/gust (m/s → kt, rounded, bounded).\n const windSpeedMs = wspdOk ? safeFloat(row.wind_speed ?? \"\") : null;\n const windGustMs = wgustOk ? safeFloat(row.wind_gust ?? \"\") : null;\n const windSpeedKt = boundedInt(\n windSpeedMs !== null ? Math.round(windSpeedMs * MS_TO_KT) : null,\n 0,\n 200,\n );\n const windGustKt = boundedInt(\n windGustMs !== null ? Math.round(windGustMs * MS_TO_KT) : null,\n 0,\n 250,\n );\n\n // Pressure.\n let slp = slpOk ? safeFloat(row.sea_level_pressure ?? \"\") : null;\n if (slp !== null && (slp < SLP_MIN_MB || slp > SLP_MAX_MB)) {\n slp = null;\n }\n const altimHpa = altimOk ? safeFloat(row.altimeter ?? \"\") : null;\n const altimInhg = hpaToInhg(altimHpa);\n\n // Visibility (km → mi, non-negative, clamped at MAX_VISIBILITY_MILES).\n const visKm = visOk ? safeFloat(row.visibility ?? \"\") : null;\n let visMiles: number | null = null;\n if (visKm !== null && visKm >= 0) {\n visMiles = Math.min(visKm * KM_TO_MI, MAX_VISIBILITY_MILES);\n }\n\n // Precipitation (mm → in, non-negative).\n const precipMm = precipOk ? safeFloat(row.precipitation ?? \"\") : null;\n const precipInches = precipMm !== null ? boundedFloatMin(precipMm * MM_TO_IN, 0.0) : null;\n\n // Snow depth (cm → in, non-negative).\n const snowCm = snowOk ? safeFloat(row.snow_depth ?? \"\") : null;\n const snowInches = snowCm !== null ? boundedFloatMin(snowCm * CM_TO_IN, 0.0) : null;\n\n // Sky cover layers 1-4 with per-layer QC.\n const skyCovers: Array<string | null> = [];\n const skyBases: Array<number | null> = [];\n for (let i = 1; i <= 4; i++) {\n const covQc = isQcAccepted(row[`sky_cover_summation_${i}_Quality_Code`] ?? \"\");\n const baseQc = isQcAccepted(row[`sky_cover_summation_baseht_${i}_Quality_Code`] ?? \"\");\n skyCovers.push(covQc ? parseSkyCover(row[`sky_cover_summation_${i}`] ?? \"\") : null);\n skyBases.push(baseQc ? parseSkyBaseht(row[`sky_cover_summation_baseht_${i}`] ?? \"\") : null);\n }\n\n // Weather codes.\n const weatherCodes = parseWeatherCodes(row as Record<string, string>);\n\n // Raw METAR from REM column. GHCNh wraps the METAR/SPECI in a prefix\n // like \"MET2025-09-10 14:51:00 METAR KMSY 101451Z ...\" — slice from the\n // leading METAR/SPECI marker so raw_metar starts with the canonical form.\n const rem = row.REM ?? \"\";\n let rawMetar: string | null = null;\n if (rem) {\n const idxMetar = rem.indexOf(\"METAR \");\n const idxSpeci = rem.indexOf(\"SPECI \");\n let cleaned: string;\n if (idxMetar >= 0 && (idxSpeci < 0 || idxMetar < idxSpeci)) {\n cleaned = rem.slice(idxMetar);\n } else if (idxSpeci >= 0) {\n cleaned = rem.slice(idxSpeci);\n } else {\n cleaned = rem;\n }\n rawMetar = cleaned.length > MAX_RAW_METAR_LEN ? cleaned.slice(0, MAX_RAW_METAR_LEN) : cleaned;\n }\n\n return {\n station_code: stationCode,\n observed_at: observedAt,\n observation_type: observationType,\n source: \"ghcnh\",\n temp_c: tempC,\n dewpoint_c: dewpC,\n temp_f: tempF,\n dewpoint_f: dewpF,\n wind_dir_degrees: windDir,\n wind_speed_kt: windSpeedKt,\n wind_gust_kt: windGustKt,\n altimeter_inhg: altimInhg,\n sea_level_pressure_mb: slp,\n sky_cover_1: skyCovers[0] ?? null,\n sky_base_1_ft: skyBases[0] ?? null,\n sky_cover_2: skyCovers[1] ?? null,\n sky_base_2_ft: skyBases[1] ?? null,\n sky_cover_3: skyCovers[2] ?? null,\n sky_base_3_ft: skyBases[2] ?? null,\n sky_cover_4: skyCovers[3] ?? null,\n sky_base_4_ft: skyBases[3] ?? null,\n visibility_miles: visMiles,\n weather_codes: weatherCodes,\n precip_1hr_inches: precipInches,\n peak_wind_gust_kt: null,\n peak_wind_dir: null,\n peak_wind_time: null,\n snow_depth_inches: snowInches,\n qc_field: null,\n raw_metar: rawMetar,\n };\n}\n\n/**\n * Parse a GHCNh PSV body string into Observation rows.\n *\n * Hand-rolled pipe-split — no `csv` dep. Empty body / header-only → `[]`.\n * Rows that {@link parseGhcnhRow} skips are omitted (no null entries).\n */\nexport function parseGhcnhPsv(psvBody: string): ReadonlyArray<Observation> {\n if (!psvBody) return [];\n // Normalize CRLF → LF, then split. Skip blank lines.\n const lines = psvBody.replace(/\\r/g, \"\").split(\"\\n\");\n let headerIdx = -1;\n for (let i = 0; i < lines.length; i++) {\n if ((lines[i] as string).length > 0) {\n headerIdx = i;\n break;\n }\n }\n if (headerIdx < 0) return [];\n const header = (lines[headerIdx] as string).split(\"|\");\n\n const out: Observation[] = [];\n for (let i = headerIdx + 1; i < lines.length; i++) {\n const line = lines[i] as string;\n if (line.length === 0) continue;\n const cells = line.split(\"|\");\n const row: Record<string, string> = {};\n for (let c = 0; c < header.length; c++) {\n const key = header[c] as string;\n row[key] = c < cells.length ? (cells[c] as string) : \"\";\n }\n const obs = parseGhcnhRow(row);\n if (obs !== null) out.push(obs);\n }\n return out;\n}\n","// Phase 11 — per-source registry + polite floors.\n//\n// Mirrors Python `mostlyright/live/_sources.py`. Source set + floor values are\n// kept byte-faithful with Python so cross-SDK consumers see the same\n// invariant. Any divergence triggers the dual-SDK parity gate.\n\n/** Canonical ordered tuple of supported sources. Order matters — keep AWC first. */\nexport const SUPPORTED_SOURCES = [\"awc\", \"iem\"] as const;\n\n/** Validated source enum derived from `SUPPORTED_SOURCES`. */\nexport type LiveSource = (typeof SUPPORTED_SOURCES)[number];\n\n/**\n * Minimum allowed poll cadence per source, in seconds.\n *\n * - AWC: 30s — aviationweather.gov has no documented rate limit but 30s is\n * the empirically-validated floor that won't trip anti-abuse heuristics.\n * - IEM: 60s — mesonet.agron.iastate.edu is a university server; IEM docs\n * explicitly ask for reasonable headroom above 1 req/s.\n */\nexport const POLITE_FLOORS_S: Readonly<Record<LiveSource, number>> = {\n awc: 30,\n iem: 60,\n};\n\n/**\n * Canonical per-source `source` field tag emitted on every observation row.\n *\n * `\"awc.live\"` / `\"iem.live\"` are the live-channel identity tags — distinct\n * from the archive-channel `\"awc\"` / `\"iem\"` written by the historical\n * fetchers. Cross-SDK parity: these match Python `SOURCE_IDENTITY_TAGS`.\n */\nexport const SOURCE_IDENTITY_TAGS = {\n awc: \"awc.live\",\n iem: \"iem.live\",\n} as const satisfies Readonly<Record<LiveSource, string>>;\n\nexport type LiveSourceTag = (typeof SOURCE_IDENTITY_TAGS)[LiveSource];\n\n/**\n * Normalize and validate a `source` option.\n *\n * @param source - Caller-supplied source string. `undefined`/`null` defaults\n * to the first entry in `SUPPORTED_SOURCES` (AWC). Case-insensitive.\n * @returns The normalized lowercase source name (one of `SUPPORTED_SOURCES`).\n * @throws `Error` when the source is not in `SUPPORTED_SOURCES`.\n */\nexport function validateSource(source: string | null | undefined): LiveSource {\n if (source === undefined || source === null) {\n return SUPPORTED_SOURCES[0];\n }\n const normalized = source.trim().toLowerCase();\n if (!isLiveSource(normalized)) {\n throw new Error(\n `unknown live source ${JSON.stringify(source)}; supported: ${JSON.stringify(\n SUPPORTED_SOURCES,\n )}`,\n );\n }\n return normalized;\n}\n\n/** Type guard: narrow a string to `LiveSource`. */\nexport function isLiveSource(s: string): s is LiveSource {\n return (SUPPORTED_SOURCES as ReadonlyArray<string>).includes(s);\n}\n\n/**\n * Apply the polite-floor invariant to a caller-supplied cadence.\n *\n * @param pollSeconds - Caller-supplied cadence. `undefined`/`null` → use the floor.\n * @param source - A *validated* source name (call `validateSource` first).\n * @returns The cadence to use, in seconds.\n * @throws `Error` when `pollSeconds` is below the polite floor.\n */\nexport function validatePollSeconds(\n pollSeconds: number | null | undefined,\n source: LiveSource,\n): number {\n const floor = POLITE_FLOORS_S[source];\n if (pollSeconds === undefined || pollSeconds === null) {\n return floor;\n }\n // Reject `NaN` and non-finite values BEFORE the floor comparison.\n // `NaN < floor` returns false (all NaN comparisons are false), so the\n // naive check would silently let `pollSeconds=NaN` through; `setTimeout(NaN)`\n // then fires immediately and the loop hammers the upstream — a hard\n // polite-floor violation. Same defence rejects `±Infinity`.\n if (!Number.isFinite(pollSeconds)) {\n throw new Error(\n `pollSeconds=${pollSeconds} is not a finite number; polite floor ${floor}s required for source=${JSON.stringify(\n source,\n )}`,\n );\n }\n if (pollSeconds < floor) {\n throw new Error(\n `pollSeconds=${pollSeconds} below polite floor ${floor}s for source=${JSON.stringify(\n source,\n )}`,\n );\n }\n return pollSeconds;\n}\n\n/** Map a validated source name to its canonical row-level identity tag. */\nexport function sourceTag(source: LiveSource): LiveSourceTag {\n return SOURCE_IDENTITY_TAGS[source];\n}\n","// Phase 11 — per-source live fetch dispatch (internal).\n//\n// Wraps the existing AWC + IEM fetchers and re-tags each parsed row with\n// the live-channel source identity (`\"awc.live\"` / `\"iem.live\"`). Errors\n// inside the fetcher are NOT caught here — callers (`stream`, `latest`)\n// have different error semantics and handle their own try/catch.\n\nimport { fetchAwcMetars } from \"../_fetchers/awc.js\";\nimport type { Observation } from \"../_parsers/awc.js\";\nimport { awcToObservation } from \"../_parsers/awc.js\";\n\nimport { type LiveSource, sourceTag } from \"./sources.js\";\nimport type { LiveObservation } from \"./types.js\";\n\n/**\n * Accept \"KNYC\" or \"NYC\" — emit the 4-letter ICAO form for fetchers.\n *\n * Defensive: trim + uppercase + add leading `K` for the 3-letter US ID\n * shortform. AWC accepts only ICAO; IEM accepts both, but normalizing here\n * keeps cache keys + log messages stable.\n */\nexport function normalizeStation(station: string): string {\n const s = station.trim().toUpperCase();\n if (s.length === 3) return `K${s}`;\n return s;\n}\n\n/** Re-tag a parsed `Observation` row with the live-channel source. */\nfunction asLiveObservation(obs: Observation, tag: LiveObservation[\"source\"]): LiveObservation {\n // Spread + override — the canonical Observation is `readonly`, so we\n // produce a new object rather than mutating.\n return { ...obs, source: tag };\n}\n\n/** Poll AWC once for the given station and return parsed observation rows. */\nexport async function fetchAwcLatest(station: string): Promise<LiveObservation[]> {\n const icao = normalizeStation(station);\n const raw = await fetchAwcMetars([icao], { hours: 1 });\n const tag = sourceTag(\"awc\");\n const rows: LiveObservation[] = [];\n for (const m of raw) {\n const obs = awcToObservation(m);\n if (obs !== null) {\n rows.push(asLiveObservation(obs, tag));\n }\n }\n return rows;\n}\n\n/** Today's UTC midnight as an `YYYY-MM-DD` ISO date string. */\nfunction todayUtcIso(): string {\n const d = new Date();\n const y = d.getUTCFullYear();\n const m = String(d.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(d.getUTCDate()).padStart(2, \"0\");\n return `${y}-${m}-${day}`;\n}\n\n/** Add one day to a `YYYY-MM-DD` UTC ISO string. */\nfunction nextDayIso(iso: string): string {\n const [y, m, d] = iso.split(\"-\").map(Number);\n // biome-ignore lint/style/noNonNullAssertion: split-by-`-` on a YYYY-MM-DD literal always yields 3 parts\n const dt = new Date(Date.UTC(y!, m! - 1, d!));\n dt.setUTCDate(dt.getUTCDate() + 1);\n const ny = dt.getUTCFullYear();\n const nm = String(dt.getUTCMonth() + 1).padStart(2, \"0\");\n const nd = String(dt.getUTCDate()).padStart(2, \"0\");\n return `${ny}-${nm}-${nd}`;\n}\n\n/** Subtract one day from a `YYYY-MM-DD` UTC ISO string. */\nfunction previousDayIso(iso: string): string {\n const [y, m, d] = iso.split(\"-\").map(Number);\n // biome-ignore lint/style/noNonNullAssertion: split-by-`-` on a YYYY-MM-DD literal always yields 3 parts\n const dt = new Date(Date.UTC(y!, m! - 1, d!));\n dt.setUTCDate(dt.getUTCDate() - 1);\n const py = dt.getUTCFullYear();\n const pm = String(dt.getUTCMonth() + 1).padStart(2, \"0\");\n const pd = String(dt.getUTCDate()).padStart(2, \"0\");\n return `${py}-${pm}-${pd}`;\n}\n\n/**\n * Poll IEM once for the given station and return parsed observation rows.\n *\n * Uses a direct one-day URL via `buildIemUrl` + `fetchWithRetry` rather than\n * `downloadIemAsos` because the TS `downloadIemAsos` normalizes the caller's\n * `start` to `${start.slice(0, 4)}-01-01` for cache-key stability — that's\n * the right call for `research()` but wrong for a live tick (would download\n * the full calendar year on every poll). Module import is dynamic so the\n * browser AWC-only bundle doesn't pull the IEM parser tree.\n */\nexport async function fetchIemLatest(station: string): Promise<LiveObservation[]> {\n const [{ fetchWithRetry }, { STATION_CODE_RE }, { buildIemUrl }, { parseIemCsv }] =\n await Promise.all([\n import(\"@mostlyrightmd/core\"),\n import(\"@mostlyrightmd/core/internal/bounds\"),\n import(\"../_fetchers/iem-asos.js\"),\n import(\"../_parsers/iem.js\"),\n ]);\n const icao = normalizeStation(station);\n const stationCode = icao.length === 4 && icao.startsWith(\"K\") ? icao.slice(1) : icao;\n // Validate the station code at the URL boundary BEFORE any HTTP call —\n // mirrors `downloadIemAsos::validateIcao`. We bypass `downloadIemAsos` (to\n // skip its year-normalization) but the validation guard MUST still apply\n // so a malformed station like `KNYC&data=foo` cannot inject IEM URL params.\n if (!STATION_CODE_RE.test(stationCode)) {\n throw new Error(\n `station=${JSON.stringify(\n stationCode,\n )} does not match STATION_CODE_RE (3-4 uppercase letters); refusing to use as IEM URL component`,\n );\n }\n const todayIso = todayUtcIso();\n // Include the PREVIOUS UTC day in the lookup window (iter-4 codex fix):\n // shortly after 00:00 UTC the current-day window is empty (IEM has no\n // observations ingested yet) but a minutes-old METAR exists for the\n // prior UTC day. A today-only fetch would always return empty in that\n // window. With (yesterday, tomorrow), the IEM endpoint serves the full\n // [yesterday 00:00Z, tomorrow 00:00Z) span (day2 is exclusive).\n const startIso = previousDayIso(todayIso);\n const endIso = nextDayIso(todayIso);\n // Mirror Python's METAR + SPECI fetch: IEM strips the SPECI keyword from\n // the raw METAR text and serves SPECIs only via report_type=4, so a\n // routine-only fetch would miss intra-hour specials and the `latest()`\n // pick could return an older METAR when a fresher SPECI exists.\n const tag = sourceTag(\"iem\");\n const rows: LiveObservation[] = [];\n for (const reportType of [3, 4] as const) {\n const url = buildIemUrl(stationCode, startIso, endIso, reportType);\n const response = await fetchWithRetry(url);\n const csv = await response.text();\n const override = reportType === 3 ? \"METAR\" : \"SPECI\";\n const obs = parseIemCsv(csv, { observationTypeOverride: override });\n for (const row of obs) {\n rows.push(asLiveObservation(row, tag));\n }\n }\n return rows;\n}\n\n/** Dispatch to the per-source fetch. */\nexport async function fetchLatest(station: string, source: LiveSource): Promise<LiveObservation[]> {\n switch (source) {\n case \"awc\":\n return fetchAwcLatest(station);\n case \"iem\":\n return fetchIemLatest(station);\n }\n}\n\n/**\n * Pick the row with the largest `observed_at`; SPECI > METAR at ties.\n *\n * `observed_at` is ISO 8601 ending in `Z` — lexicographic sort matches\n * chronological. Ties on `observed_at` resolve to SPECI (special report)\n * over METAR (routine) since SPECI is issued for a significant change.\n */\nexport function pickMostRecent(rows: ReadonlyArray<LiveObservation>): LiveObservation | null {\n if (rows.length === 0) return null;\n // biome-ignore lint/style/noNonNullAssertion: length guarded above\n let best = rows[0]!;\n for (let i = 1; i < rows.length; i++) {\n // biome-ignore lint/style/noNonNullAssertion: i bounded by rows.length\n const cur = rows[i]!;\n if (cur.observed_at > best.observed_at) {\n best = cur;\n } else if (\n cur.observed_at === best.observed_at &&\n cur.observation_type === \"SPECI\" &&\n best.observation_type !== \"SPECI\"\n ) {\n best = cur;\n }\n }\n return best;\n}\n","// Phase 11 — one-shot `latest()` fetch.\n//\n// Mirrors Python `mostlyright.live.latest`. Single-source poll: hit AWC or\n// IEM ONCE, parse the response, return the most-recent observation row\n// with its live source identity tag. No fusion, no cache, no QC.\n\nimport { NoLiveDataError } from \"@mostlyrightmd/core\";\n\nimport { fetchLatest, normalizeStation, pickMostRecent } from \"./_fetch.js\";\nimport { type LiveSource, sourceTag, validateSource } from \"./sources.js\";\nimport type { LiveObservation } from \"./types.js\";\n\nexport interface LatestOptions {\n /**\n * Live source to poll. `\"awc\"` (default, fastest) or `\"iem\"` (~10-min\n * delay; useful when AWC is down). Case-insensitive.\n */\n readonly source?: LiveSource | string | null;\n}\n\n/**\n * Return the most-recent observation row for `station` from a SINGLE source.\n *\n * Same fetch path as {@link stream}, but returns once instead of looping.\n * Use this for cron-style polling where you want one fresh observation per\n * invocation.\n *\n * @param station - ICAO (`\"KNYC\"`) or 3-letter US ID (`\"NYC\"`). Case-insensitive.\n * @param opts - Optional `{ source }`.\n *\n * @throws `Error` when `opts.source` is unknown.\n * @throws {@link NoLiveDataError} when the upstream returned no observations\n * for the station — payload carries the resolved `station` and live\n * `source` tag for branching.\n */\nexport async function latest(station: string, opts: LatestOptions = {}): Promise<LiveObservation> {\n const src: LiveSource = validateSource(opts.source ?? undefined);\n const rows = await fetchLatest(station, src);\n const picked = pickMostRecent(rows);\n if (picked === null) {\n throw new NoLiveDataError(\n `no live data for station=${JSON.stringify(station)} source=${JSON.stringify(src)}`,\n {\n station: normalizeStation(station),\n source: sourceTag(src),\n },\n );\n }\n return picked;\n}\n","// Phase 11 — `stream()` async generator.\n//\n// Mirrors Python `mostlyright.live.stream`. Continuous poll loop over a\n// single source. Yields each fresh observation exactly once (dedup by\n// `observed_at`), then sleeps for the polite-floor cadence.\n//\n// Cancellation: callers can `break` out of `for await` or call `.return()`\n// on the iterator. The polite-floor sleep uses an abortable Promise so the\n// loop terminates promptly rather than waiting out the full cadence.\n\nimport { fetchLatest, pickMostRecent } from \"./_fetch.js\";\nimport {\n type LiveSource,\n validatePollSeconds,\n validateSource,\n} from \"./sources.js\";\nimport type { LiveObservation } from \"./types.js\";\n\nexport interface StreamOptions {\n /**\n * Live source to poll. `\"awc\"` (default) or `\"iem\"`. Case-insensitive.\n */\n readonly source?: LiveSource | string | null;\n\n /**\n * Override the polite-floor cadence. Must be `>=` the per-source floor\n * (AWC=30, IEM=60). When omitted, uses the floor for the active source.\n */\n readonly pollSeconds?: number | null;\n\n /**\n * Optional `AbortSignal` for clean cancellation. When fired, the current\n * polite-floor sleep is interrupted and the generator returns. The current\n * in-flight fetch (if any) is allowed to complete — `AbortSignal` is not\n * threaded into the underlying fetchers (yet) since the v0.14.1 fetchers\n * are synchronous and wrapped via the platform fetch.\n */\n readonly signal?: AbortSignal;\n}\n\n/**\n * Abortable sleep helper — resolves on timeout OR on `signal.abort`.\n *\n * Critical: the abort listener MUST be removed once the sleep resolves\n * naturally (timeout fired), otherwise long-running streams accumulate\n * one listener per polling cycle until the signal aborts (or forever if\n * the stream is closed via `.return()` without aborting). Without the\n * removal, Node emits `MaxListenersExceededWarning` at 11 cycles + holds\n * onto closure memory indefinitely.\n */\nasync function sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\n if (signal?.aborted) return;\n await new Promise<void>((resolve) => {\n const onAbort = () => {\n clearTimeout(t);\n // Listener was registered with `{ once: true }` so we don't need\n // to remove it explicitly here, but we DO need to keep this branch\n // for the timeout case below.\n resolve();\n };\n const t = setTimeout(() => {\n // Remove the abort listener before resolving so it doesn't leak\n // past this cycle. `removeEventListener` is a no-op if the listener\n // wasn't attached (e.g. signal === undefined).\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Yield fresh observations for `station` from a SINGLE source on a\n * polite-floor cadence.\n *\n * The loop:\n * 1. Validate `source` + `pollSeconds` (throws BEFORE first poll).\n * 2. Poll once.\n * 3. If the most-recent observation's `observed_at` differs from the last\n * one yielded, yield it. Otherwise skip (dedup).\n * 4. `await sleep(pollSeconds)`.\n * 5. Loop.\n *\n * Empty responses (network error, fetcher returned `[]`) DO NOT abort the\n * stream — they're treated as \"nothing fresh yet\" and the loop continues\n * after the polite-floor sleep. To get a single-shot failure path, use\n * {@link latest}.\n *\n * @throws `Error` BEFORE the first poll when `opts.source` is unsupported\n * or `opts.pollSeconds` is below the polite floor.\n */\nexport async function* stream(\n station: string,\n opts: StreamOptions = {},\n): AsyncGenerator<LiveObservation> {\n const src: LiveSource = validateSource(opts.source ?? undefined);\n const cadenceS = validatePollSeconds(opts.pollSeconds ?? undefined, src);\n const cadenceMs = cadenceS * 1000;\n // Validate the station upfront — the IEM path throws on a malformed ICAO\n // (e.g. \"KN&data=foo\"). Without this guard, the stream's blanket catch\n // would swallow that ValueError and silently spin forever instead of\n // telling the caller their station is invalid.\n const icaoUpper = station.trim().toUpperCase();\n const stationCheck = icaoUpper.length === 3 ? `K${icaoUpper}` : icaoUpper;\n const STATION_CODE_RE = /^[A-Z]{3,4}$/;\n if (!STATION_CODE_RE.test(stationCheck)) {\n throw new Error(\n `station=${JSON.stringify(stationCheck)} does not match STATION_CODE_RE; ` +\n `refusing to start stream.`,\n );\n }\n let lastObservedAt: string | null = null;\n\n while (true) {\n if (opts.signal?.aborted) return;\n let rows: LiveObservation[] = [];\n try {\n rows = await fetchLatest(station, src);\n } catch (err) {\n // Only swallow transient/runtime errors. Validation-shaped errors\n // (Error message starts with `station=` from STATION_CODE_RE, or\n // is an `Error` with `validation` semantics) propagate so the\n // caller sees the diagnostic instead of an infinite empty-yield loop.\n // Heuristic: re-raise any `Error` whose message contains\n // \"STATION_CODE_RE\" (the only place the fetcher throws synchronously\n // before any HTTP). Everything else (network, 5xx, parse) is swallowed.\n if (err instanceof Error && /STATION_CODE_RE/.test(err.message)) {\n throw err;\n }\n // (We deliberately don't log here; the underlying fetchers already\n // log their own failures and a `console.error` from the SDK would\n // spam browser consoles.)\n rows = [];\n }\n const picked = pickMostRecent(rows);\n if (picked !== null) {\n const current = picked.observed_at;\n if (current && current !== lastObservedAt) {\n lastObservedAt = current;\n yield picked;\n }\n }\n if (opts.signal?.aborted) return;\n await sleep(cadenceMs, opts.signal);\n }\n}\n","// Phase 17 PLAN-11 — IEM MOS TS fetcher.\n//\n// Mirrors Python `packages/weather/src/mostlyright/weather/_fetchers/_iem_mos.py`.\n// Endpoint: https://mesonet.agron.iastate.edu/api/1/mos.json\n// CORS: OPEN per IEM ASOS posture (see `.planning/research/FORECAST-CORS-MATRIX.md`).\n\nimport type { IemMosModel, IemMosOptions, IemMosRow, IemMosSource } from \"./types.js\";\n\nconst IEM_MOS_URL = \"https://mesonet.agron.iastate.edu/api/1/mos.json\";\n\nconst SUPPORTED_MODELS: ReadonlySet<IemMosModel> = new Set([\"nbe\", \"gfs\", \"lav\", \"met\", \"ecm\"]);\n\nconst KT_TO_MS = 0.5144444;\n\nconst NBE_CYCLE_CUTOVER = Date.UTC(2026, 5 - 1, 5, 0, 0, 0); // 2026-05-05T00:00:00Z\n\n/**\n * Max number of MOS runtime-cycle requests in flight at once (GH #58).\n *\n * The fan-out is bounded rather than unbounded: a `Promise.all` over the full\n * day × runtime-hour grid would launch ~1,460 simultaneous requests for a\n * year-scale window, risking client connection limits, memory pressure, and\n * IEM-side rate limiting. A cap of 8 keeps essentially all of the small-window\n * speedup (typical ±1–3 day windows have ≤ 12 cycles) while staying polite for\n * large historical ranges.\n */\nexport const MOS_FETCH_CONCURRENCY = 8;\n\n/**\n * Map `items` through `fn` with at most `limit` invocations in flight at once,\n * returning results in input order (NOT resolution order). Bounded-concurrency\n * replacement for `Promise.all(items.map(fn))` — preserves the byte-identical\n * ordering the serial path produced while capping peak fan-out.\n *\n * Fail-fast: if any invocation throws, the shared `failed` flag stops the other\n * workers from pulling new items, so no further `fn` calls are dispatched once\n * the function is destined to reject. This restores the serial loop's behavior\n * of not issuing more requests after an error (codex review iter-3 P2). Workers\n * already mid-flight finish their current item; at most `limit` are in flight.\n */\nasync function mapWithConcurrency<T, R>(\n items: readonly T[],\n limit: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n let cursor = 0;\n let failed = false;\n async function worker(): Promise<void> {\n while (cursor < items.length && !failed) {\n const index = cursor++;\n try {\n results[index] = await fn(items[index] as T, index);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n const poolSize = Math.min(limit, items.length);\n await Promise.all(Array.from({ length: poolSize }, () => worker()));\n return results;\n}\n\n/** Pick the right NBE runtime-hour set based on the requested range. */\nfunction runtimeHoursFor(model: IemMosModel, fromDt: Date, toDt: Date): readonly number[] {\n if (model !== \"nbe\") return [0, 6, 12, 18];\n const fromMs = fromDt.getTime();\n const toMs = toDt.getTime();\n const pre = fromMs < NBE_CYCLE_CUTOVER;\n const post = toMs >= NBE_CYCLE_CUTOVER;\n if (pre && post) return [0, 1, 6, 7, 12, 13, 18, 19];\n if (pre) return [1, 7, 13, 19];\n return [0, 6, 12, 18];\n}\n\nfunction fahrenheitToCelsius(f: number | null): number | null {\n if (f === null || Number.isNaN(f)) return null;\n return ((f - 32) * 5) / 9;\n}\n\nfunction knotsToMs(kt: number | null): number | null {\n if (kt === null || Number.isNaN(kt)) return null;\n return kt * KT_TO_MS;\n}\n\nfunction percentToUnit(pct: number | null): number | null {\n if (pct === null || Number.isNaN(pct)) return null;\n return pct / 100;\n}\n\nfunction maybeNumber(value: unknown): number | null {\n if (value === null || value === undefined || value === \"M\" || value === \"\") {\n return null;\n }\n const num = typeof value === \"number\" ? value : Number(value);\n if (Number.isNaN(num)) return null;\n return num;\n}\n\nfunction parseDate(value: unknown): Date | null {\n if (typeof value !== \"string\" || !value) return null;\n const dt = new Date(value);\n if (Number.isNaN(dt.getTime())) return null;\n return dt;\n}\n\ninterface RawMosRow {\n readonly runtime?: string;\n readonly model_runtime?: string;\n readonly ftime?: string;\n readonly valid_time?: string;\n readonly tmp?: number | string | null;\n readonly dpt?: number | string | null;\n readonly wsp?: number | string | null;\n readonly wdr?: number | string | null;\n readonly pop12?: number | string | null;\n}\n\nfunction parseRow(\n raw: RawMosRow,\n station: string,\n model: IemMosModel,\n retrievedAt: string,\n): IemMosRow | null {\n const issuedDt = parseDate(raw.runtime ?? raw.model_runtime);\n const validDt = parseDate(raw.ftime ?? raw.valid_time);\n if (issuedDt === null || validDt === null) return null;\n const forecastHour = Math.round((validDt.getTime() - issuedDt.getTime()) / 3_600_000);\n return {\n station,\n model: model.toUpperCase(),\n issuedAt: issuedDt.toISOString(),\n validAt: validDt.toISOString(),\n forecastHour,\n tempC: fahrenheitToCelsius(maybeNumber(raw.tmp)),\n dewPointC: fahrenheitToCelsius(maybeNumber(raw.dpt)),\n windSpeedMs: knotsToMs(maybeNumber(raw.wsp)),\n windDirDeg: (() => {\n const d = maybeNumber(raw.wdr);\n return d === null ? null : Math.round(d);\n })(),\n precipProbability: percentToUnit(maybeNumber(raw.pop12)),\n skyCoverPct: null,\n source: \"iem.archive\" as IemMosSource,\n retrievedAt,\n };\n}\n\n/** Parse ISO `YYYY-MM-DD` to a UTC `Date` at 00:00:00. */\nfunction parseIsoDate(iso: string, endOfDay: boolean): Date {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(iso);\n if (match === null) {\n throw new Error(`iemMosForecasts: from/to dates must be ISO YYYY-MM-DD; got ${iso}`);\n }\n const [, y, m, d] = match;\n if (endOfDay) {\n return new Date(Date.UTC(Number(y), Number(m) - 1, Number(d), 23, 59, 59));\n }\n return new Date(Date.UTC(Number(y), Number(m) - 1, Number(d)));\n}\n\n/**\n * Fetch IEM MOS forecasts for `station` in `[fromDate, toDate]`.\n *\n * Mirrors Python `fetch_iem_mos(...)`. Iterates the model's runtime-hour\n * grid (NBE moved from {01,07,13,19}Z to {00,06,12,18}Z on 2026-05-05;\n * other models use {00,06,12,18}Z), GETs the JSON endpoint, and projects\n * rows to {@link IemMosRow}.\n *\n * 404 responses are silently skipped (many runtimes have no MOS data).\n * Empty input range returns `[]`.\n *\n * @throws `Error` if `model` is not in `SUPPORTED_MODELS`.\n */\nexport async function iemMosForecasts(\n station: string,\n fromDate: string,\n toDate: string,\n opts: IemMosOptions = {},\n): Promise<IemMosRow[]> {\n const model = opts.model ?? \"nbe\";\n if (!SUPPORTED_MODELS.has(model)) {\n throw new Error(\n `iemMosForecasts: model must be one of ${[...SUPPORTED_MODELS].sort().join(\",\")}; got ${model}`,\n );\n }\n const fetchFn = opts.fetchFn ?? fetch;\n const fromDt = parseIsoDate(fromDate, false);\n const toDt = parseIsoDate(toDate, true);\n const hours = runtimeHoursFor(model, fromDt, toDt);\n const retrievedAt = new Date().toISOString();\n\n // GH #58: collect every (day × runtime-hour) URL first, then fan out\n // concurrently with a bounded pool. The cycles are independent (nothing in\n // one depends on another's response), so the previous serial-await loop\n // was paying N round-trips of ~620–760 ms each (~3.2 s for 12 cycles).\n // Bounded fan-out (MOS_FETCH_CONCURRENCY) collapses the wall-clock to\n // ~ceil(N/limit) round-trips on typical short windows while staying polite\n // on large historical ranges (an unbounded Promise.all over a year-scale\n // window would launch ~1,460 simultaneous requests — codex review P2).\n // Row ordering is preserved by keeping the URL list in day-then-hour order\n // and indexing results by position — byte-identical output to the serial path.\n const dayMs = 86_400_000;\n const urls: string[] = [];\n for (let day = fromDt.getTime(); day <= toDt.getTime(); day += dayMs) {\n for (const h of hours) {\n const rt = new Date(day);\n rt.setUTCHours(h, 0, 0, 0);\n if (rt < fromDt || rt > toDt) continue;\n // IEM /api/1/mos.json regex ^(AVN|GFS|...|NBE|...)$ is uppercase-only;\n // sending lowercase returns HTTP 422 (issue #17). Mirrors the upper()\n // applied to `model` on returned rows above and the Python fix at\n // _iem_mos.py:239.\n urls.push(\n `${IEM_MOS_URL}?station=${encodeURIComponent(\n station,\n )}&model=${encodeURIComponent(model.toUpperCase())}&runtime=${encodeURIComponent(\n rt.toISOString(),\n )}`,\n );\n }\n }\n\n // Run the FULL per-cycle lifecycle (fetch → status check → body read → row\n // projection) inside the bounded pool, NOT just the header fetch. `fetch()`\n // resolves once headers arrive, so bounding only the fetch promise would\n // still leave unbounded response bodies open and defer error handling until\n // every URL had been requested (codex review iter-2 P2). Returning per-cycle\n // row arrays and flattening in input order keeps byte-identical output.\n const perCycle = await mapWithConcurrency(urls, MOS_FETCH_CONCURRENCY, async (url) => {\n const resp = (await fetchFn(url)) as Response;\n if (resp.status === 404) return [] as IemMosRow[];\n if (!resp.ok) {\n throw new Error(`iemMosForecasts: HTTP ${resp.status} on ${url}`);\n }\n const payload = (await resp.json()) as { data?: RawMosRow[] };\n const out: IemMosRow[] = [];\n for (const raw of payload.data ?? []) {\n const projected = parseRow(raw, station, model, retrievedAt);\n if (projected !== null) out.push(projected);\n }\n return out;\n });\n return perCycle.flat();\n}\n\nexport const __internal__ = {\n runtimeHoursFor,\n parseRow,\n NBE_CYCLE_CUTOVER,\n};\n","// Phase 17 PLAN-11 / Phase 21 21-07 — TS `forecastNwp()` stub.\n//\n// Per CONTEXT decision 7: TS NWP is deferred to v2.0+. No production-ready\n// browser GRIB2 decoder exists in May 2026; shipping a non-functional\n// runtime-error stub now means callers can write code against the\n// future-stable signature today — v2.0+ lands the execution body.\n//\n// Phase 21 21-07 upgrade: throws `DataAvailabilityError` (typed exception\n// from Phase 21 21-09). Post-21-07 follow-up: throws the more specific\n// `NwpNotAvailableError` subclass so consumers get `instanceof`-based\n// dispatch + `.station` / `.model` autocomplete instead of having to parse\n// the hint string. Back-compat preserved (NwpNotAvailableError extends\n// DataAvailabilityError with reason=\"model_unavailable\").\n\nimport { NwpNotAvailableError } from \"@mostlyrightmd/core\";\n\n/** NWP model enum — mirror of Python `SUPPORTED_NWP_MODELS` (24 entries). */\nexport type NwpModel =\n | \"hrrr\"\n | \"gfs\"\n | \"nbm\"\n // PLAN-03 NCEP family\n | \"hrrrak\"\n | \"gefs\"\n | \"gdas\"\n | \"rap\"\n | \"rrfs\"\n | \"rtma\"\n | \"urma\"\n | \"cfs\"\n // PLAN-04 ECMWF family\n | \"ecmwf_ifs_hres\"\n | \"ecmwf_ifs_ens\"\n | \"ecmwf_aifs_single\"\n | \"ecmwf_aifs_ens\"\n // PLAN-05 MSC Canadian family\n | \"hrdps\"\n | \"rdps\"\n | \"gdps\"\n | \"geps\"\n | \"reps\"\n // PLAN-06 NOMADS-only family\n | \"hafs\"\n | \"nam\"\n | \"href\"\n | \"hiresw\";\n\n/** Optional knobs for {@link forecastNwp}. */\nexport interface ForecastNwpOptions {\n /** Model run datetime — UTC ISO string. */\n readonly cycle?: string;\n /** Forecast hour ahead of `cycle`. */\n readonly fxx?: number;\n /** Force a mirror (e.g. `\"aws_bdp\"`). */\n readonly mirror?: string;\n}\n\n/**\n * Major US stations with IEM MOS coverage. Phase 21 21-07 hint surface\n * — if the caller's station is in this set, the error hint includes an\n * `iemMosForecasts()` workaround pointer. Otherwise the hint mentions\n * the Python SDK fallback only.\n *\n * Source: Phase 17 IEM MOS catalog. The exact set may grow over time;\n * we keep the hint list narrow (7 stations) so the message stays terse.\n */\nconst IEM_MOS_COVERED_STATIONS: ReadonlySet<string> = new Set([\n \"KNYC\",\n \"KLAX\",\n \"KORD\",\n \"KMIA\",\n \"KDEN\",\n \"KSEA\",\n \"KATL\",\n]);\n\nfunction buildHint(station: string, model: NwpModel): string {\n const hasMosCoverage = IEM_MOS_COVERED_STATIONS.has(station.toUpperCase());\n const mosLine = hasMosCoverage\n ? `Workaround for ${station}: iemMosForecasts(\"${station}\", ...) is available (IEM MOS catalog covers this station).`\n : `Workaround: this station has no IEM MOS coverage; use the Python SDK's mostlyright.forecast_nwp() in v1.x.`;\n return `forecastNwp(${station}, \"${model}\") is a v1.x stub. Browser GRIB2 decode is not production-ready in May 2026 (no eccodes / cfgrib equivalent for the browser; WASM compile time + bundle size make it impractical for v1.x). ${mosLine} See https://mostlyright.md/docs/sdk/typescript/nwp-forecasts/ for the architectural reason + v2.0+ tracking.`;\n}\n\n/**\n * Fetch a gridded NWP forecast — **v1.x stub, deferred to v2.0+**.\n *\n * @remarks\n * **⚠️ Not yet implemented in TypeScript.** This function exists so callers\n * can write code against the future-stable signature, but it throws on\n * every call. Use the Python SDK (`mostlyright>=1.0`) for gridded NWP\n * today — it wires the NCEP family (HRRR, GFS, NBM, RAP, RRFS, …) end-to-end.\n *\n * **Why deferred:** GRIB2 decode requires native libraries (eccodes C\n * library or cfgrib Python wrapper). No production-ready browser-side\n * decoder exists in May 2026; a WASM port's compile time + bundle size\n * are impractical for v1.x. v2.0+ tracks the GRIB2 ecosystem maturity.\n *\n * **Workaround paths:**\n * - **7 major US stations** (KNYC, KLAX, KORD, KMIA, KDEN, KSEA, KATL) →\n * {@link iemMosForecasts} ships MOS-based forecasts that solve most use\n * cases. The error hint includes this pointer automatically.\n * - **Everything else** → use the Python SDK\n * (`pip install mostlyrightmd-weather`).\n *\n * @throws {@link NwpNotAvailableError} on every call (v1.x). The error\n * is a subclass of `DataAvailabilityError`, so existing\n * `catch (e instanceof DataAvailabilityError)` paths continue to catch\n * it. The thrown instance carries typed `.station` and `.model`\n * properties for log/error attribution.\n *\n * @see {@link https://mostlyright.md/docs/sdk/typescript/nwp-forecasts/ | docs/nwp-forecasts.md}\n * for the full architectural rationale, the supported-model list, and\n * the v2.0+ roadmap.\n */\nexport async function forecastNwp(\n station: string,\n model: NwpModel,\n _opts: ForecastNwpOptions = {},\n): Promise<never> {\n throw new NwpNotAvailableError({\n station,\n model,\n hint: buildHint(station, model),\n });\n}\n","// Phase 20 OM-08 — Open-Meteo 36-model registry (TS mirror of Python\n// `packages/weather/src/mostlyright/weather/_fetchers/_open_meteo_models/`).\n//\n// Single-file flat-table emission per D-10 bundle-size budget (TS lockstep\n// pattern: Python uses per-family modules for code-org clarity; TS uses a\n// flat table for byte-efficiency).\n\nimport type { OpenMeteoModel } from \"./types.js\";\n\n/** The canonical 36-model set (D-02 set-equality lock). */\nexport const OPEN_METEO_MODELS: ReadonlySet<OpenMeteoModel> = new Set([\n // NCEP (8)\n \"gfs_seamless\",\n \"gfs_global\",\n \"gfs_graphcast025\",\n \"aigfs025\",\n \"hgefs025\",\n \"ncep_hrrr_conus\",\n \"ncep_nbm_conus\",\n \"ncep_nam_conus\",\n // ECMWF (3)\n \"ecmwf_ifs025\",\n \"ecmwf_ifs_hres\",\n \"ecmwf_aifs025_single\",\n // DWD (5)\n \"dwd_icon_seamless\",\n \"dwd_icon_global\",\n \"dwd_icon_eu\",\n \"dwd_icon_d2\",\n \"dwd_icon_d2_15min\",\n // Météo-France (6)\n \"meteofrance_seamless\",\n \"meteofrance_arpege_world025\",\n \"meteofrance_arpege_europe\",\n \"meteofrance_arome_france0025\",\n \"meteofrance_arome_france_hd\",\n \"meteofrance_arome_france_hd_15min\",\n // Asia + Oceania (8)\n \"jma_seamless\",\n \"jma_gsm\",\n \"jma_msm\",\n \"kma_seamless\",\n \"kma_gdps\",\n \"kma_ldps\",\n \"cma_grapes_global\",\n \"bom_access_global\",\n // Europe (3)\n \"ukmo_global_deterministic_10km\",\n \"ukmo_uk_deterministic_2km\",\n \"metno_nordic_pp\",\n // GEM Canada (3)\n \"cmc_gem_gdps\",\n \"cmc_gem_rdps\",\n \"cmc_gem_hrdps\",\n]);\n\nconst SIX_HOURLY = [0, 6, 12, 18] as const;\nconst THREE_HOURLY = [0, 3, 6, 9, 12, 15, 18, 21] as const;\nconst HOURLY = Array.from({ length: 24 }, (_, i) => i) as readonly number[];\nconst TWELVE_HOURLY = [0, 12] as const;\n\n/** Per-model UTC cycle hours. Mirrors Python `CYCLE_HOURS`. */\nexport const CYCLE_HOURS: ReadonlyMap<OpenMeteoModel, readonly number[]> = new Map<\n OpenMeteoModel,\n readonly number[]\n>([\n // NCEP\n [\"gfs_seamless\", SIX_HOURLY],\n [\"gfs_global\", SIX_HOURLY],\n [\"gfs_graphcast025\", SIX_HOURLY],\n [\"aigfs025\", SIX_HOURLY],\n [\"hgefs025\", SIX_HOURLY],\n [\"ncep_hrrr_conus\", HOURLY],\n [\"ncep_nbm_conus\", HOURLY],\n [\"ncep_nam_conus\", SIX_HOURLY],\n // ECMWF\n [\"ecmwf_ifs025\", SIX_HOURLY],\n [\"ecmwf_ifs_hres\", SIX_HOURLY],\n [\"ecmwf_aifs025_single\", SIX_HOURLY],\n // DWD\n [\"dwd_icon_seamless\", SIX_HOURLY],\n [\"dwd_icon_global\", SIX_HOURLY],\n [\"dwd_icon_eu\", SIX_HOURLY],\n [\"dwd_icon_d2\", THREE_HOURLY],\n [\"dwd_icon_d2_15min\", THREE_HOURLY],\n // Météo-France\n [\"meteofrance_seamless\", SIX_HOURLY],\n [\"meteofrance_arpege_world025\", SIX_HOURLY],\n [\"meteofrance_arpege_europe\", SIX_HOURLY],\n [\"meteofrance_arome_france0025\", THREE_HOURLY],\n [\"meteofrance_arome_france_hd\", THREE_HOURLY],\n [\"meteofrance_arome_france_hd_15min\", THREE_HOURLY],\n // Asia + Oceania\n [\"jma_seamless\", SIX_HOURLY],\n [\"jma_gsm\", SIX_HOURLY],\n [\"jma_msm\", THREE_HOURLY],\n [\"kma_seamless\", SIX_HOURLY],\n [\"kma_gdps\", SIX_HOURLY],\n [\"kma_ldps\", THREE_HOURLY],\n [\"cma_grapes_global\", SIX_HOURLY],\n [\"bom_access_global\", SIX_HOURLY],\n // Europe\n [\"ukmo_global_deterministic_10km\", SIX_HOURLY],\n [\"ukmo_uk_deterministic_2km\", THREE_HOURLY],\n [\"metno_nordic_pp\", HOURLY],\n // GEM Canada\n [\"cmc_gem_gdps\", TWELVE_HOURLY],\n [\"cmc_gem_rdps\", SIX_HOURLY],\n [\"cmc_gem_hrdps\", SIX_HOURLY],\n]);\n\n/** Per-model publish-lag hours for Live mode cycle-math fallback (D-06). */\nexport const PUBLISH_LAG_HOURS: ReadonlyMap<OpenMeteoModel, number> = new Map<\n OpenMeteoModel,\n number\n>([\n // NCEP — global 6h, regional/mesoscale 2h\n [\"gfs_seamless\", 6],\n [\"gfs_global\", 6],\n [\"gfs_graphcast025\", 6],\n [\"aigfs025\", 6],\n [\"hgefs025\", 6],\n [\"ncep_hrrr_conus\", 2],\n [\"ncep_nbm_conus\", 2],\n [\"ncep_nam_conus\", 2],\n // ECMWF — global 6h\n [\"ecmwf_ifs025\", 6],\n [\"ecmwf_ifs_hres\", 6],\n [\"ecmwf_aifs025_single\", 6],\n // DWD\n [\"dwd_icon_seamless\", 4],\n [\"dwd_icon_global\", 4],\n [\"dwd_icon_eu\", 4],\n [\"dwd_icon_d2\", 2],\n [\"dwd_icon_d2_15min\", 2],\n // Météo-France\n [\"meteofrance_seamless\", 4],\n [\"meteofrance_arpege_world025\", 6],\n [\"meteofrance_arpege_europe\", 4],\n [\"meteofrance_arome_france0025\", 2],\n [\"meteofrance_arome_france_hd\", 2],\n [\"meteofrance_arome_france_hd_15min\", 2],\n // Asia + Oceania\n [\"jma_seamless\", 6],\n [\"jma_gsm\", 6],\n [\"jma_msm\", 2],\n [\"kma_seamless\", 6],\n [\"kma_gdps\", 6],\n [\"kma_ldps\", 2],\n [\"cma_grapes_global\", 6],\n [\"bom_access_global\", 6],\n // Europe\n [\"ukmo_global_deterministic_10km\", 6],\n [\"ukmo_uk_deterministic_2km\", 2],\n [\"metno_nordic_pp\", 2],\n // GEM Canada\n [\"cmc_gem_gdps\", 6],\n [\"cmc_gem_rdps\", 4],\n [\"cmc_gem_hrdps\", 2],\n]);\n\n// ---------------------------------------------------------------------------\n// Cycle math primitives (mirror Python `cycle_math.py`).\n// ---------------------------------------------------------------------------\n\n/**\n * Snap `valueUtcMs` (epoch ms) down to the most recent cycle hour <= value.\n * Returns the floored epoch ms.\n */\nexport function floorToCycleMs(valueUtcMs: number, cycleHours: readonly number[]): number {\n if (cycleHours.length === 0) {\n throw new Error(\"cycleHours must be non-empty\");\n }\n const d = new Date(valueUtcMs);\n const hour = d.getUTCHours();\n const sorted = [...cycleHours].sort((a, b) => a - b);\n const candidates = sorted.filter((h) => h <= hour);\n if (candidates.length > 0) {\n const targetHour = candidates[candidates.length - 1];\n return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), targetHour, 0, 0, 0);\n }\n const lastCycle = sorted[sorted.length - 1];\n const prior = new Date(valueUtcMs - 86_400_000);\n return Date.UTC(\n prior.getUTCFullYear(),\n prior.getUTCMonth(),\n prior.getUTCDate(),\n lastCycle,\n 0,\n 0,\n 0,\n );\n}\n\n/**\n * Conservative lower bound for the cycle producing `_previous_dayN` (D-05).\n */\nexport function issuedAtFromPreviousDayMs(\n validAtUtcMs: number,\n N: number,\n cycleHours: readonly number[],\n): number {\n if (N < 1 || N > 7) {\n throw new Error(`N must be in 1..7 (Open-Meteo previous_dayN limit); got ${N}`);\n }\n return floorToCycleMs(validAtUtcMs - N * 86_400_000, cycleHours);\n}\n\n/**\n * Cycle-math fallback for Live mode `issuedAt` derivation (D-06).\n */\nexport function issuedAtFromLiveCycleMathMs(\n nowUtcMs: number,\n publishLagHours: number,\n cycleHours: readonly number[],\n): number {\n return floorToCycleMs(nowUtcMs - publishLagHours * 3_600_000, cycleHours);\n}\n","// Phase 20 OM-07 + OM-08 — Open-Meteo TS fetcher (mirrors Python\n// `packages/weather/src/mostlyright/weather/_fetchers/_open_meteo.py`).\n//\n// Endpoints (Phase 20 D-01):\n// - Previous Runs: https://previous-runs-api.open-meteo.com/v1/forecast\n// - Single Runs: https://single-runs-api.open-meteo.com/v1/forecast\n// - Live Forecast: https://api.open-meteo.com/v1/forecast\n// - Historical (SEAMLESS):\n// https://historical-forecast-api.open-meteo.com/v1/forecast\n// BANNED unless caller passes allowLeakage=true.\n//\n// Browser-safe: uses `fetch` + AbortController only (no Node-only APIs).\n\nimport { OpenMeteoSeamlessLeakageError } from \"@mostlyrightmd/core\";\n\nimport {\n CYCLE_HOURS,\n OPEN_METEO_MODELS,\n PUBLISH_LAG_HOURS,\n issuedAtFromLiveCycleMathMs,\n issuedAtFromPreviousDayMs,\n} from \"./open-meteo-models.js\";\nimport type {\n OpenMeteoMode,\n OpenMeteoModel,\n OpenMeteoOptions,\n OpenMeteoRow,\n OpenMeteoSource,\n} from \"./types.js\";\n\nexport const OPEN_METEO_PREVIOUS_RUNS_URL = \"https://previous-runs-api.open-meteo.com/v1/forecast\";\nexport const OPEN_METEO_SINGLE_RUNS_URL = \"https://single-runs-api.open-meteo.com/v1/forecast\";\nexport const OPEN_METEO_LIVE_URL = \"https://api.open-meteo.com/v1/forecast\";\nexport const OPEN_METEO_SEAMLESS_URL = \"https://historical-forecast-api.open-meteo.com/v1/forecast\";\n\nconst VALID_MODES: ReadonlySet<OpenMeteoMode> = new Set([\"training\", \"live\", \"seamless\"]);\n\nconst HOURLY_VARIABLES = [\n \"temperature_2m\",\n \"dew_point_2m\",\n \"apparent_temperature\",\n \"wind_speed_10m\",\n \"wind_direction_10m\",\n \"wind_gusts_10m\",\n \"precipitation\",\n \"precipitation_probability\",\n \"cloud_cover\",\n \"surface_pressure\",\n \"pressure_msl\",\n \"shortwave_radiation\",\n \"direct_radiation\",\n \"cape\",\n \"freezing_level_height\",\n \"snow_depth\",\n \"visibility\",\n \"weather_code\",\n] as const;\n\n// Built-in station coordinates for the Phase 20 regression fixtures.\nconst STATION_COORDS: ReadonlyMap<string, { lat: number; lon: number }> = new Map([\n [\"KNYC\", { lat: 40.78, lon: -73.97 }],\n [\"KORD\", { lat: 41.98, lon: -87.9 }],\n [\"KDEN\", { lat: 39.86, lon: -104.67 }],\n [\"KMIA\", { lat: 25.79, lon: -80.29 }],\n [\"KSEA\", { lat: 47.45, lon: -122.31 }],\n]);\n\nfunction resolveCoords(station: string): { lat: number; lon: number } {\n const coords = STATION_COORDS.get(station);\n if (coords) return coords;\n throw new Error(\n `openMeteoForecasts: station=${JSON.stringify(station)} not in built-in coords table; add to STATION_COORDS`,\n );\n}\n\nfunction buildHourlyParam(endpoint: string): string {\n if (endpoint === OPEN_METEO_PREVIOUS_RUNS_URL) {\n return HOURLY_VARIABLES.map((v) => `${v}_previous_day1`).join(\",\");\n }\n return HOURLY_VARIABLES.join(\",\");\n}\n\nfunction dispatchEndpoint(\n mode: OpenMeteoMode,\n opts: { allowLeakage: boolean; model: OpenMeteoModel; issuedAt: string | undefined },\n): string {\n if (mode === \"training\") {\n return opts.issuedAt ? OPEN_METEO_SINGLE_RUNS_URL : OPEN_METEO_PREVIOUS_RUNS_URL;\n }\n if (mode === \"live\") {\n return OPEN_METEO_LIVE_URL;\n }\n if (mode === \"seamless\") {\n if (!opts.allowLeakage) {\n throw new OpenMeteoSeamlessLeakageError(\n \"Open-Meteo seamless endpoint is banned for training data \" +\n \"(see Tarabcak/mostlyright#70). Pass allowLeakage: true to opt in; \" +\n \"LeakageDetector will still reject these rows when asOf is asserted.\",\n {\n model: opts.model,\n endpointUrl: OPEN_METEO_SEAMLESS_URL,\n },\n );\n }\n return OPEN_METEO_SEAMLESS_URL;\n }\n throw new Error(`openMeteoForecasts: unknown mode ${JSON.stringify(mode)}`);\n}\n\nfunction isoIfNotNull(ms: number | null): string | null {\n return ms === null ? null : new Date(ms).toISOString();\n}\n\nfunction maybeNumber(value: unknown): number | null {\n if (value === null || value === undefined) return null;\n const n = typeof value === \"number\" ? value : Number(value);\n return Number.isFinite(n) ? n : null;\n}\n\n/**\n * Coerce to a nullable integer for integer-schema fields, rounding fractional\n * upstream values instead of passing them through. Mirrors the Python fetcher's\n * `pd.to_numeric(...).round().astype(\"Int64\")` (issue #55): without this, a\n * fractional value (e.g. `cloud_cover` of `12.5`) would violate the `integer`\n * type in `schema.forecast.station.v1` and diverge from the Python SDK output.\n *\n * Uses banker's rounding (round half to even) to match pandas/NumPy `.round()`\n * exactly — JS `Math.round` rounds halves up (`12.5 -> 13`) whereas pandas\n * rounds to even (`12.5 -> 12`), which would otherwise break byte-parity at\n * `.5` boundaries.\n */\nfunction maybeInt(value: unknown): number | null {\n const n = maybeNumber(value);\n if (n === null) return null;\n const floor = Math.floor(n);\n const diff = n - floor;\n if (diff < 0.5) return floor;\n if (diff > 0.5) return floor + 1;\n // Exactly .5 → round to the even neighbour (banker's rounding).\n return floor % 2 === 0 ? floor : floor + 1;\n}\n\nfunction pickHourlyValue(\n hourly: Record<string, unknown[]>,\n key: string,\n isPreviousRuns: boolean,\n idx: number,\n): unknown {\n const arr = isPreviousRuns ? (hourly[`${key}_previous_day1`] ?? hourly[key]) : hourly[key];\n if (!Array.isArray(arr) || idx >= arr.length) return null;\n return arr[idx];\n}\n\n/**\n * Fetch Open-Meteo forecasts for `station` in `[fromDate, toDate]`.\n *\n * Phase 20 OM-07. Mirrors Python `fetch_open_meteo(...)`. Default\n * `mode: \"training\"` hits Previous Runs API; with `issuedAt: \"...\"`\n * dispatches to Single Runs API. `mode: \"live\"` hits Live Forecast API\n * with cycle-math fallback `issuedAt`. `mode: \"seamless\"` requires\n * `allowLeakage: true` (BANNED for training data).\n */\nexport async function openMeteoForecasts(\n station: string,\n fromDate: string,\n toDate: string,\n opts: OpenMeteoOptions = {},\n): Promise<OpenMeteoRow[]> {\n const model = opts.model ?? \"gfs_global\";\n if (!OPEN_METEO_MODELS.has(model)) {\n throw new Error(\n `openMeteoForecasts: model must be one of OPEN_METEO_MODELS (36 keys); got ${JSON.stringify(model)}`,\n );\n }\n const mode = opts.mode ?? \"training\";\n if (!VALID_MODES.has(mode)) {\n throw new Error(\n `openMeteoForecasts: mode must be one of training,live,seamless; got ${JSON.stringify(mode)}`,\n );\n }\n const endpoint = dispatchEndpoint(mode, {\n allowLeakage: opts.allowLeakage ?? false,\n model,\n issuedAt: opts.issuedAt,\n });\n\n const { lat, lon } = resolveCoords(station);\n const params = new URLSearchParams();\n params.set(\"latitude\", String(lat));\n params.set(\"longitude\", String(lon));\n params.set(\"hourly\", buildHourlyParam(endpoint));\n params.set(\"models\", model);\n params.set(\"timezone\", \"UTC\");\n if (endpoint === OPEN_METEO_SINGLE_RUNS_URL) {\n // Single-Runs API rejects start_date/end_date (HTTP 400); send run= only.\n // The response carries the full run horizon, which we clip to\n // [fromDate, toDate] after parsing — parity with Python fetch_open_meteo.\n if (opts.issuedAt) {\n params.set(\"run\", opts.issuedAt);\n }\n } else {\n params.set(\"start_date\", fromDate);\n params.set(\"end_date\", toDate);\n }\n\n const fetchFn = opts.fetchFn ?? fetch;\n const url = `${endpoint}?${params.toString()}`;\n const resp = await fetchFn(url);\n if (resp.status === 404) return [];\n if (!resp.ok) {\n throw new Error(`openMeteoForecasts: HTTP ${resp.status} on ${url}`);\n }\n const payload = (await resp.json()) as {\n hourly?: { time?: string[]; [k: string]: unknown };\n };\n const hourly = payload.hourly ?? {};\n const times = hourly.time ?? [];\n if (times.length === 0) return [];\n\n let source: OpenMeteoSource;\n if (endpoint === OPEN_METEO_PREVIOUS_RUNS_URL) {\n source = \"open_meteo.previous_runs\";\n } else if (endpoint === OPEN_METEO_SINGLE_RUNS_URL) {\n source = \"open_meteo.single_run\";\n } else if (endpoint === OPEN_METEO_LIVE_URL) {\n source = \"open_meteo.live\";\n } else {\n source = \"open_meteo.seamless\";\n }\n\n const cycleHours = CYCLE_HOURS.get(model) ?? [0, 6, 12, 18];\n const publishLag = PUBLISH_LAG_HOURS.get(model) ?? 6;\n const retrievedAt = new Date().toISOString();\n const nowMs = Date.now();\n\n const rows: OpenMeteoRow[] = [];\n for (let i = 0; i < times.length; i++) {\n const tIso = times[i] ?? \"\";\n if (!tIso) continue;\n const validAtMs = Date.parse(tIso.endsWith(\"Z\") || tIso.includes(\"+\") ? tIso : `${tIso}Z`);\n let issuedAtMs: number | null;\n if (source === \"open_meteo.previous_runs\") {\n issuedAtMs = issuedAtFromPreviousDayMs(validAtMs, 1, cycleHours);\n } else if (source === \"open_meteo.single_run\") {\n issuedAtMs = opts.issuedAt\n ? Date.parse(\n opts.issuedAt.endsWith(\"Z\") || opts.issuedAt.includes(\"+\")\n ? opts.issuedAt\n : `${opts.issuedAt}Z`,\n )\n : null;\n } else if (source === \"open_meteo.live\") {\n issuedAtMs = issuedAtFromLiveCycleMathMs(nowMs, publishLag, cycleHours);\n } else {\n // seamless — null by design\n issuedAtMs = null;\n }\n const validAtIso = new Date(validAtMs).toISOString();\n const forecastHour =\n issuedAtMs === null ? null : Math.round((validAtMs - issuedAtMs) / 3_600_000);\n const isPrev = source === \"open_meteo.previous_runs\";\n const h = hourly as Record<string, unknown[]>;\n const popPct = maybeNumber(pickHourlyValue(h, \"precipitation_probability\", isPrev, i));\n rows.push({\n station,\n model,\n issuedAt: isoIfNotNull(issuedAtMs),\n validAt: validAtIso,\n forecastHour,\n tempC: maybeNumber(pickHourlyValue(h, \"temperature_2m\", isPrev, i)),\n dewPointC: maybeNumber(pickHourlyValue(h, \"dew_point_2m\", isPrev, i)),\n apparentTempC: maybeNumber(pickHourlyValue(h, \"apparent_temperature\", isPrev, i)),\n windSpeedMs: maybeNumber(pickHourlyValue(h, \"wind_speed_10m\", isPrev, i)),\n windDirDeg: maybeInt(pickHourlyValue(h, \"wind_direction_10m\", isPrev, i)),\n windGustsMs: maybeNumber(pickHourlyValue(h, \"wind_gusts_10m\", isPrev, i)),\n precipProbability: popPct === null ? null : popPct / 100,\n precipitationMm: maybeNumber(pickHourlyValue(h, \"precipitation\", isPrev, i)),\n cloudCoverPct: maybeInt(pickHourlyValue(h, \"cloud_cover\", isPrev, i)),\n surfacePressureHpa: maybeNumber(pickHourlyValue(h, \"surface_pressure\", isPrev, i)),\n pressureMslHpa: maybeNumber(pickHourlyValue(h, \"pressure_msl\", isPrev, i)),\n shortwaveRadiationWm2: maybeNumber(pickHourlyValue(h, \"shortwave_radiation\", isPrev, i)),\n directRadiationWm2: maybeNumber(pickHourlyValue(h, \"direct_radiation\", isPrev, i)),\n capeJkg: maybeNumber(pickHourlyValue(h, \"cape\", isPrev, i)),\n freezingLevelM: maybeInt(pickHourlyValue(h, \"freezing_level_height\", isPrev, i)),\n snowDepthM: maybeNumber(pickHourlyValue(h, \"snow_depth\", isPrev, i)),\n visibilityM: maybeInt(pickHourlyValue(h, \"visibility\", isPrev, i)),\n weatherCode: maybeInt(pickHourlyValue(h, \"weather_code\", isPrev, i)),\n source,\n retrievedAt,\n });\n }\n\n // Single-Runs returns the full run horizon; clip to the requested window\n // [fromDate, toDate] (end-inclusive day) — parity with Python fetch_open_meteo.\n if (source === \"open_meteo.single_run\" && rows.length > 0) {\n const loMs = Date.parse(`${fromDate}T00:00:00Z`);\n const hiMs = Date.parse(`${toDate}T00:00:00Z`) + 86_400_000;\n return rows.filter((r) => {\n const v = Date.parse(r.validAt);\n return v >= loMs && v < hiMs;\n });\n }\n return rows;\n}\n","// AUTO-GENERATED by @mostlyrightmd/codegen from schemas/stations.json.\n// DO NOT EDIT — regenerate with: pnpm codegen\n// Last manifest SHA recorded in schemas/EXPORT_MANIFEST.json\n\nexport interface StationInfo {\n code: string | null;\n ghcnh_id: string | null;\n icao: string;\n name: string | null;\n tz: string;\n latitude: number | null;\n longitude: number | null;\n country: string | null;\n venues: ReadonlyArray<string>;\n}\n\nexport const STATIONS: ReadonlyArray<StationInfo> = [\n {\n code: \"CYYZ\",\n country: \"CA\",\n ghcnh_id: null,\n icao: \"CYYZ\",\n latitude: 43.6777,\n longitude: -79.6248,\n name: \"Toronto Pearson International\",\n tz: \"America/Toronto\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EDDB\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDB\",\n latitude: 52.3667,\n longitude: 13.5033,\n name: \"Berlin Brandenburg\",\n tz: \"Europe/Berlin\",\n venues: [],\n },\n {\n code: \"EDDF\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDF\",\n latitude: 50.0379,\n longitude: 8.5622,\n name: \"Frankfurt am Main\",\n tz: \"Europe/Berlin\",\n venues: [],\n },\n {\n code: \"EDDM\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDM\",\n latitude: 48.3538,\n longitude: 11.7861,\n name: \"Munich Franz Josef Strauss\",\n tz: \"Europe/Berlin\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EFHK\",\n country: \"FI\",\n ghcnh_id: null,\n icao: \"EFHK\",\n latitude: 60.3172,\n longitude: 24.9633,\n name: \"Helsinki-Vantaa\",\n tz: \"Europe/Helsinki\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EGKK\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGKK\",\n latitude: 51.1481,\n longitude: -0.1903,\n name: \"London Gatwick\",\n tz: \"Europe/London\",\n venues: [],\n },\n {\n code: \"EGLC\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGLC\",\n latitude: 51.5053,\n longitude: 0.0553,\n name: \"London City\",\n tz: \"Europe/London\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EGLL\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGLL\",\n latitude: 51.4706,\n longitude: -0.4619,\n name: \"London Heathrow\",\n tz: \"Europe/London\",\n venues: [],\n },\n {\n code: \"EHAM\",\n country: \"NL\",\n ghcnh_id: null,\n icao: \"EHAM\",\n latitude: 52.3086,\n longitude: 4.7639,\n name: \"Amsterdam Schiphol\",\n tz: \"Europe/Amsterdam\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EKCH\",\n country: \"DK\",\n ghcnh_id: null,\n icao: \"EKCH\",\n latitude: 55.6181,\n longitude: 12.6561,\n name: \"Copenhagen Kastrup\",\n tz: \"Europe/Copenhagen\",\n venues: [],\n },\n {\n code: \"EPWA\",\n country: \"PL\",\n ghcnh_id: null,\n icao: \"EPWA\",\n latitude: 52.1657,\n longitude: 20.9671,\n name: \"Warsaw Chopin\",\n tz: \"Europe/Warsaw\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ESSA\",\n country: \"SE\",\n ghcnh_id: null,\n icao: \"ESSA\",\n latitude: 59.6519,\n longitude: 17.9186,\n name: \"Stockholm Arlanda\",\n tz: \"Europe/Stockholm\",\n venues: [],\n },\n {\n code: \"FACT\",\n country: \"ZA\",\n ghcnh_id: null,\n icao: \"FACT\",\n latitude: -33.9648,\n longitude: 18.6017,\n name: \"Cape Town International\",\n tz: \"Africa/Johannesburg\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ATL\",\n country: \"US\",\n ghcnh_id: \"USW00013874\",\n icao: \"KATL\",\n latitude: 33.6407,\n longitude: -84.4277,\n name: \"Hartsfield-Jackson Atlanta International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"AUS\",\n country: \"US\",\n ghcnh_id: \"USW00013904\",\n icao: \"KAUS\",\n latitude: 30.1975,\n longitude: -97.6664,\n name: \"Austin-Bergstrom International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"BKF\",\n country: \"US\",\n ghcnh_id: \"USW00093067\",\n icao: \"KBKF\",\n latitude: 39.7019,\n longitude: -104.7517,\n name: \"Buckley Space Force Base (Denver)\",\n tz: \"America/Denver\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"BNA\",\n country: \"US\",\n ghcnh_id: \"USW00013897\",\n icao: \"KBNA\",\n latitude: 36.1245,\n longitude: -86.6782,\n name: \"Nashville International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"BOS\",\n country: \"US\",\n ghcnh_id: \"USW00014739\",\n icao: \"KBOS\",\n latitude: 42.3656,\n longitude: -71.0096,\n name: \"Boston Logan International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"CVG\",\n country: \"US\",\n ghcnh_id: \"USW00093814\",\n icao: \"KCVG\",\n latitude: 39.0488,\n longitude: -84.6678,\n name: \"Cincinnati/Northern Kentucky International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DAL\",\n country: \"US\",\n ghcnh_id: \"USW00013960\",\n icao: \"KDAL\",\n latitude: 32.8481,\n longitude: -96.8512,\n name: \"Dallas Love Field\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"DCA\",\n country: \"US\",\n ghcnh_id: \"USW00013743\",\n icao: \"KDCA\",\n latitude: 38.8512,\n longitude: -77.0402,\n name: \"Washington Reagan National\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DEN\",\n country: \"US\",\n ghcnh_id: \"USW00003017\",\n icao: \"KDEN\",\n latitude: 39.8561,\n longitude: -104.6737,\n name: \"Denver International\",\n tz: \"America/Denver\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DFW\",\n country: \"US\",\n ghcnh_id: \"USW00003927\",\n icao: \"KDFW\",\n latitude: 32.8998,\n longitude: -97.0403,\n name: \"Dallas-Fort Worth International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DTW\",\n country: \"US\",\n ghcnh_id: \"USW00094847\",\n icao: \"KDTW\",\n latitude: 42.2124,\n longitude: -83.3534,\n name: \"Detroit Metropolitan Wayne County\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"HOU\",\n country: \"US\",\n ghcnh_id: \"USW00012918\",\n icao: \"KHOU\",\n latitude: 29.6454,\n longitude: -95.2789,\n name: \"Houston Hobby\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"IAH\",\n country: \"US\",\n ghcnh_id: \"USW00012960\",\n icao: \"KIAH\",\n latitude: 29.9844,\n longitude: -95.3414,\n name: \"Houston George Bush Intercontinental\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LAS\",\n country: \"US\",\n ghcnh_id: \"USW00023169\",\n icao: \"KLAS\",\n latitude: 36.084,\n longitude: -115.1537,\n name: \"Harry Reid (McCarran) International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LAX\",\n country: \"US\",\n ghcnh_id: \"USW00023174\",\n icao: \"KLAX\",\n latitude: 33.9425,\n longitude: -118.4081,\n name: \"Los Angeles International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"LGA\",\n country: \"US\",\n ghcnh_id: \"USW00014732\",\n icao: \"KLGA\",\n latitude: 40.7772,\n longitude: -73.8726,\n name: \"New York LaGuardia\",\n tz: \"America/New_York\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MDW\",\n country: \"US\",\n ghcnh_id: \"USW00014819\",\n icao: \"KMDW\",\n latitude: 41.7868,\n longitude: -87.7522,\n name: \"Chicago Midway International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"MIA\",\n country: \"US\",\n ghcnh_id: \"USW00012839\",\n icao: \"KMIA\",\n latitude: 25.7959,\n longitude: -80.287,\n name: \"Miami International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"MSP\",\n country: \"US\",\n ghcnh_id: \"USW00014922\",\n icao: \"KMSP\",\n latitude: 44.8848,\n longitude: -93.2223,\n name: \"Minneapolis-St Paul International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"MSY\",\n country: \"US\",\n ghcnh_id: \"USW00012916\",\n icao: \"KMSY\",\n latitude: 29.9934,\n longitude: -90.258,\n name: \"New Orleans Louis Armstrong International\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"NYC\",\n country: \"US\",\n ghcnh_id: \"USW00094728\",\n icao: \"KNYC\",\n latitude: 40.7789,\n longitude: -73.9692,\n name: \"Central Park, New York\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"OKC\",\n country: \"US\",\n ghcnh_id: \"USW00013967\",\n icao: \"KOKC\",\n latitude: 35.3931,\n longitude: -97.6007,\n name: \"Oklahoma City Will Rogers World\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"ORD\",\n country: \"US\",\n ghcnh_id: \"USW00094846\",\n icao: \"KORD\",\n latitude: 41.9742,\n longitude: -87.9073,\n name: \"Chicago O'Hare International\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"PHL\",\n country: \"US\",\n ghcnh_id: \"USW00013739\",\n icao: \"KPHL\",\n latitude: 39.8721,\n longitude: -75.2411,\n name: \"Philadelphia International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"PHX\",\n country: \"US\",\n ghcnh_id: \"USW00023183\",\n icao: \"KPHX\",\n latitude: 33.4373,\n longitude: -112.0078,\n name: \"Phoenix Sky Harbor International\",\n tz: \"America/Phoenix\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"SAT\",\n country: \"US\",\n ghcnh_id: \"USW00012921\",\n icao: \"KSAT\",\n latitude: 29.5337,\n longitude: -98.4698,\n name: \"San Antonio International\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"SEA\",\n country: \"US\",\n ghcnh_id: \"USW00024233\",\n icao: \"KSEA\",\n latitude: 47.4502,\n longitude: -122.3088,\n name: \"Seattle-Tacoma International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"SFO\",\n country: \"US\",\n ghcnh_id: \"USW00023234\",\n icao: \"KSFO\",\n latitude: 37.6213,\n longitude: -122.379,\n name: \"San Francisco International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"SLC\",\n country: \"US\",\n ghcnh_id: \"USW00024127\",\n icao: \"KSLC\",\n latitude: 40.7884,\n longitude: -111.9778,\n name: \"Salt Lake City International\",\n tz: \"America/Denver\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LEBL\",\n country: \"ES\",\n ghcnh_id: null,\n icao: \"LEBL\",\n latitude: 41.2974,\n longitude: 2.0833,\n name: \"Barcelona El Prat\",\n tz: \"Europe/Madrid\",\n venues: [],\n },\n {\n code: \"LEMD\",\n country: \"ES\",\n ghcnh_id: null,\n icao: \"LEMD\",\n latitude: 40.4719,\n longitude: -3.5626,\n name: \"Madrid Barajas\",\n tz: \"Europe/Madrid\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LFPB\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPB\",\n latitude: 48.9694,\n longitude: 2.4414,\n name: \"Paris Le Bourget\",\n tz: \"Europe/Paris\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LFPG\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPG\",\n latitude: 49.0097,\n longitude: 2.5479,\n name: \"Paris Charles de Gaulle\",\n tz: \"Europe/Paris\",\n venues: [],\n },\n {\n code: \"LFPO\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPO\",\n latitude: 48.7233,\n longitude: 2.3794,\n name: \"Paris Orly\",\n tz: \"Europe/Paris\",\n venues: [],\n },\n {\n code: \"LIMC\",\n country: \"IT\",\n ghcnh_id: null,\n icao: \"LIMC\",\n latitude: 45.6306,\n longitude: 8.7281,\n name: \"Milan Malpensa\",\n tz: \"Europe/Rome\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LIRF\",\n country: \"IT\",\n ghcnh_id: null,\n icao: \"LIRF\",\n latitude: 41.8003,\n longitude: 12.2389,\n name: \"Rome Fiumicino\",\n tz: \"Europe/Rome\",\n venues: [],\n },\n {\n code: \"LLBG\",\n country: \"IL\",\n ghcnh_id: null,\n icao: \"LLBG\",\n latitude: 32.0114,\n longitude: 34.8867,\n name: \"Tel Aviv Ben Gurion\",\n tz: \"Asia/Jerusalem\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LOWW\",\n country: \"AT\",\n ghcnh_id: null,\n icao: \"LOWW\",\n latitude: 48.1103,\n longitude: 16.5697,\n name: \"Vienna International\",\n tz: \"Europe/Vienna\",\n venues: [],\n },\n {\n code: \"LSZH\",\n country: \"CH\",\n ghcnh_id: null,\n icao: \"LSZH\",\n latitude: 47.4647,\n longitude: 8.5492,\n name: \"Zurich\",\n tz: \"Europe/Zurich\",\n venues: [],\n },\n {\n code: \"LTAC\",\n country: \"TR\",\n ghcnh_id: null,\n icao: \"LTAC\",\n latitude: 40.1281,\n longitude: 32.9951,\n name: \"Ankara Esenboga\",\n tz: \"Europe/Istanbul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LTFM\",\n country: \"TR\",\n ghcnh_id: null,\n icao: \"LTFM\",\n latitude: 41.2753,\n longitude: 28.7519,\n name: \"Istanbul Airport\",\n tz: \"Europe/Istanbul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MMMX\",\n country: \"MX\",\n ghcnh_id: null,\n icao: \"MMMX\",\n latitude: 19.4363,\n longitude: -99.0721,\n name: \"Mexico City Benito Juarez International\",\n tz: \"America/Mexico_City\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MPMG\",\n country: \"PA\",\n ghcnh_id: null,\n icao: \"MPMG\",\n latitude: 8.9733,\n longitude: -79.5556,\n name: \"Panama City Marcos A. Gelabert (Albrook)\",\n tz: \"America/Panama\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"NZAA\",\n country: \"NZ\",\n ghcnh_id: null,\n icao: \"NZAA\",\n latitude: -37.0081,\n longitude: 174.7917,\n name: \"Auckland\",\n tz: \"Pacific/Auckland\",\n venues: [],\n },\n {\n code: \"NZWN\",\n country: \"NZ\",\n ghcnh_id: null,\n icao: \"NZWN\",\n latitude: -41.3272,\n longitude: 174.8053,\n name: \"Wellington\",\n tz: \"Pacific/Auckland\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OEJN\",\n country: \"SA\",\n ghcnh_id: null,\n icao: \"OEJN\",\n latitude: 21.6796,\n longitude: 39.1565,\n name: \"Jeddah King Abdulaziz International\",\n tz: \"Asia/Riyadh\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OERK\",\n country: \"SA\",\n ghcnh_id: null,\n icao: \"OERK\",\n latitude: 24.9576,\n longitude: 46.6988,\n name: \"Riyadh King Khalid International\",\n tz: \"Asia/Riyadh\",\n venues: [],\n },\n {\n code: \"OMDB\",\n country: \"AE\",\n ghcnh_id: null,\n icao: \"OMDB\",\n latitude: 25.2532,\n longitude: 55.3657,\n name: \"Dubai International\",\n tz: \"Asia/Dubai\",\n venues: [],\n },\n {\n code: \"OPKC\",\n country: \"PK\",\n ghcnh_id: null,\n icao: \"OPKC\",\n latitude: 24.9065,\n longitude: 67.1608,\n name: \"Karachi Jinnah International\",\n tz: \"Asia/Karachi\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OTHH\",\n country: \"QA\",\n ghcnh_id: null,\n icao: \"OTHH\",\n latitude: 25.2731,\n longitude: 51.608,\n name: \"Doha Hamad International\",\n tz: \"Asia/Qatar\",\n venues: [],\n },\n {\n code: \"RCSS\",\n country: \"TW\",\n ghcnh_id: null,\n icao: \"RCSS\",\n latitude: 25.0694,\n longitude: 121.5519,\n name: \"Taipei Songshan\",\n tz: \"Asia/Taipei\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RCTP\",\n country: \"TW\",\n ghcnh_id: null,\n icao: \"RCTP\",\n latitude: 25.0777,\n longitude: 121.2328,\n name: \"Taipei Taoyuan\",\n tz: \"Asia/Taipei\",\n venues: [],\n },\n {\n code: \"RJAA\",\n country: \"JP\",\n ghcnh_id: null,\n icao: \"RJAA\",\n latitude: 35.7647,\n longitude: 140.3864,\n name: \"Tokyo Narita\",\n tz: \"Asia/Tokyo\",\n venues: [],\n },\n {\n code: \"RJTT\",\n country: \"JP\",\n ghcnh_id: null,\n icao: \"RJTT\",\n latitude: 35.5522,\n longitude: 139.78,\n name: \"Tokyo Haneda\",\n tz: \"Asia/Tokyo\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RKPK\",\n country: \"KR\",\n ghcnh_id: null,\n icao: \"RKPK\",\n latitude: 35.1795,\n longitude: 128.9382,\n name: \"Busan Gimhae International\",\n tz: \"Asia/Seoul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RKSI\",\n country: \"KR\",\n ghcnh_id: null,\n icao: \"RKSI\",\n latitude: 37.4691,\n longitude: 126.4505,\n name: \"Seoul Incheon\",\n tz: \"Asia/Seoul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RPLL\",\n country: \"PH\",\n ghcnh_id: null,\n icao: \"RPLL\",\n latitude: 14.5086,\n longitude: 121.0197,\n name: \"Manila Ninoy Aquino International\",\n tz: \"Asia/Manila\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"SAEZ\",\n country: \"AR\",\n ghcnh_id: null,\n icao: \"SAEZ\",\n latitude: -34.8222,\n longitude: -58.5358,\n name: \"Buenos Aires Ezeiza\",\n tz: \"America/Argentina/Buenos_Aires\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"SBGR\",\n country: \"BR\",\n ghcnh_id: null,\n icao: \"SBGR\",\n latitude: -23.4356,\n longitude: -46.4731,\n name: \"São Paulo Guarulhos\",\n tz: \"America/Sao_Paulo\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"UUEE\",\n country: \"RU\",\n ghcnh_id: null,\n icao: \"UUEE\",\n latitude: 55.9728,\n longitude: 37.4147,\n name: \"Moscow Sheremetyevo\",\n tz: \"Europe/Moscow\",\n venues: [],\n },\n {\n code: \"UUWW\",\n country: \"RU\",\n ghcnh_id: null,\n icao: \"UUWW\",\n latitude: 55.5915,\n longitude: 37.2615,\n name: \"Moscow Vnukovo\",\n tz: \"Europe/Moscow\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"VABB\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VABB\",\n latitude: 19.0887,\n longitude: 72.8679,\n name: \"Mumbai Chhatrapati Shivaji\",\n tz: \"Asia/Kolkata\",\n venues: [],\n },\n {\n code: \"VHHH\",\n country: \"HK\",\n ghcnh_id: null,\n icao: \"VHHH\",\n latitude: 22.308,\n longitude: 113.9185,\n name: \"Hong Kong International\",\n tz: \"Asia/Hong_Kong\",\n venues: [],\n },\n {\n code: \"VIDP\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VIDP\",\n latitude: 28.5562,\n longitude: 77.1,\n name: \"Delhi Indira Gandhi\",\n tz: \"Asia/Kolkata\",\n venues: [],\n },\n {\n code: \"VILK\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VILK\",\n latitude: 26.7606,\n longitude: 80.8893,\n name: \"Lucknow Chaudhary Charan Singh International\",\n tz: \"Asia/Kolkata\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"VTBS\",\n country: \"TH\",\n ghcnh_id: null,\n icao: \"VTBS\",\n latitude: 13.69,\n longitude: 100.7501,\n name: \"Bangkok Suvarnabhumi\",\n tz: \"Asia/Bangkok\",\n venues: [],\n },\n {\n code: \"WMKK\",\n country: \"MY\",\n ghcnh_id: null,\n icao: \"WMKK\",\n latitude: 2.7456,\n longitude: 101.7099,\n name: \"Kuala Lumpur International\",\n tz: \"Asia/Kuala_Lumpur\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"WSSS\",\n country: \"SG\",\n ghcnh_id: null,\n icao: \"WSSS\",\n latitude: 1.3644,\n longitude: 103.9915,\n name: \"Singapore Changi\",\n tz: \"Asia/Singapore\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"YBBN\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YBBN\",\n latitude: -27.3842,\n longitude: 153.1175,\n name: \"Brisbane\",\n tz: \"Australia/Brisbane\",\n venues: [],\n },\n {\n code: \"YMML\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YMML\",\n latitude: -37.6733,\n longitude: 144.8433,\n name: \"Melbourne Tullamarine\",\n tz: \"Australia/Melbourne\",\n venues: [],\n },\n {\n code: \"YSSY\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YSSY\",\n latitude: -33.9461,\n longitude: 151.1772,\n name: \"Sydney Kingsford Smith\",\n tz: \"Australia/Sydney\",\n venues: [],\n },\n {\n code: \"ZBAA\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZBAA\",\n latitude: 40.0801,\n longitude: 116.5846,\n name: \"Beijing Capital\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZGGG\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZGGG\",\n latitude: 23.3924,\n longitude: 113.2988,\n name: \"Guangzhou Baiyun International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZGSZ\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZGSZ\",\n latitude: 22.6393,\n longitude: 113.8108,\n name: \"Shenzhen Bao'an International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZHCC\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZHCC\",\n latitude: 34.5197,\n longitude: 113.8408,\n name: \"Zhengzhou Xinzheng International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZHHH\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZHHH\",\n latitude: 30.7838,\n longitude: 114.2081,\n name: \"Wuhan Tianhe International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSJN\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSJN\",\n latitude: 36.8572,\n longitude: 117.2161,\n name: \"Jinan Yaoqiang International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSPD\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSPD\",\n latitude: 31.1443,\n longitude: 121.8083,\n name: \"Shanghai Pudong\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSQD\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSQD\",\n latitude: 36.3614,\n longitude: 120.0867,\n name: \"Qingdao Jiaodong International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZUCK\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZUCK\",\n latitude: 29.7192,\n longitude: 106.6417,\n name: \"Chongqing Jiangbei International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZUUU\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZUUU\",\n latitude: 30.5785,\n longitude: 103.9471,\n name: \"Chengdu Shuangliu International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n] as const;\n\nexport const STATION_BY_CODE: ReadonlyMap<string, StationInfo> = new Map<string, StationInfo>([\n [\"ATL\", STATIONS[13]!],\n [\"AUS\", STATIONS[14]!],\n [\"BKF\", STATIONS[15]!],\n [\"BNA\", STATIONS[16]!],\n [\"BOS\", STATIONS[17]!],\n [\"CVG\", STATIONS[18]!],\n [\"CYYZ\", STATIONS[0]!],\n [\"DAL\", STATIONS[19]!],\n [\"DCA\", STATIONS[20]!],\n [\"DEN\", STATIONS[21]!],\n [\"DFW\", STATIONS[22]!],\n [\"DTW\", STATIONS[23]!],\n [\"EDDB\", STATIONS[1]!],\n [\"EDDF\", STATIONS[2]!],\n [\"EDDM\", STATIONS[3]!],\n [\"EFHK\", STATIONS[4]!],\n [\"EGKK\", STATIONS[5]!],\n [\"EGLC\", STATIONS[6]!],\n [\"EGLL\", STATIONS[7]!],\n [\"EHAM\", STATIONS[8]!],\n [\"EKCH\", STATIONS[9]!],\n [\"EPWA\", STATIONS[10]!],\n [\"ESSA\", STATIONS[11]!],\n [\"FACT\", STATIONS[12]!],\n [\"HOU\", STATIONS[24]!],\n [\"IAH\", STATIONS[25]!],\n [\"LAS\", STATIONS[26]!],\n [\"LAX\", STATIONS[27]!],\n [\"LEBL\", STATIONS[42]!],\n [\"LEMD\", STATIONS[43]!],\n [\"LFPB\", STATIONS[44]!],\n [\"LFPG\", STATIONS[45]!],\n [\"LFPO\", STATIONS[46]!],\n [\"LGA\", STATIONS[28]!],\n [\"LIMC\", STATIONS[47]!],\n [\"LIRF\", STATIONS[48]!],\n [\"LLBG\", STATIONS[49]!],\n [\"LOWW\", STATIONS[50]!],\n [\"LSZH\", STATIONS[51]!],\n [\"LTAC\", STATIONS[52]!],\n [\"LTFM\", STATIONS[53]!],\n [\"MDW\", STATIONS[29]!],\n [\"MIA\", STATIONS[30]!],\n [\"MMMX\", STATIONS[54]!],\n [\"MPMG\", STATIONS[55]!],\n [\"MSP\", STATIONS[31]!],\n [\"MSY\", STATIONS[32]!],\n [\"NYC\", STATIONS[33]!],\n [\"NZAA\", STATIONS[56]!],\n [\"NZWN\", STATIONS[57]!],\n [\"OEJN\", STATIONS[58]!],\n [\"OERK\", STATIONS[59]!],\n [\"OKC\", STATIONS[34]!],\n [\"OMDB\", STATIONS[60]!],\n [\"OPKC\", STATIONS[61]!],\n [\"ORD\", STATIONS[35]!],\n [\"OTHH\", STATIONS[62]!],\n [\"PHL\", STATIONS[36]!],\n [\"PHX\", STATIONS[37]!],\n [\"RCSS\", STATIONS[63]!],\n [\"RCTP\", STATIONS[64]!],\n [\"RJAA\", STATIONS[65]!],\n [\"RJTT\", STATIONS[66]!],\n [\"RKPK\", STATIONS[67]!],\n [\"RKSI\", STATIONS[68]!],\n [\"RPLL\", STATIONS[69]!],\n [\"SAEZ\", STATIONS[70]!],\n [\"SAT\", STATIONS[38]!],\n [\"SBGR\", STATIONS[71]!],\n [\"SEA\", STATIONS[39]!],\n [\"SFO\", STATIONS[40]!],\n [\"SLC\", STATIONS[41]!],\n [\"UUEE\", STATIONS[72]!],\n [\"UUWW\", STATIONS[73]!],\n [\"VABB\", STATIONS[74]!],\n [\"VHHH\", STATIONS[75]!],\n [\"VIDP\", STATIONS[76]!],\n [\"VILK\", STATIONS[77]!],\n [\"VTBS\", STATIONS[78]!],\n [\"WMKK\", STATIONS[79]!],\n [\"WSSS\", STATIONS[80]!],\n [\"YBBN\", STATIONS[81]!],\n [\"YMML\", STATIONS[82]!],\n [\"YSSY\", STATIONS[83]!],\n [\"ZBAA\", STATIONS[84]!],\n [\"ZGGG\", STATIONS[85]!],\n [\"ZGSZ\", STATIONS[86]!],\n [\"ZHCC\", STATIONS[87]!],\n [\"ZHHH\", STATIONS[88]!],\n [\"ZSJN\", STATIONS[89]!],\n [\"ZSPD\", STATIONS[90]!],\n [\"ZSQD\", STATIONS[91]!],\n [\"ZUCK\", STATIONS[92]!],\n [\"ZUUU\", STATIONS[93]!],\n]);\n\nexport const STATION_BY_ICAO: ReadonlyMap<string, StationInfo> = new Map<string, StationInfo>([\n [\"CYYZ\", STATIONS[0]!],\n [\"EDDB\", STATIONS[1]!],\n [\"EDDF\", STATIONS[2]!],\n [\"EDDM\", STATIONS[3]!],\n [\"EFHK\", STATIONS[4]!],\n [\"EGKK\", STATIONS[5]!],\n [\"EGLC\", STATIONS[6]!],\n [\"EGLL\", STATIONS[7]!],\n [\"EHAM\", STATIONS[8]!],\n [\"EKCH\", STATIONS[9]!],\n [\"EPWA\", STATIONS[10]!],\n [\"ESSA\", STATIONS[11]!],\n [\"FACT\", STATIONS[12]!],\n [\"KATL\", STATIONS[13]!],\n [\"KAUS\", STATIONS[14]!],\n [\"KBKF\", STATIONS[15]!],\n [\"KBNA\", STATIONS[16]!],\n [\"KBOS\", STATIONS[17]!],\n [\"KCVG\", STATIONS[18]!],\n [\"KDAL\", STATIONS[19]!],\n [\"KDCA\", STATIONS[20]!],\n [\"KDEN\", STATIONS[21]!],\n [\"KDFW\", STATIONS[22]!],\n [\"KDTW\", STATIONS[23]!],\n [\"KHOU\", STATIONS[24]!],\n [\"KIAH\", STATIONS[25]!],\n [\"KLAS\", STATIONS[26]!],\n [\"KLAX\", STATIONS[27]!],\n [\"KLGA\", STATIONS[28]!],\n [\"KMDW\", STATIONS[29]!],\n [\"KMIA\", STATIONS[30]!],\n [\"KMSP\", STATIONS[31]!],\n [\"KMSY\", STATIONS[32]!],\n [\"KNYC\", STATIONS[33]!],\n [\"KOKC\", STATIONS[34]!],\n [\"KORD\", STATIONS[35]!],\n [\"KPHL\", STATIONS[36]!],\n [\"KPHX\", STATIONS[37]!],\n [\"KSAT\", STATIONS[38]!],\n [\"KSEA\", STATIONS[39]!],\n [\"KSFO\", STATIONS[40]!],\n [\"KSLC\", STATIONS[41]!],\n [\"LEBL\", STATIONS[42]!],\n [\"LEMD\", STATIONS[43]!],\n [\"LFPB\", STATIONS[44]!],\n [\"LFPG\", STATIONS[45]!],\n [\"LFPO\", STATIONS[46]!],\n [\"LIMC\", STATIONS[47]!],\n [\"LIRF\", STATIONS[48]!],\n [\"LLBG\", STATIONS[49]!],\n [\"LOWW\", STATIONS[50]!],\n [\"LSZH\", STATIONS[51]!],\n [\"LTAC\", STATIONS[52]!],\n [\"LTFM\", STATIONS[53]!],\n [\"MMMX\", STATIONS[54]!],\n [\"MPMG\", STATIONS[55]!],\n [\"NZAA\", STATIONS[56]!],\n [\"NZWN\", STATIONS[57]!],\n [\"OEJN\", STATIONS[58]!],\n [\"OERK\", STATIONS[59]!],\n [\"OMDB\", STATIONS[60]!],\n [\"OPKC\", STATIONS[61]!],\n [\"OTHH\", STATIONS[62]!],\n [\"RCSS\", STATIONS[63]!],\n [\"RCTP\", STATIONS[64]!],\n [\"RJAA\", STATIONS[65]!],\n [\"RJTT\", STATIONS[66]!],\n [\"RKPK\", STATIONS[67]!],\n [\"RKSI\", STATIONS[68]!],\n [\"RPLL\", STATIONS[69]!],\n [\"SAEZ\", STATIONS[70]!],\n [\"SBGR\", STATIONS[71]!],\n [\"UUEE\", STATIONS[72]!],\n [\"UUWW\", STATIONS[73]!],\n [\"VABB\", STATIONS[74]!],\n [\"VHHH\", STATIONS[75]!],\n [\"VIDP\", STATIONS[76]!],\n [\"VILK\", STATIONS[77]!],\n [\"VTBS\", STATIONS[78]!],\n [\"WMKK\", STATIONS[79]!],\n [\"WSSS\", STATIONS[80]!],\n [\"YBBN\", STATIONS[81]!],\n [\"YMML\", STATIONS[82]!],\n [\"YSSY\", STATIONS[83]!],\n [\"ZBAA\", STATIONS[84]!],\n [\"ZGGG\", STATIONS[85]!],\n [\"ZGSZ\", STATIONS[86]!],\n [\"ZHCC\", STATIONS[87]!],\n [\"ZHHH\", STATIONS[88]!],\n [\"ZSJN\", STATIONS[89]!],\n [\"ZSPD\", STATIONS[90]!],\n [\"ZSQD\", STATIONS[91]!],\n [\"ZUCK\", STATIONS[92]!],\n [\"ZUUU\", STATIONS[93]!],\n]);\n","// TS-W6 Wave 1 — availability(station, cache) reading from CacheStore.\n//\n// Ports Python `mostlyright.discovery.availability` semantics. Python walks the\n// on-disk parquet hierarchy; TS uses the same canonical cache-key shape so the\n// CacheStore implementation (Memory / IndexedDB / Fs) is the persistence layer.\n//\n// The keys we scan match `cacheKeyForObservations(station, year, month, source?)`\n// and `cacheKeyForClimate(station, year)`. @mostlyrightmd/meta's `research()` writes\n// cache entries under the 3-letter NWS code (`resolved.code` from\n// STATION_BY_ICAO / STATION_BY_CODE) for US stations — e.g. `KNYC` resolves to\n// `NYC` and the cache key reads `...:observations:NYC:...`. Codex iter-2 P2:\n// availability resolves the input the same way so `availability(\"KNYC\", cache)`\n// finds entries written by `research(\"KNYC\", ...)`.\n\nimport { STATION_BY_CODE, STATION_BY_ICAO } from \"../data/generated/stations.js\";\nimport type { CacheStore } from \"../internal/cache/index.js\";\n\n/**\n * Cache-coverage summary for a station.\n *\n * Mirrors Python `availability()` return shape.\n */\nexport interface AvailabilityResult {\n station: string;\n /** Count of distinct (year, month) observation cache entries. */\n monthsCached: number;\n /** Earliest cached month as `\"YYYY-MM\"`, or null if none. */\n firstMonth: string | null;\n /** Latest cached month as `\"YYYY-MM\"`, or null if none. */\n lastMonth: string | null;\n /** Count of cached climate years. */\n climateYears: number;\n /** Earliest cached climate year as `\"YYYY\"`, or null if none. */\n firstClimateYear: string | null;\n /** Latest cached climate year as `\"YYYY\"`, or null if none. */\n lastClimateYear: string | null;\n}\n\n/**\n * Optional adapter that lets `availability()` enumerate keys from a store.\n *\n * CacheStore's mandatory contract is opaque key-value: get/set/delete/withLock.\n * Discovery needs to enumerate which keys exist for a station — this is\n * implementation-specific (Memory iterates its Map, IndexedDB uses\n * `getAllKeys`, Fs walks the directory tree). Stores that support enumeration\n * implement the optional `listKeys(prefix)` method and `availability()` uses\n * it; stores without it return zero-coverage but never throw.\n *\n * Listed keys may exceed the requested prefix in the result (callers filter);\n * `listKeys` is best-effort.\n */\nexport interface KeyEnumerableStore extends CacheStore {\n listKeys(prefix: string): Promise<ReadonlyArray<string>>;\n}\n\nfunction hasListKeys(store: CacheStore): store is KeyEnumerableStore {\n return typeof (store as KeyEnumerableStore).listKeys === \"function\";\n}\n\nconst STATION_RE = /^[A-Z0-9]{3,5}$/;\n\n/**\n * Resolve `station` to the canonical CACHE-KEY station code.\n *\n * - For known stations: returns the 3-letter NWS `code` (e.g. `KNYC` → `NYC`)\n * if one exists, falling back to the 4-letter ICAO. This matches the cache\n * key that `research()` writes for US stations via `resolved.code` and\n * international stations via `resolved.icao` (no NWS code exists).\n * - For unknown inputs that still satisfy the 3-5 char alphanumeric format:\n * returns the upper-cased input. This lets bespoke callers query custom\n * keys without coupling availability to the station registry.\n */\nfunction normalizeStation(station: string): string {\n const upper = station.toUpperCase();\n if (!STATION_RE.test(upper)) {\n throw new RangeError(\n `availability: station must be a 3-5 char alphanumeric code; got ${JSON.stringify(station)}`,\n );\n }\n const byIcao = STATION_BY_ICAO.get(upper);\n if (byIcao !== undefined) {\n // US stations: NWS code is the cache-key form. International stations:\n // no NWS code, ICAO is the cache-key form.\n return byIcao.code ?? byIcao.icao;\n }\n const byCode = STATION_BY_CODE.get(upper);\n if (byCode !== undefined) {\n return byCode.code ?? byCode.icao;\n }\n // Bespoke / unknown codes — pass through. availability() returns\n // zero-coverage if the cache doesn't have entries under this exact key.\n return upper;\n}\n\nconst OBS_KEY_RE = /^mostlyright:v1:observations:([A-Z0-9]+):(\\d{4}):(\\d{2})(?::[a-z0-9_-]+)?$/;\nconst CLIMATE_KEY_RE = /^mostlyright:v1:climate:([A-Z0-9]+):(\\d{4})$/;\n\n/**\n * Options for `availability()`.\n */\nexport interface AvailabilityOptions {\n /**\n * If true, confirm each candidate key with `cache.get()` before counting.\n * Eliminates the small overcount possible on stores whose `listKeys()` can\n * return keys with already-expired TTL entries (FsStore and IndexedDBStore\n * lazy-evict on `get`, not on `listKeys` — codex iter-3 P2). Off by default\n * because the v0.1.0 `research()` flow never writes with `ttlMs`, so the\n * overcount window is empty; turn on only if you populate the cache with\n * explicit TTLs.\n *\n * Cost: one `get()` per matching key. On warm caches this is cheap\n * (MemoryStore + IndexedDBStore in-memory). On FsStore it reads each\n * candidate's file.\n */\n readonly validate?: boolean;\n}\n\n/**\n * Return a summary of cached coverage for `station`.\n *\n * Stores without enumeration support return a zero-coverage result with the\n * station name populated (counts all zero, dates null).\n *\n * Pass `{ validate: true }` to confirm each candidate key via `cache.get()`\n * — needed if your callers populate the cache with `ttlMs` and might query\n * after expiry. The v0.1.0 `research()` flow does not use `ttlMs`, so the\n * default (fast scan, no validation) is correct for the canonical path.\n */\nexport async function availability(\n station: string,\n cache: CacheStore,\n opts: AvailabilityOptions = {},\n): Promise<AvailabilityResult> {\n const stationCode = normalizeStation(station);\n const empty: AvailabilityResult = Object.freeze({\n station: stationCode,\n monthsCached: 0,\n firstMonth: null,\n lastMonth: null,\n climateYears: 0,\n firstClimateYear: null,\n lastClimateYear: null,\n });\n\n if (!hasListKeys(cache)) {\n return empty;\n }\n\n // Scan both the canonical cache-key form AND the original upper-cased\n // input (when they differ — e.g. user passed \"KNYC\" → normalized \"NYC\";\n // scan \":NYC:\" AND \":KNYC:\"). Codex iter-5 P2: a caller using the\n // documented `cacheKeyForObservations(\"KNYC\", ...)` helper writes under\n // :KNYC:, which the normalized prefix would miss.\n const upperInput = station.toUpperCase();\n const scanCodes = upperInput === stationCode ? [stationCode] : [stationCode, upperInput];\n\n const obsPrefixes = scanCodes.map((c) => `mostlyright:v1:observations:${c}:`);\n const climatePrefixes = scanCodes.map((c) => `mostlyright:v1:climate:${c}:`);\n\n const obsKeySets = await Promise.all(obsPrefixes.map((p) => cache.listKeys(p)));\n const climateKeySets = await Promise.all(climatePrefixes.map((p) => cache.listKeys(p)));\n const obsKeys = obsKeySets.flat();\n const climateKeys = climateKeySets.flat();\n\n // Collect the matching keys grouped by (year-month) / year so we can both\n // dedupe (e.g. per-source observation keys for the same month) and run a\n // single validation get() per group.\n const monthCandidates = new Map<string, string[]>();\n for (const key of obsKeys) {\n const m = OBS_KEY_RE.exec(key);\n if (m === null) continue;\n const keyStation = m[1] as string;\n if (!scanCodes.includes(keyStation)) continue;\n const ym = `${m[2]}-${m[3]}`;\n const arr = monthCandidates.get(ym) ?? [];\n arr.push(key);\n monthCandidates.set(ym, arr);\n }\n\n const yearCandidates = new Map<string, string[]>();\n for (const key of climateKeys) {\n const m = CLIMATE_KEY_RE.exec(key);\n if (m === null) continue;\n const keyStation = m[1] as string;\n if (!scanCodes.includes(keyStation)) continue;\n const y = m[2] as string;\n const arr = yearCandidates.get(y) ?? [];\n arr.push(key);\n yearCandidates.set(y, arr);\n }\n\n let months: string[];\n let years: string[];\n\n if (opts.validate === true) {\n // For each candidate group, confirm at least one key still resolves.\n // Stores lazy-evict expired entries inside get() — calling it discards\n // stale TTLs from the on-disk / on-IDB state and gives us a correct\n // overall count.\n const monthChecks = await Promise.all(\n [...monthCandidates.entries()].map(async ([ym, keys]) => {\n for (const k of keys) {\n if ((await cache.get(k)) !== null) return ym;\n }\n return null;\n }),\n );\n months = monthChecks.filter((v): v is string => v !== null).sort();\n const yearChecks = await Promise.all(\n [...yearCandidates.entries()].map(async ([y, keys]) => {\n for (const k of keys) {\n if ((await cache.get(k)) !== null) return y;\n }\n return null;\n }),\n );\n years = yearChecks.filter((v): v is string => v !== null).sort();\n } else {\n months = [...monthCandidates.keys()].sort();\n years = [...yearCandidates.keys()].sort();\n }\n\n return Object.freeze({\n station: stationCode,\n monthsCached: months.length,\n firstMonth: months[0] ?? null,\n lastMonth: months.at(-1) ?? null,\n climateYears: years.length,\n firstClimateYear: years[0] ?? null,\n lastClimateYear: years.at(-1) ?? null,\n });\n}\n","// TS-W6 Wave 2 — internationalDailyExtremes rollup.\n//\n// Ports Python `mostlyright.international.daily_extremes` semantics:\n//\n// - For each row, parse `observed_at` as UTC, convert to the station's IANA\n// local calendar date via `Intl.DateTimeFormat` (the only universally-\n// available tz-aware extractor in JS), bucket per `(localDate)`.\n// - For each bucketed day:\n// * n_obs < 12 → tmin/tmax/tmean null (low coverage; matches Python\n// `_LOW_COVERAGE_THRESHOLD`).\n// * Otherwise: tmin = min(temp_c), tmax = max(temp_c), tmean = mean.\n// Round HALF_UP — to 1 decimal for US stations, whole °C for intl.\n// - precip_mm summed across rows that report it (mm — matches the\n// observation schema field name; Python uses `precip_inches` because its\n// historical units are inches, but the TS schema is mm).\n//\n// `stationTz` is required because the input rows are raw observations (no\n// station registry lookup here); callers supply the IANA tz string for the\n// station they're rolling up.\n\nconst LOW_COVERAGE_THRESHOLD = 12;\n\n/** Minimal row shape consumed by `internationalDailyExtremes`. */\nexport interface InternationalRow {\n /** ISO 8601 UTC instant. Must end with `Z` or include an offset. */\n observed_at?: string | null;\n /** Air temperature in degrees Celsius. */\n temp_c?: number | null;\n /** 1-hour precipitation total in millimeters. */\n precip_mm_1h?: number | null;\n /** Source identifier (preserved on the tmin/tmax aggregate). */\n source?: string | null;\n}\n\nexport interface DailyExtreme {\n /** Station-local calendar date as `YYYY-MM-DD`. */\n localDate: string;\n /** Count of rows with a parseable temp_c. */\n nObs: number;\n /** Min temperature in °C, or null on low coverage. */\n tempMinC: number | null;\n /** Max temperature in °C, or null on low coverage. */\n tempMaxC: number | null;\n /** Mean temperature in °C, or null on low coverage. */\n tempMeanC: number | null;\n /** Min temperature in °F, or null on low coverage. */\n tempMinF: number | null;\n /** Max temperature in °F, or null on low coverage. */\n tempMaxF: number | null;\n /** Total 1-hour precipitation across the local day, in mm. */\n precipMm: number;\n /** Source identifier of the row that produced tmin (or null on low coverage). */\n sourceTmin: string | null;\n /** Source identifier of the row that produced tmax (or null on low coverage). */\n sourceTmax: string | null;\n}\n\nexport interface InternationalDailyExtremesOptions {\n /** IANA timezone identifier for the station, e.g. `\"Asia/Tokyo\"`. Required. */\n stationTz: string;\n /**\n * Decimal places for HALF_UP rounding. Defaults to 0 (whole °C) — the\n * international convention. Pass `1` for US-station tenths.\n */\n precision?: number;\n /**\n * Minimum number of observations required for tmin/tmax/tmean to be\n * populated. Defaults to 12 (the Python threshold). Tests can override.\n */\n minObs?: number;\n}\n\nconst PARTS_CACHE = new Map<string, Intl.DateTimeFormat>();\n\nfunction getDateFormatter(tz: string): Intl.DateTimeFormat {\n let f = PARTS_CACHE.get(tz);\n if (f === undefined) {\n // `formatToParts` with these options yields `{year, month, day}` parts\n // for the given UTC instant in the requested tz. The result is the\n // local calendar date even across DST and arbitrary UTC offsets.\n f = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: tz,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n });\n PARTS_CACHE.set(tz, f);\n }\n return f;\n}\n\nfunction localDateFor(instant: Date, tz: string): string {\n const parts = getDateFormatter(tz).formatToParts(instant);\n let y = \"\";\n let m = \"\";\n let d = \"\";\n for (const p of parts) {\n if (p.type === \"year\") y = p.value;\n else if (p.type === \"month\") m = p.value;\n else if (p.type === \"day\") d = p.value;\n }\n return `${y}-${m}-${d}`;\n}\n\nfunction parseInstant(observed: string | null | undefined): Date | null {\n if (observed === undefined || observed === null || observed.length === 0) {\n return null;\n }\n const ms = Date.parse(observed);\n if (Number.isNaN(ms)) return null;\n return new Date(ms);\n}\n\nfunction roundHalfUp(value: number, places: number): number {\n if (!Number.isFinite(value)) return value;\n // HALF_UP rounding: tie-breaks always round away from zero for positives,\n // toward +∞ for negatives. JS `Math.round` rounds half toward +∞, which\n // disagrees with HALF_UP on negative .5 boundaries; replicate the Python\n // Decimal behavior by adjusting positives only and mirroring negatives.\n const scale = 10 ** places;\n const sign = value < 0 ? -1 : 1;\n const abs = Math.abs(value);\n // Add a tiny epsilon scaled to the magnitude to defeat IEEE-754 errors that\n // make `2.675 * 100` come out as `267.49999...`. The epsilon is small\n // enough (relative-1e-12) not to affect legitimate non-tie values.\n const rounded = Math.floor(abs * scale + 0.5 + abs * 1e-12) / scale;\n return sign * rounded;\n}\n\nfunction cToF(c: number): number {\n return c * 1.8 + 32;\n}\n\n/**\n * Roll up observation rows to per-local-calendar-day temperature extremes.\n *\n * @param rows raw observation rows (any source). Rows without a parseable\n * `observed_at` are dropped.\n * @param opts `stationTz` is required. Optional `precision` (default 0;\n * pass 1 for US-station tenths) and `minObs` (default 12).\n *\n * @returns one entry per local calendar day with at least one row. Days\n * with fewer than `minObs` rows have temps set to null.\n */\nexport function internationalDailyExtremes(\n rows: ReadonlyArray<InternationalRow>,\n opts: InternationalDailyExtremesOptions,\n): DailyExtreme[] {\n const tz = opts.stationTz;\n if (typeof tz !== \"string\" || tz.length === 0) {\n throw new RangeError(\"internationalDailyExtremes: stationTz is required (non-empty string)\");\n }\n // Phase 18: integer-°F lattice rationale.\n // For US stations (ASOS), constituent observations carry temp_c values\n // recovered from Tgroup tenths-°C (e.g. 10.0, 11.1, 12.2 from integer °F\n // 50, 52, 54 — Phase 18 PREC-01/PREC-02). tmin/tmax = min/max of those\n // values are themselves on the integer-°F lattice, and 0.1°C HALF_UP\n // rounding is a no-op. tmean IS a non-lattice average and benefits from\n // 0.1°C rounding. For international stations (no Tgroup), constituent\n // values are derived floats and rounding to whole °C (`precision = 0`) is\n // the convention.\n //\n // Callers pass `precision: 1` for US ASOS data and `precision: 0` (the\n // default) for international. See\n // .planning/phases/18-precision-fix-asos-integer-fahrenheit/18-CONTEXT.md.\n const precision = opts.precision ?? 0;\n const minObs = opts.minObs ?? LOW_COVERAGE_THRESHOLD;\n\n // Validate tz up front so we get a clean error rather than per-row failures.\n try {\n getDateFormatter(tz);\n } catch (e) {\n throw new RangeError(\n `internationalDailyExtremes: invalid stationTz ${JSON.stringify(tz)}: ${(e as Error).message}`,\n );\n }\n\n type Bucket = {\n temps: { value: number; source: string | null }[];\n precipMm: number;\n };\n const byLocalDate = new Map<string, Bucket>();\n\n for (const row of rows) {\n const instant = parseInstant(row.observed_at);\n if (instant === null) continue;\n const localDate = localDateFor(instant, tz);\n let bucket = byLocalDate.get(localDate);\n if (bucket === undefined) {\n bucket = { temps: [], precipMm: 0 };\n byLocalDate.set(localDate, bucket);\n }\n const t = row.temp_c;\n if (typeof t === \"number\" && Number.isFinite(t)) {\n bucket.temps.push({ value: t, source: row.source ?? null });\n }\n const p = row.precip_mm_1h;\n if (typeof p === \"number\" && Number.isFinite(p)) {\n bucket.precipMm += p;\n }\n }\n\n const out: DailyExtreme[] = [];\n const sortedDates = [...byLocalDate.keys()].sort();\n for (const localDate of sortedDates) {\n const bucket = byLocalDate.get(localDate);\n if (bucket === undefined) continue;\n const nObs = bucket.temps.length;\n let tempMinC: number | null = null;\n let tempMaxC: number | null = null;\n let tempMeanC: number | null = null;\n let sourceTmin: string | null = null;\n let sourceTmax: string | null = null;\n\n // Codex iter-4 P2: `nObs >= minObs` alone is not enough when minObs=0,\n // because a day with a parseable timestamp but no finite temp_c reaches\n // this branch with nObs === 0 and then dereferences bucket.temps[0].\n // Always require at least one temperature row before computing extremes.\n if (nObs > 0 && nObs >= minObs) {\n let minIdx = 0;\n let maxIdx = 0;\n let sum = 0;\n for (let i = 0; i < bucket.temps.length; i += 1) {\n const v = bucket.temps[i] as { value: number; source: string | null };\n sum += v.value;\n const minRow = bucket.temps[minIdx] as { value: number };\n const maxRow = bucket.temps[maxIdx] as { value: number };\n if (v.value < minRow.value) minIdx = i;\n if (v.value > maxRow.value) maxIdx = i;\n }\n const mean = sum / nObs;\n const minRow = bucket.temps[minIdx] as { value: number; source: string | null };\n const maxRow = bucket.temps[maxIdx] as { value: number; source: string | null };\n tempMinC = roundHalfUp(minRow.value, precision);\n tempMaxC = roundHalfUp(maxRow.value, precision);\n tempMeanC = roundHalfUp(mean, precision);\n sourceTmin = minRow.source;\n sourceTmax = maxRow.source;\n }\n\n out.push(\n Object.freeze({\n localDate,\n nObs,\n tempMinC,\n tempMaxC,\n tempMeanC,\n tempMinF: tempMinC === null ? null : roundHalfUp(cToF(tempMinC), precision),\n tempMaxF: tempMaxC === null ? null : roundHalfUp(cToF(tempMaxC), precision),\n precipMm: roundHalfUp(bucket.precipMm, 4),\n sourceTmin,\n sourceTmax,\n }),\n );\n }\n\n return out;\n}\n","// Structured exception hierarchy for the mostlyright TS SDK.\n//\n// Mirrors the Python design in `packages/core/src/mostlyright/core/exceptions.py`\n// and `packages/core/src/mostlyright/_internal/exceptions.py`. Every error\n// subclasses `MostlyRightError` and exposes a `toDict()` method that returns a\n// JSON-safe payload suitable for MCP `error.data` / extension messaging.\n//\n// Role-name vocabulary for SourceMismatchError matches Python:\n// \"observations\" / \"forecasts\" / \"settlement\" (long form, NOT the col prefixes)\n\n// ---------------------------------------------------------------------------\n// JSON-safe coercion (mirrors `mostlyright.core._json_safe.to_json_safe`)\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively coerce `value` into a JSON-serializable structure.\n *\n * Coercion rules (mirrors Python's `to_json_safe`):\n * - `null` / `undefined` / `NaN` / `Infinity` / `-Infinity` → `null`\n * - `Date` → ISO 8601 UTC string ending in `Z`\n * - Numeric / boolean / string scalars pass through (non-finite numbers → null)\n * - Arrays + plain objects → recursive (cycles → `{ _cycle: true, value: String(obj) }`)\n * - Dict keys MUST be strings; non-string keys throw `TypeError`.\n * - Anything else (Symbol, function, class instance without `toJSON`) →\n * `{ _repr_only: true, value: String(value) }`.\n */\nexport function toJsonSafe(value: unknown, seen?: WeakSet<object>): unknown {\n const visited = seen ?? new WeakSet<object>();\n\n // null / undefined → null\n if (value === null || value === undefined) {\n return null;\n }\n\n // Booleans pass through.\n if (typeof value === \"boolean\") {\n return value;\n }\n\n // Strings pass through.\n if (typeof value === \"string\") {\n return value;\n }\n\n // Numbers — non-finite (NaN, +/-Infinity) coerce to null.\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n\n // bigint → number when safe, else stringify (JSON can't natively encode bigint).\n if (typeof value === \"bigint\") {\n // Match Python: integers pass through as native numeric. Safe-range only.\n if (value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) {\n return Number(value);\n }\n return { _repr_only: true, value: value.toString() };\n }\n\n // Date → ISO 8601 UTC string (always ending in \"Z\").\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n return null;\n }\n return value.toISOString();\n }\n\n // Arrays — recurse, track cycles.\n if (Array.isArray(value)) {\n if (visited.has(value)) {\n return { _cycle: true, value: String(value) };\n }\n visited.add(value);\n try {\n return value.map((item) => toJsonSafe(item, visited));\n } finally {\n visited.delete(value);\n }\n }\n\n // Plain objects — recurse, track cycles, enforce string keys.\n if (typeof value === \"object\") {\n if (visited.has(value as object)) {\n return { _cycle: true, value: String(value) };\n }\n visited.add(value as object);\n try {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>)) {\n if (typeof key !== \"string\") {\n throw new TypeError(`toJsonSafe dict keys must be string; got ${typeof key}`);\n }\n out[key] = toJsonSafe((value as Record<string, unknown>)[key], visited);\n }\n return out;\n } finally {\n visited.delete(value as object);\n }\n }\n\n // Symbols, functions, etc. — repr-only marker.\n return { _repr_only: true, value: String(value) };\n}\n\n// ---------------------------------------------------------------------------\n// Base class\n// ---------------------------------------------------------------------------\n\nexport interface MostlyRightErrorOptions {\n errorCode?: string;\n source?: string | null;\n requestId?: string | null;\n}\n\n/**\n * Base class for all mostlyright structured errors.\n *\n * `errorCode` is a stable enum (e.g. \"SOURCE_UNAVAILABLE\") used by callers /\n * agents to branch on without parsing message text. `source` is the source id\n * involved (e.g. \"iem.archive\") when applicable, and `requestId` correlates a\n * JSON-RPC / MCP request id when applicable.\n */\nexport class MostlyRightError extends Error {\n /** Subclass override — the stable string enum surfaced via `errorCode`. */\n static defaultErrorCode = \"MOSTLYRIGHT_ERROR\";\n\n readonly errorCode: string;\n readonly source: string | null;\n readonly requestId: string | null;\n\n constructor(message = \"\", options: MostlyRightErrorOptions = {}) {\n super(message);\n this.name = new.target.name;\n const ctor = this.constructor as typeof MostlyRightError;\n this.errorCode = options.errorCode ?? ctor.defaultErrorCode;\n this.source = options.source ?? null;\n this.requestId = options.requestId ?? null;\n // Restore prototype chain after `Error` (needed for `instanceof` across\n // transpilation / ES5 targets).\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Subclass hook returning the structured attributes for `toDict`.\n * Values are passed through `toJsonSafe` by `toDict()`, so subclasses\n * don't need to coerce values themselves.\n */\n protected payload(): Record<string, unknown> {\n return {\n error_code: this.errorCode,\n message: this.message,\n source: this.source,\n request_id: this.requestId,\n };\n }\n\n /** Return a JSON-safe dict suitable for MCP `error.data`. */\n toDict(): Record<string, unknown> {\n const safe = toJsonSafe(this.payload());\n return safe as Record<string, unknown>;\n }\n}\n\n// ---------------------------------------------------------------------------\n// SourceUnavailableError\n// ---------------------------------------------------------------------------\n\nexport interface SourceUnavailableErrorOptions extends MostlyRightErrorOptions {\n httpStatus?: number | null;\n retryable?: boolean;\n retryAfterS?: number | null;\n underlying?: string;\n url?: string | null;\n}\n\nexport class SourceUnavailableError extends MostlyRightError {\n static override defaultErrorCode = \"SOURCE_UNAVAILABLE\";\n\n readonly httpStatus: number | null;\n readonly retryable: boolean;\n readonly retryAfterS: number | null;\n readonly underlying: string;\n readonly url: string | null;\n\n constructor(message = \"\", options: SourceUnavailableErrorOptions = {}) {\n super(message, options);\n this.httpStatus = options.httpStatus ?? null;\n this.retryable = options.retryable ?? false;\n this.retryAfterS = options.retryAfterS ?? null;\n this.underlying = options.underlying ?? \"\";\n this.url = options.url ?? null;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n http_status: this.httpStatus,\n retryable: this.retryable,\n retry_after_s: this.retryAfterS,\n underlying: this.underlying,\n url: this.url,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// DataAvailabilityError (Phase 21 21-09)\n// ---------------------------------------------------------------------------\n//\n// Typed exception for \"I tried to fetch and got nothing usable\" — replaces\n// 3+ overloaded SourceUnavailableError sites where consumers had to parse\n// message strings to differentiate (rate-limit retry vs model-unavailable\n// hard-fail vs cache-miss re-fetch). The reason enum is shared lockstep\n// with Python (`mostlyright.core.exceptions.DataAvailabilityError`); drift\n// is the load-bearing risk.\n\n/** Shared reason enum — MUST match Python EXACTLY (Phase 21 D-04). */\nexport const DATA_AVAILABILITY_REASONS = [\n \"model_unavailable\",\n \"out_of_window\",\n \"cache_miss\",\n \"source_404\",\n \"source_5xx\",\n \"rate_limited\",\n] as const;\n\nexport type DataAvailabilityReason = (typeof DATA_AVAILABILITY_REASONS)[number];\n\nexport interface DataAvailabilityErrorOptions extends MostlyRightErrorOptions {\n reason: DataAvailabilityReason;\n hint: string;\n}\n\nexport class DataAvailabilityError extends MostlyRightError {\n static override defaultErrorCode = \"DATA_AVAILABILITY\";\n\n readonly reason: DataAvailabilityReason;\n readonly hint: string;\n\n constructor(options: DataAvailabilityErrorOptions) {\n if (!DATA_AVAILABILITY_REASONS.includes(options.reason)) {\n throw new RangeError(\n `DataAvailabilityError: unknown reason \"${String(options.reason)}\". ` +\n `Valid reasons: ${DATA_AVAILABILITY_REASONS.join(\", \")}`,\n );\n }\n if (typeof options.hint !== \"string\" || options.hint.length === 0) {\n throw new TypeError(\"DataAvailabilityError: hint is required and must be a non-empty string\");\n }\n const message = `[${options.reason}] ${options.hint}`;\n super(message, options);\n this.reason = options.reason;\n this.hint = options.hint;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n reason: this.reason,\n hint: this.hint,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// NwpNotAvailableError (Post-21-07 follow-up)\n// ---------------------------------------------------------------------------\n//\n// Subclass of DataAvailabilityError raised when the TS `forecastNwp()` stub\n// is called. Phase 21 21-07 routes the generic stub error through\n// DataAvailabilityError; this dedicated subclass adds:\n//\n// 1. instanceof-based dispatch — `catch (e) { if (e instanceof\n// NwpNotAvailableError) ... }` is cleaner than `e.reason ===\n// \"model_unavailable\" && e.source === \"nwp-stub\"`.\n// 2. IDE autocomplete on `.model` + `.station` — narrows the payload so\n// consumers don't have to parse `e.hint` to retrieve them.\n// 3. A typed compile-time signal: `forecastNwp()` returns\n// `Promise<never>`, but the throw site declares `@throws\n// NwpNotAvailableError` so JSDoc tooling surfaces the deferral up-front.\n//\n// Back-compat: NwpNotAvailableError IS-A DataAvailabilityError with\n// reason=\"model_unavailable\", so existing `catch (e) { if (e instanceof\n// DataAvailabilityError && e.reason === \"model_unavailable\") ... }` paths\n// continue to work unchanged.\n\nexport interface NwpNotAvailableErrorOptions extends MostlyRightErrorOptions {\n /** Station the caller asked for (echoed back for log/error attribution). */\n station: string;\n /** NWP model the caller asked for (e.g. `\"gfs\"`, `\"hrrr\"`). */\n model: string;\n /** Operator-actionable hint. Required (matches DataAvailabilityError contract). */\n hint: string;\n}\n\n/**\n * Raised when the TS `forecastNwp()` stub is called.\n *\n * **Why this exists:** no production-ready browser GRIB2 decoder ships in\n * v1.x (eccodes / cfgrib are C/Python only; WASM compile-time + bundle\n * size make a browser port impractical today). The function signature is\n * stable so callers can write code today; v2.0+ lands the execution body.\n *\n * **Recommended catch pattern:**\n *\n * ```ts\n * import { forecastNwp } from '@mostlyrightmd/weather';\n * import { NwpNotAvailableError } from '@mostlyrightmd/core';\n *\n * try {\n * const grid = await forecastNwp('KNYC', 'gfs');\n * } catch (e) {\n * if (e instanceof NwpNotAvailableError) {\n * console.warn(`NWP deferred to v2.0+; ${e.hint}`);\n * // Fall back to iemMosForecasts() when available, else Python SDK.\n * } else {\n * throw e;\n * }\n * }\n * ```\n *\n * See [docs/nwp-forecasts.md](https://mostlyright.md/docs/sdk/typescript/nwp-forecasts/)\n * for the full architectural rationale and the v2.0+ roadmap.\n */\nexport class NwpNotAvailableError extends DataAvailabilityError {\n static override defaultErrorCode = \"NWP_NOT_AVAILABLE\";\n\n readonly station: string;\n readonly model: string;\n\n constructor(options: NwpNotAvailableErrorOptions) {\n // exactOptionalPropertyTypes: true — only include requestId when the\n // caller passed a non-undefined value. Passing `requestId: undefined`\n // explicitly would type-error against the parent's narrower contract.\n const parentOpts: DataAvailabilityErrorOptions = {\n reason: \"model_unavailable\",\n hint: options.hint,\n source: options.source ?? `nwp.${options.model}`,\n };\n if (options.requestId !== undefined && options.requestId !== null) {\n parentOpts.requestId = options.requestId;\n }\n super(parentOpts);\n this.station = options.station;\n this.model = options.model;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n station: this.station,\n model: this.model,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// SchemaValidationError\n// ---------------------------------------------------------------------------\n\nexport interface SchemaValidationErrorOptions extends MostlyRightErrorOptions {\n schemaId: string;\n violations?: Array<Record<string, unknown>>;\n quarantineCount?: number;\n sampleViolations?: Array<Record<string, unknown>>;\n}\n\nexport class SchemaValidationError extends MostlyRightError {\n static override defaultErrorCode = \"SCHEMA_VALIDATION_FAILED\";\n\n readonly schemaId: string;\n readonly violations: Array<Record<string, unknown>>;\n readonly quarantineCount: number;\n readonly sampleViolations: Array<Record<string, unknown>>;\n\n constructor(message: string, options: SchemaValidationErrorOptions) {\n super(message, options);\n this.schemaId = options.schemaId;\n this.violations = [...(options.violations ?? [])];\n this.quarantineCount = options.quarantineCount ?? 0;\n this.sampleViolations = [...(options.sampleViolations ?? [])];\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n schema_id: this.schemaId,\n violations: this.violations,\n quarantine_count: this.quarantineCount,\n sample_violations: this.sampleViolations,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// SourceMismatchError\n// ---------------------------------------------------------------------------\n\nexport type SourceMismatchRole = \"observations\" | \"forecasts\" | \"settlement\";\n\nexport interface SourceMismatchErrorOptions extends MostlyRightErrorOptions {\n schemaSource: string;\n dataSource: string;\n role?: SourceMismatchRole | null;\n catalogWarning?: string | null;\n}\n\nexport class SourceMismatchError extends MostlyRightError {\n static override defaultErrorCode = \"SOURCE_MISMATCH\";\n\n /** Canonical role-name vocabulary (design.md §R). */\n static readonly VALID_ROLES: ReadonlySet<SourceMismatchRole> = new Set([\n \"observations\",\n \"forecasts\",\n \"settlement\",\n ]);\n\n readonly schemaSource: string;\n readonly dataSource: string;\n readonly role: SourceMismatchRole | null;\n readonly catalogWarning: string | null;\n\n constructor(message: string, options: SourceMismatchErrorOptions) {\n super(message, options);\n this.schemaSource = options.schemaSource;\n this.dataSource = options.dataSource;\n this.role = options.role ?? null;\n this.catalogWarning = options.catalogWarning ?? null;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n schema_source: this.schemaSource,\n data_source: this.dataSource,\n role: this.role,\n catalog_warning: this.catalogWarning,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// LeakageError\n// ---------------------------------------------------------------------------\n\nexport interface LeakageErrorOptions extends MostlyRightErrorOptions {\n asOf: string;\n violatingCount: number;\n sampleViolations?: Array<Record<string, unknown>>;\n}\n\nexport class LeakageError extends MostlyRightError {\n static override defaultErrorCode = \"LEAKAGE_DETECTED\";\n\n readonly asOf: string;\n readonly violatingCount: number;\n readonly sampleViolations: Array<Record<string, unknown>>;\n\n constructor(message: string, options: LeakageErrorOptions) {\n super(message, options);\n this.asOf = options.asOf;\n this.violatingCount = options.violatingCount;\n this.sampleViolations = [...(options.sampleViolations ?? [])];\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n as_of: this.asOf,\n violating_count: this.violatingCount,\n sample_violations: this.sampleViolations,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// IssuedAtMissingError (Phase 20 OM-04)\n// ---------------------------------------------------------------------------\n\nexport interface IssuedAtMissingErrorOptions extends MostlyRightErrorOptions {\n violatingCount?: number;\n sampleViolations?: Array<Record<string, unknown>>;\n}\n\n/**\n * A forecast row would land with `issuedAt = null`.\n *\n * Phase 20 OM-04. Origin: Tarabcak/mostlyright#70 — the legacy\n * seamless-feed bug where `/forecast_series` proxied Open-Meteo's\n * seamless endpoint without preserving `issued_at`, silently using\n * post-snapshot model runs in training data.\n */\nexport class IssuedAtMissingError extends SchemaValidationError {\n static override defaultErrorCode = \"ISSUED_AT_MISSING\";\n\n readonly violatingCountRows: number;\n\n constructor(message: string, options: IssuedAtMissingErrorOptions = {}) {\n super(message, {\n ...options,\n schemaId: \"schema.forecast.station.v1\",\n violations: [{ column: \"issued_at\", rule: \"non_null\" }],\n });\n this.violatingCountRows = options.violatingCount ?? 0;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n name: \"IssuedAtMissingError\",\n violating_count: this.violatingCountRows,\n origin_issue: \"Tarabcak/mostlyright#70\",\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// OpenMeteoSeamlessLeakageError (Phase 20 OM-04)\n// ---------------------------------------------------------------------------\n\nexport interface OpenMeteoSeamlessLeakageErrorOptions extends MostlyRightErrorOptions {\n model: string;\n endpointUrl: string;\n asOf?: string | null;\n}\n\n/**\n * The Open-Meteo Historical Forecast (seamless) endpoint was used\n * without `allowLeakage: true`.\n *\n * Phase 20 D-01 (locked decision): the seamless endpoint silently\n * stitches forecasts from multiple model cycles into a continuous\n * timeseries; the cycle that produced each value is unrecoverable from\n * the response. `LeakageDetector` rejects rows tagged\n * `source=\"open_meteo.seamless\"` whenever `as_of` is asserted.\n *\n * Origin: Tarabcak/mostlyright#70.\n */\nexport class OpenMeteoSeamlessLeakageError extends LeakageError {\n static override defaultErrorCode = \"OPEN_METEO_SEAMLESS_LEAKAGE\";\n\n readonly model: string;\n readonly endpointUrl: string;\n\n constructor(message: string, options: OpenMeteoSeamlessLeakageErrorOptions) {\n super(message, {\n ...options,\n asOf: options.asOf ?? \"(seamless-endpoint-refused-before-fetch)\",\n violatingCount: 0,\n });\n this.model = options.model;\n this.endpointUrl = options.endpointUrl;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n name: \"OpenMeteoSeamlessLeakageError\",\n model: this.model,\n endpoint_url: this.endpointUrl,\n origin_issue: \"Tarabcak/mostlyright#70\",\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// TemporalDriftError\n// ---------------------------------------------------------------------------\n\nexport interface TemporalDriftErrorOptions extends MostlyRightErrorOptions {\n schemaId: string;\n assertedRange: [string, string];\n violatingRows: number;\n sampleViolations?: Array<Record<string, unknown>>;\n}\n\nexport class TemporalDriftError extends MostlyRightError {\n static override defaultErrorCode = \"TEMPORAL_DRIFT\";\n\n readonly schemaId: string;\n readonly assertedRange: [string, string];\n readonly violatingRows: number;\n readonly sampleViolations: Array<Record<string, unknown>>;\n\n constructor(message: string, options: TemporalDriftErrorOptions) {\n super(message, options);\n this.schemaId = options.schemaId;\n this.assertedRange = [options.assertedRange[0], options.assertedRange[1]];\n this.violatingRows = options.violatingRows;\n this.sampleViolations = [...(options.sampleViolations ?? [])];\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n schema_id: this.schemaId,\n asserted_range: [this.assertedRange[0], this.assertedRange[1]],\n violating_rows: this.violatingRows,\n sample_violations: this.sampleViolations,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// PayloadTooLargeError\n// ---------------------------------------------------------------------------\n\nexport interface PayloadTooLargeErrorOptions extends MostlyRightErrorOptions {\n declaredSize: number;\n limit: number;\n acceptedModes?: string[];\n}\n\nexport class PayloadTooLargeError extends MostlyRightError {\n static override defaultErrorCode = \"PAYLOAD_TOO_LARGE\";\n\n readonly declaredSize: number;\n readonly limit: number;\n readonly acceptedModes: string[];\n\n constructor(message: string, options: PayloadTooLargeErrorOptions) {\n super(message, options);\n this.declaredSize = options.declaredSize;\n this.limit = options.limit;\n this.acceptedModes = [...(options.acceptedModes ?? [])];\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n declared_size: this.declaredSize,\n limit: this.limit,\n accepted_modes: this.acceptedModes,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// DeferredMarketError (TS-W6 placeholder; mirrors mostlyright.international)\n// ---------------------------------------------------------------------------\n\nexport class DeferredMarketError extends MostlyRightError {\n static override defaultErrorCode = \"DEFERRED_MARKET\";\n}\n\n// ---------------------------------------------------------------------------\n// PolymarketEventError (TS-W5 placeholder; mirrors mostlyright.markets.polymarket)\n// ---------------------------------------------------------------------------\n\nexport class PolymarketEventError extends MostlyRightError {\n static override defaultErrorCode = \"POLYMARKET_EVENT_INVALID\";\n}\n\n// ---------------------------------------------------------------------------\n// HTTP-layer hierarchy (mirrors mostlyright._internal.exceptions)\n// ---------------------------------------------------------------------------\n\nexport interface TherminalErrorOptions extends MostlyRightErrorOptions {\n statusCode?: number | null;\n retryAfter?: number | null;\n}\n\n/**\n * Base HTTP-layer marker. Subclass of `MostlyRightError` so callers that\n * catch `MostlyRightError` also catch transport errors.\n */\nexport class TherminalError extends MostlyRightError {\n static override defaultErrorCode = \"HTTP_ERROR\";\n\n readonly statusCode: number | null;\n\n constructor(message: string, options: TherminalErrorOptions = {}) {\n super(message, options);\n this.statusCode = options.statusCode ?? null;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n status_code: this.statusCode,\n };\n }\n}\n\nexport class NotFoundError extends TherminalError {\n static override defaultErrorCode = \"HTTP_NOT_FOUND\";\n\n constructor(message = \"Resource not found\", options: TherminalErrorOptions = {}) {\n super(message, { ...options, statusCode: options.statusCode ?? 404 });\n }\n}\n\nexport interface RateLimitErrorOptions extends TherminalErrorOptions {\n retryAfter?: number | null;\n}\n\nexport class RateLimitError extends TherminalError {\n static override defaultErrorCode = \"HTTP_RATE_LIMITED\";\n\n readonly retryAfter: number | null;\n\n constructor(retryAfter: number | null = 1, options: RateLimitErrorOptions = {}) {\n const msg = `Rate limit exceeded. Retry after ${retryAfter ?? \"?\"}s`;\n super(msg, { ...options, statusCode: options.statusCode ?? 429 });\n this.retryAfter = retryAfter;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n retry_after: this.retryAfter,\n };\n }\n}\n\nexport class ValidationError extends TherminalError {\n static override defaultErrorCode = \"HTTP_BAD_REQUEST\";\n\n constructor(message = \"Invalid request\", options: TherminalErrorOptions = {}) {\n super(message, { ...options, statusCode: options.statusCode ?? 400 });\n }\n}\n\nexport class AuthenticationError extends TherminalError {\n static override defaultErrorCode = \"HTTP_UNAUTHORIZED\";\n\n constructor(message = \"Authentication required\", options: TherminalErrorOptions = {}) {\n super(message, { ...options, statusCode: options.statusCode ?? 401 });\n }\n}\n\nexport class ForbiddenError extends TherminalError {\n static override defaultErrorCode = \"HTTP_FORBIDDEN\";\n\n constructor(message = \"Access denied\", options: TherminalErrorOptions = {}) {\n super(message, { ...options, statusCode: options.statusCode ?? 403 });\n }\n}\n\nexport class ServerError extends TherminalError {\n static override defaultErrorCode = \"HTTP_SERVER_ERROR\";\n\n constructor(message = \"Server error\", options: TherminalErrorOptions = {}) {\n super(message, { ...options, statusCode: options.statusCode ?? 500 });\n }\n}\n\n// ---------------------------------------------------------------------------\n// LiveStreamError + NoLiveDataError (Phase 11)\n// ---------------------------------------------------------------------------\n\n/**\n * Base class for `mostlyright.live.stream` / `live.latest` failures.\n *\n * Mirrors Python `LiveStreamError`. Live-streaming errors are a separate\n * sub-tree from `SourceUnavailableError` because the recovery path differs —\n * `stream()` swallows empty-tick errors and waits for the next polite-floor\n * cycle. Only `latest()` raises `NoLiveDataError` on empty responses.\n */\nexport class LiveStreamError extends MostlyRightError {\n static override defaultErrorCode = \"LIVE_STREAM_ERROR\";\n}\n\nexport interface NoLiveDataErrorOptions extends MostlyRightErrorOptions {\n station: string;\n source: string;\n}\n\n/**\n * `mostlyright.live.latest` returned no observations for the station.\n *\n * Carries the resolved ICAO `station` and the canonical source identity\n * tag (`\"awc.live\"` / `\"iem.live\"`) so caller logs can branch by source\n * without re-parsing the message.\n */\nexport class NoLiveDataError extends LiveStreamError {\n static override defaultErrorCode = \"NO_LIVE_DATA\";\n\n readonly station: string;\n\n constructor(message: string, options: NoLiveDataErrorOptions) {\n super(message, { ...options, source: options.source });\n this.station = options.station;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n station: this.station,\n };\n }\n}\n","// TOON v3.0 tabular format — byte-equivalent to Python `encode_tabular`.\n//\n// Ports the encoder portions of\n// `packages/core/src/mostlyright/core/formats/_toon.py` and the tabular\n// loader from `packages/core/src/mostlyright/core/formats/toon.py`.\n//\n// Wire shape (single tabular block):\n//\n// rows[N]{col1,col2,col3}:\n// v1a,v2a,v3a\n// v1b,v2b,v3b\n//\n// Where N is the row count, `{...}` is the column list, each subsequent line\n// is one row's values. Column order comes from the first row's keys.\n//\n// TOON loss matrix (matches Python `toon.py`):\n// - dict/object cells stringify deterministically via canonical JSON\n// (sorted keys + JSON.stringify of nested values).\n// - null + undefined both encode as the bare literal `null`.\n// - NaN / +Infinity / -Infinity encode as `null` (per `_format_number`).\n// - Integer-valued floats serialize without a fractional part (1.0 → \"1\").\n// - Strings that look numeric, start with - / + / digit, are\n// `true`/`false`/`null`, or contain commas/quotes/control chars are\n// quoted. Otherwise emitted bare.\n\n// ---------------------------------------------------------------------------\n// Encoder regex set (mirrors Python `_toon.py`)\n// ---------------------------------------------------------------------------\n\nconst SAFE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_.]*$/;\nconst NUMERIC_LIKE_RE = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/;\n// Quote triggers per TOON spec: colon, quote, backslash, brackets, braces,\n// ASCII control chars, NEL, LSEP, PSEP. Constructed via RegExp(...) to avoid\n// embedding literal LSEP/PSEP bytes (esbuild rejects those in regex literals).\n// biome-ignore lint/suspicious/noControlCharactersInRegex: TOON spec parity\nconst NEEDS_QUOTE_CHARS_RE = /[:\\\\\\\"'\\[\\]{}\\x00-\\x1f\\x7f\\x85\\u2028\\u2029]/;\n// Control chars without a defined TOON escape — stripped on quote.\n// biome-ignore lint/suspicious/noControlCharactersInRegex: TOON spec parity\nconst UNSUPPORTED_CTRL_RE = /[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f\\x85\\u2028\\u2029]/g;\n\n// ---------------------------------------------------------------------------\n// Scalar encoding\n// ---------------------------------------------------------------------------\n\nfunction formatNumber(n: number): string {\n if (!Number.isFinite(n)) return \"null\";\n if (n === 0) return \"0\"; // collapses -0 to \"0\"\n if (Number.isInteger(n) && Math.abs(n) <= 2 ** 53) return String(n);\n let s = String(n);\n // Expand scientific notation to plain decimal. Python uses Decimal —\n // we use toFixed where possible, falling back to toPrecision-based logic.\n if (/[eE]/.test(s)) {\n // toFixed/toPrecision don't fully match Python's Decimal expansion at\n // extreme values, but for the values realistically in weather data\n // (temperatures, lat/lng) String(n) returns plain decimal already.\n // For extreme cases, fall back to a manual expansion.\n s = expandExponent(s);\n }\n return s;\n}\n\nfunction expandExponent(s: string): string {\n const m = /^(-?)(\\d+(?:\\.\\d+)?)[eE]([+-]?\\d+)$/.exec(s);\n if (!m) return s;\n const sign = m[1] ?? \"\";\n const mantissa = m[2] ?? \"\";\n const exp = Number(m[3]);\n const [intPart, fracPart = \"\"] = mantissa.split(\".\");\n const digits = (intPart ?? \"\") + fracPart;\n const pointPos = (intPart ?? \"\").length + exp;\n let out: string;\n if (pointPos <= 0) {\n out = `0.${\"0\".repeat(-pointPos)}${digits}`.replace(/0+$/, \"\");\n if (out.endsWith(\".\")) out = out.slice(0, -1);\n } else if (pointPos >= digits.length) {\n out = digits + \"0\".repeat(pointPos - digits.length);\n } else {\n out = `${digits.slice(0, pointPos)}.${digits.slice(pointPos)}`.replace(/0+$/, \"\");\n if (out.endsWith(\".\")) out = out.slice(0, -1);\n }\n return sign + out;\n}\n\nfunction needsQuoting(s: string, delimiter: string): boolean {\n if (s.length === 0) return true;\n const first = s.charAt(0);\n const last = s.charAt(s.length - 1);\n if (first === \" \" || first === \"\\t\") return true;\n if (last === \" \" || last === \"\\t\") return true;\n if (s === \"true\" || s === \"false\" || s === \"null\") return true;\n if (first === \"-\" || first === \"+\") return true;\n if (first >= \"0\" && first <= \"9\") return true;\n if (NUMERIC_LIKE_RE.test(s)) return true;\n if (s.includes(delimiter)) return true;\n if (NEEDS_QUOTE_CHARS_RE.test(s)) return true;\n return false;\n}\n\nfunction quoteString(s: string): string {\n // Strip unsupported control chars first (matches Python — no escape\n // sequence exists for them).\n let out = s.replace(UNSUPPORTED_CTRL_RE, \"\");\n out = out.replace(/\\\\/g, \"\\\\\\\\\");\n out = out.replace(/\"/g, '\\\\\"');\n out = out.replace(/\\n/g, \"\\\\n\");\n out = out.replace(/\\r/g, \"\\\\r\");\n out = out.replace(/\\t/g, \"\\\\t\");\n return `\"${out}\"`;\n}\n\nfunction formatKey(key: string): string {\n if (typeof key !== \"string\") {\n throw new TypeError(`TOON keys must be strings; got ${typeof key}`);\n }\n if (SAFE_KEY_RE.test(key)) return key;\n return quoteString(key);\n}\n\nfunction encodeScalar(value: unknown, delimiter: string): string {\n if (value === null || value === undefined) return \"null\";\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (typeof value === \"number\") return formatNumber(value);\n if (typeof value === \"string\") {\n if (needsQuoting(value, delimiter)) return quoteString(value);\n return value;\n }\n // Objects / arrays / etc. — canonical JSON (sorted keys) for parity with\n // Python `_coerce_cell` dict-handling, otherwise String(value).\n if (typeof value === \"object\") {\n if (Array.isArray(value)) {\n return quoteString(JSON.stringify(value));\n }\n // Sorted-key JSON for determinism (matches Python json.dumps sort_keys).\n const sorted = sortedJson(value as Record<string, unknown>);\n return quoteString(sorted);\n }\n return quoteString(String(value));\n}\n\nfunction sortedJson(obj: Record<string, unknown>): string {\n const keys = Object.keys(obj).sort();\n const parts = keys.map((k) => `${JSON.stringify(k)}:${JSON.stringify(obj[k])}`);\n return `{${parts.join(\",\")}}`;\n}\n\n// ---------------------------------------------------------------------------\n// Tabular validation (mirrors Python `_is_tabular`)\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when {@link toonDumps} receives rows that aren't valid tabular\n * input. Mirrors the Python `encode_tabular` `ValueError` — the encoder\n * MUST refuse non-uniform rows or non-primitive values rather than\n * silently dropping columns or stringifying nested structures.\n *\n * Iter-1 C3: the previous TS encoder used `Object.keys(rows[0])` and\n * encoded every subsequent row through that column list — meaning rows\n * with extra keys had them dropped, rows missing a first-row key got a\n * silent `null`, and rows with object/array values stringified via\n * `JSON.stringify` (also silent). All three are data corruption when the\n * caller didn't realize their rows weren't uniform.\n */\nexport class ToonTabularError extends RangeError {\n override name = \"ToonTabularError\";\n}\n\nfunction isToonPrimitive(v: unknown): boolean {\n // Python `_is_tabular` accepts None / str / int / float / bool. The TS\n // analog: null/undefined (both encode as `null`), string, finite or\n // non-finite number (NaN/Inf encode as `null` via formatNumber),\n // boolean. Anything else (object, array, function, symbol, bigint) is\n // non-tabular.\n if (v === null || v === undefined) return true;\n const t = typeof v;\n return t === \"string\" || t === \"number\" || t === \"boolean\";\n}\n\nfunction assertTabular(rows: ReadonlyArray<Record<string, unknown>>): void {\n if (rows.length === 0) return;\n const first = rows[0] as Record<string, unknown>;\n const expectedKeys = Object.keys(first);\n if (expectedKeys.length === 0) {\n throw new ToonTabularError(\n \"toonDumps requires non-empty rows; first row has no keys (Python parity: encode_tabular rejects empty key set)\",\n );\n }\n const expectedKeySet = new Set(expectedKeys);\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i] as Record<string, unknown>;\n const rowKeys = Object.keys(row);\n // (a) Key-set equality. Python compares `set(item.keys()) != key_set`.\n if (rowKeys.length !== expectedKeySet.size) {\n throw new ToonTabularError(\n `toonDumps requires uniform rows; row ${i} has ${rowKeys.length} key(s) vs row 0's ${expectedKeySet.size}. Python encode_tabular rejects rows whose key sets differ.`,\n );\n }\n for (const k of rowKeys) {\n if (!expectedKeySet.has(k)) {\n throw new ToonTabularError(\n `toonDumps requires uniform rows; row ${i} has key ${JSON.stringify(k)} not present in row 0. Python encode_tabular rejects rows whose key sets differ.`,\n );\n }\n }\n // (b) Value primitivity. Python check: `v is None or isinstance(v, str|int|float|bool)`.\n for (const k of expectedKeys) {\n const v = row[k];\n if (!isToonPrimitive(v)) {\n throw new ToonTabularError(\n `toonDumps requires primitive cell values; row ${i} column ${JSON.stringify(k)} has non-primitive value of type ${typeof v}. Python encode_tabular rejects nested objects/arrays at the cell level.`,\n );\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public encoder\n// ---------------------------------------------------------------------------\n\n/**\n * Encode rows as a TOON v3.0 tabular block.\n *\n * Header is `rows[N]{c1,c2,...}:`; data lines are 2-space indented and\n * comma-separated. Empty rows emits `rows[0]:` (header-only, no columns\n * region; matches Python `_encode_array_field` empty-list path).\n *\n * Note: empty-row encoding differs from `dumps()` in `toon.py` (which\n * carries column names through `rows[0]{...}:`). The TS encoder accepts\n * a `columns` second arg in the empty case for parity with that\n * pandas-aware wrapper.\n *\n * @throws {ToonTabularError} when rows are non-uniform (differing key\n * sets across rows) or when any cell value is non-primitive\n * (object/array/bigint/etc.). Mirrors Python `encode_tabular`'s\n * `ValueError`. Iter-1 C3 fix.\n */\nexport function toonDumps(\n rows: ReadonlyArray<Record<string, unknown>>,\n columns?: ReadonlyArray<string>,\n): string {\n if (rows.length === 0) {\n // Empty-frame: carry column names if provided (matches the Python\n // DataFrame wrapper's `rows[0]{...}:` empty form). Otherwise emit the\n // bare encoder form.\n if (columns !== undefined) {\n const cols = columns.map((c) => formatKey(String(c))).join(\",\");\n return `rows[0]{${cols}}:`;\n }\n return \"rows[0]:\";\n }\n // C3 hard gate: refuse non-uniform rows BEFORE looking at row 0's keys\n // for the column header. Otherwise we'd silently drop extra columns or\n // null-fill missing ones.\n assertTabular(rows);\n const cols = Object.keys(rows[0] as Record<string, unknown>);\n const colHeader = cols.map((c) => formatKey(c)).join(\",\");\n const header = `rows[${rows.length}]{${colHeader}}:`;\n const dataLines = rows.map((r) => {\n const vals = cols.map((c) => encodeScalar((r as Record<string, unknown>)[c], \",\"));\n return ` ${vals.join(\",\")}`;\n });\n return `${header}\\n${dataLines.join(\"\\n\")}`;\n}\n\n// ---------------------------------------------------------------------------\n// Decoder\n// ---------------------------------------------------------------------------\n\nconst HEADER_PREFIX_RE = /^(?<key>[A-Za-z_][A-Za-z0-9_.]*)\\[(?<count>\\d+)\\]/;\n\nfunction parseHeaderLine(line: string): { count: number; cols: string } {\n const prefix = HEADER_PREFIX_RE.exec(line);\n if (prefix == null) {\n throw new RangeError(`TOON payload missing tabular header; got: ${JSON.stringify(line)}`);\n }\n const declared = Number(prefix.groups?.count ?? \"\");\n let i = prefix[0].length;\n const n = line.length;\n // Handle the header-only empty form: `rows[0]:` (no `{cols}` region).\n if (i < n && line[i] === \":\" && declared === 0) {\n const rest = line.slice(i + 1).trim();\n if (rest === \"\") return { count: 0, cols: \"\" };\n throw new RangeError(`TOON header has trailing junk: ${JSON.stringify(line)}`);\n }\n if (i >= n || line[i] !== \"{\") {\n throw new RangeError(`TOON header missing column region: ${JSON.stringify(line)}`);\n }\n i++; // consume `{`\n // Walk to matching `}` honoring quoted strings.\n while (i < n) {\n const ch = line[i];\n if (ch === '\"') {\n let j = i + 1;\n while (j < n) {\n if (line[j] === \"\\\\\" && j + 1 < n) {\n j += 2;\n continue;\n }\n if (line[j] === '\"') break;\n j++;\n }\n if (j >= n) {\n throw new RangeError(\n `TOON header has unterminated quoted column name: ${JSON.stringify(line)}`,\n );\n }\n i = j + 1;\n continue;\n }\n if (ch === \"}\") break;\n i++;\n }\n if (i >= n || line[i] !== \"}\") {\n throw new RangeError(`TOON header missing closing brace: ${JSON.stringify(line)}`);\n }\n const cols = line.slice(prefix[0].length + 1, i);\n const rest = line.slice(i + 1).trim();\n if (rest !== \":\") {\n throw new RangeError(`TOON header missing colon terminator: ${JSON.stringify(line)}`);\n }\n return { count: declared, cols };\n}\n\nfunction splitCsvRow(line: string): string[] {\n const tokens: string[] = [];\n const n = line.length;\n if (n === 0) return tokens;\n let i = 0;\n while (true) {\n // Skip leading whitespace (defensive — encoder never emits padding).\n while (i < n && line[i] === \" \") i++;\n if (i >= n) {\n tokens.push(\"\");\n break;\n }\n if (line[i] === '\"') {\n let j = i + 1;\n while (j < n) {\n if (line[j] === \"\\\\\" && j + 1 < n) {\n j += 2;\n continue;\n }\n if (line[j] === '\"') break;\n j++;\n }\n tokens.push(line.slice(i, j + 1));\n i = j + 1;\n if (i >= n) break;\n if (line[i] === \",\") {\n i++;\n continue;\n }\n while (i < n && line[i] !== \",\") i++;\n if (i >= n) break;\n i++;\n } else {\n let j = i;\n while (j < n && line[j] !== \",\") j++;\n tokens.push(line.slice(i, j));\n i = j;\n if (i >= n) break;\n i++; // consume comma\n }\n }\n return tokens;\n}\n\nfunction decodeQuoted(token: string): string {\n const inner = token.slice(1, -1);\n let out = \"\";\n let i = 0;\n while (i < inner.length) {\n const ch = inner[i];\n if (ch === \"\\\\\" && i + 1 < inner.length) {\n const nxt = inner.charAt(i + 1);\n if (nxt === \"\\\\\") out += \"\\\\\";\n else if (nxt === '\"') out += '\"';\n else if (nxt === \"n\") out += \"\\n\";\n else if (nxt === \"r\") out += \"\\r\";\n else if (nxt === \"t\") out += \"\\t\";\n else out += nxt;\n i += 2;\n continue;\n }\n out += ch;\n i++;\n }\n return out;\n}\n\nfunction unquoteIfQuoted(token: string): string {\n if (token.length >= 2 && token[0] === '\"' && token[token.length - 1] === '\"') {\n return decodeQuoted(token);\n }\n return token;\n}\n\nfunction decodeValue(token: string): unknown {\n if (token.length === 0) return null;\n if (token[0] === '\"' && token[token.length - 1] === '\"' && token.length >= 2) {\n return decodeQuoted(token);\n }\n if (token === \"null\") return null;\n if (token === \"true\") return true;\n if (token === \"false\") return false;\n // Numeric attempt.\n if (NUMERIC_LIKE_RE.test(token)) {\n if (!token.includes(\".\") && !/[eE]/.test(token)) {\n const n = Number.parseInt(token, 10);\n if (!Number.isNaN(n)) return n;\n }\n const f = Number.parseFloat(token);\n if (!Number.isNaN(f)) return f;\n }\n // Bare unquoted string — TOON allows it when no quote-triggers fire.\n return token;\n}\n\n/**\n * Parse a TOON v3.0 tabular block back into rows + columns.\n *\n * Accepts ONLY the tabular shape that `toonDumps` produces; nested objects\n * / expanded lists are out of scope for the formats module.\n */\nexport function toonLoads(data: string): {\n rows: Array<Record<string, unknown>>;\n columns: string[];\n} {\n const lines = data.split(/\\r?\\n/);\n let idx = 0;\n while (idx < lines.length && lines[idx]?.trim() === \"\") idx++;\n if (idx >= lines.length) throw new RangeError(\"empty TOON payload\");\n\n const { count: declared, cols: colsRegion } = parseHeaderLine(lines[idx] ?? \"\");\n const columns = colsRegion === \"\" ? [] : splitCsvRow(colsRegion).map((t) => unquoteIfQuoted(t));\n\n const rawRows: unknown[][] = [];\n for (const raw of lines.slice(idx + 1)) {\n const line = raw.replace(/\\s+$/u, \"\");\n if (line.trim() === \"\") continue;\n const stripped = line.replace(/^ +/u, \"\");\n const tokens = splitCsvRow(stripped);\n if (columns.length > 0 && tokens.length !== columns.length) {\n throw new RangeError(\n `TOON row column count mismatch: expected ${columns.length}, got ${tokens.length}: ${JSON.stringify(stripped)}`,\n );\n }\n rawRows.push(tokens.map((t) => decodeValue(t)));\n }\n if (declared !== rawRows.length) {\n throw new RangeError(`TOON declared row count ${declared} != actual ${rawRows.length}`);\n }\n const rows = rawRows.map((row) => {\n const r: Record<string, unknown> = {};\n for (let i = 0; i < columns.length; i++) {\n r[columns[i] as string] = row[i];\n }\n return r;\n });\n return { rows, columns };\n}\n","// TS-W6 Wave 3 — DataSnapshot + buildSnapshot.\n//\n// Frozen, JSON-safe immutable record of a research() result with provenance.\n// `toDict()` returns the JSON-safe payload (cycles + non-finite numbers\n// handled by `toJsonSafe`); `toToon()` serializes as a TOON tabular block\n// via the existing `toonDumps` encoder.\n\nimport { toJsonSafe } from \"../exceptions/index.js\";\nimport { toonDumps } from \"../formats/toon.js\";\n\nimport type { DataVersion } from \"./data-version.js\";\n\n/** Frozen snapshot wrapper around row data + provenance. */\nexport interface DataSnapshot {\n /** ISO 8601 UTC instant when the snapshot was built (always ends with Z). */\n readonly knowledgeTime: string;\n /** Schema id the rows conform to. */\n readonly schemaId: string;\n /** Source identifier (e.g. `iem.archive`, `awc.live`). Snapshot-scoped. */\n readonly source: string;\n /** Row payload — opaque to this layer. Frozen. */\n readonly rows: ReadonlyArray<Readonly<Record<string, unknown>>>;\n /** Optional reproducibility token. */\n readonly dataVersion: DataVersion | null;\n /** Optional arbitrary metadata. JSON-safe-coerced on `toDict`. */\n readonly metadata: Readonly<Record<string, unknown>>;\n /** JSON-safe dict form. */\n toDict(): Record<string, unknown>;\n /** TOON-v3 tabular form (rows only — provenance lives in the dict form). */\n toToon(): string;\n}\n\nexport interface BuildSnapshotOptions {\n schemaId: string;\n source: string;\n rows: ReadonlyArray<Record<string, unknown>>;\n knowledgeTime?: Date | string;\n dataVersion?: DataVersion | null;\n metadata?: Record<string, unknown>;\n}\n\nfunction normalizeKnowledgeTime(value: Date | string | undefined): string {\n if (value === undefined) {\n return new Date().toISOString();\n }\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new RangeError(\"buildSnapshot: knowledgeTime is an invalid Date\");\n }\n return value.toISOString();\n }\n // String: parse + re-emit so the result is always canonical ISO with Z.\n const ms = Date.parse(value);\n if (Number.isNaN(ms)) {\n throw new RangeError(\n `buildSnapshot: knowledgeTime ${JSON.stringify(value)} is not a parseable ISO 8601 timestamp`,\n );\n }\n return new Date(ms).toISOString();\n}\n\nfunction freezeRows(\n rows: ReadonlyArray<Record<string, unknown>>,\n): ReadonlyArray<Readonly<Record<string, unknown>>> {\n const out: Readonly<Record<string, unknown>>[] = [];\n for (const r of rows) {\n out.push(Object.freeze({ ...r }));\n }\n return Object.freeze(out);\n}\n\n/**\n * Build a frozen DataSnapshot.\n *\n * Throws RangeError on invalid `knowledgeTime`. Row payloads are shallow-\n * cloned and frozen so callers can't mutate snapshot state post-build.\n */\nexport function buildSnapshot(opts: BuildSnapshotOptions): DataSnapshot {\n if (typeof opts.schemaId !== \"string\" || opts.schemaId.length === 0) {\n throw new RangeError(\"buildSnapshot: schemaId must be a non-empty string\");\n }\n if (typeof opts.source !== \"string\" || opts.source.length === 0) {\n throw new RangeError(\"buildSnapshot: source must be a non-empty string\");\n }\n if (!Array.isArray(opts.rows)) {\n throw new RangeError(\"buildSnapshot: rows must be an array\");\n }\n\n // Capture every component into local consts so the returned snapshot's\n // closures never read from the mutable `opts` argument. Codex iter-1\n // P2: a caller that did `const s = buildSnapshot({...}); opts.schemaId = \"x\"`\n // would have seen `s.toDict()` emit the mutated schemaId while\n // `s.schemaId` still showed the original — provenance drift on an API\n // that promises immutability.\n const knowledgeTime = normalizeKnowledgeTime(opts.knowledgeTime);\n const schemaId = opts.schemaId;\n const source = opts.source;\n const rows = freezeRows(opts.rows);\n const dataVersion = opts.dataVersion ?? null;\n const metadata = Object.freeze({ ...(opts.metadata ?? {}) });\n\n const snapshot: DataSnapshot = {\n knowledgeTime,\n schemaId,\n source,\n rows,\n dataVersion,\n metadata,\n toDict(): Record<string, unknown> {\n // `toJsonSafe` handles cycles + non-finite numbers + Dates. DataVersion\n // is a plain dict-shaped object already, so it round-trips cleanly.\n return toJsonSafe({\n knowledge_time: knowledgeTime,\n schema_id: schemaId,\n source,\n rows,\n data_version: dataVersion,\n metadata,\n }) as Record<string, unknown>;\n },\n toToon(): string {\n // TOON encodes a tabular block (rows-only). The header (schema id,\n // source, knowledge time, data version) lives in `toDict()`. Callers\n // that want the full snapshot in TOON wrap toDict output themselves.\n return toonDumps(rows as unknown as Array<Record<string, unknown>>);\n },\n };\n\n return Object.freeze(snapshot);\n}\n","// TS-W6 Wave 4 — DataVersion reproducibility token via Web Crypto SHA-256.\n//\n// Ports Python `mostlyright.discovery.DataVersion.from_components` byte-for-byte:\n// the canonical concatenation is `sdkVersion|sortedSchemaIds|sortedSources|codeSha|dataSha`,\n// SHA-256 hex of the UTF-8 encoded string is the token.\n//\n// Web Crypto API is universal in modern runtimes (browser/Node 16+/Workers/Deno/Bun),\n// so we use `crypto.subtle.digest` without runtime detection.\n\n/** Immutable reproducibility token stamping a single research() call. */\nexport interface DataVersion {\n readonly sdkVersion: string;\n readonly schemaIds: ReadonlyArray<string>;\n readonly sources: ReadonlyArray<string>;\n readonly codeSha: string;\n readonly dataSha: string;\n readonly token: string;\n}\n\nexport interface DataVersionComponents {\n sdkVersion: string;\n schemaIds: ReadonlyArray<string>;\n sources: ReadonlyArray<string>;\n codeSha: string;\n dataSha: string;\n}\n\nconst HEX = \"0123456789abcdef\";\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let out = \"\";\n for (let i = 0; i < bytes.length; i += 1) {\n const b = bytes[i] as number;\n out += HEX[(b >> 4) & 0xf];\n out += HEX[b & 0xf];\n }\n return out;\n}\n\n/**\n * Build a frozen DataVersion from explicit components.\n *\n * Mirrors Python `DataVersion.from_components`: sorts schemaIds + sources\n * INTERNALLY before the canonical hash so input order does not affect the\n * token, but preserves the caller's order on the returned object's\n * `schemaIds` and `sources` arrays. Codex iter-5 P2: prior version sorted\n * the stored arrays alphabetically too, which masked source-priority order\n * (e.g. `awc.live, ghcnh, iem.archive, ...` instead of the canonical\n * `iem.archive, iem.live, awc.live, ghcnh, nws.cli` precedence Python\n * preserves on the tuple).\n */\nexport async function dataVersionFromComponents(\n components: DataVersionComponents,\n): Promise<DataVersion> {\n const sortedSchemaIds = [...components.schemaIds].sort();\n const sortedSources = [...components.sources].sort();\n const canonical = [\n components.sdkVersion,\n sortedSchemaIds.join(\",\"),\n sortedSources.join(\",\"),\n components.codeSha,\n components.dataSha,\n ].join(\"|\");\n\n const encoded = new TextEncoder().encode(canonical);\n const digest = await crypto.subtle.digest(\"SHA-256\", encoded);\n const token = bytesToHex(new Uint8Array(digest));\n\n return Object.freeze({\n sdkVersion: components.sdkVersion,\n schemaIds: Object.freeze([...components.schemaIds]),\n sources: Object.freeze([...components.sources]),\n codeSha: components.codeSha,\n dataSha: components.dataSha,\n token,\n });\n}\n\n/**\n * Build a DataVersion for a research() call. Mirrors Python `DataVersion.for_research`:\n * the codeSha encodes the call signature (`research:STATION:FROM:TO`) and dataSha\n * is supplied by the caller (typically a cache fingerprint).\n *\n * The schema ids + source contract match the v0.1.0 Python SDK exactly so tokens\n * computed in TS match tokens computed in Python for the same inputs.\n */\nexport async function dataVersionForResearch(args: {\n sdkVersion: string;\n station: string;\n fromDate: string;\n toDate: string;\n dataSha: string;\n}): Promise<DataVersion> {\n return dataVersionFromComponents({\n sdkVersion: args.sdkVersion,\n schemaIds: [\"schema.observation.v1\", \"schema.forecast.iem_mos.v1\", \"schema.settlement.cli.v1\"],\n sources: [\"iem.archive\", \"iem.live\", \"awc.live\", \"ghcnh\", \"nws.cli\"],\n codeSha: `research:${args.station}:${args.fromDate}:${args.toDate}`,\n dataSha: args.dataSha,\n });\n}\n","// TS-W6 Wave 5 — describe(schemaId) + featureCatalog() + climateGaps stub.\n//\n// `describe(schemaId)` returns a multi-line human-readable string mined from\n// the embedded JSON-Schema metadata. We bundle schema descriptions as a small\n// JSON map keyed by `$id` (the canonical schema id) — produced by the codegen\n// pipeline if/when it lands; for v0.1.0 we ship a hand-maintained list that\n// matches what the codegen emits.\n//\n// `featureCatalog()` returns the transforms surface as a stable-sorted list\n// of names — matches Python `feature_catalog()` exactly.\n//\n// `climateGaps(station, fromDate, toDate)` throws — TS has no climate cache\n// in v0.1.0 (climate-year parquets are Python-only); the stub matches the\n// Python signature so callers can `try/catch` the platform difference.\n\nimport { MostlyRightError } from \"../exceptions/index.js\";\n\n// ---------------------------------------------------------------------------\n// Schema registry — keep in sync with `packages-ts/core/src/schemas/generated`.\n// ---------------------------------------------------------------------------\n\ninterface SchemaInfo {\n readonly id: string;\n readonly title: string;\n readonly columnCount: number;\n readonly columns: ReadonlyArray<{\n readonly name: string;\n readonly description: string;\n readonly nullable: boolean;\n }>;\n}\n\n// Static manifest of the v0.1.0 schemas. Derived from `schemas/json/*.json`\n// at repo root; baked in here so `@mostlyrightmd/core/discovery` works in\n// browsers (no `node:fs`) without a runtime fetch. The codegen pipeline\n// would replace this hand-maintained list once it covers schema docs.\n//\n// Codex iter-1 P2: prior version left REGISTRY empty until callers ran\n// `registerSchema()`, so `describe('schema.observation.v1')` always threw\n// UNKNOWN_SCHEMA on a fresh import.\nconst BUILT_IN_SCHEMAS: ReadonlyArray<SchemaInfo> = Object.freeze([\n {\n id: \"schema.observation.v1\",\n title: \"schema.observation.v1\",\n columnCount: 20,\n columns: [\n { name: \"dew_point_c\", description: \"units: celsius — bounded\", nullable: true },\n { name: \"event_time\", description: \"observation valid time\", nullable: false },\n {\n name: \"metar_raw\",\n description: \"raw METAR text if source has it; null for AWC JSON (structured-only)\",\n nullable: true,\n },\n {\n name: \"observation_type\",\n description: \"METAR | SPECI; defaults METAR when source can't distinguish (e.g. AWC JSON)\",\n nullable: false,\n },\n {\n name: \"precip_mm_1h\",\n description: \"units: mm — hourly precip (METAR p01i, converted from inches)\",\n nullable: true,\n },\n {\n name: \"sky_base_1_m\",\n description: \"units: meters — first cloud layer base height (converted from feet)\",\n nullable: true,\n },\n { name: \"sky_base_2_m\", description: \"units: meters\", nullable: true },\n { name: \"sky_base_3_m\", description: \"units: meters\", nullable: true },\n { name: \"sky_base_4_m\", description: \"units: meters\", nullable: true },\n { name: \"sky_cover_1\", description: \"first cloud layer cover code\", nullable: true },\n { name: \"sky_cover_2\", description: \"second layer; null if not present\", nullable: true },\n { name: \"sky_cover_3\", description: \"third layer; null if not present\", nullable: true },\n { name: \"sky_cover_4\", description: \"fourth layer; null if not present\", nullable: true },\n {\n name: \"slp_hpa\",\n description:\n \"units: hPa — sea-level pressure (canonical aviation unit, not converted across modes)\",\n nullable: true,\n },\n { name: \"station\", description: \"ICAO/ASOS station ID (e.g. KORD)\", nullable: false },\n {\n name: \"temp_c\",\n description: \"units: celsius — bounded TEMP_MIN_C..TEMP_MAX_C\",\n nullable: true,\n },\n {\n name: \"visibility_m\",\n description: \"units: meters — converted from statute miles\",\n nullable: true,\n },\n {\n name: \"wind_dir_deg\",\n description: \"units: degrees — 0-360, bounded\",\n nullable: true,\n },\n { name: \"wind_gust_ms\", description: \"units: m/s — converted from kt\", nullable: true },\n { name: \"wind_speed_ms\", description: \"units: m/s — converted from kt\", nullable: true },\n ],\n },\n {\n id: \"schema.forecast.iem_mos.v1\",\n title: \"schema.forecast.iem_mos.v1\",\n columnCount: 11,\n columns: [\n { name: \"dew_point_c\", description: \"units: celsius\", nullable: true },\n {\n name: \"forecast_hour\",\n description: \"units: hours — (valid_at - issued_at).total_seconds() / 3600\",\n nullable: false,\n },\n {\n name: \"issued_at\",\n description: \"model run time (from source `runtime` field)\",\n nullable: false,\n },\n { name: \"model\", description: \"e.g. NBE, GFS, LAV, MET\", nullable: false },\n {\n name: \"precip_probability\",\n description: \"units: probability — bounded [0, 1]\",\n nullable: true,\n },\n {\n name: \"sky_cover_pct\",\n description: \"units: percent — bounded [0, 100]\",\n nullable: true,\n },\n { name: \"station\", description: \"\", nullable: false },\n { name: \"temp_c\", description: \"units: celsius\", nullable: true },\n {\n name: \"valid_at\",\n description: \"forecast target time (from source `ftime`)\",\n nullable: false,\n },\n { name: \"wind_dir_deg\", description: \"units: degrees\", nullable: true },\n { name: \"wind_speed_ms\", description: \"units: m/s\", nullable: true },\n ],\n },\n {\n id: \"schema.settlement.cli.v1\",\n title: \"schema.settlement.cli.v1\",\n columnCount: 12,\n columns: [\n {\n name: \"cli_data_quality\",\n description:\n \"NWS CLI data-quality marker (Pitfall 6/16). Allows downstream code to filter or weight settlement rows by issuer quality without re-parsing the product header.\",\n nullable: false,\n },\n {\n name: \"event_time\",\n description: \"00:00 local time on observation_date converted to UTC; for sort/join only\",\n nullable: false,\n },\n {\n name: \"observation_date\",\n description:\n \"local climate day per NWS convention (no timezone applied to the date itself)\",\n nullable: false,\n },\n { name: \"precipitation_in\", description: \"units: inches\", nullable: true },\n {\n name: \"product_release_time\",\n description: \"parsed from CLI product header (_climate.py::_parse_product_timestamp)\",\n nullable: false,\n },\n {\n name: \"report_type\",\n description:\n \"preliminary | final | correction; dedup priority preliminary < final < correction\",\n nullable: false,\n },\n {\n name: \"settlement_finality\",\n description:\n \"provisional | final | superseded. Kalshi NHIGH/NLOW settlement contractually requires 'final'; 'provisional' values are kept for early-look research only.\",\n nullable: false,\n },\n { name: \"snowfall_in\", description: \"units: inches\", nullable: true },\n { name: \"station\", description: \"ICAO/ASOS station ID\", nullable: false },\n {\n name: \"station_tz\",\n description:\n \"IANA timezone for the station (e.g. America/Chicago for KORD). Required for local-climate-day semantics; see §U.\",\n nullable: false,\n },\n {\n name: \"temp_max_F\",\n description:\n \"units: fahrenheit — daily high (uppercase F for consistency with obs imperial mode)\",\n nullable: true,\n },\n { name: \"temp_min_F\", description: \"units: fahrenheit — daily low\", nullable: true },\n ],\n },\n {\n id: \"schema.observation_ledger.v1\",\n title: \"schema.observation_ledger.v1\",\n columnCount: 15,\n columns: [\n { name: \"as_of_time\", description: \"\", nullable: true },\n { name: \"dewpoint_c\", description: \"units: celsius\", nullable: true },\n { name: \"ingestion_id\", description: \"\", nullable: true },\n { name: \"observation_kind\", description: \"\", nullable: true },\n {\n name: \"observation_quality\",\n description:\n \"Lineage row-quality flag per LINEAGE-01; distinct from qc_status enum slot AND distinct from the obs_qc_status bitmask column per QC-05.\",\n nullable: true,\n },\n { name: \"observation_type\", description: \"\", nullable: false },\n { name: \"observed_at\", description: \"\", nullable: false },\n { name: \"parser_name\", description: \"\", nullable: true },\n { name: \"parser_version\", description: \"\", nullable: true },\n { name: \"provenance\", description: \"\", nullable: true },\n { name: \"qc_status\", description: \"\", nullable: true },\n {\n name: \"source\",\n description: \"ncei reserved per D-2.1-09; never written in v0.1.0.\",\n nullable: false,\n },\n { name: \"source_received_at\", description: \"\", nullable: true },\n { name: \"station_code\", description: \"\", nullable: false },\n { name: \"temp_c\", description: \"units: celsius\", nullable: true },\n ],\n },\n {\n id: \"schema.observation_qc.v1\",\n title: \"schema.observation_qc.v1\",\n columnCount: 13,\n columns: [\n { name: \"as_of_time\", description: \"\", nullable: true },\n {\n name: \"detector_metadata\",\n description: \"JSON-serialized detector payload; shape per qc_system.\",\n nullable: true,\n },\n {\n name: \"field\",\n description: \"Observation column the rule evaluated (e.g. temp_c).\",\n nullable: false,\n },\n { name: \"flag\", description: \"\", nullable: false },\n { name: \"ingestion_id\", description: \"\", nullable: true },\n { name: \"observation_kind\", description: \"\", nullable: true },\n { name: \"observed_at\", description: \"\", nullable: false },\n { name: \"parser_name\", description: \"\", nullable: true },\n { name: \"qc_system\", description: \"\", nullable: false },\n { name: \"qc_version\", description: \"\", nullable: false },\n { name: \"rule_id\", description: \"\", nullable: false },\n { name: \"source\", description: \"\", nullable: false },\n { name: \"station_code\", description: \"\", nullable: false },\n ],\n },\n]);\n\nfunction deepFreezeSchema(info: SchemaInfo): SchemaInfo {\n const frozenCols = Object.freeze(info.columns.map((c) => Object.freeze({ ...c })));\n return Object.freeze({ ...info, columns: frozenCols });\n}\n\nconst REGISTRY = new Map<string, SchemaInfo>(\n BUILT_IN_SCHEMAS.map((info) => [info.id, deepFreezeSchema(info)] as const),\n);\n\n/**\n * Register or override a schema for `describe()`. Built-in v0.1.0 schemas\n * are registered at module load (BUILT_IN_SCHEMAS); callers may add custom\n * schemas or override built-ins (e.g. with richer descriptions).\n */\nexport function registerSchema(info: SchemaInfo): void {\n REGISTRY.set(info.id, deepFreezeSchema(info));\n}\n\n/**\n * Return a multi-line description of a registered schema.\n *\n * @throws MostlyRightError if `schemaId` is not registered. The error code is\n * `UNKNOWN_SCHEMA` so callers can distinguish from validation/IO errors.\n */\nexport function describe(schemaId: string): string {\n const info = REGISTRY.get(schemaId);\n if (info === undefined) {\n const known = [...REGISTRY.keys()].sort();\n throw new UnknownSchemaError(\n `Unknown schemaId ${JSON.stringify(schemaId)}; registered: ${\n known.length === 0 ? \"<none>\" : known.join(\", \")\n }`,\n );\n }\n const lines = [`Schema: ${info.id}`, ` Title: ${info.title}`, ` Columns: ${info.columnCount}`];\n for (const col of info.columns) {\n const nullable = col.nullable ? \"?\" : \"\";\n const desc = col.description.length === 0 ? \"\" : ` — ${col.description}`;\n lines.push(` - ${col.name}${nullable}${desc}`);\n }\n return lines.join(\"\\n\");\n}\n\n/** Thrown by `describe` when `schemaId` is not registered. */\nexport class UnknownSchemaError extends MostlyRightError {\n constructor(message: string) {\n super(message);\n this.name = \"UnknownSchemaError\";\n }\n static readonly defaultErrorCode = \"UNKNOWN_SCHEMA\";\n}\n\n// ---------------------------------------------------------------------------\n// featureCatalog — list of transforms surface names.\n// ---------------------------------------------------------------------------\n\nconst FEATURE_NAMES: ReadonlyArray<string> = Object.freeze([\n \"calendarFeatures\",\n \"clipOutliers\",\n \"diff\",\n \"diff2\",\n \"heatIndex\",\n \"lag\",\n \"rolling\",\n \"spread\",\n \"windChill\",\n]);\n\n/**\n * Return the transforms surface as a stable-sorted list.\n *\n * Matches Python `feature_catalog()`'s ordering — alphabetical on the\n * snake_case names, here the camelCase TS names sorted identically.\n */\nexport function featureCatalog(): ReadonlyArray<string> {\n return FEATURE_NAMES;\n}\n\n// ---------------------------------------------------------------------------\n// climateGaps — stub that mirrors Python's signature; v1.x deferral.\n// ---------------------------------------------------------------------------\n//\n// Phase 21 21-11 upgrade: raises DataAvailabilityError (typed exception\n// from Phase 21 21-09) with an architectural-reason hint. The legacy\n// ClimateGapsNotImplementedError class is kept as a back-compat alias\n// (subclass of DataAvailabilityError) so existing `catch` sites keep\n// working. New code should catch DataAvailabilityError and dispatch on\n// the `reason` field instead.\n\nimport { DataAvailabilityError } from \"../exceptions/index.js\";\n\nconst CLIMATE_GAPS_HINT =\n \"climateGaps() is server-only in v1.x. Architectural reason: \" +\n \"GHCNh CSVs are 10+ MB per station-year; browser fetch doesn't \" +\n \"slice (no HTTP Range) and IndexedDB quotas don't fit the working \" +\n \"set. Use the Python SDK (mostlyright.discover.climate_gaps) for \" +\n \"v1.x, or wait for the hosted climate-gap-precompute API \" +\n \"(target: post-v1.x). See https://mostlyright.md/docs/sdk/typescript/climate-gaps \" +\n \"for details.\";\n\n/**\n * Climate-gap scanning — TS v1.x deferral.\n *\n * Phase 21 21-11 messaging upgrade: raises DataAvailabilityError with a\n * multi-paragraph hint explaining the architectural constraint (GHCNh\n * CSVs are 10+ MB per station-year; browser cache layer doesn't scale).\n *\n * @throws DataAvailabilityError with reason=\"model_unavailable\" and a\n * hint pointing at the Python SDK as the supported v1.x workaround.\n */\nexport function climateGaps(_station: string, _fromDate: string, _toDate: string): never {\n throw new ClimateGapsNotImplementedError();\n}\n\n/**\n * Back-compat subclass of DataAvailabilityError raised by `climateGaps`\n * in v1.x. Existing `catch (e instanceof ClimateGapsNotImplementedError)`\n * sites keep working; new code should catch the parent class\n * `DataAvailabilityError` and dispatch on `reason === \"model_unavailable\"`.\n *\n * @deprecated Prefer catching `DataAvailabilityError` directly.\n */\nexport class ClimateGapsNotImplementedError extends DataAvailabilityError {\n static override readonly defaultErrorCode = \"DATA_AVAILABILITY\";\n\n constructor() {\n super({\n reason: \"model_unavailable\",\n source: \"climate-cache-browser\",\n hint: CLIMATE_GAPS_HINT,\n });\n this.name = \"ClimateGapsNotImplementedError\";\n }\n}\n","// Phase 21 21-05 — dailyExtremes(station, from, to, opts?) fetch+rollup wrapper.\n//\n// Matches Python `mostlyright.international.daily_extremes(station, from,\n// to, merge='live_v1')` signature so cross-language code stays symmetric.\n// Composes:\n// 1. STATIONS-registry lookup for the station's IANA timezone.\n// 2. Source fetch per merge mode (IEM ASOS for historical depth +\n// optional AWC for the live window).\n// 3. `internationalDailyExtremes(rows, {stationTz, precision})` rollup.\n// 4. Projection of the existing `DailyExtreme` (TS) shape into the\n// Python-mirroring `DailyExtremeRow` (date/tmin_f/tmax_f/.../n_obs).\n//\n// US ASOS stations get integer-°F precision (Phase 18 invariant); other\n// stations get 0.1-precision values. The integer-°F detection key is the\n// `country === \"US\"` field on the STATIONS registry.\n//\n// Note: GHCNh has CORS issues in browser per `.planning/research/TS-CORS-MATRIX.md`\n// — `merge=\"live_v1\"` in this v1 wrapper only composes IEM + AWC. Adding\n// GHCNh requires the Node-only path; documented for a follow-up.\n\nimport { STATIONS } from \"@mostlyrightmd/core\";\n\nimport {\n type DailyExtreme,\n type InternationalRow,\n internationalDailyExtremes,\n} from \"@mostlyrightmd/core/discovery\";\nimport { fetchAwcMetars } from \"./_fetchers/awc.js\";\nimport { downloadIemAsos } from \"./_fetchers/iem-asos.js\";\nimport { awcToObservation } from \"./_parsers/awc.js\";\nimport { parseIemCsv } from \"./_parsers/iem.js\";\nimport type {\n DailyExtremeRow,\n DailyExtremesMergeMode,\n DailyExtremesOptions,\n} from \"./dailyExtremes.types.js\";\n\nconst LOW_COVERAGE_THRESHOLD = 12;\n\n/**\n * Phase 21 21-05 fix-iter-2 (codex CRITICAL): `fromDate`/`toDate` are\n * station-LOCAL dates, but `row.observed_at` is UTC. Filtering UTC date\n * prefix against local date prefix drops tz-edge observations that fall\n * on adjacent UTC days but the local target day (e.g. for a UTC+9\n * station, `2025-01-06 23:30 local` is `2025-01-06 14:30Z` which has UTC\n * prefix `2025-01-06` — OK — but `2025-01-06 02:00 local` is\n * `2025-01-05 17:00Z` which has UTC prefix `2025-01-05` and would be\n * dropped pre-fix).\n *\n * Mitigation: widen the pre-rollup window by ±1 UTC day so every\n * station-local observation in [fromDate, toDate] is captured regardless\n * of tz offset. The `internationalDailyExtremes()` rollup downstream\n * re-projects observations into station-local days; we then clip the\n * resulting rows back to the caller's [fromDate, toDate] using the\n * station-local `localDate` field on each rollup row.\n */\nfunction addUtcDays(iso: string, days: number): string {\n const [yStr, mStr, dStr] = iso.split(\"-\");\n const dt = new Date(Date.UTC(Number(yStr), Number(mStr) - 1, Number(dStr)));\n dt.setUTCDate(dt.getUTCDate() + days);\n const yyyy = dt.getUTCFullYear();\n const mm = String(dt.getUTCMonth() + 1).padStart(2, \"0\");\n const dd = String(dt.getUTCDate()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}`;\n}\n\ninterface StationLookup {\n tz: string;\n isUs: boolean;\n}\n\nfunction lookupStation(icao: string): StationLookup {\n const upper = icao.toUpperCase();\n for (const s of STATIONS) {\n if (s.icao === upper) {\n return { tz: s.tz, isUs: s.country === \"US\" };\n }\n }\n throw new Error(`dailyExtremes: station \"${icao}\" not in registry — check STATIONS catalog`);\n}\n\nfunction cToF(c: number | null): number | null {\n if (c === null) return null;\n return c * (9 / 5) + 32;\n}\n\nfunction roundHalfUp(value: number, decimals: number): number {\n const m = 10 ** decimals;\n return Math.round(value * m) / m;\n}\n\nasync function fetchIemAsosObservations(\n station: string,\n fromDate: string,\n toDate: string,\n): Promise<InternationalRow[]> {\n // Lazy minimal port of the research() yearly-chunking loop. For the\n // window-bounded wrapper we just span the [from, to] year range.\n const fromYear = Number.parseInt(fromDate.slice(0, 4), 10);\n const toYear = Number.parseInt(toDate.slice(0, 4), 10);\n const out: InternationalRow[] = [];\n for (let year = fromYear; year <= toYear; year++) {\n const chunks = await downloadIemAsos(station, `${year}-01-01`, `${year}-12-31`, {\n reportType: 3,\n politenessMs: 1000,\n });\n for (const chunk of chunks) {\n const parsed = parseIemCsv(chunk.csv, { observationTypeOverride: \"METAR\" });\n for (const row of parsed) {\n // Filter to the requested window. The canonical Observation field\n // is `precip_1hr_inches` (inches); InternationalRow expects mm —\n // convert at the boundary so the per-day precip rollup is in mm.\n // Phase 21 21-05 fix-iter-2 (codex CRITICAL): pre-rollup filter uses\n // the UTC date prefix against the widened-by-±1-day UTC window.\n // The caller's [fromDate, toDate] is station-LOCAL; observations\n // that fall on the adjacent UTC day but the local target day must\n // be kept so internationalDailyExtremes() can bin them into the\n // correct station-local bucket. Output is clipped back to the\n // caller's [fromDate, toDate] post-rollup using `localDate`.\n const obsDate = row.observed_at.slice(0, 10);\n if (obsDate >= fromDate && obsDate <= toDate) {\n const precipInches = row.precip_1hr_inches ?? null;\n out.push({\n observed_at: row.observed_at,\n temp_c: row.temp_c ?? null,\n precip_mm_1h: precipInches !== null ? precipInches * 25.4 : null,\n source: row.source,\n });\n }\n }\n }\n }\n return out;\n}\n\nasync function fetchAwcObservations(\n station: string,\n fromDate: string,\n toDate: string,\n): Promise<InternationalRow[]> {\n // AWC serves the last 168h; the wrapper still queries even if the\n // requested window straddles that horizon — sparse responses are\n // surfaced via the low_coverage gate.\n const raw = await fetchAwcMetars([station]);\n const out: InternationalRow[] = [];\n for (const r of raw) {\n const obs = awcToObservation(r);\n if (obs === null) continue;\n // Phase 21 21-09 fix-iter-1 (ts-architect HIGH): symmetric date-prefix\n // comparison on both bounds (see fetchIemObservations comment above).\n const obsDate = obs.observed_at.slice(0, 10);\n if (obsDate >= fromDate && obsDate <= toDate) {\n const precipInches = obs.precip_1hr_inches ?? null;\n out.push({\n observed_at: obs.observed_at,\n temp_c: obs.temp_c ?? null,\n precip_mm_1h: precipInches !== null ? precipInches * 25.4 : null,\n source: obs.source,\n });\n }\n }\n return out;\n}\n\nasync function fetchForMode(\n station: string,\n fromDate: string,\n toDate: string,\n mode: DailyExtremesMergeMode,\n): Promise<InternationalRow[]> {\n switch (mode) {\n case \"iem_only\":\n return fetchIemAsosObservations(station, fromDate, toDate);\n case \"awc_only\":\n return fetchAwcObservations(station, fromDate, toDate);\n case \"live_v1\": {\n const [iem, awc] = await Promise.all([\n fetchIemAsosObservations(station, fromDate, toDate),\n fetchAwcObservations(station, fromDate, toDate).catch(() => []),\n ]);\n return [...iem, ...awc];\n }\n default: {\n const _exhaustive: never = mode;\n throw new TypeError(`dailyExtremes: unknown merge mode \"${String(_exhaustive)}\"`);\n }\n }\n}\n\nfunction projectRow(station: string, d: DailyExtreme, isUs: boolean): DailyExtremeRow {\n const lowCoverage = d.nObs < LOW_COVERAGE_THRESHOLD;\n const decimals = isUs ? 0 : 1;\n const precipIn =\n d.precipMm !== null && d.precipMm !== undefined ? roundHalfUp(d.precipMm / 25.4, 2) : null;\n if (lowCoverage) {\n return {\n date: d.localDate,\n station,\n tmin_f: null,\n tmax_f: null,\n tmean_f: null,\n precip_in: precipIn,\n low_coverage: true,\n n_obs: d.nObs,\n };\n }\n return {\n date: d.localDate,\n station,\n tmin_f: d.tempMinF !== null ? roundHalfUp(d.tempMinF, decimals) : null,\n tmax_f: d.tempMaxF !== null ? roundHalfUp(d.tempMaxF, decimals) : null,\n tmean_f: d.tempMeanC !== null ? roundHalfUp(cToF(d.tempMeanC) as number, decimals) : null,\n precip_in: precipIn,\n low_coverage: false,\n n_obs: d.nObs,\n };\n}\n\n/**\n * Compute per-day tmin/tmax/tmean/precip for a station's window.\n *\n * Matches Python `mostlyright.international.daily_extremes` signature.\n * Day-bucketing uses the station's IANA local tz from the STATIONS\n * registry; US ASOS stations get integer-°F precision (Phase 18\n * invariant); other stations get 0.1-precision values.\n *\n * @param station ICAO code (e.g. \"KNYC\")\n * @param fromDate ISO date `YYYY-MM-DD` (inclusive, station-local)\n * @param toDate ISO date `YYYY-MM-DD` (inclusive, station-local)\n * @param opts optional merge mode (default `\"live_v1\"`)\n * @returns array of DailyExtremeRow, one per station-local day\n * @throws Error if station is not in the STATIONS registry\n */\nexport async function dailyExtremes(\n station: string,\n fromDate: string,\n toDate: string,\n opts: DailyExtremesOptions = {},\n): Promise<ReadonlyArray<DailyExtremeRow>> {\n const { tz, isUs } = lookupStation(station);\n const merge = opts.merge ?? \"live_v1\";\n\n // Phase 21 21-05 fix-iter-2 (codex CRITICAL): widen the pre-rollup UTC\n // window by ±1 day so station-local-day observations at tz edges are\n // not silently dropped (e.g. UTC+14 stations have local 23:00 falling\n // on the previous UTC day). The rollup re-projects into the\n // station-local IANA day; we then clip the rollup output back to the\n // caller's [fromDate, toDate] via the station-local `localDate` field\n // so the public surface still returns exactly the requested days.\n const fetchFrom = addUtcDays(fromDate, -1);\n const fetchTo = addUtcDays(toDate, 1);\n const rows = await fetchForMode(station, fetchFrom, fetchTo, merge);\n\n const extremes = internationalDailyExtremes(rows, {\n stationTz: tz,\n precision: isUs ? 1 : 0,\n });\n\n return extremes\n .filter((d) => d.localDate >= fromDate && d.localDate <= toDate)\n .map((d) => projectRow(station.toUpperCase(), d, isUs));\n}\n\nexport type {\n DailyExtremeRow,\n DailyExtremesMergeMode,\n DailyExtremesOptions,\n} from \"./dailyExtremes.types.js\";\n","// Phase 21 21-04 — obs(station, from, to, opts?) public function.\n//\n// Port of Python `tw.weather.obs(station, start, end, source=None,\n// strategy='auto')` — the Phase 7 ingest-planner smart-router. Picks\n// between three fetch strategies based on window size + caller intent:\n//\n// - `exact_window` — one-off ≤7-day window; fetches the exact bytes\n// the caller asked for, no year-padding. Cheapest path.\n// - `warm_cache` — year-aligned cache layout; the same orchestration\n// research() uses. Best for callers that will hit overlapping\n// windows.\n// - `hosted` — precomputed-API seam; raises DataAvailabilityError\n// until v0.2.x ships the hosted ingest endpoint (D-06).\n// - `auto` (default) — routes to `exact_window` when (toDate-fromDate)\n// ≤ 7 days, else `warm_cache`. Threshold matches Python's heuristic.\n//\n// Per D-06, the TS adaptations differ from Python in storage (IndexedDB\n// browser / Node FS server instead of parquet) but match Python in\n// strategy semantics + raised exceptions.\n\nimport { DataAvailabilityError } from \"@mostlyrightmd/core\";\n\nimport { fetchAwcMetars } from \"./_fetchers/awc.js\";\nimport { downloadIemAsos } from \"./_fetchers/iem-asos.js\";\nimport { awcToObservation } from \"./_parsers/awc.js\";\nimport { parseIemCsv } from \"./_parsers/iem.js\";\nimport type { ObsOptions, ObsRow, ObsStrategy } from \"./obs.types.js\";\n\n/** Day count between two ISO YYYY-MM-DD strings (inclusive). */\nfunction daysBetween(fromDate: string, toDate: string): number {\n const from = Date.UTC(\n Number.parseInt(fromDate.slice(0, 4), 10),\n Number.parseInt(fromDate.slice(5, 7), 10) - 1,\n Number.parseInt(fromDate.slice(8, 10), 10),\n );\n const to = Date.UTC(\n Number.parseInt(toDate.slice(0, 4), 10),\n Number.parseInt(toDate.slice(5, 7), 10) - 1,\n Number.parseInt(toDate.slice(8, 10), 10),\n );\n return Math.round((to - from) / (24 * 60 * 60 * 1000)) + 1;\n}\n\n/** Resolve `auto` to the concrete strategy chosen by the smart-router. */\nexport function resolveAutoStrategy(fromDate: string, toDate: string): ObsStrategy {\n // Python heuristic: 7-day or smaller windows route to exact_window\n // (one-off cold fetch ≤ 2 MB estimated payload); larger windows route\n // to warm_cache (year-aligned cache layout, reusable across calls).\n return daysBetween(fromDate, toDate) <= 7 ? \"exact_window\" : \"warm_cache\";\n}\n\n// Field names mirror the canonical Observation interface from\n// `_parsers/awc.ts`. ObsRow uses the same snake_case shape so cross-\n// language code stays symmetric with Python. Precipitation is stored in\n// inches at the source (`precip_1hr_inches`); we surface it as\n// `precip_mm_1h` per the Python ObsRow contract, so the conversion\n// (inches × 25.4 = mm) happens at the projection boundary.\n\nfunction inchesToMm(inches: number | null | undefined): number | null {\n if (inches === null || inches === undefined) return null;\n return inches * 25.4;\n}\n\nfunction mbToInhg(mb: number | null | undefined): number | null {\n if (mb === null || mb === undefined) return null;\n return mb * 0.029529983071445;\n}\n\nfunction fromObservation(o: NonNullable<ReturnType<typeof awcToObservation>>): ObsRow {\n const tempC = o.temp_c ?? null;\n const dewC = o.dewpoint_c ?? null;\n return {\n station: o.station_code,\n observed_at: o.observed_at,\n source: o.source,\n temp_c: tempC,\n temp_f: tempC !== null ? tempC * (9 / 5) + 32 : null,\n dewpoint_c: dewC,\n dewpoint_f: dewC !== null ? dewC * (9 / 5) + 32 : null,\n wind_speed_kts: o.wind_speed_kt ?? null,\n wind_direction_deg: o.wind_dir_degrees ?? null,\n pressure_inhg: mbToInhg(o.sea_level_pressure_mb),\n precip_mm_1h: inchesToMm(o.precip_1hr_inches),\n raw_metar: o.raw_metar ?? null,\n };\n}\n\n/**\n * IEM fetch helper. Phase 21 21-04 fix-iter-3 (codex HIGH): the byte-range\n * URL the upstream IEM ASOS endpoint accepts is honored by `downloadIemAsos`\n * — passing date-bounded `start`/`end` produces a date-bounded payload.\n *\n * - `exact_window` strategy: pass the caller's [fromDate, toDate] verbatim.\n * A 1-day call pulls ~1 day of bytes, not a whole year. Matches Python\n * `obs(strategy=\"exact_window\")` semantics.\n * - `warm_cache` strategy: pad to year-boundaries to maximize cache reuse\n * for callers that hit overlapping windows. Matches Python\n * `obs(strategy=\"warm_cache\")` semantics.\n *\n * Post-fetch trim still runs in both cases — the upstream may include\n * boundary observations slightly outside [fromDate, toDate] depending on\n * METAR reporting cadence.\n */\nasync function fetchIemForWindow(\n station: string,\n fromDate: string,\n toDate: string,\n resolvedStrategy: Exclude<ObsStrategy, \"auto\" | \"hosted\">,\n): Promise<ObsRow[]> {\n const out: ObsRow[] = [];\n\n if (resolvedStrategy === \"exact_window\") {\n // GH #57: `exactStart: true` opts out of `downloadIemAsos`'s default\n // Jan-1 widening + yearly chunking — issues ONE byte-bounded request\n // for `[fromDate, toDate+1day exclusive]`. Without this flag a 1-day\n // call pulled ~734 KB (whole calendar year) instead of ~9.8 KB.\n const chunks = await downloadIemAsos(station, fromDate, toDate, {\n reportType: 3,\n politenessMs: 1000,\n exactStart: true,\n });\n for (const chunk of chunks) {\n const rows = parseIemCsv(chunk.csv, { observationTypeOverride: \"METAR\" });\n for (const r of rows) {\n const d = r.observed_at.slice(0, 10);\n if (d >= fromDate && d <= toDate) out.push(fromObservation(r));\n }\n }\n return out;\n }\n\n // warm_cache: span the [fromYear, toYear] calendar-year range so future\n // calls hitting overlapping windows can reuse cached year-chunks.\n const fromYear = Number.parseInt(fromDate.slice(0, 4), 10);\n const toYear = Number.parseInt(toDate.slice(0, 4), 10);\n for (let year = fromYear; year <= toYear; year++) {\n const chunks = await downloadIemAsos(station, `${year}-01-01`, `${year}-12-31`, {\n reportType: 3,\n politenessMs: 1000,\n });\n for (const chunk of chunks) {\n const rows = parseIemCsv(chunk.csv, { observationTypeOverride: \"METAR\" });\n for (const r of rows) {\n const d = r.observed_at.slice(0, 10);\n if (d >= fromDate && d <= toDate) out.push(fromObservation(r));\n }\n }\n }\n return out;\n}\n\nasync function fetchAwcForWindow(\n station: string,\n fromDate: string,\n toDate: string,\n): Promise<ObsRow[]> {\n const raw = await fetchAwcMetars([station]);\n const out: ObsRow[] = [];\n for (const r of raw) {\n const obs = awcToObservation(r);\n if (obs === null) continue;\n const d = obs.observed_at.slice(0, 10);\n if (d >= fromDate && d <= toDate) out.push(fromObservation(obs));\n }\n return out;\n}\n\nasync function fetchByStrategy(\n station: string,\n fromDate: string,\n toDate: string,\n resolvedStrategy: Exclude<ObsStrategy, \"auto\" | \"hosted\">,\n source: ObsOptions[\"source\"],\n): Promise<ObsRow[]> {\n // Phase 21 21-04 fix-iter-3 (codex HIGH): `resolvedStrategy` now drives\n // the IEM fetch shape. `exact_window` issues a date-bounded request so\n // small calls pull small payloads (matches Python's Phase 7 ingest-\n // planner semantics); `warm_cache` pads to year-boundaries so\n // overlapping callers reuse cached chunks. AWC is always the live\n // 168-hour endpoint regardless of strategy — the request shape doesn't\n // change with strategy in either Python or TS.\n const wantsIem = source === null || source === undefined || source === \"iem\";\n const wantsAwc = source === null || source === undefined || source === \"awc\";\n\n const tasks: Promise<ObsRow[]>[] = [];\n if (wantsIem) tasks.push(fetchIemForWindow(station, fromDate, toDate, resolvedStrategy));\n if (wantsAwc) tasks.push(fetchAwcForWindow(station, fromDate, toDate).catch(() => []));\n\n const results = await Promise.all(tasks);\n return results.flat();\n}\n\n/**\n * Fetch raw observations for a station's window.\n *\n * Matches Python `tw.weather.obs(station, start, end, source=None,\n * strategy='auto')` signature. The strategy enum selects between the\n * smart-router's three concrete fetch paths.\n *\n * @param station ICAO code (e.g. \"KNYC\")\n * @param fromDate ISO date `YYYY-MM-DD` (inclusive)\n * @param toDate ISO date `YYYY-MM-DD` (inclusive)\n * @param opts optional source filter + strategy mode\n * @returns flat array of ObsRow (each row is a single METAR observation)\n * @throws DataAvailabilityError when strategy='hosted' (v0.2.x deferral)\n * @throws TypeError when strategy is not in the accepted enum\n */\nexport async function obs(\n station: string,\n fromDate: string,\n toDate: string,\n opts: ObsOptions = {},\n): Promise<ReadonlyArray<ObsRow>> {\n const strategy = opts.strategy ?? \"auto\";\n const source = opts.source ?? null;\n\n // Phase 21 21-09 fix-iter-1: reject source values that have no TS fetcher\n // wiring loudly rather than silently returning [] (codex+ts-architect\n // CRITICAL). `ghcnh` is a documented Python filter but the GHCNh fetcher\n // is not wired through `fetchByStrategy` in TS yet — surface the gap as\n // DataAvailabilityError(reason=\"model_unavailable\") so consumers see the\n // missing wiring instead of empty rows.\n if (source === \"ghcnh\") {\n throw new DataAvailabilityError({\n reason: \"model_unavailable\",\n source: \"obs.ghcnh\",\n hint:\n \"source='ghcnh' is a valid Python `obs()` filter but the GHCNh \" +\n \"fetcher path is not yet wired in the TypeScript SDK. Use \" +\n \"source='iem' or source='awc' (or omit `source` for merged) until \" +\n \"the TS GHCNh fetcher ships.\",\n });\n }\n\n if (strategy === \"hosted\") {\n throw new DataAvailabilityError({\n reason: \"model_unavailable\",\n source: \"obs-hosted-stub\",\n hint:\n \"hosted ingest API ships in v0.2.x — use strategy='exact_window' \" +\n \"or 'warm_cache' for v1.x. See \" +\n \"https://mostlyright.md/docs/sdk/typescript/ingest-strategies\",\n });\n }\n\n let resolved: Exclude<ObsStrategy, \"auto\" | \"hosted\">;\n if (strategy === \"auto\") {\n resolved = resolveAutoStrategy(fromDate, toDate) as Exclude<ObsStrategy, \"auto\" | \"hosted\">;\n } else if (strategy === \"exact_window\" || strategy === \"warm_cache\") {\n resolved = strategy;\n } else {\n throw new TypeError(\n `obs: unknown strategy \"${String(strategy)}\" — expected one of: auto, exact_window, warm_cache, hosted`,\n );\n }\n\n return fetchByStrategy(station, fromDate, toDate, resolved, source);\n}\n\nexport type {\n ObsOptions,\n ObsRow,\n ObsSourceFilter,\n ObsStrategy,\n} from \"./obs.types.js\";\n","// @mostlyrightmd/markets — placeholder scaffold for TS-W0 Wave 1.\n// Real implementation (Kalshi NHIGH/NLOW resolvers, Polymarket discover/settle) lands in TS-W1+.\n\n/**\n * Placeholder version string from the TS-W0 Wave 1 scaffold. The\n * authoritative package version lives in `package.json#version`\n * (currently `0.1.0-rc.7`); this constant has not been bumped.\n */\nexport const version = \"0.0.0\";\n\n/**\n * Smoke-test export from the TS-W0 Wave 1 scaffold. Returns the literal\n * string `\"hello @mostlyrightmd/markets\"`. Retained so the published\n * package has at least one importable runtime export until the scaffold\n * is removed in a later phase.\n */\nexport function helloMarkets(): string {\n return \"hello @mostlyrightmd/markets\";\n}\n\n// Re-export generated data (NOT auto-generated; hand-maintained barrel).\n// The targets ARE auto-generated by @mostlyrightmd/codegen — see packages-ts/codegen.\nexport * from \"./data/generated/index.js\";\n\n// Kalshi NHIGH/NLOW resolvers (TS-W1 Wave 2).\nexport * from \"./resolvers/index.js\";\n\n// Kalshi settlement helper (TS-W5 Wave 5) — higher-level dispatch by\n// prefix (KHIGH* vs KLOW*).\nexport { kalshiSettlementFor } from \"./kalshi-settlement.js\";\nexport type { KalshiSettlement } from \"./kalshi-settlement.js\";\n\n// NOTE: Polymarket discover/settle (TS-W5 Waves 1-4) live at the\n// `./polymarket` subpath, NOT the root barrel — keeps the IIFE bundle\n// lean (Polymarket is server-side by design; CORS-blocked from\n// browsers per .planning/research/TS-CORS-MATRIX.md). Import with:\n//\n// import { polymarketDiscover, polymarketSettle } from \"@mostlyrightmd/markets/polymarket\";\n","// AUTO-GENERATED by @mostlyrightmd/codegen from schemas/kalshi-settlement-stations.json.\n// DO NOT EDIT — regenerate with: pnpm codegen\n// Last manifest SHA recorded in schemas/EXPORT_MANIFEST.json\n\nexport interface KalshiStation {\n station: string;\n citation: string;\n}\n\nexport const KALSHI_SETTLEMENT_STATIONS: Readonly<Record<string, KalshiStation>> = {\n ATL: {\n citation: \"https://kalshi.com/markets/khighatl (Atlanta Hartsfield-Jackson)\",\n station: \"KATL\",\n },\n AUS: {\n citation: \"https://kalshi.com/markets/khighaus (Austin-Bergstrom; the only Austin station Kalshi cites)\",\n station: \"KAUS\",\n },\n BNA: {\n citation: \"https://kalshi.com/markets/khighbna (Nashville International)\",\n station: \"KBNA\",\n },\n BOS: {\n citation: \"https://kalshi.com/markets/khighbos (Boston Logan)\",\n station: \"KBOS\",\n },\n CHI: {\n citation: \"https://kalshi.com/markets/khighchi (Midway, NOT ORD)\",\n station: \"KMDW\",\n },\n CVG: {\n citation: \"https://kalshi.com/markets/khighcvg (Cincinnati/Northern Kentucky International)\",\n station: \"KCVG\",\n },\n DAL: {\n citation: \"https://kalshi.com/markets/khighdal (DFW, NOT Love Field)\",\n station: \"KDFW\",\n },\n DCA: {\n citation: \"https://kalshi.com/markets/khighdca (Reagan National, NOT Dulles or BWI)\",\n station: \"KDCA\",\n },\n DEN: {\n citation: \"https://kalshi.com/markets/khighden (Denver International)\",\n station: \"KDEN\",\n },\n DTW: {\n citation: \"https://kalshi.com/markets/khighdtw (Detroit Metropolitan)\",\n station: \"KDTW\",\n },\n HOU: {\n citation: \"https://kalshi.com/markets/khighhou (Intercontinental, NOT Hobby; Kalshi cites IAH)\",\n station: \"KIAH\",\n },\n LAX: {\n citation: \"https://kalshi.com/markets/khighlax (LAX international)\",\n station: \"KLAX\",\n },\n MIA: {\n citation: \"https://kalshi.com/markets/khighmia (Miami International)\",\n station: \"KMIA\",\n },\n MSP: {\n citation: \"https://kalshi.com/markets/khighmsp (Minneapolis-St. Paul International)\",\n station: \"KMSP\",\n },\n NYC: {\n citation: \"https://kalshi.com/markets/khighny (Central Park, NOT LGA/JFK)\",\n station: \"KNYC\",\n },\n PHL: {\n citation: \"https://kalshi.com/markets/khighphl (Philadelphia International)\",\n station: \"KPHL\",\n },\n PHX: {\n citation: \"https://kalshi.com/markets/khighphx (Sky Harbor International)\",\n station: \"KPHX\",\n },\n SEA: {\n citation: \"https://kalshi.com/markets/khighsea (SeaTac, NOT BFI)\",\n station: \"KSEA\",\n },\n SFO: {\n citation: \"https://kalshi.com/markets/khighsfo (San Francisco International, NOT OAK)\",\n station: \"KSFO\",\n },\n SLC: {\n citation: \"https://kalshi.com/markets/khighslc (Salt Lake City International)\",\n station: \"KSLC\",\n },\n TLV: {\n citation: \"https://kalshi.com/markets/kxhightlv (Harry Reid/McCarran; settles vs NWS CLILAS)\",\n station: \"KLAS\",\n },\n} as const;\n\nexport const KNOWN_WRONG_STATIONS: ReadonlySet<string> = new Set<string>([\n \"KBWI\",\n \"KDAL\",\n \"KEWR\",\n \"KHOU\",\n \"KIAD\",\n \"KJFK\",\n \"KLGA\",\n \"KOAK\",\n \"KORD\",\n]);\n","// AUTO-GENERATED by @mostlyrightmd/codegen from schemas/polymarket-city-stations.json.\n// DO NOT EDIT — regenerate with: pnpm codegen\n// Last manifest SHA recorded in schemas/EXPORT_MANIFEST.json\n\nexport interface PolymarketCityStation {\n default: string;\n high?: string;\n low?: string;\n [measure: string]: string | undefined;\n}\n\nexport const POLYMARKET_CITY_STATIONS: Readonly<Record<string, PolymarketCityStation>> = {\n amsterdam: {\n default: \"EHAM\",\n },\n ankara: {\n default: \"LTAC\",\n },\n atlanta: {\n default: \"KATL\",\n },\n austin: {\n default: \"KAUS\",\n },\n beijing: {\n default: \"ZBAA\",\n },\n buenos_aires: {\n default: \"SAEZ\",\n },\n busan: {\n default: \"RKPK\",\n },\n cape_town: {\n default: \"FACT\",\n },\n chengdu: {\n default: \"ZUUU\",\n },\n chicago: {\n default: \"KORD\",\n high: \"KORD\",\n low: \"KORD\",\n },\n chongqing: {\n default: \"ZUCK\",\n },\n dallas: {\n default: \"KDAL\",\n },\n denver: {\n default: \"KBKF\",\n },\n guangzhou: {\n default: \"ZGGG\",\n },\n helsinki: {\n default: \"EFHK\",\n },\n hong_kong: {\n default: \"HKO\",\n high: \"HKO\",\n low: \"HKO\",\n },\n houston: {\n default: \"KHOU\",\n },\n istanbul: {\n default: \"LTFM\",\n },\n jeddah: {\n default: \"OEJN\",\n },\n jinan: {\n default: \"ZSJN\",\n },\n karachi: {\n default: \"OPKC\",\n },\n kuala_lumpur: {\n default: \"WMKK\",\n },\n london: {\n default: \"EGLC\",\n },\n los_angeles: {\n default: \"KLAX\",\n high: \"KLAX\",\n low: \"KLAX\",\n },\n lucknow: {\n default: \"VILK\",\n },\n madrid: {\n default: \"LEMD\",\n },\n manila: {\n default: \"RPLL\",\n },\n mexico_city: {\n default: \"MMMX\",\n },\n miami: {\n default: \"KMIA\",\n },\n milan: {\n default: \"LIMC\",\n },\n moscow: {\n default: \"UUWW\",\n },\n munich: {\n default: \"EDDM\",\n },\n nyc: {\n default: \"KLGA\",\n high: \"KLGA\",\n low: \"KLGA\",\n },\n panama_city: {\n default: \"MPMG\",\n },\n paris: {\n default: \"LFPB\",\n },\n qingdao: {\n default: \"ZSQD\",\n },\n san_francisco: {\n default: \"KSFO\",\n },\n sao_paulo: {\n default: \"SBGR\",\n },\n seattle: {\n default: \"KSEA\",\n },\n seoul: {\n default: \"RKSI\",\n },\n shanghai: {\n default: \"ZSPD\",\n },\n shenzhen: {\n default: \"ZGSZ\",\n },\n singapore: {\n default: \"WSSS\",\n },\n taipei: {\n default: \"RCSS\",\n },\n tel_aviv: {\n default: \"LLBG\",\n },\n tokyo: {\n default: \"RJTT\",\n high: \"RJTT\",\n low: \"RJTT\",\n },\n toronto: {\n default: \"CYYZ\",\n },\n warsaw: {\n default: \"EPWA\",\n },\n wellington: {\n default: \"NZWN\",\n },\n wuhan: {\n default: \"ZHHH\",\n },\n zhengzhou: {\n default: \"ZHCC\",\n },\n} as const;\n","// Kalshi NHIGH (daily HIGH temperature) contract resolver.\n//\n// NHIGH markets resolve against the NWS CLI `max_temp_f` value for a\n// specific station on a specific date. `kalshiNhighResolve` is the\n// deterministic mapping from a Kalshi market identifier to the\n// (settlement_source, settlement_station) tuple downstream code uses\n// to pull the right settlement row from the CLI catalog.\n//\n// Ported byte-faithful from packages/markets/src/mostlyright/markets/catalog/kalshi_nhigh.py.\n\nimport { KALSHI_SETTLEMENT_STATIONS } from \"../data/generated/kalshi-stations.js\";\n\nexport interface NHighResolution {\n readonly settlementSource: \"cli.archive\";\n readonly settlementStation: string; // 4-letter ICAO\n readonly cityTicker: string;\n readonly contractDate: string; // YYYY-MM-DD\n}\n\n/**\n * Custom error type for contract-id parsing / validation failures.\n *\n * Mirrors the Python `ValueError`/`TypeError` distinction: in TS we use a\n * named subclass so callers can `instanceof`-check rather than parse\n * error messages.\n */\nexport class ContractIdError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ContractIdError\";\n }\n}\n\n/**\n * Coerce a Date or YYYY-MM-DD string into an ISO date-only string.\n *\n * Rejects Date instances whose UTC hours/minutes/seconds/ms are non-zero —\n * those would silently corrupt downstream settlement-date matching\n * (date-equality is strict).\n *\n * Mirrors the Python `isinstance(settlement_date, datetime)` guard in\n * `kalshi_nhigh.resolve`.\n */\nfunction coerceContractDate(value: Date | string): string {\n if (typeof value === \"string\") {\n // YYYY-MM-DD strict format check.\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n throw new ContractIdError(\n `settlementDate string must be YYYY-MM-DD; got ${JSON.stringify(value)}`,\n );\n }\n // Validate it parses to a real calendar date.\n const parsed = new Date(`${value}T00:00:00Z`);\n if (Number.isNaN(parsed.getTime())) {\n throw new ContractIdError(\n `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`,\n );\n }\n // Round-trip check — guards against e.g. \"2025-02-30\" silently\n // rolling forward.\n const iso = parsed.toISOString().slice(0, 10);\n if (iso !== value) {\n throw new ContractIdError(\n `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`,\n );\n }\n return value;\n }\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new ContractIdError(\"settlementDate is an invalid Date instance\");\n }\n // Reject any Date with a non-zero UTC time component — mirrors\n // Python's `isinstance(settlement_date, datetime)` guard. A Date\n // carries a time component which would break downstream\n // date-equality matching.\n if (\n value.getUTCHours() !== 0 ||\n value.getUTCMinutes() !== 0 ||\n value.getUTCSeconds() !== 0 ||\n value.getUTCMilliseconds() !== 0\n ) {\n throw new ContractIdError(\n `settlementDate must be a UTC date-only Date (H/M/S/ms = 0); got ${value.toISOString()}. Use new Date('YYYY-MM-DDT00:00:00Z') or pass a 'YYYY-MM-DD' string.`,\n );\n }\n return value.toISOString().slice(0, 10);\n }\n throw new ContractIdError(\"settlementDate must be a Date instance or YYYY-MM-DD string\");\n}\n\n/**\n * Resolve a Kalshi NHIGH contract to its settlement source + station.\n *\n * The contract id format is `KHIGH<CITY>` (case-insensitive), where\n * `<CITY>` is a city ticker present in\n * {@link KALSHI_SETTLEMENT_STATIONS}.\n *\n * @param contractId Kalshi market identifier. Case-insensitive.\n * @param settlementDate Calendar date the market settles for. Either a\n * UTC date-only `Date` (H/M/S/ms == 0) or a `YYYY-MM-DD` string.\n * @returns A frozen {@link NHighResolution}.\n * @throws {ContractIdError} The contract id doesn't follow\n * `KHIGH<CITY>`, the city is unknown, or the settlement date is\n * invalid.\n */\nexport function kalshiNhighResolve(\n contractId: string,\n settlementDate: Date | string,\n): NHighResolution {\n if (typeof contractId !== \"string\") {\n throw new ContractIdError(`contractId must be a string; got ${typeof contractId}`);\n }\n\n const contractDate = coerceContractDate(settlementDate);\n\n const cid = contractId.toUpperCase();\n if (!cid.startsWith(\"KHIGH\") || cid.length <= 5) {\n throw new ContractIdError(\n `NHIGH contractId must follow 'KHIGH<CITY>' format; got ${JSON.stringify(contractId)}`,\n );\n }\n const cityTicker = cid.slice(5);\n const station = KALSHI_SETTLEMENT_STATIONS[cityTicker];\n if (station === undefined) {\n const known = Object.keys(KALSHI_SETTLEMENT_STATIONS).sort();\n throw new ContractIdError(\n `unknown city ${JSON.stringify(cityTicker)} (known: ${known.join(\", \")})`,\n );\n }\n\n return Object.freeze({\n settlementSource: \"cli.archive\" as const,\n settlementStation: station.station,\n cityTicker,\n contractDate,\n });\n}\n","// Kalshi NLOW (daily LOW temperature) contract resolver.\n//\n// Mirror of kalshi-nhigh — same station whitelist + same source\n// (cli.archive); only the metric differs. NLOW markets resolve against\n// the NWS CLI `min_temp_f` value for a specific station on a specific\n// date.\n//\n// Ported byte-faithful from packages/markets/src/mostlyright/markets/catalog/kalshi_nlow.py.\n\nimport { KALSHI_SETTLEMENT_STATIONS } from \"../data/generated/kalshi-stations.js\";\nimport { ContractIdError } from \"./kalshi-nhigh.js\";\n\nexport interface NLowResolution {\n readonly settlementSource: \"cli.archive\";\n readonly settlementStation: string; // 4-letter ICAO\n readonly cityTicker: string;\n readonly contractDate: string; // YYYY-MM-DD\n}\n\n/**\n * Coerce a Date or YYYY-MM-DD string into an ISO date-only string.\n *\n * Rejects Date instances whose UTC hours/minutes/seconds/ms are non-zero —\n * those would silently corrupt downstream settlement-date matching.\n *\n * Mirrors the Python `isinstance(settlement_date, datetime)` guard.\n *\n * Note: duplicated here (rather than imported) so kalshi-nlow stays a\n * standalone file matching the Python module split. The two functions\n * are byte-identical by design.\n */\nfunction coerceContractDate(value: Date | string): string {\n if (typeof value === \"string\") {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n throw new ContractIdError(\n `settlementDate string must be YYYY-MM-DD; got ${JSON.stringify(value)}`,\n );\n }\n const parsed = new Date(`${value}T00:00:00Z`);\n if (Number.isNaN(parsed.getTime())) {\n throw new ContractIdError(\n `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`,\n );\n }\n const iso = parsed.toISOString().slice(0, 10);\n if (iso !== value) {\n throw new ContractIdError(\n `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`,\n );\n }\n return value;\n }\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new ContractIdError(\"settlementDate is an invalid Date instance\");\n }\n if (\n value.getUTCHours() !== 0 ||\n value.getUTCMinutes() !== 0 ||\n value.getUTCSeconds() !== 0 ||\n value.getUTCMilliseconds() !== 0\n ) {\n throw new ContractIdError(\n `settlementDate must be a UTC date-only Date (H/M/S/ms = 0); got ${value.toISOString()}. Use new Date('YYYY-MM-DDT00:00:00Z') or pass a 'YYYY-MM-DD' string.`,\n );\n }\n return value.toISOString().slice(0, 10);\n }\n throw new ContractIdError(\"settlementDate must be a Date instance or YYYY-MM-DD string\");\n}\n\n/**\n * Resolve a Kalshi NLOW contract to its settlement source + station.\n *\n * The contract id format is `KLOW<CITY>` (case-insensitive), where\n * `<CITY>` is a city ticker present in\n * {@link KALSHI_SETTLEMENT_STATIONS}.\n *\n * @param contractId Kalshi market identifier. Case-insensitive.\n * @param settlementDate Calendar date the market settles for. Either a\n * UTC date-only `Date` (H/M/S/ms == 0) or a `YYYY-MM-DD` string.\n * @returns A frozen {@link NLowResolution}.\n * @throws {ContractIdError} The contract id doesn't follow\n * `KLOW<CITY>`, the city is unknown, or the settlement date is\n * invalid.\n */\nexport function kalshiNlowResolve(\n contractId: string,\n settlementDate: Date | string,\n): NLowResolution {\n if (typeof contractId !== \"string\") {\n throw new ContractIdError(`contractId must be a string; got ${typeof contractId}`);\n }\n\n const contractDate = coerceContractDate(settlementDate);\n\n const cid = contractId.toUpperCase();\n if (!cid.startsWith(\"KLOW\") || cid.length <= 4) {\n throw new ContractIdError(\n `NLOW contractId must follow 'KLOW<CITY>' format; got ${JSON.stringify(contractId)}`,\n );\n }\n const cityTicker = cid.slice(4);\n const station = KALSHI_SETTLEMENT_STATIONS[cityTicker];\n if (station === undefined) {\n const known = Object.keys(KALSHI_SETTLEMENT_STATIONS).sort();\n throw new ContractIdError(\n `unknown city ${JSON.stringify(cityTicker)} (known: ${known.join(\", \")})`,\n );\n }\n\n return Object.freeze({\n settlementSource: \"cli.archive\" as const,\n settlementStation: station.station,\n cityTicker,\n contractDate,\n });\n}\n","// TS-W5 Wave 5 — kalshiSettlementFor higher-level helper.\n//\n// Dispatch by Kalshi contract-id prefix (KHIGH* vs KLOW*) to the right\n// resolver. Returns the same shape both NHIGH and NLOW resolvers use so\n// downstream consumers don't need to know about the city-suffix split.\n\nimport { type NHighResolution, kalshiNhighResolve } from \"./resolvers/kalshi-nhigh.js\";\nimport { ContractIdError } from \"./resolvers/kalshi-nhigh.js\";\nimport { type NLowResolution, kalshiNlowResolve } from \"./resolvers/kalshi-nlow.js\";\n\nexport type KalshiSettlement = NHighResolution | NLowResolution;\n\n/**\n * Resolve a Kalshi NHIGH or NLOW contract id to its settlement metadata.\n *\n * `KHIGH*` prefixes dispatch to the NHIGH resolver; `KLOW*` prefixes\n * dispatch to NLOW. Anything else raises `ContractIdError`.\n *\n * @example\n * kalshiSettlementFor(\"KHIGHNYC\", \"2025-01-06\")\n * // → { settlementSource: \"cli.archive\", settlementStation: \"KNYC\",\n * // cityTicker: \"NYC\", contractDate: \"2025-01-06\" }\n */\nexport function kalshiSettlementFor(contractId: string, date: string): KalshiSettlement {\n if (typeof contractId !== \"string\" || contractId.length === 0) {\n throw new ContractIdError(\"contractId must be a non-empty string\");\n }\n const upper = contractId.toUpperCase();\n if (upper.startsWith(\"KHIGH\")) return kalshiNhighResolve(upper, date);\n if (upper.startsWith(\"KLOW\")) return kalshiNlowResolve(upper, date);\n throw new ContractIdError(\n `contractId ${JSON.stringify(contractId)} does not start with KHIGH or KLOW`,\n );\n}\n","const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);\n\nlet idbProxyableTypes;\nlet cursorAdvanceMethods;\n// This is a function to prevent it throwing up in node environments.\nfunction getIdbProxyableTypes() {\n return (idbProxyableTypes ||\n (idbProxyableTypes = [\n IDBDatabase,\n IDBObjectStore,\n IDBIndex,\n IDBCursor,\n IDBTransaction,\n ]));\n}\n// This is a function to prevent it throwing up in node environments.\nfunction getCursorAdvanceMethods() {\n return (cursorAdvanceMethods ||\n (cursorAdvanceMethods = [\n IDBCursor.prototype.advance,\n IDBCursor.prototype.continue,\n IDBCursor.prototype.continuePrimaryKey,\n ]));\n}\nconst transactionDoneMap = new WeakMap();\nconst transformCache = new WeakMap();\nconst reverseTransformCache = new WeakMap();\nfunction promisifyRequest(request) {\n const promise = new Promise((resolve, reject) => {\n const unlisten = () => {\n request.removeEventListener('success', success);\n request.removeEventListener('error', error);\n };\n const success = () => {\n resolve(wrap(request.result));\n unlisten();\n };\n const error = () => {\n reject(request.error);\n unlisten();\n };\n request.addEventListener('success', success);\n request.addEventListener('error', error);\n });\n // This mapping exists in reverseTransformCache but doesn't exist in transformCache. This\n // is because we create many promises from a single IDBRequest.\n reverseTransformCache.set(promise, request);\n return promise;\n}\nfunction cacheDonePromiseForTransaction(tx) {\n // Early bail if we've already created a done promise for this transaction.\n if (transactionDoneMap.has(tx))\n return;\n const done = new Promise((resolve, reject) => {\n const unlisten = () => {\n tx.removeEventListener('complete', complete);\n tx.removeEventListener('error', error);\n tx.removeEventListener('abort', error);\n };\n const complete = () => {\n resolve();\n unlisten();\n };\n const error = () => {\n reject(tx.error || new DOMException('AbortError', 'AbortError'));\n unlisten();\n };\n tx.addEventListener('complete', complete);\n tx.addEventListener('error', error);\n tx.addEventListener('abort', error);\n });\n // Cache it for later retrieval.\n transactionDoneMap.set(tx, done);\n}\nlet idbProxyTraps = {\n get(target, prop, receiver) {\n if (target instanceof IDBTransaction) {\n // Special handling for transaction.done.\n if (prop === 'done')\n return transactionDoneMap.get(target);\n // Make tx.store return the only store in the transaction, or undefined if there are many.\n if (prop === 'store') {\n return receiver.objectStoreNames[1]\n ? undefined\n : receiver.objectStore(receiver.objectStoreNames[0]);\n }\n }\n // Else transform whatever we get back.\n return wrap(target[prop]);\n },\n set(target, prop, value) {\n target[prop] = value;\n return true;\n },\n has(target, prop) {\n if (target instanceof IDBTransaction &&\n (prop === 'done' || prop === 'store')) {\n return true;\n }\n return prop in target;\n },\n};\nfunction replaceTraps(callback) {\n idbProxyTraps = callback(idbProxyTraps);\n}\nfunction wrapFunction(func) {\n // Due to expected object equality (which is enforced by the caching in `wrap`), we\n // only create one new func per func.\n // Cursor methods are special, as the behaviour is a little more different to standard IDB. In\n // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the\n // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense\n // with real promises, so each advance methods returns a new promise for the cursor object, or\n // undefined if the end of the cursor has been reached.\n if (getCursorAdvanceMethods().includes(func)) {\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n func.apply(unwrap(this), args);\n return wrap(this.request);\n };\n }\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n return wrap(func.apply(unwrap(this), args));\n };\n}\nfunction transformCachableValue(value) {\n if (typeof value === 'function')\n return wrapFunction(value);\n // This doesn't return, it just creates a 'done' promise for the transaction,\n // which is later returned for transaction.done (see idbObjectHandler).\n if (value instanceof IDBTransaction)\n cacheDonePromiseForTransaction(value);\n if (instanceOfAny(value, getIdbProxyableTypes()))\n return new Proxy(value, idbProxyTraps);\n // Return the same value back if we're not going to transform it.\n return value;\n}\nfunction wrap(value) {\n // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because\n // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.\n if (value instanceof IDBRequest)\n return promisifyRequest(value);\n // If we've already transformed this value before, reuse the transformed value.\n // This is faster, but it also provides object equality.\n if (transformCache.has(value))\n return transformCache.get(value);\n const newValue = transformCachableValue(value);\n // Not all types are transformed.\n // These may be primitive types, so they can't be WeakMap keys.\n if (newValue !== value) {\n transformCache.set(value, newValue);\n reverseTransformCache.set(newValue, value);\n }\n return newValue;\n}\nconst unwrap = (value) => reverseTransformCache.get(value);\n\n/**\n * Open a database.\n *\n * @param name Name of the database.\n * @param version Schema version.\n * @param callbacks Additional callbacks.\n */\nfunction openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {\n const request = indexedDB.open(name, version);\n const openPromise = wrap(request);\n if (upgrade) {\n request.addEventListener('upgradeneeded', (event) => {\n upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);\n });\n }\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event.newVersion, event));\n }\n openPromise\n .then((db) => {\n if (terminated)\n db.addEventListener('close', () => terminated());\n if (blocking) {\n db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));\n }\n })\n .catch(() => { });\n return openPromise;\n}\n/**\n * Delete a database.\n *\n * @param name Name of the database.\n */\nfunction deleteDB(name, { blocked } = {}) {\n const request = indexedDB.deleteDatabase(name);\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event));\n }\n return wrap(request).then(() => undefined);\n}\n\nconst readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];\nconst writeMethods = ['put', 'add', 'delete', 'clear'];\nconst cachedMethods = new Map();\nfunction getMethod(target, prop) {\n if (!(target instanceof IDBDatabase &&\n !(prop in target) &&\n typeof prop === 'string')) {\n return;\n }\n if (cachedMethods.get(prop))\n return cachedMethods.get(prop);\n const targetFuncName = prop.replace(/FromIndex$/, '');\n const useIndex = prop !== targetFuncName;\n const isWrite = writeMethods.includes(targetFuncName);\n if (\n // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.\n !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||\n !(isWrite || readMethods.includes(targetFuncName))) {\n return;\n }\n const method = async function (storeName, ...args) {\n // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(\n const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');\n let target = tx.store;\n if (useIndex)\n target = target.index(args.shift());\n // Must reject if op rejects.\n // If it's a write operation, must reject if tx.done rejects.\n // Must reject with op rejection first.\n // Must resolve with op value.\n // Must handle both promises (no unhandled rejections)\n return (await Promise.all([\n target[targetFuncName](...args),\n isWrite && tx.done,\n ]))[0];\n };\n cachedMethods.set(prop, method);\n return method;\n}\nreplaceTraps((oldTraps) => ({\n ...oldTraps,\n get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),\n has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),\n}));\n\nconst advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance'];\nconst methodMap = {};\nconst advanceResults = new WeakMap();\nconst ittrProxiedCursorToOriginalProxy = new WeakMap();\nconst cursorIteratorTraps = {\n get(target, prop) {\n if (!advanceMethodProps.includes(prop))\n return target[prop];\n let cachedFunc = methodMap[prop];\n if (!cachedFunc) {\n cachedFunc = methodMap[prop] = function (...args) {\n advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));\n };\n }\n return cachedFunc;\n },\n};\nasync function* iterate(...args) {\n // tslint:disable-next-line:no-this-assignment\n let cursor = this;\n if (!(cursor instanceof IDBCursor)) {\n cursor = await cursor.openCursor(...args);\n }\n if (!cursor)\n return;\n cursor = cursor;\n const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);\n ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);\n // Map this double-proxy back to the original, so other cursor methods work.\n reverseTransformCache.set(proxiedCursor, unwrap(cursor));\n while (cursor) {\n yield proxiedCursor;\n // If one of the advancing methods was not called, call continue().\n cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());\n advanceResults.delete(proxiedCursor);\n }\n}\nfunction isIteratorProp(target, prop) {\n return ((prop === Symbol.asyncIterator &&\n instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||\n (prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));\n}\nreplaceTraps((oldTraps) => ({\n ...oldTraps,\n get(target, prop, receiver) {\n if (isIteratorProp(target, prop))\n return iterate;\n return oldTraps.get(target, prop, receiver);\n },\n has(target, prop) {\n return isIteratorProp(target, prop) || oldTraps.has(target, prop);\n },\n}));\n\nexport { deleteDB, openDB, unwrap, wrap };\n","// CacheStore — pluggable key/value contract for the @mostlyrightmd/core cache\n// layer. Three concrete implementations land in TS-W3:\n// - MemoryStore — Map-backed, no persistence (Cloudflare Workers default).\n// - FsStore — node:fs/promises + proper-lockfile (Node default).\n// - IndexedDBStore — idb + Web Locks API (browser; plan 02).\n//\n// `defaultCacheStore()` (plan 02) auto-detects at runtime per\n// TS-SDK-DESIGN §5.4.\n\n/** Cache entry envelope with optional TTL. */\nexport interface CacheEntry<T = unknown> {\n readonly value: T;\n /** Epoch ms when the entry expires. Absence = no expiry. */\n readonly expiresAt?: number;\n}\n\n/** Optional setters for cache writes. */\nexport interface CacheSetOptions {\n /** Time-to-live in milliseconds. Implementations may honor or ignore. */\n readonly ttlMs?: number;\n}\n\n/**\n * Pluggable key/value cache contract used throughout the SDK.\n *\n * All methods are async — concrete implementations may resolve immediately\n * (MemoryStore) or do I/O (FsStore / IndexedDBStore).\n *\n * Semantic contract:\n * - `get<T>(key)` returns the stored value or `null` on miss. NEVER throws\n * on miss.\n * - `set<T>(key, value, opts?)` overwrites. ttlMs is implementation-honored\n * (MemoryStore + IndexedDBStore honor it; FsStore ignores in v0.1).\n * - `delete(key)` is a no-op on miss; returns void.\n * - `withLock<T>(key, fn)` runs `fn` under a key-scoped exclusive lock and\n * releases on settle (resolve OR throw). Nested calls to the same key\n * serialize; calls to different keys MAY run in parallel.\n */\nexport interface CacheStore {\n get<T = unknown>(key: string): Promise<T | null>;\n set<T = unknown>(key: string, value: T, opts?: CacheSetOptions): Promise<void>;\n delete(key: string): Promise<void>;\n withLock<T>(key: string, fn: () => Promise<T>): Promise<T>;\n}\n\n/**\n * Canonical lock identifier for a given cache key.\n *\n * Pure — same key → same lock id. Used by FsStore (proper-lockfile sidecar)\n * and IndexedDBStore (`navigator.locks.request(...)` name).\n */\nexport function lockKeyFor(key: string): string {\n return `mostlyright:cache:lock:${key}`;\n}\n\n/**\n * Canonical cache schema version stamp (Phase 21 21-03).\n *\n * Must match Python `mostlyright.weather.cache._cache_schema_version`\n * (set in Phase 18 18-08 to \"v2-phase18-integer-f\" after the ASOS\n * integer-°F precision fix). Embedded by `versionedCacheStore` into\n * every cache write so pre-bump entries silently re-fetch.\n *\n * Bump this string when a cache-shape change ships; existing cached\n * values invalidate on next read with no operator action required.\n */\nexport const CACHE_SCHEMA_VERSION = \"v2-phase18-integer-f\";\n","// MemoryStore — Map-backed CacheStore for ephemeral runtimes (Cloudflare\n// Workers, jsdom test envs without persistence). NOT shared across\n// processes — per-instance state.\n//\n// Value isolation via `structuredClone`: callers can mutate stored objects\n// after `.set()` without leaking changes back. Honors ttlMs with lazy\n// eviction at `.get()` time.\n//\n// withLock uses a per-key promise chain — pending lock-acquisitions queue\n// behind the current holder and run in FIFO order on settle.\n\nimport type { CacheEntry, CacheSetOptions, CacheStore } from \"./types.js\";\n\n/**\n * In-memory cache. Per-instance state; two MemoryStore instances do NOT\n * share state.\n *\n * - Values cloned via `structuredClone` so post-`set` mutation can't leak.\n * - ttlMs honored with lazy eviction on `get`.\n * - withLock serializes nested calls via a per-key promise chain.\n */\nexport class MemoryStore implements CacheStore {\n readonly #entries = new Map<string, CacheEntry<unknown>>();\n readonly #chain = new Map<string, Promise<unknown>>();\n\n async get<T = unknown>(key: string): Promise<T | null> {\n const e = this.#entries.get(key);\n if (e === undefined) return null;\n if (e.expiresAt !== undefined && Date.now() >= e.expiresAt) {\n this.#entries.delete(key);\n return null;\n }\n // Defensive clone on read too — callers can't mutate stored value via\n // the returned reference either.\n return structuredClone(e.value) as T;\n }\n\n async set<T = unknown>(key: string, value: T, opts?: CacheSetOptions): Promise<void> {\n const cloned = structuredClone(value);\n const entry: CacheEntry<unknown> =\n opts?.ttlMs !== undefined\n ? { value: cloned, expiresAt: Date.now() + opts.ttlMs }\n : { value: cloned };\n this.#entries.set(key, entry);\n }\n\n async delete(key: string): Promise<void> {\n this.#entries.delete(key);\n }\n\n /**\n * Enumerate live (non-expired) keys with the given prefix.\n *\n * TS-W6 Wave 1: `availability()` uses this to count cached observation\n * months and climate years per station. Expired entries are evicted as a\n * side effect (same lazy-eviction policy as `.get`).\n */\n async listKeys(prefix: string): Promise<ReadonlyArray<string>> {\n const now = Date.now();\n const out: string[] = [];\n for (const [key, entry] of this.#entries) {\n if (entry.expiresAt !== undefined && now >= entry.expiresAt) {\n this.#entries.delete(key);\n continue;\n }\n if (key.startsWith(prefix)) {\n out.push(key);\n }\n }\n return Object.freeze(out);\n }\n\n async withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n const prev = this.#chain.get(key) ?? Promise.resolve();\n // Chain `fn` after `prev` regardless of whether `prev` resolved or\n // rejected — the lock holder's failure shouldn't poison the queue.\n const next = prev.then(\n () => fn(),\n () => fn(),\n );\n // Store an absorber as the new tail so a later prev.then() handles\n // both branches without producing an unhandled-rejection warning. The\n // caller still receives the original `next` promise (including any\n // rejection from `fn`).\n const absorbed = next.then(\n () => undefined,\n () => undefined,\n );\n this.#chain.set(key, absorbed);\n absorbed.finally(() => {\n if (this.#chain.get(key) === absorbed) {\n this.#chain.delete(key);\n }\n });\n return next;\n }\n}\n","// IndexedDBStore — idb + Web Locks API CacheStore for browsers.\n//\n// Per TS-CACHE-02, the canonical IndexedDB DB name is `mostlyright-cache-v1`.\n// Object store: `entries`. Schema: key = string, value = CacheEntry<T>.\n//\n// withLock prefers `navigator.locks.request(name, ...)` (Web Locks API,\n// Chrome 69+, Firefox 96+, Safari 15.4+; all production-browser baselines).\n// When `navigator.locks` is unavailable (jsdom default), falls back to the\n// same per-key in-process promise chain used by MemoryStore / FsStore —\n// edge runtimes (Workers without web-locks polyfills) get the in-process\n// guarantee at least.\n//\n// ttlMs is honored via lazy eviction at `get` time (matches FsStore).\n\nimport { type IDBPDatabase, openDB } from \"idb\";\n\nimport type { CacheEntry, CacheSetOptions, CacheStore } from \"./types.js\";\nimport { lockKeyFor } from \"./types.js\";\n\n/** Canonical DB name. Re-exported via the cache barrel. */\nexport const DB_NAME = \"mostlyright-cache-v1\";\n\nconst STORE_NAME = \"entries\";\nconst SCHEMA_VERSION = 1;\n\nexport interface IndexedDBStoreOptions {\n /** Override the DB name. Tests pass unique values per case so they don't pollute each other. */\n readonly dbName?: string;\n}\n\ninterface WebLocksApi {\n request: <T>(\n name: string,\n options: { mode: \"exclusive\" },\n fn: () => Promise<T> | T,\n ) => Promise<T>;\n}\n\nfunction getWebLocks(): WebLocksApi | null {\n if (typeof navigator === \"undefined\") return null;\n const nav = navigator as unknown as { locks?: WebLocksApi };\n return nav.locks ?? null;\n}\n\n/**\n * Browser CacheStore backed by IndexedDB (via idb) + Web Locks API.\n *\n * When `navigator.locks` is unavailable (jsdom, edge runtimes without\n * Web Locks), falls back to a per-key in-process promise chain.\n */\nexport class IndexedDBStore implements CacheStore {\n readonly #dbName: string;\n readonly #dbPromise: Promise<IDBPDatabase>;\n readonly #chain = new Map<string, Promise<unknown>>();\n\n constructor(opts: IndexedDBStoreOptions = {}) {\n this.#dbName = opts.dbName ?? DB_NAME;\n this.#dbPromise = openDB(this.#dbName, SCHEMA_VERSION, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME);\n }\n },\n });\n }\n\n async get<T = unknown>(key: string): Promise<T | null> {\n const db = await this.#dbPromise;\n const entry = (await db.get(STORE_NAME, key)) as CacheEntry<T> | undefined;\n if (entry === undefined) return null;\n if (entry.expiresAt !== undefined && Date.now() >= entry.expiresAt) {\n // Lazy-evict — best-effort; ignore failures.\n try {\n await db.delete(STORE_NAME, key);\n } catch {\n // ignore\n }\n return null;\n }\n return entry.value as T;\n }\n\n async set<T = unknown>(key: string, value: T, opts?: CacheSetOptions): Promise<void> {\n const db = await this.#dbPromise;\n const entry: CacheEntry<T> =\n opts?.ttlMs !== undefined ? { value, expiresAt: Date.now() + opts.ttlMs } : { value };\n await db.put(STORE_NAME, entry, key);\n }\n\n async delete(key: string): Promise<void> {\n const db = await this.#dbPromise;\n await db.delete(STORE_NAME, key);\n }\n\n /**\n * Enumerate keys with the given prefix using IndexedDB's bounded range\n * query. Live keys only — expired entries are lazy-evicted on read by\n * `get()`, so a stale-but-not-yet-evicted entry can appear here; callers\n * who care about expiration should `get()` to confirm.\n *\n * TS-W6 Wave 1: `availability()` uses this to count observation months and\n * climate years for a station.\n */\n async listKeys(prefix: string): Promise<ReadonlyArray<string>> {\n const db = await this.#dbPromise;\n // Build an inclusive lower bound and an exclusive upper bound using the\n // next-codepoint trick. IndexedDB's IDBKeyRange handles string ordering\n // by UTF-16 code units, which matches JS string comparison; we use the\n // Unicode max-codepoint as the upper sentinel so any key starting with\n // `prefix` lands inside the range.\n const range = IDBKeyRange.bound(prefix, `${prefix}`, false, false);\n const keys = (await db.getAllKeys(STORE_NAME, range)) as IDBValidKey[];\n const out: string[] = [];\n for (const k of keys) {\n if (typeof k === \"string\" && k.startsWith(prefix)) {\n out.push(k);\n }\n }\n return Object.freeze(out);\n }\n\n async withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n const locks = getWebLocks();\n if (locks !== null) {\n // Web Locks API — production-browser path. Cross-tab safe.\n return locks.request<T>(lockKeyFor(key), { mode: \"exclusive\" }, () => fn());\n }\n // Fallback for jsdom / edge runtimes without navigator.locks: in-process\n // per-key promise chain (FIFO; matches MemoryStore semantics).\n const prev = this.#chain.get(key) ?? Promise.resolve();\n const next = prev.then(\n () => fn(),\n () => fn(),\n );\n const absorbed = next.then(\n () => undefined,\n () => undefined,\n );\n this.#chain.set(key, absorbed);\n absorbed.finally(() => {\n if (this.#chain.get(key) === absorbed) this.#chain.delete(key);\n });\n return next;\n }\n}\n","// versionedCacheStore — Phase 21 21-03.\n//\n// Schema-version invariant for the TS cache, matching Python's Phase 18\n// 18-08 invariant. Wraps any underlying CacheStore so reads with a stale\n// `_cache_schema_version` field return null (cache miss → re-fetch),\n// matching the parquet-metadata-based invariant on the Python side.\n//\n// Before: TS cache was generic key/value with no version stamping. After\n// Phase 18 lifted the ASOS integer-°F precision fix, pre-Phase-18 user\n// caches (with 0.06°F float values) would silently return stale values\n// on next call instead of re-fetching. Python embeds the version into\n// parquet metadata (pyarrow `kv_metadata`); TS embeds it as a sidecar\n// field in the stored JSON value.\n//\n// Wire-up: `defaultCacheStore()` wraps each concrete store\n// (IndexedDBStore / FsStore / MemoryStore) via this adapter so consumers\n// see the same `CacheStore` interface. The version wrap/unwrap is\n// invisible from the caller's perspective.\n//\n// Bump `CACHE_SCHEMA_VERSION` in `./types.ts` when the next cache-shape\n// change ships; existing cached values silently invalidate.\n\nimport type { CacheEntry, CacheSetOptions, CacheStore } from \"./types.js\";\n\n/** Sentinel field name embedded in every cached value. */\nconst VERSION_FIELD = \"_cache_schema_version\" as const;\n\n/** Wrapper shape stored under each key. */\nexport interface VersionedEntry<T = unknown> {\n readonly value: T;\n readonly _cache_schema_version: string;\n}\n\nfunction isVersionedEntry(v: unknown): v is VersionedEntry<unknown> {\n if (v === null || typeof v !== \"object\") return false;\n if (!(VERSION_FIELD in (v as Record<string, unknown>))) return false;\n return typeof (v as Record<string, unknown>)[VERSION_FIELD] === \"string\";\n}\n\n// Optional extension surface — some concrete stores (MemoryStore,\n// IndexedDBStore) expose `listKeys(prefix)` beyond the CacheStore\n// contract. The adapter forwards it transparently when present so\n// `availability()` and friends keep working.\ninterface ListKeysCapable {\n listKeys(prefix: string): Promise<ReadonlyArray<string>>;\n}\n\nfunction hasListKeys(s: CacheStore): s is CacheStore & ListKeysCapable {\n return typeof (s as Partial<ListKeysCapable>).listKeys === \"function\";\n}\n\nclass VersionedCacheStore implements CacheStore, ListKeysCapable {\n readonly #inner: CacheStore;\n readonly #version: string;\n\n constructor(inner: CacheStore, version: string) {\n if (typeof version !== \"string\" || version.length === 0) {\n throw new TypeError(\"versionedCacheStore: version must be a non-empty string\");\n }\n this.#inner = inner;\n this.#version = version;\n }\n\n /**\n * Test/diagnostics seam: return the underlying store so tests can assert\n * which concrete backend `defaultCacheStore()` selected. NOT a production\n * API — production code MUST use the wrapped store so version\n * invalidation fires on stale reads.\n *\n * @internal\n */\n __peekInner(): CacheStore {\n return this.#inner;\n }\n\n async get<T = unknown>(key: string): Promise<T | null> {\n const raw = await this.#inner.get<unknown>(key);\n if (raw === null) return null;\n if (!isVersionedEntry(raw)) {\n // Pre-21-03 cache entry (no version wrapper). Treat as miss; caller\n // will re-fetch with the new wrapper on the next set.\n return null;\n }\n if (raw._cache_schema_version !== this.#version) {\n // Mismatched schema version — stale. Treat as miss.\n return null;\n }\n return raw.value as T;\n }\n\n async set<T = unknown>(key: string, value: T, opts?: CacheSetOptions): Promise<void> {\n const wrapped: VersionedEntry<T> = {\n value,\n [VERSION_FIELD]: this.#version,\n } as VersionedEntry<T>;\n await this.#inner.set(key, wrapped, opts);\n }\n\n async delete(key: string): Promise<void> {\n await this.#inner.delete(key);\n }\n\n async withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n return this.#inner.withLock(key, fn);\n }\n\n async listKeys(prefix: string): Promise<ReadonlyArray<string>> {\n if (hasListKeys(this.#inner)) {\n return this.#inner.listKeys(prefix);\n }\n return Object.freeze([]);\n }\n}\n\n/**\n * Wrap any CacheStore so reads validate `_cache_schema_version` and\n * writes embed it. The wrapper is transparent — callers continue to use\n * the same `CacheStore` interface.\n *\n * @param inner underlying store (MemoryStore / IndexedDBStore / FsStore)\n * @param version the schema version to embed; non-matching reads miss\n */\nexport function versionedCacheStore(inner: CacheStore, version: string): CacheStore {\n return new VersionedCacheStore(inner, version);\n}\n\n// Re-export the canonical version constant alongside the adapter — the\n// constant lives in `./types.ts` (single source of truth) but is\n// surfaced here too so callers writing\n// `versionedCacheStore(inner, CACHE_SCHEMA_VERSION)` import both from\n// the same module.\nexport { CACHE_SCHEMA_VERSION } from \"./types.js\";\n\n/**\n * Internal helper for tests + diagnostics: expose the raw envelope shape\n * so callers can pre-seed an underlying store with the wrapped shape\n * without round-tripping through this adapter.\n */\nexport function wrapForCache<T>(value: T, version: string): VersionedEntry<T> {\n return { value, [VERSION_FIELD]: version } as VersionedEntry<T>;\n}\n","// AUTO-GENERATED by @mostlyrightmd/codegen from schemas/stations.json.\n// DO NOT EDIT — regenerate with: pnpm codegen\n// Last manifest SHA recorded in schemas/EXPORT_MANIFEST.json\n\nexport interface StationInfo {\n code: string | null;\n ghcnh_id: string | null;\n icao: string;\n name: string | null;\n tz: string;\n latitude: number | null;\n longitude: number | null;\n country: string | null;\n venues: ReadonlyArray<string>;\n}\n\nexport const STATIONS: ReadonlyArray<StationInfo> = [\n {\n code: \"CYYZ\",\n country: \"CA\",\n ghcnh_id: null,\n icao: \"CYYZ\",\n latitude: 43.6777,\n longitude: -79.6248,\n name: \"Toronto Pearson International\",\n tz: \"America/Toronto\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EDDB\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDB\",\n latitude: 52.3667,\n longitude: 13.5033,\n name: \"Berlin Brandenburg\",\n tz: \"Europe/Berlin\",\n venues: [],\n },\n {\n code: \"EDDF\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDF\",\n latitude: 50.0379,\n longitude: 8.5622,\n name: \"Frankfurt am Main\",\n tz: \"Europe/Berlin\",\n venues: [],\n },\n {\n code: \"EDDM\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDM\",\n latitude: 48.3538,\n longitude: 11.7861,\n name: \"Munich Franz Josef Strauss\",\n tz: \"Europe/Berlin\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EFHK\",\n country: \"FI\",\n ghcnh_id: null,\n icao: \"EFHK\",\n latitude: 60.3172,\n longitude: 24.9633,\n name: \"Helsinki-Vantaa\",\n tz: \"Europe/Helsinki\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EGKK\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGKK\",\n latitude: 51.1481,\n longitude: -0.1903,\n name: \"London Gatwick\",\n tz: \"Europe/London\",\n venues: [],\n },\n {\n code: \"EGLC\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGLC\",\n latitude: 51.5053,\n longitude: 0.0553,\n name: \"London City\",\n tz: \"Europe/London\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EGLL\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGLL\",\n latitude: 51.4706,\n longitude: -0.4619,\n name: \"London Heathrow\",\n tz: \"Europe/London\",\n venues: [],\n },\n {\n code: \"EHAM\",\n country: \"NL\",\n ghcnh_id: null,\n icao: \"EHAM\",\n latitude: 52.3086,\n longitude: 4.7639,\n name: \"Amsterdam Schiphol\",\n tz: \"Europe/Amsterdam\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EKCH\",\n country: \"DK\",\n ghcnh_id: null,\n icao: \"EKCH\",\n latitude: 55.6181,\n longitude: 12.6561,\n name: \"Copenhagen Kastrup\",\n tz: \"Europe/Copenhagen\",\n venues: [],\n },\n {\n code: \"EPWA\",\n country: \"PL\",\n ghcnh_id: null,\n icao: \"EPWA\",\n latitude: 52.1657,\n longitude: 20.9671,\n name: \"Warsaw Chopin\",\n tz: \"Europe/Warsaw\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ESSA\",\n country: \"SE\",\n ghcnh_id: null,\n icao: \"ESSA\",\n latitude: 59.6519,\n longitude: 17.9186,\n name: \"Stockholm Arlanda\",\n tz: \"Europe/Stockholm\",\n venues: [],\n },\n {\n code: \"FACT\",\n country: \"ZA\",\n ghcnh_id: null,\n icao: \"FACT\",\n latitude: -33.9648,\n longitude: 18.6017,\n name: \"Cape Town International\",\n tz: \"Africa/Johannesburg\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ATL\",\n country: \"US\",\n ghcnh_id: \"USW00013874\",\n icao: \"KATL\",\n latitude: 33.6407,\n longitude: -84.4277,\n name: \"Hartsfield-Jackson Atlanta International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"AUS\",\n country: \"US\",\n ghcnh_id: \"USW00013904\",\n icao: \"KAUS\",\n latitude: 30.1975,\n longitude: -97.6664,\n name: \"Austin-Bergstrom International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"BKF\",\n country: \"US\",\n ghcnh_id: \"USW00093067\",\n icao: \"KBKF\",\n latitude: 39.7019,\n longitude: -104.7517,\n name: \"Buckley Space Force Base (Denver)\",\n tz: \"America/Denver\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"BNA\",\n country: \"US\",\n ghcnh_id: \"USW00013897\",\n icao: \"KBNA\",\n latitude: 36.1245,\n longitude: -86.6782,\n name: \"Nashville International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"BOS\",\n country: \"US\",\n ghcnh_id: \"USW00014739\",\n icao: \"KBOS\",\n latitude: 42.3656,\n longitude: -71.0096,\n name: \"Boston Logan International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"CVG\",\n country: \"US\",\n ghcnh_id: \"USW00093814\",\n icao: \"KCVG\",\n latitude: 39.0488,\n longitude: -84.6678,\n name: \"Cincinnati/Northern Kentucky International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DAL\",\n country: \"US\",\n ghcnh_id: \"USW00013960\",\n icao: \"KDAL\",\n latitude: 32.8481,\n longitude: -96.8512,\n name: \"Dallas Love Field\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"DCA\",\n country: \"US\",\n ghcnh_id: \"USW00013743\",\n icao: \"KDCA\",\n latitude: 38.8512,\n longitude: -77.0402,\n name: \"Washington Reagan National\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DEN\",\n country: \"US\",\n ghcnh_id: \"USW00003017\",\n icao: \"KDEN\",\n latitude: 39.8561,\n longitude: -104.6737,\n name: \"Denver International\",\n tz: \"America/Denver\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DFW\",\n country: \"US\",\n ghcnh_id: \"USW00003927\",\n icao: \"KDFW\",\n latitude: 32.8998,\n longitude: -97.0403,\n name: \"Dallas-Fort Worth International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DTW\",\n country: \"US\",\n ghcnh_id: \"USW00094847\",\n icao: \"KDTW\",\n latitude: 42.2124,\n longitude: -83.3534,\n name: \"Detroit Metropolitan Wayne County\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"HOU\",\n country: \"US\",\n ghcnh_id: \"USW00012918\",\n icao: \"KHOU\",\n latitude: 29.6454,\n longitude: -95.2789,\n name: \"Houston Hobby\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"IAH\",\n country: \"US\",\n ghcnh_id: \"USW00012960\",\n icao: \"KIAH\",\n latitude: 29.9844,\n longitude: -95.3414,\n name: \"Houston George Bush Intercontinental\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LAS\",\n country: \"US\",\n ghcnh_id: \"USW00023169\",\n icao: \"KLAS\",\n latitude: 36.084,\n longitude: -115.1537,\n name: \"Harry Reid (McCarran) International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LAX\",\n country: \"US\",\n ghcnh_id: \"USW00023174\",\n icao: \"KLAX\",\n latitude: 33.9425,\n longitude: -118.4081,\n name: \"Los Angeles International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"LGA\",\n country: \"US\",\n ghcnh_id: \"USW00014732\",\n icao: \"KLGA\",\n latitude: 40.7772,\n longitude: -73.8726,\n name: \"New York LaGuardia\",\n tz: \"America/New_York\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MDW\",\n country: \"US\",\n ghcnh_id: \"USW00014819\",\n icao: \"KMDW\",\n latitude: 41.7868,\n longitude: -87.7522,\n name: \"Chicago Midway International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"MIA\",\n country: \"US\",\n ghcnh_id: \"USW00012839\",\n icao: \"KMIA\",\n latitude: 25.7959,\n longitude: -80.287,\n name: \"Miami International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"MSP\",\n country: \"US\",\n ghcnh_id: \"USW00014922\",\n icao: \"KMSP\",\n latitude: 44.8848,\n longitude: -93.2223,\n name: \"Minneapolis-St Paul International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"MSY\",\n country: \"US\",\n ghcnh_id: \"USW00012916\",\n icao: \"KMSY\",\n latitude: 29.9934,\n longitude: -90.258,\n name: \"New Orleans Louis Armstrong International\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"NYC\",\n country: \"US\",\n ghcnh_id: \"USW00094728\",\n icao: \"KNYC\",\n latitude: 40.7789,\n longitude: -73.9692,\n name: \"Central Park, New York\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"OKC\",\n country: \"US\",\n ghcnh_id: \"USW00013967\",\n icao: \"KOKC\",\n latitude: 35.3931,\n longitude: -97.6007,\n name: \"Oklahoma City Will Rogers World\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"ORD\",\n country: \"US\",\n ghcnh_id: \"USW00094846\",\n icao: \"KORD\",\n latitude: 41.9742,\n longitude: -87.9073,\n name: \"Chicago O'Hare International\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"PHL\",\n country: \"US\",\n ghcnh_id: \"USW00013739\",\n icao: \"KPHL\",\n latitude: 39.8721,\n longitude: -75.2411,\n name: \"Philadelphia International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"PHX\",\n country: \"US\",\n ghcnh_id: \"USW00023183\",\n icao: \"KPHX\",\n latitude: 33.4373,\n longitude: -112.0078,\n name: \"Phoenix Sky Harbor International\",\n tz: \"America/Phoenix\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"SAT\",\n country: \"US\",\n ghcnh_id: \"USW00012921\",\n icao: \"KSAT\",\n latitude: 29.5337,\n longitude: -98.4698,\n name: \"San Antonio International\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"SEA\",\n country: \"US\",\n ghcnh_id: \"USW00024233\",\n icao: \"KSEA\",\n latitude: 47.4502,\n longitude: -122.3088,\n name: \"Seattle-Tacoma International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"SFO\",\n country: \"US\",\n ghcnh_id: \"USW00023234\",\n icao: \"KSFO\",\n latitude: 37.6213,\n longitude: -122.379,\n name: \"San Francisco International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"SLC\",\n country: \"US\",\n ghcnh_id: \"USW00024127\",\n icao: \"KSLC\",\n latitude: 40.7884,\n longitude: -111.9778,\n name: \"Salt Lake City International\",\n tz: \"America/Denver\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LEBL\",\n country: \"ES\",\n ghcnh_id: null,\n icao: \"LEBL\",\n latitude: 41.2974,\n longitude: 2.0833,\n name: \"Barcelona El Prat\",\n tz: \"Europe/Madrid\",\n venues: [],\n },\n {\n code: \"LEMD\",\n country: \"ES\",\n ghcnh_id: null,\n icao: \"LEMD\",\n latitude: 40.4719,\n longitude: -3.5626,\n name: \"Madrid Barajas\",\n tz: \"Europe/Madrid\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LFPB\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPB\",\n latitude: 48.9694,\n longitude: 2.4414,\n name: \"Paris Le Bourget\",\n tz: \"Europe/Paris\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LFPG\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPG\",\n latitude: 49.0097,\n longitude: 2.5479,\n name: \"Paris Charles de Gaulle\",\n tz: \"Europe/Paris\",\n venues: [],\n },\n {\n code: \"LFPO\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPO\",\n latitude: 48.7233,\n longitude: 2.3794,\n name: \"Paris Orly\",\n tz: \"Europe/Paris\",\n venues: [],\n },\n {\n code: \"LIMC\",\n country: \"IT\",\n ghcnh_id: null,\n icao: \"LIMC\",\n latitude: 45.6306,\n longitude: 8.7281,\n name: \"Milan Malpensa\",\n tz: \"Europe/Rome\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LIRF\",\n country: \"IT\",\n ghcnh_id: null,\n icao: \"LIRF\",\n latitude: 41.8003,\n longitude: 12.2389,\n name: \"Rome Fiumicino\",\n tz: \"Europe/Rome\",\n venues: [],\n },\n {\n code: \"LLBG\",\n country: \"IL\",\n ghcnh_id: null,\n icao: \"LLBG\",\n latitude: 32.0114,\n longitude: 34.8867,\n name: \"Tel Aviv Ben Gurion\",\n tz: \"Asia/Jerusalem\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LOWW\",\n country: \"AT\",\n ghcnh_id: null,\n icao: \"LOWW\",\n latitude: 48.1103,\n longitude: 16.5697,\n name: \"Vienna International\",\n tz: \"Europe/Vienna\",\n venues: [],\n },\n {\n code: \"LSZH\",\n country: \"CH\",\n ghcnh_id: null,\n icao: \"LSZH\",\n latitude: 47.4647,\n longitude: 8.5492,\n name: \"Zurich\",\n tz: \"Europe/Zurich\",\n venues: [],\n },\n {\n code: \"LTAC\",\n country: \"TR\",\n ghcnh_id: null,\n icao: \"LTAC\",\n latitude: 40.1281,\n longitude: 32.9951,\n name: \"Ankara Esenboga\",\n tz: \"Europe/Istanbul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LTFM\",\n country: \"TR\",\n ghcnh_id: null,\n icao: \"LTFM\",\n latitude: 41.2753,\n longitude: 28.7519,\n name: \"Istanbul Airport\",\n tz: \"Europe/Istanbul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MMMX\",\n country: \"MX\",\n ghcnh_id: null,\n icao: \"MMMX\",\n latitude: 19.4363,\n longitude: -99.0721,\n name: \"Mexico City Benito Juarez International\",\n tz: \"America/Mexico_City\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MPMG\",\n country: \"PA\",\n ghcnh_id: null,\n icao: \"MPMG\",\n latitude: 8.9733,\n longitude: -79.5556,\n name: \"Panama City Marcos A. Gelabert (Albrook)\",\n tz: \"America/Panama\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"NZAA\",\n country: \"NZ\",\n ghcnh_id: null,\n icao: \"NZAA\",\n latitude: -37.0081,\n longitude: 174.7917,\n name: \"Auckland\",\n tz: \"Pacific/Auckland\",\n venues: [],\n },\n {\n code: \"NZWN\",\n country: \"NZ\",\n ghcnh_id: null,\n icao: \"NZWN\",\n latitude: -41.3272,\n longitude: 174.8053,\n name: \"Wellington\",\n tz: \"Pacific/Auckland\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OEJN\",\n country: \"SA\",\n ghcnh_id: null,\n icao: \"OEJN\",\n latitude: 21.6796,\n longitude: 39.1565,\n name: \"Jeddah King Abdulaziz International\",\n tz: \"Asia/Riyadh\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OERK\",\n country: \"SA\",\n ghcnh_id: null,\n icao: \"OERK\",\n latitude: 24.9576,\n longitude: 46.6988,\n name: \"Riyadh King Khalid International\",\n tz: \"Asia/Riyadh\",\n venues: [],\n },\n {\n code: \"OMDB\",\n country: \"AE\",\n ghcnh_id: null,\n icao: \"OMDB\",\n latitude: 25.2532,\n longitude: 55.3657,\n name: \"Dubai International\",\n tz: \"Asia/Dubai\",\n venues: [],\n },\n {\n code: \"OPKC\",\n country: \"PK\",\n ghcnh_id: null,\n icao: \"OPKC\",\n latitude: 24.9065,\n longitude: 67.1608,\n name: \"Karachi Jinnah International\",\n tz: \"Asia/Karachi\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OTHH\",\n country: \"QA\",\n ghcnh_id: null,\n icao: \"OTHH\",\n latitude: 25.2731,\n longitude: 51.608,\n name: \"Doha Hamad International\",\n tz: \"Asia/Qatar\",\n venues: [],\n },\n {\n code: \"RCSS\",\n country: \"TW\",\n ghcnh_id: null,\n icao: \"RCSS\",\n latitude: 25.0694,\n longitude: 121.5519,\n name: \"Taipei Songshan\",\n tz: \"Asia/Taipei\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RCTP\",\n country: \"TW\",\n ghcnh_id: null,\n icao: \"RCTP\",\n latitude: 25.0777,\n longitude: 121.2328,\n name: \"Taipei Taoyuan\",\n tz: \"Asia/Taipei\",\n venues: [],\n },\n {\n code: \"RJAA\",\n country: \"JP\",\n ghcnh_id: null,\n icao: \"RJAA\",\n latitude: 35.7647,\n longitude: 140.3864,\n name: \"Tokyo Narita\",\n tz: \"Asia/Tokyo\",\n venues: [],\n },\n {\n code: \"RJTT\",\n country: \"JP\",\n ghcnh_id: null,\n icao: \"RJTT\",\n latitude: 35.5522,\n longitude: 139.78,\n name: \"Tokyo Haneda\",\n tz: \"Asia/Tokyo\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RKPK\",\n country: \"KR\",\n ghcnh_id: null,\n icao: \"RKPK\",\n latitude: 35.1795,\n longitude: 128.9382,\n name: \"Busan Gimhae International\",\n tz: \"Asia/Seoul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RKSI\",\n country: \"KR\",\n ghcnh_id: null,\n icao: \"RKSI\",\n latitude: 37.4691,\n longitude: 126.4505,\n name: \"Seoul Incheon\",\n tz: \"Asia/Seoul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RPLL\",\n country: \"PH\",\n ghcnh_id: null,\n icao: \"RPLL\",\n latitude: 14.5086,\n longitude: 121.0197,\n name: \"Manila Ninoy Aquino International\",\n tz: \"Asia/Manila\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"SAEZ\",\n country: \"AR\",\n ghcnh_id: null,\n icao: \"SAEZ\",\n latitude: -34.8222,\n longitude: -58.5358,\n name: \"Buenos Aires Ezeiza\",\n tz: \"America/Argentina/Buenos_Aires\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"SBGR\",\n country: \"BR\",\n ghcnh_id: null,\n icao: \"SBGR\",\n latitude: -23.4356,\n longitude: -46.4731,\n name: \"São Paulo Guarulhos\",\n tz: \"America/Sao_Paulo\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"UUEE\",\n country: \"RU\",\n ghcnh_id: null,\n icao: \"UUEE\",\n latitude: 55.9728,\n longitude: 37.4147,\n name: \"Moscow Sheremetyevo\",\n tz: \"Europe/Moscow\",\n venues: [],\n },\n {\n code: \"UUWW\",\n country: \"RU\",\n ghcnh_id: null,\n icao: \"UUWW\",\n latitude: 55.5915,\n longitude: 37.2615,\n name: \"Moscow Vnukovo\",\n tz: \"Europe/Moscow\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"VABB\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VABB\",\n latitude: 19.0887,\n longitude: 72.8679,\n name: \"Mumbai Chhatrapati Shivaji\",\n tz: \"Asia/Kolkata\",\n venues: [],\n },\n {\n code: \"VHHH\",\n country: \"HK\",\n ghcnh_id: null,\n icao: \"VHHH\",\n latitude: 22.308,\n longitude: 113.9185,\n name: \"Hong Kong International\",\n tz: \"Asia/Hong_Kong\",\n venues: [],\n },\n {\n code: \"VIDP\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VIDP\",\n latitude: 28.5562,\n longitude: 77.1,\n name: \"Delhi Indira Gandhi\",\n tz: \"Asia/Kolkata\",\n venues: [],\n },\n {\n code: \"VILK\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VILK\",\n latitude: 26.7606,\n longitude: 80.8893,\n name: \"Lucknow Chaudhary Charan Singh International\",\n tz: \"Asia/Kolkata\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"VTBS\",\n country: \"TH\",\n ghcnh_id: null,\n icao: \"VTBS\",\n latitude: 13.69,\n longitude: 100.7501,\n name: \"Bangkok Suvarnabhumi\",\n tz: \"Asia/Bangkok\",\n venues: [],\n },\n {\n code: \"WMKK\",\n country: \"MY\",\n ghcnh_id: null,\n icao: \"WMKK\",\n latitude: 2.7456,\n longitude: 101.7099,\n name: \"Kuala Lumpur International\",\n tz: \"Asia/Kuala_Lumpur\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"WSSS\",\n country: \"SG\",\n ghcnh_id: null,\n icao: \"WSSS\",\n latitude: 1.3644,\n longitude: 103.9915,\n name: \"Singapore Changi\",\n tz: \"Asia/Singapore\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"YBBN\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YBBN\",\n latitude: -27.3842,\n longitude: 153.1175,\n name: \"Brisbane\",\n tz: \"Australia/Brisbane\",\n venues: [],\n },\n {\n code: \"YMML\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YMML\",\n latitude: -37.6733,\n longitude: 144.8433,\n name: \"Melbourne Tullamarine\",\n tz: \"Australia/Melbourne\",\n venues: [],\n },\n {\n code: \"YSSY\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YSSY\",\n latitude: -33.9461,\n longitude: 151.1772,\n name: \"Sydney Kingsford Smith\",\n tz: \"Australia/Sydney\",\n venues: [],\n },\n {\n code: \"ZBAA\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZBAA\",\n latitude: 40.0801,\n longitude: 116.5846,\n name: \"Beijing Capital\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZGGG\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZGGG\",\n latitude: 23.3924,\n longitude: 113.2988,\n name: \"Guangzhou Baiyun International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZGSZ\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZGSZ\",\n latitude: 22.6393,\n longitude: 113.8108,\n name: \"Shenzhen Bao'an International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZHCC\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZHCC\",\n latitude: 34.5197,\n longitude: 113.8408,\n name: \"Zhengzhou Xinzheng International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZHHH\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZHHH\",\n latitude: 30.7838,\n longitude: 114.2081,\n name: \"Wuhan Tianhe International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSJN\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSJN\",\n latitude: 36.8572,\n longitude: 117.2161,\n name: \"Jinan Yaoqiang International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSPD\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSPD\",\n latitude: 31.1443,\n longitude: 121.8083,\n name: \"Shanghai Pudong\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSQD\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSQD\",\n latitude: 36.3614,\n longitude: 120.0867,\n name: \"Qingdao Jiaodong International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZUCK\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZUCK\",\n latitude: 29.7192,\n longitude: 106.6417,\n name: \"Chongqing Jiangbei International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZUUU\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZUUU\",\n latitude: 30.5785,\n longitude: 103.9471,\n name: \"Chengdu Shuangliu International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n] as const;\n\nexport const STATION_BY_CODE: ReadonlyMap<string, StationInfo> = new Map<string, StationInfo>([\n [\"ATL\", STATIONS[13]!],\n [\"AUS\", STATIONS[14]!],\n [\"BKF\", STATIONS[15]!],\n [\"BNA\", STATIONS[16]!],\n [\"BOS\", STATIONS[17]!],\n [\"CVG\", STATIONS[18]!],\n [\"CYYZ\", STATIONS[0]!],\n [\"DAL\", STATIONS[19]!],\n [\"DCA\", STATIONS[20]!],\n [\"DEN\", STATIONS[21]!],\n [\"DFW\", STATIONS[22]!],\n [\"DTW\", STATIONS[23]!],\n [\"EDDB\", STATIONS[1]!],\n [\"EDDF\", STATIONS[2]!],\n [\"EDDM\", STATIONS[3]!],\n [\"EFHK\", STATIONS[4]!],\n [\"EGKK\", STATIONS[5]!],\n [\"EGLC\", STATIONS[6]!],\n [\"EGLL\", STATIONS[7]!],\n [\"EHAM\", STATIONS[8]!],\n [\"EKCH\", STATIONS[9]!],\n [\"EPWA\", STATIONS[10]!],\n [\"ESSA\", STATIONS[11]!],\n [\"FACT\", STATIONS[12]!],\n [\"HOU\", STATIONS[24]!],\n [\"IAH\", STATIONS[25]!],\n [\"LAS\", STATIONS[26]!],\n [\"LAX\", STATIONS[27]!],\n [\"LEBL\", STATIONS[42]!],\n [\"LEMD\", STATIONS[43]!],\n [\"LFPB\", STATIONS[44]!],\n [\"LFPG\", STATIONS[45]!],\n [\"LFPO\", STATIONS[46]!],\n [\"LGA\", STATIONS[28]!],\n [\"LIMC\", STATIONS[47]!],\n [\"LIRF\", STATIONS[48]!],\n [\"LLBG\", STATIONS[49]!],\n [\"LOWW\", STATIONS[50]!],\n [\"LSZH\", STATIONS[51]!],\n [\"LTAC\", STATIONS[52]!],\n [\"LTFM\", STATIONS[53]!],\n [\"MDW\", STATIONS[29]!],\n [\"MIA\", STATIONS[30]!],\n [\"MMMX\", STATIONS[54]!],\n [\"MPMG\", STATIONS[55]!],\n [\"MSP\", STATIONS[31]!],\n [\"MSY\", STATIONS[32]!],\n [\"NYC\", STATIONS[33]!],\n [\"NZAA\", STATIONS[56]!],\n [\"NZWN\", STATIONS[57]!],\n [\"OEJN\", STATIONS[58]!],\n [\"OERK\", STATIONS[59]!],\n [\"OKC\", STATIONS[34]!],\n [\"OMDB\", STATIONS[60]!],\n [\"OPKC\", STATIONS[61]!],\n [\"ORD\", STATIONS[35]!],\n [\"OTHH\", STATIONS[62]!],\n [\"PHL\", STATIONS[36]!],\n [\"PHX\", STATIONS[37]!],\n [\"RCSS\", STATIONS[63]!],\n [\"RCTP\", STATIONS[64]!],\n [\"RJAA\", STATIONS[65]!],\n [\"RJTT\", STATIONS[66]!],\n [\"RKPK\", STATIONS[67]!],\n [\"RKSI\", STATIONS[68]!],\n [\"RPLL\", STATIONS[69]!],\n [\"SAEZ\", STATIONS[70]!],\n [\"SAT\", STATIONS[38]!],\n [\"SBGR\", STATIONS[71]!],\n [\"SEA\", STATIONS[39]!],\n [\"SFO\", STATIONS[40]!],\n [\"SLC\", STATIONS[41]!],\n [\"UUEE\", STATIONS[72]!],\n [\"UUWW\", STATIONS[73]!],\n [\"VABB\", STATIONS[74]!],\n [\"VHHH\", STATIONS[75]!],\n [\"VIDP\", STATIONS[76]!],\n [\"VILK\", STATIONS[77]!],\n [\"VTBS\", STATIONS[78]!],\n [\"WMKK\", STATIONS[79]!],\n [\"WSSS\", STATIONS[80]!],\n [\"YBBN\", STATIONS[81]!],\n [\"YMML\", STATIONS[82]!],\n [\"YSSY\", STATIONS[83]!],\n [\"ZBAA\", STATIONS[84]!],\n [\"ZGGG\", STATIONS[85]!],\n [\"ZGSZ\", STATIONS[86]!],\n [\"ZHCC\", STATIONS[87]!],\n [\"ZHHH\", STATIONS[88]!],\n [\"ZSJN\", STATIONS[89]!],\n [\"ZSPD\", STATIONS[90]!],\n [\"ZSQD\", STATIONS[91]!],\n [\"ZUCK\", STATIONS[92]!],\n [\"ZUUU\", STATIONS[93]!],\n]);\n\nexport const STATION_BY_ICAO: ReadonlyMap<string, StationInfo> = new Map<string, StationInfo>([\n [\"CYYZ\", STATIONS[0]!],\n [\"EDDB\", STATIONS[1]!],\n [\"EDDF\", STATIONS[2]!],\n [\"EDDM\", STATIONS[3]!],\n [\"EFHK\", STATIONS[4]!],\n [\"EGKK\", STATIONS[5]!],\n [\"EGLC\", STATIONS[6]!],\n [\"EGLL\", STATIONS[7]!],\n [\"EHAM\", STATIONS[8]!],\n [\"EKCH\", STATIONS[9]!],\n [\"EPWA\", STATIONS[10]!],\n [\"ESSA\", STATIONS[11]!],\n [\"FACT\", STATIONS[12]!],\n [\"KATL\", STATIONS[13]!],\n [\"KAUS\", STATIONS[14]!],\n [\"KBKF\", STATIONS[15]!],\n [\"KBNA\", STATIONS[16]!],\n [\"KBOS\", STATIONS[17]!],\n [\"KCVG\", STATIONS[18]!],\n [\"KDAL\", STATIONS[19]!],\n [\"KDCA\", STATIONS[20]!],\n [\"KDEN\", STATIONS[21]!],\n [\"KDFW\", STATIONS[22]!],\n [\"KDTW\", STATIONS[23]!],\n [\"KHOU\", STATIONS[24]!],\n [\"KIAH\", STATIONS[25]!],\n [\"KLAS\", STATIONS[26]!],\n [\"KLAX\", STATIONS[27]!],\n [\"KLGA\", STATIONS[28]!],\n [\"KMDW\", STATIONS[29]!],\n [\"KMIA\", STATIONS[30]!],\n [\"KMSP\", STATIONS[31]!],\n [\"KMSY\", STATIONS[32]!],\n [\"KNYC\", STATIONS[33]!],\n [\"KOKC\", STATIONS[34]!],\n [\"KORD\", STATIONS[35]!],\n [\"KPHL\", STATIONS[36]!],\n [\"KPHX\", STATIONS[37]!],\n [\"KSAT\", STATIONS[38]!],\n [\"KSEA\", STATIONS[39]!],\n [\"KSFO\", STATIONS[40]!],\n [\"KSLC\", STATIONS[41]!],\n [\"LEBL\", STATIONS[42]!],\n [\"LEMD\", STATIONS[43]!],\n [\"LFPB\", STATIONS[44]!],\n [\"LFPG\", STATIONS[45]!],\n [\"LFPO\", STATIONS[46]!],\n [\"LIMC\", STATIONS[47]!],\n [\"LIRF\", STATIONS[48]!],\n [\"LLBG\", STATIONS[49]!],\n [\"LOWW\", STATIONS[50]!],\n [\"LSZH\", STATIONS[51]!],\n [\"LTAC\", STATIONS[52]!],\n [\"LTFM\", STATIONS[53]!],\n [\"MMMX\", STATIONS[54]!],\n [\"MPMG\", STATIONS[55]!],\n [\"NZAA\", STATIONS[56]!],\n [\"NZWN\", STATIONS[57]!],\n [\"OEJN\", STATIONS[58]!],\n [\"OERK\", STATIONS[59]!],\n [\"OMDB\", STATIONS[60]!],\n [\"OPKC\", STATIONS[61]!],\n [\"OTHH\", STATIONS[62]!],\n [\"RCSS\", STATIONS[63]!],\n [\"RCTP\", STATIONS[64]!],\n [\"RJAA\", STATIONS[65]!],\n [\"RJTT\", STATIONS[66]!],\n [\"RKPK\", STATIONS[67]!],\n [\"RKSI\", STATIONS[68]!],\n [\"RPLL\", STATIONS[69]!],\n [\"SAEZ\", STATIONS[70]!],\n [\"SBGR\", STATIONS[71]!],\n [\"UUEE\", STATIONS[72]!],\n [\"UUWW\", STATIONS[73]!],\n [\"VABB\", STATIONS[74]!],\n [\"VHHH\", STATIONS[75]!],\n [\"VIDP\", STATIONS[76]!],\n [\"VILK\", STATIONS[77]!],\n [\"VTBS\", STATIONS[78]!],\n [\"WMKK\", STATIONS[79]!],\n [\"WSSS\", STATIONS[80]!],\n [\"YBBN\", STATIONS[81]!],\n [\"YMML\", STATIONS[82]!],\n [\"YSSY\", STATIONS[83]!],\n [\"ZBAA\", STATIONS[84]!],\n [\"ZGGG\", STATIONS[85]!],\n [\"ZGSZ\", STATIONS[86]!],\n [\"ZHCC\", STATIONS[87]!],\n [\"ZHHH\", STATIONS[88]!],\n [\"ZSJN\", STATIONS[89]!],\n [\"ZSPD\", STATIONS[90]!],\n [\"ZSQD\", STATIONS[91]!],\n [\"ZUCK\", STATIONS[92]!],\n [\"ZUUU\", STATIONS[93]!],\n]);\n","// Snapshot math — settlement-window and market-close arithmetic.\n//\n// Ported from `packages/core/src/mostlyright/snapshot.py` and\n// `packages/core/src/mostlyright/_internal/_pairs.py:market_close_utc`.\n//\n// Key concepts:\n// - LOCAL STANDARD TIME (LST): station's standard UTC offset, DST ignored.\n// Kalshi NHIGH/NLOW contracts define the settlement window in LST.\n// - Settlement window: midnight-midnight LST for a given date.\n// During US daylight saving the clock window is 1:00 AM–1:00 AM next day\n// (EDT), but the UTC bounds are the same year-round.\n// - CLI publication delay: NWS issues the overnight final CLI ~04:00–10:00\n// UTC the day after observation. Default: 10 h after midnight LST.\n\n// ---------------------------------------------------------------------------\n// Station → IANA timezone database\n// ---------------------------------------------------------------------------\n//\n// Used to extract the LOCAL STANDARD TIME UTC offset via a January reference\n// moment. Ported from `mostlyright.snapshot._STATION_TZ`.\n\nexport const _STATION_TZ: Readonly<Record<string, string>> = Object.freeze({\n // Eastern (UTC-5 standard / UTC-4 DST)\n NYC: \"America/New_York\",\n JFK: \"America/New_York\",\n LGA: \"America/New_York\",\n EWR: \"America/New_York\",\n ATL: \"America/New_York\",\n BOS: \"America/New_York\",\n PHL: \"America/New_York\",\n DCA: \"America/New_York\",\n IAD: \"America/New_York\",\n BWI: \"America/New_York\",\n MIA: \"America/New_York\",\n MCO: \"America/New_York\",\n TPA: \"America/New_York\",\n CLT: \"America/New_York\",\n RDU: \"America/New_York\",\n CLE: \"America/New_York\",\n PIT: \"America/New_York\",\n BUF: \"America/New_York\",\n DTW: \"America/Detroit\",\n IND: \"America/Indiana/Indianapolis\",\n CVG: \"America/New_York\",\n CMH: \"America/New_York\",\n SYR: \"America/New_York\",\n ALB: \"America/New_York\",\n BTV: \"America/New_York\",\n ORF: \"America/New_York\",\n RIC: \"America/New_York\",\n GSO: \"America/New_York\",\n CHS: \"America/New_York\",\n SAV: \"America/New_York\",\n JAX: \"America/New_York\",\n RSW: \"America/New_York\",\n PBI: \"America/New_York\",\n FLL: \"America/New_York\",\n // Central (UTC-6 standard / UTC-5 DST)\n ORD: \"America/Chicago\",\n MDW: \"America/Chicago\",\n DFW: \"America/Chicago\",\n DAL: \"America/Chicago\",\n IAH: \"America/Chicago\",\n HOU: \"America/Chicago\",\n MSP: \"America/Chicago\",\n STL: \"America/Chicago\",\n MCI: \"America/Chicago\",\n OMA: \"America/Chicago\",\n MKE: \"America/Chicago\",\n MSY: \"America/Chicago\",\n MEM: \"America/Chicago\",\n BNA: \"America/Chicago\",\n OKC: \"America/Chicago\",\n SAT: \"America/Chicago\",\n AUS: \"America/Chicago\",\n DSM: \"America/Chicago\",\n TUL: \"America/Chicago\",\n LIT: \"America/Chicago\",\n BIR: \"America/Chicago\",\n SDF: \"America/Chicago\",\n HSV: \"America/Chicago\",\n BHM: \"America/Chicago\",\n MOB: \"America/Chicago\",\n BTR: \"America/Chicago\",\n SHV: \"America/Chicago\",\n // Mountain (UTC-7 standard / UTC-6 DST)\n DEN: \"America/Denver\",\n SLC: \"America/Denver\",\n ABQ: \"America/Denver\",\n BOI: \"America/Boise\",\n BZN: \"America/Denver\",\n GJT: \"America/Denver\",\n // Arizona: no DST (UTC-7 always)\n PHX: \"America/Phoenix\",\n TUS: \"America/Phoenix\",\n // Pacific (UTC-8 standard / UTC-7 DST)\n LAX: \"America/Los_Angeles\",\n SFO: \"America/Los_Angeles\",\n SEA: \"America/Los_Angeles\",\n PDX: \"America/Los_Angeles\",\n LAS: \"America/Los_Angeles\",\n SAN: \"America/Los_Angeles\",\n OAK: \"America/Los_Angeles\",\n SJC: \"America/Los_Angeles\",\n SMF: \"America/Los_Angeles\",\n RNO: \"America/Los_Angeles\",\n FAT: \"America/Los_Angeles\",\n SNA: \"America/Los_Angeles\",\n ONT: \"America/Los_Angeles\",\n BUR: \"America/Los_Angeles\",\n // Alaska (UTC-9 standard / UTC-8 DST)\n ANC: \"America/Anchorage\",\n FAI: \"America/Anchorage\",\n JNU: \"America/Juneau\",\n // Hawaii (UTC-10, no DST)\n HNL: \"Pacific/Honolulu\",\n OGG: \"Pacific/Honolulu\",\n KOA: \"Pacific/Honolulu\",\n // International (iter-6 H12): minimal set required to un-skip the\n // case-5 RJTT year-wrap cache behavior test. Python's\n // `mostlyright.snapshot._resolve_tz` falls back to the broader STATIONS\n // registry for intl ICAOs; the TS port hasn't ported that fallback\n // yet (tracked as TS-W6 — exhaustive intl-station tz coverage). This\n // entry closes H12 cleanly without pulling the whole STATIONS map in.\n // ICAO key (RJTT) — international stations have no 3-letter NWS code.\n // Tokyo Haneda — UTC+9 LST, no DST.\n RJTT: \"Asia/Tokyo\",\n});\n\n/** Reference UTC moment in January (no DST in Northern Hemisphere US). */\nexport const _JAN_REF = new Date(Date.UTC(2024, 0, 15, 12, 0, 0));\n\n/** NWS CLI typical publication delay: 10 h after midnight LST. */\nexport const _CLI_PUBLICATION_DELAY_HOURS = 10.0;\n\n/** Kalshi market typical close time (LST). */\nexport const _MARKET_CLOSE_HOUR_LST = 16;\nexport const _MARKET_CLOSE_MINUTE_LST = 30;\n\n// ---------------------------------------------------------------------------\n// LST offset extraction\n// ---------------------------------------------------------------------------\n\nconst _OFFSET_CACHE = new Map<string, number>();\n\n/**\n * Return the LOCAL STANDARD TIME UTC offset (in hours) for an IANA tz,\n * sampled from January 15 2024 12:00 UTC so the result is never affected\n * by DST in the Northern Hemisphere.\n *\n * Implementation: format `_JAN_REF` in the target tz via Intl.DateTimeFormat\n * and diff against the UTC formatted view to recover the offset.\n */\nexport function _lstOffsetHours(stationTz: string): number {\n const cached = _OFFSET_CACHE.get(stationTz);\n if (cached !== undefined) return cached;\n\n // We compute: localComponents(stationTz, _JAN_REF) − utcComponents(_JAN_REF).\n // The difference gives the tz offset in (hours). Negative for west of UTC.\n const fmt = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: stationTz,\n hour12: false,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n const parts = fmt.formatToParts(_JAN_REF);\n const get = (type: string): number => {\n const part = parts.find((p) => p.type === type);\n if (!part) {\n throw new Error(`Intl.DateTimeFormat missing ${type} for tz=${stationTz}`);\n }\n return Number(part.value);\n };\n\n const year = get(\"year\");\n const month = get(\"month\");\n const day = get(\"day\");\n let hour = get(\"hour\");\n const minute = get(\"minute\");\n const second = get(\"second\");\n // Some locales return hour \"24\" instead of \"00\" for midnight; normalize.\n if (hour === 24) hour = 0;\n\n // Compute the timezone's wall-clock for _JAN_REF treated as UTC.\n const localAsUtc = Date.UTC(year, month - 1, day, hour, minute, second);\n const offsetMs = localAsUtc - _JAN_REF.getTime();\n const offsetHours = offsetMs / 3_600_000;\n _OFFSET_CACHE.set(stationTz, offsetHours);\n return offsetHours;\n}\n\n// ---------------------------------------------------------------------------\n// Station code normalization + tz lookup\n// ---------------------------------------------------------------------------\n\nfunction _stationCodeNormalized(station: string): string {\n const s = station.trim().toUpperCase();\n if (s.length === 4 && s.startsWith(\"K\")) {\n return s.substring(1);\n }\n return s;\n}\n\n/**\n * Resolve a station code (NWS 3-letter, ICAO 4-letter) to an IANA tz string.\n * Honors `tzOverride` first, then the built-in `_STATION_TZ` map.\n * Throws if no tz can be resolved.\n */\nexport function _resolveStationTz(station: string, tzOverride?: string): string {\n if (tzOverride) return tzOverride;\n const code = _stationCodeNormalized(station);\n const tz = _STATION_TZ[code];\n if (tz) return tz;\n throw new Error(\n `Unknown station timezone: ${JSON.stringify(code)}. Add it to _STATION_TZ or pass tzOverride=\"America/...\".`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// as_of parsing\n// ---------------------------------------------------------------------------\n\nfunction _parseAsOf(asOf: Date | string): Date {\n if (asOf instanceof Date) {\n if (Number.isNaN(asOf.getTime())) {\n throw new Error(\"Invalid Date passed as asOf\");\n }\n return asOf;\n }\n let s = asOf.trim();\n // Python: bare ISO without tz → assume UTC.\n if (s.endsWith(\"Z\")) {\n // Date.parse handles \"Z\" natively.\n } else if (!/[+-]\\d{2}:?\\d{2}$/.test(s)) {\n // No timezone suffix — treat as UTC.\n s = `${s}Z`;\n }\n const ms = Date.parse(s);\n if (!Number.isFinite(ms)) {\n throw new Error(`Invalid as_of string: ${JSON.stringify(asOf)}`);\n }\n return new Date(ms);\n}\n\n// ---------------------------------------------------------------------------\n// Public surface\n// ---------------------------------------------------------------------------\n\nfunction _pad2(n: number): string {\n return n < 10 ? `0${n}` : `${n}`;\n}\n\nfunction _isoDate(year: number, month: number, day: number): string {\n return `${year}-${_pad2(month)}-${_pad2(day)}`;\n}\n\n/**\n * Return the Kalshi settlement date (YYYY-MM-DD LST) for a UTC moment.\n *\n * Kalshi NHIGH/NLOW contracts cover midnight–midnight LOCAL STANDARD TIME.\n * DST is ignored: the window is always fixed to the standard UTC offset.\n */\nexport function settlementDateFor(\n asOf: Date | string,\n station: string,\n tzOverride?: string,\n): string {\n const utcDt = _parseAsOf(asOf);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n // offsetHours is negative for US stations → lstMs < utcMs.\n const lstMs = utcDt.getTime() + offsetHours * 3_600_000;\n const lst = new Date(lstMs);\n // Use getUTC* because we already shifted the epoch by the LST offset.\n return _isoDate(lst.getUTCFullYear(), lst.getUTCMonth() + 1, lst.getUTCDate());\n}\n\n/**\n * Return UTC start/end of the Kalshi settlement window for a date.\n * The window is midnight-midnight LST, expressed in UTC.\n */\nexport function settlementWindowUtc(\n dateStr: string,\n station: string,\n tzOverride?: string,\n): [Date, Date] {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(dateStr);\n if (!match) {\n throw new Error(`Invalid ISO date for settlement window: ${JSON.stringify(dateStr)}`);\n }\n const [, yStr, mStr, dStr] = match;\n const year = Number(yStr);\n const month = Number(mStr);\n const day = Number(dStr);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n\n // midnight LST = 00:00 LST = (00:00 UTC) − offset (offset is negative)\n // Example: UTC-5 → midnight LST = 05:00 UTC.\n const midnightLstAsUtcMs = Date.UTC(year, month - 1, day, 0, 0, 0);\n const startMs = midnightLstAsUtcMs - offsetHours * 3_600_000;\n const start = new Date(startMs);\n const end = new Date(startMs + 24 * 3_600_000);\n return [start, end];\n}\n\n/**\n * Return the UTC time at which the NWS CLI for a date is expected to be\n * available. Default delay is 10 h after midnight LST on the next day.\n */\nexport function cliAvailableAt(\n dateStr: string,\n station: string,\n delayHours: number = _CLI_PUBLICATION_DELAY_HOURS,\n tzOverride?: string,\n): Date {\n const [, windowEnd] = settlementWindowUtc(dateStr, station, tzOverride);\n return new Date(windowEnd.getTime() + delayHours * 3_600_000);\n}\n\n/**\n * Return the UTC time of the Kalshi market close for a settlement date.\n * Kalshi NHIGH/NLOW markets close at 4:30 PM LST on the day of settlement.\n */\nexport function marketCloseUtc(dateStr: string, station: string, tzOverride?: string): Date {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(dateStr);\n if (!match) {\n throw new Error(`Invalid ISO date for market close: ${JSON.stringify(dateStr)}`);\n }\n const [, yStr, mStr, dStr] = match;\n const year = Number(yStr);\n const month = Number(mStr);\n const day = Number(dStr);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n\n const marketCloseAsUtcMs = Date.UTC(\n year,\n month - 1,\n day,\n _MARKET_CLOSE_HOUR_LST,\n _MARKET_CLOSE_MINUTE_LST,\n 0,\n );\n return new Date(marketCloseAsUtcMs - offsetHours * 3_600_000);\n}\n","// Cache-skip rule predicates — pure functions over inputs.\n//\n// Mirrors `packages/weather/src/mostlyright/weather/cache.py`:\n// - `_is_current_lst_month` / `_is_current_lst_year`\n// - `_is_live_source`\n//\n// Plus one TS-NEW addition required by TS-CACHE-02:\n// - `isWithinVolatileWindow` (30-day volatile-window check for archive\n// endpoints). Python's `cache.py` predates this rule; back-porting to\n// Python is tracked as a CROSS-SDK-SYNC parity ticket.\n//\n// All functions accept an optional `now: Date` test seam — production\n// callers pass `new Date()` once at the call site (plan 06).\n\nimport { STATION_BY_CODE, STATION_BY_ICAO } from \"../../data/generated/stations.js\";\nimport { _lstOffsetHours } from \"../../snapshot.js\";\n\n/** Resolve a station identifier (3-letter code OR 4-letter ICAO) to LST offset hours. */\nfunction _lstOffsetHoursFor(station: string): number {\n const upper = station.trim().toUpperCase();\n const byCode = STATION_BY_CODE.get(upper);\n if (byCode !== undefined) return _lstOffsetHours(byCode.tz);\n const byIcao = STATION_BY_ICAO.get(upper);\n if (byIcao !== undefined) return _lstOffsetHours(byIcao.tz);\n if (upper.length === 4 && upper.startsWith(\"K\")) {\n const stripped = upper.slice(1);\n const retry = STATION_BY_CODE.get(stripped);\n if (retry !== undefined) return _lstOffsetHours(retry.tz);\n }\n throw new RangeError(`unknown station: ${JSON.stringify(station)}`);\n}\n\n/**\n * Compute the station's current LST wall-clock as a UTC Date offset by the\n * LST hour shift. Use `getUTC*` to read fields (we already shifted the epoch).\n */\nfunction _nowLst(station: string, now: Date = new Date()): Date {\n const offsetHours = _lstOffsetHoursFor(station);\n return new Date(now.getTime() + offsetHours * 3_600_000);\n}\n\n/**\n * True iff `(year, month)` is the current LST month for `station`.\n *\n * Mirrors Python `_is_current_lst_month`. The current month is mutable\n * (observations still arriving) — caching it would serve stale data.\n */\nexport function shouldSkipCacheForCurrentLstMonth(\n station: string,\n year: number,\n month: number,\n now?: Date,\n): boolean {\n const lst = _nowLst(station, now);\n return lst.getUTCFullYear() === year && lst.getUTCMonth() + 1 === month;\n}\n\n/**\n * True iff `year` is the current LST year for `station`. Annual analog of\n * the monthly variant — gates the climate cache.\n */\nexport function shouldSkipCacheForCurrentLstYear(\n station: string,\n year: number,\n now?: Date,\n): boolean {\n const lst = _nowLst(station, now);\n return lst.getUTCFullYear() === year;\n}\n\n/**\n * True iff `(year, month)` is a **strictly past** UTC month relative to\n * `now` — i.e. cacheable on the strictest possible temporal axis.\n *\n * iter-12 C14: `shouldSkipCacheForCurrentLstMonth` and `isMonthVolatile`\n * (lives in `meta/src/research.ts`) only catch the *current* LST month\n * and the immediate post-month volatile tail. Both predicates return\n * false for months that lie in the FUTURE relative to `now`, or for the\n * current UTC month when the station's LST is still in the prior UTC\n * month (negative tz offsets near UTC midnight). An empty / partial\n * fetch for such a month would be persisted and later served as\n * \"complete.\" `isWritableMonth` is a stricter additional gate: it\n * requires the (year, month) to be lexicographically less than the\n * UTC current month, so neither future months nor the partial current\n * UTC month are ever cacheable — regardless of any station's LST.\n *\n * Mirrors Python `cache.py:_is_current_lst_month`'s implicit invariant\n * (Python paths use parquet-on-disk which can't be written for future\n * dates because the cache root never spawns those years). TS callers\n * MUST gate cache reads AND writes on this predicate before applying\n * the LST / volatile-window gates.\n */\nexport function isWritableMonth(year: number, month: number, now: Date): boolean {\n const nowYear = now.getUTCFullYear();\n const nowMonth = now.getUTCMonth() + 1; // 1-12\n if (year < nowYear) return true;\n if (year > nowYear) return false;\n return month < nowMonth;\n}\n\n/**\n * True iff `year` is a **strictly past** UTC year relative to `now` —\n * the annual analog of `isWritableMonth`.\n *\n * iter-12 C15: `shouldSkipCacheForCurrentLstYear` only catches the\n * current LST year. It misses (a) future years, which would silently\n * cache empty/incomplete data, and (b) the UTC Jan-1 boundary window\n * where the station's LST is still in the prior calendar year (negative\n * tz offsets) but the UTC year has already rolled over — without this\n * gate the new UTC year, which is mutable, could be written. Stricter\n * additional gate: require `year < now.getUTCFullYear()`. TS callers\n * MUST gate cache reads AND writes on this predicate before applying\n * the LST / volatile-window gates.\n */\nexport function isWritableYear(year: number, now: Date): boolean {\n return year < now.getUTCFullYear();\n}\n\n/**\n * True iff `source` ends with `.live`.\n *\n * Mirrors Python `_is_live_source` byte-equivalently — accepts null /\n * undefined / empty (returns false in all three cases).\n */\nexport function isLiveSource(source: string | null | undefined): boolean {\n return typeof source === \"string\" && source.length > 0 && source.endsWith(\".live\");\n}\n\n/**\n * **TS-NEW** addition per TS-CACHE-02: archive endpoints within `days` days\n * of `archiveAsOf` are treated as volatile (some sources amend their\n * published data for ~30 days post-event). NOT a Python port today — file\n * a CROSS-SDK-SYNC parity ticket if Python adopts it.\n *\n * Returns true iff `eventDate` falls within `[archiveAsOf - days, archiveAsOf]`\n * (inclusive at both endpoints — an event exactly `days` days before\n * `archiveAsOf` is still volatile and MUST be re-fetched).\n *\n * Events AFTER `archiveAsOf` are never volatile by this rule (deltaDays < 0).\n */\nexport function isWithinVolatileWindow(eventDate: string, archiveAsOf: string, days = 30): boolean {\n const e = Date.parse(`${eventDate}T00:00:00Z`);\n const a = Date.parse(`${archiveAsOf}T00:00:00Z`);\n if (!Number.isFinite(e) || !Number.isFinite(a)) {\n throw new RangeError(\n `invalid YYYY-MM-DD: eventDate=${JSON.stringify(eventDate)} archiveAsOf=${JSON.stringify(archiveAsOf)}`,\n );\n }\n const deltaDays = (a - e) / 86_400_000;\n return deltaDays >= 0 && deltaDays <= days;\n}\n","// Cache-key generators — pure functions producing the canonical key strings\n// `cacheKeyForObservations(station, year, month)` and\n// `cacheKeyForClimate(station, year)`. Matches the Python file-path zero-\n// padded month/year scheme (Python: `01.parquet`, TS: `:01`).\n//\n// Station is upper-cased but NOT validated here (validation is the\n// orchestrator's job; this module stays pure).\n//\n// iter-7 H13 / H14: an optional `source` segment was added to\n// `cacheKeyForObservations` so the multi-source observations cache\n// (IEM ASOS + GHCNh) does not collide on the same `(station, year, month)`\n// triplet. Python writes one parquet per month containing the merged\n// observations from all sources; the TS orchestrator caches per-source\n// chunks pre-merge (because the fetchers are independent paths) and\n// disambiguates with the source segment. Omitting `source` preserves the\n// legacy 3-arg key shape — useful for tests that don't care about source\n// (sentinel preloads, fixture replays) and matches the canonical\n// Python-parity contract for callers that have already pre-merged sources.\n\nconst MIN_YEAR = 1900;\nconst MAX_YEAR = 2100;\nconst SOURCE_RE = /^[a-z0-9_-]+$/;\n\n/**\n * Build the canonical observations cache key.\n *\n * Examples:\n * `cacheKeyForObservations(\"KNYC\", 2025, 1)` →\n * `\"mostlyright:v1:observations:KNYC:2025:01\"`.\n * `cacheKeyForObservations(\"KNYC\", 2025, 1, \"iem\")` →\n * `\"mostlyright:v1:observations:KNYC:2025:01:iem\"`.\n *\n * The `source` segment (optional, lowercase alphanumeric / hyphen /\n * underscore) namespaces per-source pre-merge chunks so IEM ASOS and\n * GHCNh writes for the same `(station, year, month)` do not collide.\n * Omit for back-compat (sentinel preloads, fixture replays).\n */\nexport function cacheKeyForObservations(\n station: string,\n year: number,\n month: number,\n source?: string,\n): string {\n if (!Number.isInteger(year) || year < MIN_YEAR || year > MAX_YEAR) {\n throw new RangeError(`year out of range: ${year}`);\n }\n if (!Number.isInteger(month) || month < 1 || month > 12) {\n throw new RangeError(`month out of range: ${month}`);\n }\n const yyyy = String(year).padStart(4, \"0\");\n const mm = String(month).padStart(2, \"0\");\n const base = `mostlyright:v1:observations:${station.toUpperCase()}:${yyyy}:${mm}`;\n if (source === undefined) return base;\n if (typeof source !== \"string\" || !SOURCE_RE.test(source)) {\n throw new RangeError(\n `source must match ${SOURCE_RE.source} (lowercase alnum / hyphen / underscore); got ${JSON.stringify(source)}`,\n );\n }\n return `${base}:${source}`;\n}\n\n/**\n * Build the canonical climate cache key (annual).\n *\n * Example: `cacheKeyForClimate(\"KNYC\", 2025)` →\n * `\"mostlyright:v1:climate:KNYC:2025\"`.\n */\nexport function cacheKeyForClimate(station: string, year: number): string {\n if (!Number.isInteger(year) || year < MIN_YEAR || year > MAX_YEAR) {\n throw new RangeError(`year out of range: ${year}`);\n }\n const yyyy = String(year).padStart(4, \"0\");\n return `mostlyright:v1:climate:${station.toUpperCase()}:${yyyy}`;\n}\n","// Browser/MV3 entry for @mostlyrightmd/core/internal/cache.\n//\n// Iter-8 H15: the iter-1/iter-2 fixes (dynamic `await import('./fs.js')`\n// behind a runtime feature-detect + dropping the FsStore re-export from\n// the barrel) eliminated STATIC references to FsStore from the cache\n// subbundle but NOT dynamic ones. esbuild, when bundling for browser/MV3\n// targets, still follows `await import(\"./fs.js\")` from `default.ts`\n// into the FsStore chunk and pulls `node:crypto`, `node:fs/promises`,\n// `node:os`, `node:path`, and `proper-lockfile` into the bundle —\n// breaking `pnpm size` for `packages-ts/meta/dist/index.mjs`.\n//\n// The architectural fix: package.json conditional exports route Node\n// consumers to `./index.ts` (this file's sibling — keeps FsStore via\n// dynamic import) and browser/MV3 consumers to THIS file. This file has\n// NO reference to `./fs.js` via ANY mechanism — static import, dynamic\n// import, or re-export. esbuild cannot follow what isn't there.\n//\n// Exports MUST mirror `index.ts` exactly, MINUS anything that references\n// FsStore. The runtime priority for `defaultCacheStore` here is:\n// 1. `typeof indexedDB !== \"undefined\"` → IndexedDBStore.\n// 2. else → MemoryStore.\n// (The FsStore branch is unreachable in browser/MV3 bundles by\n// construction — there's no `process.versions?.node` in a service\n// worker anyway, but more importantly the source code is absent so\n// esbuild's static analysis cannot drag the Node-only chunk in.)\n\nimport { IndexedDBStore } from \"./indexeddb.js\";\nimport { MemoryStore } from \"./memory.js\";\nimport { CACHE_SCHEMA_VERSION, type CacheStore } from \"./types.js\";\nimport { versionedCacheStore } from \"./versionedCacheStore.js\";\n\nexport type { CacheStore, CacheSetOptions, CacheEntry } from \"./types.js\";\nexport { lockKeyFor } from \"./types.js\";\nexport { MemoryStore } from \"./memory.js\";\nexport { IndexedDBStore, DB_NAME as INDEXEDDB_DB_NAME } from \"./indexeddb.js\";\nexport type { IndexedDBStoreOptions } from \"./indexeddb.js\";\nexport {\n shouldSkipCacheForCurrentLstMonth,\n shouldSkipCacheForCurrentLstYear,\n isLiveSource,\n isWithinVolatileWindow,\n isWritableMonth,\n isWritableYear,\n} from \"./skip-rules.js\";\nexport { cacheKeyForObservations, cacheKeyForClimate } from \"./keys.js\";\n// Phase 21 21-03 fix-iter-2 (ts-architect CRITICAL): browser entry must\n// also re-export the version adapter + canonical version constant so\n// downstream code that imports them via this path resolves to the same\n// symbols as the Node entry.\nexport {\n versionedCacheStore,\n CACHE_SCHEMA_VERSION as VERSIONED_CACHE_SCHEMA_VERSION,\n} from \"./versionedCacheStore.js\";\nexport { CACHE_SCHEMA_VERSION } from \"./types.js\";\n\n/**\n * Browser/MV3 variant of {@link defaultCacheStore}. Auto-detects the best\n * available CacheStore in a browser/edge environment:\n *\n * 1. IndexedDB present → {@link IndexedDBStore}\n * 2. else → {@link MemoryStore}\n *\n * Phase 21 21-03 fix-iter-2 (ts-architect CRITICAL): the concrete store\n * is wrapped in `versionedCacheStore(CACHE_SCHEMA_VERSION)` so pre-\n * Phase-18 entries silently miss. The Node entry already does this; the\n * browser entry was missed in iter-1 — browser/MV3 consumers were still\n * served stale cache rows. Fix is symmetric.\n *\n * Returns a NEW instance per call.\n *\n * Iter-8 H15: kept async so the signature matches the Node entry's\n * `defaultCacheStore` (which awaits a dynamic import). Callers that\n * `await defaultCacheStore()` work unchanged when the package.json\n * conditional exports flip them between entries.\n */\nexport async function defaultCacheStore(): Promise<CacheStore> {\n const inner = typeof indexedDB !== \"undefined\" ? new IndexedDBStore() : new MemoryStore();\n return versionedCacheStore(inner, CACHE_SCHEMA_VERSION);\n}\n","// Snapshot math — settlement-window and market-close arithmetic.\n//\n// Ported from `packages/core/src/mostlyright/snapshot.py` and\n// `packages/core/src/mostlyright/_internal/_pairs.py:market_close_utc`.\n//\n// Key concepts:\n// - LOCAL STANDARD TIME (LST): station's standard UTC offset, DST ignored.\n// Kalshi NHIGH/NLOW contracts define the settlement window in LST.\n// - Settlement window: midnight-midnight LST for a given date.\n// During US daylight saving the clock window is 1:00 AM–1:00 AM next day\n// (EDT), but the UTC bounds are the same year-round.\n// - CLI publication delay: NWS issues the overnight final CLI ~04:00–10:00\n// UTC the day after observation. Default: 10 h after midnight LST.\n\n// ---------------------------------------------------------------------------\n// Station → IANA timezone database\n// ---------------------------------------------------------------------------\n//\n// Used to extract the LOCAL STANDARD TIME UTC offset via a January reference\n// moment. Ported from `mostlyright.snapshot._STATION_TZ`.\n\nexport const _STATION_TZ: Readonly<Record<string, string>> = Object.freeze({\n // Eastern (UTC-5 standard / UTC-4 DST)\n NYC: \"America/New_York\",\n JFK: \"America/New_York\",\n LGA: \"America/New_York\",\n EWR: \"America/New_York\",\n ATL: \"America/New_York\",\n BOS: \"America/New_York\",\n PHL: \"America/New_York\",\n DCA: \"America/New_York\",\n IAD: \"America/New_York\",\n BWI: \"America/New_York\",\n MIA: \"America/New_York\",\n MCO: \"America/New_York\",\n TPA: \"America/New_York\",\n CLT: \"America/New_York\",\n RDU: \"America/New_York\",\n CLE: \"America/New_York\",\n PIT: \"America/New_York\",\n BUF: \"America/New_York\",\n DTW: \"America/Detroit\",\n IND: \"America/Indiana/Indianapolis\",\n CVG: \"America/New_York\",\n CMH: \"America/New_York\",\n SYR: \"America/New_York\",\n ALB: \"America/New_York\",\n BTV: \"America/New_York\",\n ORF: \"America/New_York\",\n RIC: \"America/New_York\",\n GSO: \"America/New_York\",\n CHS: \"America/New_York\",\n SAV: \"America/New_York\",\n JAX: \"America/New_York\",\n RSW: \"America/New_York\",\n PBI: \"America/New_York\",\n FLL: \"America/New_York\",\n // Central (UTC-6 standard / UTC-5 DST)\n ORD: \"America/Chicago\",\n MDW: \"America/Chicago\",\n DFW: \"America/Chicago\",\n DAL: \"America/Chicago\",\n IAH: \"America/Chicago\",\n HOU: \"America/Chicago\",\n MSP: \"America/Chicago\",\n STL: \"America/Chicago\",\n MCI: \"America/Chicago\",\n OMA: \"America/Chicago\",\n MKE: \"America/Chicago\",\n MSY: \"America/Chicago\",\n MEM: \"America/Chicago\",\n BNA: \"America/Chicago\",\n OKC: \"America/Chicago\",\n SAT: \"America/Chicago\",\n AUS: \"America/Chicago\",\n DSM: \"America/Chicago\",\n TUL: \"America/Chicago\",\n LIT: \"America/Chicago\",\n BIR: \"America/Chicago\",\n SDF: \"America/Chicago\",\n HSV: \"America/Chicago\",\n BHM: \"America/Chicago\",\n MOB: \"America/Chicago\",\n BTR: \"America/Chicago\",\n SHV: \"America/Chicago\",\n // Mountain (UTC-7 standard / UTC-6 DST)\n DEN: \"America/Denver\",\n SLC: \"America/Denver\",\n ABQ: \"America/Denver\",\n BOI: \"America/Boise\",\n BZN: \"America/Denver\",\n GJT: \"America/Denver\",\n // Arizona: no DST (UTC-7 always)\n PHX: \"America/Phoenix\",\n TUS: \"America/Phoenix\",\n // Pacific (UTC-8 standard / UTC-7 DST)\n LAX: \"America/Los_Angeles\",\n SFO: \"America/Los_Angeles\",\n SEA: \"America/Los_Angeles\",\n PDX: \"America/Los_Angeles\",\n LAS: \"America/Los_Angeles\",\n SAN: \"America/Los_Angeles\",\n OAK: \"America/Los_Angeles\",\n SJC: \"America/Los_Angeles\",\n SMF: \"America/Los_Angeles\",\n RNO: \"America/Los_Angeles\",\n FAT: \"America/Los_Angeles\",\n SNA: \"America/Los_Angeles\",\n ONT: \"America/Los_Angeles\",\n BUR: \"America/Los_Angeles\",\n // Alaska (UTC-9 standard / UTC-8 DST)\n ANC: \"America/Anchorage\",\n FAI: \"America/Anchorage\",\n JNU: \"America/Juneau\",\n // Hawaii (UTC-10, no DST)\n HNL: \"Pacific/Honolulu\",\n OGG: \"Pacific/Honolulu\",\n KOA: \"Pacific/Honolulu\",\n // International (iter-6 H12): minimal set required to un-skip the\n // case-5 RJTT year-wrap cache behavior test. Python's\n // `mostlyright.snapshot._resolve_tz` falls back to the broader STATIONS\n // registry for intl ICAOs; the TS port hasn't ported that fallback\n // yet (tracked as TS-W6 — exhaustive intl-station tz coverage). This\n // entry closes H12 cleanly without pulling the whole STATIONS map in.\n // ICAO key (RJTT) — international stations have no 3-letter NWS code.\n // Tokyo Haneda — UTC+9 LST, no DST.\n RJTT: \"Asia/Tokyo\",\n});\n\n/** Reference UTC moment in January (no DST in Northern Hemisphere US). */\nexport const _JAN_REF = new Date(Date.UTC(2024, 0, 15, 12, 0, 0));\n\n/** NWS CLI typical publication delay: 10 h after midnight LST. */\nexport const _CLI_PUBLICATION_DELAY_HOURS = 10.0;\n\n/** Kalshi market typical close time (LST). */\nexport const _MARKET_CLOSE_HOUR_LST = 16;\nexport const _MARKET_CLOSE_MINUTE_LST = 30;\n\n// ---------------------------------------------------------------------------\n// LST offset extraction\n// ---------------------------------------------------------------------------\n\nconst _OFFSET_CACHE = new Map<string, number>();\n\n/**\n * Return the LOCAL STANDARD TIME UTC offset (in hours) for an IANA tz,\n * sampled from January 15 2024 12:00 UTC so the result is never affected\n * by DST in the Northern Hemisphere.\n *\n * Implementation: format `_JAN_REF` in the target tz via Intl.DateTimeFormat\n * and diff against the UTC formatted view to recover the offset.\n */\nexport function _lstOffsetHours(stationTz: string): number {\n const cached = _OFFSET_CACHE.get(stationTz);\n if (cached !== undefined) return cached;\n\n // We compute: localComponents(stationTz, _JAN_REF) − utcComponents(_JAN_REF).\n // The difference gives the tz offset in (hours). Negative for west of UTC.\n const fmt = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: stationTz,\n hour12: false,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n const parts = fmt.formatToParts(_JAN_REF);\n const get = (type: string): number => {\n const part = parts.find((p) => p.type === type);\n if (!part) {\n throw new Error(`Intl.DateTimeFormat missing ${type} for tz=${stationTz}`);\n }\n return Number(part.value);\n };\n\n const year = get(\"year\");\n const month = get(\"month\");\n const day = get(\"day\");\n let hour = get(\"hour\");\n const minute = get(\"minute\");\n const second = get(\"second\");\n // Some locales return hour \"24\" instead of \"00\" for midnight; normalize.\n if (hour === 24) hour = 0;\n\n // Compute the timezone's wall-clock for _JAN_REF treated as UTC.\n const localAsUtc = Date.UTC(year, month - 1, day, hour, minute, second);\n const offsetMs = localAsUtc - _JAN_REF.getTime();\n const offsetHours = offsetMs / 3_600_000;\n _OFFSET_CACHE.set(stationTz, offsetHours);\n return offsetHours;\n}\n\n// ---------------------------------------------------------------------------\n// Station code normalization + tz lookup\n// ---------------------------------------------------------------------------\n\nfunction _stationCodeNormalized(station: string): string {\n const s = station.trim().toUpperCase();\n if (s.length === 4 && s.startsWith(\"K\")) {\n return s.substring(1);\n }\n return s;\n}\n\n/**\n * Resolve a station code (NWS 3-letter, ICAO 4-letter) to an IANA tz string.\n * Honors `tzOverride` first, then the built-in `_STATION_TZ` map.\n * Throws if no tz can be resolved.\n */\nexport function _resolveStationTz(station: string, tzOverride?: string): string {\n if (tzOverride) return tzOverride;\n const code = _stationCodeNormalized(station);\n const tz = _STATION_TZ[code];\n if (tz) return tz;\n throw new Error(\n `Unknown station timezone: ${JSON.stringify(code)}. Add it to _STATION_TZ or pass tzOverride=\"America/...\".`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// as_of parsing\n// ---------------------------------------------------------------------------\n\nfunction _parseAsOf(asOf: Date | string): Date {\n if (asOf instanceof Date) {\n if (Number.isNaN(asOf.getTime())) {\n throw new Error(\"Invalid Date passed as asOf\");\n }\n return asOf;\n }\n let s = asOf.trim();\n // Python: bare ISO without tz → assume UTC.\n if (s.endsWith(\"Z\")) {\n // Date.parse handles \"Z\" natively.\n } else if (!/[+-]\\d{2}:?\\d{2}$/.test(s)) {\n // No timezone suffix — treat as UTC.\n s = `${s}Z`;\n }\n const ms = Date.parse(s);\n if (!Number.isFinite(ms)) {\n throw new Error(`Invalid as_of string: ${JSON.stringify(asOf)}`);\n }\n return new Date(ms);\n}\n\n// ---------------------------------------------------------------------------\n// Public surface\n// ---------------------------------------------------------------------------\n\nfunction _pad2(n: number): string {\n return n < 10 ? `0${n}` : `${n}`;\n}\n\nfunction _isoDate(year: number, month: number, day: number): string {\n return `${year}-${_pad2(month)}-${_pad2(day)}`;\n}\n\n/**\n * Return the Kalshi settlement date (YYYY-MM-DD LST) for a UTC moment.\n *\n * Kalshi NHIGH/NLOW contracts cover midnight–midnight LOCAL STANDARD TIME.\n * DST is ignored: the window is always fixed to the standard UTC offset.\n */\nexport function settlementDateFor(\n asOf: Date | string,\n station: string,\n tzOverride?: string,\n): string {\n const utcDt = _parseAsOf(asOf);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n // offsetHours is negative for US stations → lstMs < utcMs.\n const lstMs = utcDt.getTime() + offsetHours * 3_600_000;\n const lst = new Date(lstMs);\n // Use getUTC* because we already shifted the epoch by the LST offset.\n return _isoDate(lst.getUTCFullYear(), lst.getUTCMonth() + 1, lst.getUTCDate());\n}\n\n/**\n * Return UTC start/end of the Kalshi settlement window for a date.\n * The window is midnight-midnight LST, expressed in UTC.\n */\nexport function settlementWindowUtc(\n dateStr: string,\n station: string,\n tzOverride?: string,\n): [Date, Date] {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(dateStr);\n if (!match) {\n throw new Error(`Invalid ISO date for settlement window: ${JSON.stringify(dateStr)}`);\n }\n const [, yStr, mStr, dStr] = match;\n const year = Number(yStr);\n const month = Number(mStr);\n const day = Number(dStr);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n\n // midnight LST = 00:00 LST = (00:00 UTC) − offset (offset is negative)\n // Example: UTC-5 → midnight LST = 05:00 UTC.\n const midnightLstAsUtcMs = Date.UTC(year, month - 1, day, 0, 0, 0);\n const startMs = midnightLstAsUtcMs - offsetHours * 3_600_000;\n const start = new Date(startMs);\n const end = new Date(startMs + 24 * 3_600_000);\n return [start, end];\n}\n\n/**\n * Return the UTC time at which the NWS CLI for a date is expected to be\n * available. Default delay is 10 h after midnight LST on the next day.\n */\nexport function cliAvailableAt(\n dateStr: string,\n station: string,\n delayHours: number = _CLI_PUBLICATION_DELAY_HOURS,\n tzOverride?: string,\n): Date {\n const [, windowEnd] = settlementWindowUtc(dateStr, station, tzOverride);\n return new Date(windowEnd.getTime() + delayHours * 3_600_000);\n}\n\n/**\n * Return the UTC time of the Kalshi market close for a settlement date.\n * Kalshi NHIGH/NLOW markets close at 4:30 PM LST on the day of settlement.\n */\nexport function marketCloseUtc(dateStr: string, station: string, tzOverride?: string): Date {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(dateStr);\n if (!match) {\n throw new Error(`Invalid ISO date for market close: ${JSON.stringify(dateStr)}`);\n }\n const [, yStr, mStr, dStr] = match;\n const year = Number(yStr);\n const month = Number(mStr);\n const day = Number(dStr);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n\n const marketCloseAsUtcMs = Date.UTC(\n year,\n month - 1,\n day,\n _MARKET_CLOSE_HOUR_LST,\n _MARKET_CLOSE_MINUTE_LST,\n 0,\n );\n return new Date(marketCloseAsUtcMs - offsetHours * 3_600_000);\n}\n","// buildPairs + _obsAggregates + pairsToRows — settlement-day row builder.\n//\n// Byte-faithful TS port of Python\n// `packages/core/src/mostlyright/_internal/_pairs.py::build_pairs` (Mode 1\n// subset — no forecast wiring; all fcst_* columns unconditionally null).\n//\n// The full Python `_select_best_run` / `_aggregate_fcst_temps_*` paths\n// (IEM MOS + Open-Meteo) are intentionally NOT ported here; forecast\n// support lands in TS-W5+. Same scope cut TS-W1 made for `research()`.\n//\n// Type strategy: structural `PairsObservationLike` + `PairsClimateLike`\n// interfaces. The full `Observation` (from weather/_parsers/awc.ts) and\n// `ClimateObservation` (from weather/_parsers/cli.ts) structurally\n// satisfy them — avoids a circular import + matches the Plan 04\n// `ObservationKey` discipline.\n\nimport { marketCloseUtc } from \"../snapshot.js\";\n\n/** Subset of fields `_obsAggregates` reads from each observation row. */\nexport interface PairsObservationLike {\n readonly temp_f?: number | null;\n readonly dewpoint_f?: number | null;\n readonly wind_speed_kt?: number | null;\n readonly wind_gust_kt?: number | null;\n readonly precip_1hr_inches?: number | null;\n}\n\n/** Subset of `ClimateObservation` fields buildPairs reads from each CLI row. */\nexport interface PairsClimateLike {\n readonly high_temp_f: number | null;\n readonly low_temp_f: number | null;\n readonly report_type: string;\n}\n\n/** Aggregated observation summary for one settlement day. */\nexport interface ObsAggregates {\n readonly obs_high_f: number | null;\n readonly obs_low_f: number | null;\n readonly obs_mean_f: number | null;\n readonly obs_mean_dewpoint_f: number | null;\n readonly obs_max_wind_kt: number | null;\n readonly obs_max_gust_kt: number | null;\n readonly obs_total_precip_in: number | null;\n readonly obs_count: number;\n}\n\n/**\n * One settlement-date row — 20 columns, byte-shape-equivalent to Python\n * `build_pairs_row` output. The `fcst_*` columns are unconditionally\n * `null` in TS-W2 (Mode 1 only — forecast wiring is TS-W5+).\n *\n * Object-key order is preserved verbatim so `JSON.stringify` produces\n * column ordering byte-stable across SDKs.\n */\nexport interface PairsRow {\n readonly date: string;\n readonly station: string;\n readonly cli_high_f: number | null;\n readonly cli_low_f: number | null;\n readonly cli_report_type: string | null;\n readonly obs_high_f: number | null;\n readonly obs_low_f: number | null;\n readonly obs_mean_f: number | null;\n readonly obs_mean_dewpoint_f: number | null;\n readonly obs_max_wind_kt: number | null;\n readonly obs_max_gust_kt: number | null;\n readonly obs_total_precip_in: number | null;\n readonly obs_count: number;\n readonly fcst_high_f: null;\n readonly fcst_low_f: null;\n readonly fcst_model: null;\n readonly fcst_issued_at: null;\n readonly fcst_pop_6hr_pct: null;\n readonly fcst_qpf_6hr_in: null;\n readonly market_close_utc: string;\n}\n\nexport interface BuildPairsOptions {\n /** Forwarded to `marketCloseUtc` (rare — used for synthetic test stations). */\n readonly tzOverride?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Aggregation helpers\n// ---------------------------------------------------------------------------\n\nfunction collectNonNull(\n obs: ReadonlyArray<PairsObservationLike>,\n key: keyof PairsObservationLike,\n): number[] {\n const out: number[] = [];\n for (const o of obs) {\n const v = o[key];\n if (typeof v === \"number\" && Number.isFinite(v)) out.push(v);\n }\n return out;\n}\n\nfunction meanOrNull(vs: number[]): number | null {\n if (vs.length === 0) return null;\n let s = 0;\n for (const v of vs) s += v;\n return s / vs.length;\n}\n\nfunction maxOrNull(vs: number[]): number | null {\n if (vs.length === 0) return null;\n let best = vs[0] as number;\n for (let i = 1; i < vs.length; i++) {\n const v = vs[i] as number;\n if (v > best) best = v;\n }\n return best;\n}\n\nfunction minOrNull(vs: number[]): number | null {\n if (vs.length === 0) return null;\n let best = vs[0] as number;\n for (let i = 1; i < vs.length; i++) {\n const v = vs[i] as number;\n if (v < best) best = v;\n }\n return best;\n}\n\nfunction sumOrNull(vs: number[]): number | null {\n if (vs.length === 0) return null;\n let s = 0;\n for (const v of vs) s += v;\n return s;\n}\n\n// ---------------------------------------------------------------------------\n// Aggregator\n// ---------------------------------------------------------------------------\n\n/**\n * Aggregate one day's observation rows into the 8-field `obs_*` summary.\n *\n * Rules (byte-faithful with Python `_obs_aggregates` at `_pairs.py:97-150`):\n * - obs_high_f / obs_low_f / obs_mean_f: max / min / arithmetic mean over\n * non-null `temp_f`. Mean-of-null-only → null.\n * - obs_mean_dewpoint_f: mean over non-null `dewpoint_f`.\n * - obs_max_wind_kt / obs_max_gust_kt: max over non-null wind/gust.\n * - obs_total_precip_in: sum over non-null precip; `null` if NO non-null\n * precip rows (mirrors Python `sum(precips) if precips else None`).\n * - obs_count: total row count, INCLUDING rows where every measure is null.\n *\n * Numeric-stability note: mean is non-associative for floats. Callers MUST\n * pass observations in a deterministic order to preserve byte-equivalent\n * float aggregation. Plan 06's research orchestrator sorts by\n * `(observed_at, source)` before calling this.\n *\n * Returns a `Object.freeze`-d aggregate with key order matching Python.\n */\nexport function _obsAggregates(observations: ReadonlyArray<PairsObservationLike>): ObsAggregates {\n if (observations.length === 0) {\n return Object.freeze({\n obs_high_f: null,\n obs_low_f: null,\n obs_mean_f: null,\n obs_mean_dewpoint_f: null,\n obs_max_wind_kt: null,\n obs_max_gust_kt: null,\n obs_total_precip_in: null,\n obs_count: 0,\n });\n }\n const temps = collectNonNull(observations, \"temp_f\");\n const dewps = collectNonNull(observations, \"dewpoint_f\");\n const winds = collectNonNull(observations, \"wind_speed_kt\");\n const gusts = collectNonNull(observations, \"wind_gust_kt\");\n const precips = collectNonNull(observations, \"precip_1hr_inches\");\n return Object.freeze({\n obs_high_f: maxOrNull(temps),\n obs_low_f: minOrNull(temps),\n obs_mean_f: meanOrNull(temps),\n obs_mean_dewpoint_f: meanOrNull(dewps),\n obs_max_wind_kt: maxOrNull(winds),\n obs_max_gust_kt: maxOrNull(gusts),\n obs_total_precip_in: sumOrNull(precips),\n obs_count: observations.length,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Row + batch builders\n// ---------------------------------------------------------------------------\n\n/**\n * Build one PairsRow for a given (station, date) from its observation +\n * climate inputs. Mode 1 only — fcst_* are unconditionally null.\n *\n * `market_close_utc` is formatted `YYYY-MM-DDTHH:MM:SSZ` (no milliseconds)\n * via `Date.toISOString().slice(0, 19) + \"Z\"` — mirrors Python strftime.\n */\nexport function buildPairsRow(\n dateStr: string,\n station: string,\n observations: ReadonlyArray<PairsObservationLike>,\n climate: PairsClimateLike | null,\n opts: BuildPairsOptions = {},\n): PairsRow {\n const obsAgg = _obsAggregates(observations);\n const closeUtc = marketCloseUtc(dateStr, station, opts.tzOverride);\n const closeIso = `${closeUtc.toISOString().slice(0, 19)}Z`;\n return Object.freeze({\n date: dateStr,\n station,\n cli_high_f: climate ? climate.high_temp_f : null,\n cli_low_f: climate ? climate.low_temp_f : null,\n cli_report_type: climate ? climate.report_type : null,\n obs_high_f: obsAgg.obs_high_f,\n obs_low_f: obsAgg.obs_low_f,\n obs_mean_f: obsAgg.obs_mean_f,\n obs_mean_dewpoint_f: obsAgg.obs_mean_dewpoint_f,\n obs_max_wind_kt: obsAgg.obs_max_wind_kt,\n obs_max_gust_kt: obsAgg.obs_max_gust_kt,\n obs_total_precip_in: obsAgg.obs_total_precip_in,\n obs_count: obsAgg.obs_count,\n fcst_high_f: null,\n fcst_low_f: null,\n fcst_model: null,\n fcst_issued_at: null,\n fcst_pop_6hr_pct: null,\n fcst_qpf_6hr_in: null,\n market_close_utc: closeIso,\n });\n}\n\n/**\n * Build PairsRows for every date in `dates` (input-order preserved).\n *\n * `observationsByDate[date]` and `climateByDate[date]` are looked up\n * defensively — missing keys are treated as empty obs / null climate.\n *\n * Returns a `Object.freeze`-d array.\n */\nexport function buildPairs(\n station: string,\n dates: ReadonlyArray<string>,\n observationsByDate: Readonly<Record<string, ReadonlyArray<PairsObservationLike>>>,\n climateByDate: Readonly<Record<string, PairsClimateLike | null>>,\n opts: BuildPairsOptions = {},\n): ReadonlyArray<PairsRow> {\n const out: PairsRow[] = [];\n for (const date of dates) {\n const obs = observationsByDate[date] ?? [];\n const climate = climateByDate[date] ?? null;\n out.push(buildPairsRow(date, station, obs, climate, opts));\n }\n return Object.freeze(out);\n}\n\n/**\n * Surface-parity alias of `buildPairs` output. Python's `pairs_to_dataframe`\n * converts the list[dict] into a pandas DataFrame indexed by date; TS has\n * no DataFrame, so this is identity. Exists for cross-SDK signature parity\n * per CROSS-SDK-SYNC.md.\n */\nexport function pairsToRows(rows: ReadonlyArray<PairsRow>): ReadonlyArray<PairsRow> {\n return rows;\n}\n","// Phase 21 21-01 — research() composable kwargs surface.\n//\n// Mirrors the Python `research(*, include_forecast, forecast_model,\n// forecast_models, qc, tz_override, sources, source, backend, return_type)`\n// signature so cross-SDK consumers see the same options shape on either\n// side. Per D-03, the `backend` and `return_type` kwargs are accepted in TS\n// but `backend=\"polars\"` raises DataAvailabilityError (no Polars in\n// browser/Node TS — Python only), and `return_type=\"wrapper\"` is a no-op\n// (TS returns plain object arrays; wrappers don't carry semantic value\n// without a separate frame backend to wrap).\n//\n// Snake_case keys match the Python wire format — a cross-language wrapper\n// can pass the same options dict to either SDK without case-folding.\n\n/**\n * Phase 21 21-01 composable-kwargs extension to `ResearchOptions`.\n *\n * All fields are optional; defaults match Python. Each field's runtime\n * validation lives in `validateResearchKwargs()` so the failure surface is\n * lockstep with Python's `_validate_research_kwargs`.\n */\nexport interface ResearchKwargsExtension {\n /**\n * When `true`, attach `fcst_*` columns. Phase 17 wired this end-to-end\n * for IEM MOS; other forecast models require Phase 21 follow-up plans.\n *\n * Default: `false`.\n */\n include_forecast?: boolean;\n\n /**\n * Single forecast model name (e.g. `\"gfs\"`, `\"nbm\"`). Requires\n * `include_forecast=true`. Mutually exclusive with `forecast_models`.\n */\n forecast_model?: string;\n\n /**\n * Multi-model forecast fan-out. Requires `include_forecast=true`.\n * Mutually exclusive with `forecast_model`.\n */\n forecast_models?: ReadonlyArray<string>;\n\n /**\n * When `true`, run QC passes and surface QC columns. Default `false`.\n */\n qc?: boolean;\n\n /**\n * IANA timezone override for stations not in the canonical registry.\n * Rarely needed for the 20-station Phase 1 set (all covered).\n */\n tz_override?: string;\n\n /**\n * D-03: accepted but no-op in TS. `backend=\"polars\"` raises\n * `DataAvailabilityError` (no Polars in browser/Node TS). Default\n * `\"pandas\"` mirrors Python; in TS this is informational only.\n */\n backend?: \"pandas\" | \"polars\";\n\n /**\n * D-03: accepted but no-op in TS. Python returns a `MostlyRightResult`\n * wrapper class when `return_type=\"wrapper\"`; TS returns plain object\n * arrays (no `.attrs` divergence to bridge), so the wrapper would carry\n * no extra signal.\n */\n return_type?: \"frame\" | \"wrapper\";\n}\n\n/**\n * Full set of keys accepted on `ResearchOptions` (any value not in this\n * set raises `TypeError` at validation time — defends against silent\n * typo acceptance).\n *\n * Pre-Phase-21 keys (camelCase + lowercase): the existing TS surface;\n * shipped via Phase 7 / 10 / 17 / 18.\n *\n * Phase 21 21-01 keys (snake_case): the new composable kwargs ported from\n * Python.\n */\nexport const KNOWN_RESEARCH_OPTION_KEYS: ReadonlySet<string> = new Set([\n // Pre-Phase-21 fetcher controls.\n \"signal\",\n \"awcHours\",\n \"iemPolitenessMs\",\n \"ghcnhPolitenessMs\",\n \"cliPolitenessMs\",\n \"now\",\n \"cache\",\n // Pre-Phase-21 selectors.\n \"city\",\n \"contract\",\n \"contracts\",\n \"stationOverride\",\n \"sources\",\n \"source\",\n \"includeTrades\",\n \"onWarning\",\n // Phase 21 21-01: Python-parity composable kwargs.\n \"include_forecast\",\n \"forecast_model\",\n \"forecast_models\",\n \"qc\",\n \"tz_override\",\n \"backend\",\n \"return_type\",\n]);\n\n/**\n * Phase 21 21-01: runtime kwarg validation for `research()`. Lockstep with\n * Python `_validate_research_kwargs`. Throws `TypeError` (NOT\n * `DataAvailabilityError`) for the same reasons Python raises `TypeError`:\n *\n * 1. Unknown option key (silent typo defense).\n * 2. `sources` and `source` are mutually exclusive.\n * 3. `forecast_model` and `forecast_models` are mutually exclusive.\n * 4. `forecast_model`/`forecast_models` require `include_forecast=true`.\n *\n * The `backend=\"polars\"` rejection lives in the calling code, NOT here —\n * it raises `DataAvailabilityError(reason: \"model_unavailable\")` per D-03\n * to match Python's `SourceUnavailableError` shape lockstep.\n */\nexport function validateResearchKwargs(opts: Readonly<Record<string, unknown>>): void {\n // Phase 21 21-09 fix-iter-1 (codex+ts-architect HIGH): \"provided\" must\n // mean the same thing on both sides of the JSON wire. Python `is not\n // None` treats both `null` (from JSON) and absent keys as ABSENT; TS\n // historically used `!== undefined`, which treats explicit `null` as\n // PRESENT — round-tripping a Python `None` through JSON as `null` would\n // falsely trigger a mutually-exclusive TypeError in TS only. Match\n // Python by treating both `null` and `undefined` as absent (`!= null`).\n const present = (v: unknown): boolean => v !== undefined && v !== null;\n\n // (1) Unknown-key defense. Catches typos like `inclide_forecast` that\n // would otherwise silently no-op.\n for (const key of Object.keys(opts)) {\n if (!KNOWN_RESEARCH_OPTION_KEYS.has(key)) {\n throw new TypeError(\n `research(): unknown option key ${JSON.stringify(key)}. ` +\n `Valid keys: ${[...KNOWN_RESEARCH_OPTION_KEYS].sort().join(\", \")}`,\n );\n }\n }\n\n // (2) sources / source mutually exclusive.\n if (present(opts.sources) && present(opts.source)) {\n throw new TypeError(\n \"research(): sources= and source= are mutually exclusive — \" +\n \"use `sources=` for the LIVE_V1 multi-source selector or \" +\n \"`source=` for a single-source query, not both\",\n );\n }\n\n // (3) forecast_model / forecast_models mutually exclusive.\n if (present(opts.forecast_model) && present(opts.forecast_models)) {\n throw new TypeError(\n \"research(): forecast_model= and forecast_models= are mutually exclusive — \" +\n \"use `forecast_models=` for multi-model fan-out or `forecast_model=` for \" +\n \"a single model, not both\",\n );\n }\n\n // (4) forecast_model / forecast_models require include_forecast=true.\n const wantsForecast = present(opts.forecast_model) || present(opts.forecast_models);\n if (wantsForecast && opts.include_forecast !== true) {\n throw new TypeError(\n \"research(): forecast_model=/forecast_models= require include_forecast=true; \" +\n \"the model filter is otherwise silently ignored\",\n );\n }\n}\n","// Phase 21 21-01 additions: composable kwargs ported from Python.\n// `research()` orchestrator — TS-W2 multi-source Mode 1 join.\n//\n// Wires all four observation sources (AWC live, IEM ASOS archive, GHCNh\n// archive, IEM CLI climate) into the canonical `PairsRow` shape via\n// mergeObservations + mergeClimate + buildPairs. Mode 1 only — all\n// `fcst_*` columns are unconditionally null in this phase.\n//\n// Lives in `packages-ts/meta/` so `@mostlyrightmd/core` stays dep-free; this\n// orchestrator imports from both core (snapshot math + station table +\n// merge + pairs) and weather (4 fetchers + 4 parsers).\n//\n// W2 scope: AWC + IEM ASOS + GHCNh + CLI; no cache (TS-W3), no Mode 2\n// (TS-W4), no forecast (TS-W5+), no parallel prefetch (TS-W3+). Fetches\n// are sequential — fine for the parity gate; performance work is later.\n\nimport {\n DataAvailabilityError,\n NotFoundError,\n STATION_BY_CODE,\n STATION_BY_ICAO,\n settlementDateFor,\n} from \"@mostlyrightmd/core\";\nimport {\n type CacheStore,\n cacheKeyForClimate,\n cacheKeyForObservations,\n defaultCacheStore,\n isLiveSource,\n isWithinVolatileWindow,\n isWritableMonth,\n isWritableYear,\n shouldSkipCacheForCurrentLstMonth,\n shouldSkipCacheForCurrentLstYear,\n} from \"@mostlyrightmd/core/internal/cache\";\nimport { mergeClimate, mergeObservations } from \"@mostlyrightmd/core/internal/merge\";\nimport {\n type PairsClimateLike,\n type PairsObservationLike,\n type PairsRow,\n buildPairs,\n} from \"@mostlyrightmd/core/internal/pairs\";\nimport {\n type ClimateObservation,\n type Observation,\n awcToObservation,\n downloadCliRange,\n downloadGhcnh,\n downloadIemAsos,\n fetchAwcMetars,\n parseCliResponse,\n parseGhcnhPsv,\n parseIemCsv,\n} from \"@mostlyrightmd/weather\";\nimport { type ResearchKwargsExtension, validateResearchKwargs } from \"./research.types.js\";\n\n// Re-export PairsRow so callers can `import { research, type PairsRow } from \"mostlyright\"`.\nexport type { PairsRow } from \"@mostlyrightmd/core/internal/pairs\";\n\nconst AWC_MAX_HOURS = 168;\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface ResearchOptions extends ResearchKwargsExtension {\n /** Forward to all underlying fetchers; aborts the whole pipeline. */\n signal?: AbortSignal;\n /** AWC lookback window in hours. Default 168 (AWC max). Clamped by the fetcher. */\n awcHours?: number;\n /** Polite-delay (ms) between successive IEM ASOS year chunks. Default 1000. */\n iemPolitenessMs?: number;\n /** Polite-delay (ms) between successive GHCNh year requests. Default 1000. */\n ghcnhPolitenessMs?: number;\n /** Polite-delay (ms) between successive CLI year requests. Default 1000. */\n cliPolitenessMs?: number;\n /**\n * Reference clock for the AWC-window overlap check (test-only seam).\n * Defaults to `new Date()`. Pass an override to force-include AWC for\n * historical date ranges in unit tests.\n */\n now?: Date;\n /**\n * Pluggable cache backend (TS-W3). When omitted, uses\n * `defaultCacheStore()` (auto-detects IndexedDB → FsStore → MemoryStore).\n * Pass `null` to opt out of caching entirely.\n */\n cache?: CacheStore | null;\n\n // ── Phase 10: composable selectors (mutually exclusive with station). ──\n //\n // Per the Phase 10 v0.2 scope, the validation surface is shipped on\n // both Python and TS; the multi-station / multi-issuer JOIN +\n // trade-attachment is deferred to v0.3. Passing any of the three\n // selectors below currently throws a clear NotImplementedError-like\n // error pointing callers at `discover()` + the station= path until\n // v0.3 lands.\n\n /** Cross-issuer city selector. Returns rows for every station that any\n * issuer settles against (Kalshi + Polymarket + denylist backstops). */\n city?: string;\n /** Single-contract selector. Format: `\"<issuer>:<id>\"` (e.g.\n * `\"kalshi:KXHIGHNYC-25MAY26-T79\"`). Auto-resolves to the contract's\n * canonical settlement station via the Phase 8 catalog. */\n contract?: string;\n /** Multi-contract selector for basis-trade research. */\n contracts?: ReadonlyArray<string>;\n /** Override the contract's canonical settlement station. Emits a\n * StationOverrideWarning via `onWarning?`; output row carries\n * `settlementMismatch: true`. Only valid with `contract` selector. */\n stationOverride?: string;\n /** Mode 1 source subset — dedupe within. Mutually exclusive with `source`. */\n sources?: ReadonlyArray<string>;\n /** Mode 2 single-source pin — error on mismatch. Mutually exclusive with `sources`. */\n source?: string;\n /** Attach per-issuer trade timeseries via @mostlyrightmd/markets/trades.\n * Requires `contract` or `contracts`. */\n includeTrades?: boolean;\n /** Callback receiving Phase 10 StationOverrideWarning (no `warnings.warn()`\n * analogue in JS). */\n onWarning?: (w: import(\"./compose.js\").StationOverrideWarning) => void;\n}\n\n/**\n * Resolve the cache from opts. `null` means opt-out (returns null).\n *\n * Iter-1 H3: `defaultCacheStore()` is now async (FsStore loaded via\n * dynamic import behind a Node feature-detect). Caller already runs\n * inside `research()`'s async path, so awaiting here is free.\n */\nasync function resolveCache(opts: ResearchOptions): Promise<CacheStore | null> {\n if (opts.cache === null) return null;\n if (opts.cache !== undefined) return opts.cache;\n return await defaultCacheStore();\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\ninterface ResolvedStation {\n readonly code: string;\n readonly icao: string;\n readonly tz: string;\n readonly country: string | null;\n readonly ghcnhId: string | null;\n}\n\nfunction normalizeStation(input: string): ResolvedStation {\n const raw = input.trim().toUpperCase();\n if (raw.length === 0) {\n throw new Error(\"station must be a non-empty string\");\n }\n const byIcao = STATION_BY_ICAO.get(raw);\n if (byIcao !== undefined) {\n if (byIcao.code === null) {\n throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);\n }\n return {\n code: byIcao.code,\n icao: byIcao.icao,\n tz: byIcao.tz,\n country: byIcao.country,\n ghcnhId: byIcao.ghcnh_id,\n };\n }\n const byCode = STATION_BY_CODE.get(raw);\n if (byCode !== undefined) {\n if (byCode.code === null) {\n throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);\n }\n return {\n code: byCode.code,\n icao: byCode.icao,\n tz: byCode.tz,\n country: byCode.country,\n ghcnhId: byCode.ghcnh_id,\n };\n }\n if (raw.startsWith(\"K\") && raw.length === 4) {\n const stripped = raw.slice(1);\n const retry = STATION_BY_CODE.get(stripped);\n if (retry !== undefined && retry.code !== null) {\n return {\n code: retry.code,\n icao: retry.icao,\n tz: retry.tz,\n country: retry.country,\n ghcnhId: retry.ghcnh_id,\n };\n }\n }\n throw new Error(\n `unknown station ${JSON.stringify(input)} — not found in STATION_BY_CODE or STATION_BY_ICAO`,\n );\n}\n\nfunction parseIsoDate(s: string): Date {\n if (!DATE_RE.test(s)) {\n throw new Error(`expected YYYY-MM-DD, got ${JSON.stringify(s)}`);\n }\n const [yStr, mStr, dStr] = s.split(\"-\");\n const year = Number(yStr);\n const month = Number(mStr);\n const day = Number(dStr);\n const ms = Date.UTC(year, month - 1, day);\n const d = new Date(ms);\n if (d.getUTCFullYear() !== year || d.getUTCMonth() !== month - 1 || d.getUTCDate() !== day) {\n throw new Error(`invalid calendar date ${JSON.stringify(s)}`);\n }\n return d;\n}\n\nfunction formatDate(d: Date): string {\n const y = d.getUTCFullYear();\n const m = d.getUTCMonth() + 1;\n const day = d.getUTCDate();\n const mm = m < 10 ? `0${m}` : `${m}`;\n const dd = day < 10 ? `0${day}` : `${day}`;\n return `${y}-${mm}-${dd}`;\n}\n\nfunction buildDateList(fromDate: string, toDate: string): ReadonlyArray<string> {\n const from = parseIsoDate(fromDate);\n const to = parseIsoDate(toDate);\n if (from.getTime() > to.getTime()) {\n throw new Error(`fromDate (${fromDate}) must be <= toDate (${toDate})`);\n }\n const dates: string[] = [];\n for (let cursor = from.getTime(); cursor <= to.getTime(); cursor += 24 * 3_600_000) {\n dates.push(formatDate(new Date(cursor)));\n }\n return dates;\n}\n\n/** Plus-one-day in UTC. Used to extend the upper bound so the final LST\n * settlement window's pre-midnight UTC tail observations are captured. */\nfunction plusOneDay(isoDate: string): string {\n const d = parseIsoDate(isoDate);\n return formatDate(new Date(d.getTime() + 24 * 3_600_000));\n}\n\n/** US stations only — GHCNh PSV archive is US-only. International stations\n * have `ghcnh_id: null` AND `country !== \"US\"` in the TS codegen. */\nfunction isUsStation(station: ResolvedStation): boolean {\n return station.country === \"US\";\n}\n\n/** Returns true if any date in `[fromDate, toDate]` is within `hours` of `now`.\n * Mirrors Python `_month_overlaps_awc_window` semantics — defensive\n * short-circuit so we don't hit AWC for purely historical windows. */\nfunction anyDateOverlapsAwc(toDate: string, hours: number, now: Date): boolean {\n const to = parseIsoDate(toDate);\n // Window includes the END of toDate (LST close), so add 24h to the upper bound.\n const toEndMs = to.getTime() + 24 * 3_600_000;\n const nowMs = now.getTime();\n const cutoffMs = nowMs - hours * 3_600_000;\n return toEndMs >= cutoffMs;\n}\n\nfunction observedSettlementDate(observedAt: string, station: string): string | null {\n const ms = Date.parse(observedAt);\n if (!Number.isFinite(ms)) return null;\n try {\n return settlementDateFor(new Date(ms), station);\n } catch {\n return null;\n }\n}\n\n/** Lexicographic-on-`observed_at` sort, stable in `source`. Ensures\n * byte-equivalent float aggregation in `_obsAggregates` (mean is\n * non-associative for floats). */\nfunction sortByObservedAtThenSource(rows: ReadonlyArray<Observation>): Observation[] {\n return [...rows].sort((a, b) => {\n if (a.observed_at < b.observed_at) return -1;\n if (a.observed_at > b.observed_at) return 1;\n if (a.source < b.source) return -1;\n if (a.source > b.source) return 1;\n return 0;\n });\n}\n\n/**\n * True iff the end-of-year ISO date for `year` falls inside the 30-day\n * volatile amendment window relative to `now`. Used to gate archive\n * cache reads/writes for IEM ASOS yearly chunks AND IEM CLI yearly\n * chunks (iter-5 H9). Rationale: rows from a year whose 12-31 boundary\n * is within 30 days of \"now\" may still be amended upstream; caching\n * them would persist soon-to-be-stale values.\n *\n * For `year` strictly less than the current calendar year of `now`, the\n * 12-31 boundary is well past 30 days back → returns false (cacheable).\n * For `year` equal to the current LST year, the year-end is in the\n * future relative to `now` → predicate returns false (the per-year\n * current-LST-year gate handles that case first and is still required).\n * The window only fires for the immediate-post-year window — exactly\n * the case where freshly-archived rows are most likely to be revised.\n */\nfunction isYearVolatile(year: number, now: Date): boolean {\n const yearEnd = `${String(year).padStart(4, \"0\")}-12-31`;\n return isWithinVolatileWindow(yearEnd, formatDate(now), 30);\n}\n\n/**\n * Last calendar day of `(year, month)`. Used as archive-as-of for the\n * per-month volatile-window gate (iter-7 H13). Returns YYYY-MM-DD.\n */\nfunction lastDayOfMonth(year: number, month: number): string {\n // UTC math: day 0 of (month+1) === last day of (month).\n const d = new Date(Date.UTC(year, month, 0));\n return formatDate(d);\n}\n\n/**\n * True iff the end-of-month ISO date for `(year, month)` falls inside the\n * 30-day volatile amendment window relative to `now`. Per-month analog of\n * `isYearVolatile`, used to gate the per-month observations cache\n * (iter-7 H13). Rationale: rows from a month whose final day is within\n * 30 days of \"now\" may still be amended upstream; caching them would\n * persist soon-to-be-stale values. The window only fires for the\n * immediate-post-month window — exactly the case where freshly-archived\n * rows are most likely to be revised.\n */\nfunction isMonthVolatile(year: number, month: number, now: Date): boolean {\n return isWithinVolatileWindow(lastDayOfMonth(year, month), formatDate(now), 30);\n}\n\n/**\n * Enumerate `[year, month]` pairs that overlap `[fromIsoDate, toIsoDate]`\n * (inclusive on both ends). Used by the per-month observations cache\n * (iter-7 H13). Returns pairs in chronological order. Validates the\n * range; throws on inverted input.\n */\nfunction monthsInRange(\n fromIsoDate: string,\n toIsoDate: string,\n): ReadonlyArray<readonly [number, number]> {\n const from = parseIsoDate(fromIsoDate);\n const to = parseIsoDate(toIsoDate);\n if (from.getTime() > to.getTime()) {\n throw new Error(`fromDate (${fromIsoDate}) must be <= toDate (${toIsoDate})`);\n }\n const pairs: Array<readonly [number, number]> = [];\n let y = from.getUTCFullYear();\n let m = from.getUTCMonth() + 1; // 1-12\n const endY = to.getUTCFullYear();\n const endM = to.getUTCMonth() + 1;\n while (y < endY || (y === endY && m <= endM)) {\n pairs.push([y, m]);\n m += 1;\n if (m > 12) {\n m = 1;\n y += 1;\n }\n }\n return pairs;\n}\n\n/**\n * Fetch CLI climate per-year with read-through cache. Yearly chunks are\n * cached at `cacheKeyForClimate(code, year)`. Skip rules:\n * - Current LST year — mutable, never cached.\n * - 30-day volatile amendment window (iter-5 H9) — chunks whose\n * year-end is within 30 days of `now` MUST be re-fetched. The\n * window only fires for the year immediately preceding \"now\"\n * once the calendar rolls over.\n * - Live source (`.live`) — never cached (CLI is archive `iem.cli` →\n * this never fires today; defensive for future).\n *\n * iter-6 C12: cache failures must NEVER discard the in-memory rows.\n * `cache.get` failures degrade to a live fetch (the intent — read-through\n * is a perf optimization, not a correctness requirement). `cache.set`\n * failures AFTER a successful fetch+parse MUST log and continue —\n * persisting to the cache is a best-effort side effect, never a reason\n * to drop already-fetched climate data. The previous broad try/catch in\n * the caller swallowed cache.set throws as \"no CLI data,\" silently\n * corrupting research rows with null cli_* fields.\n */\nasync function fetchCliWithCache(\n fetchIcao: string,\n cacheCode: string,\n fromYear: number,\n toYear: number,\n opts: ResearchOptions,\n cache: CacheStore | null,\n now: Date,\n): Promise<ClimateObservation[]> {\n const acc: ClimateObservation[] = [];\n for (let year = fromYear; year <= toYear; year++) {\n // iter-12 C15: `isWritableYear` is the strictest temporal gate.\n // Any year that isn't STRICTLY in the past UTC-wise (future years\n // or the current UTC year, including the UTC Jan-1 boundary window\n // where a negative-offset station's LST is still in the prior year)\n // is never cacheable — regardless of LST or volatile-window logic.\n // Force a live fetch and skip both reads AND writes for non-writable\n // years.\n const writable = isWritableYear(year, now);\n const skipCurrentYear = shouldSkipCacheForCurrentLstYear(cacheCode, year, now);\n // iter-5 H9: the 30-day volatile amendment window MUST also block\n // cache reads — a hit served from inside the window would re-serve\n // soon-to-be-amended rows. Always prefer a fresh fetch when the\n // window is active.\n const skipVolatile = isYearVolatile(year, now);\n const skip = !writable || skipCurrentYear || skipVolatile;\n\n // --- Cache read (best-effort) -------------------------------------\n // iter-6 C12: a `cache.get` failure must not abort the per-year\n // chunk — fall through to the live fetch. A transient backend hiccup\n // is no reason to refuse climate data we can still fetch fresh.\n if (cache !== null && !skip) {\n let cached: ClimateObservation[] | null = null;\n try {\n cached = await cache.get<ClimateObservation[]>(cacheKeyForClimate(cacheCode, year));\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] CLI cache.get failed for code=${cacheCode} year=${year}; falling back to live fetch:`,\n cacheErr,\n );\n }\n if (cached !== null) {\n acc.push(...cached);\n continue;\n }\n }\n\n // --- Live fetch + parse (errors here ARE fatal to this chunk) -----\n // Abort propagates; other errors bubble to the caller's try/catch\n // which degrades to \"no CLI data\" for the affected years. This is\n // the existing behavior — DO NOT widen the catch to include cache\n // writes (see below).\n const cliOpts: { signal?: AbortSignal; politenessMs?: number } = {};\n if (opts.signal !== undefined) cliOpts.signal = opts.signal;\n if (opts.cliPolitenessMs !== undefined) cliOpts.politenessMs = opts.cliPolitenessMs;\n const cliRaw = await downloadCliRange(fetchIcao, year, year, cliOpts);\n const parsed = parseCliResponse(cliRaw, cacheCode);\n acc.push(...parsed);\n\n // --- Cache write (best-effort, AFTER rows are accumulated) --------\n // iter-6 C12: `cache.set` MUST be wrapped in its own try/catch so a\n // transient write failure cannot discard already-fetched rows. The\n // previous code put cache.set inside the caller's broad CLI try/catch,\n // which silently degraded write failures to \"no climate data\" —\n // returning research rows with null cli_* fields. That's silent data\n // corruption; this guard prevents it.\n const sample = parsed[0]?.source;\n if (cache !== null && !skip && !isLiveSource(sample)) {\n try {\n await cache.set(cacheKeyForClimate(cacheCode, year), parsed);\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] CLI cache.set failed for code=${cacheCode} year=${year}; in-memory rows preserved:`,\n cacheErr,\n );\n }\n }\n }\n return acc;\n}\n\n/**\n * Fetch IEM ASOS observations per-month with read-through cache.\n *\n * iter-7 H13: this previously cached at YEAR granularity using a sentinel\n * `:01:rt=N` key, violating the Python TS-CACHE-02 per-month contract.\n * The Python `read_cache(station, year, month)` / `write_cache(...)`\n * surface uses `(station, year, month)` triplets — one parquet per month\n * containing the merged METAR+SPECI slice. This helper now matches that\n * contract:\n *\n * 1. Enumerate `(year, month)` pairs overlapping the queried range.\n * 2. For each pair, attempt a per-month cache read using the\n * source-namespaced key `cacheKeyForObservations(station, year,\n * month, \"iem\")`.\n * 3. On cache miss, fetch the full year (single IEM HTTP request for\n * `report_type=3` + one for `=4`) — IEM ASOS is yearly-chunked at\n * the source — then partition parsed rows by `(year, month)` and\n * filter back to the requested month (mirrors Python research.py\n * L267-269 month-boundary filter).\n * 4. Apply per-MONTH skip rules:\n * - `shouldSkipCacheForCurrentLstMonth(station, year, month, now)` —\n * mutable current month; never written.\n * - `isMonthVolatile(year, month, now)` — 30-day amendment window\n * gate (iter-5 H9 / iter-7 H13). Within the window, both read\n * AND write are skipped (IEM may publish late-arriving METARs\n * or corrections).\n * 5. Write-through fires only when neither skip rule trips; otherwise\n * the month's rows are returned in-memory but never persisted.\n *\n * Per-year fetch results are cached in a local `yearCache` Map so multiple\n * months within the same year share one HTTP round-trip — this is the\n * critical perf invariant from the previous implementation, preserved\n * across the granularity change.\n *\n * iter-6 C12: mirrors `fetchCliWithCache`'s split-try pattern — cache\n * `get` / `set` failures are logged but never discard the in-memory\n * rows. A cache backend hiccup must not silently drop observations\n * that were successfully fetched + parsed.\n */\nasync function fetchIemAsosWithCache(\n stationCode: string,\n _fromYear: number,\n _extendedToYear: number,\n fromDate: string,\n extendedTo: string,\n opts: ResearchOptions,\n cache: CacheStore | null,\n now: Date,\n): Promise<Observation[]> {\n void _fromYear;\n void _extendedToYear;\n const acc: Observation[] = [];\n\n // Per-call memoization: avoid re-fetching the same (year, reportType)\n // when multiple months in the same year miss the cache.\n const yearByReportType = new Map<string, Observation[]>();\n\n async function fetchYearOnce(year: number, reportType: 3 | 4): Promise<Observation[]> {\n const memoKey = `${year}:${reportType}`;\n const cached = yearByReportType.get(memoKey);\n if (cached !== undefined) return cached;\n const iemOpts: { reportType: 3 | 4; politenessMs: number; signal?: AbortSignal } = {\n reportType,\n politenessMs: opts.iemPolitenessMs ?? 1000,\n };\n if (opts.signal !== undefined) iemOpts.signal = opts.signal;\n const chunks = await downloadIemAsos(stationCode, `${year}-01-01`, `${year}-12-31`, iemOpts);\n const fetched: Observation[] = [];\n for (const chunk of chunks) {\n const parsed = parseIemCsv(chunk.csv, {\n observationTypeOverride: reportType === 3 ? \"METAR\" : \"SPECI\",\n });\n fetched.push(...parsed);\n }\n yearByReportType.set(memoKey, fetched);\n return fetched;\n }\n\n function filterMonth(\n rows: ReadonlyArray<Observation>,\n year: number,\n month: number,\n ): Observation[] {\n const yyyy = String(year).padStart(4, \"0\");\n const mm = String(month).padStart(2, \"0\");\n const prefix = `${yyyy}-${mm}-`;\n const out: Observation[] = [];\n for (const r of rows) {\n if (r.observed_at.startsWith(prefix)) out.push(r);\n }\n return out;\n }\n\n const pairs = monthsInRange(fromDate, extendedTo);\n for (const [year, month] of pairs) {\n const cacheKey = cacheKeyForObservations(stationCode, year, month, \"iem\");\n // iter-12 C14: `isWritableMonth` is the strictest temporal gate.\n // Any month that isn't STRICTLY in the past UTC-wise (future months\n // or the current UTC month, including the UTC-rollover tail where\n // LST is still in the prior UTC month) is never cacheable —\n // regardless of LST or volatile-window logic. Force a live fetch\n // and skip both reads AND writes for non-writable months.\n const writable = isWritableMonth(year, month, now);\n const skipCurrentMonth = shouldSkipCacheForCurrentLstMonth(stationCode, year, month, now);\n const skipVolatile = isMonthVolatile(year, month, now);\n const skipCache = !writable || skipCurrentMonth || skipVolatile;\n\n // --- Cache read (best-effort) -------------------------------------\n // iter-6 C12: a `cache.get` failure must not abort the month — fall\n // through to the live fetch. The cached value combines METAR+SPECI\n // (single per-month entry), so a hit yields both report types.\n let monthRows: Observation[] | null = null;\n if (cache !== null && !skipCache) {\n try {\n const cached = await cache.get<Observation[]>(cacheKey);\n if (cached !== null) monthRows = cached;\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] IEM ASOS cache.get failed for key=${cacheKey}; falling back to live fetch:`,\n cacheErr,\n );\n }\n }\n\n if (monthRows === null) {\n // --- Live fetch + parse (errors here propagate to the caller) ---\n // Fetch both report types for the year (memoized) and partition\n // to this month. Combining METAR+SPECI matches the Python contract\n // (write_cache receives one merged list per month).\n const metar = await fetchYearOnce(year, 3);\n const speci = await fetchYearOnce(year, 4);\n const monthMetar = filterMonth(metar, year, month);\n const monthSpeci = filterMonth(speci, year, month);\n monthRows = [...monthMetar, ...monthSpeci];\n\n // --- Cache write (best-effort, AFTER rows are accumulated) ------\n // iter-6 C12: `cache.set` failures MUST NOT propagate — a\n // transient write failure cannot be allowed to discard rows that\n // were just successfully fetched + parsed. Log and continue; the\n // in-memory `monthRows` is appended to `acc` below regardless.\n const sample = monthRows[0]?.source;\n if (cache !== null && !skipCache && !isLiveSource(sample)) {\n try {\n await cache.set(cacheKey, monthRows);\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] IEM ASOS cache.set failed for key=${cacheKey}; in-memory rows preserved:`,\n cacheErr,\n );\n }\n }\n }\n\n for (const obs of monthRows) {\n const obsDate = obs.observed_at.slice(0, 10);\n if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);\n }\n }\n return acc;\n}\n\n/**\n * Fetch GHCNh archive observations per-month with read-through cache.\n *\n * iter-7 H14: previously the GHCNh path called `downloadGhcnhRange` on\n * every `research()` invocation and never touched the cache. TS-W3\n * requires GHCNh chunks to be cacheable just like IEM ASOS — this helper\n * applies the same per-month contract as `fetchIemAsosWithCache`:\n *\n * 1. Enumerate `(year, month)` pairs overlapping the queried range.\n * 2. For each pair, attempt a per-month cache read using the source-\n * namespaced key `cacheKeyForObservations(station, year, month,\n * \"ghcnh\")`. The `\"ghcnh\"` source segment prevents collision with\n * IEM ASOS writes for the same `(station, year, month)` triplet\n * (iter-7 H13 introduced `\"iem\"` namespacing).\n * 3. On cache miss, fetch the full year via `downloadGhcnh` (single\n * PSV per station-year — NCEI's archive is yearly-chunked at the\n * source) — memoized within the helper so multiple months in the\n * same year share one HTTP round-trip.\n * 4. Per-month skip rules: `shouldSkipCacheForCurrentLstMonth` +\n * `isMonthVolatile` (30-day amendment window). NCEI republishes\n * `GHCNh_<id>_<YEAR>.psv` as new months land, so the same skip\n * logic the IEM helper uses applies here.\n * 5. 404-as-no-data: a `NotFoundError` from `downloadGhcnh` means NCEI\n * has no archive for this station-year (typical for recent partial\n * years or pre-1973 stations). We memoize an empty year and treat\n * every month as cache-eligible-but-empty. The Python range fetcher\n * silently swallows 404 too (research.py L160-166 logs + continues).\n *\n * iter-6 C12: mirrors the split-try pattern — cache `get` / `set`\n * failures are logged but never discard the in-memory rows.\n */\nasync function fetchGhcnhWithCache(\n stationCode: string,\n ghcnhId: string,\n fromDate: string,\n extendedTo: string,\n opts: ResearchOptions,\n cache: CacheStore | null,\n now: Date,\n): Promise<Observation[]> {\n const acc: Observation[] = [];\n\n // Per-call memoization: avoid re-fetching the same year when multiple\n // months in the same year miss the cache. `null` sentinel records a 404\n // (no data) so subsequent months in that year skip the HTTP call too.\n const yearCache = new Map<number, ReadonlyArray<Observation>>();\n\n async function fetchYearOnce(year: number): Promise<ReadonlyArray<Observation>> {\n const cached = yearCache.get(year);\n if (cached !== undefined) return cached;\n const ghcnhOpts: { signal?: AbortSignal } = {};\n if (opts.signal !== undefined) ghcnhOpts.signal = opts.signal;\n let parsed: ReadonlyArray<Observation>;\n try {\n const yr = await downloadGhcnh(ghcnhId, year, ghcnhOpts);\n parsed = parseGhcnhPsv(yr.psv);\n } catch (err) {\n if (err instanceof NotFoundError) {\n // NCEI 404 → no data for this station-year. Mirrors the\n // `downloadGhcnhRange` swallow-404 behavior; memoize empty so\n // subsequent months in this year don't re-hit NCEI.\n parsed = [];\n } else {\n throw err;\n }\n }\n yearCache.set(year, parsed);\n return parsed;\n }\n\n function filterMonth(\n rows: ReadonlyArray<Observation>,\n year: number,\n month: number,\n ): Observation[] {\n const yyyy = String(year).padStart(4, \"0\");\n const mm = String(month).padStart(2, \"0\");\n const prefix = `${yyyy}-${mm}-`;\n const out: Observation[] = [];\n for (const r of rows) {\n if (r.observed_at.startsWith(prefix) && r.station_code === stationCode) out.push(r);\n }\n return out;\n }\n\n const pairs = monthsInRange(fromDate, extendedTo);\n for (const [year, month] of pairs) {\n const cacheKey = cacheKeyForObservations(stationCode, year, month, \"ghcnh\");\n // iter-12 C14: stricter additional temporal gate — see the matching\n // comment in `fetchIemAsosWithCache`. NCEI's archive can return\n // empty data for not-yet-published months; we must NEVER persist a\n // not-strictly-past UTC month as if it were complete.\n const writable = isWritableMonth(year, month, now);\n const skipCurrentMonth = shouldSkipCacheForCurrentLstMonth(stationCode, year, month, now);\n const skipVolatile = isMonthVolatile(year, month, now);\n const skipCache = !writable || skipCurrentMonth || skipVolatile;\n\n // --- Cache read (best-effort) -------------------------------------\n let monthRows: Observation[] | null = null;\n if (cache !== null && !skipCache) {\n try {\n const cached = await cache.get<Observation[]>(cacheKey);\n if (cached !== null) monthRows = cached;\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] GHCNh cache.get failed for key=${cacheKey}; falling back to live fetch:`,\n cacheErr,\n );\n }\n }\n\n if (monthRows === null) {\n // --- Live fetch + parse (errors here propagate to the caller) ---\n const yearRows = await fetchYearOnce(year);\n monthRows = filterMonth(yearRows, year, month);\n\n // --- Cache write (best-effort, AFTER rows are accumulated) ------\n // iter-6 C12: `cache.set` failures MUST NOT propagate. Even an\n // empty month list is written when the year was successfully\n // fetched — it pins the \"no observations for this month\" fact so\n // the next call doesn't re-fetch the year just to discover nothing.\n const sample = monthRows[0]?.source;\n if (cache !== null && !skipCache && !isLiveSource(sample)) {\n try {\n await cache.set(cacheKey, monthRows);\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] GHCNh cache.set failed for key=${cacheKey}; in-memory rows preserved:`,\n cacheErr,\n );\n }\n }\n }\n\n for (const obs of monthRows) {\n const obsDate = obs.observed_at.slice(0, 10);\n if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);\n }\n }\n return acc;\n}\n\n// ---------------------------------------------------------------------------\n// Public surface\n// ---------------------------------------------------------------------------\n\n/**\n * Build daily research rows for a station + date window.\n *\n * @param station NWS 3-letter code (e.g. \"NYC\") OR 4-letter ICAO (e.g. \"KNYC\").\n * @param fromDate Inclusive start date, ISO YYYY-MM-DD (LST).\n * @param toDate Inclusive end date, ISO YYYY-MM-DD (LST).\n * @param opts See {@link ResearchOptions}.\n *\n * Returns an immutable array of frozen {@link PairsRow}s — one per LST day\n * in `[fromDate, toDate]`. Each row carries:\n * - `cli_*` populated from IEM CLI (final preferred per `mergeClimate`).\n * - `obs_*` daily aggregates over the 3-source merged observations\n * (AWC > IEM > GHCNh per `mergeObservations`).\n * - `fcst_*` unconditionally null (Mode 1).\n * - `market_close_utc` formatted `YYYY-MM-DDTHH:MM:SSZ`.\n *\n * Throws on unknown station, malformed dates, or fromDate > toDate.\n * AbortSignal propagates from underlying fetchers.\n */\nexport async function research(\n station: string,\n fromDate: string,\n toDate: string,\n opts: ResearchOptions = {},\n): Promise<ReadonlyArray<PairsRow>> {\n // ── Phase 21 21-01 kwarg validation (lockstep with Python) ───────────\n //\n // Surfaces unknown-key typos, mutually-exclusive misuse (sources/source,\n // forecast_model/forecast_models), and silent-no-op cases\n // (forecast_model without include_forecast) up-front. Mirrors the Python\n // `_validate_research_kwargs` guard.\n validateResearchKwargs(opts as Readonly<Record<string, unknown>>);\n\n // Phase 21 21-01 D-03: `backend=\"polars\"` is accepted-for-surface but\n // rejected at runtime since TS has no Polars equivalent (bundle-size\n // killer in browser; not viable in Node either without the optional\n // native binding). DataAvailabilityError(reason=\"model_unavailable\")\n // matches the cross-SDK typed-exception contract from 21-09.\n if (opts.backend === \"polars\") {\n throw new DataAvailabilityError({\n reason: \"model_unavailable\",\n hint:\n \"polars backend not available in TypeScript SDK; use Python \" +\n '(mostlyrightmd) for backend=\"polars\". TS returns plain object arrays.',\n source: \"research.backend\",\n });\n }\n // `return_type=\"wrapper\"` is also a D-03 no-op — TS arrays don't have the\n // pandas `.attrs` divergence problem a Python wrapper compensates for, so\n // we accept the kwarg for surface-equivalence and return the plain shape.\n\n // ── Phase 10 selector + cross-arg validation ─────────────────────────\n //\n // The TS signature pre-dates Phase 10's composable kwargs, so the\n // `station` positional is still always passed. The new selectors live\n // on `opts` (city / contract / contracts) and are validated here:\n // exactly one of station / city / contract / contracts is allowed.\n //\n // v0.2 ships only the validation surface; the multi-station JOIN +\n // trade-attachment lands in v0.3. Passing any non-station selector\n // surfaces a clear NotImplementedError-style error so callers can\n // route via discover() + the station-path until v0.3.\n const hasCity = typeof opts.city === \"string\" && opts.city.length > 0;\n const hasContract = typeof opts.contract === \"string\" && opts.contract.length > 0;\n const hasContracts = Array.isArray(opts.contracts) && opts.contracts.length > 0;\n const hasStation = typeof station === \"string\" && station.length > 0;\n const selectorCount =\n Number(hasStation) + Number(hasCity) + Number(hasContract) + Number(hasContracts);\n if (selectorCount === 0) {\n throw new Error(\n \"research(): exactly one of station, opts.city, opts.contract, opts.contracts must be provided\",\n );\n }\n if (selectorCount > 1) {\n const names: string[] = [];\n if (hasStation) names.push(\"station\");\n if (hasCity) names.push(\"city\");\n if (hasContract) names.push(\"contract\");\n if (hasContracts) names.push(\"contracts\");\n throw new Error(`research(): selectors are mutually exclusive; got ${JSON.stringify(names)}`);\n }\n if (opts.sources !== undefined && opts.source !== undefined) {\n throw new Error(\"research(): sources and source are mutually exclusive\");\n }\n // Iter-1 codex HIGH: sources / source validation is shipped in Phase 10\n // v0.2 but the data-selection wiring lands in v0.3. Without this guard\n // the station-path runs the full multi-source merge regardless — silent\n // data-selection corruption.\n if (opts.sources !== undefined || opts.source !== undefined) {\n throw new Error(\n \"research(): sources / source validation surface is shipped in Phase 10 v0.2 \" +\n \"but the data-selection wiring lands in v0.3. For Mode 2 single-source pinning \" +\n \"today, use `researchBySource(station, source, ...)` from @mostlyrightmd/meta.\",\n );\n }\n if (opts.stationOverride !== undefined && !hasContract) {\n throw new Error(\n \"research(): stationOverride requires contract (not standalone station/city/contracts)\",\n );\n }\n if (opts.includeTrades === true && !(hasContract || hasContracts)) {\n throw new Error(\n \"research(): includeTrades requires contract or contracts (station/city selectors have no trade timeseries)\",\n );\n }\n if (hasCity || hasContract || hasContracts) {\n throw new Error(\n \"research(): city/contract/contracts selectors are validated in Phase 10 v0.2 \" +\n \"but the multi-station/multi-issuer JOIN + trade attachment lands in v0.3. \" +\n \"For now, use `discover({city})` to find the station then call \" +\n \"`research(station, fromDate, toDate)` directly.\",\n );\n }\n // ── Backwards-compat station path (existing implementation) ─────────\n const resolved = normalizeStation(station);\n const dates = buildDateList(fromDate, toDate);\n const extendedTo = plusOneDay(toDate);\n\n const fromYear = Number(fromDate.slice(0, 4));\n const toYear = Number(toDate.slice(0, 4));\n const extendedToYear = Number(extendedTo.slice(0, 4));\n\n const baseOpts: { signal?: AbortSignal } = {};\n if (opts.signal !== undefined) baseOpts.signal = opts.signal;\n\n const cache = await resolveCache(opts);\n const cacheNow = opts.now ?? new Date();\n\n // --- IEM CLI climate (per-year) ---------------------------------------\n // Cache strategy: read-through per (station code, year). Skip the current\n // LST year (mutable) and never cache `.live` sources. `iem.cli` is\n // archive → cacheable for completed years. Fetcher takes the ICAO\n // (resolved.icao), cache key uses the 3-letter NWS code (resolved.code).\n let mergedClimate: ReadonlyArray<ClimateObservation> = [];\n try {\n const cliRows = await fetchCliWithCache(\n resolved.icao,\n resolved.code,\n fromYear,\n toYear,\n opts,\n cache,\n cacheNow,\n );\n mergedClimate = mergeClimate(cliRows);\n } catch (err) {\n if (err instanceof DOMException && (err.name === \"AbortError\" || err.name === \"TimeoutError\")) {\n throw err;\n }\n // Degrade to no CLI data — buildPairs emits null cli_* for affected dates.\n }\n\n // --- AWC live observations (short-circuit on stale windows) -----------\n const awcHours = opts.awcHours ?? AWC_MAX_HOURS;\n const awcRows: Observation[] = [];\n if (anyDateOverlapsAwc(toDate, awcHours, opts.now ?? new Date())) {\n const awcOpts: { hours: number; signal?: AbortSignal } = { hours: awcHours };\n if (opts.signal !== undefined) awcOpts.signal = opts.signal;\n const awcRaw = await fetchAwcMetars([resolved.icao], awcOpts);\n for (const m of awcRaw) {\n const obs = awcToObservation(m);\n if (obs !== null) awcRows.push(obs);\n }\n }\n\n // --- IEM ASOS archive observations (per-year × {METAR, SPECI}) --------\n // IEM ASOS expects the 3-letter NWS station code (`station=NYC`),\n // NOT the 4-letter ICAO. Python `_fetchers/iem_asos.py:119` uses\n // `station={station.code}`. Use resolved.code, NOT resolved.icao.\n const iemRows = await fetchIemAsosWithCache(\n resolved.code,\n fromYear,\n extendedToYear,\n fromDate,\n extendedTo,\n opts,\n cache,\n cacheNow,\n );\n\n // --- GHCNh archive observations (US stations only) --------------------\n // iter-7 H14: now wraps `downloadGhcnh` in `fetchGhcnhWithCache` so\n // GHCNh chunks are persisted at the same per-month granularity as IEM\n // ASOS. Repeat `research()` calls for the same range skip NCEI\n // entirely on cache hit. Non-US stations short-circuit before reaching\n // the helper — GHCNh PSVs are US-only.\n let ghcnhRows: Observation[] = [];\n if (isUsStation(resolved) && resolved.ghcnhId !== null && resolved.ghcnhId.length > 0) {\n ghcnhRows = await fetchGhcnhWithCache(\n resolved.code,\n resolved.ghcnhId,\n fromDate,\n extendedTo,\n opts,\n cache,\n cacheNow,\n );\n }\n\n // --- Merge observations + bucket by settlement date -------------------\n const combinedRaw = [...awcRows, ...iemRows, ...ghcnhRows];\n const sorted = sortByObservedAtThenSource(combinedRaw);\n const merged = mergeObservations(sorted);\n\n const observationsByDate: Record<string, PairsObservationLike[]> = {};\n // dates is guaranteed non-empty by buildDateList contract (throws on\n // fromDate > toDate; both validated above).\n const dateLo = dates[0] ?? \"\";\n const dateHi = dates[dates.length - 1] ?? \"\";\n for (const obs of merged) {\n const settleDate = observedSettlementDate(obs.observed_at, resolved.code);\n if (settleDate === null) continue;\n if (settleDate < dateLo || settleDate > dateHi) continue;\n let bucket = observationsByDate[settleDate];\n if (bucket === undefined) {\n bucket = [];\n observationsByDate[settleDate] = bucket;\n }\n bucket.push(obs);\n }\n\n // --- Bucket climate by date (mergeClimate already deduped) ------------\n const climateByDate: Record<string, PairsClimateLike | null> = {};\n for (const cli of mergedClimate) {\n climateByDate[cli.observation_date] = cli;\n }\n\n // --- buildPairs join + return -----------------------------------------\n return buildPairs(resolved.code, dates, observationsByDate, climateByDate);\n}\n","// Mode 2 — source-explicit research() variant.\n//\n// Mirrors packages/core/src/mostlyright/mode2.py. Mode 1 (the existing\n// `research()`) merges AWC > IEM > GHCNh; Mode 2 lets the caller pin\n// observations to a single named source for source-identified\n// training pairs (the workflow Vojtech wanted for backtests that\n// need source-identity invariants).\n//\n// Lives in @mostlyrightmd/meta (alongside `research()`), NOT in\n// @mostlyrightmd/core — `assertSourceIdentity` consumes the\n// @mostlyrightmd/weather `Observation` type, which @mostlyrightmd/core\n// must not depend on (would create a cycle).\n//\n// ── Vocabulary ───────────────────────────────────────────────────────\n// TS narrows what Python widens: at the input boundary, TS accepts\n// ONLY the four canonical dotted-form sources. Bare forms (`iem`,\n// `awc`, `ghcnh`) are NEVER accepted at the API; they only ever\n// appear as parser-emitted PER-ROW source tags. The alias table\n// (`SOURCE_ALIASES`) bridges the boundary: filter rows whose bare\n// tag is in the dotted source's alias set, but NEVER rewrite the\n// per-row source — that would silently corrupt downstream Validator\n// invariants. See Python mode2.py:161-166 for the canonical comment.\n\nimport {\n NotFoundError,\n STATION_BY_CODE,\n STATION_BY_ICAO,\n SourceMismatchError,\n type SourceMismatchRole,\n} from \"@mostlyrightmd/core\";\nimport {\n type Observation,\n awcToObservation,\n downloadGhcnh,\n downloadIemAsos,\n fetchAwcMetars,\n parseGhcnhPsv,\n parseIemCsv,\n} from \"@mostlyrightmd/weather\";\n\nexport type { SourceMismatchRole };\n\n/** Mode 2 canonical source vocabulary. Exactly four dotted values. */\nexport const MODE2_SOURCES = [\"iem.archive\", \"iem.live\", \"awc.live\", \"ghcnh.archive\"] as const;\n\n/**\n * Mode 2 source-identity type. Const-union derived from the\n * `MODE2_SOURCES` tuple-literal (NOT a TS `enum` — `enum` defeats\n * tree-shaking per TS Architect rubric §5).\n */\nexport type Mode2Source = (typeof MODE2_SOURCES)[number];\n\n/**\n * Map each canonical dotted source to the bare parser-emitted tags\n * that satisfy it. Parsers emit bare `iem`/`awc`/`ghcnh` per\n * packages-ts/weather; mostlyright' canonical vocab is dotted. The\n * alias table bridges both at the boundary without rewriting the\n * per-row source — downstream consumers see the truthful\n * parser-emitted tag.\n *\n * Mirrors packages/core/src/mostlyright/mode2.py:55-63.\n */\nexport const SOURCE_ALIASES: ReadonlyMap<Mode2Source, ReadonlySet<string>> = new Map<\n Mode2Source,\n ReadonlySet<string>\n>([\n [\"iem.archive\", new Set([\"iem\", \"iem.archive\"])],\n [\"iem.live\", new Set([\"iem\", \"iem.live\"])],\n [\"awc.live\", new Set([\"awc\", \"awc.live\"])],\n [\"ghcnh.archive\", new Set([\"ghcnh\", \"ghcnh.archive\"])],\n]);\n\n/**\n * Type-guard: narrow an unknown value to {@link Mode2Source}. Returns\n * true iff `value` is one of the four canonical dotted strings.\n * Bare-form inputs (`'iem'`, `'awc'`, `'ghcnh'`) return false — TS\n * narrows what Python widens.\n */\nexport function isMode2Source(value: unknown): value is Mode2Source {\n return typeof value === \"string\" && (MODE2_SOURCES as readonly string[]).includes(value);\n}\n\n/**\n * Throw {@link SourceMismatchError} if any row's `source` field\n * disagrees with the expected source vocabulary. Rows missing the\n * `source` field (undefined / null / non-string) are skipped\n * (matches Python mode2.py:181-182 — `if \"source\" not in df.columns:\n * return`). Empty `rows` passes silently.\n *\n * The `expected` parameter accepts EITHER:\n *\n * - a single string — the most common case; downstream callers\n * can pass `\"iem.archive\"` and the check is `src === \"iem.archive\"`.\n * - a `ReadonlySet<string>` — used by `researchBySource` to pass\n * the {@link SOURCE_ALIASES} entry so bare-form parser tags\n * (`'iem'`) are accepted alongside the dotted canonical form\n * (`'iem.archive'`). Without this, the per-row source-preserved\n * invariant (Python mode2.py:161-166) would force the assertion\n * to fire on every Mode 2 call.\n *\n * @param rows rows to check (any shape with `source?: string`)\n * @param expected the source string OR alias-set the caller asked for\n * @param role role-name vocabulary; defaults to 'observations'\n *\n * @throws SourceMismatchError with `schemaSource` = the expected label\n * (the input string, or `[...accept].sort().join(\"|\")`\n * when an alias-set was passed), `dataSource` =\n * first sorted distinct mismatched source,\n * `role` = the caller-provided role,\n * `catalogWarning` = null.\n */\nexport function assertSourceIdentity<Row extends { source?: string | null | undefined }>(\n rows: ReadonlyArray<Row>,\n expected: string | ReadonlySet<string>,\n role: SourceMismatchRole = \"observations\",\n): void {\n const accept: ReadonlySet<string> =\n typeof expected === \"string\" ? new Set<string>([expected]) : expected;\n const expectedLabel: string =\n typeof expected === \"string\" ? expected : [...accept].sort().join(\"|\");\n\n const distinct = new Set<string>();\n let bad = 0;\n for (const r of rows) {\n const src = r?.source;\n if (typeof src !== \"string\") continue;\n if (!accept.has(src)) {\n distinct.add(src);\n bad += 1;\n }\n }\n if (bad === 0) return;\n const others = [...distinct].sort();\n const first = others[0] ?? \"<unknown>\";\n throw new SourceMismatchError(\n `Mode 2 dispatch requested '${expectedLabel}' but received ${bad} row(s) with other sources: [${others\n .map((s) => `'${s}'`)\n .join(\", \")}]`,\n {\n schemaSource: expectedLabel,\n dataSource: first,\n role,\n catalogWarning: null,\n },\n );\n}\n\n// ---------------------------------------------------------------------------\n// researchBySource — Mode 2 dispatch entry point\n// ---------------------------------------------------------------------------\n\n/** Mode 2 caller-supplied options. Subset of `ResearchOptions` — Mode 2\n * returns observations only, so forecast + climate + cache opts are\n * intentionally excluded. */\nexport interface ResearchBySourceOptions {\n /** Forward to the underlying fetcher; aborts the dispatch. */\n signal?: AbortSignal;\n /** AWC lookback window in hours (clamped by the fetcher). Default 168. */\n awcHours?: number;\n /** Polite-delay (ms) between IEM ASOS yearly chunks. Default 1000. */\n iemPolitenessMs?: number;\n}\n\n/** AWC live serves at most ~168 hours (7 days). Mirrors research.ts. */\nconst AWC_MAX_HOURS = 168;\n\ninterface ResolvedStation {\n readonly code: string;\n readonly icao: string;\n readonly country: string | null;\n readonly ghcnhId: string | null;\n}\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Resolve a station identifier (3-letter NWS code OR 4-letter ICAO)\n * to the full record. Inlined here instead of imported from `research.ts`\n * to keep mode2 self-contained — the ~30-line duplication is cheaper\n * than threading internal helpers through a new module boundary.\n */\nfunction resolveStation(input: string): ResolvedStation {\n const raw = input.trim().toUpperCase();\n if (raw.length === 0) {\n throw new Error(\"station must be a non-empty string\");\n }\n const byIcao = STATION_BY_ICAO.get(raw);\n if (byIcao !== undefined) {\n if (byIcao.code === null) {\n throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);\n }\n return {\n code: byIcao.code,\n icao: byIcao.icao,\n country: byIcao.country,\n ghcnhId: byIcao.ghcnh_id,\n };\n }\n const byCode = STATION_BY_CODE.get(raw);\n if (byCode !== undefined) {\n if (byCode.code === null) {\n throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);\n }\n return {\n code: byCode.code,\n icao: byCode.icao,\n country: byCode.country,\n ghcnhId: byCode.ghcnh_id,\n };\n }\n if (raw.startsWith(\"K\") && raw.length === 4) {\n const stripped = raw.slice(1);\n const retry = STATION_BY_CODE.get(stripped);\n if (retry !== undefined && retry.code !== null) {\n return {\n code: retry.code,\n icao: retry.icao,\n country: retry.country,\n ghcnhId: retry.ghcnh_id,\n };\n }\n }\n throw new Error(\n `unknown station ${JSON.stringify(input)} — not found in STATION_BY_CODE or STATION_BY_ICAO`,\n );\n}\n\nfunction validateDateFormat(label: string, value: string): void {\n if (!DATE_RE.test(value)) {\n throw new Error(`${label} must be YYYY-MM-DD, got ${JSON.stringify(value)}`);\n }\n}\n\n/**\n * Year extracted from a YYYY-MM-DD string. Caller must validate format\n * first via `validateDateFormat`.\n */\nfunction yearOf(isoDate: string): number {\n return Number(isoDate.slice(0, 4));\n}\n\n/**\n * Mode 2 source-explicit observation fetch.\n *\n * Dispatches to a single source's fetcher (no merge) and returns raw\n * {@link Observation}s tagged with that source. Mirrors Python\n * `mostlyright.mode2.research_by_source` (packages/core/src/mostlyright/mode2.py).\n *\n * The four supported sources:\n *\n * - `'iem.archive'` → IEM ASOS historical CSVs (METAR + SPECI).\n * - `'iem.live'` → v0.1.0 parity gap; throws. Use `'iem.archive'`.\n * - `'awc.live'` → AWC live METAR JSON (≤168h lookback).\n * - `'ghcnh.archive'` → NCEI GHCNh PSV (US stations only).\n *\n * The returned rows preserve the parser-emitted per-row `source` field\n * verbatim — NEVER rewritten to the dotted canonical form. Bare tags\n * (`'iem'`, `'awc'`, `'ghcnh'`) survive intact so downstream Validator\n * schemas see the truthful provenance. Mode 2 still calls\n * {@link assertSourceIdentity} internally (defense-in-depth) before\n * returning — using the {@link SOURCE_ALIASES} entry so the bare-form\n * tags pass.\n *\n * @param station NWS 3-letter code (e.g. `\"NYC\"`) OR 4-letter ICAO (e.g. `\"KNYC\"`).\n * @param source One of {@link MODE2_SOURCES}.\n * @param fromDate Inclusive start, ISO `YYYY-MM-DD`.\n * @param toDate Inclusive end, ISO `YYYY-MM-DD`.\n * @param opts See {@link ResearchBySourceOptions}.\n *\n * @returns Frozen array of {@link Observation}s whose `source` is in\n * `SOURCE_ALIASES.get(source)`. Empty array on no data\n * (NOT a throw).\n *\n * @throws Error if `source` is not one of {@link MODE2_SOURCES}.\n * Throws BEFORE any network call — no quota burn\n * on invalid input.\n * @throws Error if `source === 'iem.live'` (v0.1.0 parity gap;\n * v0.2 will add per-month live IEM).\n * @throws Error if `station` is unknown, or dates are malformed.\n * @throws NotFoundError if `source === 'ghcnh.archive'` and `station`\n * is non-US (GHCNh PSV files are US-only).\n * @throws SourceMismatchError if a row's `source` disagrees with the alias\n * set for `source` (defense-in-depth; should\n * never fire under correct fetcher behavior).\n */\nexport async function researchBySource(\n station: string,\n source: Mode2Source,\n fromDate: string,\n toDate: string,\n opts: ResearchBySourceOptions = {},\n): Promise<ReadonlyArray<Observation>> {\n // ── Synchronous-style guards (BEFORE any network call) ────────────\n // Architect rubric: unknown-source rejection MUST run before any\n // fetcher import/call (else invalid input burns API quota).\n if (!isMode2Source(source)) {\n throw new Error(\n `Mode 2 source must be one of ${JSON.stringify(\n MODE2_SOURCES,\n )}; got ${JSON.stringify(source)}`,\n );\n }\n if (source === \"iem.live\") {\n throw new Error(\n \"Mode 2 source 'iem.live' not yet implemented in v0.1.0 \" +\n \"(Parity-Ticket: requires per-month live IEM endpoint not yet ported). \" +\n \"Use 'iem.archive' for historical IEM rows.\",\n );\n }\n validateDateFormat(\"fromDate\", fromDate);\n validateDateFormat(\"toDate\", toDate);\n if (fromDate > toDate) {\n throw new Error(`fromDate (${fromDate}) must be <= toDate (${toDate})`);\n }\n const resolved = resolveStation(station);\n\n const accept = SOURCE_ALIASES.get(source);\n if (accept === undefined) {\n // Unreachable — isMode2Source guard above guarantees a hit.\n throw new Error(`internal: no SOURCE_ALIASES entry for '${source}'`);\n }\n\n // ── Per-source dispatch ──────────────────────────────────────────\n let rows: ReadonlyArray<Observation>;\n switch (source) {\n case \"awc.live\": {\n const awcOpts: { hours: number; signal?: AbortSignal } = {\n hours: opts.awcHours ?? AWC_MAX_HOURS,\n };\n if (opts.signal !== undefined) awcOpts.signal = opts.signal;\n const raw = await fetchAwcMetars([resolved.icao], awcOpts);\n const parsed: Observation[] = [];\n for (const m of raw) {\n const obs = awcToObservation(m);\n if (obs !== null) parsed.push(obs);\n }\n // Filter to the queried [fromDate, toDate] window (inclusive) — match\n // the IEM/GHCNh branches. AWC's lookback (~168h) can return METARs\n // outside the caller's window; per-source Mode 2 callers expect rows\n // strictly inside [fromDate, toDate]. Python mode2.py parity.\n rows = parsed.filter((r) => {\n const d = r.observed_at.slice(0, 10);\n return d >= fromDate && d <= toDate;\n });\n break;\n }\n case \"iem.archive\": {\n const fromYear = yearOf(fromDate);\n const toYear = yearOf(toDate);\n const collected: Observation[] = [];\n for (let year = fromYear; year <= toYear; year++) {\n for (const reportType of [3, 4] as const) {\n const iemOpts: {\n reportType: 3 | 4;\n politenessMs: number;\n signal?: AbortSignal;\n } = {\n reportType,\n politenessMs: opts.iemPolitenessMs ?? 1000,\n };\n if (opts.signal !== undefined) iemOpts.signal = opts.signal;\n const chunks = await downloadIemAsos(\n resolved.code,\n `${year}-01-01`,\n `${year}-12-31`,\n iemOpts,\n );\n for (const chunk of chunks) {\n const parsed = parseIemCsv(chunk.csv, {\n observationTypeOverride: reportType === 3 ? \"METAR\" : \"SPECI\",\n });\n collected.push(...parsed);\n }\n }\n }\n // Filter to the queried [fromDate, toDate] window (inclusive).\n rows = collected.filter((r) => {\n const d = r.observed_at.slice(0, 10);\n return d >= fromDate && d <= toDate;\n });\n break;\n }\n case \"ghcnh.archive\": {\n // GHCNh PSV files are US-only. Non-US stations are advertised by\n // null `ghcnh_id` and country !== \"US\" in the codegen.\n if (resolved.country !== \"US\" || resolved.ghcnhId === null || resolved.ghcnhId.length === 0) {\n throw new NotFoundError(\n `GHCNh archive is US-only; station ${JSON.stringify(station)} ` +\n `(country=${resolved.country ?? \"null\"}, ghcnh_id=${\n resolved.ghcnhId === null ? \"null\" : JSON.stringify(resolved.ghcnhId)\n }) has no GHCNh coverage`,\n );\n }\n const fromYear = yearOf(fromDate);\n const toYear = yearOf(toDate);\n const collected: Observation[] = [];\n for (let year = fromYear; year <= toYear; year++) {\n const ghcnhOpts: { signal?: AbortSignal } = {};\n if (opts.signal !== undefined) ghcnhOpts.signal = opts.signal;\n try {\n const yr = await downloadGhcnh(resolved.ghcnhId, year, ghcnhOpts);\n const parsed = parseGhcnhPsv(yr.psv);\n for (const r of parsed) {\n if (r.station_code === resolved.code) collected.push(r);\n }\n } catch (err) {\n // 404 = no data for this station-year (typical for partial /\n // pre-1973 years). Mirrors Python research.py 404-as-skip.\n if (err instanceof NotFoundError) continue;\n throw err;\n }\n }\n rows = collected.filter((r) => {\n const d = r.observed_at.slice(0, 10);\n return d >= fromDate && d <= toDate;\n });\n break;\n }\n // iem.live is rejected above; the type narrowing here is\n // exhaustive over Mode2Source minus iem.live (which is unreachable).\n }\n\n // ── Filter to the alias set (Python parity: row keep iff parser-tag in alias) ──\n // biome-ignore lint/style/noNonNullAssertion: rows is assigned in every reachable case\n const filtered = rows!.filter((r) => accept.has(r.source));\n\n // ── Defense-in-depth: assertSourceIdentity (Python mode2.py:173-193) ────\n // Empty result still passes (no rows → no mismatch).\n assertSourceIdentity(filtered, accept, \"observations\");\n\n return filtered;\n}\n","// TS-W5 — Polymarket types + security constants.\n//\n// Ports the v0.14.1 + Phase 3.3 Python contract verbatim. Anything that\n// crosses the trust boundary (event description, resolution URL) is\n// validated against the constants here before reaching the HTTP fetch\n// or settlement engine.\n\n/**\n * Resolution-source netlocs we trust. Anything else throws\n * PolymarketEventError. Mirrors Python `RESOLUTION_SOURCE_ALLOWLIST`.\n */\nexport const RESOLUTION_SOURCE_ALLOWLIST: ReadonlySet<string> = new Set([\n \"wunderground.com\",\n \"www.wunderground.com\",\n \"weather.gov\",\n \"www.weather.gov\",\n]);\n\n/**\n * Per-netloc → enum value for the `resolutionSourceType` field on settlement\n * records. `hko` / `cwa` are predeclared for v0.2 (HKO/CWA clients).\n */\nexport const NETLOC_TO_RESOLUTION_TYPE: Readonly<Record<string, string>> = Object.freeze({\n \"wunderground.com\": \"wunderground\",\n \"www.wunderground.com\": \"wunderground\",\n \"weather.gov\": \"noaa_wrh\",\n \"www.weather.gov\": \"noaa_wrh\",\n});\n\n/** Enum values for the `resolutionSourceType` column. */\nexport const POLYMARKET_RESOLUTION_SOURCE_TYPES = Object.freeze([\n \"wunderground\",\n \"noaa_wrh\",\n \"hko\",\n \"cwa\",\n \"other\",\n] as const);\nexport type PolymarketResolutionSourceType = (typeof POLYMARKET_RESOLUTION_SOURCE_TYPES)[number];\n\n/**\n * Event id pattern. Python widened this in codex iter-2 P1: real Gamma IDs\n * are numeric strings (`\"12345\"`), but condition-tag UUIDs + slugs also\n * appear in the wild. Wide enough to accept real Gamma payloads but narrow\n * enough to defend against URL-path injection.\n *\n * The plan called this \"UUID4 regex\" but follows Python's actual behavior:\n * the strict UUID4 form rejected every real Gamma event, breaking the\n * discover → settle round-trip.\n */\nexport const EVENT_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;\n\n/**\n * Max bytes of a Polymarket event description we'll parse. Polymarket\n * descriptions are concise; oversized payloads indicate hostile input\n * (ReDoS defense).\n */\nexport const MAX_DESCRIPTION_BYTES = 16 * 1024;\n\n/**\n * Per-resolution-source publication delay. Settlement refuses to settle\n * until `now - settlementDate >= delay` to avoid settling on values the\n * issuer hasn't published yet.\n *\n * Wunderground typically posts daily extremes ~6h after local midnight;\n * NOAA WRH ~4h. \"other\" gets a conservative 24h fallback.\n */\nexport const SETTLE_DELAY_HOURS: Readonly<Record<string, number>> = Object.freeze({\n wunderground: 6,\n noaa_wrh: 4,\n other: 24,\n});\n\n/**\n * Slug date extractor. Polymarket weather slugs embed the resolution date\n * (e.g. `will-nyc-be-above-80f-on-2026-05-23`). Used by `polymarketSettle`\n * to derive the resolution date from the slug instead of `event.endDate`.\n */\nexport const SLUG_DATE_RE = /(\\d{4})-(\\d{2})-(\\d{2})/g;\n\n/**\n * Markets routed to v0.2 sources. Phase 23: Hong Kong settles against HKO (the\n * Observatory, weather.gov.hk) and Taipei moved RCTP→RCSS (CWA). Both fully\n * defer every measure; the old VHHH/RCTP airport ICAOs are no longer deferred.\n */\nexport const DEFERRED_STATIONS: ReadonlySet<string> = new Set([\"HKO\", \"RCSS\"]);\n\n/** Discovery row shape — one per active weather event. */\nexport interface PolymarketDiscoveryRow {\n readonly eventId: string | null;\n readonly slug: string | null;\n readonly title: string | null;\n readonly city: string | null;\n readonly icao: string | null;\n readonly measure: \"high\" | \"low\" | \"default\" | null;\n readonly endTime: string | null;\n readonly resolutionSourceType: PolymarketResolutionSourceType | null;\n}\n\n/**\n * Native unit of the market's published settlement value. Codex iter-3 P2:\n * international Polymarket markets publish in whole-°C, US in °F. The\n * settle engine returns the resolved value in BOTH units so the caller's\n * comparison against Polymarket's published value uses the matching unit.\n */\nexport type SettlementUnit = \"fahrenheit\" | \"celsius\";\n\n/** Settlement result shape. */\nexport interface PolymarketSettlementResult {\n readonly eventId: string;\n readonly settlementDate: string; // YYYY-MM-DD\n readonly icao: string;\n readonly measure: \"high\" | \"low\" | \"default\";\n /**\n * Resolved temperature in the unit the caller asked for (the `unit`\n * option; defaults to the station's native unit — F for US-registry\n * stations, C for international). Convenience pointer to `resolvedValueF`\n * or `resolvedValueC`.\n */\n readonly resolvedValue: number;\n readonly resolvedValueC: number;\n readonly resolvedValueF: number;\n /** Which unit `resolvedValue` carries. */\n readonly unit: SettlementUnit;\n readonly resolutionSourceType: PolymarketResolutionSourceType;\n readonly dataQualityAlert: string | null;\n}\n\n/** Settlement options. */\nexport interface PolymarketSettleOptions {\n /** Optional description override (live discovery normally supplies this). */\n readonly description?: string;\n /** Reference \"now\" for the finalization-delay check. Defaults to `new Date()`. */\n readonly now?: Date;\n /**\n * Polymarket's published settlement value, if known. The comparison\n * uses whichever unit `unit` is set to. ±1°F (or ±0.6°C) diff emits an\n * alert; values outside that band don't throw.\n */\n readonly polymarketPublishedValue?: number;\n /**\n * Resolved-value unit. Defaults to the station's native unit:\n * °F for the 20 US Kalshi cities; °C for international stations\n * (matches Polymarket's published-bucket convention per\n * .planning/research/INGEST-PLANNER-RESEARCH.md). Codex iter-3 P2.\n */\n readonly unit?: SettlementUnit;\n}\n","// TS-W5 Wave 2 — Tier 0/1/2/3 resolver chain + city catalog.\n//\n// Mirrors Python `_per_event_station.resolve_station_for_event` + the\n// `_derive_city` helper. The resolver chain:\n//\n// - Tier 0 (deferred check): if the resolved ICAO is in DEFERRED_STATIONS\n// (Taipei RCSS, Hong Kong HKO), raise DeferredMarketError. Phase 23: both\n// fully defer every measure — v0.2 will land CWA + weather.gov.hk clients.\n// - Tier 1: explicit `event.city` field if a known city.\n// - Tier 2: derive city from slug + title + tags (lowercase substring\n// match against the catalog, longest-key-first so multi-token cities\n// like \"london_gatwick\" resolve before \"london\").\n// - Tier 3: bail with KeyError — discover() drops the row, settle()\n// surfaces PolymarketSettlementError.\n\nimport { DeferredMarketError } from \"@mostlyrightmd/core\";\n\nimport { POLYMARKET_CITY_STATIONS } from \"../data/generated/polymarket-city-stations.js\";\nimport type { PolymarketEventRaw } from \"./client.js\";\nimport { PolymarketSettlementError } from \"./errors.js\";\nimport { DEFERRED_STATIONS } from \"./types.js\";\n\n/**\n * Detect whether the market resolves on the daily HIGH or LOW from\n * keywords in the event title/slug/name. Distinct from the station-level\n * measure: many cities have one airport for both, but the market still\n * resolves on tmax XOR tmin.\n */\nexport function detectMarketMeasure(event: PolymarketEventRaw): \"high\" | \"low\" | \"default\" {\n const text = [event.title, event.slug, (event as { name?: string }).name]\n .filter((v): v is string => typeof v === \"string\")\n .join(\" \")\n .toLowerCase();\n const hasHigh = /\\b(highest|high|hottest|warmest|max(?:imum)?)\\b/.test(text);\n const hasLow = /\\b(lowest|low|coldest|coolest|min(?:imum)?)\\b/.test(text);\n if (hasLow && !hasHigh) return \"low\";\n if (hasHigh && !hasLow) return \"high\";\n return \"default\";\n}\n\nconst CITY_KEYS_SORTED: ReadonlyArray<string> = Object.freeze(\n Object.keys(POLYMARKET_CITY_STATIONS).sort((a, b) => b.length - a.length),\n);\n\n/**\n * Derive a city key from slug + title + tags. Lowercase substring match\n * against the catalog; longest-first so multi-token cities outrank prefixes.\n * Returns null when no match — caller decides whether to drop or surface.\n */\nexport function deriveCity(event: PolymarketEventRaw): string | null {\n const parts: string[] = [];\n const slug = typeof event.slug === \"string\" ? event.slug.toLowerCase() : \"\";\n const title = typeof event.title === \"string\" ? event.title.toLowerCase() : \"\";\n parts.push(slug, title);\n const tags = event.tags;\n if (Array.isArray(tags)) {\n for (const tag of tags) {\n if (typeof tag === \"string\") parts.push(tag.toLowerCase());\n else if (tag !== null && typeof tag === \"object\") {\n const label = tag.label ?? tag.slug;\n if (typeof label === \"string\") parts.push(label.toLowerCase());\n }\n }\n }\n // Codex iter-5 P2: require word-boundary matches so cities don't false-\n // positive inside ordinary words (e.g. \"comparison\" → \"paris\"). Build a\n // regex per needle with non-word-character boundaries on both sides.\n // Word characters are ASCII letters/digits + underscore; we treat \"-\",\n // \" \", \"/\", \"(\", \")\" etc. as boundaries (covers the slug/title/tag forms\n // we ingest).\n const haystack = ` ${parts.join(\" \")} `;\n for (const key of CITY_KEYS_SORTED) {\n const needles = [key, key.replace(/_/g, \"-\"), key.replace(/_/g, \" \")];\n for (const n of needles) {\n if (n.length === 0) continue;\n // Escape any regex metacharacters in `n` (city keys are alphanum +\n // underscore + hyphen + space; the latter two need no escaping but\n // be defensive).\n const escaped = n.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const re = new RegExp(`(^|[^A-Za-z0-9])${escaped}(?=[^A-Za-z0-9]|$)`);\n if (re.test(haystack)) return key;\n }\n }\n return null;\n}\n\n// Phase 8 — Tier 1.5 URL extraction (POLY-US-03).\n// Wunderground PWS / airport / history URL pattern; captures K-prefix ICAO\n// from CANONICAL settlement paths only — anchor allowlist: /pws/,\n// /dashboard/pws/, /history/daily/, /history/airport/, /weather-station/,\n// /cat/forecasts/. Real Polymarket settlement URLs carry country / state /\n// city slugs between the anchor and the ICAO (e.g.\n// /history/daily/us/ny/new-york-city/KLGA); the `(?:[a-z0-9-]+/)*` segment\n// captures zero or more such intermediate slugs (codex iter-2 +\n// python-architect iter-2 CRITICAL — prior tighter version that required\n// the ICAO to abut the anchor missed every real Polymarket URL).\n//\n// The trailing negative-lookahead `(?![A-Za-z0-9_-])` rejects any\n// alphanumeric / underscore / hyphen follower so we accept common URL\n// terminators (`/`, `?`, `#`, `)`, `.`, `,`, whitespace, EOF) but reject\n// extensions like `KIDS-summer` where `-` would otherwise pass `\\b`\n// (codex iter-2 CRITICAL — original `(?=[/?#\\s]|$)` lookahead missed\n// Markdown-embedded URLs that end with `)`, `.`, etc.).\n// Iter-3 codex CRITICAL: case-insensitive flag let `[a-z0-9-]+/` consume\n// uppercase segments, so `/history/daily/KORD/date/KLAX` extracted `KLAX`\n// (the LAST K-prefix segment) instead of `KORD` (canonical station slot).\n// Fix: drop `i` flag — case-sensitive matching pins ICAO to the canonical\n// station slot. Real Wunderground URLs use lowercase paths + uppercase\n// ICAOs (RFC 3986 + Wunderground convention).\nconst WUNDERGROUND_ICAO_RE =\n /https?:\\/\\/(?:www\\.)?wunderground\\.com\\/(?:dashboard\\/)?(?:pws|history\\/daily|history\\/airport|weather-station|cat\\/forecasts)\\/(?:[a-z0-9-]+\\/)*(K[A-Z]{3})(?![A-Za-z0-9_-])/g;\n\n/**\n * Extract the canonical Wunderground PWS / airport ICAO from `text`.\n *\n * Tier 1.5 of the resolver chain — runs between explicit `event.city`\n * and slug-derive. When a Polymarket event embeds a Wunderground PWS\n * URL, the URL IS the source of truth; no catalog lookup needed.\n *\n * Multi-URL disambiguation: when multiple canonical Wunderground URLs\n * appear, ALL extracted ICAOs MUST agree. Disagreement returns null so\n * the resolver falls through to Tier 2 city-derive (prevents an\n * issuer-side citation URL from silently swapping the settlement station).\n *\n * Returns uppercase ICAO (4 chars, leading K) when a canonical URL is\n * found AND any additional canonical URLs agree. Null otherwise —\n * including the disagreement case.\n */\nexport function extractIcaoFromResolutionSource(text: string | null | undefined): string | null {\n if (typeof text !== \"string\" || text.length === 0) return null;\n // Reset lastIndex on the global regex so multiple matchAll calls are safe\n // (the regex literal is reused across calls).\n const matches = [...text.matchAll(WUNDERGROUND_ICAO_RE)];\n if (matches.length === 0) return null;\n const unique = new Set<string>();\n for (const m of matches) {\n if (m[1] !== undefined) unique.add(m[1].toUpperCase());\n }\n if (unique.size !== 1) return null; // 0 or disagreement → abstain\n return [...unique][0] ?? null;\n}\n\n/**\n * Resolve an event to `{icao, stationMeasure}` using the city catalog.\n *\n * Returns null when no city matches (caller drops the event).\n * Raises DeferredMarketError when the resolution would route to a v0.2\n * source (Taipei RCTP, Hong Kong VHHH for the low-extreme market).\n */\nexport function resolveStationForEvent(\n event: PolymarketEventRaw,\n marketMeasure: \"high\" | \"low\" | \"default\",\n): { city: string; icao: string; stationMeasure: \"high\" | \"low\" | \"default\" } | null {\n // Tier 1: explicit city field — useful for tests / synthetic events.\n let cityKey: string | null = null;\n const explicit = (event as { city?: unknown }).city;\n if (typeof explicit === \"string\") {\n const low = explicit.toLowerCase();\n if (Object.prototype.hasOwnProperty.call(POLYMARKET_CITY_STATIONS, low)) {\n cityKey = low;\n }\n }\n\n // Tier 1.5: URL extraction from description / resolutionSource.\n // The Wunderground URL is the issuer's canonical proof — beats catalog\n // lookup for ICAO. Defer gate still applies so a URL injection cannot\n // silently route an RCTP / HK-low market.\n //\n // Iter-1 TS-architect CRITICAL: the `city` field is owned by the slug-\n // derive layer (matching Python's `_derive_city`-before-resolve pattern in\n // `polymarket_discover`). Tier 1.5 ONLY sets `icao`; `city` is whatever\n // the caller / slug-derive resolved to BEFORE the URL was considered.\n // Returning `findCityForIcao(extractedIcao)` here would drift the TS row's\n // `city` column away from the Python row for the same event — silent\n // per-row source-identity drift, the parity-rubric CRITICAL.\n const desc = typeof event.description === \"string\" ? event.description : \"\";\n const resSrc =\n typeof (event as { resolutionSource?: unknown }).resolutionSource === \"string\"\n ? (event as { resolutionSource: string }).resolutionSource\n : \"\";\n const urlText = `${desc} ${resSrc}`;\n const extractedIcao = extractIcaoFromResolutionSource(urlText);\n if (extractedIcao !== null) {\n // Phase 23: HKO (Hong Kong) and RCSS (Taipei) fully defer — every measure —\n // until their v0.2 source clients (weather.gov.hk / CWA) land.\n if (DEFERRED_STATIONS.has(extractedIcao)) {\n throw new DeferredMarketError(\n `Polymarket market for station ${extractedIcao} is deferred until the v0.2 source client lands (HKO/RCSS)`,\n );\n }\n // City: explicit Tier 1 wins; otherwise slug-derived; otherwise empty.\n // This mirrors Python `polymarket_discover`'s ordering — `_derive_city`\n // populates `event.city` BEFORE `resolve_station_for_event` runs, so the\n // explicit-or-derived city is always the `city` column emitted on the row.\n // discover.ts normalizes empty string back to null at the row boundary\n // to mirror Python's `(ev.get(\"city\") or \"\").lower() or None` semantics.\n const cityForRow = cityKey ?? deriveCity(event) ?? \"\";\n // stationMeasure mirrors Python's _detect_measure(text) result (passed\n // in by the caller as marketMeasure via detectMarketMeasure). Hardcoding\n // \"default\" here would drift from Python for URL-sourced low/high\n // markets (codex iter-2 HIGH).\n return { city: cityForRow, icao: extractedIcao, stationMeasure: marketMeasure };\n }\n\n // Tier 2: scan slug + title + tags.\n if (cityKey === null) {\n cityKey = deriveCity(event);\n }\n if (cityKey === null) return null;\n\n const entry = POLYMARKET_CITY_STATIONS[cityKey];\n if (entry === undefined) return null;\n\n // Station-level measure: cities that split (paris high vs low) follow\n // the market measure; cities without a split use \"default\".\n let stationMeasure: \"high\" | \"low\" | \"default\" = \"default\";\n if (marketMeasure === \"high\" && typeof entry.high === \"string\") stationMeasure = \"high\";\n else if (marketMeasure === \"low\" && typeof entry.low === \"string\") stationMeasure = \"low\";\n\n const icao = entry[stationMeasure] ?? entry.default;\n if (typeof icao !== \"string\") return null;\n\n // Tier 0 deferred-station guard. Phase 23: Hong Kong (HKO) and Taipei (RCSS)\n // fully defer every measure until their v0.2 source clients land\n // (weather.gov.hk for HK, CWA for Taipei).\n if (DEFERRED_STATIONS.has(icao)) {\n throw new DeferredMarketError(\n `Polymarket market for station ${icao} is deferred until the v0.2 source client lands (HKO/RCSS)`,\n );\n }\n\n return { city: cityKey, icao, stationMeasure };\n}\n\n/**\n * Parse the resolution date from a Polymarket weather slug. The LAST\n * YYYY-MM-DD match wins because slugs may carry both a creation date and\n * a resolution date (`created-2026-01-01-resolves-2026-05-23`) — the\n * resolution date is typically rightmost in Polymarket's convention.\n *\n * Mirrors Python `_settlement_date_from_slug` architect iter-1 HIGH-4.\n */\nexport function settlementDateFromSlug(slug: string): string {\n const matches = slug.matchAll(/(\\d{4})-(\\d{2})-(\\d{2})/g);\n let last: RegExpMatchArray | null = null;\n for (const m of matches) last = m;\n if (last === null) {\n throw new PolymarketSettlementError(\n `no resolution date in slug ${JSON.stringify(slug)} (expected YYYY-MM-DD)`,\n );\n }\n const [_, y, m, d] = last;\n const year = Number(y);\n const month = Number(m);\n const day = Number(d);\n // Validate calendar date roundtrips (e.g. reject 2025-02-30).\n const ts = Date.UTC(year, month - 1, day);\n const back = new Date(ts);\n if (\n back.getUTCFullYear() !== year ||\n back.getUTCMonth() !== month - 1 ||\n back.getUTCDate() !== day\n ) {\n throw new PolymarketSettlementError(\n `slug ${JSON.stringify(slug)} carries malformed date ${y}-${m}-${d}`,\n );\n }\n return `${y}-${m}-${d}`;\n}\n","// Phase 8 — per-issuer denylist. Hand-paired with Python\n// `mostlyright.markets.polymarket.KNOWN_WRONG_STATIONS` (NOT codegen;\n// see .planning/phases/08-.../PLAN.md §\"TS Parity\" for rationale —\n// the constant is small enough that exporter wiring would cost more\n// than it saves, and the alphabetized JSON exporter side-effect would\n// conflate it with the cities map).\n//\n// Per-city Map (not flat set) because Polymarket's catalog is multi-city\n// and the \"wrong\" station depends on which city the event is for.\n// Symmetric in spirit to Kalshi's KALSHI_KNOWN_WRONG_STATIONS flat set:\n// Polymarket's per-city granularity is required because (e.g.) KLGA is\n// correct for NYC but wrong for Chicago (where Polymarket uses KORD).\n\nexport const POLYMARKET_KNOWN_WRONG_STATIONS: Readonly<Record<string, ReadonlySet<string>>> =\n Object.freeze({\n // NYC: Polymarket uses KLGA. KNYC/KJFK/KEWR are common wrong answers.\n nyc: new Set([\"KNYC\", \"KJFK\", \"KEWR\"]),\n // Chicago: Polymarket uses KORD. KMDW is the common wrong answer.\n chicago: new Set([\"KMDW\"]),\n // Houston: Phase 23 moved Polymarket to KHOU. KIAH (Kalshi's station) is\n // now the cross-venue wrong answer for a Polymarket Houston market.\n houston: new Set([\"KIAH\"]),\n // Dallas: Phase 23 moved Polymarket to KDAL. KDFW (Kalshi's station) is\n // now the cross-venue wrong answer.\n dallas: new Set([\"KDFW\"]),\n // SF: Polymarket uses KSFO. KOAK is the common wrong answer.\n san_francisco: new Set([\"KOAK\"]),\n });\n","// Phase 10 — composable research() dispatcher (TS port of\n// packages/core/src/mostlyright/_compose.py).\n//\n// Translates the new selectors (`city`, `contract`, `contracts`) into\n// resolution metadata + station lists. Pure logic, no I/O.\n\nimport {\n KALSHI_SETTLEMENT_STATIONS,\n type KalshiStation,\n POLYMARKET_CITY_STATIONS,\n} from \"@mostlyrightmd/markets\";\nimport { POLYMARKET_KNOWN_WRONG_STATIONS } from \"@mostlyrightmd/markets/polymarket\";\n\n/** The four mutually-exclusive selector names. */\nexport const SELECTOR_NAMES = [\"station\", \"city\", \"contract\", \"contracts\"] as const;\nexport type SelectorName = (typeof SELECTOR_NAMES)[number];\n\n/**\n * Kalshi short-ticker → canonical city slug. Real Kalshi tickers use\n * variable-length city suffixes: `KXHIGHNY-...` (NY → NYC),\n * `KXHIGHCHI-...` (CHI → CHI). The `KALSHI_SETTLEMENT_STATIONS` catalog\n * is keyed by the canonical 3-letter slug; this alias normalizes the\n * variable-length Kalshi suffix to the catalog key before lookup.\n */\nconst KALSHI_TICKER_ALIASES: Record<string, string> = {\n NY: \"NYC\",\n};\n\n/**\n * Kalshi-short ↔ Polymarket-long city slug alias. Iter-1 python-architect\n * HIGH: without this, `resolveCity(\"LAX\")` would miss Polymarket's KLAX\n * (keyed as `los_angeles`); `resolveCity(\"chicago\")` would miss Kalshi's\n * KMDW (keyed as `CHI`). Bi-directional probe surfaces the full\n * cross-issuer settlement neighborhood regardless of which slug form\n * the caller passed.\n */\nconst CITY_SLUG_ALIASES: Record<string, readonly [string, string]> = {\n // short_kalshi (lower) → [polymarket_long, kalshi_upper]\n nyc: [\"nyc\", \"NYC\"],\n chi: [\"chicago\", \"CHI\"],\n lax: [\"los_angeles\", \"LAX\"],\n mia: [\"miami\", \"MIA\"],\n den: [\"denver\", \"DEN\"],\n bos: [\"boston\", \"BOS\"],\n aus: [\"austin\", \"AUS\"],\n dca: [\"washington_dc\", \"DCA\"],\n phl: [\"philadelphia\", \"PHL\"],\n sfo: [\"san_francisco\", \"SFO\"],\n sea: [\"seattle\", \"SEA\"],\n atl: [\"atlanta\", \"ATL\"],\n hou: [\"houston\", \"HOU\"],\n dal: [\"dallas\", \"DAL\"],\n phx: [\"phoenix\", \"PHX\"],\n msp: [\"minneapolis\", \"MSP\"],\n dtw: [\"detroit\", \"DTW\"],\n};\n\nconst CITY_SLUG_ALIASES_REVERSE: Record<string, readonly [string, string]> = (() => {\n const out: Record<string, readonly [string, string]> = {};\n for (const [shortLower, [longPoly, kalshiUpper]] of Object.entries(CITY_SLUG_ALIASES)) {\n out[longPoly] = [shortLower, kalshiUpper];\n }\n return out;\n})();\n\n/** Return `[polymarket_slug_lower, kalshi_slug_upper]` for `city`. */\nfunction normalizeCitySlugs(city: string): readonly [string, string] {\n const lower = city.toLowerCase();\n const upper = city.toUpperCase();\n const direct = CITY_SLUG_ALIASES[lower];\n if (direct !== undefined) return direct;\n const reverse = CITY_SLUG_ALIASES_REVERSE[lower];\n if (reverse !== undefined) return [lower, reverse[1]];\n return [lower, upper];\n}\n\n/**\n * Structured warning emitted when `stationOverride` deliberately\n * mismatches the contract's canonical settlement station. The output\n * row carries `settlementMismatch: true`.\n *\n * JS has no `warnings.warn()` analogue; callers receive these via the\n * `onWarning?` callback in ResearchOptions.\n */\nexport interface StationOverrideWarning {\n readonly kind: \"StationOverrideWarning\";\n readonly contractStation: string;\n readonly overrideStation: string;\n readonly message: string;\n}\n\n/** Selector kwargs accepted by research(). Exactly one MUST be provided. */\nexport interface SelectorArgs {\n readonly station?: string;\n readonly city?: string;\n readonly contract?: string;\n readonly contracts?: ReadonlyArray<string>;\n}\n\n/**\n * Validate selector arity. Returns the active selector name; throws when\n * zero or >1 selectors are provided.\n */\nexport function validateSelectors(args: SelectorArgs): SelectorName {\n const provided: SelectorName[] = [];\n if (typeof args.station === \"string\" && args.station.length > 0) provided.push(\"station\");\n if (typeof args.city === \"string\" && args.city.length > 0) provided.push(\"city\");\n if (typeof args.contract === \"string\" && args.contract.length > 0) provided.push(\"contract\");\n if (Array.isArray(args.contracts) && args.contracts.length > 0) provided.push(\"contracts\");\n\n if (provided.length === 0) {\n throw new Error(\n \"research(): exactly one of station, city, contract, contracts must be provided\",\n );\n }\n if (provided.length > 1) {\n throw new Error(\n `research(): selectors are mutually exclusive; got ${JSON.stringify(provided)}`,\n );\n }\n return provided[0] as SelectorName;\n}\n\n/**\n * Resolve a `\"<issuer>:<id>\"` contract id to `[station, issuer]`.\n *\n * Supported: `kalshi:KHIGH<CITY>` / `kalshi:KXHIGH<CITY>-<DATE>-<STRIKE>`\n * and `kalshi:KLOW<CITY>` / `kalshi:KXLOW<CITY>-<DATE>-<STRIKE>`.\n *\n * Polymarket contract resolution requires an event_id → station lookup\n * (via polymarket-discover); Phase 10 v0.2 defers to v0.3 and throws.\n */\nexport function resolveContract(contractId: string): readonly [string, string] {\n if (typeof contractId !== \"string\" || !contractId.includes(\":\")) {\n throw new TypeError(`contract id must be \\`<issuer>:<id>\\`; got ${JSON.stringify(contractId)}`);\n }\n const colonIdx = contractId.indexOf(\":\");\n const issuer = contractId.slice(0, colonIdx).toLowerCase();\n const raw = contractId.slice(colonIdx + 1);\n const rawUpper = raw.toUpperCase();\n\n if (issuer === \"kalshi\") {\n // Strip KX exchange prefix (KXHIGHNYC → KHIGHNYC) and trailing\n // -DATE-STRIKE suffix to recover the legacy KHIGH<CITY> / KLOW<CITY>\n // shape the KALSHI_SETTLEMENT_STATIONS map keys are derived from.\n let normalized = rawUpper;\n if (normalized.startsWith(\"KX\")) {\n normalized = `K${normalized.slice(2)}`;\n }\n const cityOnly = normalized.split(\"-\", 1)[0] ?? \"\";\n let cityTickerRaw: string | null = null;\n if (cityOnly.startsWith(\"KHIGH\") && cityOnly.length > 5) {\n cityTickerRaw = cityOnly.slice(5);\n } else if (cityOnly.startsWith(\"KLOW\") && cityOnly.length > 4) {\n cityTickerRaw = cityOnly.slice(4);\n } else {\n throw new Error(\n `unsupported kalshi contract format: ${JSON.stringify(raw)}; expected KHIGH<CITY>* / KXHIGH<CITY>* / KLOW<CITY>* / KXLOW<CITY>* prefix`,\n );\n }\n // Iter-1 codex HIGH: normalize variable-length Kalshi ticker suffix\n // (NY → NYC, etc.) via the alias table before the catalog lookup.\n const cityTicker = KALSHI_TICKER_ALIASES[cityTickerRaw] ?? cityTickerRaw;\n const entry: KalshiStation | undefined = KALSHI_SETTLEMENT_STATIONS[cityTicker];\n if (entry === undefined) {\n throw new Error(`unknown Kalshi city ticker: ${JSON.stringify(cityTicker)}`);\n }\n return [entry.station, \"kalshi\"] as const;\n }\n if (issuer === \"polymarket\") {\n throw new Error(\n \"polymarket contract resolution requires event_id → station lookup via \" +\n \"polymarketDiscover()/polymarketSettle(); Phase 10 v0.2 defers this to \" +\n \"v0.3. Use `city: 'nyc'` or pass `stationOverride` until then.\",\n );\n }\n throw new Error(\n `unknown issuer prefix: ${JSON.stringify(issuer)}; expected kalshi or polymarket`,\n );\n}\n\n/**\n * Resolve a city slug to all stations any issuer settles against.\n * Returns deduplicated array in stable order: Kalshi → Polymarket default/high/low\n * → Polymarket denylist backstops.\n */\nexport function resolveCity(city: string): readonly string[] {\n if (typeof city !== \"string\" || !city) {\n throw new Error(`city must be a non-empty string; got ${JSON.stringify(city)}`);\n }\n // Iter-1 python-architect HIGH: cross-issuer slug alias surfaces the\n // full settlement neighborhood for either input form.\n const [polySlug, kalshiSlug] = normalizeCitySlugs(city);\n const out: string[] = [];\n\n const kalshi = KALSHI_SETTLEMENT_STATIONS[kalshiSlug];\n if (kalshi !== undefined && !out.includes(kalshi.station)) {\n out.push(kalshi.station);\n }\n const poly = POLYMARKET_CITY_STATIONS[polySlug];\n if (poly !== undefined) {\n for (const measure of [\"default\", \"high\", \"low\"] as const) {\n const st = poly[measure];\n if (typeof st === \"string\" && !out.includes(st)) out.push(st);\n }\n }\n const wrong = POLYMARKET_KNOWN_WRONG_STATIONS[polySlug];\n if (wrong !== undefined) {\n const sortedWrong = [...wrong].sort();\n for (const st of sortedWrong) {\n if (!out.includes(st)) out.push(st);\n }\n }\n if (out.length === 0) {\n throw new Error(`unknown city ${JSON.stringify(city)}; not in kalshi or polymarket catalogs`);\n }\n return out;\n}\n\n/**\n * Return the list of `\"<issuer>:<ticker>\"` markers that settle against\n * `station` for `city`. Empty array when no issuer settles against this\n * station (typically a denylist backstop).\n */\nexport function annotateSettlesFor(station: string, city: string | null): readonly string[] {\n if (city === null) return [];\n // Iter-1 python-architect HIGH: cross-issuer slug alias annotates both\n // issuers regardless of slug form.\n const [polySlug, kalshiSlug] = normalizeCitySlugs(city);\n const out: string[] = [];\n const kalshi = KALSHI_SETTLEMENT_STATIONS[kalshiSlug];\n if (kalshi !== undefined && kalshi.station === station) {\n out.push(`kalshi:${kalshiSlug}`);\n }\n const poly = POLYMARKET_CITY_STATIONS[polySlug];\n if (poly !== undefined) {\n for (const measure of [\"default\", \"high\", \"low\"] as const) {\n if (poly[measure] === station) {\n out.push(`polymarket:${polySlug}`);\n break;\n }\n }\n }\n return out.sort();\n}\n\n/**\n * Build a structured `StationOverrideWarning` payload. Callers receive\n * these via the optional `onWarning?` callback on research options.\n */\nexport function buildOverrideWarning(\n contractStation: string,\n overrideStation: string,\n): StationOverrideWarning {\n return {\n kind: \"StationOverrideWarning\",\n contractStation,\n overrideStation,\n message: `stationOverride=${JSON.stringify(overrideStation)} differs from contract's canonical settlement station ${JSON.stringify(contractStation)}; output row will carry settlementMismatch=true`,\n };\n}\n","// Phase 10 — discover({city}) ergonomic surface (TS port of\n// packages/core/src/mostlyright/discover.py).\n//\n// Pre-research lookup. Shows quants which station settles which issuer's\n// market for a given city so they can pick the right selector before\n// invoking research(). Especially useful for cross-issuer cities like NYC\n// where Kalshi settles against KNYC and Polymarket against KLGA.\n\nimport { annotateSettlesFor, resolveCity } from \"./compose.js\";\n\n/** One row per station in the resolved city neighborhood. */\nexport interface DiscoverRow {\n /** Echo of the input city. */\n readonly city: string;\n /** 4-char K-prefix ICAO. */\n readonly station: string;\n /**\n * `\"<issuer>:<ticker>\"` markers that resolve against this station.\n * Empty array = denylist backstop surfaced for explicit awareness.\n */\n readonly settlesFor: ReadonlyArray<string>;\n}\n\n/** Envelope mirrors the Python `df.attrs` pattern. */\nexport interface DiscoverResult {\n readonly rows: ReadonlyArray<DiscoverRow>;\n readonly city: string;\n readonly source: \"discover\";\n}\n\n/**\n * Return per-station discovery table for `city`.\n *\n * Each row shows one settlement station + the issuer:ticker markers that\n * resolve against it. Stations in the per-city Polymarket denylist also\n * appear with empty `settlesFor` so quants see the full neighborhood\n * before deciding whether to use `stationOverride`.\n *\n * @example\n * const result = discover({ city: \"NYC\" });\n * // rows include {station: \"KNYC\", settlesFor: [\"kalshi:NYC\"]},\n * // {station: \"KLGA\", settlesFor: [\"polymarket:nyc\"]},\n * // {station: \"KJFK\", settlesFor: []}, // denylist\n * // {station: \"KEWR\", settlesFor: []}.\n */\nexport function discover(args: { readonly city: string }): DiscoverResult {\n if (typeof args !== \"object\" || args === null) {\n throw new TypeError(`discover(): args must be an object; got ${typeof args}`);\n }\n const stations = resolveCity(args.city);\n const rows: DiscoverRow[] = stations.map((station) => ({\n city: args.city,\n station,\n settlesFor: annotateSettlesFor(station, args.city),\n }));\n return Object.freeze({\n rows: Object.freeze(rows),\n city: args.city,\n source: \"discover\" as const,\n });\n}\n","// TS-W4 Plan 04 Task 2 — clipOutliers (winsorize) + PHYSICS_BOUNDS.\n//\n// Pure row→row port of Python `mostlyright.preprocessing.clip_outliers` at\n// packages/core/src/mostlyright/preprocessing.py:49-91. The v0.1.0 canonical\n// surface (supersedes the older `transforms.clip_outliers`).\n//\n// Decision tree (mirrors Python preprocessing.py:75-91):\n// 1. opts.bounds set → clip to explicit [lo, hi]\n// 2. PHYSICS_BOUNDS.has(col) → clip to physics defaults\n// 3. else → sigma fallback (mu ± std*sigma)\n//\n// Phase 3.5 review-iter HIGH fixes (preserved here):\n// - Architect iter-1 HIGH: std<=0 in the sigma branch silently collapses\n// every row to the mean. Python raises ValueError; we throw RangeError.\n// - Sigma=0 pass-through: when all values are identical, sample sigma is\n// zero and the clamp [mu, mu] would collapse the column. Pass values\n// through unchanged instead (a TS-side improvement on top of Python).\n//\n// Numeric coercion is STRICT: only `typeof v === 'number' && Number.isFinite(v)`\n// passes through. Strings like '5' do NOT auto-parse. Matches Wave 2/3/04-task1.\n\n/**\n * Physics-based clipping defaults for canonical observation columns.\n *\n * Mirrors Python `mostlyright.preprocessing.PHYSICS_BOUNDS` (preprocessing.py:34-46).\n * Values are `[min, max]` tuples in canonical units (°C for temp, m/s and kt\n * for wind, hPa for pressure, percent for humidity, mm for precip).\n *\n * Both `dew_point_c`/`dewpoint_c` and `wind_dir_deg`/`wind_dir_degrees` are\n * aliased to support legacy + canonical column names.\n */\nexport const PHYSICS_BOUNDS: ReadonlyMap<string, readonly [number, number]> = new Map([\n [\"temp_c\", [-89.0, 57.0] as const],\n [\"dew_point_c\", [-89.0, 35.0] as const],\n [\"dewpoint_c\", [-89.0, 35.0] as const],\n [\"wind_speed_ms\", [0.0, 100.0] as const],\n [\"wind_speed_kt\", [0.0, 200.0] as const],\n [\"wind_dir_deg\", [0.0, 360.0] as const],\n [\"wind_dir_degrees\", [0.0, 360.0] as const],\n [\"slp_hpa\", [870.0, 1085.0] as const],\n [\"sea_level_pressure_mb\", [870.0, 1085.0] as const],\n [\"relative_humidity_pct_2m\", [0.0, 100.0] as const],\n [\"precip_mm_1h\", [0.0, 305.0] as const],\n]);\n\nexport interface ClipOutliersOptions {\n /** Explicit `[lo, hi]` range. Overrides PHYSICS_BOUNDS and sigma fallback. */\n bounds?: readonly [number, number];\n /** Sigma multiplier for the fallback branch. Default 3.0. Must be > 0. */\n std?: number;\n}\n\n/**\n * Winsorize a numeric column.\n *\n * Mirrors Python `mostlyright.preprocessing.clip_outliers`. Returns rows with\n * a derived `{col}_clipped` column; the source `col` is preserved unchanged.\n *\n * Decision tree:\n * - `opts.bounds` set → clip to explicit `[lo, hi]`\n * - `PHYSICS_BOUNDS.has(col)` → clip to physics defaults\n * - else → sigma fallback (`mu ± std*sigma`)\n *\n * **Phase 3.5 review-iter fixes:**\n * - Throws `RangeError` if `std ≤ 0` in the sigma fallback (matches Python\n * `ValueError` at preprocessing.py:84-88; silent dataset corruption\n * otherwise).\n * - Sigma=0 pass-through: when all values are identical, sample sigma is\n * zero and the clamp `[mu, mu]` would collapse the column. Pass values\n * through unchanged instead.\n *\n * @param rows input rows (NOT mutated; pure function)\n * @param col column to clip\n * @param opts optional bounds / std overrides; defaults: PHYSICS_BOUNDS or sigma=3\n * @returns new array of rows, each carrying `{col}_clipped`\n * @throws RangeError if sigma fallback would use `std <= 0` or non-finite std\n */\nexport function clipOutliers<Row extends Record<string, unknown>>(\n rows: ReadonlyArray<Row>,\n col: string,\n opts: ClipOutliersOptions = {},\n): ReadonlyArray<Row & Record<string, number | null>> {\n const std = opts.std ?? 3.0;\n const key = `${col}_clipped`;\n\n // Determine clip range. `passThrough` short-circuits to \"copy value unchanged\"\n // for the sigma=0 / n<2 edge cases (Phase 3.5 review-iter HIGH fix).\n let lo: number;\n let hi: number;\n let passThrough = false;\n\n if (opts.bounds !== undefined) {\n [lo, hi] = opts.bounds;\n } else if (PHYSICS_BOUNDS.has(col)) {\n const b = PHYSICS_BOUNDS.get(col);\n if (b === undefined) {\n // Unreachable (we just checked has()), but the narrowing requires it.\n throw new Error(`PHYSICS_BOUNDS.get(${col}) unexpectedly undefined`);\n }\n [lo, hi] = b;\n } else {\n // Sigma fallback. Architect iter-1 HIGH: std<=0 collapses to mu.\n if (!Number.isFinite(std) || std <= 0) {\n throw new RangeError(\n `clipOutliers: std must be > 0 for the sigma fallback (got ${std}); pass bounds=[lo, hi] or use a physics-default column`,\n );\n }\n // Compute mu + sigma over non-null finite values.\n const vals: number[] = [];\n for (const r of rows) {\n const v = r?.[col];\n if (typeof v === \"number\" && Number.isFinite(v)) vals.push(v);\n }\n if (vals.length < 2) {\n // Not enough values to compute sample sigma → pass-through.\n passThrough = true;\n lo = Number.NEGATIVE_INFINITY;\n hi = Number.POSITIVE_INFINITY;\n } else {\n const mu = vals.reduce((a, b) => a + b, 0) / vals.length;\n const sumSq = vals.reduce((a, b) => a + (b - mu) ** 2, 0);\n const sigma = Math.sqrt(sumSq / (vals.length - 1)); // sample stdev (Bessel n-1)\n if (sigma === 0 || !Number.isFinite(sigma)) {\n // Phase 3.5 review-iter HIGH: pass values through unchanged\n // instead of collapsing to [mu, mu] (NOT NaN, NOT mu).\n passThrough = true;\n lo = Number.NEGATIVE_INFINITY;\n hi = Number.POSITIVE_INFINITY;\n } else {\n lo = mu - std * sigma;\n hi = mu + std * sigma;\n }\n }\n }\n\n const out: Array<Row & Record<string, number | null>> = [];\n for (const r of rows) {\n const v = r?.[col];\n let clipped: number | null;\n if (typeof v === \"number\" && Number.isFinite(v)) {\n clipped = passThrough ? v : Math.min(Math.max(v, lo), hi);\n } else {\n clipped = null;\n }\n out.push({ ...(r as Row), [key]: clipped } as Row & Record<string, number | null>);\n }\n return out;\n}\n","// TS-W4 Plan 06 — crosscheckIemGhcnh: disagreement detection between IEM +\n// GHCNh temperature readings. Mirrors Python\n// `mostlyright.qc.crosscheck_iem_ghcnh` at\n// `packages/core/src/mostlyright/qc.py:191-228`.\n//\n// Inner-joins by composite key `(station, eventTime)`. For matched pairs\n// where both temp_c values are finite numbers and the absolute delta\n// exceeds `opts.tolC` (default 2.0 °C), emits a disagreement row.\n//\n// Threshold is STRICT `>` (NOT `>=`) per Python qc.py:228 —\n// `merged.loc[merged[\"delta_c\"] > tol_c]`. A delta exactly equal to the\n// tolerance produces NO disagreement.\n//\n// Parity-Ticket: Python returns snake_case keys\n// (event_time, temp_c_iem, temp_c_ghcnh, delta_c); TS returns camelCase\n// (eventTime, tempCIem, tempCGhcnh, deltaC) to match the TS-idiom used\n// elsewhere in the codebase (see `obsQcStatus` from Wave 5). Wire-format\n// conversion to snake_case happens at the JSON serializer boundary\n// (TS-W3 Plan 07 `jsonDumps`).\n//\n// Lives at the `@mostlyrightmd/core/qc` subpath (NOT root barrel) to keep\n// the main `@mostlyrightmd/core` bundle under its 25 KB size-limit gate.\n\n/** Options for {@link crosscheckIemGhcnh}. */\nexport interface CrosscheckOptions {\n /**\n * Maximum acceptable absolute delta in °C between paired IEM/GHCNh\n * `temp_c` values. Defaults to `2.0` °C (matches Python\n * `crosscheck_iem_ghcnh(tol_c=2.0)`). A delta strictly greater than\n * `tolC` produces a disagreement row; equality does NOT.\n */\n tolC?: number;\n}\n\n/**\n * Disagreement row emitted by {@link crosscheckIemGhcnh}. Keys are\n * camelCase per the TS-idiom Parity-Ticket; Python's snake_case\n * equivalents are `event_time`, `temp_c_iem`, `temp_c_ghcnh`, `delta_c`.\n */\nexport interface CrosscheckDisagreement {\n readonly station: string;\n readonly eventTime: string;\n readonly tempCIem: number;\n readonly tempCGhcnh: number;\n readonly deltaC: number;\n}\n\n/**\n * Minimal row shape consumed by {@link crosscheckIemGhcnh}. Rows MUST\n * carry `station: string`, `eventTime: string`, and `temp_c: number |\n * null` (or `undefined`/non-finite, which are skipped). Additional keys\n * are allowed and ignored.\n */\ninterface CrosscheckRowIn {\n station?: unknown;\n eventTime?: unknown;\n temp_c?: unknown;\n}\n\n/**\n * Cross-check IEM and GHCNh temperatures; return rows where the two\n * sources disagree above `opts.tolC` (default 2.0 °C).\n *\n * Algorithm:\n * 1. If `iemRows.length === 0 || ghcnhRows.length === 0` → return `[]`\n * (matches Python qc.py:212-215).\n * 2. Validate `station` + `eventTime` present (string) on every input\n * row; throw `Error` on first violation (parity with Python\n * `ValueError` at qc.py:217-220).\n * 3. Build `iemMap: Map<string, IemRow>` keyed by\n * `${row.station}|${row.eventTime}`. On duplicate keys, LAST iem row\n * wins — deterministic but a documented deviation from Python's\n * `pd.merge` (which would cartesian-product duplicates).\n * 4. For each GHCNh row, look up the matching IEM row by composite key.\n * If missing → skip. If either `temp_c` is null / non-finite →\n * skip.\n * 5. If `Math.abs(iem.temp_c - ghcnh.temp_c) > tolC` → emit a\n * disagreement row. STRICT `>` (NOT `>=`).\n *\n * Output array order matches the iteration order of `ghcnhRows`\n * (deterministic, independent of `iemRows` order).\n *\n * Pure: input arrays are NOT mutated.\n *\n * @param iemRows IEM observation rows.\n * @param ghcnhRows GHCNh observation rows.\n * @param opts Tolerance options. `tolC` default = 2.0.\n * @throws Error if any iem or ghcnh row is missing `station` or\n * `eventTime` (or they are not strings).\n */\nexport function crosscheckIemGhcnh(\n iemRows: ReadonlyArray<CrosscheckRowIn>,\n ghcnhRows: ReadonlyArray<CrosscheckRowIn>,\n opts: CrosscheckOptions = {},\n): ReadonlyArray<CrosscheckDisagreement> {\n const tolC = opts.tolC ?? 2.0;\n\n if (iemRows.length === 0 || ghcnhRows.length === 0) return [];\n\n // Validate column presence upfront (parity with Python ValueError).\n for (const r of iemRows) {\n if (typeof r?.station !== \"string\" || typeof r?.eventTime !== \"string\") {\n throw new Error(\n \"crosscheckIemGhcnh: iem rows must carry 'station' (string) and 'eventTime' (string) keys\",\n );\n }\n }\n for (const r of ghcnhRows) {\n if (typeof r?.station !== \"string\" || typeof r?.eventTime !== \"string\") {\n throw new Error(\n \"crosscheckIemGhcnh: ghcnh rows must carry 'station' (string) and 'eventTime' (string) keys\",\n );\n }\n }\n\n // Build iem lookup map. Last-wins on duplicate (station, eventTime).\n const iemMap = new Map<string, CrosscheckRowIn>();\n for (const r of iemRows) {\n const key = `${r.station as string}|${r.eventTime as string}`;\n iemMap.set(key, r);\n }\n\n const out: CrosscheckDisagreement[] = [];\n for (const g of ghcnhRows) {\n const key = `${g.station as string}|${g.eventTime as string}`;\n const i = iemMap.get(key);\n if (i === undefined) continue;\n const iT = typeof i.temp_c === \"number\" && Number.isFinite(i.temp_c) ? i.temp_c : null;\n const gT = typeof g.temp_c === \"number\" && Number.isFinite(g.temp_c) ? g.temp_c : null;\n if (iT === null || gT === null) continue;\n const delta = Math.abs(iT - gT);\n if (delta > tolC) {\n out.push({\n station: g.station as string,\n eventTime: g.eventTime as string,\n tempCIem: iT,\n tempCGhcnh: gT,\n deltaC: delta,\n });\n }\n }\n return out;\n}\n","// mostlyright (meta) — convenience re-export of the three scoped packages.\n// Use this if you want a single `import { research } from \"mostlyright\"` entry point;\n// otherwise import the scoped packages directly.\n//\n// Note: each underlying package exports its own `version` constant; to avoid\n// ambiguous re-exports we expose them under namespaced module objects in\n// addition to a top-level `version` for the meta package itself.\n\nexport { helloCore } from \"@mostlyrightmd/core\";\nexport { helloWeather } from \"@mostlyrightmd/weather\";\nexport { helloMarkets } from \"@mostlyrightmd/markets\";\n\nimport * as core from \"@mostlyrightmd/core\";\nimport * as markets from \"@mostlyrightmd/markets\";\nimport * as weather from \"@mostlyrightmd/weather\";\n\nexport { core, markets, weather };\n\n// TS-W2 Wave 4: full multi-source `research()` orchestrator (AWC + IEM\n// ASOS + GHCNh + CLI). Lives here (NOT in @mostlyrightmd/core) so the core\n// package stays dep-free; the orchestrator pulls in both core + weather.\n// `PairsRow` is the canonical row shape from @mostlyrightmd/core/internal/pairs.\nexport { research, type ResearchOptions, type PairsRow } from \"./research.js\";\n\n// TS-W4 Wave 1: Mode 2 source-explicit dispatch (researchBySource +\n// assertSourceIdentity + Mode2Source const-union). Lives in the meta\n// package alongside research() — the dispatch needs the @mostlyrightmd/weather\n// Observation type and assertSourceIdentity consumes it structurally;\n// @mostlyrightmd/core must NOT depend on weather (cycle).\nexport {\n MODE2_SOURCES,\n SOURCE_ALIASES,\n assertSourceIdentity,\n isMode2Source,\n researchBySource,\n type Mode2Source,\n type ResearchBySourceOptions,\n type SourceMismatchRole,\n} from \"./mode2.js\";\n\n// Phase 10: composable research() dispatcher + discover() ergonomic\n// surface. Lives in @mostlyrightmd/meta because compose.ts pulls in both\n// @mostlyrightmd/core (cache, station registry) and @mostlyrightmd/markets\n// (Kalshi catalog + Polymarket catalog + denylist) — keeping it in\n// core would create a cycle (markets depends on core).\nexport {\n SELECTOR_NAMES,\n annotateSettlesFor,\n buildOverrideWarning,\n resolveCity,\n resolveContract,\n validateSelectors,\n type SelectorArgs,\n type SelectorName,\n type StationOverrideWarning,\n} from \"./compose.js\";\n\nexport { discover, type DiscoverResult, type DiscoverRow } from \"./discover.js\";\n\n// Phase 11 — `mostlyright.live` ticker surface re-exported through the\n// meta package so all three import shapes resolve to the same surface:\n// import { stream, latest } from \"mostlyright\" // meta\n// import { stream } from \"@mostlyrightmd/weather\" // main barrel\n// import { stream } from \"@mostlyrightmd/weather/live\" // subpath\nexport {\n POLITE_FLOORS_S,\n SOURCE_IDENTITY_TAGS,\n SUPPORTED_SOURCES,\n isLiveSource,\n latest,\n sourceTag,\n stream,\n validatePollSeconds,\n validateSource,\n type LatestOptions,\n type LiveObservation,\n type LiveSource,\n type LiveSourceTag,\n type StreamOptions,\n} from \"@mostlyrightmd/weather\";\nexport { LiveStreamError, NoLiveDataError } from \"@mostlyrightmd/core\";\n\n// Phase 21 21-05 — dailyExtremes wrapper surfaced via meta so callers\n// can `import { dailyExtremes } from \"mostlyright\"` matching Python\n// `mostlyright.international.daily_extremes`.\nexport {\n dailyExtremes,\n type DailyExtremeRow,\n type DailyExtremesMergeMode,\n type DailyExtremesOptions,\n} from \"@mostlyrightmd/weather\";\n\n// Phase 21 21-04 — obs(station, from, to, opts?) Phase 7 ingest-planner\n// surface re-exported via meta.\nexport {\n obs,\n type ObsOptions,\n type ObsRow,\n type ObsSourceFilter,\n type ObsStrategy,\n} from \"@mostlyrightmd/weather\";\n\n// Phase 21 21-10 — `preprocessing` lowercase namespace, matching Python\n// `mostlyright.preprocessing`. Surfaces `clipOutliers`, `PHYSICS_BOUNDS`,\n// `iemCrosscheck` under a single namespace so cross-language code reads\n// lexically symmetric:\n//\n// Python: from mostlyright.preprocessing import clip_outliers\n// TS: import { preprocessing } from \"mostlyright\"\n// preprocessing.clipOutliers(...)\n//\n// The PascalCase `Preprocessing` alias below is the deprecated form\n// (TSDoc @deprecated triggers IDE strikethrough); scheduled for removal\n// in v2.0.0. Both aliases point at the same module object.\nimport * as preprocessing from \"@mostlyrightmd/core/preprocessing\";\n\nexport { preprocessing };\n\n/**\n * @deprecated Use the lowercase `preprocessing` namespace instead — it\n * matches the Python `mostlyright.preprocessing` namespace. The\n * PascalCase alias is scheduled for removal in v2.0.0.\n */\nexport const Preprocessing = preprocessing;\n\n/**\n * Placeholder version string for the meta package. The authoritative\n * package version lives in `package.json#version` (currently\n * `0.1.0-rc.7`); this constant has not been bumped. Sibling packages\n * (`@mostlyrightmd/core` / `weather` / `markets`) each export their own\n * `version` constant, exposed here via the namespaced module objects.\n */\nexport const version = \"0.0.0\";\n"],"mappings":"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;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;AACF;AAEO,IAAMC,mBAAoD,oBAAI,IAAyB;EAC5F,CAAC,OAAOD,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;AACxB,CAAC;AAEM,IAAME,mBAAoD,oBAAI,IAAyB;EAC5F,CAAC,QAAQF,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,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MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;AACF;AAEO,IAAMC,mBAAoD,oBAAI,IAAyB;EAC5F,CAAC,OAAOD,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;AACxB,CAAC;AAEM,IAAME,mBAAoD,oBAAI,IAAyB;EAC5F,CAAC,QAAQF,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,C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","names":["src_exports","STATION_CODE_RE","obs","sleep","DATE_RE","obs","obs","fetchWithRetry","STATION_CODE_RE","buildIemUrl","parseIemCsv","sleep","STATION_CODE_RE","maybeNumber","STATIONS","STATION_BY_CODE","STATION_BY_ICAO","minRow","maxRow","LOW_COVERAGE_THRESHOLD","cToF","roundHalfUp","obs","obs","src_exports","version","coerceContractDate","version","version","target","version","STATIONS","STATION_BY_CODE","STATION_BY_ICAO","isLiveSource","MIN_YEAR","MAX_YEAR","_STATION_TZ","_JAN_REF","_OFFSET_CACHE","_lstOffsetHours","obs","AWC_MAX_HOURS","DATE_RE","normalizeStation","parseIsoDate","isLiveSource","obs","AWC_MAX_HOURS","DATE_RE","obs","version"]}
|
|
1
|
+
{"version":3,"sources":["../../weather/src/index.ts","../../weather/src/_fetchers/awc.ts","../../weather/src/_fetchers/iem-cli.ts","../../core/src/internal/merge/observations.ts","../../core/src/internal/merge/climate.ts","../../weather/src/_parsers/cli.ts","../../weather/src/_fetchers/ghcnh.ts","../../weather/src/_parsers/_station_translator.ts","../../weather/src/_parsers/ghcnh.ts","../../weather/src/live/sources.ts","../../weather/src/live/_fetch.ts","../../weather/src/live/latest.ts","../../weather/src/live/stream.ts","../../weather/src/forecasts/iem-mos.ts","../../weather/src/forecasts/nwp-stub.ts","../../weather/src/forecasts/open-meteo-models.ts","../../weather/src/forecasts/open-meteo.ts","../../core/src/data/generated/stations.ts","../../core/src/discovery/availability.ts","../../core/src/discovery/international.ts","../../core/src/exceptions/index.ts","../../core/src/formats/toon.ts","../../core/src/discovery/snapshot.ts","../../core/src/discovery/data-version.ts","../../core/src/discovery/describe.ts","../../weather/src/dailyExtremes.ts","../../weather/src/obs.ts","../../markets/src/index.ts","../../markets/src/data/generated/kalshi-stations.ts","../../markets/src/data/generated/polymarket-city-stations.ts","../../markets/src/resolvers/kalshi-nhigh.ts","../../markets/src/resolvers/kalshi-nlow.ts","../../markets/src/kalshi-settlement.ts","../../../node_modules/.pnpm/idb@8.0.3/node_modules/idb/build/index.js","../../core/src/internal/cache/types.ts","../../core/src/internal/cache/memory.ts","../../core/src/internal/cache/indexeddb.ts","../../core/src/internal/cache/versionedCacheStore.ts","../../core/src/data/generated/stations.ts","../../core/src/snapshot.ts","../../core/src/internal/cache/skip-rules.ts","../../core/src/internal/cache/keys.ts","../../core/src/internal/cache/index.browser.ts","../../core/src/snapshot.ts","../../core/src/internal/pairs.ts","../src/research.types.ts","../src/research.ts","../src/mode2.ts","../../markets/src/polymarket/types.ts","../../markets/src/polymarket/resolver.ts","../../markets/src/polymarket/known-wrong-stations.ts","../src/compose.ts","../src/discover.ts","../../core/src/transforms/clip.ts","../../core/src/qc/crosscheck.ts","../src/index.ts"],"sourcesContent":["// @mostlyrightmd/weather — weather fetchers + parsers.\n//\n// TS-W1 ships AWC (Wave 3) + IEM CLI (Wave 4). TS-W2 Plan 01 adds IEM ASOS\n// (yearly-chunk historical METARs) + the IEM CSV parser. Subsequent TS-W2\n// plans add GHCNh + mergeObservations; TS-W3 adds the disk cache.\n\n/**\n * Placeholder version string from the TS-W0 Wave 1 scaffold. The\n * authoritative package version lives in `package.json#version`\n * (currently `0.1.0-rc.7`); this constant has not been bumped.\n */\nexport const version = \"0.0.0\";\n\n/**\n * Smoke-test export from the TS-W0 Wave 1 scaffold. Returns the literal\n * string `\"hello @mostlyrightmd/weather\"`. Retained so the published\n * package has at least one importable runtime export until the scaffold\n * is removed in a later phase.\n */\nexport function helloWeather(): string {\n return \"hello @mostlyrightmd/weather\";\n}\n\n// TS-W1 Wave 3 — AWC live METARs.\nexport {\n AWC_MAX_HOURS,\n AWC_METAR_URL,\n fetchAwcMetars,\n type AwcMetarRaw,\n type FetchAwcOptions,\n} from \"./_fetchers/awc.js\";\n// Shared row contract: `Observation.source` widened in TS-W2 Plan 01 to\n// `\"awc\" | \"iem\" | \"ghcnh\"`. Each parser still emits its own literal.\nexport {\n awcToObservation,\n icaoToStationCode,\n mapCloudCover,\n parseAwcVisibility,\n type Observation,\n} from \"./_parsers/awc.js\";\n\n// TS-W1 Wave 4 — IEM CLI fetcher + range + parser.\nexport {\n downloadCli,\n downloadCliRange,\n IEM_CLI_BASE_URL,\n IEM_CLI_POLITE_DELAY_MS,\n type CliRawRecord,\n type DownloadCliRangeOptions,\n} from \"./_fetchers/iem-cli.js\";\nexport {\n parseCliRecord,\n parseCliResponse,\n mergeClimate,\n inferReportType,\n HIGH_TEMP_MAX_F,\n HIGH_TEMP_MIN_F,\n LOW_TEMP_MAX_F,\n LOW_TEMP_MIN_F,\n type ClimateObservation,\n type ReportType,\n} from \"./_parsers/cli.js\";\n\n// TS-W2 Plan 01 — IEM ASOS yearly-chunk fetcher + chunker + CSV parser.\nexport {\n yearlyChunksExclusiveEnd,\n type IsoDate,\n} from \"./_fetchers/_iem_chunks.js\";\nexport {\n buildIemUrl,\n downloadIemAsos,\n IEM_BASE_URL,\n IEM_POLITE_DELAY_MS,\n type DownloadIemAsosOptions,\n type IemChunkResult,\n} from \"./_fetchers/iem-asos.js\";\nexport {\n iemToObservation,\n parseIemCsv,\n type IemCsvRow,\n type IemObservationTypeOverride,\n type IemToObservationOptions,\n} from \"./_parsers/iem.js\";\n\n// TS-W2 Plan 02 — GHCNh PSV fetcher + parser + station-id translator.\nexport {\n downloadGhcnh,\n downloadGhcnhRange,\n GHCNH_BASE_URL,\n NCEI_POLITE_DELAY_MS,\n type DownloadGhcnhRangeOptions,\n type GhcnhYearResult,\n} from \"./_fetchers/ghcnh.js\";\nexport {\n parseGhcnhPsv,\n parseGhcnhRow,\n ghcnhStationToCode,\n extractStationCode,\n SSID_COLUMNS,\n} from \"./_parsers/ghcnh.js\";\n\n// Phase 11 — `mostlyright.live` ticker surface (stream + latest).\nexport {\n POLITE_FLOORS_S,\n SOURCE_IDENTITY_TAGS,\n SUPPORTED_SOURCES,\n isLiveSource,\n latest,\n sourceTag,\n stream,\n validatePollSeconds,\n validateSource,\n type LatestOptions,\n type LiveObservation,\n type LiveSource,\n type LiveSourceTag,\n type StreamOptions,\n} from \"./live/index.js\";\nexport { LiveStreamError, NoLiveDataError } from \"@mostlyrightmd/core\";\n\n// Phase 17 PLAN-11 — IEM MOS forecast fetcher + NWP stub (v1.0 TS lane).\n// Available via the root barrel AND via the `@mostlyrightmd/weather/forecasts`\n// subpath (subpath bundle stays lean for browser callers; root pulls all).\nexport {\n forecastNwp,\n iemMosForecasts,\n openMeteoForecasts,\n OPEN_METEO_MODELS,\n OPEN_METEO_PREVIOUS_RUNS_URL,\n OPEN_METEO_SINGLE_RUNS_URL,\n OPEN_METEO_LIVE_URL,\n OPEN_METEO_SEAMLESS_URL,\n type ForecastNwpOptions,\n type IemMosModel,\n type IemMosOptions,\n type IemMosRow,\n type IemMosSource,\n type NwpModel,\n type OpenMeteoMode,\n type OpenMeteoModel,\n type OpenMeteoOptions,\n type OpenMeteoRow,\n type OpenMeteoSource,\n} from \"./forecasts/index.js\";\n\n// Phase 21 21-05 — dailyExtremes(station, from, to, opts?) wrapper matching\n// Python `mostlyright.international.daily_extremes`. Composes existing\n// fetchers + internationalDailyExtremes for the rollup; surfaces under\n// the weather barrel and `mostlyright` meta package.\nexport { dailyExtremes } from \"./dailyExtremes.js\";\nexport type {\n DailyExtremeRow,\n DailyExtremesMergeMode,\n DailyExtremesOptions,\n} from \"./dailyExtremes.types.js\";\n\n// Phase 21 21-04 — obs(station, from, to, opts?) Phase 7 ingest-planner\n// surface. Smart-routes between exact_window / warm_cache / hosted\n// strategies; matches Python `tw.weather.obs` signature.\nexport { obs, resolveAutoStrategy } from \"./obs.js\";\nexport type {\n ObsOptions,\n ObsRow,\n ObsSourceFilter,\n ObsStrategy,\n} from \"./obs.types.js\";\n","// AWC METAR HTTP fetcher — live observations from aviationweather.gov.\n//\n// Ported byte-faithfully from\n// `packages/weather/src/mostlyright/weather/_fetchers/awc.py::fetch_awc_metars`.\n//\n// =============================================================================\n// CORS posture: NONE (per .planning/research/TS-CORS-MATRIX.md)\n// =============================================================================\n// AWC's `aviationweather.gov` does NOT emit any `Access-Control-Allow-*` headers.\n// This means a pure browser-app (no extension) cannot call this endpoint via\n// `fetch()` directly — the browser blocks the response.\n//\n// Workarounds:\n// 1. Chrome extension (MV3 service worker): declare host permissions in\n// manifest.json — `\"host_permissions\": [\"https://aviationweather.gov/*\"]`.\n// MV3 service workers bypass CORS for hosts they have permission for.\n// 2. CORS proxy: deploy a thin Cloudflare Worker (or similar) that fronts\n// this endpoint and emits `Access-Control-Allow-Origin: *`. Then point\n// this fetcher at the proxy URL via the `urlOverride` option (TODO: when\n// added in a future wave).\n// 3. Node.js / Deno / Bun / Cloudflare Worker runtime: no CORS — works\n// out of the box.\n//\n// See `.planning/research/TS-CORS-MATRIX.md` §AWC and `docs/chrome-extension-integration.md`\n// for the canonical workaround guidance.\n// =============================================================================\n//\n// The AWC live endpoint serves only recent observations — at most ~168 hours\n// (7 days). For historical multi-day fetches use IEM ASOS (lands in TS-W4).\n//\n// Return contract: ReadonlyArray<AwcMetarRaw>. Empty array on 4xx, timeout, or\n// exhausted retries — NEVER throws (matches Python `fetch_awc_metars` so the\n// caller can degrade gracefully when AWC is down).\n\nimport { type FetchWithRetryOptions, TherminalError, fetchWithRetry } from \"@mostlyrightmd/core\";\n\n/** Canonical AWC METAR endpoint. */\nexport const AWC_METAR_URL = \"https://aviationweather.gov/api/data/metar\";\n\n/**\n * AWC live serves at most ~168 hours (7 days). Beyond that the endpoint\n * either silently truncates or returns an empty list — use IEM ASOS for\n * history.\n */\nexport const AWC_MAX_HOURS = 168;\n\n/**\n * Raw AWC METAR record as returned by the public JSON endpoint.\n *\n * Fields are documented loosely — the upstream payload is not formally\n * schema-versioned. We pass-through optional fields to the parser\n * (`_parsers/awc.ts`) which validates them against bounds.\n */\nexport interface AwcMetarRaw {\n /** Four-letter ICAO identifier (e.g. \"KNYC\"). REQUIRED. */\n icaoId: string;\n /** Observation time as Unix epoch seconds. REQUIRED. */\n obsTime: number;\n metarType?: \"METAR\" | \"SPECI\" | string;\n /** Wind direction in degrees, or \"VRB\" for variable. */\n wdir?: number | \"VRB\" | string;\n wspd?: number | null;\n wgst?: number | null;\n /** Altimeter setting in hPa. */\n altim?: number | null;\n /** Sea-level pressure in mb/hPa. */\n slp?: number | null;\n /** Temperature in Celsius. */\n temp?: number | null;\n /** Dewpoint in Celsius. */\n dewp?: number | null;\n /** Visibility — may be number, \"10+\", \"1/2\", \"2 1/4\", \"M1/4\", etc. */\n visib?: string | number | null;\n clouds?: ReadonlyArray<{ cover?: string; base?: number | null }>;\n /** Raw METAR text — includes remarks. */\n rawOb?: string | null;\n /** Weather codes (e.g. \"RA BR\"). */\n wxString?: string | null;\n /** Precipitation past hour (inches). \"T\" = trace. */\n precip?: number | \"T\" | string | null;\n /** QC bitmask field. */\n qcField?: number | null;\n}\n\nexport interface FetchAwcOptions extends FetchWithRetryOptions {\n /** Lookback window in hours. Default 168 (max). Values above `AWC_MAX_HOURS` are clamped. */\n hours?: number;\n}\n\n/**\n * Fetch recent METARs from AWC for one or more ICAO stations.\n *\n * Returns the raw JSON array as-is (typed as `AwcMetarRaw[]`); compose with\n * `awcToObservation` from `../_parsers/awc.ts` to map each entry to the\n * observation row schema.\n *\n * Behaviour mirrors Python `fetch_awc_metars`:\n * - empty `stationIcaos` → return `[]` immediately, no HTTP issued\n * - 4xx after retry exhaustion → `[]`\n * - 5xx / network errors after retry budget → `[]`\n * - non-array JSON body → `[]`\n * - NEVER throws (callers want graceful degradation)\n */\nexport async function fetchAwcMetars(\n stationIcaos: ReadonlyArray<string>,\n opts: FetchAwcOptions = {},\n): Promise<ReadonlyArray<AwcMetarRaw>> {\n if (stationIcaos.length === 0) {\n return [];\n }\n\n const hours = Math.min(opts.hours ?? AWC_MAX_HOURS, AWC_MAX_HOURS);\n // Encode each component (defensive — caller should already have validated\n // ICAOs via station registry, but the URL shape must be safe regardless).\n const idsCsv = stationIcaos.map((s) => encodeURIComponent(s)).join(\",\");\n const url = `${AWC_METAR_URL}?ids=${idsCsv}&format=json&taf=false&hours=${hours}`;\n\n // Strip `hours` from the options forwarded to fetchWithRetry so the type\n // is exactly `FetchWithRetryOptions`. Note: `hours` is consumed above.\n const { hours: _consumed, ...retryOpts } = opts;\n void _consumed;\n\n let response: Response;\n try {\n response = await fetchWithRetry(url, retryOpts);\n } catch (err) {\n // Any TherminalError (404, 400, 401, 403, 429-after-exhaustion, 5xx-after-\n // exhaustion) OR raw network error → return [] (mirror Python). This is\n // the \"graceful degradation\" contract; orchestration layer decides whether\n // to surface a SourceUnavailableError instead.\n if (err instanceof TherminalError) {\n return [];\n }\n // Re-raise abort errors so callers that pass AbortSignals can cancel.\n if (err instanceof DOMException && (err.name === \"AbortError\" || err.name === \"TimeoutError\")) {\n // For caller-initiated aborts we re-throw; otherwise the timeout was\n // composed in by fetchWithRetry and would already have been retried.\n if (opts.signal?.aborted) {\n throw err;\n }\n return [];\n }\n return [];\n }\n\n let data: unknown;\n try {\n data = await response.json();\n } catch {\n return [];\n }\n\n if (!Array.isArray(data)) {\n return [];\n }\n\n return data as ReadonlyArray<AwcMetarRaw>;\n}\n","// IEM CLI (NWS climate) historical fetcher — settlement-grade source.\n//\n// Ported from `packages/weather/src/mostlyright/weather/_fetchers/iem_cli.py`\n// (TS-W1 Wave 4). Uses the native `fetch` via `@mostlyrightmd/core`'s\n// `fetchWithRetry`, so this module runs in browsers, Node 20+, Cloudflare\n// Workers, and Deno.\n//\n// CORS posture: OPEN (Access-Control-Allow-Origin: *). See\n// `.planning/research/TS-CORS-MATRIX.md`.\n//\n// Granularity is whole-year (one HTTP request per station-year). Callers\n// that want a window filter the parsed records downstream — IEM's cli.py\n// endpoint does not support partial-year requests.\n\nimport { NotFoundError, fetchWithRetry } from \"@mostlyrightmd/core\";\nimport type { FetchWithRetryOptions } from \"@mostlyrightmd/core\";\n\n/** IEM cli.py JSON endpoint. Mirrors Python `IEM_CLI_BASE_URL`. */\nexport const IEM_CLI_BASE_URL = \"https://mesonet.agron.iastate.edu/json/cli.py\";\n\n/**\n * Polite delay (ms) between range requests. Mirrors Python\n * `IEM_CLI_POLITE_DELAY = 1.0` — IEM runs on a university server.\n */\nexport const IEM_CLI_POLITE_DELAY_MS = 1000;\n\n/**\n * Station code regex (3-4 uppercase letters). Mirrors\n * `STATION_CODE_RE` in `@mostlyrightmd/core/internal/bounds`. Inlined here\n * because that helper is intentionally a deep-import in core; we don't\n * want fetchers transitively pulling in the validators barrel.\n *\n * NOTE: IEM CLI expects a 4-letter ICAO (e.g. `KNYC`). The shared regex\n * also accepts 3-letter NWS codes — we tighten the check below.\n */\nconst STATION_CODE_RE = /^[A-Z]{3,4}$/;\n\n/**\n * Raw record shape returned by IEM cli.py (post-unwrap of `{results: [...]}`).\n *\n * We capture only the fields consumed by the parser; everything else is\n * preserved as `unknown` so forward-compat is automatic when IEM adds\n * new columns.\n */\nexport interface CliRawRecord {\n /** Local climate day, YYYY-MM-DD. */\n valid: string;\n /** Observed daily high °F. Sentinel: \"M\" or empty string for missing. */\n high?: number | \"M\" | \"\" | null;\n /** Observed daily low °F. Sentinel: \"M\" or empty string for missing. */\n low?: number | \"M\" | \"\" | null;\n /** Product identifier, e.g. \"202501160620-KFFC-CDUS42-CLIATL\". */\n product?: string | null;\n [key: string]: unknown;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction validateIcao(stationIcao: string): void {\n if (typeof stationIcao !== \"string\" || !STATION_CODE_RE.test(stationIcao)) {\n throw new Error(\n `station_icao=${JSON.stringify(\n stationIcao,\n )} does not match STATION_CODE_RE (3-4 uppercase letters); refusing to use as URL component`,\n );\n }\n}\n\nfunction unwrapResults(data: unknown): ReadonlyArray<CliRawRecord> {\n // IEM cli.py returns either a bare array or `{results: [...]}`. Empty\n // bodies (e.g. `[]` or `{results: []}`) are treated as \"no records\".\n if (Array.isArray(data)) {\n return data as ReadonlyArray<CliRawRecord>;\n }\n if (\n data !== null &&\n typeof data === \"object\" &&\n \"results\" in data &&\n Array.isArray((data as { results: unknown }).results)\n ) {\n return (data as { results: ReadonlyArray<CliRawRecord> }).results;\n }\n // The Python port raises ValueError here; mirror that with a plain Error\n // so callers can catch it without depending on a core exception class.\n throw new Error(\n `Unexpected IEM CLI response shape: ${\n data === null ? \"null\" : Array.isArray(data) ? \"array\" : typeof data\n }`,\n );\n}\n\n/**\n * Download IEM CLI JSON for one station-year.\n *\n * URL: `${IEM_CLI_BASE_URL}?station={icao}&year={year}`.\n *\n * Response may be wrapped as `{\"results\": [...]}` — we unwrap and return\n * the inner array so downstream parsers always see the same shape.\n *\n * Throws {@link NotFoundError} on HTTP 404 (no data for that year);\n * {@link downloadCliRange} catches and continues. Other transport errors\n * propagate as the structured exceptions defined by `fetchWithRetry`.\n */\nexport async function downloadCli(\n stationIcao: string,\n year: number,\n opts: FetchWithRetryOptions = {},\n): Promise<ReadonlyArray<CliRawRecord>> {\n validateIcao(stationIcao);\n const url = `${IEM_CLI_BASE_URL}?station=${stationIcao}&year=${year}`;\n const response = await fetchWithRetry(url, opts);\n const data = (await response.json()) as unknown;\n return unwrapResults(data);\n}\n\nexport interface DownloadCliRangeOptions extends FetchWithRetryOptions {\n /**\n * Delay (ms) between successive year requests. Defaults to\n * {@link IEM_CLI_POLITE_DELAY_MS}.\n */\n politenessMs?: number;\n}\n\n/**\n * Download CLI records for an inclusive year range.\n *\n * Skips 404s (IEM \"no data for this year\") so multi-year backfills do\n * not abort when a station has gaps — mirrors Python's\n * `download_cli_range` 404-skip behavior. Other HTTP errors propagate.\n *\n * Between requests we sleep `politenessMs` (default\n * {@link IEM_CLI_POLITE_DELAY_MS}).\n */\nexport async function downloadCliRange(\n stationIcao: string,\n startYear: number,\n endYear: number,\n opts: DownloadCliRangeOptions = {},\n): Promise<ReadonlyArray<CliRawRecord>> {\n if (endYear < startYear) {\n throw new Error(`endYear (${endYear}) must be >= startYear (${startYear})`);\n }\n validateIcao(stationIcao);\n\n const politenessMs = opts.politenessMs ?? IEM_CLI_POLITE_DELAY_MS;\n // Strip `politenessMs` so it isn't forwarded to fetchWithRetry's strict opts.\n const { politenessMs: _drop, ...fetchOpts } = opts;\n void _drop;\n\n const out: CliRawRecord[] = [];\n for (let year = startYear; year <= endYear; year++) {\n if (year > startYear && politenessMs > 0) {\n await sleep(politenessMs);\n }\n try {\n const records = await downloadCli(stationIcao, year, fetchOpts);\n out.push(...records);\n } catch (err) {\n if (err instanceof NotFoundError) {\n // No data for that year — log-equivalent silent skip. Continue.\n continue;\n }\n throw err;\n }\n }\n return out;\n}\n","// Observation source-priority dedup — AWC > IEM > GHCNh with strict-> + first-seen tiebreak.\n//\n// Byte-faithful TS port of\n// `packages/core/src/mostlyright/_internal/merge/observations.py::merge_observations`.\n//\n// Why this lives in @mostlyrightmd/core (not @mostlyrightmd/weather): the merge\n// policy is a mostlyright-wide invariant — every settlement-grade path that\n// joins multi-source observations depends on it. Putting it in core keeps\n// the dep direction clean (weather + meta → core), avoids a circular\n// import between weather/parsers and the orchestrator, and matches the\n// Python layout (`_internal/merge/`).\n//\n// Type strategy: a STRUCTURAL `ObservationKey` interface (4 fields) avoids\n// pulling the full `Observation` shape from @mostlyrightmd/weather into core.\n// The function is generic over `T extends ObservationKey` so callers can\n// pass the full row type without losing fields.\n\n/**\n * Source priority — strictly greater means \"wins on tiebreak\".\n * Verbatim from Python `SOURCE_PRIORITY` at\n * `packages/core/src/mostlyright/_internal/merge/observations.py:18`.\n *\n * Frozen so consumers cannot mutate the policy at runtime.\n */\nexport const SOURCE_PRIORITY: Readonly<Record<string, number>> = Object.freeze({\n awc: 3,\n iem: 2,\n ghcnh: 1,\n});\n\n/**\n * Subset of `Observation` that `mergeObservations` needs to dedup +\n * priority-rank. The four fields below form the dedup key (first three)\n * plus the source string that the priority map keys on. Unknown sources\n * resolve to priority 0 (lose to any known source).\n */\nexport interface ObservationKey {\n readonly station_code: string;\n readonly observed_at: string;\n readonly observation_type: string;\n readonly source: string;\n}\n\n/**\n * Deduplicate observation rows by `(station_code, observed_at,\n * observation_type)`, keeping the row whose source has the highest\n * {@link SOURCE_PRIORITY}.\n *\n * Tiebreak: **STRICT `>`** — on equal priority the FIRST row seen wins.\n * This is the byte-faithful semantics of Python v0.14.1, which preserved\n * input order through `dict.values()`. TS uses `Map.values()` with the\n * same insertion-order guarantee.\n *\n * The order-dependent tiebreak is intentional: callers can rely on a\n * canonical fetch order (AWC live → IEM yearly chunk → GHCNh yearly\n * chunk) to deterministically pick the AWC row over IEM/GHCNh on tied\n * priority (which only happens for unknown sources in practice).\n *\n * Unknown source strings resolve to priority 0 (lose to any of awc/iem/\n * ghcnh). Empty input returns an empty array.\n *\n * Output order is `Map.values()` insertion order — first row per key\n * wins both priority AND output position when no later row outranks it.\n *\n * Generic over `T extends ObservationKey` so the consumer (e.g. the\n * `research()` orchestrator in TS-W2 Plan 06) can pass the full\n * `Observation` row type without losing fields. The returned array is\n * a freshly-allocated `T[]`, not the input array.\n */\nexport function mergeObservations<T extends ObservationKey>(\n rows: ReadonlyArray<T>,\n): ReadonlyArray<T> {\n const best = new Map<string, T>();\n for (const row of rows) {\n // Null-byte separator: station_code is `[A-Z]{3,4}`, observed_at is\n // `\\d{4}-...Z`, observation_type is `METAR|SPECI` — none can carry a\n // literal `\\x00`. Defense-in-depth against upstream parser bugs that\n // might leak weird characters.\n const key = `${row.station_code}\\x00${row.observed_at}\\x00${row.observation_type}`;\n const existing = best.get(key);\n if (existing === undefined) {\n best.set(key, row);\n continue;\n }\n const priority = SOURCE_PRIORITY[row.source] ?? 0;\n const existingPriority = SOURCE_PRIORITY[existing.source] ?? 0;\n if (priority > existingPriority) {\n // STRICT `>`: on equal priority the first-seen row stays.\n best.set(key, row);\n }\n }\n return Array.from(best.values());\n}\n","// Climate-row dedup — keep highest `report_type_priority` per (station, date).\n//\n// Migrated from `packages-ts/weather/src/_parsers/cli.ts::mergeClimate`\n// (TS-W1 Wave 4) to its canonical home under @mostlyrightmd/core/internal/merge\n// in TS-W2 Plan 04. Behavior is unchanged — only the module location moves.\n//\n// Byte-faithful TS port of `mostlyright._internal.merge.climate.merge_climate`\n// (Python), itself a lift of `_dedup_climate_rows` from\n// `monorepo-v0.14.1/ingest/storage/parquet.py:477-494`.\n//\n// Type strategy: structural `ClimateKey` interface (3 fields) so this\n// module does not pull `ClimateObservation` from @mostlyrightmd/weather into\n// @mostlyrightmd/core. Callers pass the full row type and the generic\n// preserves it.\n\n/**\n * Subset of `ClimateObservation` that `mergeClimate` needs.\n * The first two fields form the dedup key; `report_type_priority` is the\n * tiebreak field (codegen-sourced from `REPORT_TYPE_PRIORITY`).\n */\nexport interface ClimateKey {\n readonly station_code: string;\n readonly observation_date: string;\n readonly report_type_priority: number;\n}\n\n/**\n * Deduplicate climate rows by `(station_code, observation_date)`.\n *\n * Keeps the row with the highest `report_type_priority` using **STRICT `>`**\n * (not `>=`). First-seen wins at equal priority — this preserves the\n * overnight `final` (which IS the Kalshi settlement value) when a\n * `preliminary` arrives first in input order.\n *\n * Generic over `T extends ClimateKey` so consumers can pass the full\n * `ClimateObservation` shape without losing fields.\n *\n * Empty input returns an empty array.\n */\nexport function mergeClimate<T extends ClimateKey>(rows: ReadonlyArray<T>): ReadonlyArray<T> {\n const best = new Map<string, T>();\n for (const row of rows) {\n // Null-byte separator — station_code is `[A-Z]{3,4}`, observation_date\n // is `YYYY-MM-DD`; neither can carry a literal `\\x00`.\n const key = `${row.station_code}\\x00${row.observation_date}`;\n const existing = best.get(key);\n if (existing === undefined) {\n best.set(key, row);\n continue;\n }\n // Strict `>`; first-seen wins on equal priority.\n if (row.report_type_priority > existing.report_type_priority) {\n best.set(key, row);\n }\n }\n return Array.from(best.values());\n}\n","// IEM CLI daily climate report parser.\n//\n// Ported from `packages/weather/src/mostlyright/weather/_climate.py` —\n// THE Kalshi settlement source. Report-type priority determines dedup:\n// `final` (3.0) overwrites `preliminary` (1.0), but a second `final`\n// never overwrites the first (strict `>`, first-seen wins at equal\n// priority). The overnight `final` IS the Kalshi settlement value.\n//\n// `CLIMATE_REPORT_TYPE_PRIORITY` is consumed from `@mostlyrightmd/core`'s\n// codegen output — do not re-define here.\n\nimport { CLIMATE_REPORT_TYPE_PRIORITY } from \"@mostlyrightmd/core\";\n\nimport type { CliRawRecord } from \"../_fetchers/iem-cli.js\";\n\n/** Climate temp bounds from `specs/climate.json`. Inclusive. */\nexport const HIGH_TEMP_MIN_F = -60;\nexport const HIGH_TEMP_MAX_F = 150;\nexport const LOW_TEMP_MIN_F = -80;\nexport const LOW_TEMP_MAX_F = 130;\n\nexport type ReportType = \"final\" | \"ncei_final\" | \"correction\" | \"preliminary\" | \"estimated\";\n\nexport interface ClimateObservation {\n /** Station code (3-letter NWS or 4-letter ICAO, caller's choice). */\n station_code: string;\n /** Local climate day, YYYY-MM-DD. */\n observation_date: string;\n /** Daily high °F, rounded to int. `null` when missing or out-of-bounds. */\n high_temp_f: number | null;\n /** Daily low °F, rounded to int. `null` when missing or out-of-bounds. */\n low_temp_f: number | null;\n /** Inferred report type. */\n report_type: ReportType;\n /**\n * Numeric priority for dedup (final=3, ncei_final=2.5, correction=2,\n * preliminary=1, estimated=0). Sourced from\n * `CLIMATE_REPORT_TYPE_PRIORITY` in `@mostlyrightmd/core` codegen.\n */\n report_type_priority: number;\n /** Always `\"iem\"` for CLI records. */\n source: \"iem\";\n /** Raw NWS product identifier when present. */\n product_id: string | null;\n /** ISO 8601 UTC issuance time parsed from `product[:12]`, else `null`. */\n issued_at: string | null;\n}\n\n// Pre-compiled regexes — mirror Python's module-level `re.compile`.\nconst PRODUCT_TS_RE = /^(\\d{12})/;\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Parse the first-12-character product timestamp to a UTC `Date`.\n *\n * Format: `\"202501160620-KFFC-CDUS42-CLIATL\"` → `2025-01-16T06:20:00Z`.\n * Returns `null` for empty, malformed, or invalid calendar timestamps.\n */\nfunction parseProductTimestamp(product: string): Date | null {\n if (!product) return null;\n const m = PRODUCT_TS_RE.exec(product);\n if (m === null) return null;\n const ts = m[1];\n if (ts === undefined) return null;\n const year = Number.parseInt(ts.slice(0, 4), 10);\n const month = Number.parseInt(ts.slice(4, 6), 10);\n const day = Number.parseInt(ts.slice(6, 8), 10);\n const hour = Number.parseInt(ts.slice(8, 10), 10);\n const minute = Number.parseInt(ts.slice(10, 12), 10);\n if (\n !Number.isFinite(year) ||\n !Number.isFinite(month) ||\n !Number.isFinite(day) ||\n !Number.isFinite(hour) ||\n !Number.isFinite(minute)\n ) {\n return null;\n }\n // Reject hour/minute out of range — `Date.UTC` would silently roll forward.\n if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59) {\n return null;\n }\n const millis = Date.UTC(year, month - 1, day, hour, minute, 0, 0);\n if (!Number.isFinite(millis)) return null;\n const d = new Date(millis);\n // Round-trip check rejects e.g. \"20250230\" (Feb 30) silently rolling to Mar 2.\n if (\n d.getUTCFullYear() !== year ||\n d.getUTCMonth() !== month - 1 ||\n d.getUTCDate() !== day ||\n d.getUTCHours() !== hour ||\n d.getUTCMinutes() !== minute\n ) {\n return null;\n }\n return d;\n}\n\n/**\n * Parse an ISO date-only string (\"YYYY-MM-DD\") as a UTC `Date`. Returns\n * `null` if the string is missing/malformed or if the calendar date does\n * not exist (e.g. \"2025-02-30\"). Mirrors Python `date.fromisoformat`.\n */\nfunction parseObservationDate(observationDate: string): Date | null {\n if (!DATE_RE.test(observationDate)) return null;\n const year = Number.parseInt(observationDate.slice(0, 4), 10);\n const month = Number.parseInt(observationDate.slice(5, 7), 10);\n const day = Number.parseInt(observationDate.slice(8, 10), 10);\n if (month < 1 || month > 12 || day < 1 || day > 31) return null;\n const millis = Date.UTC(year, month - 1, day);\n const d = new Date(millis);\n if (d.getUTCFullYear() !== year || d.getUTCMonth() !== month - 1 || d.getUTCDate() !== day) {\n return null;\n }\n return d;\n}\n\n/**\n * Infer report type from product timestamp vs observation date.\n *\n * Rules (byte-faithful port of Python `_climate.infer_report_type`):\n * - No product → `\"preliminary\"` (safe default).\n * - Unparseable product or observation date → `\"preliminary\"`.\n * - Issued same day as observation → `\"preliminary\"`.\n * - Issued the next day, 04:00–10:00 UTC → `\"final\"` (overnight CLI window).\n * - Issued the next day outside that window → `\"correction\"`.\n * - Issued >1 day later → `\"correction\"`.\n */\nexport function inferReportType(\n product: string | null | undefined,\n observationDate: string,\n): ReportType {\n if (!product) return \"preliminary\";\n\n const issued = parseProductTimestamp(product);\n if (issued === null) return \"preliminary\";\n\n const obs = parseObservationDate(observationDate);\n if (obs === null) return \"preliminary\";\n\n // Compare issued *date* vs observation date in UTC days.\n const issuedDayUtc = Date.UTC(issued.getUTCFullYear(), issued.getUTCMonth(), issued.getUTCDate());\n const obsDayUtc = obs.getTime(); // already a UTC midnight from parseObservationDate\n const deltaDays = Math.round((issuedDayUtc - obsDayUtc) / 86_400_000);\n\n if (deltaDays <= 0) return \"preliminary\";\n if (deltaDays === 1) {\n const hour = issued.getUTCHours();\n if (hour >= 4 && hour <= 10) return \"final\";\n return \"correction\";\n }\n return \"correction\";\n}\n\n/** Parse a temperature sentinel into an integer °F or `null`. */\nfunction parseTemp(val: unknown): number | null {\n if (val === null || val === undefined || val === \"M\" || val === \"\") return null;\n const n = typeof val === \"number\" ? val : Number(val);\n if (!Number.isFinite(n)) return null;\n // Python `round(float(val))` uses banker's rounding (round-half-to-even).\n // CLI temps are int-valued in practice (e.g. 45, -3); the sub-degree edge\n // case is rare enough that `Math.round` (half-up) matches expectations.\n return Math.round(n);\n}\n\n/**\n * Parse one IEM CLI record into a {@link ClimateObservation}.\n *\n * Returns `null` if:\n * - `valid` is missing, non-string, or not a real calendar date, OR\n * - **both** high and low end up `null` after bounds checks.\n *\n * Mirrors Python `parse_cli_record`.\n */\nexport function parseCliRecord(\n record: CliRawRecord,\n stationCode: string,\n): ClimateObservation | null {\n const observationDate = record.valid;\n if (typeof observationDate !== \"string\" || observationDate.length === 0) return null;\n if (parseObservationDate(observationDate) === null) return null;\n\n let high = parseTemp(record.high);\n let low = parseTemp(record.low);\n\n if (high !== null && (high < HIGH_TEMP_MIN_F || high > HIGH_TEMP_MAX_F)) {\n high = null;\n }\n if (low !== null && (low < LOW_TEMP_MIN_F || low > LOW_TEMP_MAX_F)) {\n low = null;\n }\n\n if (high === null && low === null) return null;\n\n const product =\n typeof record.product === \"string\" && record.product.length > 0 ? record.product : null;\n\n const reportType = inferReportType(product, observationDate);\n const priority = CLIMATE_REPORT_TYPE_PRIORITY[reportType];\n // `priority` should always resolve — every ReportType is keyed in\n // CLIMATE_REPORT_TYPE_PRIORITY by construction. Guard for codegen drift.\n if (priority === undefined) {\n throw new Error(\n `report type ${JSON.stringify(reportType)} missing from CLIMATE_REPORT_TYPE_PRIORITY (codegen drift)`,\n );\n }\n\n let issuedAt: string | null = null;\n if (product !== null) {\n const issuedDt = parseProductTimestamp(product);\n if (issuedDt !== null) {\n // Format `YYYY-MM-DDTHH:MM:SSZ` to match Python's strftime output.\n issuedAt = `${issuedDt.toISOString().slice(0, 19)}Z`;\n }\n }\n\n return {\n station_code: stationCode,\n observation_date: observationDate,\n high_temp_f: high,\n low_temp_f: low,\n report_type: reportType,\n report_type_priority: priority,\n source: \"iem\",\n product_id: product,\n issued_at: issuedAt,\n };\n}\n\n/**\n * Parse a full IEM CLI response (post-unwrap) into climate observations,\n * filtering out records where both temps are missing or the date is\n * invalid. Mirrors Python `parse_cli_response`.\n */\nexport function parseCliResponse(\n data: ReadonlyArray<CliRawRecord>,\n stationCode: string,\n): ReadonlyArray<ClimateObservation> {\n const out: ClimateObservation[] = [];\n for (const record of data) {\n const parsed = parseCliRecord(record, stationCode);\n if (parsed !== null) out.push(parsed);\n }\n return out;\n}\n\n// Backward-compat re-export. mergeClimate canonically lives at\n// @mostlyrightmd/core/internal/merge as of TS-W2 Plan 04. Existing imports\n// from @mostlyrightmd/weather continue to work; new code should prefer\n// `import { mergeClimate } from \"@mostlyrightmd/core/internal/merge\"`.\nexport { mergeClimate } from \"@mostlyrightmd/core/internal/merge\";\n","// NCEI GHCNh per-year PSV fetcher — single station-year and inclusive range.\n//\n// Byte-faithful TS port of Python\n// `packages/weather/src/mostlyright/weather/_fetchers/ghcnh.py::download_ghcnh`\n// and `download_ghcnh_range`, with the following deliberate adaptations:\n//\n// 1. No disk cache. Python writes per-station PSVs under\n// `dest_dir/{station_id}/GHCNh_{station}_{year}.psv`; the TS port\n// returns the PSV body in-memory via `{ stationId, year, psv }`.\n// The disk-cache layer lands in TS-W3 — out of scope here.\n// 2. Station-id validation at the URL boundary uses an inline regex\n// (`^[A-Z0-9-]{1,32}$`) matching the path-traversal guard from Python\n// `_internal/_bounds.py::validate_ghcnh_id_for_path`. There's no path\n// here in TS-W2, so the regex check is the defense-in-depth measure.\n//\n// 404-as-no-data behavior in `downloadGhcnhRange` is byte-faithful — NCEI\n// returns 404 for station-years that have no published data (typical for\n// recent partial years or pre-1973 stations), and the range function\n// silently skips them. Other HTTP errors propagate.\n//\n// CORS posture: NCEI GHCNh emits `Access-Control-Allow-Origin: *` per\n// `.planning/research/TS-CORS-MATRIX.md` §GHCNh — OPEN. Works in browsers,\n// Node 20+, Cloudflare Workers, Deno.\n\nimport { NotFoundError, fetchWithRetry } from \"@mostlyrightmd/core\";\nimport type { FetchWithRetryOptions } from \"@mostlyrightmd/core\";\n\n/** NCEI GHCNh public archive base URL (no trailing slash). Mirrors Python `GHCNH_BASE_URL`. */\nexport const GHCNH_BASE_URL =\n \"https://www.ncei.noaa.gov/oa/global-historical-climatology-network/hourly/access\";\n\n/**\n * Polite delay (ms) between consecutive NCEI HTTP requests in range mode.\n * Mirrors Python `NCEI_POLITE_DELAY = 1.0` (s). The delay fires AFTER each\n * successful response; 404s and network errors do NOT pay the tax.\n */\nexport const NCEI_POLITE_DELAY_MS = 1000;\n\n/**\n * GHCNh station-id boundary regex. Permits the USAF-WBAN form\n * (`744860-94789`), the 11-char NCEI id (`USW00094789`), and short ICAO-like\n * tokens. Rejects path separators, NUL, and any whitespace before the value\n * ever reaches a URL path segment.\n */\nconst GHCNH_STATION_ID_RE = /^[A-Z0-9-]{1,32}$/;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction validateStationId(stationId: string): void {\n if (typeof stationId !== \"string\" || !GHCNH_STATION_ID_RE.test(stationId)) {\n throw new Error(\n `station_id=${JSON.stringify(\n stationId,\n )} does not match GHCNH_STATION_ID_RE (uppercase alphanumeric + hyphen, 1-32 chars); refusing to use as URL component`,\n );\n }\n}\n\nfunction buildGhcnhUrl(stationId: string, year: number): string {\n return `${GHCNH_BASE_URL}/by-year/${year}/psv/GHCNh_${stationId}_${year}.psv`;\n}\n\n/** One downloaded station-year PSV. */\nexport interface GhcnhYearResult {\n readonly stationId: string;\n readonly year: number;\n /** Raw PSV body returned by NCEI (text/plain, pipe-delimited). */\n readonly psv: string;\n}\n\nexport interface DownloadGhcnhRangeOptions extends FetchWithRetryOptions {\n /**\n * Delay (ms) between successive year requests. Defaults to\n * {@link NCEI_POLITE_DELAY_MS}. Set to `0` in unit tests.\n */\n politenessMs?: number;\n}\n\n/**\n * Download a NOAA GHCNh PSV for one station-year.\n *\n * URL: `${GHCNH_BASE_URL}/by-year/{year}/psv/GHCNh_{stationId}_{year}.psv`.\n *\n * @throws {NotFoundError} when NCEI returns 404 (no data for this station-year).\n * Callers that want 404-as-skip behavior should use {@link downloadGhcnhRange}.\n * @throws If `stationId` does not match `GHCNH_STATION_ID_RE`.\n * @throws Whatever `fetchWithRetry` propagates on persistent network/HTTP errors.\n */\nexport async function downloadGhcnh(\n stationId: string,\n year: number,\n opts: FetchWithRetryOptions = {},\n): Promise<GhcnhYearResult> {\n validateStationId(stationId);\n const url = buildGhcnhUrl(stationId, year);\n const response = await fetchWithRetry(url, opts);\n const psv = await response.text();\n return { stationId, year, psv };\n}\n\n/**\n * Download GHCNh PSVs for an inclusive year range.\n *\n * Iterates `[startYear, endYear]`. Years that return 404 (no data for that\n * station-year) are silently skipped — they do NOT appear in the output\n * array. Other HTTP errors propagate.\n *\n * `endYear < startYear` returns `[]` with zero HTTP requests.\n *\n * Polite delay fires AFTER each successful response only.\n */\nexport async function downloadGhcnhRange(\n stationId: string,\n startYear: number,\n endYear: number,\n opts: DownloadGhcnhRangeOptions = {},\n): Promise<ReadonlyArray<GhcnhYearResult>> {\n validateStationId(stationId);\n\n if (endYear < startYear) {\n return [];\n }\n\n const politenessMs = opts.politenessMs ?? NCEI_POLITE_DELAY_MS;\n const { politenessMs: _pmDrop, ...fetchOpts } = opts;\n void _pmDrop;\n\n const out: GhcnhYearResult[] = [];\n for (let year = startYear; year <= endYear; year++) {\n let result: GhcnhYearResult;\n try {\n result = await downloadGhcnh(stationId, year, fetchOpts);\n } catch (err) {\n if (err instanceof NotFoundError) {\n // 404 → silent skip. Mirrors Python L160-166 (log + continue).\n continue;\n }\n throw err;\n }\n out.push(result);\n if (politenessMs > 0) {\n await sleep(politenessMs);\n }\n }\n return out;\n}\n","// GHCNh Source_Station_ID → station_code translator.\n//\n// Byte-faithful TS port of Python\n// `packages/weather/src/mostlyright/weather/_ghcnh.py::ghcnh_station_to_code`\n// (L133-145) and `_extract_station_code` (L148-155) + the `_SSID_COLUMNS`\n// tuple verbatim.\n//\n// GHCNh PSV rows carry up to 11 Source_Station_ID columns (per variable);\n// we walk them in priority order and return the first ICAO-prefixed value\n// that resolves to a valid station code.\n\nimport { STATION_CODE_RE } from \"@mostlyrightmd/core/internal/bounds\";\n\nimport { icaoToStationCode } from \"./awc.js\";\n\n/**\n * Source_Station_ID column priority. Order matches Python `_SSID_COLUMNS`\n * tuple at `_ghcnh.py:45-57` EXACTLY (temperature first, then dew_point,\n * wind_speed, wind_direction, sea_level_pressure, altimeter, visibility,\n * then four sky_cover_summation layers).\n */\nexport const SSID_COLUMNS = [\n \"temperature_Source_Station_ID\",\n \"dew_point_temperature_Source_Station_ID\",\n \"wind_speed_Source_Station_ID\",\n \"wind_direction_Source_Station_ID\",\n \"sea_level_pressure_Source_Station_ID\",\n \"altimeter_Source_Station_ID\",\n \"visibility_Source_Station_ID\",\n \"sky_cover_summation_1_Source_Station_ID\",\n \"sky_cover_summation_2_Source_Station_ID\",\n \"sky_cover_summation_3_Source_Station_ID\",\n \"sky_cover_summation_4_Source_Station_ID\",\n] as const;\n\n/**\n * Extract the 3-letter station code from a GHCNh Source_Station_ID value.\n *\n * `\"ICAO-KJFK\"` → `\"JFK\"` (strip prefix, apply ICAO→code conversion)\n * `\"744860-94789\"` → `null` (WMO USAF-WBAN format, no ICAO prefix)\n * `\"\"` / `\"ICAO-\"` → `null`\n *\n * Returns `null` for any input that does not resolve to a value matching\n * `STATION_CODE_RE` (3-4 uppercase letters).\n */\nexport function ghcnhStationToCode(sourceStationId: string): string | null {\n if (!sourceStationId || !sourceStationId.startsWith(\"ICAO-\")) {\n return null;\n }\n const icao = sourceStationId.slice(5);\n if (!icao) {\n return null;\n }\n const code = icaoToStationCode(icao);\n if (STATION_CODE_RE.test(code)) {\n return code;\n }\n return null;\n}\n\n/**\n * Walk `SSID_COLUMNS` in priority order and return the first non-null\n * result from `ghcnhStationToCode`. Returns `null` if every column misses.\n */\nexport function extractStationCode(row: Readonly<Record<string, string>>): string | null {\n for (const col of SSID_COLUMNS) {\n const ssid = row[col] ?? \"\";\n const code = ghcnhStationToCode(ssid);\n if (code !== null) {\n return code;\n }\n }\n return null;\n}\n","// GHCNh PSV parser — pipe-delimited body in, Observation rows out.\n//\n// Byte-faithful TS port of Python\n// `packages/weather/src/mostlyright/weather/_ghcnh.py::parse_ghcnh_row` and\n// `parse_ghcnh_file`. The TS port consumes the PSV body in-memory (a\n// string returned by `downloadGhcnh`); the Python version walks a file\n// handle but the semantics are identical.\n//\n// GHCNh ships pre-QC'd data with per-variable `*_Quality_Code` columns.\n// We keep raw observations only (`{0, 1, 4, 5, \"\"}`) and drop QC-flagged\n// variables (`{2, 3, 6, 7, I, P, R, U}`). The empty-string acceptance is\n// load-bearing — many GHCNh rows omit Quality_Code entirely and would\n// otherwise drop silently (parity case 5).\n//\n// Unit conversions: m/s → kt, km → mi, m → ft, mm → in, cm → in.\n// Sky-cover layers 1-4 + per-layer baseht; present-weather codes from\n// pres_wx_AW{1..3}; raw_metar extracted from REM column.\n//\n// CSV implementation: hand-rolled pipe-split + header-map. NO `csv` dep —\n// the parser preserves bundle-size discipline (TS Architect rubric §2).\n\nimport {\n MAX_RAW_METAR_LEN,\n MAX_VISIBILITY_MILES,\n MAX_WX_CODES_LEN,\n MAX_YEAR,\n MIN_YEAR,\n SKY_BASE_MAX_FT,\n SLP_MAX_MB,\n SLP_MIN_MB,\n TEMP_MAX_C,\n TEMP_MIN_C,\n boundedFloat,\n boundedFloatMin,\n boundedInt,\n} from \"@mostlyrightmd/core/internal/bounds\";\nimport { celsiusToFahrenheit, hpaToInhg } from \"@mostlyrightmd/core/internal/convert\";\n\nimport { extractStationCode } from \"./_station_translator.js\";\nimport { type Observation, mapCloudCover } from \"./awc.js\";\n\nexport {\n SSID_COLUMNS,\n extractStationCode,\n ghcnhStationToCode,\n} from \"./_station_translator.js\";\n\n// ---------------------------------------------------------------------------\n// Constants (byte-faithful with Python `_ghcnh.py`)\n// ---------------------------------------------------------------------------\n\nconst MS_TO_KT = 1 / 0.514444;\nconst KM_TO_MI = 1 / 1.60934;\nconst M_TO_FT = 3.28084;\nconst MM_TO_IN = 1 / 25.4;\nconst CM_TO_IN = 1 / 2.54;\n\n/** ISO 8601 form GHCNh uses in its DATE column. Optional trailing `Z`. */\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z?$/;\n\n/** Quality_Code values that pass the raw-only filter. Empty string also accepts. */\nconst ALLOWED_QC: ReadonlySet<string> = new Set([\"0\", \"1\", \"4\", \"5\"]);\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction safeFloat(val: string): number | null {\n if (!val || val === \"NA\") return null;\n const f = Number(val);\n return Number.isFinite(f) ? f : null;\n}\n\nfunction safeInt(val: string): number | null {\n const f = safeFloat(val);\n return f === null ? null : Math.round(f);\n}\n\n/**\n * Quality_Code acceptance gate. Accepts {0, 1, 4, 5} and empty string.\n * Rejects {2, 3, 6, 7} and letter flags {I, P, R, U}. Empty-string\n * acceptance is critical — without it, ungraded rows drop silently\n * (parity case 5 KMSY Hurricane Francine).\n */\nfunction isQcAccepted(qc: string): boolean {\n const stripped = qc.trim();\n if (!stripped) return true;\n return ALLOWED_QC.has(stripped);\n}\n\n/** `\"SCT:04;\"` → `\"SCT\"` via {@link mapCloudCover}. */\nfunction parseSkyCover(val: string): string | null {\n if (!val) return null;\n let code: string;\n if (val.includes(\":\")) {\n const colonIdx = val.indexOf(\":\");\n code = val.slice(0, colonIdx);\n } else {\n code = val.endsWith(\";\") ? val.slice(0, -1) : val;\n }\n return mapCloudCover(code);\n}\n\n/** sky_cover_summation_baseht_N (meters) → integer feet (rounded, bounded). */\nfunction parseSkyBaseht(val: string): number | null {\n const meters = safeFloat(val);\n if (meters === null || meters < 0) return null;\n const feet = Math.round(meters * M_TO_FT);\n return feet <= SKY_BASE_MAX_FT ? feet : null;\n}\n\n/**\n * Parse `pres_wx_AW{1..3}` columns. GHCNh format is `\"TS:90\"` / `\"+RA:02\"`\n * (METAR text + WMO AW code) — extract the leading METAR token, filter\n * bare-numeric WMO codes, honor per-column Quality_Code (`{3, P}` reject).\n */\nfunction parseWeatherCodes(row: Record<string, string>): string | null {\n const codes: string[] = [];\n for (let i = 1; i <= 3; i++) {\n const val = row[`pres_wx_AW${i}`] ?? \"\";\n if (!val) continue;\n const qc = (row[`pres_wx_AW${i}_Quality_Code`] ?? \"\").trim();\n if (qc === \"3\" || qc === \"P\") continue;\n const code = val.includes(\":\") ? val.slice(0, val.indexOf(\":\")) : val;\n if (!code) continue;\n // Skip bare-numeric WMO codes (`\"02\"`, `\"+90\"`, `\"-10\"`).\n const numericProbe = code.replace(/^[+-]/, \"\");\n if (numericProbe.length > 0 && /^\\d+$/.test(numericProbe)) continue;\n codes.push(code);\n }\n if (codes.length === 0) return null;\n const result = codes.join(\" \");\n return result.length > MAX_WX_CODES_LEN ? result.slice(0, MAX_WX_CODES_LEN) : result;\n}\n\n/**\n * Calendar-validity round-trip for `YYYY-MM-DDTHH:MM:SS[Z]`. Returns true if\n * the parts round-trip exactly through `Date.UTC` — rejects `2025-02-30`,\n * `2025-13-01`, etc. Mirrors Python's `datetime.fromisoformat` exception path.\n */\nfunction isValidCalendarDate(iso: string): boolean {\n const stripped = iso.endsWith(\"Z\") ? iso.slice(0, -1) : iso;\n // Format-guard already applied by DATE_RE — substring math is safe.\n const year = Number.parseInt(stripped.slice(0, 4), 10);\n const month = Number.parseInt(stripped.slice(5, 7), 10);\n const day = Number.parseInt(stripped.slice(8, 10), 10);\n const hour = Number.parseInt(stripped.slice(11, 13), 10);\n const minute = Number.parseInt(stripped.slice(14, 16), 10);\n const second = Number.parseInt(stripped.slice(17, 19), 10);\n if (month < 1 || month > 12) return false;\n if (day < 1 || day > 31) return false;\n if (hour > 23 || minute > 59 || second > 59) return false;\n const millis = Date.UTC(year, month - 1, day, hour, minute, second, 0);\n if (!Number.isFinite(millis)) return false;\n const d = new Date(millis);\n return (\n d.getUTCFullYear() === year &&\n d.getUTCMonth() === month - 1 &&\n d.getUTCDate() === day &&\n d.getUTCHours() === hour &&\n d.getUTCMinutes() === minute &&\n d.getUTCSeconds() === second\n );\n}\n\n// ---------------------------------------------------------------------------\n// Row parser\n// ---------------------------------------------------------------------------\n\n/**\n * Parse one GHCNh PSV row (header→cell dict) into the canonical Observation\n * row. Returns `null` if the row should be skipped:\n * - Station code unresolvable from any `Source_Station_ID` column\n * - DATE missing / malformed / calendar-invalid / out-of-year-range\n * - ALL four key vars (temperature, dew_point_temperature, wind_speed,\n * sea_level_pressure) fail Quality_Code\n *\n * Output dict-key order is preserved verbatim to keep downstream\n * `JSON.stringify` byte-stable across SDKs.\n */\nexport function parseGhcnhRow(row: Readonly<Record<string, string>>): Observation | null {\n const stationCode = extractStationCode(row);\n if (stationCode === null) return null;\n\n const dateStr = row.DATE ?? \"\";\n if (!dateStr || !DATE_RE.test(dateStr)) return null;\n if (!isValidCalendarDate(dateStr)) return null;\n\n const year = Number.parseInt(dateStr.slice(0, 4), 10);\n if (year < MIN_YEAR || year > MAX_YEAR) return null;\n\n const observedAt = dateStr.endsWith(\"Z\") ? dateStr : `${dateStr}Z`;\n\n const reportType = row.temperature_Report_Type ?? \"\";\n const observationType: \"METAR\" | \"SPECI\" = reportType === \"FM16\" ? \"SPECI\" : \"METAR\";\n\n // Per-variable Quality_Code gates.\n const tempOk = isQcAccepted(row.temperature_Quality_Code ?? \"\");\n const dewpOk = isQcAccepted(row.dew_point_temperature_Quality_Code ?? \"\");\n const wspdOk = isQcAccepted(row.wind_speed_Quality_Code ?? \"\");\n const wdirOk = isQcAccepted(row.wind_direction_Quality_Code ?? \"\");\n const wgustOk = isQcAccepted(row.wind_gust_Quality_Code ?? \"\");\n const slpOk = isQcAccepted(row.sea_level_pressure_Quality_Code ?? \"\");\n const altimOk = isQcAccepted(row.altimeter_Quality_Code ?? \"\");\n const visOk = isQcAccepted(row.visibility_Quality_Code ?? \"\");\n const precipOk = isQcAccepted(row.precipitation_Quality_Code ?? \"\");\n const snowOk = isQcAccepted(row.snow_depth_Quality_Code ?? \"\");\n\n // Skip-row gate (Python L200-201).\n if (!(tempOk || dewpOk || wspdOk || slpOk)) return null;\n\n // Temperature (no rounding, bounded; °C native).\n const tempC = tempOk\n ? boundedFloat(safeFloat(row.temperature ?? \"\"), TEMP_MIN_C, TEMP_MAX_C, { field: \"temp_c\" })\n : null;\n const dewpC = dewpOk\n ? boundedFloat(safeFloat(row.dew_point_temperature ?? \"\"), TEMP_MIN_C, TEMP_MAX_C, {\n field: \"dewpoint_c\",\n })\n : null;\n // Phase 18: GHCNh tempF conversion path deferred from Phase 18 fix.\n // NCEI publishes hourly observations with `temperature` in °C as the\n // primary field; whether the underlying ASOS source data is integer-°F\n // native (with NCEI doing the °F→°C conversion server-side) is NOT\n // documented in the NCEI data dictionary. Until NCEI native-units\n // documentation is obtained, the existing celsiusToFahrenheit path is\n // preserved here as the safe default. AWC + IEM Tgroup-recovery fixes\n // (Phase 18 PREC-01 + PREC-02) cover the two sources whose raw METAR\n // carries an unambiguous Tgroup. See\n // .planning/phases/18-precision-fix-asos-integer-fahrenheit/18-CONTEXT.md\n // for the deferral rationale.\n const tempF = celsiusToFahrenheit(tempC);\n const dewpF = celsiusToFahrenheit(dewpC);\n\n // Wind direction (degrees, bounded 0..360).\n const windDir = wdirOk ? boundedInt(safeInt(row.wind_direction ?? \"\"), 0, 360) : null;\n\n // Wind speed/gust (m/s → kt, rounded, bounded).\n const windSpeedMs = wspdOk ? safeFloat(row.wind_speed ?? \"\") : null;\n const windGustMs = wgustOk ? safeFloat(row.wind_gust ?? \"\") : null;\n const windSpeedKt = boundedInt(\n windSpeedMs !== null ? Math.round(windSpeedMs * MS_TO_KT) : null,\n 0,\n 200,\n );\n const windGustKt = boundedInt(\n windGustMs !== null ? Math.round(windGustMs * MS_TO_KT) : null,\n 0,\n 250,\n );\n\n // Pressure.\n let slp = slpOk ? safeFloat(row.sea_level_pressure ?? \"\") : null;\n if (slp !== null && (slp < SLP_MIN_MB || slp > SLP_MAX_MB)) {\n slp = null;\n }\n const altimHpa = altimOk ? safeFloat(row.altimeter ?? \"\") : null;\n const altimInhg = hpaToInhg(altimHpa);\n\n // Visibility (km → mi, non-negative, clamped at MAX_VISIBILITY_MILES).\n const visKm = visOk ? safeFloat(row.visibility ?? \"\") : null;\n let visMiles: number | null = null;\n if (visKm !== null && visKm >= 0) {\n visMiles = Math.min(visKm * KM_TO_MI, MAX_VISIBILITY_MILES);\n }\n\n // Precipitation (mm → in, non-negative).\n const precipMm = precipOk ? safeFloat(row.precipitation ?? \"\") : null;\n const precipInches = precipMm !== null ? boundedFloatMin(precipMm * MM_TO_IN, 0.0) : null;\n\n // Snow depth (cm → in, non-negative).\n const snowCm = snowOk ? safeFloat(row.snow_depth ?? \"\") : null;\n const snowInches = snowCm !== null ? boundedFloatMin(snowCm * CM_TO_IN, 0.0) : null;\n\n // Sky cover layers 1-4 with per-layer QC.\n const skyCovers: Array<string | null> = [];\n const skyBases: Array<number | null> = [];\n for (let i = 1; i <= 4; i++) {\n const covQc = isQcAccepted(row[`sky_cover_summation_${i}_Quality_Code`] ?? \"\");\n const baseQc = isQcAccepted(row[`sky_cover_summation_baseht_${i}_Quality_Code`] ?? \"\");\n skyCovers.push(covQc ? parseSkyCover(row[`sky_cover_summation_${i}`] ?? \"\") : null);\n skyBases.push(baseQc ? parseSkyBaseht(row[`sky_cover_summation_baseht_${i}`] ?? \"\") : null);\n }\n\n // Weather codes.\n const weatherCodes = parseWeatherCodes(row as Record<string, string>);\n\n // Raw METAR from REM column. GHCNh wraps the METAR/SPECI in a prefix\n // like \"MET2025-09-10 14:51:00 METAR KMSY 101451Z ...\" — slice from the\n // leading METAR/SPECI marker so raw_metar starts with the canonical form.\n const rem = row.REM ?? \"\";\n let rawMetar: string | null = null;\n if (rem) {\n const idxMetar = rem.indexOf(\"METAR \");\n const idxSpeci = rem.indexOf(\"SPECI \");\n let cleaned: string;\n if (idxMetar >= 0 && (idxSpeci < 0 || idxMetar < idxSpeci)) {\n cleaned = rem.slice(idxMetar);\n } else if (idxSpeci >= 0) {\n cleaned = rem.slice(idxSpeci);\n } else {\n cleaned = rem;\n }\n rawMetar = cleaned.length > MAX_RAW_METAR_LEN ? cleaned.slice(0, MAX_RAW_METAR_LEN) : cleaned;\n }\n\n return {\n station_code: stationCode,\n observed_at: observedAt,\n observation_type: observationType,\n source: \"ghcnh\",\n temp_c: tempC,\n dewpoint_c: dewpC,\n temp_f: tempF,\n dewpoint_f: dewpF,\n wind_dir_degrees: windDir,\n wind_speed_kt: windSpeedKt,\n wind_gust_kt: windGustKt,\n altimeter_inhg: altimInhg,\n sea_level_pressure_mb: slp,\n sky_cover_1: skyCovers[0] ?? null,\n sky_base_1_ft: skyBases[0] ?? null,\n sky_cover_2: skyCovers[1] ?? null,\n sky_base_2_ft: skyBases[1] ?? null,\n sky_cover_3: skyCovers[2] ?? null,\n sky_base_3_ft: skyBases[2] ?? null,\n sky_cover_4: skyCovers[3] ?? null,\n sky_base_4_ft: skyBases[3] ?? null,\n visibility_miles: visMiles,\n weather_codes: weatherCodes,\n precip_1hr_inches: precipInches,\n peak_wind_gust_kt: null,\n peak_wind_dir: null,\n peak_wind_time: null,\n snow_depth_inches: snowInches,\n qc_field: null,\n raw_metar: rawMetar,\n };\n}\n\n/**\n * Parse a GHCNh PSV body string into Observation rows.\n *\n * Hand-rolled pipe-split — no `csv` dep. Empty body / header-only → `[]`.\n * Rows that {@link parseGhcnhRow} skips are omitted (no null entries).\n */\nexport function parseGhcnhPsv(psvBody: string): ReadonlyArray<Observation> {\n if (!psvBody) return [];\n // Normalize CRLF → LF, then split. Skip blank lines.\n const lines = psvBody.replace(/\\r/g, \"\").split(\"\\n\");\n let headerIdx = -1;\n for (let i = 0; i < lines.length; i++) {\n if ((lines[i] as string).length > 0) {\n headerIdx = i;\n break;\n }\n }\n if (headerIdx < 0) return [];\n const header = (lines[headerIdx] as string).split(\"|\");\n\n const out: Observation[] = [];\n for (let i = headerIdx + 1; i < lines.length; i++) {\n const line = lines[i] as string;\n if (line.length === 0) continue;\n const cells = line.split(\"|\");\n const row: Record<string, string> = {};\n for (let c = 0; c < header.length; c++) {\n const key = header[c] as string;\n row[key] = c < cells.length ? (cells[c] as string) : \"\";\n }\n const obs = parseGhcnhRow(row);\n if (obs !== null) out.push(obs);\n }\n return out;\n}\n","// Phase 11 — per-source registry + polite floors.\n//\n// Mirrors Python `mostlyright/live/_sources.py`. Source set + floor values are\n// kept byte-faithful with Python so cross-SDK consumers see the same\n// invariant. Any divergence triggers the dual-SDK parity gate.\n\n/** Canonical ordered tuple of supported sources. Order matters — keep AWC first. */\nexport const SUPPORTED_SOURCES = [\"awc\", \"iem\"] as const;\n\n/** Validated source enum derived from `SUPPORTED_SOURCES`. */\nexport type LiveSource = (typeof SUPPORTED_SOURCES)[number];\n\n/**\n * Minimum allowed poll cadence per source, in seconds.\n *\n * - AWC: 30s — aviationweather.gov has no documented rate limit but 30s is\n * the empirically-validated floor that won't trip anti-abuse heuristics.\n * - IEM: 60s — mesonet.agron.iastate.edu is a university server; IEM docs\n * explicitly ask for reasonable headroom above 1 req/s.\n */\nexport const POLITE_FLOORS_S: Readonly<Record<LiveSource, number>> = {\n awc: 30,\n iem: 60,\n};\n\n/**\n * Canonical per-source `source` field tag emitted on every observation row.\n *\n * `\"awc.live\"` / `\"iem.live\"` are the live-channel identity tags — distinct\n * from the archive-channel `\"awc\"` / `\"iem\"` written by the historical\n * fetchers. Cross-SDK parity: these match Python `SOURCE_IDENTITY_TAGS`.\n */\nexport const SOURCE_IDENTITY_TAGS = {\n awc: \"awc.live\",\n iem: \"iem.live\",\n} as const satisfies Readonly<Record<LiveSource, string>>;\n\nexport type LiveSourceTag = (typeof SOURCE_IDENTITY_TAGS)[LiveSource];\n\n/**\n * Normalize and validate a `source` option.\n *\n * @param source - Caller-supplied source string. `undefined`/`null` defaults\n * to the first entry in `SUPPORTED_SOURCES` (AWC). Case-insensitive.\n * @returns The normalized lowercase source name (one of `SUPPORTED_SOURCES`).\n * @throws `Error` when the source is not in `SUPPORTED_SOURCES`.\n */\nexport function validateSource(source: string | null | undefined): LiveSource {\n if (source === undefined || source === null) {\n return SUPPORTED_SOURCES[0];\n }\n const normalized = source.trim().toLowerCase();\n if (!isLiveSource(normalized)) {\n throw new Error(\n `unknown live source ${JSON.stringify(source)}; supported: ${JSON.stringify(\n SUPPORTED_SOURCES,\n )}`,\n );\n }\n return normalized;\n}\n\n/** Type guard: narrow a string to `LiveSource`. */\nexport function isLiveSource(s: string): s is LiveSource {\n return (SUPPORTED_SOURCES as ReadonlyArray<string>).includes(s);\n}\n\n/**\n * Apply the polite-floor invariant to a caller-supplied cadence.\n *\n * @param pollSeconds - Caller-supplied cadence. `undefined`/`null` → use the floor.\n * @param source - A *validated* source name (call `validateSource` first).\n * @returns The cadence to use, in seconds.\n * @throws `Error` when `pollSeconds` is below the polite floor.\n */\nexport function validatePollSeconds(\n pollSeconds: number | null | undefined,\n source: LiveSource,\n): number {\n const floor = POLITE_FLOORS_S[source];\n if (pollSeconds === undefined || pollSeconds === null) {\n return floor;\n }\n // Reject `NaN` and non-finite values BEFORE the floor comparison.\n // `NaN < floor` returns false (all NaN comparisons are false), so the\n // naive check would silently let `pollSeconds=NaN` through; `setTimeout(NaN)`\n // then fires immediately and the loop hammers the upstream — a hard\n // polite-floor violation. Same defence rejects `±Infinity`.\n if (!Number.isFinite(pollSeconds)) {\n throw new Error(\n `pollSeconds=${pollSeconds} is not a finite number; polite floor ${floor}s required for source=${JSON.stringify(\n source,\n )}`,\n );\n }\n if (pollSeconds < floor) {\n throw new Error(\n `pollSeconds=${pollSeconds} below polite floor ${floor}s for source=${JSON.stringify(\n source,\n )}`,\n );\n }\n return pollSeconds;\n}\n\n/** Map a validated source name to its canonical row-level identity tag. */\nexport function sourceTag(source: LiveSource): LiveSourceTag {\n return SOURCE_IDENTITY_TAGS[source];\n}\n","// Phase 11 — per-source live fetch dispatch (internal).\n//\n// Wraps the existing AWC + IEM fetchers and re-tags each parsed row with\n// the live-channel source identity (`\"awc.live\"` / `\"iem.live\"`). Errors\n// inside the fetcher are NOT caught here — callers (`stream`, `latest`)\n// have different error semantics and handle their own try/catch.\n\nimport { fetchAwcMetars } from \"../_fetchers/awc.js\";\nimport type { Observation } from \"../_parsers/awc.js\";\nimport { awcToObservation } from \"../_parsers/awc.js\";\n\nimport { type LiveSource, sourceTag } from \"./sources.js\";\nimport type { LiveObservation } from \"./types.js\";\n\n/**\n * Accept \"KNYC\" or \"NYC\" — emit the 4-letter ICAO form for fetchers.\n *\n * Defensive: trim + uppercase + add leading `K` for the 3-letter US ID\n * shortform. AWC accepts only ICAO; IEM accepts both, but normalizing here\n * keeps cache keys + log messages stable.\n */\nexport function normalizeStation(station: string): string {\n const s = station.trim().toUpperCase();\n if (s.length === 3) return `K${s}`;\n return s;\n}\n\n/** Re-tag a parsed `Observation` row with the live-channel source. */\nfunction asLiveObservation(obs: Observation, tag: LiveObservation[\"source\"]): LiveObservation {\n // Spread + override — the canonical Observation is `readonly`, so we\n // produce a new object rather than mutating.\n return { ...obs, source: tag };\n}\n\n/** Poll AWC once for the given station and return parsed observation rows. */\nexport async function fetchAwcLatest(station: string): Promise<LiveObservation[]> {\n const icao = normalizeStation(station);\n const raw = await fetchAwcMetars([icao], { hours: 1 });\n const tag = sourceTag(\"awc\");\n const rows: LiveObservation[] = [];\n for (const m of raw) {\n const obs = awcToObservation(m);\n if (obs !== null) {\n rows.push(asLiveObservation(obs, tag));\n }\n }\n return rows;\n}\n\n/** Today's UTC midnight as an `YYYY-MM-DD` ISO date string. */\nfunction todayUtcIso(): string {\n const d = new Date();\n const y = d.getUTCFullYear();\n const m = String(d.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(d.getUTCDate()).padStart(2, \"0\");\n return `${y}-${m}-${day}`;\n}\n\n/** Add one day to a `YYYY-MM-DD` UTC ISO string. */\nfunction nextDayIso(iso: string): string {\n const [y, m, d] = iso.split(\"-\").map(Number);\n // biome-ignore lint/style/noNonNullAssertion: split-by-`-` on a YYYY-MM-DD literal always yields 3 parts\n const dt = new Date(Date.UTC(y!, m! - 1, d!));\n dt.setUTCDate(dt.getUTCDate() + 1);\n const ny = dt.getUTCFullYear();\n const nm = String(dt.getUTCMonth() + 1).padStart(2, \"0\");\n const nd = String(dt.getUTCDate()).padStart(2, \"0\");\n return `${ny}-${nm}-${nd}`;\n}\n\n/** Subtract one day from a `YYYY-MM-DD` UTC ISO string. */\nfunction previousDayIso(iso: string): string {\n const [y, m, d] = iso.split(\"-\").map(Number);\n // biome-ignore lint/style/noNonNullAssertion: split-by-`-` on a YYYY-MM-DD literal always yields 3 parts\n const dt = new Date(Date.UTC(y!, m! - 1, d!));\n dt.setUTCDate(dt.getUTCDate() - 1);\n const py = dt.getUTCFullYear();\n const pm = String(dt.getUTCMonth() + 1).padStart(2, \"0\");\n const pd = String(dt.getUTCDate()).padStart(2, \"0\");\n return `${py}-${pm}-${pd}`;\n}\n\n/**\n * Poll IEM once for the given station and return parsed observation rows.\n *\n * Uses a direct one-day URL via `buildIemUrl` + `fetchWithRetry` rather than\n * `downloadIemAsos` because the TS `downloadIemAsos` normalizes the caller's\n * `start` to `${start.slice(0, 4)}-01-01` for cache-key stability — that's\n * the right call for `research()` but wrong for a live tick (would download\n * the full calendar year on every poll). Module import is dynamic so the\n * browser AWC-only bundle doesn't pull the IEM parser tree.\n */\nexport async function fetchIemLatest(station: string): Promise<LiveObservation[]> {\n const [{ fetchWithRetry }, { STATION_CODE_RE }, { buildIemUrl }, { parseIemCsv }] =\n await Promise.all([\n import(\"@mostlyrightmd/core\"),\n import(\"@mostlyrightmd/core/internal/bounds\"),\n import(\"../_fetchers/iem-asos.js\"),\n import(\"../_parsers/iem.js\"),\n ]);\n const icao = normalizeStation(station);\n const stationCode = icao.length === 4 && icao.startsWith(\"K\") ? icao.slice(1) : icao;\n // Validate the station code at the URL boundary BEFORE any HTTP call —\n // mirrors `downloadIemAsos::validateIcao`. We bypass `downloadIemAsos` (to\n // skip its year-normalization) but the validation guard MUST still apply\n // so a malformed station like `KNYC&data=foo` cannot inject IEM URL params.\n if (!STATION_CODE_RE.test(stationCode)) {\n throw new Error(\n `station=${JSON.stringify(\n stationCode,\n )} does not match STATION_CODE_RE (3-4 uppercase letters); refusing to use as IEM URL component`,\n );\n }\n const todayIso = todayUtcIso();\n // Include the PREVIOUS UTC day in the lookup window (iter-4 codex fix):\n // shortly after 00:00 UTC the current-day window is empty (IEM has no\n // observations ingested yet) but a minutes-old METAR exists for the\n // prior UTC day. A today-only fetch would always return empty in that\n // window. With (yesterday, tomorrow), the IEM endpoint serves the full\n // [yesterday 00:00Z, tomorrow 00:00Z) span (day2 is exclusive).\n const startIso = previousDayIso(todayIso);\n const endIso = nextDayIso(todayIso);\n // Mirror Python's METAR + SPECI fetch: IEM strips the SPECI keyword from\n // the raw METAR text and serves SPECIs only via report_type=4, so a\n // routine-only fetch would miss intra-hour specials and the `latest()`\n // pick could return an older METAR when a fresher SPECI exists.\n const tag = sourceTag(\"iem\");\n const rows: LiveObservation[] = [];\n for (const reportType of [3, 4] as const) {\n const url = buildIemUrl(stationCode, startIso, endIso, reportType);\n const response = await fetchWithRetry(url);\n const csv = await response.text();\n const override = reportType === 3 ? \"METAR\" : \"SPECI\";\n const obs = parseIemCsv(csv, { observationTypeOverride: override });\n for (const row of obs) {\n rows.push(asLiveObservation(row, tag));\n }\n }\n return rows;\n}\n\n/** Dispatch to the per-source fetch. */\nexport async function fetchLatest(station: string, source: LiveSource): Promise<LiveObservation[]> {\n switch (source) {\n case \"awc\":\n return fetchAwcLatest(station);\n case \"iem\":\n return fetchIemLatest(station);\n }\n}\n\n/**\n * Pick the row with the largest `observed_at`; SPECI > METAR at ties.\n *\n * `observed_at` is ISO 8601 ending in `Z` — lexicographic sort matches\n * chronological. Ties on `observed_at` resolve to SPECI (special report)\n * over METAR (routine) since SPECI is issued for a significant change.\n */\nexport function pickMostRecent(rows: ReadonlyArray<LiveObservation>): LiveObservation | null {\n if (rows.length === 0) return null;\n // biome-ignore lint/style/noNonNullAssertion: length guarded above\n let best = rows[0]!;\n for (let i = 1; i < rows.length; i++) {\n // biome-ignore lint/style/noNonNullAssertion: i bounded by rows.length\n const cur = rows[i]!;\n if (cur.observed_at > best.observed_at) {\n best = cur;\n } else if (\n cur.observed_at === best.observed_at &&\n cur.observation_type === \"SPECI\" &&\n best.observation_type !== \"SPECI\"\n ) {\n best = cur;\n }\n }\n return best;\n}\n","// Phase 11 — one-shot `latest()` fetch.\n//\n// Mirrors Python `mostlyright.live.latest`. Single-source poll: hit AWC or\n// IEM ONCE, parse the response, return the most-recent observation row\n// with its live source identity tag. No fusion, no cache, no QC.\n\nimport { NoLiveDataError } from \"@mostlyrightmd/core\";\n\nimport { fetchLatest, normalizeStation, pickMostRecent } from \"./_fetch.js\";\nimport { type LiveSource, sourceTag, validateSource } from \"./sources.js\";\nimport type { LiveObservation } from \"./types.js\";\n\nexport interface LatestOptions {\n /**\n * Live source to poll. `\"awc\"` (default, fastest) or `\"iem\"` (~10-min\n * delay; useful when AWC is down). Case-insensitive.\n */\n readonly source?: LiveSource | string | null;\n}\n\n/**\n * Return the most-recent observation row for `station` from a SINGLE source.\n *\n * Same fetch path as {@link stream}, but returns once instead of looping.\n * Use this for cron-style polling where you want one fresh observation per\n * invocation.\n *\n * @param station - ICAO (`\"KNYC\"`) or 3-letter US ID (`\"NYC\"`). Case-insensitive.\n * @param opts - Optional `{ source }`.\n *\n * @throws `Error` when `opts.source` is unknown.\n * @throws {@link NoLiveDataError} when the upstream returned no observations\n * for the station — payload carries the resolved `station` and live\n * `source` tag for branching.\n */\nexport async function latest(station: string, opts: LatestOptions = {}): Promise<LiveObservation> {\n const src: LiveSource = validateSource(opts.source ?? undefined);\n const rows = await fetchLatest(station, src);\n const picked = pickMostRecent(rows);\n if (picked === null) {\n throw new NoLiveDataError(\n `no live data for station=${JSON.stringify(station)} source=${JSON.stringify(src)}`,\n {\n station: normalizeStation(station),\n source: sourceTag(src),\n },\n );\n }\n return picked;\n}\n","// Phase 11 — `stream()` async generator.\n//\n// Mirrors Python `mostlyright.live.stream`. Continuous poll loop over a\n// single source. Yields each fresh observation exactly once (dedup by\n// `observed_at`), then sleeps for the polite-floor cadence.\n//\n// Cancellation: callers can `break` out of `for await` or call `.return()`\n// on the iterator. The polite-floor sleep uses an abortable Promise so the\n// loop terminates promptly rather than waiting out the full cadence.\n\nimport { fetchLatest, pickMostRecent } from \"./_fetch.js\";\nimport {\n type LiveSource,\n validatePollSeconds,\n validateSource,\n} from \"./sources.js\";\nimport type { LiveObservation } from \"./types.js\";\n\nexport interface StreamOptions {\n /**\n * Live source to poll. `\"awc\"` (default) or `\"iem\"`. Case-insensitive.\n */\n readonly source?: LiveSource | string | null;\n\n /**\n * Override the polite-floor cadence. Must be `>=` the per-source floor\n * (AWC=30, IEM=60). When omitted, uses the floor for the active source.\n */\n readonly pollSeconds?: number | null;\n\n /**\n * Optional `AbortSignal` for clean cancellation. When fired, the current\n * polite-floor sleep is interrupted and the generator returns. The current\n * in-flight fetch (if any) is allowed to complete — `AbortSignal` is not\n * threaded into the underlying fetchers (yet) since the v0.14.1 fetchers\n * are synchronous and wrapped via the platform fetch.\n */\n readonly signal?: AbortSignal;\n}\n\n/**\n * Abortable sleep helper — resolves on timeout OR on `signal.abort`.\n *\n * Critical: the abort listener MUST be removed once the sleep resolves\n * naturally (timeout fired), otherwise long-running streams accumulate\n * one listener per polling cycle until the signal aborts (or forever if\n * the stream is closed via `.return()` without aborting). Without the\n * removal, Node emits `MaxListenersExceededWarning` at 11 cycles + holds\n * onto closure memory indefinitely.\n */\nasync function sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\n if (signal?.aborted) return;\n await new Promise<void>((resolve) => {\n const onAbort = () => {\n clearTimeout(t);\n // Listener was registered with `{ once: true }` so we don't need\n // to remove it explicitly here, but we DO need to keep this branch\n // for the timeout case below.\n resolve();\n };\n const t = setTimeout(() => {\n // Remove the abort listener before resolving so it doesn't leak\n // past this cycle. `removeEventListener` is a no-op if the listener\n // wasn't attached (e.g. signal === undefined).\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Yield fresh observations for `station` from a SINGLE source on a\n * polite-floor cadence.\n *\n * The loop:\n * 1. Validate `source` + `pollSeconds` (throws BEFORE first poll).\n * 2. Poll once.\n * 3. If the most-recent observation's `observed_at` differs from the last\n * one yielded, yield it. Otherwise skip (dedup).\n * 4. `await sleep(pollSeconds)`.\n * 5. Loop.\n *\n * Empty responses (network error, fetcher returned `[]`) DO NOT abort the\n * stream — they're treated as \"nothing fresh yet\" and the loop continues\n * after the polite-floor sleep. To get a single-shot failure path, use\n * {@link latest}.\n *\n * @throws `Error` BEFORE the first poll when `opts.source` is unsupported\n * or `opts.pollSeconds` is below the polite floor.\n */\nexport async function* stream(\n station: string,\n opts: StreamOptions = {},\n): AsyncGenerator<LiveObservation> {\n const src: LiveSource = validateSource(opts.source ?? undefined);\n const cadenceS = validatePollSeconds(opts.pollSeconds ?? undefined, src);\n const cadenceMs = cadenceS * 1000;\n // Validate the station upfront — the IEM path throws on a malformed ICAO\n // (e.g. \"KN&data=foo\"). Without this guard, the stream's blanket catch\n // would swallow that ValueError and silently spin forever instead of\n // telling the caller their station is invalid.\n const icaoUpper = station.trim().toUpperCase();\n const stationCheck = icaoUpper.length === 3 ? `K${icaoUpper}` : icaoUpper;\n const STATION_CODE_RE = /^[A-Z]{3,4}$/;\n if (!STATION_CODE_RE.test(stationCheck)) {\n throw new Error(\n `station=${JSON.stringify(stationCheck)} does not match STATION_CODE_RE; ` +\n `refusing to start stream.`,\n );\n }\n let lastObservedAt: string | null = null;\n\n while (true) {\n if (opts.signal?.aborted) return;\n let rows: LiveObservation[] = [];\n try {\n rows = await fetchLatest(station, src);\n } catch (err) {\n // Only swallow transient/runtime errors. Validation-shaped errors\n // (Error message starts with `station=` from STATION_CODE_RE, or\n // is an `Error` with `validation` semantics) propagate so the\n // caller sees the diagnostic instead of an infinite empty-yield loop.\n // Heuristic: re-raise any `Error` whose message contains\n // \"STATION_CODE_RE\" (the only place the fetcher throws synchronously\n // before any HTTP). Everything else (network, 5xx, parse) is swallowed.\n if (err instanceof Error && /STATION_CODE_RE/.test(err.message)) {\n throw err;\n }\n // (We deliberately don't log here; the underlying fetchers already\n // log their own failures and a `console.error` from the SDK would\n // spam browser consoles.)\n rows = [];\n }\n const picked = pickMostRecent(rows);\n if (picked !== null) {\n const current = picked.observed_at;\n if (current && current !== lastObservedAt) {\n lastObservedAt = current;\n yield picked;\n }\n }\n if (opts.signal?.aborted) return;\n await sleep(cadenceMs, opts.signal);\n }\n}\n","// Phase 17 PLAN-11 — IEM MOS TS fetcher.\n//\n// Mirrors Python `packages/weather/src/mostlyright/weather/_fetchers/_iem_mos.py`.\n// Endpoint: https://mesonet.agron.iastate.edu/api/1/mos.json\n// CORS: OPEN per IEM ASOS posture (see `.planning/research/FORECAST-CORS-MATRIX.md`).\n\nimport type { IemMosModel, IemMosOptions, IemMosRow, IemMosSource } from \"./types.js\";\n\nconst IEM_MOS_URL = \"https://mesonet.agron.iastate.edu/api/1/mos.json\";\n\nconst SUPPORTED_MODELS: ReadonlySet<IemMosModel> = new Set([\"nbe\", \"gfs\", \"lav\", \"met\", \"ecm\"]);\n\nconst KT_TO_MS = 0.5144444;\n\nconst NBE_CYCLE_CUTOVER = Date.UTC(2026, 5 - 1, 5, 0, 0, 0); // 2026-05-05T00:00:00Z\n\n/**\n * Max number of MOS runtime-cycle requests in flight at once (GH #58).\n *\n * The fan-out is bounded rather than unbounded: a `Promise.all` over the full\n * day × runtime-hour grid would launch ~1,460 simultaneous requests for a\n * year-scale window, risking client connection limits, memory pressure, and\n * IEM-side rate limiting. A cap of 8 keeps essentially all of the small-window\n * speedup (typical ±1–3 day windows have ≤ 12 cycles) while staying polite for\n * large historical ranges.\n */\nexport const MOS_FETCH_CONCURRENCY = 8;\n\n/**\n * Map `items` through `fn` with at most `limit` invocations in flight at once,\n * returning results in input order (NOT resolution order). Bounded-concurrency\n * replacement for `Promise.all(items.map(fn))` — preserves the byte-identical\n * ordering the serial path produced while capping peak fan-out.\n *\n * Fail-fast: if any invocation throws, the shared `failed` flag stops the other\n * workers from pulling new items, so no further `fn` calls are dispatched once\n * the function is destined to reject. This restores the serial loop's behavior\n * of not issuing more requests after an error (codex review iter-3 P2). Workers\n * already mid-flight finish their current item; at most `limit` are in flight.\n */\nasync function mapWithConcurrency<T, R>(\n items: readonly T[],\n limit: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n let cursor = 0;\n let failed = false;\n async function worker(): Promise<void> {\n while (cursor < items.length && !failed) {\n const index = cursor++;\n try {\n results[index] = await fn(items[index] as T, index);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n const poolSize = Math.min(limit, items.length);\n await Promise.all(Array.from({ length: poolSize }, () => worker()));\n return results;\n}\n\n/** Pick the right NBE runtime-hour set based on the requested range. */\nfunction runtimeHoursFor(model: IemMosModel, fromDt: Date, toDt: Date): readonly number[] {\n if (model !== \"nbe\") return [0, 6, 12, 18];\n const fromMs = fromDt.getTime();\n const toMs = toDt.getTime();\n const pre = fromMs < NBE_CYCLE_CUTOVER;\n const post = toMs >= NBE_CYCLE_CUTOVER;\n if (pre && post) return [0, 1, 6, 7, 12, 13, 18, 19];\n if (pre) return [1, 7, 13, 19];\n return [0, 6, 12, 18];\n}\n\nfunction fahrenheitToCelsius(f: number | null): number | null {\n if (f === null || Number.isNaN(f)) return null;\n return ((f - 32) * 5) / 9;\n}\n\nfunction knotsToMs(kt: number | null): number | null {\n if (kt === null || Number.isNaN(kt)) return null;\n return kt * KT_TO_MS;\n}\n\nfunction percentToUnit(pct: number | null): number | null {\n if (pct === null || Number.isNaN(pct)) return null;\n return pct / 100;\n}\n\nfunction maybeNumber(value: unknown): number | null {\n if (value === null || value === undefined || value === \"M\" || value === \"\") {\n return null;\n }\n const num = typeof value === \"number\" ? value : Number(value);\n if (Number.isNaN(num)) return null;\n return num;\n}\n\nfunction parseDate(value: unknown): Date | null {\n if (typeof value !== \"string\" || !value) return null;\n const dt = new Date(value);\n if (Number.isNaN(dt.getTime())) return null;\n return dt;\n}\n\ninterface RawMosRow {\n readonly runtime?: string;\n readonly model_runtime?: string;\n readonly ftime?: string;\n readonly valid_time?: string;\n readonly tmp?: number | string | null;\n readonly dpt?: number | string | null;\n readonly wsp?: number | string | null;\n readonly wdr?: number | string | null;\n readonly pop12?: number | string | null;\n}\n\nfunction parseRow(\n raw: RawMosRow,\n station: string,\n model: IemMosModel,\n retrievedAt: string,\n): IemMosRow | null {\n const issuedDt = parseDate(raw.runtime ?? raw.model_runtime);\n const validDt = parseDate(raw.ftime ?? raw.valid_time);\n if (issuedDt === null || validDt === null) return null;\n const forecastHour = Math.round((validDt.getTime() - issuedDt.getTime()) / 3_600_000);\n return {\n station,\n model: model.toUpperCase(),\n issuedAt: issuedDt.toISOString(),\n validAt: validDt.toISOString(),\n forecastHour,\n tempC: fahrenheitToCelsius(maybeNumber(raw.tmp)),\n dewPointC: fahrenheitToCelsius(maybeNumber(raw.dpt)),\n windSpeedMs: knotsToMs(maybeNumber(raw.wsp)),\n windDirDeg: (() => {\n const d = maybeNumber(raw.wdr);\n return d === null ? null : Math.round(d);\n })(),\n precipProbability: percentToUnit(maybeNumber(raw.pop12)),\n skyCoverPct: null,\n source: \"iem.archive\" as IemMosSource,\n retrievedAt,\n };\n}\n\n/** Parse ISO `YYYY-MM-DD` to a UTC `Date` at 00:00:00. */\nfunction parseIsoDate(iso: string, endOfDay: boolean): Date {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(iso);\n if (match === null) {\n throw new Error(`iemMosForecasts: from/to dates must be ISO YYYY-MM-DD; got ${iso}`);\n }\n const [, y, m, d] = match;\n if (endOfDay) {\n return new Date(Date.UTC(Number(y), Number(m) - 1, Number(d), 23, 59, 59));\n }\n return new Date(Date.UTC(Number(y), Number(m) - 1, Number(d)));\n}\n\n/**\n * Fetch IEM MOS forecasts for `station` in `[fromDate, toDate]`.\n *\n * Mirrors Python `fetch_iem_mos(...)`. Iterates the model's runtime-hour\n * grid (NBE moved from {01,07,13,19}Z to {00,06,12,18}Z on 2026-05-05;\n * other models use {00,06,12,18}Z), GETs the JSON endpoint, and projects\n * rows to {@link IemMosRow}.\n *\n * 404 responses are silently skipped (many runtimes have no MOS data).\n * Empty input range returns `[]`.\n *\n * @throws `Error` if `model` is not in `SUPPORTED_MODELS`.\n */\nexport async function iemMosForecasts(\n station: string,\n fromDate: string,\n toDate: string,\n opts: IemMosOptions = {},\n): Promise<IemMosRow[]> {\n const model = opts.model ?? \"nbe\";\n if (!SUPPORTED_MODELS.has(model)) {\n throw new Error(\n `iemMosForecasts: model must be one of ${[...SUPPORTED_MODELS].sort().join(\",\")}; got ${model}`,\n );\n }\n const fetchFn = opts.fetchFn ?? fetch;\n const fromDt = parseIsoDate(fromDate, false);\n const toDt = parseIsoDate(toDate, true);\n const hours = runtimeHoursFor(model, fromDt, toDt);\n const retrievedAt = new Date().toISOString();\n\n // GH #58: collect every (day × runtime-hour) URL first, then fan out\n // concurrently with a bounded pool. The cycles are independent (nothing in\n // one depends on another's response), so the previous serial-await loop\n // was paying N round-trips of ~620–760 ms each (~3.2 s for 12 cycles).\n // Bounded fan-out (MOS_FETCH_CONCURRENCY) collapses the wall-clock to\n // ~ceil(N/limit) round-trips on typical short windows while staying polite\n // on large historical ranges (an unbounded Promise.all over a year-scale\n // window would launch ~1,460 simultaneous requests — codex review P2).\n // Row ordering is preserved by keeping the URL list in day-then-hour order\n // and indexing results by position — byte-identical output to the serial path.\n const dayMs = 86_400_000;\n const urls: string[] = [];\n for (let day = fromDt.getTime(); day <= toDt.getTime(); day += dayMs) {\n for (const h of hours) {\n const rt = new Date(day);\n rt.setUTCHours(h, 0, 0, 0);\n if (rt < fromDt || rt > toDt) continue;\n // IEM /api/1/mos.json regex ^(AVN|GFS|...|NBE|...)$ is uppercase-only;\n // sending lowercase returns HTTP 422 (issue #17). Mirrors the upper()\n // applied to `model` on returned rows above and the Python fix at\n // _iem_mos.py:239.\n urls.push(\n `${IEM_MOS_URL}?station=${encodeURIComponent(\n station,\n )}&model=${encodeURIComponent(model.toUpperCase())}&runtime=${encodeURIComponent(\n rt.toISOString(),\n )}`,\n );\n }\n }\n\n // Run the FULL per-cycle lifecycle (fetch → status check → body read → row\n // projection) inside the bounded pool, NOT just the header fetch. `fetch()`\n // resolves once headers arrive, so bounding only the fetch promise would\n // still leave unbounded response bodies open and defer error handling until\n // every URL had been requested (codex review iter-2 P2). Returning per-cycle\n // row arrays and flattening in input order keeps byte-identical output.\n const perCycle = await mapWithConcurrency(urls, MOS_FETCH_CONCURRENCY, async (url) => {\n const resp = (await fetchFn(url)) as Response;\n if (resp.status === 404) return [] as IemMosRow[];\n if (!resp.ok) {\n throw new Error(`iemMosForecasts: HTTP ${resp.status} on ${url}`);\n }\n const payload = (await resp.json()) as { data?: RawMosRow[] };\n const out: IemMosRow[] = [];\n for (const raw of payload.data ?? []) {\n const projected = parseRow(raw, station, model, retrievedAt);\n if (projected !== null) out.push(projected);\n }\n return out;\n });\n return perCycle.flat();\n}\n\nexport const __internal__ = {\n runtimeHoursFor,\n parseRow,\n NBE_CYCLE_CUTOVER,\n};\n","// Phase 17 PLAN-11 / Phase 21 21-07 — TS `forecastNwp()` stub.\n//\n// Per CONTEXT decision 7: TS NWP is deferred to v2.0+. No production-ready\n// browser GRIB2 decoder exists in May 2026; shipping a non-functional\n// runtime-error stub now means callers can write code against the\n// future-stable signature today — v2.0+ lands the execution body.\n//\n// Phase 21 21-07 upgrade: throws `DataAvailabilityError` (typed exception\n// from Phase 21 21-09). Post-21-07 follow-up: throws the more specific\n// `NwpNotAvailableError` subclass so consumers get `instanceof`-based\n// dispatch + `.station` / `.model` autocomplete instead of having to parse\n// the hint string. Back-compat preserved (NwpNotAvailableError extends\n// DataAvailabilityError with reason=\"model_unavailable\").\n\nimport { NwpNotAvailableError } from \"@mostlyrightmd/core\";\n\n/** NWP model enum — mirror of Python `SUPPORTED_NWP_MODELS` (24 entries). */\nexport type NwpModel =\n | \"hrrr\"\n | \"gfs\"\n | \"nbm\"\n // PLAN-03 NCEP family\n | \"hrrrak\"\n | \"gefs\"\n | \"gdas\"\n | \"rap\"\n | \"rrfs\"\n | \"rtma\"\n | \"urma\"\n | \"cfs\"\n // PLAN-04 ECMWF family\n | \"ecmwf_ifs_hres\"\n | \"ecmwf_ifs_ens\"\n | \"ecmwf_aifs_single\"\n | \"ecmwf_aifs_ens\"\n // PLAN-05 MSC Canadian family\n | \"hrdps\"\n | \"rdps\"\n | \"gdps\"\n | \"geps\"\n | \"reps\"\n // PLAN-06 NOMADS-only family\n | \"hafs\"\n | \"nam\"\n | \"href\"\n | \"hiresw\";\n\n/** Optional knobs for {@link forecastNwp}. */\nexport interface ForecastNwpOptions {\n /** Model run datetime — UTC ISO string. */\n readonly cycle?: string;\n /** Forecast hour ahead of `cycle`. */\n readonly fxx?: number;\n /** Force a mirror (e.g. `\"aws_bdp\"`). */\n readonly mirror?: string;\n /**\n * Ensemble member id (e.g. GEFS `\"p05\"`, CFS `\"03\"`). Mirrors the\n * Python `member=` kwarg (issue #74); only meaningful for GEFS / CFS.\n * Signature-forward only — TS NWP execution lands in v2.0+.\n */\n readonly member?: string;\n}\n\n/**\n * Major US stations with IEM MOS coverage. Phase 21 21-07 hint surface\n * — if the caller's station is in this set, the error hint includes an\n * `iemMosForecasts()` workaround pointer. Otherwise the hint mentions\n * the Python SDK fallback only.\n *\n * Source: Phase 17 IEM MOS catalog. The exact set may grow over time;\n * we keep the hint list narrow (7 stations) so the message stays terse.\n */\nconst IEM_MOS_COVERED_STATIONS: ReadonlySet<string> = new Set([\n \"KNYC\",\n \"KLAX\",\n \"KORD\",\n \"KMIA\",\n \"KDEN\",\n \"KSEA\",\n \"KATL\",\n]);\n\nfunction buildHint(station: string, model: NwpModel): string {\n const hasMosCoverage = IEM_MOS_COVERED_STATIONS.has(station.toUpperCase());\n const mosLine = hasMosCoverage\n ? `Workaround for ${station}: iemMosForecasts(\"${station}\", ...) is available (IEM MOS catalog covers this station).`\n : `Workaround: this station has no IEM MOS coverage; use the Python SDK's mostlyright.forecast_nwp() in v1.x.`;\n return `forecastNwp(${station}, \"${model}\") is a v1.x stub. Browser GRIB2 decode is not production-ready in May 2026 (no eccodes / cfgrib equivalent for the browser; WASM compile time + bundle size make it impractical for v1.x). ${mosLine} See https://mostlyright.md/docs/sdk/typescript/nwp-forecasts/ for the architectural reason + v2.0+ tracking.`;\n}\n\n/**\n * Fetch a gridded NWP forecast — **v1.x stub, deferred to v2.0+**.\n *\n * @remarks\n * **⚠️ Not yet implemented in TypeScript.** This function exists so callers\n * can write code against the future-stable signature, but it throws on\n * every call. Use the Python SDK (`mostlyright>=1.0`) for gridded NWP\n * today — it wires the NCEP family (HRRR, GFS, NBM, RAP, RRFS, …) end-to-end.\n *\n * **Why deferred:** GRIB2 decode requires native libraries (eccodes C\n * library or cfgrib Python wrapper). No production-ready browser-side\n * decoder exists in May 2026; a WASM port's compile time + bundle size\n * are impractical for v1.x. v2.0+ tracks the GRIB2 ecosystem maturity.\n *\n * **Workaround paths:**\n * - **7 major US stations** (KNYC, KLAX, KORD, KMIA, KDEN, KSEA, KATL) →\n * {@link iemMosForecasts} ships MOS-based forecasts that solve most use\n * cases. The error hint includes this pointer automatically.\n * - **Everything else** → use the Python SDK\n * (`pip install mostlyrightmd-weather`).\n *\n * @throws {@link NwpNotAvailableError} on every call (v1.x). The error\n * is a subclass of `DataAvailabilityError`, so existing\n * `catch (e instanceof DataAvailabilityError)` paths continue to catch\n * it. The thrown instance carries typed `.station` and `.model`\n * properties for log/error attribution.\n *\n * @see {@link https://mostlyright.md/docs/sdk/typescript/nwp-forecasts/ | docs/nwp-forecasts.md}\n * for the full architectural rationale, the supported-model list, and\n * the v2.0+ roadmap.\n */\nexport async function forecastNwp(\n station: string,\n model: NwpModel,\n _opts: ForecastNwpOptions = {},\n): Promise<never> {\n throw new NwpNotAvailableError({\n station,\n model,\n hint: buildHint(station, model),\n });\n}\n","// Phase 20 OM-08 — Open-Meteo 36-model registry (TS mirror of Python\n// `packages/weather/src/mostlyright/weather/_fetchers/_open_meteo_models/`).\n//\n// Single-file flat-table emission per D-10 bundle-size budget (TS lockstep\n// pattern: Python uses per-family modules for code-org clarity; TS uses a\n// flat table for byte-efficiency).\n\nimport type { OpenMeteoModel } from \"./types.js\";\n\n/** The canonical 36-model set (D-02 set-equality lock). */\nexport const OPEN_METEO_MODELS: ReadonlySet<OpenMeteoModel> = new Set([\n // NCEP (8)\n \"gfs_seamless\",\n \"gfs_global\",\n \"gfs_graphcast025\",\n \"aigfs025\",\n \"hgefs025\",\n \"ncep_hrrr_conus\",\n \"ncep_nbm_conus\",\n \"ncep_nam_conus\",\n // ECMWF (3)\n \"ecmwf_ifs025\",\n \"ecmwf_ifs_hres\",\n \"ecmwf_aifs025_single\",\n // DWD (5)\n \"dwd_icon_seamless\",\n \"dwd_icon_global\",\n \"dwd_icon_eu\",\n \"dwd_icon_d2\",\n \"dwd_icon_d2_15min\",\n // Météo-France (6)\n \"meteofrance_seamless\",\n \"meteofrance_arpege_world025\",\n \"meteofrance_arpege_europe\",\n \"meteofrance_arome_france0025\",\n \"meteofrance_arome_france_hd\",\n \"meteofrance_arome_france_hd_15min\",\n // Asia + Oceania (8)\n \"jma_seamless\",\n \"jma_gsm\",\n \"jma_msm\",\n \"kma_seamless\",\n \"kma_gdps\",\n \"kma_ldps\",\n \"cma_grapes_global\",\n \"bom_access_global\",\n // Europe (3)\n \"ukmo_global_deterministic_10km\",\n \"ukmo_uk_deterministic_2km\",\n \"metno_nordic_pp\",\n // GEM Canada (3)\n \"cmc_gem_gdps\",\n \"cmc_gem_rdps\",\n \"cmc_gem_hrdps\",\n]);\n\nconst SIX_HOURLY = [0, 6, 12, 18] as const;\nconst THREE_HOURLY = [0, 3, 6, 9, 12, 15, 18, 21] as const;\nconst HOURLY = Array.from({ length: 24 }, (_, i) => i) as readonly number[];\nconst TWELVE_HOURLY = [0, 12] as const;\n\n/** Per-model UTC cycle hours. Mirrors Python `CYCLE_HOURS`. */\nexport const CYCLE_HOURS: ReadonlyMap<OpenMeteoModel, readonly number[]> = new Map<\n OpenMeteoModel,\n readonly number[]\n>([\n // NCEP\n [\"gfs_seamless\", SIX_HOURLY],\n [\"gfs_global\", SIX_HOURLY],\n [\"gfs_graphcast025\", SIX_HOURLY],\n [\"aigfs025\", SIX_HOURLY],\n [\"hgefs025\", SIX_HOURLY],\n [\"ncep_hrrr_conus\", HOURLY],\n [\"ncep_nbm_conus\", HOURLY],\n [\"ncep_nam_conus\", SIX_HOURLY],\n // ECMWF\n [\"ecmwf_ifs025\", SIX_HOURLY],\n [\"ecmwf_ifs_hres\", SIX_HOURLY],\n [\"ecmwf_aifs025_single\", SIX_HOURLY],\n // DWD\n [\"dwd_icon_seamless\", SIX_HOURLY],\n [\"dwd_icon_global\", SIX_HOURLY],\n [\"dwd_icon_eu\", SIX_HOURLY],\n [\"dwd_icon_d2\", THREE_HOURLY],\n [\"dwd_icon_d2_15min\", THREE_HOURLY],\n // Météo-France\n [\"meteofrance_seamless\", SIX_HOURLY],\n [\"meteofrance_arpege_world025\", SIX_HOURLY],\n [\"meteofrance_arpege_europe\", SIX_HOURLY],\n [\"meteofrance_arome_france0025\", THREE_HOURLY],\n [\"meteofrance_arome_france_hd\", THREE_HOURLY],\n [\"meteofrance_arome_france_hd_15min\", THREE_HOURLY],\n // Asia + Oceania\n [\"jma_seamless\", SIX_HOURLY],\n [\"jma_gsm\", SIX_HOURLY],\n [\"jma_msm\", THREE_HOURLY],\n [\"kma_seamless\", SIX_HOURLY],\n [\"kma_gdps\", SIX_HOURLY],\n [\"kma_ldps\", THREE_HOURLY],\n [\"cma_grapes_global\", SIX_HOURLY],\n [\"bom_access_global\", SIX_HOURLY],\n // Europe\n [\"ukmo_global_deterministic_10km\", SIX_HOURLY],\n [\"ukmo_uk_deterministic_2km\", THREE_HOURLY],\n [\"metno_nordic_pp\", HOURLY],\n // GEM Canada\n [\"cmc_gem_gdps\", TWELVE_HOURLY],\n [\"cmc_gem_rdps\", SIX_HOURLY],\n [\"cmc_gem_hrdps\", SIX_HOURLY],\n]);\n\n/** Per-model publish-lag hours for Live mode cycle-math fallback (D-06). */\nexport const PUBLISH_LAG_HOURS: ReadonlyMap<OpenMeteoModel, number> = new Map<\n OpenMeteoModel,\n number\n>([\n // NCEP — global 6h, regional/mesoscale 2h\n [\"gfs_seamless\", 6],\n [\"gfs_global\", 6],\n [\"gfs_graphcast025\", 6],\n [\"aigfs025\", 6],\n [\"hgefs025\", 6],\n [\"ncep_hrrr_conus\", 2],\n [\"ncep_nbm_conus\", 2],\n [\"ncep_nam_conus\", 2],\n // ECMWF — global 6h\n [\"ecmwf_ifs025\", 6],\n [\"ecmwf_ifs_hres\", 6],\n [\"ecmwf_aifs025_single\", 6],\n // DWD\n [\"dwd_icon_seamless\", 4],\n [\"dwd_icon_global\", 4],\n [\"dwd_icon_eu\", 4],\n [\"dwd_icon_d2\", 2],\n [\"dwd_icon_d2_15min\", 2],\n // Météo-France\n [\"meteofrance_seamless\", 4],\n [\"meteofrance_arpege_world025\", 6],\n [\"meteofrance_arpege_europe\", 4],\n [\"meteofrance_arome_france0025\", 2],\n [\"meteofrance_arome_france_hd\", 2],\n [\"meteofrance_arome_france_hd_15min\", 2],\n // Asia + Oceania\n [\"jma_seamless\", 6],\n [\"jma_gsm\", 6],\n [\"jma_msm\", 2],\n [\"kma_seamless\", 6],\n [\"kma_gdps\", 6],\n [\"kma_ldps\", 2],\n [\"cma_grapes_global\", 6],\n [\"bom_access_global\", 6],\n // Europe\n [\"ukmo_global_deterministic_10km\", 6],\n [\"ukmo_uk_deterministic_2km\", 2],\n [\"metno_nordic_pp\", 2],\n // GEM Canada\n [\"cmc_gem_gdps\", 6],\n [\"cmc_gem_rdps\", 4],\n [\"cmc_gem_hrdps\", 2],\n]);\n\n// ---------------------------------------------------------------------------\n// Cycle math primitives (mirror Python `cycle_math.py`).\n// ---------------------------------------------------------------------------\n\n/**\n * Snap `valueUtcMs` (epoch ms) down to the most recent cycle hour <= value.\n * Returns the floored epoch ms.\n */\nexport function floorToCycleMs(valueUtcMs: number, cycleHours: readonly number[]): number {\n if (cycleHours.length === 0) {\n throw new Error(\"cycleHours must be non-empty\");\n }\n const d = new Date(valueUtcMs);\n const hour = d.getUTCHours();\n const sorted = [...cycleHours].sort((a, b) => a - b);\n const candidates = sorted.filter((h) => h <= hour);\n if (candidates.length > 0) {\n const targetHour = candidates[candidates.length - 1];\n return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), targetHour, 0, 0, 0);\n }\n const lastCycle = sorted[sorted.length - 1];\n const prior = new Date(valueUtcMs - 86_400_000);\n return Date.UTC(\n prior.getUTCFullYear(),\n prior.getUTCMonth(),\n prior.getUTCDate(),\n lastCycle,\n 0,\n 0,\n 0,\n );\n}\n\n/**\n * Conservative lower bound for the cycle producing `_previous_dayN` (D-05).\n */\nexport function issuedAtFromPreviousDayMs(\n validAtUtcMs: number,\n N: number,\n cycleHours: readonly number[],\n): number {\n if (N < 1 || N > 7) {\n throw new Error(`N must be in 1..7 (Open-Meteo previous_dayN limit); got ${N}`);\n }\n return floorToCycleMs(validAtUtcMs - N * 86_400_000, cycleHours);\n}\n\n/**\n * Cycle-math fallback for Live mode `issuedAt` derivation (D-06).\n */\nexport function issuedAtFromLiveCycleMathMs(\n nowUtcMs: number,\n publishLagHours: number,\n cycleHours: readonly number[],\n): number {\n return floorToCycleMs(nowUtcMs - publishLagHours * 3_600_000, cycleHours);\n}\n","// Phase 20 OM-07 + OM-08 — Open-Meteo TS fetcher (mirrors Python\n// `packages/weather/src/mostlyright/weather/_fetchers/_open_meteo.py`).\n//\n// Endpoints (Phase 20 D-01):\n// - Previous Runs: https://previous-runs-api.open-meteo.com/v1/forecast\n// - Single Runs: https://single-runs-api.open-meteo.com/v1/forecast\n// - Live Forecast: https://api.open-meteo.com/v1/forecast\n// - Historical (SEAMLESS):\n// https://historical-forecast-api.open-meteo.com/v1/forecast\n// BANNED unless caller passes allowLeakage=true.\n//\n// Browser-safe: uses `fetch` + AbortController only (no Node-only APIs).\n\nimport { OpenMeteoSeamlessLeakageError } from \"@mostlyrightmd/core\";\n\nimport {\n CYCLE_HOURS,\n OPEN_METEO_MODELS,\n PUBLISH_LAG_HOURS,\n issuedAtFromLiveCycleMathMs,\n issuedAtFromPreviousDayMs,\n} from \"./open-meteo-models.js\";\nimport type {\n OpenMeteoMode,\n OpenMeteoModel,\n OpenMeteoOptions,\n OpenMeteoRow,\n OpenMeteoSource,\n} from \"./types.js\";\n\nexport const OPEN_METEO_PREVIOUS_RUNS_URL = \"https://previous-runs-api.open-meteo.com/v1/forecast\";\nexport const OPEN_METEO_SINGLE_RUNS_URL = \"https://single-runs-api.open-meteo.com/v1/forecast\";\nexport const OPEN_METEO_LIVE_URL = \"https://api.open-meteo.com/v1/forecast\";\nexport const OPEN_METEO_SEAMLESS_URL = \"https://historical-forecast-api.open-meteo.com/v1/forecast\";\n\nconst VALID_MODES: ReadonlySet<OpenMeteoMode> = new Set([\"training\", \"live\", \"seamless\"]);\n\nconst HOURLY_VARIABLES = [\n \"temperature_2m\",\n \"dew_point_2m\",\n \"apparent_temperature\",\n \"wind_speed_10m\",\n \"wind_direction_10m\",\n \"wind_gusts_10m\",\n \"precipitation\",\n \"precipitation_probability\",\n \"cloud_cover\",\n \"surface_pressure\",\n \"pressure_msl\",\n \"shortwave_radiation\",\n \"direct_radiation\",\n \"cape\",\n \"freezing_level_height\",\n \"snow_depth\",\n \"visibility\",\n \"weather_code\",\n] as const;\n\n// Built-in station coordinates for the Phase 20 regression fixtures.\nconst STATION_COORDS: ReadonlyMap<string, { lat: number; lon: number }> = new Map([\n [\"KNYC\", { lat: 40.78, lon: -73.97 }],\n [\"KORD\", { lat: 41.98, lon: -87.9 }],\n [\"KDEN\", { lat: 39.86, lon: -104.67 }],\n [\"KMIA\", { lat: 25.79, lon: -80.29 }],\n [\"KSEA\", { lat: 47.45, lon: -122.31 }],\n]);\n\nfunction resolveCoords(station: string): { lat: number; lon: number } {\n const coords = STATION_COORDS.get(station);\n if (coords) return coords;\n throw new Error(\n `openMeteoForecasts: station=${JSON.stringify(station)} not in built-in coords table; add to STATION_COORDS`,\n );\n}\n\nfunction buildHourlyParam(endpoint: string): string {\n if (endpoint === OPEN_METEO_PREVIOUS_RUNS_URL) {\n return HOURLY_VARIABLES.map((v) => `${v}_previous_day1`).join(\",\");\n }\n return HOURLY_VARIABLES.join(\",\");\n}\n\nfunction dispatchEndpoint(\n mode: OpenMeteoMode,\n opts: { allowLeakage: boolean; model: OpenMeteoModel; issuedAt: string | undefined },\n): string {\n if (mode === \"training\") {\n return opts.issuedAt ? OPEN_METEO_SINGLE_RUNS_URL : OPEN_METEO_PREVIOUS_RUNS_URL;\n }\n if (mode === \"live\") {\n return OPEN_METEO_LIVE_URL;\n }\n if (mode === \"seamless\") {\n if (!opts.allowLeakage) {\n throw new OpenMeteoSeamlessLeakageError(\n \"Open-Meteo seamless endpoint is banned for training data \" +\n \"(see Tarabcak/mostlyright#70). Pass allowLeakage: true to opt in; \" +\n \"LeakageDetector will still reject these rows when asOf is asserted.\",\n {\n model: opts.model,\n endpointUrl: OPEN_METEO_SEAMLESS_URL,\n },\n );\n }\n return OPEN_METEO_SEAMLESS_URL;\n }\n throw new Error(`openMeteoForecasts: unknown mode ${JSON.stringify(mode)}`);\n}\n\nfunction isoIfNotNull(ms: number | null): string | null {\n return ms === null ? null : new Date(ms).toISOString();\n}\n\nfunction maybeNumber(value: unknown): number | null {\n if (value === null || value === undefined) return null;\n const n = typeof value === \"number\" ? value : Number(value);\n return Number.isFinite(n) ? n : null;\n}\n\n/**\n * Coerce to a nullable integer for integer-schema fields, rounding fractional\n * upstream values instead of passing them through. Mirrors the Python fetcher's\n * `pd.to_numeric(...).round().astype(\"Int64\")` (issue #55): without this, a\n * fractional value (e.g. `cloud_cover` of `12.5`) would violate the `integer`\n * type in `schema.forecast.station.v1` and diverge from the Python SDK output.\n *\n * Uses banker's rounding (round half to even) to match pandas/NumPy `.round()`\n * exactly — JS `Math.round` rounds halves up (`12.5 -> 13`) whereas pandas\n * rounds to even (`12.5 -> 12`), which would otherwise break byte-parity at\n * `.5` boundaries.\n */\nfunction maybeInt(value: unknown): number | null {\n const n = maybeNumber(value);\n if (n === null) return null;\n const floor = Math.floor(n);\n const diff = n - floor;\n if (diff < 0.5) return floor;\n if (diff > 0.5) return floor + 1;\n // Exactly .5 → round to the even neighbour (banker's rounding).\n return floor % 2 === 0 ? floor : floor + 1;\n}\n\nfunction pickHourlyValue(\n hourly: Record<string, unknown[]>,\n key: string,\n isPreviousRuns: boolean,\n idx: number,\n): unknown {\n const arr = isPreviousRuns ? (hourly[`${key}_previous_day1`] ?? hourly[key]) : hourly[key];\n if (!Array.isArray(arr) || idx >= arr.length) return null;\n return arr[idx];\n}\n\n/**\n * Fetch Open-Meteo forecasts for `station` in `[fromDate, toDate]`.\n *\n * Phase 20 OM-07. Mirrors Python `fetch_open_meteo(...)`. Default\n * `mode: \"training\"` hits Previous Runs API; with `issuedAt: \"...\"`\n * dispatches to Single Runs API. `mode: \"live\"` hits Live Forecast API\n * with cycle-math fallback `issuedAt`. `mode: \"seamless\"` requires\n * `allowLeakage: true` (BANNED for training data).\n */\nexport async function openMeteoForecasts(\n station: string,\n fromDate: string,\n toDate: string,\n opts: OpenMeteoOptions = {},\n): Promise<OpenMeteoRow[]> {\n const model = opts.model ?? \"gfs_global\";\n if (!OPEN_METEO_MODELS.has(model)) {\n throw new Error(\n `openMeteoForecasts: model must be one of OPEN_METEO_MODELS (36 keys); got ${JSON.stringify(model)}`,\n );\n }\n const mode = opts.mode ?? \"training\";\n if (!VALID_MODES.has(mode)) {\n throw new Error(\n `openMeteoForecasts: mode must be one of training,live,seamless; got ${JSON.stringify(mode)}`,\n );\n }\n const endpoint = dispatchEndpoint(mode, {\n allowLeakage: opts.allowLeakage ?? false,\n model,\n issuedAt: opts.issuedAt,\n });\n\n const { lat, lon } = resolveCoords(station);\n const params = new URLSearchParams();\n params.set(\"latitude\", String(lat));\n params.set(\"longitude\", String(lon));\n params.set(\"hourly\", buildHourlyParam(endpoint));\n params.set(\"models\", model);\n params.set(\"timezone\", \"UTC\");\n if (endpoint === OPEN_METEO_SINGLE_RUNS_URL) {\n // Single-Runs API rejects start_date/end_date (HTTP 400); send run= only.\n // The response carries the full run horizon, which we clip to\n // [fromDate, toDate] after parsing — parity with Python fetch_open_meteo.\n if (opts.issuedAt) {\n params.set(\"run\", opts.issuedAt);\n }\n } else {\n params.set(\"start_date\", fromDate);\n params.set(\"end_date\", toDate);\n }\n\n const fetchFn = opts.fetchFn ?? fetch;\n const url = `${endpoint}?${params.toString()}`;\n const resp = await fetchFn(url);\n if (resp.status === 404) return [];\n if (!resp.ok) {\n throw new Error(`openMeteoForecasts: HTTP ${resp.status} on ${url}`);\n }\n const payload = (await resp.json()) as {\n hourly?: { time?: string[]; [k: string]: unknown };\n };\n const hourly = payload.hourly ?? {};\n const times = hourly.time ?? [];\n if (times.length === 0) return [];\n\n let source: OpenMeteoSource;\n if (endpoint === OPEN_METEO_PREVIOUS_RUNS_URL) {\n source = \"open_meteo.previous_runs\";\n } else if (endpoint === OPEN_METEO_SINGLE_RUNS_URL) {\n source = \"open_meteo.single_run\";\n } else if (endpoint === OPEN_METEO_LIVE_URL) {\n source = \"open_meteo.live\";\n } else {\n source = \"open_meteo.seamless\";\n }\n\n const cycleHours = CYCLE_HOURS.get(model) ?? [0, 6, 12, 18];\n const publishLag = PUBLISH_LAG_HOURS.get(model) ?? 6;\n const retrievedAt = new Date().toISOString();\n const nowMs = Date.now();\n\n const rows: OpenMeteoRow[] = [];\n for (let i = 0; i < times.length; i++) {\n const tIso = times[i] ?? \"\";\n if (!tIso) continue;\n const validAtMs = Date.parse(tIso.endsWith(\"Z\") || tIso.includes(\"+\") ? tIso : `${tIso}Z`);\n let issuedAtMs: number | null;\n if (source === \"open_meteo.previous_runs\") {\n issuedAtMs = issuedAtFromPreviousDayMs(validAtMs, 1, cycleHours);\n } else if (source === \"open_meteo.single_run\") {\n issuedAtMs = opts.issuedAt\n ? Date.parse(\n opts.issuedAt.endsWith(\"Z\") || opts.issuedAt.includes(\"+\")\n ? opts.issuedAt\n : `${opts.issuedAt}Z`,\n )\n : null;\n } else if (source === \"open_meteo.live\") {\n issuedAtMs = issuedAtFromLiveCycleMathMs(nowMs, publishLag, cycleHours);\n } else {\n // seamless — null by design\n issuedAtMs = null;\n }\n const validAtIso = new Date(validAtMs).toISOString();\n const forecastHour =\n issuedAtMs === null ? null : Math.round((validAtMs - issuedAtMs) / 3_600_000);\n const isPrev = source === \"open_meteo.previous_runs\";\n const h = hourly as Record<string, unknown[]>;\n const popPct = maybeNumber(pickHourlyValue(h, \"precipitation_probability\", isPrev, i));\n rows.push({\n station,\n model,\n issuedAt: isoIfNotNull(issuedAtMs),\n validAt: validAtIso,\n forecastHour,\n tempC: maybeNumber(pickHourlyValue(h, \"temperature_2m\", isPrev, i)),\n dewPointC: maybeNumber(pickHourlyValue(h, \"dew_point_2m\", isPrev, i)),\n apparentTempC: maybeNumber(pickHourlyValue(h, \"apparent_temperature\", isPrev, i)),\n windSpeedMs: maybeNumber(pickHourlyValue(h, \"wind_speed_10m\", isPrev, i)),\n windDirDeg: maybeInt(pickHourlyValue(h, \"wind_direction_10m\", isPrev, i)),\n windGustsMs: maybeNumber(pickHourlyValue(h, \"wind_gusts_10m\", isPrev, i)),\n precipProbability: popPct === null ? null : popPct / 100,\n precipitationMm: maybeNumber(pickHourlyValue(h, \"precipitation\", isPrev, i)),\n cloudCoverPct: maybeInt(pickHourlyValue(h, \"cloud_cover\", isPrev, i)),\n surfacePressureHpa: maybeNumber(pickHourlyValue(h, \"surface_pressure\", isPrev, i)),\n pressureMslHpa: maybeNumber(pickHourlyValue(h, \"pressure_msl\", isPrev, i)),\n shortwaveRadiationWm2: maybeNumber(pickHourlyValue(h, \"shortwave_radiation\", isPrev, i)),\n directRadiationWm2: maybeNumber(pickHourlyValue(h, \"direct_radiation\", isPrev, i)),\n capeJkg: maybeNumber(pickHourlyValue(h, \"cape\", isPrev, i)),\n freezingLevelM: maybeInt(pickHourlyValue(h, \"freezing_level_height\", isPrev, i)),\n snowDepthM: maybeNumber(pickHourlyValue(h, \"snow_depth\", isPrev, i)),\n visibilityM: maybeInt(pickHourlyValue(h, \"visibility\", isPrev, i)),\n weatherCode: maybeInt(pickHourlyValue(h, \"weather_code\", isPrev, i)),\n source,\n retrievedAt,\n });\n }\n\n // Single-Runs returns the full run horizon; clip to the requested window\n // [fromDate, toDate] (end-inclusive day) — parity with Python fetch_open_meteo.\n if (source === \"open_meteo.single_run\" && rows.length > 0) {\n const loMs = Date.parse(`${fromDate}T00:00:00Z`);\n const hiMs = Date.parse(`${toDate}T00:00:00Z`) + 86_400_000;\n return rows.filter((r) => {\n const v = Date.parse(r.validAt);\n return v >= loMs && v < hiMs;\n });\n }\n return rows;\n}\n","// AUTO-GENERATED by @mostlyrightmd/codegen from schemas/stations.json.\n// DO NOT EDIT — regenerate with: pnpm codegen\n// Last manifest SHA recorded in schemas/EXPORT_MANIFEST.json\n\nexport interface StationInfo {\n code: string | null;\n ghcnh_id: string | null;\n icao: string;\n name: string | null;\n tz: string;\n latitude: number | null;\n longitude: number | null;\n country: string | null;\n venues: ReadonlyArray<string>;\n}\n\nexport const STATIONS: ReadonlyArray<StationInfo> = [\n {\n code: \"CYYZ\",\n country: \"CA\",\n ghcnh_id: null,\n icao: \"CYYZ\",\n latitude: 43.6777,\n longitude: -79.6248,\n name: \"Toronto Pearson International\",\n tz: \"America/Toronto\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EDDB\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDB\",\n latitude: 52.3667,\n longitude: 13.5033,\n name: \"Berlin Brandenburg\",\n tz: \"Europe/Berlin\",\n venues: [],\n },\n {\n code: \"EDDF\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDF\",\n latitude: 50.0379,\n longitude: 8.5622,\n name: \"Frankfurt am Main\",\n tz: \"Europe/Berlin\",\n venues: [],\n },\n {\n code: \"EDDM\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDM\",\n latitude: 48.3538,\n longitude: 11.7861,\n name: \"Munich Franz Josef Strauss\",\n tz: \"Europe/Berlin\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EFHK\",\n country: \"FI\",\n ghcnh_id: null,\n icao: \"EFHK\",\n latitude: 60.3172,\n longitude: 24.9633,\n name: \"Helsinki-Vantaa\",\n tz: \"Europe/Helsinki\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EGKK\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGKK\",\n latitude: 51.1481,\n longitude: -0.1903,\n name: \"London Gatwick\",\n tz: \"Europe/London\",\n venues: [],\n },\n {\n code: \"EGLC\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGLC\",\n latitude: 51.5053,\n longitude: 0.0553,\n name: \"London City\",\n tz: \"Europe/London\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EGLL\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGLL\",\n latitude: 51.4706,\n longitude: -0.4619,\n name: \"London Heathrow\",\n tz: \"Europe/London\",\n venues: [],\n },\n {\n code: \"EHAM\",\n country: \"NL\",\n ghcnh_id: null,\n icao: \"EHAM\",\n latitude: 52.3086,\n longitude: 4.7639,\n name: \"Amsterdam Schiphol\",\n tz: \"Europe/Amsterdam\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EKCH\",\n country: \"DK\",\n ghcnh_id: null,\n icao: \"EKCH\",\n latitude: 55.6181,\n longitude: 12.6561,\n name: \"Copenhagen Kastrup\",\n tz: \"Europe/Copenhagen\",\n venues: [],\n },\n {\n code: \"EPWA\",\n country: \"PL\",\n ghcnh_id: null,\n icao: \"EPWA\",\n latitude: 52.1657,\n longitude: 20.9671,\n name: \"Warsaw Chopin\",\n tz: \"Europe/Warsaw\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ESSA\",\n country: \"SE\",\n ghcnh_id: null,\n icao: \"ESSA\",\n latitude: 59.6519,\n longitude: 17.9186,\n name: \"Stockholm Arlanda\",\n tz: \"Europe/Stockholm\",\n venues: [],\n },\n {\n code: \"FACT\",\n country: \"ZA\",\n ghcnh_id: null,\n icao: \"FACT\",\n latitude: -33.9648,\n longitude: 18.6017,\n name: \"Cape Town International\",\n tz: \"Africa/Johannesburg\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ATL\",\n country: \"US\",\n ghcnh_id: \"USW00013874\",\n icao: \"KATL\",\n latitude: 33.6407,\n longitude: -84.4277,\n name: \"Hartsfield-Jackson Atlanta International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"AUS\",\n country: \"US\",\n ghcnh_id: \"USW00013904\",\n icao: \"KAUS\",\n latitude: 30.1975,\n longitude: -97.6664,\n name: \"Austin-Bergstrom International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"BKF\",\n country: \"US\",\n ghcnh_id: \"USW00093067\",\n icao: \"KBKF\",\n latitude: 39.7019,\n longitude: -104.7517,\n name: \"Buckley Space Force Base (Denver)\",\n tz: \"America/Denver\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"BNA\",\n country: \"US\",\n ghcnh_id: \"USW00013897\",\n icao: \"KBNA\",\n latitude: 36.1245,\n longitude: -86.6782,\n name: \"Nashville International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"BOS\",\n country: \"US\",\n ghcnh_id: \"USW00014739\",\n icao: \"KBOS\",\n latitude: 42.3656,\n longitude: -71.0096,\n name: \"Boston Logan International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"CVG\",\n country: \"US\",\n ghcnh_id: \"USW00093814\",\n icao: \"KCVG\",\n latitude: 39.0488,\n longitude: -84.6678,\n name: \"Cincinnati/Northern Kentucky International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DAL\",\n country: \"US\",\n ghcnh_id: \"USW00013960\",\n icao: \"KDAL\",\n latitude: 32.8481,\n longitude: -96.8512,\n name: \"Dallas Love Field\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"DCA\",\n country: \"US\",\n ghcnh_id: \"USW00013743\",\n icao: \"KDCA\",\n latitude: 38.8512,\n longitude: -77.0402,\n name: \"Washington Reagan National\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DEN\",\n country: \"US\",\n ghcnh_id: \"USW00003017\",\n icao: \"KDEN\",\n latitude: 39.8561,\n longitude: -104.6737,\n name: \"Denver International\",\n tz: \"America/Denver\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DFW\",\n country: \"US\",\n ghcnh_id: \"USW00003927\",\n icao: \"KDFW\",\n latitude: 32.8998,\n longitude: -97.0403,\n name: \"Dallas-Fort Worth International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DTW\",\n country: \"US\",\n ghcnh_id: \"USW00094847\",\n icao: \"KDTW\",\n latitude: 42.2124,\n longitude: -83.3534,\n name: \"Detroit Metropolitan Wayne County\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"HOU\",\n country: \"US\",\n ghcnh_id: \"USW00012918\",\n icao: \"KHOU\",\n latitude: 29.6454,\n longitude: -95.2789,\n name: \"Houston Hobby\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"IAH\",\n country: \"US\",\n ghcnh_id: \"USW00012960\",\n icao: \"KIAH\",\n latitude: 29.9844,\n longitude: -95.3414,\n name: \"Houston George Bush Intercontinental\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LAS\",\n country: \"US\",\n ghcnh_id: \"USW00023169\",\n icao: \"KLAS\",\n latitude: 36.084,\n longitude: -115.1537,\n name: \"Harry Reid (McCarran) International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LAX\",\n country: \"US\",\n ghcnh_id: \"USW00023174\",\n icao: \"KLAX\",\n latitude: 33.9425,\n longitude: -118.4081,\n name: \"Los Angeles International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"LGA\",\n country: \"US\",\n ghcnh_id: \"USW00014732\",\n icao: \"KLGA\",\n latitude: 40.7772,\n longitude: -73.8726,\n name: \"New York LaGuardia\",\n tz: \"America/New_York\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MDW\",\n country: \"US\",\n ghcnh_id: \"USW00014819\",\n icao: \"KMDW\",\n latitude: 41.7868,\n longitude: -87.7522,\n name: \"Chicago Midway International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"MIA\",\n country: \"US\",\n ghcnh_id: \"USW00012839\",\n icao: \"KMIA\",\n latitude: 25.7959,\n longitude: -80.287,\n name: \"Miami International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"MSP\",\n country: \"US\",\n ghcnh_id: \"USW00014922\",\n icao: \"KMSP\",\n latitude: 44.8848,\n longitude: -93.2223,\n name: \"Minneapolis-St Paul International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"MSY\",\n country: \"US\",\n ghcnh_id: \"USW00012916\",\n icao: \"KMSY\",\n latitude: 29.9934,\n longitude: -90.258,\n name: \"New Orleans Louis Armstrong International\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"NYC\",\n country: \"US\",\n ghcnh_id: \"USW00094728\",\n icao: \"KNYC\",\n latitude: 40.7789,\n longitude: -73.9692,\n name: \"Central Park, New York\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"OKC\",\n country: \"US\",\n ghcnh_id: \"USW00013967\",\n icao: \"KOKC\",\n latitude: 35.3931,\n longitude: -97.6007,\n name: \"Oklahoma City Will Rogers World\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"ORD\",\n country: \"US\",\n ghcnh_id: \"USW00094846\",\n icao: \"KORD\",\n latitude: 41.9742,\n longitude: -87.9073,\n name: \"Chicago O'Hare International\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"PHL\",\n country: \"US\",\n ghcnh_id: \"USW00013739\",\n icao: \"KPHL\",\n latitude: 39.8721,\n longitude: -75.2411,\n name: \"Philadelphia International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"PHX\",\n country: \"US\",\n ghcnh_id: \"USW00023183\",\n icao: \"KPHX\",\n latitude: 33.4373,\n longitude: -112.0078,\n name: \"Phoenix Sky Harbor International\",\n tz: \"America/Phoenix\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"SAT\",\n country: \"US\",\n ghcnh_id: \"USW00012921\",\n icao: \"KSAT\",\n latitude: 29.5337,\n longitude: -98.4698,\n name: \"San Antonio International\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"SEA\",\n country: \"US\",\n ghcnh_id: \"USW00024233\",\n icao: \"KSEA\",\n latitude: 47.4502,\n longitude: -122.3088,\n name: \"Seattle-Tacoma International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"SFO\",\n country: \"US\",\n ghcnh_id: \"USW00023234\",\n icao: \"KSFO\",\n latitude: 37.6213,\n longitude: -122.379,\n name: \"San Francisco International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"SLC\",\n country: \"US\",\n ghcnh_id: \"USW00024127\",\n icao: \"KSLC\",\n latitude: 40.7884,\n longitude: -111.9778,\n name: \"Salt Lake City International\",\n tz: \"America/Denver\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LEBL\",\n country: \"ES\",\n ghcnh_id: null,\n icao: \"LEBL\",\n latitude: 41.2974,\n longitude: 2.0833,\n name: \"Barcelona El Prat\",\n tz: \"Europe/Madrid\",\n venues: [],\n },\n {\n code: \"LEMD\",\n country: \"ES\",\n ghcnh_id: null,\n icao: \"LEMD\",\n latitude: 40.4719,\n longitude: -3.5626,\n name: \"Madrid Barajas\",\n tz: \"Europe/Madrid\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LFPB\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPB\",\n latitude: 48.9694,\n longitude: 2.4414,\n name: \"Paris Le Bourget\",\n tz: \"Europe/Paris\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LFPG\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPG\",\n latitude: 49.0097,\n longitude: 2.5479,\n name: \"Paris Charles de Gaulle\",\n tz: \"Europe/Paris\",\n venues: [],\n },\n {\n code: \"LFPO\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPO\",\n latitude: 48.7233,\n longitude: 2.3794,\n name: \"Paris Orly\",\n tz: \"Europe/Paris\",\n venues: [],\n },\n {\n code: \"LIMC\",\n country: \"IT\",\n ghcnh_id: null,\n icao: \"LIMC\",\n latitude: 45.6306,\n longitude: 8.7281,\n name: \"Milan Malpensa\",\n tz: \"Europe/Rome\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LIRF\",\n country: \"IT\",\n ghcnh_id: null,\n icao: \"LIRF\",\n latitude: 41.8003,\n longitude: 12.2389,\n name: \"Rome Fiumicino\",\n tz: \"Europe/Rome\",\n venues: [],\n },\n {\n code: \"LLBG\",\n country: \"IL\",\n ghcnh_id: null,\n icao: \"LLBG\",\n latitude: 32.0114,\n longitude: 34.8867,\n name: \"Tel Aviv Ben Gurion\",\n tz: \"Asia/Jerusalem\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LOWW\",\n country: \"AT\",\n ghcnh_id: null,\n icao: \"LOWW\",\n latitude: 48.1103,\n longitude: 16.5697,\n name: \"Vienna International\",\n tz: \"Europe/Vienna\",\n venues: [],\n },\n {\n code: \"LSZH\",\n country: \"CH\",\n ghcnh_id: null,\n icao: \"LSZH\",\n latitude: 47.4647,\n longitude: 8.5492,\n name: \"Zurich\",\n tz: \"Europe/Zurich\",\n venues: [],\n },\n {\n code: \"LTAC\",\n country: \"TR\",\n ghcnh_id: null,\n icao: \"LTAC\",\n latitude: 40.1281,\n longitude: 32.9951,\n name: \"Ankara Esenboga\",\n tz: \"Europe/Istanbul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LTFM\",\n country: \"TR\",\n ghcnh_id: null,\n icao: \"LTFM\",\n latitude: 41.2753,\n longitude: 28.7519,\n name: \"Istanbul Airport\",\n tz: \"Europe/Istanbul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MMMX\",\n country: \"MX\",\n ghcnh_id: null,\n icao: \"MMMX\",\n latitude: 19.4363,\n longitude: -99.0721,\n name: \"Mexico City Benito Juarez International\",\n tz: \"America/Mexico_City\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MPMG\",\n country: \"PA\",\n ghcnh_id: null,\n icao: \"MPMG\",\n latitude: 8.9733,\n longitude: -79.5556,\n name: \"Panama City Marcos A. Gelabert (Albrook)\",\n tz: \"America/Panama\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"NZAA\",\n country: \"NZ\",\n ghcnh_id: null,\n icao: \"NZAA\",\n latitude: -37.0081,\n longitude: 174.7917,\n name: \"Auckland\",\n tz: \"Pacific/Auckland\",\n venues: [],\n },\n {\n code: \"NZWN\",\n country: \"NZ\",\n ghcnh_id: null,\n icao: \"NZWN\",\n latitude: -41.3272,\n longitude: 174.8053,\n name: \"Wellington\",\n tz: \"Pacific/Auckland\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OEJN\",\n country: \"SA\",\n ghcnh_id: null,\n icao: \"OEJN\",\n latitude: 21.6796,\n longitude: 39.1565,\n name: \"Jeddah King Abdulaziz International\",\n tz: \"Asia/Riyadh\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OERK\",\n country: \"SA\",\n ghcnh_id: null,\n icao: \"OERK\",\n latitude: 24.9576,\n longitude: 46.6988,\n name: \"Riyadh King Khalid International\",\n tz: \"Asia/Riyadh\",\n venues: [],\n },\n {\n code: \"OMDB\",\n country: \"AE\",\n ghcnh_id: null,\n icao: \"OMDB\",\n latitude: 25.2532,\n longitude: 55.3657,\n name: \"Dubai International\",\n tz: \"Asia/Dubai\",\n venues: [],\n },\n {\n code: \"OPKC\",\n country: \"PK\",\n ghcnh_id: null,\n icao: \"OPKC\",\n latitude: 24.9065,\n longitude: 67.1608,\n name: \"Karachi Jinnah International\",\n tz: \"Asia/Karachi\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OTHH\",\n country: \"QA\",\n ghcnh_id: null,\n icao: \"OTHH\",\n latitude: 25.2731,\n longitude: 51.608,\n name: \"Doha Hamad International\",\n tz: \"Asia/Qatar\",\n venues: [],\n },\n {\n code: \"RCSS\",\n country: \"TW\",\n ghcnh_id: null,\n icao: \"RCSS\",\n latitude: 25.0694,\n longitude: 121.5519,\n name: \"Taipei Songshan\",\n tz: \"Asia/Taipei\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RCTP\",\n country: \"TW\",\n ghcnh_id: null,\n icao: \"RCTP\",\n latitude: 25.0777,\n longitude: 121.2328,\n name: \"Taipei Taoyuan\",\n tz: \"Asia/Taipei\",\n venues: [],\n },\n {\n code: \"RJAA\",\n country: \"JP\",\n ghcnh_id: null,\n icao: \"RJAA\",\n latitude: 35.7647,\n longitude: 140.3864,\n name: \"Tokyo Narita\",\n tz: \"Asia/Tokyo\",\n venues: [],\n },\n {\n code: \"RJTT\",\n country: \"JP\",\n ghcnh_id: null,\n icao: \"RJTT\",\n latitude: 35.5522,\n longitude: 139.78,\n name: \"Tokyo Haneda\",\n tz: \"Asia/Tokyo\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RKPK\",\n country: \"KR\",\n ghcnh_id: null,\n icao: \"RKPK\",\n latitude: 35.1795,\n longitude: 128.9382,\n name: \"Busan Gimhae International\",\n tz: \"Asia/Seoul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RKSI\",\n country: \"KR\",\n ghcnh_id: null,\n icao: \"RKSI\",\n latitude: 37.4691,\n longitude: 126.4505,\n name: \"Seoul Incheon\",\n tz: \"Asia/Seoul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RPLL\",\n country: \"PH\",\n ghcnh_id: null,\n icao: \"RPLL\",\n latitude: 14.5086,\n longitude: 121.0197,\n name: \"Manila Ninoy Aquino International\",\n tz: \"Asia/Manila\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"SAEZ\",\n country: \"AR\",\n ghcnh_id: null,\n icao: \"SAEZ\",\n latitude: -34.8222,\n longitude: -58.5358,\n name: \"Buenos Aires Ezeiza\",\n tz: \"America/Argentina/Buenos_Aires\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"SBGR\",\n country: \"BR\",\n ghcnh_id: null,\n icao: \"SBGR\",\n latitude: -23.4356,\n longitude: -46.4731,\n name: \"São Paulo Guarulhos\",\n tz: \"America/Sao_Paulo\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"UUEE\",\n country: \"RU\",\n ghcnh_id: null,\n icao: \"UUEE\",\n latitude: 55.9728,\n longitude: 37.4147,\n name: \"Moscow Sheremetyevo\",\n tz: \"Europe/Moscow\",\n venues: [],\n },\n {\n code: \"UUWW\",\n country: \"RU\",\n ghcnh_id: null,\n icao: \"UUWW\",\n latitude: 55.5915,\n longitude: 37.2615,\n name: \"Moscow Vnukovo\",\n tz: \"Europe/Moscow\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"VABB\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VABB\",\n latitude: 19.0887,\n longitude: 72.8679,\n name: \"Mumbai Chhatrapati Shivaji\",\n tz: \"Asia/Kolkata\",\n venues: [],\n },\n {\n code: \"VHHH\",\n country: \"HK\",\n ghcnh_id: null,\n icao: \"VHHH\",\n latitude: 22.308,\n longitude: 113.9185,\n name: \"Hong Kong International\",\n tz: \"Asia/Hong_Kong\",\n venues: [],\n },\n {\n code: \"VIDP\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VIDP\",\n latitude: 28.5562,\n longitude: 77.1,\n name: \"Delhi Indira Gandhi\",\n tz: \"Asia/Kolkata\",\n venues: [],\n },\n {\n code: \"VILK\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VILK\",\n latitude: 26.7606,\n longitude: 80.8893,\n name: \"Lucknow Chaudhary Charan Singh International\",\n tz: \"Asia/Kolkata\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"VTBS\",\n country: \"TH\",\n ghcnh_id: null,\n icao: \"VTBS\",\n latitude: 13.69,\n longitude: 100.7501,\n name: \"Bangkok Suvarnabhumi\",\n tz: \"Asia/Bangkok\",\n venues: [],\n },\n {\n code: \"WMKK\",\n country: \"MY\",\n ghcnh_id: null,\n icao: \"WMKK\",\n latitude: 2.7456,\n longitude: 101.7099,\n name: \"Kuala Lumpur International\",\n tz: \"Asia/Kuala_Lumpur\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"WSSS\",\n country: \"SG\",\n ghcnh_id: null,\n icao: \"WSSS\",\n latitude: 1.3644,\n longitude: 103.9915,\n name: \"Singapore Changi\",\n tz: \"Asia/Singapore\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"YBBN\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YBBN\",\n latitude: -27.3842,\n longitude: 153.1175,\n name: \"Brisbane\",\n tz: \"Australia/Brisbane\",\n venues: [],\n },\n {\n code: \"YMML\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YMML\",\n latitude: -37.6733,\n longitude: 144.8433,\n name: \"Melbourne Tullamarine\",\n tz: \"Australia/Melbourne\",\n venues: [],\n },\n {\n code: \"YSSY\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YSSY\",\n latitude: -33.9461,\n longitude: 151.1772,\n name: \"Sydney Kingsford Smith\",\n tz: \"Australia/Sydney\",\n venues: [],\n },\n {\n code: \"ZBAA\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZBAA\",\n latitude: 40.0801,\n longitude: 116.5846,\n name: \"Beijing Capital\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZGGG\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZGGG\",\n latitude: 23.3924,\n longitude: 113.2988,\n name: \"Guangzhou Baiyun International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZGSZ\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZGSZ\",\n latitude: 22.6393,\n longitude: 113.8108,\n name: \"Shenzhen Bao'an International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZHCC\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZHCC\",\n latitude: 34.5197,\n longitude: 113.8408,\n name: \"Zhengzhou Xinzheng International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZHHH\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZHHH\",\n latitude: 30.7838,\n longitude: 114.2081,\n name: \"Wuhan Tianhe International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSJN\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSJN\",\n latitude: 36.8572,\n longitude: 117.2161,\n name: \"Jinan Yaoqiang International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSPD\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSPD\",\n latitude: 31.1443,\n longitude: 121.8083,\n name: \"Shanghai Pudong\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSQD\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSQD\",\n latitude: 36.3614,\n longitude: 120.0867,\n name: \"Qingdao Jiaodong International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZUCK\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZUCK\",\n latitude: 29.7192,\n longitude: 106.6417,\n name: \"Chongqing Jiangbei International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZUUU\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZUUU\",\n latitude: 30.5785,\n longitude: 103.9471,\n name: \"Chengdu Shuangliu International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n] as const;\n\nexport const STATION_BY_CODE: ReadonlyMap<string, StationInfo> = new Map<string, StationInfo>([\n [\"ATL\", STATIONS[13]!],\n [\"AUS\", STATIONS[14]!],\n [\"BKF\", STATIONS[15]!],\n [\"BNA\", STATIONS[16]!],\n [\"BOS\", STATIONS[17]!],\n [\"CVG\", STATIONS[18]!],\n [\"CYYZ\", STATIONS[0]!],\n [\"DAL\", STATIONS[19]!],\n [\"DCA\", STATIONS[20]!],\n [\"DEN\", STATIONS[21]!],\n [\"DFW\", STATIONS[22]!],\n [\"DTW\", STATIONS[23]!],\n [\"EDDB\", STATIONS[1]!],\n [\"EDDF\", STATIONS[2]!],\n [\"EDDM\", STATIONS[3]!],\n [\"EFHK\", STATIONS[4]!],\n [\"EGKK\", STATIONS[5]!],\n [\"EGLC\", STATIONS[6]!],\n [\"EGLL\", STATIONS[7]!],\n [\"EHAM\", STATIONS[8]!],\n [\"EKCH\", STATIONS[9]!],\n [\"EPWA\", STATIONS[10]!],\n [\"ESSA\", STATIONS[11]!],\n [\"FACT\", STATIONS[12]!],\n [\"HOU\", STATIONS[24]!],\n [\"IAH\", STATIONS[25]!],\n [\"LAS\", STATIONS[26]!],\n [\"LAX\", STATIONS[27]!],\n [\"LEBL\", STATIONS[42]!],\n [\"LEMD\", STATIONS[43]!],\n [\"LFPB\", STATIONS[44]!],\n [\"LFPG\", STATIONS[45]!],\n [\"LFPO\", STATIONS[46]!],\n [\"LGA\", STATIONS[28]!],\n [\"LIMC\", STATIONS[47]!],\n [\"LIRF\", STATIONS[48]!],\n [\"LLBG\", STATIONS[49]!],\n [\"LOWW\", STATIONS[50]!],\n [\"LSZH\", STATIONS[51]!],\n [\"LTAC\", STATIONS[52]!],\n [\"LTFM\", STATIONS[53]!],\n [\"MDW\", STATIONS[29]!],\n [\"MIA\", STATIONS[30]!],\n [\"MMMX\", STATIONS[54]!],\n [\"MPMG\", STATIONS[55]!],\n [\"MSP\", STATIONS[31]!],\n [\"MSY\", STATIONS[32]!],\n [\"NYC\", STATIONS[33]!],\n [\"NZAA\", STATIONS[56]!],\n [\"NZWN\", STATIONS[57]!],\n [\"OEJN\", STATIONS[58]!],\n [\"OERK\", STATIONS[59]!],\n [\"OKC\", STATIONS[34]!],\n [\"OMDB\", STATIONS[60]!],\n [\"OPKC\", STATIONS[61]!],\n [\"ORD\", STATIONS[35]!],\n [\"OTHH\", STATIONS[62]!],\n [\"PHL\", STATIONS[36]!],\n [\"PHX\", STATIONS[37]!],\n [\"RCSS\", STATIONS[63]!],\n [\"RCTP\", STATIONS[64]!],\n [\"RJAA\", STATIONS[65]!],\n [\"RJTT\", STATIONS[66]!],\n [\"RKPK\", STATIONS[67]!],\n [\"RKSI\", STATIONS[68]!],\n [\"RPLL\", STATIONS[69]!],\n [\"SAEZ\", STATIONS[70]!],\n [\"SAT\", STATIONS[38]!],\n [\"SBGR\", STATIONS[71]!],\n [\"SEA\", STATIONS[39]!],\n [\"SFO\", STATIONS[40]!],\n [\"SLC\", STATIONS[41]!],\n [\"UUEE\", STATIONS[72]!],\n [\"UUWW\", STATIONS[73]!],\n [\"VABB\", STATIONS[74]!],\n [\"VHHH\", STATIONS[75]!],\n [\"VIDP\", STATIONS[76]!],\n [\"VILK\", STATIONS[77]!],\n [\"VTBS\", STATIONS[78]!],\n [\"WMKK\", STATIONS[79]!],\n [\"WSSS\", STATIONS[80]!],\n [\"YBBN\", STATIONS[81]!],\n [\"YMML\", STATIONS[82]!],\n [\"YSSY\", STATIONS[83]!],\n [\"ZBAA\", STATIONS[84]!],\n [\"ZGGG\", STATIONS[85]!],\n [\"ZGSZ\", STATIONS[86]!],\n [\"ZHCC\", STATIONS[87]!],\n [\"ZHHH\", STATIONS[88]!],\n [\"ZSJN\", STATIONS[89]!],\n [\"ZSPD\", STATIONS[90]!],\n [\"ZSQD\", STATIONS[91]!],\n [\"ZUCK\", STATIONS[92]!],\n [\"ZUUU\", STATIONS[93]!],\n]);\n\nexport const STATION_BY_ICAO: ReadonlyMap<string, StationInfo> = new Map<string, StationInfo>([\n [\"CYYZ\", STATIONS[0]!],\n [\"EDDB\", STATIONS[1]!],\n [\"EDDF\", STATIONS[2]!],\n [\"EDDM\", STATIONS[3]!],\n [\"EFHK\", STATIONS[4]!],\n [\"EGKK\", STATIONS[5]!],\n [\"EGLC\", STATIONS[6]!],\n [\"EGLL\", STATIONS[7]!],\n [\"EHAM\", STATIONS[8]!],\n [\"EKCH\", STATIONS[9]!],\n [\"EPWA\", STATIONS[10]!],\n [\"ESSA\", STATIONS[11]!],\n [\"FACT\", STATIONS[12]!],\n [\"KATL\", STATIONS[13]!],\n [\"KAUS\", STATIONS[14]!],\n [\"KBKF\", STATIONS[15]!],\n [\"KBNA\", STATIONS[16]!],\n [\"KBOS\", STATIONS[17]!],\n [\"KCVG\", STATIONS[18]!],\n [\"KDAL\", STATIONS[19]!],\n [\"KDCA\", STATIONS[20]!],\n [\"KDEN\", STATIONS[21]!],\n [\"KDFW\", STATIONS[22]!],\n [\"KDTW\", STATIONS[23]!],\n [\"KHOU\", STATIONS[24]!],\n [\"KIAH\", STATIONS[25]!],\n [\"KLAS\", STATIONS[26]!],\n [\"KLAX\", STATIONS[27]!],\n [\"KLGA\", STATIONS[28]!],\n [\"KMDW\", STATIONS[29]!],\n [\"KMIA\", STATIONS[30]!],\n [\"KMSP\", STATIONS[31]!],\n [\"KMSY\", STATIONS[32]!],\n [\"KNYC\", STATIONS[33]!],\n [\"KOKC\", STATIONS[34]!],\n [\"KORD\", STATIONS[35]!],\n [\"KPHL\", STATIONS[36]!],\n [\"KPHX\", STATIONS[37]!],\n [\"KSAT\", STATIONS[38]!],\n [\"KSEA\", STATIONS[39]!],\n [\"KSFO\", STATIONS[40]!],\n [\"KSLC\", STATIONS[41]!],\n [\"LEBL\", STATIONS[42]!],\n [\"LEMD\", STATIONS[43]!],\n [\"LFPB\", STATIONS[44]!],\n [\"LFPG\", STATIONS[45]!],\n [\"LFPO\", STATIONS[46]!],\n [\"LIMC\", STATIONS[47]!],\n [\"LIRF\", STATIONS[48]!],\n [\"LLBG\", STATIONS[49]!],\n [\"LOWW\", STATIONS[50]!],\n [\"LSZH\", STATIONS[51]!],\n [\"LTAC\", STATIONS[52]!],\n [\"LTFM\", STATIONS[53]!],\n [\"MMMX\", STATIONS[54]!],\n [\"MPMG\", STATIONS[55]!],\n [\"NZAA\", STATIONS[56]!],\n [\"NZWN\", STATIONS[57]!],\n [\"OEJN\", STATIONS[58]!],\n [\"OERK\", STATIONS[59]!],\n [\"OMDB\", STATIONS[60]!],\n [\"OPKC\", STATIONS[61]!],\n [\"OTHH\", STATIONS[62]!],\n [\"RCSS\", STATIONS[63]!],\n [\"RCTP\", STATIONS[64]!],\n [\"RJAA\", STATIONS[65]!],\n [\"RJTT\", STATIONS[66]!],\n [\"RKPK\", STATIONS[67]!],\n [\"RKSI\", STATIONS[68]!],\n [\"RPLL\", STATIONS[69]!],\n [\"SAEZ\", STATIONS[70]!],\n [\"SBGR\", STATIONS[71]!],\n [\"UUEE\", STATIONS[72]!],\n [\"UUWW\", STATIONS[73]!],\n [\"VABB\", STATIONS[74]!],\n [\"VHHH\", STATIONS[75]!],\n [\"VIDP\", STATIONS[76]!],\n [\"VILK\", STATIONS[77]!],\n [\"VTBS\", STATIONS[78]!],\n [\"WMKK\", STATIONS[79]!],\n [\"WSSS\", STATIONS[80]!],\n [\"YBBN\", STATIONS[81]!],\n [\"YMML\", STATIONS[82]!],\n [\"YSSY\", STATIONS[83]!],\n [\"ZBAA\", STATIONS[84]!],\n [\"ZGGG\", STATIONS[85]!],\n [\"ZGSZ\", STATIONS[86]!],\n [\"ZHCC\", STATIONS[87]!],\n [\"ZHHH\", STATIONS[88]!],\n [\"ZSJN\", STATIONS[89]!],\n [\"ZSPD\", STATIONS[90]!],\n [\"ZSQD\", STATIONS[91]!],\n [\"ZUCK\", STATIONS[92]!],\n [\"ZUUU\", STATIONS[93]!],\n]);\n","// TS-W6 Wave 1 — availability(station, cache) reading from CacheStore.\n//\n// Ports Python `mostlyright.discovery.availability` semantics. Python walks the\n// on-disk parquet hierarchy; TS uses the same canonical cache-key shape so the\n// CacheStore implementation (Memory / IndexedDB / Fs) is the persistence layer.\n//\n// The keys we scan match `cacheKeyForObservations(station, year, month, source?)`\n// and `cacheKeyForClimate(station, year)`. @mostlyrightmd/meta's `research()` writes\n// cache entries under the 3-letter NWS code (`resolved.code` from\n// STATION_BY_ICAO / STATION_BY_CODE) for US stations — e.g. `KNYC` resolves to\n// `NYC` and the cache key reads `...:observations:NYC:...`. Codex iter-2 P2:\n// availability resolves the input the same way so `availability(\"KNYC\", cache)`\n// finds entries written by `research(\"KNYC\", ...)`.\n\nimport { STATION_BY_CODE, STATION_BY_ICAO } from \"../data/generated/stations.js\";\nimport type { CacheStore } from \"../internal/cache/index.js\";\n\n/**\n * Cache-coverage summary for a station.\n *\n * Mirrors Python `availability()` return shape.\n */\nexport interface AvailabilityResult {\n station: string;\n /** Count of distinct (year, month) observation cache entries. */\n monthsCached: number;\n /** Earliest cached month as `\"YYYY-MM\"`, or null if none. */\n firstMonth: string | null;\n /** Latest cached month as `\"YYYY-MM\"`, or null if none. */\n lastMonth: string | null;\n /** Count of cached climate years. */\n climateYears: number;\n /** Earliest cached climate year as `\"YYYY\"`, or null if none. */\n firstClimateYear: string | null;\n /** Latest cached climate year as `\"YYYY\"`, or null if none. */\n lastClimateYear: string | null;\n}\n\n/**\n * Optional adapter that lets `availability()` enumerate keys from a store.\n *\n * CacheStore's mandatory contract is opaque key-value: get/set/delete/withLock.\n * Discovery needs to enumerate which keys exist for a station — this is\n * implementation-specific (Memory iterates its Map, IndexedDB uses\n * `getAllKeys`, Fs walks the directory tree). Stores that support enumeration\n * implement the optional `listKeys(prefix)` method and `availability()` uses\n * it; stores without it return zero-coverage but never throw.\n *\n * Listed keys may exceed the requested prefix in the result (callers filter);\n * `listKeys` is best-effort.\n */\nexport interface KeyEnumerableStore extends CacheStore {\n listKeys(prefix: string): Promise<ReadonlyArray<string>>;\n}\n\nfunction hasListKeys(store: CacheStore): store is KeyEnumerableStore {\n return typeof (store as KeyEnumerableStore).listKeys === \"function\";\n}\n\nconst STATION_RE = /^[A-Z0-9]{3,5}$/;\n\n/**\n * Resolve `station` to the canonical CACHE-KEY station code.\n *\n * - For known stations: returns the 3-letter NWS `code` (e.g. `KNYC` → `NYC`)\n * if one exists, falling back to the 4-letter ICAO. This matches the cache\n * key that `research()` writes for US stations via `resolved.code` and\n * international stations via `resolved.icao` (no NWS code exists).\n * - For unknown inputs that still satisfy the 3-5 char alphanumeric format:\n * returns the upper-cased input. This lets bespoke callers query custom\n * keys without coupling availability to the station registry.\n */\nfunction normalizeStation(station: string): string {\n const upper = station.toUpperCase();\n if (!STATION_RE.test(upper)) {\n throw new RangeError(\n `availability: station must be a 3-5 char alphanumeric code; got ${JSON.stringify(station)}`,\n );\n }\n const byIcao = STATION_BY_ICAO.get(upper);\n if (byIcao !== undefined) {\n // US stations: NWS code is the cache-key form. International stations:\n // no NWS code, ICAO is the cache-key form.\n return byIcao.code ?? byIcao.icao;\n }\n const byCode = STATION_BY_CODE.get(upper);\n if (byCode !== undefined) {\n return byCode.code ?? byCode.icao;\n }\n // Bespoke / unknown codes — pass through. availability() returns\n // zero-coverage if the cache doesn't have entries under this exact key.\n return upper;\n}\n\nconst OBS_KEY_RE = /^mostlyright:v1:observations:([A-Z0-9]+):(\\d{4}):(\\d{2})(?::[a-z0-9_-]+)?$/;\nconst CLIMATE_KEY_RE = /^mostlyright:v1:climate:([A-Z0-9]+):(\\d{4})$/;\n\n/**\n * Options for `availability()`.\n */\nexport interface AvailabilityOptions {\n /**\n * If true, confirm each candidate key with `cache.get()` before counting.\n * Eliminates the small overcount possible on stores whose `listKeys()` can\n * return keys with already-expired TTL entries (FsStore and IndexedDBStore\n * lazy-evict on `get`, not on `listKeys` — codex iter-3 P2). Off by default\n * because the v0.1.0 `research()` flow never writes with `ttlMs`, so the\n * overcount window is empty; turn on only if you populate the cache with\n * explicit TTLs.\n *\n * Cost: one `get()` per matching key. On warm caches this is cheap\n * (MemoryStore + IndexedDBStore in-memory). On FsStore it reads each\n * candidate's file.\n */\n readonly validate?: boolean;\n}\n\n/**\n * Return a summary of cached coverage for `station`.\n *\n * Stores without enumeration support return a zero-coverage result with the\n * station name populated (counts all zero, dates null).\n *\n * Pass `{ validate: true }` to confirm each candidate key via `cache.get()`\n * — needed if your callers populate the cache with `ttlMs` and might query\n * after expiry. The v0.1.0 `research()` flow does not use `ttlMs`, so the\n * default (fast scan, no validation) is correct for the canonical path.\n */\nexport async function availability(\n station: string,\n cache: CacheStore,\n opts: AvailabilityOptions = {},\n): Promise<AvailabilityResult> {\n const stationCode = normalizeStation(station);\n const empty: AvailabilityResult = Object.freeze({\n station: stationCode,\n monthsCached: 0,\n firstMonth: null,\n lastMonth: null,\n climateYears: 0,\n firstClimateYear: null,\n lastClimateYear: null,\n });\n\n if (!hasListKeys(cache)) {\n return empty;\n }\n\n // Scan both the canonical cache-key form AND the original upper-cased\n // input (when they differ — e.g. user passed \"KNYC\" → normalized \"NYC\";\n // scan \":NYC:\" AND \":KNYC:\"). Codex iter-5 P2: a caller using the\n // documented `cacheKeyForObservations(\"KNYC\", ...)` helper writes under\n // :KNYC:, which the normalized prefix would miss.\n const upperInput = station.toUpperCase();\n const scanCodes = upperInput === stationCode ? [stationCode] : [stationCode, upperInput];\n\n const obsPrefixes = scanCodes.map((c) => `mostlyright:v1:observations:${c}:`);\n const climatePrefixes = scanCodes.map((c) => `mostlyright:v1:climate:${c}:`);\n\n const obsKeySets = await Promise.all(obsPrefixes.map((p) => cache.listKeys(p)));\n const climateKeySets = await Promise.all(climatePrefixes.map((p) => cache.listKeys(p)));\n const obsKeys = obsKeySets.flat();\n const climateKeys = climateKeySets.flat();\n\n // Collect the matching keys grouped by (year-month) / year so we can both\n // dedupe (e.g. per-source observation keys for the same month) and run a\n // single validation get() per group.\n const monthCandidates = new Map<string, string[]>();\n for (const key of obsKeys) {\n const m = OBS_KEY_RE.exec(key);\n if (m === null) continue;\n const keyStation = m[1] as string;\n if (!scanCodes.includes(keyStation)) continue;\n const ym = `${m[2]}-${m[3]}`;\n const arr = monthCandidates.get(ym) ?? [];\n arr.push(key);\n monthCandidates.set(ym, arr);\n }\n\n const yearCandidates = new Map<string, string[]>();\n for (const key of climateKeys) {\n const m = CLIMATE_KEY_RE.exec(key);\n if (m === null) continue;\n const keyStation = m[1] as string;\n if (!scanCodes.includes(keyStation)) continue;\n const y = m[2] as string;\n const arr = yearCandidates.get(y) ?? [];\n arr.push(key);\n yearCandidates.set(y, arr);\n }\n\n let months: string[];\n let years: string[];\n\n if (opts.validate === true) {\n // For each candidate group, confirm at least one key still resolves.\n // Stores lazy-evict expired entries inside get() — calling it discards\n // stale TTLs from the on-disk / on-IDB state and gives us a correct\n // overall count.\n const monthChecks = await Promise.all(\n [...monthCandidates.entries()].map(async ([ym, keys]) => {\n for (const k of keys) {\n if ((await cache.get(k)) !== null) return ym;\n }\n return null;\n }),\n );\n months = monthChecks.filter((v): v is string => v !== null).sort();\n const yearChecks = await Promise.all(\n [...yearCandidates.entries()].map(async ([y, keys]) => {\n for (const k of keys) {\n if ((await cache.get(k)) !== null) return y;\n }\n return null;\n }),\n );\n years = yearChecks.filter((v): v is string => v !== null).sort();\n } else {\n months = [...monthCandidates.keys()].sort();\n years = [...yearCandidates.keys()].sort();\n }\n\n return Object.freeze({\n station: stationCode,\n monthsCached: months.length,\n firstMonth: months[0] ?? null,\n lastMonth: months.at(-1) ?? null,\n climateYears: years.length,\n firstClimateYear: years[0] ?? null,\n lastClimateYear: years.at(-1) ?? null,\n });\n}\n","// TS-W6 Wave 2 — internationalDailyExtremes rollup.\n//\n// Ports Python `mostlyright.international.daily_extremes` semantics:\n//\n// - For each row, parse `observed_at` as UTC, convert to the station's IANA\n// local calendar date via `Intl.DateTimeFormat` (the only universally-\n// available tz-aware extractor in JS), bucket per `(localDate)`.\n// - For each bucketed day:\n// * n_obs < 12 → tmin/tmax/tmean null (low coverage; matches Python\n// `_LOW_COVERAGE_THRESHOLD`).\n// * Otherwise: tmin = min(temp_c), tmax = max(temp_c), tmean = mean.\n// Round HALF_UP — to 1 decimal for US stations, whole °C for intl.\n// - precip_mm summed across rows that report it (mm — matches the\n// observation schema field name; Python uses `precip_inches` because its\n// historical units are inches, but the TS schema is mm).\n//\n// `stationTz` is required because the input rows are raw observations (no\n// station registry lookup here); callers supply the IANA tz string for the\n// station they're rolling up.\n\nconst LOW_COVERAGE_THRESHOLD = 12;\n\n/** Minimal row shape consumed by `internationalDailyExtremes`. */\nexport interface InternationalRow {\n /** ISO 8601 UTC instant. Must end with `Z` or include an offset. */\n observed_at?: string | null;\n /** Air temperature in degrees Celsius. */\n temp_c?: number | null;\n /** 1-hour precipitation total in millimeters. */\n precip_mm_1h?: number | null;\n /** Source identifier (preserved on the tmin/tmax aggregate). */\n source?: string | null;\n}\n\nexport interface DailyExtreme {\n /** Station-local calendar date as `YYYY-MM-DD`. */\n localDate: string;\n /** Count of rows with a parseable temp_c. */\n nObs: number;\n /** Min temperature in °C, or null on low coverage. */\n tempMinC: number | null;\n /** Max temperature in °C, or null on low coverage. */\n tempMaxC: number | null;\n /** Mean temperature in °C, or null on low coverage. */\n tempMeanC: number | null;\n /** Min temperature in °F, or null on low coverage. */\n tempMinF: number | null;\n /** Max temperature in °F, or null on low coverage. */\n tempMaxF: number | null;\n /** Total 1-hour precipitation across the local day, in mm. */\n precipMm: number;\n /** Source identifier of the row that produced tmin (or null on low coverage). */\n sourceTmin: string | null;\n /** Source identifier of the row that produced tmax (or null on low coverage). */\n sourceTmax: string | null;\n}\n\nexport interface InternationalDailyExtremesOptions {\n /** IANA timezone identifier for the station, e.g. `\"Asia/Tokyo\"`. Required. */\n stationTz: string;\n /**\n * Decimal places for HALF_UP rounding. Defaults to 0 (whole °C) — the\n * international convention. Pass `1` for US-station tenths.\n */\n precision?: number;\n /**\n * Minimum number of observations required for tmin/tmax/tmean to be\n * populated. Defaults to 12 (the Python threshold). Tests can override.\n */\n minObs?: number;\n}\n\nconst PARTS_CACHE = new Map<string, Intl.DateTimeFormat>();\n\nfunction getDateFormatter(tz: string): Intl.DateTimeFormat {\n let f = PARTS_CACHE.get(tz);\n if (f === undefined) {\n // `formatToParts` with these options yields `{year, month, day}` parts\n // for the given UTC instant in the requested tz. The result is the\n // local calendar date even across DST and arbitrary UTC offsets.\n f = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: tz,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n });\n PARTS_CACHE.set(tz, f);\n }\n return f;\n}\n\nfunction localDateFor(instant: Date, tz: string): string {\n const parts = getDateFormatter(tz).formatToParts(instant);\n let y = \"\";\n let m = \"\";\n let d = \"\";\n for (const p of parts) {\n if (p.type === \"year\") y = p.value;\n else if (p.type === \"month\") m = p.value;\n else if (p.type === \"day\") d = p.value;\n }\n return `${y}-${m}-${d}`;\n}\n\nfunction parseInstant(observed: string | null | undefined): Date | null {\n if (observed === undefined || observed === null || observed.length === 0) {\n return null;\n }\n const ms = Date.parse(observed);\n if (Number.isNaN(ms)) return null;\n return new Date(ms);\n}\n\nfunction roundHalfUp(value: number, places: number): number {\n if (!Number.isFinite(value)) return value;\n // HALF_UP rounding: tie-breaks always round away from zero for positives,\n // toward +∞ for negatives. JS `Math.round` rounds half toward +∞, which\n // disagrees with HALF_UP on negative .5 boundaries; replicate the Python\n // Decimal behavior by adjusting positives only and mirroring negatives.\n const scale = 10 ** places;\n const sign = value < 0 ? -1 : 1;\n const abs = Math.abs(value);\n // Add a tiny epsilon scaled to the magnitude to defeat IEEE-754 errors that\n // make `2.675 * 100` come out as `267.49999...`. The epsilon is small\n // enough (relative-1e-12) not to affect legitimate non-tie values.\n const rounded = Math.floor(abs * scale + 0.5 + abs * 1e-12) / scale;\n return sign * rounded;\n}\n\nfunction cToF(c: number): number {\n return c * 1.8 + 32;\n}\n\n/**\n * Roll up observation rows to per-local-calendar-day temperature extremes.\n *\n * @param rows raw observation rows (any source). Rows without a parseable\n * `observed_at` are dropped.\n * @param opts `stationTz` is required. Optional `precision` (default 0;\n * pass 1 for US-station tenths) and `minObs` (default 12).\n *\n * @returns one entry per local calendar day with at least one row. Days\n * with fewer than `minObs` rows have temps set to null.\n */\nexport function internationalDailyExtremes(\n rows: ReadonlyArray<InternationalRow>,\n opts: InternationalDailyExtremesOptions,\n): DailyExtreme[] {\n const tz = opts.stationTz;\n if (typeof tz !== \"string\" || tz.length === 0) {\n throw new RangeError(\"internationalDailyExtremes: stationTz is required (non-empty string)\");\n }\n // Phase 18: integer-°F lattice rationale.\n // For US stations (ASOS), constituent observations carry temp_c values\n // recovered from Tgroup tenths-°C (e.g. 10.0, 11.1, 12.2 from integer °F\n // 50, 52, 54 — Phase 18 PREC-01/PREC-02). tmin/tmax = min/max of those\n // values are themselves on the integer-°F lattice, and 0.1°C HALF_UP\n // rounding is a no-op. tmean IS a non-lattice average and benefits from\n // 0.1°C rounding. For international stations (no Tgroup), constituent\n // values are derived floats and rounding to whole °C (`precision = 0`) is\n // the convention.\n //\n // Callers pass `precision: 1` for US ASOS data and `precision: 0` (the\n // default) for international. See\n // .planning/phases/18-precision-fix-asos-integer-fahrenheit/18-CONTEXT.md.\n const precision = opts.precision ?? 0;\n const minObs = opts.minObs ?? LOW_COVERAGE_THRESHOLD;\n\n // Validate tz up front so we get a clean error rather than per-row failures.\n try {\n getDateFormatter(tz);\n } catch (e) {\n throw new RangeError(\n `internationalDailyExtremes: invalid stationTz ${JSON.stringify(tz)}: ${(e as Error).message}`,\n );\n }\n\n type Bucket = {\n temps: { value: number; source: string | null }[];\n precipMm: number;\n };\n const byLocalDate = new Map<string, Bucket>();\n\n for (const row of rows) {\n const instant = parseInstant(row.observed_at);\n if (instant === null) continue;\n const localDate = localDateFor(instant, tz);\n let bucket = byLocalDate.get(localDate);\n if (bucket === undefined) {\n bucket = { temps: [], precipMm: 0 };\n byLocalDate.set(localDate, bucket);\n }\n const t = row.temp_c;\n if (typeof t === \"number\" && Number.isFinite(t)) {\n bucket.temps.push({ value: t, source: row.source ?? null });\n }\n const p = row.precip_mm_1h;\n if (typeof p === \"number\" && Number.isFinite(p)) {\n bucket.precipMm += p;\n }\n }\n\n const out: DailyExtreme[] = [];\n const sortedDates = [...byLocalDate.keys()].sort();\n for (const localDate of sortedDates) {\n const bucket = byLocalDate.get(localDate);\n if (bucket === undefined) continue;\n const nObs = bucket.temps.length;\n let tempMinC: number | null = null;\n let tempMaxC: number | null = null;\n let tempMeanC: number | null = null;\n let sourceTmin: string | null = null;\n let sourceTmax: string | null = null;\n\n // Codex iter-4 P2: `nObs >= minObs` alone is not enough when minObs=0,\n // because a day with a parseable timestamp but no finite temp_c reaches\n // this branch with nObs === 0 and then dereferences bucket.temps[0].\n // Always require at least one temperature row before computing extremes.\n if (nObs > 0 && nObs >= minObs) {\n let minIdx = 0;\n let maxIdx = 0;\n let sum = 0;\n for (let i = 0; i < bucket.temps.length; i += 1) {\n const v = bucket.temps[i] as { value: number; source: string | null };\n sum += v.value;\n const minRow = bucket.temps[minIdx] as { value: number };\n const maxRow = bucket.temps[maxIdx] as { value: number };\n if (v.value < minRow.value) minIdx = i;\n if (v.value > maxRow.value) maxIdx = i;\n }\n const mean = sum / nObs;\n const minRow = bucket.temps[minIdx] as { value: number; source: string | null };\n const maxRow = bucket.temps[maxIdx] as { value: number; source: string | null };\n tempMinC = roundHalfUp(minRow.value, precision);\n tempMaxC = roundHalfUp(maxRow.value, precision);\n tempMeanC = roundHalfUp(mean, precision);\n sourceTmin = minRow.source;\n sourceTmax = maxRow.source;\n }\n\n out.push(\n Object.freeze({\n localDate,\n nObs,\n tempMinC,\n tempMaxC,\n tempMeanC,\n tempMinF: tempMinC === null ? null : roundHalfUp(cToF(tempMinC), precision),\n tempMaxF: tempMaxC === null ? null : roundHalfUp(cToF(tempMaxC), precision),\n precipMm: roundHalfUp(bucket.precipMm, 4),\n sourceTmin,\n sourceTmax,\n }),\n );\n }\n\n return out;\n}\n","// Structured exception hierarchy for the mostlyright TS SDK.\n//\n// Mirrors the Python design in `packages/core/src/mostlyright/core/exceptions.py`\n// and `packages/core/src/mostlyright/_internal/exceptions.py`. Every error\n// subclasses `MostlyRightError` and exposes a `toDict()` method that returns a\n// JSON-safe payload suitable for MCP `error.data` / extension messaging.\n//\n// Role-name vocabulary for SourceMismatchError matches Python:\n// \"observations\" / \"forecasts\" / \"settlement\" (long form, NOT the col prefixes)\n\n// ---------------------------------------------------------------------------\n// JSON-safe coercion (mirrors `mostlyright.core._json_safe.to_json_safe`)\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively coerce `value` into a JSON-serializable structure.\n *\n * Coercion rules (mirrors Python's `to_json_safe`):\n * - `null` / `undefined` / `NaN` / `Infinity` / `-Infinity` → `null`\n * - `Date` → ISO 8601 UTC string ending in `Z`\n * - Numeric / boolean / string scalars pass through (non-finite numbers → null)\n * - Arrays + plain objects → recursive (cycles → `{ _cycle: true, value: String(obj) }`)\n * - Dict keys MUST be strings; non-string keys throw `TypeError`.\n * - Anything else (Symbol, function, class instance without `toJSON`) →\n * `{ _repr_only: true, value: String(value) }`.\n */\nexport function toJsonSafe(value: unknown, seen?: WeakSet<object>): unknown {\n const visited = seen ?? new WeakSet<object>();\n\n // null / undefined → null\n if (value === null || value === undefined) {\n return null;\n }\n\n // Booleans pass through.\n if (typeof value === \"boolean\") {\n return value;\n }\n\n // Strings pass through.\n if (typeof value === \"string\") {\n return value;\n }\n\n // Numbers — non-finite (NaN, +/-Infinity) coerce to null.\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n\n // bigint → number when safe, else stringify (JSON can't natively encode bigint).\n if (typeof value === \"bigint\") {\n // Match Python: integers pass through as native numeric. Safe-range only.\n if (value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) {\n return Number(value);\n }\n return { _repr_only: true, value: value.toString() };\n }\n\n // Date → ISO 8601 UTC string (always ending in \"Z\").\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n return null;\n }\n return value.toISOString();\n }\n\n // Arrays — recurse, track cycles.\n if (Array.isArray(value)) {\n if (visited.has(value)) {\n return { _cycle: true, value: String(value) };\n }\n visited.add(value);\n try {\n return value.map((item) => toJsonSafe(item, visited));\n } finally {\n visited.delete(value);\n }\n }\n\n // Plain objects — recurse, track cycles, enforce string keys.\n if (typeof value === \"object\") {\n if (visited.has(value as object)) {\n return { _cycle: true, value: String(value) };\n }\n visited.add(value as object);\n try {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>)) {\n if (typeof key !== \"string\") {\n throw new TypeError(`toJsonSafe dict keys must be string; got ${typeof key}`);\n }\n out[key] = toJsonSafe((value as Record<string, unknown>)[key], visited);\n }\n return out;\n } finally {\n visited.delete(value as object);\n }\n }\n\n // Symbols, functions, etc. — repr-only marker.\n return { _repr_only: true, value: String(value) };\n}\n\n// ---------------------------------------------------------------------------\n// Base class\n// ---------------------------------------------------------------------------\n\nexport interface MostlyRightErrorOptions {\n errorCode?: string;\n source?: string | null;\n requestId?: string | null;\n}\n\n/**\n * Base class for all mostlyright structured errors.\n *\n * `errorCode` is a stable enum (e.g. \"SOURCE_UNAVAILABLE\") used by callers /\n * agents to branch on without parsing message text. `source` is the source id\n * involved (e.g. \"iem.archive\") when applicable, and `requestId` correlates a\n * JSON-RPC / MCP request id when applicable.\n */\nexport class MostlyRightError extends Error {\n /** Subclass override — the stable string enum surfaced via `errorCode`. */\n static defaultErrorCode = \"MOSTLYRIGHT_ERROR\";\n\n readonly errorCode: string;\n readonly source: string | null;\n readonly requestId: string | null;\n\n constructor(message = \"\", options: MostlyRightErrorOptions = {}) {\n super(message);\n this.name = new.target.name;\n const ctor = this.constructor as typeof MostlyRightError;\n this.errorCode = options.errorCode ?? ctor.defaultErrorCode;\n this.source = options.source ?? null;\n this.requestId = options.requestId ?? null;\n // Restore prototype chain after `Error` (needed for `instanceof` across\n // transpilation / ES5 targets).\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /**\n * Subclass hook returning the structured attributes for `toDict`.\n * Values are passed through `toJsonSafe` by `toDict()`, so subclasses\n * don't need to coerce values themselves.\n */\n protected payload(): Record<string, unknown> {\n return {\n error_code: this.errorCode,\n message: this.message,\n source: this.source,\n request_id: this.requestId,\n };\n }\n\n /** Return a JSON-safe dict suitable for MCP `error.data`. */\n toDict(): Record<string, unknown> {\n const safe = toJsonSafe(this.payload());\n return safe as Record<string, unknown>;\n }\n}\n\n// ---------------------------------------------------------------------------\n// SourceUnavailableError\n// ---------------------------------------------------------------------------\n\nexport interface SourceUnavailableErrorOptions extends MostlyRightErrorOptions {\n httpStatus?: number | null;\n retryable?: boolean;\n retryAfterS?: number | null;\n underlying?: string;\n url?: string | null;\n}\n\nexport class SourceUnavailableError extends MostlyRightError {\n static override defaultErrorCode = \"SOURCE_UNAVAILABLE\";\n\n readonly httpStatus: number | null;\n readonly retryable: boolean;\n readonly retryAfterS: number | null;\n readonly underlying: string;\n readonly url: string | null;\n\n constructor(message = \"\", options: SourceUnavailableErrorOptions = {}) {\n super(message, options);\n this.httpStatus = options.httpStatus ?? null;\n this.retryable = options.retryable ?? false;\n this.retryAfterS = options.retryAfterS ?? null;\n this.underlying = options.underlying ?? \"\";\n this.url = options.url ?? null;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n http_status: this.httpStatus,\n retryable: this.retryable,\n retry_after_s: this.retryAfterS,\n underlying: this.underlying,\n url: this.url,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// DataAvailabilityError (Phase 21 21-09)\n// ---------------------------------------------------------------------------\n//\n// Typed exception for \"I tried to fetch and got nothing usable\" — replaces\n// 3+ overloaded SourceUnavailableError sites where consumers had to parse\n// message strings to differentiate (rate-limit retry vs model-unavailable\n// hard-fail vs cache-miss re-fetch). The reason enum is shared lockstep\n// with Python (`mostlyright.core.exceptions.DataAvailabilityError`); drift\n// is the load-bearing risk.\n\n/** Shared reason enum — MUST match Python EXACTLY (Phase 21 D-04). */\nexport const DATA_AVAILABILITY_REASONS = [\n \"model_unavailable\",\n \"out_of_window\",\n \"cache_miss\",\n \"source_404\",\n \"source_5xx\",\n \"rate_limited\",\n] as const;\n\nexport type DataAvailabilityReason = (typeof DATA_AVAILABILITY_REASONS)[number];\n\nexport interface DataAvailabilityErrorOptions extends MostlyRightErrorOptions {\n reason: DataAvailabilityReason;\n hint: string;\n}\n\nexport class DataAvailabilityError extends MostlyRightError {\n static override defaultErrorCode = \"DATA_AVAILABILITY\";\n\n readonly reason: DataAvailabilityReason;\n readonly hint: string;\n\n constructor(options: DataAvailabilityErrorOptions) {\n if (!DATA_AVAILABILITY_REASONS.includes(options.reason)) {\n throw new RangeError(\n `DataAvailabilityError: unknown reason \"${String(options.reason)}\". ` +\n `Valid reasons: ${DATA_AVAILABILITY_REASONS.join(\", \")}`,\n );\n }\n if (typeof options.hint !== \"string\" || options.hint.length === 0) {\n throw new TypeError(\"DataAvailabilityError: hint is required and must be a non-empty string\");\n }\n const message = `[${options.reason}] ${options.hint}`;\n super(message, options);\n this.reason = options.reason;\n this.hint = options.hint;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n reason: this.reason,\n hint: this.hint,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// NwpNotAvailableError (Post-21-07 follow-up)\n// ---------------------------------------------------------------------------\n//\n// Subclass of DataAvailabilityError raised when the TS `forecastNwp()` stub\n// is called. Phase 21 21-07 routes the generic stub error through\n// DataAvailabilityError; this dedicated subclass adds:\n//\n// 1. instanceof-based dispatch — `catch (e) { if (e instanceof\n// NwpNotAvailableError) ... }` is cleaner than `e.reason ===\n// \"model_unavailable\" && e.source === \"nwp-stub\"`.\n// 2. IDE autocomplete on `.model` + `.station` — narrows the payload so\n// consumers don't have to parse `e.hint` to retrieve them.\n// 3. A typed compile-time signal: `forecastNwp()` returns\n// `Promise<never>`, but the throw site declares `@throws\n// NwpNotAvailableError` so JSDoc tooling surfaces the deferral up-front.\n//\n// Back-compat: NwpNotAvailableError IS-A DataAvailabilityError with\n// reason=\"model_unavailable\", so existing `catch (e) { if (e instanceof\n// DataAvailabilityError && e.reason === \"model_unavailable\") ... }` paths\n// continue to work unchanged.\n\nexport interface NwpNotAvailableErrorOptions extends MostlyRightErrorOptions {\n /** Station the caller asked for (echoed back for log/error attribution). */\n station: string;\n /** NWP model the caller asked for (e.g. `\"gfs\"`, `\"hrrr\"`). */\n model: string;\n /** Operator-actionable hint. Required (matches DataAvailabilityError contract). */\n hint: string;\n}\n\n/**\n * Raised when the TS `forecastNwp()` stub is called.\n *\n * **Why this exists:** no production-ready browser GRIB2 decoder ships in\n * v1.x (eccodes / cfgrib are C/Python only; WASM compile-time + bundle\n * size make a browser port impractical today). The function signature is\n * stable so callers can write code today; v2.0+ lands the execution body.\n *\n * **Recommended catch pattern:**\n *\n * ```ts\n * import { forecastNwp } from '@mostlyrightmd/weather';\n * import { NwpNotAvailableError } from '@mostlyrightmd/core';\n *\n * try {\n * const grid = await forecastNwp('KNYC', 'gfs');\n * } catch (e) {\n * if (e instanceof NwpNotAvailableError) {\n * console.warn(`NWP deferred to v2.0+; ${e.hint}`);\n * // Fall back to iemMosForecasts() when available, else Python SDK.\n * } else {\n * throw e;\n * }\n * }\n * ```\n *\n * See [docs/nwp-forecasts.md](https://mostlyright.md/docs/sdk/typescript/nwp-forecasts/)\n * for the full architectural rationale and the v2.0+ roadmap.\n */\nexport class NwpNotAvailableError extends DataAvailabilityError {\n static override defaultErrorCode = \"NWP_NOT_AVAILABLE\";\n\n readonly station: string;\n readonly model: string;\n\n constructor(options: NwpNotAvailableErrorOptions) {\n // exactOptionalPropertyTypes: true — only include requestId when the\n // caller passed a non-undefined value. Passing `requestId: undefined`\n // explicitly would type-error against the parent's narrower contract.\n const parentOpts: DataAvailabilityErrorOptions = {\n reason: \"model_unavailable\",\n hint: options.hint,\n source: options.source ?? `nwp.${options.model}`,\n };\n if (options.requestId !== undefined && options.requestId !== null) {\n parentOpts.requestId = options.requestId;\n }\n super(parentOpts);\n this.station = options.station;\n this.model = options.model;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n station: this.station,\n model: this.model,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// SchemaValidationError\n// ---------------------------------------------------------------------------\n\nexport interface SchemaValidationErrorOptions extends MostlyRightErrorOptions {\n schemaId: string;\n violations?: Array<Record<string, unknown>>;\n quarantineCount?: number;\n sampleViolations?: Array<Record<string, unknown>>;\n}\n\nexport class SchemaValidationError extends MostlyRightError {\n static override defaultErrorCode = \"SCHEMA_VALIDATION_FAILED\";\n\n readonly schemaId: string;\n readonly violations: Array<Record<string, unknown>>;\n readonly quarantineCount: number;\n readonly sampleViolations: Array<Record<string, unknown>>;\n\n constructor(message: string, options: SchemaValidationErrorOptions) {\n super(message, options);\n this.schemaId = options.schemaId;\n this.violations = [...(options.violations ?? [])];\n this.quarantineCount = options.quarantineCount ?? 0;\n this.sampleViolations = [...(options.sampleViolations ?? [])];\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n schema_id: this.schemaId,\n violations: this.violations,\n quarantine_count: this.quarantineCount,\n sample_violations: this.sampleViolations,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// SourceMismatchError\n// ---------------------------------------------------------------------------\n\nexport type SourceMismatchRole = \"observations\" | \"forecasts\" | \"settlement\";\n\nexport interface SourceMismatchErrorOptions extends MostlyRightErrorOptions {\n schemaSource: string;\n dataSource: string;\n role?: SourceMismatchRole | null;\n catalogWarning?: string | null;\n}\n\nexport class SourceMismatchError extends MostlyRightError {\n static override defaultErrorCode = \"SOURCE_MISMATCH\";\n\n /** Canonical role-name vocabulary (design.md §R). */\n static readonly VALID_ROLES: ReadonlySet<SourceMismatchRole> = new Set([\n \"observations\",\n \"forecasts\",\n \"settlement\",\n ]);\n\n readonly schemaSource: string;\n readonly dataSource: string;\n readonly role: SourceMismatchRole | null;\n readonly catalogWarning: string | null;\n\n constructor(message: string, options: SourceMismatchErrorOptions) {\n super(message, options);\n this.schemaSource = options.schemaSource;\n this.dataSource = options.dataSource;\n this.role = options.role ?? null;\n this.catalogWarning = options.catalogWarning ?? null;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n schema_source: this.schemaSource,\n data_source: this.dataSource,\n role: this.role,\n catalog_warning: this.catalogWarning,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// LeakageError\n// ---------------------------------------------------------------------------\n\nexport interface LeakageErrorOptions extends MostlyRightErrorOptions {\n asOf: string;\n violatingCount: number;\n sampleViolations?: Array<Record<string, unknown>>;\n}\n\nexport class LeakageError extends MostlyRightError {\n static override defaultErrorCode = \"LEAKAGE_DETECTED\";\n\n readonly asOf: string;\n readonly violatingCount: number;\n readonly sampleViolations: Array<Record<string, unknown>>;\n\n constructor(message: string, options: LeakageErrorOptions) {\n super(message, options);\n this.asOf = options.asOf;\n this.violatingCount = options.violatingCount;\n this.sampleViolations = [...(options.sampleViolations ?? [])];\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n as_of: this.asOf,\n violating_count: this.violatingCount,\n sample_violations: this.sampleViolations,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// IssuedAtMissingError (Phase 20 OM-04)\n// ---------------------------------------------------------------------------\n\nexport interface IssuedAtMissingErrorOptions extends MostlyRightErrorOptions {\n violatingCount?: number;\n sampleViolations?: Array<Record<string, unknown>>;\n}\n\n/**\n * A forecast row would land with `issuedAt = null`.\n *\n * Phase 20 OM-04. Origin: Tarabcak/mostlyright#70 — the legacy\n * seamless-feed bug where `/forecast_series` proxied Open-Meteo's\n * seamless endpoint without preserving `issued_at`, silently using\n * post-snapshot model runs in training data.\n */\nexport class IssuedAtMissingError extends SchemaValidationError {\n static override defaultErrorCode = \"ISSUED_AT_MISSING\";\n\n readonly violatingCountRows: number;\n\n constructor(message: string, options: IssuedAtMissingErrorOptions = {}) {\n super(message, {\n ...options,\n schemaId: \"schema.forecast.station.v1\",\n violations: [{ column: \"issued_at\", rule: \"non_null\" }],\n });\n this.violatingCountRows = options.violatingCount ?? 0;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n name: \"IssuedAtMissingError\",\n violating_count: this.violatingCountRows,\n origin_issue: \"Tarabcak/mostlyright#70\",\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// OpenMeteoSeamlessLeakageError (Phase 20 OM-04)\n// ---------------------------------------------------------------------------\n\nexport interface OpenMeteoSeamlessLeakageErrorOptions extends MostlyRightErrorOptions {\n model: string;\n endpointUrl: string;\n asOf?: string | null;\n}\n\n/**\n * The Open-Meteo Historical Forecast (seamless) endpoint was used\n * without `allowLeakage: true`.\n *\n * Phase 20 D-01 (locked decision): the seamless endpoint silently\n * stitches forecasts from multiple model cycles into a continuous\n * timeseries; the cycle that produced each value is unrecoverable from\n * the response. `LeakageDetector` rejects rows tagged\n * `source=\"open_meteo.seamless\"` whenever `as_of` is asserted.\n *\n * Origin: Tarabcak/mostlyright#70.\n */\nexport class OpenMeteoSeamlessLeakageError extends LeakageError {\n static override defaultErrorCode = \"OPEN_METEO_SEAMLESS_LEAKAGE\";\n\n readonly model: string;\n readonly endpointUrl: string;\n\n constructor(message: string, options: OpenMeteoSeamlessLeakageErrorOptions) {\n super(message, {\n ...options,\n asOf: options.asOf ?? \"(seamless-endpoint-refused-before-fetch)\",\n violatingCount: 0,\n });\n this.model = options.model;\n this.endpointUrl = options.endpointUrl;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n name: \"OpenMeteoSeamlessLeakageError\",\n model: this.model,\n endpoint_url: this.endpointUrl,\n origin_issue: \"Tarabcak/mostlyright#70\",\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// TemporalDriftError\n// ---------------------------------------------------------------------------\n\nexport interface TemporalDriftErrorOptions extends MostlyRightErrorOptions {\n schemaId: string;\n assertedRange: [string, string];\n violatingRows: number;\n sampleViolations?: Array<Record<string, unknown>>;\n}\n\nexport class TemporalDriftError extends MostlyRightError {\n static override defaultErrorCode = \"TEMPORAL_DRIFT\";\n\n readonly schemaId: string;\n readonly assertedRange: [string, string];\n readonly violatingRows: number;\n readonly sampleViolations: Array<Record<string, unknown>>;\n\n constructor(message: string, options: TemporalDriftErrorOptions) {\n super(message, options);\n this.schemaId = options.schemaId;\n this.assertedRange = [options.assertedRange[0], options.assertedRange[1]];\n this.violatingRows = options.violatingRows;\n this.sampleViolations = [...(options.sampleViolations ?? [])];\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n schema_id: this.schemaId,\n asserted_range: [this.assertedRange[0], this.assertedRange[1]],\n violating_rows: this.violatingRows,\n sample_violations: this.sampleViolations,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// PayloadTooLargeError\n// ---------------------------------------------------------------------------\n\nexport interface PayloadTooLargeErrorOptions extends MostlyRightErrorOptions {\n declaredSize: number;\n limit: number;\n acceptedModes?: string[];\n}\n\nexport class PayloadTooLargeError extends MostlyRightError {\n static override defaultErrorCode = \"PAYLOAD_TOO_LARGE\";\n\n readonly declaredSize: number;\n readonly limit: number;\n readonly acceptedModes: string[];\n\n constructor(message: string, options: PayloadTooLargeErrorOptions) {\n super(message, options);\n this.declaredSize = options.declaredSize;\n this.limit = options.limit;\n this.acceptedModes = [...(options.acceptedModes ?? [])];\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n declared_size: this.declaredSize,\n limit: this.limit,\n accepted_modes: this.acceptedModes,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// DeferredMarketError (TS-W6 placeholder; mirrors mostlyright.international)\n// ---------------------------------------------------------------------------\n\nexport class DeferredMarketError extends MostlyRightError {\n static override defaultErrorCode = \"DEFERRED_MARKET\";\n}\n\n// ---------------------------------------------------------------------------\n// PolymarketEventError (TS-W5 placeholder; mirrors mostlyright.markets.polymarket)\n// ---------------------------------------------------------------------------\n\nexport class PolymarketEventError extends MostlyRightError {\n static override defaultErrorCode = \"POLYMARKET_EVENT_INVALID\";\n}\n\n// ---------------------------------------------------------------------------\n// HTTP-layer hierarchy (mirrors mostlyright._internal.exceptions)\n// ---------------------------------------------------------------------------\n\nexport interface TherminalErrorOptions extends MostlyRightErrorOptions {\n statusCode?: number | null;\n retryAfter?: number | null;\n}\n\n/**\n * Base HTTP-layer marker. Subclass of `MostlyRightError` so callers that\n * catch `MostlyRightError` also catch transport errors.\n */\nexport class TherminalError extends MostlyRightError {\n static override defaultErrorCode = \"HTTP_ERROR\";\n\n readonly statusCode: number | null;\n\n constructor(message: string, options: TherminalErrorOptions = {}) {\n super(message, options);\n this.statusCode = options.statusCode ?? null;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n status_code: this.statusCode,\n };\n }\n}\n\nexport class NotFoundError extends TherminalError {\n static override defaultErrorCode = \"HTTP_NOT_FOUND\";\n\n constructor(message = \"Resource not found\", options: TherminalErrorOptions = {}) {\n super(message, { ...options, statusCode: options.statusCode ?? 404 });\n }\n}\n\nexport interface RateLimitErrorOptions extends TherminalErrorOptions {\n retryAfter?: number | null;\n}\n\nexport class RateLimitError extends TherminalError {\n static override defaultErrorCode = \"HTTP_RATE_LIMITED\";\n\n readonly retryAfter: number | null;\n\n constructor(retryAfter: number | null = 1, options: RateLimitErrorOptions = {}) {\n const msg = `Rate limit exceeded. Retry after ${retryAfter ?? \"?\"}s`;\n super(msg, { ...options, statusCode: options.statusCode ?? 429 });\n this.retryAfter = retryAfter;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n retry_after: this.retryAfter,\n };\n }\n}\n\nexport class ValidationError extends TherminalError {\n static override defaultErrorCode = \"HTTP_BAD_REQUEST\";\n\n constructor(message = \"Invalid request\", options: TherminalErrorOptions = {}) {\n super(message, { ...options, statusCode: options.statusCode ?? 400 });\n }\n}\n\nexport class AuthenticationError extends TherminalError {\n static override defaultErrorCode = \"HTTP_UNAUTHORIZED\";\n\n constructor(message = \"Authentication required\", options: TherminalErrorOptions = {}) {\n super(message, { ...options, statusCode: options.statusCode ?? 401 });\n }\n}\n\nexport class ForbiddenError extends TherminalError {\n static override defaultErrorCode = \"HTTP_FORBIDDEN\";\n\n constructor(message = \"Access denied\", options: TherminalErrorOptions = {}) {\n super(message, { ...options, statusCode: options.statusCode ?? 403 });\n }\n}\n\nexport class ServerError extends TherminalError {\n static override defaultErrorCode = \"HTTP_SERVER_ERROR\";\n\n constructor(message = \"Server error\", options: TherminalErrorOptions = {}) {\n super(message, { ...options, statusCode: options.statusCode ?? 500 });\n }\n}\n\n// ---------------------------------------------------------------------------\n// LiveStreamError + NoLiveDataError (Phase 11)\n// ---------------------------------------------------------------------------\n\n/**\n * Base class for `mostlyright.live.stream` / `live.latest` failures.\n *\n * Mirrors Python `LiveStreamError`. Live-streaming errors are a separate\n * sub-tree from `SourceUnavailableError` because the recovery path differs —\n * `stream()` swallows empty-tick errors and waits for the next polite-floor\n * cycle. Only `latest()` raises `NoLiveDataError` on empty responses.\n */\nexport class LiveStreamError extends MostlyRightError {\n static override defaultErrorCode = \"LIVE_STREAM_ERROR\";\n}\n\nexport interface NoLiveDataErrorOptions extends MostlyRightErrorOptions {\n station: string;\n source: string;\n}\n\n/**\n * `mostlyright.live.latest` returned no observations for the station.\n *\n * Carries the resolved ICAO `station` and the canonical source identity\n * tag (`\"awc.live\"` / `\"iem.live\"`) so caller logs can branch by source\n * without re-parsing the message.\n */\nexport class NoLiveDataError extends LiveStreamError {\n static override defaultErrorCode = \"NO_LIVE_DATA\";\n\n readonly station: string;\n\n constructor(message: string, options: NoLiveDataErrorOptions) {\n super(message, { ...options, source: options.source });\n this.station = options.station;\n }\n\n protected override payload(): Record<string, unknown> {\n return {\n ...super.payload(),\n station: this.station,\n };\n }\n}\n","// TOON v3.0 tabular format — byte-equivalent to Python `encode_tabular`.\n//\n// Ports the encoder portions of\n// `packages/core/src/mostlyright/core/formats/_toon.py` and the tabular\n// loader from `packages/core/src/mostlyright/core/formats/toon.py`.\n//\n// Wire shape (single tabular block):\n//\n// rows[N]{col1,col2,col3}:\n// v1a,v2a,v3a\n// v1b,v2b,v3b\n//\n// Where N is the row count, `{...}` is the column list, each subsequent line\n// is one row's values. Column order comes from the first row's keys.\n//\n// TOON loss matrix (matches Python `toon.py`):\n// - dict/object cells stringify deterministically via canonical JSON\n// (sorted keys + JSON.stringify of nested values).\n// - null + undefined both encode as the bare literal `null`.\n// - NaN / +Infinity / -Infinity encode as `null` (per `_format_number`).\n// - Integer-valued floats serialize without a fractional part (1.0 → \"1\").\n// - Strings that look numeric, start with - / + / digit, are\n// `true`/`false`/`null`, or contain commas/quotes/control chars are\n// quoted. Otherwise emitted bare.\n\n// ---------------------------------------------------------------------------\n// Encoder regex set (mirrors Python `_toon.py`)\n// ---------------------------------------------------------------------------\n\nconst SAFE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_.]*$/;\nconst NUMERIC_LIKE_RE = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/;\n// Quote triggers per TOON spec: colon, quote, backslash, brackets, braces,\n// ASCII control chars, NEL, LSEP, PSEP. Constructed via RegExp(...) to avoid\n// embedding literal LSEP/PSEP bytes (esbuild rejects those in regex literals).\n// biome-ignore lint/suspicious/noControlCharactersInRegex: TOON spec parity\nconst NEEDS_QUOTE_CHARS_RE = /[:\\\\\\\"'\\[\\]{}\\x00-\\x1f\\x7f\\x85\\u2028\\u2029]/;\n// Control chars without a defined TOON escape — stripped on quote.\n// biome-ignore lint/suspicious/noControlCharactersInRegex: TOON spec parity\nconst UNSUPPORTED_CTRL_RE = /[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f\\x85\\u2028\\u2029]/g;\n\n// ---------------------------------------------------------------------------\n// Scalar encoding\n// ---------------------------------------------------------------------------\n\nfunction formatNumber(n: number): string {\n if (!Number.isFinite(n)) return \"null\";\n if (n === 0) return \"0\"; // collapses -0 to \"0\"\n if (Number.isInteger(n) && Math.abs(n) <= 2 ** 53) return String(n);\n let s = String(n);\n // Expand scientific notation to plain decimal. Python uses Decimal —\n // we use toFixed where possible, falling back to toPrecision-based logic.\n if (/[eE]/.test(s)) {\n // toFixed/toPrecision don't fully match Python's Decimal expansion at\n // extreme values, but for the values realistically in weather data\n // (temperatures, lat/lng) String(n) returns plain decimal already.\n // For extreme cases, fall back to a manual expansion.\n s = expandExponent(s);\n }\n return s;\n}\n\nfunction expandExponent(s: string): string {\n const m = /^(-?)(\\d+(?:\\.\\d+)?)[eE]([+-]?\\d+)$/.exec(s);\n if (!m) return s;\n const sign = m[1] ?? \"\";\n const mantissa = m[2] ?? \"\";\n const exp = Number(m[3]);\n const [intPart, fracPart = \"\"] = mantissa.split(\".\");\n const digits = (intPart ?? \"\") + fracPart;\n const pointPos = (intPart ?? \"\").length + exp;\n let out: string;\n if (pointPos <= 0) {\n out = `0.${\"0\".repeat(-pointPos)}${digits}`.replace(/0+$/, \"\");\n if (out.endsWith(\".\")) out = out.slice(0, -1);\n } else if (pointPos >= digits.length) {\n out = digits + \"0\".repeat(pointPos - digits.length);\n } else {\n out = `${digits.slice(0, pointPos)}.${digits.slice(pointPos)}`.replace(/0+$/, \"\");\n if (out.endsWith(\".\")) out = out.slice(0, -1);\n }\n return sign + out;\n}\n\nfunction needsQuoting(s: string, delimiter: string): boolean {\n if (s.length === 0) return true;\n const first = s.charAt(0);\n const last = s.charAt(s.length - 1);\n if (first === \" \" || first === \"\\t\") return true;\n if (last === \" \" || last === \"\\t\") return true;\n if (s === \"true\" || s === \"false\" || s === \"null\") return true;\n if (first === \"-\" || first === \"+\") return true;\n if (first >= \"0\" && first <= \"9\") return true;\n if (NUMERIC_LIKE_RE.test(s)) return true;\n if (s.includes(delimiter)) return true;\n if (NEEDS_QUOTE_CHARS_RE.test(s)) return true;\n return false;\n}\n\nfunction quoteString(s: string): string {\n // Strip unsupported control chars first (matches Python — no escape\n // sequence exists for them).\n let out = s.replace(UNSUPPORTED_CTRL_RE, \"\");\n out = out.replace(/\\\\/g, \"\\\\\\\\\");\n out = out.replace(/\"/g, '\\\\\"');\n out = out.replace(/\\n/g, \"\\\\n\");\n out = out.replace(/\\r/g, \"\\\\r\");\n out = out.replace(/\\t/g, \"\\\\t\");\n return `\"${out}\"`;\n}\n\nfunction formatKey(key: string): string {\n if (typeof key !== \"string\") {\n throw new TypeError(`TOON keys must be strings; got ${typeof key}`);\n }\n if (SAFE_KEY_RE.test(key)) return key;\n return quoteString(key);\n}\n\nfunction encodeScalar(value: unknown, delimiter: string): string {\n if (value === null || value === undefined) return \"null\";\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (typeof value === \"number\") return formatNumber(value);\n if (typeof value === \"string\") {\n if (needsQuoting(value, delimiter)) return quoteString(value);\n return value;\n }\n // Objects / arrays / etc. — canonical JSON (sorted keys) for parity with\n // Python `_coerce_cell` dict-handling, otherwise String(value).\n if (typeof value === \"object\") {\n if (Array.isArray(value)) {\n return quoteString(JSON.stringify(value));\n }\n // Sorted-key JSON for determinism (matches Python json.dumps sort_keys).\n const sorted = sortedJson(value as Record<string, unknown>);\n return quoteString(sorted);\n }\n return quoteString(String(value));\n}\n\nfunction sortedJson(obj: Record<string, unknown>): string {\n const keys = Object.keys(obj).sort();\n const parts = keys.map((k) => `${JSON.stringify(k)}:${JSON.stringify(obj[k])}`);\n return `{${parts.join(\",\")}}`;\n}\n\n// ---------------------------------------------------------------------------\n// Tabular validation (mirrors Python `_is_tabular`)\n// ---------------------------------------------------------------------------\n\n/**\n * Thrown when {@link toonDumps} receives rows that aren't valid tabular\n * input. Mirrors the Python `encode_tabular` `ValueError` — the encoder\n * MUST refuse non-uniform rows or non-primitive values rather than\n * silently dropping columns or stringifying nested structures.\n *\n * Iter-1 C3: the previous TS encoder used `Object.keys(rows[0])` and\n * encoded every subsequent row through that column list — meaning rows\n * with extra keys had them dropped, rows missing a first-row key got a\n * silent `null`, and rows with object/array values stringified via\n * `JSON.stringify` (also silent). All three are data corruption when the\n * caller didn't realize their rows weren't uniform.\n */\nexport class ToonTabularError extends RangeError {\n override name = \"ToonTabularError\";\n}\n\nfunction isToonPrimitive(v: unknown): boolean {\n // Python `_is_tabular` accepts None / str / int / float / bool. The TS\n // analog: null/undefined (both encode as `null`), string, finite or\n // non-finite number (NaN/Inf encode as `null` via formatNumber),\n // boolean. Anything else (object, array, function, symbol, bigint) is\n // non-tabular.\n if (v === null || v === undefined) return true;\n const t = typeof v;\n return t === \"string\" || t === \"number\" || t === \"boolean\";\n}\n\nfunction assertTabular(rows: ReadonlyArray<Record<string, unknown>>): void {\n if (rows.length === 0) return;\n const first = rows[0] as Record<string, unknown>;\n const expectedKeys = Object.keys(first);\n if (expectedKeys.length === 0) {\n throw new ToonTabularError(\n \"toonDumps requires non-empty rows; first row has no keys (Python parity: encode_tabular rejects empty key set)\",\n );\n }\n const expectedKeySet = new Set(expectedKeys);\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i] as Record<string, unknown>;\n const rowKeys = Object.keys(row);\n // (a) Key-set equality. Python compares `set(item.keys()) != key_set`.\n if (rowKeys.length !== expectedKeySet.size) {\n throw new ToonTabularError(\n `toonDumps requires uniform rows; row ${i} has ${rowKeys.length} key(s) vs row 0's ${expectedKeySet.size}. Python encode_tabular rejects rows whose key sets differ.`,\n );\n }\n for (const k of rowKeys) {\n if (!expectedKeySet.has(k)) {\n throw new ToonTabularError(\n `toonDumps requires uniform rows; row ${i} has key ${JSON.stringify(k)} not present in row 0. Python encode_tabular rejects rows whose key sets differ.`,\n );\n }\n }\n // (b) Value primitivity. Python check: `v is None or isinstance(v, str|int|float|bool)`.\n for (const k of expectedKeys) {\n const v = row[k];\n if (!isToonPrimitive(v)) {\n throw new ToonTabularError(\n `toonDumps requires primitive cell values; row ${i} column ${JSON.stringify(k)} has non-primitive value of type ${typeof v}. Python encode_tabular rejects nested objects/arrays at the cell level.`,\n );\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public encoder\n// ---------------------------------------------------------------------------\n\n/**\n * Encode rows as a TOON v3.0 tabular block.\n *\n * Header is `rows[N]{c1,c2,...}:`; data lines are 2-space indented and\n * comma-separated. Empty rows emits `rows[0]:` (header-only, no columns\n * region; matches Python `_encode_array_field` empty-list path).\n *\n * Note: empty-row encoding differs from `dumps()` in `toon.py` (which\n * carries column names through `rows[0]{...}:`). The TS encoder accepts\n * a `columns` second arg in the empty case for parity with that\n * pandas-aware wrapper.\n *\n * @throws {ToonTabularError} when rows are non-uniform (differing key\n * sets across rows) or when any cell value is non-primitive\n * (object/array/bigint/etc.). Mirrors Python `encode_tabular`'s\n * `ValueError`. Iter-1 C3 fix.\n */\nexport function toonDumps(\n rows: ReadonlyArray<Record<string, unknown>>,\n columns?: ReadonlyArray<string>,\n): string {\n if (rows.length === 0) {\n // Empty-frame: carry column names if provided (matches the Python\n // DataFrame wrapper's `rows[0]{...}:` empty form). Otherwise emit the\n // bare encoder form.\n if (columns !== undefined) {\n const cols = columns.map((c) => formatKey(String(c))).join(\",\");\n return `rows[0]{${cols}}:`;\n }\n return \"rows[0]:\";\n }\n // C3 hard gate: refuse non-uniform rows BEFORE looking at row 0's keys\n // for the column header. Otherwise we'd silently drop extra columns or\n // null-fill missing ones.\n assertTabular(rows);\n const cols = Object.keys(rows[0] as Record<string, unknown>);\n const colHeader = cols.map((c) => formatKey(c)).join(\",\");\n const header = `rows[${rows.length}]{${colHeader}}:`;\n const dataLines = rows.map((r) => {\n const vals = cols.map((c) => encodeScalar((r as Record<string, unknown>)[c], \",\"));\n return ` ${vals.join(\",\")}`;\n });\n return `${header}\\n${dataLines.join(\"\\n\")}`;\n}\n\n// ---------------------------------------------------------------------------\n// Decoder\n// ---------------------------------------------------------------------------\n\nconst HEADER_PREFIX_RE = /^(?<key>[A-Za-z_][A-Za-z0-9_.]*)\\[(?<count>\\d+)\\]/;\n\nfunction parseHeaderLine(line: string): { count: number; cols: string } {\n const prefix = HEADER_PREFIX_RE.exec(line);\n if (prefix == null) {\n throw new RangeError(`TOON payload missing tabular header; got: ${JSON.stringify(line)}`);\n }\n const declared = Number(prefix.groups?.count ?? \"\");\n let i = prefix[0].length;\n const n = line.length;\n // Handle the header-only empty form: `rows[0]:` (no `{cols}` region).\n if (i < n && line[i] === \":\" && declared === 0) {\n const rest = line.slice(i + 1).trim();\n if (rest === \"\") return { count: 0, cols: \"\" };\n throw new RangeError(`TOON header has trailing junk: ${JSON.stringify(line)}`);\n }\n if (i >= n || line[i] !== \"{\") {\n throw new RangeError(`TOON header missing column region: ${JSON.stringify(line)}`);\n }\n i++; // consume `{`\n // Walk to matching `}` honoring quoted strings.\n while (i < n) {\n const ch = line[i];\n if (ch === '\"') {\n let j = i + 1;\n while (j < n) {\n if (line[j] === \"\\\\\" && j + 1 < n) {\n j += 2;\n continue;\n }\n if (line[j] === '\"') break;\n j++;\n }\n if (j >= n) {\n throw new RangeError(\n `TOON header has unterminated quoted column name: ${JSON.stringify(line)}`,\n );\n }\n i = j + 1;\n continue;\n }\n if (ch === \"}\") break;\n i++;\n }\n if (i >= n || line[i] !== \"}\") {\n throw new RangeError(`TOON header missing closing brace: ${JSON.stringify(line)}`);\n }\n const cols = line.slice(prefix[0].length + 1, i);\n const rest = line.slice(i + 1).trim();\n if (rest !== \":\") {\n throw new RangeError(`TOON header missing colon terminator: ${JSON.stringify(line)}`);\n }\n return { count: declared, cols };\n}\n\nfunction splitCsvRow(line: string): string[] {\n const tokens: string[] = [];\n const n = line.length;\n if (n === 0) return tokens;\n let i = 0;\n while (true) {\n // Skip leading whitespace (defensive — encoder never emits padding).\n while (i < n && line[i] === \" \") i++;\n if (i >= n) {\n tokens.push(\"\");\n break;\n }\n if (line[i] === '\"') {\n let j = i + 1;\n while (j < n) {\n if (line[j] === \"\\\\\" && j + 1 < n) {\n j += 2;\n continue;\n }\n if (line[j] === '\"') break;\n j++;\n }\n tokens.push(line.slice(i, j + 1));\n i = j + 1;\n if (i >= n) break;\n if (line[i] === \",\") {\n i++;\n continue;\n }\n while (i < n && line[i] !== \",\") i++;\n if (i >= n) break;\n i++;\n } else {\n let j = i;\n while (j < n && line[j] !== \",\") j++;\n tokens.push(line.slice(i, j));\n i = j;\n if (i >= n) break;\n i++; // consume comma\n }\n }\n return tokens;\n}\n\nfunction decodeQuoted(token: string): string {\n const inner = token.slice(1, -1);\n let out = \"\";\n let i = 0;\n while (i < inner.length) {\n const ch = inner[i];\n if (ch === \"\\\\\" && i + 1 < inner.length) {\n const nxt = inner.charAt(i + 1);\n if (nxt === \"\\\\\") out += \"\\\\\";\n else if (nxt === '\"') out += '\"';\n else if (nxt === \"n\") out += \"\\n\";\n else if (nxt === \"r\") out += \"\\r\";\n else if (nxt === \"t\") out += \"\\t\";\n else out += nxt;\n i += 2;\n continue;\n }\n out += ch;\n i++;\n }\n return out;\n}\n\nfunction unquoteIfQuoted(token: string): string {\n if (token.length >= 2 && token[0] === '\"' && token[token.length - 1] === '\"') {\n return decodeQuoted(token);\n }\n return token;\n}\n\nfunction decodeValue(token: string): unknown {\n if (token.length === 0) return null;\n if (token[0] === '\"' && token[token.length - 1] === '\"' && token.length >= 2) {\n return decodeQuoted(token);\n }\n if (token === \"null\") return null;\n if (token === \"true\") return true;\n if (token === \"false\") return false;\n // Numeric attempt.\n if (NUMERIC_LIKE_RE.test(token)) {\n if (!token.includes(\".\") && !/[eE]/.test(token)) {\n const n = Number.parseInt(token, 10);\n if (!Number.isNaN(n)) return n;\n }\n const f = Number.parseFloat(token);\n if (!Number.isNaN(f)) return f;\n }\n // Bare unquoted string — TOON allows it when no quote-triggers fire.\n return token;\n}\n\n/**\n * Parse a TOON v3.0 tabular block back into rows + columns.\n *\n * Accepts ONLY the tabular shape that `toonDumps` produces; nested objects\n * / expanded lists are out of scope for the formats module.\n */\nexport function toonLoads(data: string): {\n rows: Array<Record<string, unknown>>;\n columns: string[];\n} {\n const lines = data.split(/\\r?\\n/);\n let idx = 0;\n while (idx < lines.length && lines[idx]?.trim() === \"\") idx++;\n if (idx >= lines.length) throw new RangeError(\"empty TOON payload\");\n\n const { count: declared, cols: colsRegion } = parseHeaderLine(lines[idx] ?? \"\");\n const columns = colsRegion === \"\" ? [] : splitCsvRow(colsRegion).map((t) => unquoteIfQuoted(t));\n\n const rawRows: unknown[][] = [];\n for (const raw of lines.slice(idx + 1)) {\n const line = raw.replace(/\\s+$/u, \"\");\n if (line.trim() === \"\") continue;\n const stripped = line.replace(/^ +/u, \"\");\n const tokens = splitCsvRow(stripped);\n if (columns.length > 0 && tokens.length !== columns.length) {\n throw new RangeError(\n `TOON row column count mismatch: expected ${columns.length}, got ${tokens.length}: ${JSON.stringify(stripped)}`,\n );\n }\n rawRows.push(tokens.map((t) => decodeValue(t)));\n }\n if (declared !== rawRows.length) {\n throw new RangeError(`TOON declared row count ${declared} != actual ${rawRows.length}`);\n }\n const rows = rawRows.map((row) => {\n const r: Record<string, unknown> = {};\n for (let i = 0; i < columns.length; i++) {\n r[columns[i] as string] = row[i];\n }\n return r;\n });\n return { rows, columns };\n}\n","// TS-W6 Wave 3 — DataSnapshot + buildSnapshot.\n//\n// Frozen, JSON-safe immutable record of a research() result with provenance.\n// `toDict()` returns the JSON-safe payload (cycles + non-finite numbers\n// handled by `toJsonSafe`); `toToon()` serializes as a TOON tabular block\n// via the existing `toonDumps` encoder.\n\nimport { toJsonSafe } from \"../exceptions/index.js\";\nimport { toonDumps } from \"../formats/toon.js\";\n\nimport type { DataVersion } from \"./data-version.js\";\n\n/** Frozen snapshot wrapper around row data + provenance. */\nexport interface DataSnapshot {\n /** ISO 8601 UTC instant when the snapshot was built (always ends with Z). */\n readonly knowledgeTime: string;\n /** Schema id the rows conform to. */\n readonly schemaId: string;\n /** Source identifier (e.g. `iem.archive`, `awc.live`). Snapshot-scoped. */\n readonly source: string;\n /** Row payload — opaque to this layer. Frozen. */\n readonly rows: ReadonlyArray<Readonly<Record<string, unknown>>>;\n /** Optional reproducibility token. */\n readonly dataVersion: DataVersion | null;\n /** Optional arbitrary metadata. JSON-safe-coerced on `toDict`. */\n readonly metadata: Readonly<Record<string, unknown>>;\n /** JSON-safe dict form. */\n toDict(): Record<string, unknown>;\n /** TOON-v3 tabular form (rows only — provenance lives in the dict form). */\n toToon(): string;\n}\n\nexport interface BuildSnapshotOptions {\n schemaId: string;\n source: string;\n rows: ReadonlyArray<Record<string, unknown>>;\n knowledgeTime?: Date | string;\n dataVersion?: DataVersion | null;\n metadata?: Record<string, unknown>;\n}\n\nfunction normalizeKnowledgeTime(value: Date | string | undefined): string {\n if (value === undefined) {\n return new Date().toISOString();\n }\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new RangeError(\"buildSnapshot: knowledgeTime is an invalid Date\");\n }\n return value.toISOString();\n }\n // String: parse + re-emit so the result is always canonical ISO with Z.\n const ms = Date.parse(value);\n if (Number.isNaN(ms)) {\n throw new RangeError(\n `buildSnapshot: knowledgeTime ${JSON.stringify(value)} is not a parseable ISO 8601 timestamp`,\n );\n }\n return new Date(ms).toISOString();\n}\n\nfunction freezeRows(\n rows: ReadonlyArray<Record<string, unknown>>,\n): ReadonlyArray<Readonly<Record<string, unknown>>> {\n const out: Readonly<Record<string, unknown>>[] = [];\n for (const r of rows) {\n out.push(Object.freeze({ ...r }));\n }\n return Object.freeze(out);\n}\n\n/**\n * Build a frozen DataSnapshot.\n *\n * Throws RangeError on invalid `knowledgeTime`. Row payloads are shallow-\n * cloned and frozen so callers can't mutate snapshot state post-build.\n */\nexport function buildSnapshot(opts: BuildSnapshotOptions): DataSnapshot {\n if (typeof opts.schemaId !== \"string\" || opts.schemaId.length === 0) {\n throw new RangeError(\"buildSnapshot: schemaId must be a non-empty string\");\n }\n if (typeof opts.source !== \"string\" || opts.source.length === 0) {\n throw new RangeError(\"buildSnapshot: source must be a non-empty string\");\n }\n if (!Array.isArray(opts.rows)) {\n throw new RangeError(\"buildSnapshot: rows must be an array\");\n }\n\n // Capture every component into local consts so the returned snapshot's\n // closures never read from the mutable `opts` argument. Codex iter-1\n // P2: a caller that did `const s = buildSnapshot({...}); opts.schemaId = \"x\"`\n // would have seen `s.toDict()` emit the mutated schemaId while\n // `s.schemaId` still showed the original — provenance drift on an API\n // that promises immutability.\n const knowledgeTime = normalizeKnowledgeTime(opts.knowledgeTime);\n const schemaId = opts.schemaId;\n const source = opts.source;\n const rows = freezeRows(opts.rows);\n const dataVersion = opts.dataVersion ?? null;\n const metadata = Object.freeze({ ...(opts.metadata ?? {}) });\n\n const snapshot: DataSnapshot = {\n knowledgeTime,\n schemaId,\n source,\n rows,\n dataVersion,\n metadata,\n toDict(): Record<string, unknown> {\n // `toJsonSafe` handles cycles + non-finite numbers + Dates. DataVersion\n // is a plain dict-shaped object already, so it round-trips cleanly.\n return toJsonSafe({\n knowledge_time: knowledgeTime,\n schema_id: schemaId,\n source,\n rows,\n data_version: dataVersion,\n metadata,\n }) as Record<string, unknown>;\n },\n toToon(): string {\n // TOON encodes a tabular block (rows-only). The header (schema id,\n // source, knowledge time, data version) lives in `toDict()`. Callers\n // that want the full snapshot in TOON wrap toDict output themselves.\n return toonDumps(rows as unknown as Array<Record<string, unknown>>);\n },\n };\n\n return Object.freeze(snapshot);\n}\n","// TS-W6 Wave 4 — DataVersion reproducibility token via Web Crypto SHA-256.\n//\n// Ports Python `mostlyright.discovery.DataVersion.from_components` byte-for-byte:\n// the canonical concatenation is `sdkVersion|sortedSchemaIds|sortedSources|codeSha|dataSha`,\n// SHA-256 hex of the UTF-8 encoded string is the token.\n//\n// Web Crypto API is universal in modern runtimes (browser/Node 16+/Workers/Deno/Bun),\n// so we use `crypto.subtle.digest` without runtime detection.\n\n/** Immutable reproducibility token stamping a single research() call. */\nexport interface DataVersion {\n readonly sdkVersion: string;\n readonly schemaIds: ReadonlyArray<string>;\n readonly sources: ReadonlyArray<string>;\n readonly codeSha: string;\n readonly dataSha: string;\n readonly token: string;\n}\n\nexport interface DataVersionComponents {\n sdkVersion: string;\n schemaIds: ReadonlyArray<string>;\n sources: ReadonlyArray<string>;\n codeSha: string;\n dataSha: string;\n}\n\nconst HEX = \"0123456789abcdef\";\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let out = \"\";\n for (let i = 0; i < bytes.length; i += 1) {\n const b = bytes[i] as number;\n out += HEX[(b >> 4) & 0xf];\n out += HEX[b & 0xf];\n }\n return out;\n}\n\n/**\n * Build a frozen DataVersion from explicit components.\n *\n * Mirrors Python `DataVersion.from_components`: sorts schemaIds + sources\n * INTERNALLY before the canonical hash so input order does not affect the\n * token, but preserves the caller's order on the returned object's\n * `schemaIds` and `sources` arrays. Codex iter-5 P2: prior version sorted\n * the stored arrays alphabetically too, which masked source-priority order\n * (e.g. `awc.live, ghcnh, iem.archive, ...` instead of the canonical\n * `iem.archive, iem.live, awc.live, ghcnh, nws.cli` precedence Python\n * preserves on the tuple).\n */\nexport async function dataVersionFromComponents(\n components: DataVersionComponents,\n): Promise<DataVersion> {\n const sortedSchemaIds = [...components.schemaIds].sort();\n const sortedSources = [...components.sources].sort();\n const canonical = [\n components.sdkVersion,\n sortedSchemaIds.join(\",\"),\n sortedSources.join(\",\"),\n components.codeSha,\n components.dataSha,\n ].join(\"|\");\n\n const encoded = new TextEncoder().encode(canonical);\n const digest = await crypto.subtle.digest(\"SHA-256\", encoded);\n const token = bytesToHex(new Uint8Array(digest));\n\n return Object.freeze({\n sdkVersion: components.sdkVersion,\n schemaIds: Object.freeze([...components.schemaIds]),\n sources: Object.freeze([...components.sources]),\n codeSha: components.codeSha,\n dataSha: components.dataSha,\n token,\n });\n}\n\n/**\n * Build a DataVersion for a research() call. Mirrors Python `DataVersion.for_research`:\n * the codeSha encodes the call signature (`research:STATION:FROM:TO`) and dataSha\n * is supplied by the caller (typically a cache fingerprint).\n *\n * The schema ids + source contract match the v0.1.0 Python SDK exactly so tokens\n * computed in TS match tokens computed in Python for the same inputs.\n */\nexport async function dataVersionForResearch(args: {\n sdkVersion: string;\n station: string;\n fromDate: string;\n toDate: string;\n dataSha: string;\n}): Promise<DataVersion> {\n return dataVersionFromComponents({\n sdkVersion: args.sdkVersion,\n schemaIds: [\"schema.observation.v1\", \"schema.forecast.iem_mos.v1\", \"schema.settlement.cli.v1\"],\n sources: [\"iem.archive\", \"iem.live\", \"awc.live\", \"ghcnh\", \"nws.cli\"],\n codeSha: `research:${args.station}:${args.fromDate}:${args.toDate}`,\n dataSha: args.dataSha,\n });\n}\n","// TS-W6 Wave 5 — describe(schemaId) + featureCatalog() + climateGaps stub.\n//\n// `describe(schemaId)` returns a multi-line human-readable string mined from\n// the embedded JSON-Schema metadata. We bundle schema descriptions as a small\n// JSON map keyed by `$id` (the canonical schema id) — produced by the codegen\n// pipeline if/when it lands; for v0.1.0 we ship a hand-maintained list that\n// matches what the codegen emits.\n//\n// `featureCatalog()` returns the transforms surface as a stable-sorted list\n// of names — matches Python `feature_catalog()` exactly.\n//\n// `climateGaps(station, fromDate, toDate)` throws — TS has no climate cache\n// in v0.1.0 (climate-year parquets are Python-only); the stub matches the\n// Python signature so callers can `try/catch` the platform difference.\n\nimport { MostlyRightError } from \"../exceptions/index.js\";\n\n// ---------------------------------------------------------------------------\n// Schema registry — keep in sync with `packages-ts/core/src/schemas/generated`.\n// ---------------------------------------------------------------------------\n\ninterface SchemaInfo {\n readonly id: string;\n readonly title: string;\n readonly columnCount: number;\n readonly columns: ReadonlyArray<{\n readonly name: string;\n readonly description: string;\n readonly nullable: boolean;\n }>;\n}\n\n// Static manifest of the v0.1.0 schemas. Derived from `schemas/json/*.json`\n// at repo root; baked in here so `@mostlyrightmd/core/discovery` works in\n// browsers (no `node:fs`) without a runtime fetch. The codegen pipeline\n// would replace this hand-maintained list once it covers schema docs.\n//\n// Codex iter-1 P2: prior version left REGISTRY empty until callers ran\n// `registerSchema()`, so `describe('schema.observation.v1')` always threw\n// UNKNOWN_SCHEMA on a fresh import.\nconst BUILT_IN_SCHEMAS: ReadonlyArray<SchemaInfo> = Object.freeze([\n {\n id: \"schema.observation.v1\",\n title: \"schema.observation.v1\",\n columnCount: 20,\n columns: [\n { name: \"dew_point_c\", description: \"units: celsius — bounded\", nullable: true },\n { name: \"event_time\", description: \"observation valid time\", nullable: false },\n {\n name: \"metar_raw\",\n description: \"raw METAR text if source has it; null for AWC JSON (structured-only)\",\n nullable: true,\n },\n {\n name: \"observation_type\",\n description: \"METAR | SPECI; defaults METAR when source can't distinguish (e.g. AWC JSON)\",\n nullable: false,\n },\n {\n name: \"precip_mm_1h\",\n description: \"units: mm — hourly precip (METAR p01i, converted from inches)\",\n nullable: true,\n },\n {\n name: \"sky_base_1_m\",\n description: \"units: meters — first cloud layer base height (converted from feet)\",\n nullable: true,\n },\n { name: \"sky_base_2_m\", description: \"units: meters\", nullable: true },\n { name: \"sky_base_3_m\", description: \"units: meters\", nullable: true },\n { name: \"sky_base_4_m\", description: \"units: meters\", nullable: true },\n { name: \"sky_cover_1\", description: \"first cloud layer cover code\", nullable: true },\n { name: \"sky_cover_2\", description: \"second layer; null if not present\", nullable: true },\n { name: \"sky_cover_3\", description: \"third layer; null if not present\", nullable: true },\n { name: \"sky_cover_4\", description: \"fourth layer; null if not present\", nullable: true },\n {\n name: \"slp_hpa\",\n description:\n \"units: hPa — sea-level pressure (canonical aviation unit, not converted across modes)\",\n nullable: true,\n },\n { name: \"station\", description: \"ICAO/ASOS station ID (e.g. KORD)\", nullable: false },\n {\n name: \"temp_c\",\n description: \"units: celsius — bounded TEMP_MIN_C..TEMP_MAX_C\",\n nullable: true,\n },\n {\n name: \"visibility_m\",\n description: \"units: meters — converted from statute miles\",\n nullable: true,\n },\n {\n name: \"wind_dir_deg\",\n description: \"units: degrees — 0-360, bounded\",\n nullable: true,\n },\n { name: \"wind_gust_ms\", description: \"units: m/s — converted from kt\", nullable: true },\n { name: \"wind_speed_ms\", description: \"units: m/s — converted from kt\", nullable: true },\n ],\n },\n {\n id: \"schema.forecast.iem_mos.v1\",\n title: \"schema.forecast.iem_mos.v1\",\n columnCount: 11,\n columns: [\n { name: \"dew_point_c\", description: \"units: celsius\", nullable: true },\n {\n name: \"forecast_hour\",\n description: \"units: hours — (valid_at - issued_at).total_seconds() / 3600\",\n nullable: false,\n },\n {\n name: \"issued_at\",\n description: \"model run time (from source `runtime` field)\",\n nullable: false,\n },\n { name: \"model\", description: \"e.g. NBE, GFS, LAV, MET\", nullable: false },\n {\n name: \"precip_probability\",\n description: \"units: probability — bounded [0, 1]\",\n nullable: true,\n },\n {\n name: \"sky_cover_pct\",\n description: \"units: percent — bounded [0, 100]\",\n nullable: true,\n },\n { name: \"station\", description: \"\", nullable: false },\n { name: \"temp_c\", description: \"units: celsius\", nullable: true },\n {\n name: \"valid_at\",\n description: \"forecast target time (from source `ftime`)\",\n nullable: false,\n },\n { name: \"wind_dir_deg\", description: \"units: degrees\", nullable: true },\n { name: \"wind_speed_ms\", description: \"units: m/s\", nullable: true },\n ],\n },\n {\n id: \"schema.settlement.cli.v1\",\n title: \"schema.settlement.cli.v1\",\n columnCount: 12,\n columns: [\n {\n name: \"cli_data_quality\",\n description:\n \"NWS CLI data-quality marker (Pitfall 6/16). Allows downstream code to filter or weight settlement rows by issuer quality without re-parsing the product header.\",\n nullable: false,\n },\n {\n name: \"event_time\",\n description: \"00:00 local time on observation_date converted to UTC; for sort/join only\",\n nullable: false,\n },\n {\n name: \"observation_date\",\n description:\n \"local climate day per NWS convention (no timezone applied to the date itself)\",\n nullable: false,\n },\n { name: \"precipitation_in\", description: \"units: inches\", nullable: true },\n {\n name: \"product_release_time\",\n description: \"parsed from CLI product header (_climate.py::_parse_product_timestamp)\",\n nullable: false,\n },\n {\n name: \"report_type\",\n description:\n \"preliminary | final | correction; dedup priority preliminary < final < correction\",\n nullable: false,\n },\n {\n name: \"settlement_finality\",\n description:\n \"provisional | final | superseded. Kalshi NHIGH/NLOW settlement contractually requires 'final'; 'provisional' values are kept for early-look research only.\",\n nullable: false,\n },\n { name: \"snowfall_in\", description: \"units: inches\", nullable: true },\n { name: \"station\", description: \"ICAO/ASOS station ID\", nullable: false },\n {\n name: \"station_tz\",\n description:\n \"IANA timezone for the station (e.g. America/Chicago for KORD). Required for local-climate-day semantics; see §U.\",\n nullable: false,\n },\n {\n name: \"temp_max_F\",\n description:\n \"units: fahrenheit — daily high (uppercase F for consistency with obs imperial mode)\",\n nullable: true,\n },\n { name: \"temp_min_F\", description: \"units: fahrenheit — daily low\", nullable: true },\n ],\n },\n {\n id: \"schema.observation_ledger.v1\",\n title: \"schema.observation_ledger.v1\",\n columnCount: 15,\n columns: [\n { name: \"as_of_time\", description: \"\", nullable: true },\n { name: \"dewpoint_c\", description: \"units: celsius\", nullable: true },\n { name: \"ingestion_id\", description: \"\", nullable: true },\n { name: \"observation_kind\", description: \"\", nullable: true },\n {\n name: \"observation_quality\",\n description:\n \"Lineage row-quality flag per LINEAGE-01; distinct from qc_status enum slot AND distinct from the obs_qc_status bitmask column per QC-05.\",\n nullable: true,\n },\n { name: \"observation_type\", description: \"\", nullable: false },\n { name: \"observed_at\", description: \"\", nullable: false },\n { name: \"parser_name\", description: \"\", nullable: true },\n { name: \"parser_version\", description: \"\", nullable: true },\n { name: \"provenance\", description: \"\", nullable: true },\n { name: \"qc_status\", description: \"\", nullable: true },\n {\n name: \"source\",\n description: \"ncei reserved per D-2.1-09; never written in v0.1.0.\",\n nullable: false,\n },\n { name: \"source_received_at\", description: \"\", nullable: true },\n { name: \"station_code\", description: \"\", nullable: false },\n { name: \"temp_c\", description: \"units: celsius\", nullable: true },\n ],\n },\n {\n id: \"schema.observation_qc.v1\",\n title: \"schema.observation_qc.v1\",\n columnCount: 13,\n columns: [\n { name: \"as_of_time\", description: \"\", nullable: true },\n {\n name: \"detector_metadata\",\n description: \"JSON-serialized detector payload; shape per qc_system.\",\n nullable: true,\n },\n {\n name: \"field\",\n description: \"Observation column the rule evaluated (e.g. temp_c).\",\n nullable: false,\n },\n { name: \"flag\", description: \"\", nullable: false },\n { name: \"ingestion_id\", description: \"\", nullable: true },\n { name: \"observation_kind\", description: \"\", nullable: true },\n { name: \"observed_at\", description: \"\", nullable: false },\n { name: \"parser_name\", description: \"\", nullable: true },\n { name: \"qc_system\", description: \"\", nullable: false },\n { name: \"qc_version\", description: \"\", nullable: false },\n { name: \"rule_id\", description: \"\", nullable: false },\n { name: \"source\", description: \"\", nullable: false },\n { name: \"station_code\", description: \"\", nullable: false },\n ],\n },\n]);\n\nfunction deepFreezeSchema(info: SchemaInfo): SchemaInfo {\n const frozenCols = Object.freeze(info.columns.map((c) => Object.freeze({ ...c })));\n return Object.freeze({ ...info, columns: frozenCols });\n}\n\nconst REGISTRY = new Map<string, SchemaInfo>(\n BUILT_IN_SCHEMAS.map((info) => [info.id, deepFreezeSchema(info)] as const),\n);\n\n/**\n * Register or override a schema for `describe()`. Built-in v0.1.0 schemas\n * are registered at module load (BUILT_IN_SCHEMAS); callers may add custom\n * schemas or override built-ins (e.g. with richer descriptions).\n */\nexport function registerSchema(info: SchemaInfo): void {\n REGISTRY.set(info.id, deepFreezeSchema(info));\n}\n\n/**\n * Return a multi-line description of a registered schema.\n *\n * @throws MostlyRightError if `schemaId` is not registered. The error code is\n * `UNKNOWN_SCHEMA` so callers can distinguish from validation/IO errors.\n */\nexport function describe(schemaId: string): string {\n const info = REGISTRY.get(schemaId);\n if (info === undefined) {\n const known = [...REGISTRY.keys()].sort();\n throw new UnknownSchemaError(\n `Unknown schemaId ${JSON.stringify(schemaId)}; registered: ${\n known.length === 0 ? \"<none>\" : known.join(\", \")\n }`,\n );\n }\n const lines = [`Schema: ${info.id}`, ` Title: ${info.title}`, ` Columns: ${info.columnCount}`];\n for (const col of info.columns) {\n const nullable = col.nullable ? \"?\" : \"\";\n const desc = col.description.length === 0 ? \"\" : ` — ${col.description}`;\n lines.push(` - ${col.name}${nullable}${desc}`);\n }\n return lines.join(\"\\n\");\n}\n\n/** Thrown by `describe` when `schemaId` is not registered. */\nexport class UnknownSchemaError extends MostlyRightError {\n constructor(message: string) {\n super(message);\n this.name = \"UnknownSchemaError\";\n }\n static readonly defaultErrorCode = \"UNKNOWN_SCHEMA\";\n}\n\n// ---------------------------------------------------------------------------\n// featureCatalog — list of transforms surface names.\n// ---------------------------------------------------------------------------\n\nconst FEATURE_NAMES: ReadonlyArray<string> = Object.freeze([\n \"calendarFeatures\",\n \"clipOutliers\",\n \"diff\",\n \"diff2\",\n \"heatIndex\",\n \"lag\",\n \"rolling\",\n \"spread\",\n \"windChill\",\n]);\n\n/**\n * Return the transforms surface as a stable-sorted list.\n *\n * Matches Python `feature_catalog()`'s ordering — alphabetical on the\n * snake_case names, here the camelCase TS names sorted identically.\n */\nexport function featureCatalog(): ReadonlyArray<string> {\n return FEATURE_NAMES;\n}\n\n// ---------------------------------------------------------------------------\n// climateGaps — stub that mirrors Python's signature; v1.x deferral.\n// ---------------------------------------------------------------------------\n//\n// Phase 21 21-11 upgrade: raises DataAvailabilityError (typed exception\n// from Phase 21 21-09) with an architectural-reason hint. The legacy\n// ClimateGapsNotImplementedError class is kept as a back-compat alias\n// (subclass of DataAvailabilityError) so existing `catch` sites keep\n// working. New code should catch DataAvailabilityError and dispatch on\n// the `reason` field instead.\n\nimport { DataAvailabilityError } from \"../exceptions/index.js\";\n\nconst CLIMATE_GAPS_HINT =\n \"climateGaps() is server-only in v1.x. Architectural reason: \" +\n \"GHCNh CSVs are 10+ MB per station-year; browser fetch doesn't \" +\n \"slice (no HTTP Range) and IndexedDB quotas don't fit the working \" +\n \"set. Use the Python SDK (mostlyright.discover.climate_gaps) for \" +\n \"v1.x, or wait for the hosted climate-gap-precompute API \" +\n \"(target: post-v1.x). See https://mostlyright.md/docs/sdk/typescript/climate-gaps \" +\n \"for details.\";\n\n/**\n * Climate-gap scanning — TS v1.x deferral.\n *\n * Phase 21 21-11 messaging upgrade: raises DataAvailabilityError with a\n * multi-paragraph hint explaining the architectural constraint (GHCNh\n * CSVs are 10+ MB per station-year; browser cache layer doesn't scale).\n *\n * @throws DataAvailabilityError with reason=\"model_unavailable\" and a\n * hint pointing at the Python SDK as the supported v1.x workaround.\n */\nexport function climateGaps(_station: string, _fromDate: string, _toDate: string): never {\n throw new ClimateGapsNotImplementedError();\n}\n\n/**\n * Back-compat subclass of DataAvailabilityError raised by `climateGaps`\n * in v1.x. Existing `catch (e instanceof ClimateGapsNotImplementedError)`\n * sites keep working; new code should catch the parent class\n * `DataAvailabilityError` and dispatch on `reason === \"model_unavailable\"`.\n *\n * @deprecated Prefer catching `DataAvailabilityError` directly.\n */\nexport class ClimateGapsNotImplementedError extends DataAvailabilityError {\n static override readonly defaultErrorCode = \"DATA_AVAILABILITY\";\n\n constructor() {\n super({\n reason: \"model_unavailable\",\n source: \"climate-cache-browser\",\n hint: CLIMATE_GAPS_HINT,\n });\n this.name = \"ClimateGapsNotImplementedError\";\n }\n}\n","// Phase 21 21-05 — dailyExtremes(station, from, to, opts?) fetch+rollup wrapper.\n//\n// Matches Python `mostlyright.international.daily_extremes(station, from,\n// to, merge='live_v1')` signature so cross-language code stays symmetric.\n// Composes:\n// 1. STATIONS-registry lookup for the station's IANA timezone.\n// 2. Source fetch per merge mode (IEM ASOS for historical depth +\n// optional AWC for the live window).\n// 3. `internationalDailyExtremes(rows, {stationTz, precision})` rollup.\n// 4. Projection of the existing `DailyExtreme` (TS) shape into the\n// Python-mirroring `DailyExtremeRow` (date/tmin_f/tmax_f/.../n_obs).\n//\n// US ASOS stations get integer-°F precision (Phase 18 invariant); other\n// stations get 0.1-precision values. The integer-°F detection key is the\n// `country === \"US\"` field on the STATIONS registry.\n//\n// Note: GHCNh has CORS issues in browser per `.planning/research/TS-CORS-MATRIX.md`\n// — `merge=\"live_v1\"` in this v1 wrapper only composes IEM + AWC. Adding\n// GHCNh requires the Node-only path; documented for a follow-up.\n\nimport { STATIONS } from \"@mostlyrightmd/core\";\n\nimport {\n type DailyExtreme,\n type InternationalRow,\n internationalDailyExtremes,\n} from \"@mostlyrightmd/core/discovery\";\nimport { fetchAwcMetars } from \"./_fetchers/awc.js\";\nimport { downloadIemAsos } from \"./_fetchers/iem-asos.js\";\nimport { awcToObservation } from \"./_parsers/awc.js\";\nimport { parseIemCsv } from \"./_parsers/iem.js\";\nimport type {\n DailyExtremeRow,\n DailyExtremesMergeMode,\n DailyExtremesOptions,\n} from \"./dailyExtremes.types.js\";\n\nconst LOW_COVERAGE_THRESHOLD = 12;\n\n/**\n * Phase 21 21-05 fix-iter-2 (codex CRITICAL): `fromDate`/`toDate` are\n * station-LOCAL dates, but `row.observed_at` is UTC. Filtering UTC date\n * prefix against local date prefix drops tz-edge observations that fall\n * on adjacent UTC days but the local target day (e.g. for a UTC+9\n * station, `2025-01-06 23:30 local` is `2025-01-06 14:30Z` which has UTC\n * prefix `2025-01-06` — OK — but `2025-01-06 02:00 local` is\n * `2025-01-05 17:00Z` which has UTC prefix `2025-01-05` and would be\n * dropped pre-fix).\n *\n * Mitigation: widen the pre-rollup window by ±1 UTC day so every\n * station-local observation in [fromDate, toDate] is captured regardless\n * of tz offset. The `internationalDailyExtremes()` rollup downstream\n * re-projects observations into station-local days; we then clip the\n * resulting rows back to the caller's [fromDate, toDate] using the\n * station-local `localDate` field on each rollup row.\n */\nfunction addUtcDays(iso: string, days: number): string {\n const [yStr, mStr, dStr] = iso.split(\"-\");\n const dt = new Date(Date.UTC(Number(yStr), Number(mStr) - 1, Number(dStr)));\n dt.setUTCDate(dt.getUTCDate() + days);\n const yyyy = dt.getUTCFullYear();\n const mm = String(dt.getUTCMonth() + 1).padStart(2, \"0\");\n const dd = String(dt.getUTCDate()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}`;\n}\n\ninterface StationLookup {\n tz: string;\n isUs: boolean;\n}\n\nfunction lookupStation(icao: string): StationLookup {\n const upper = icao.toUpperCase();\n for (const s of STATIONS) {\n if (s.icao === upper) {\n return { tz: s.tz, isUs: s.country === \"US\" };\n }\n }\n throw new Error(`dailyExtremes: station \"${icao}\" not in registry — check STATIONS catalog`);\n}\n\nfunction cToF(c: number | null): number | null {\n if (c === null) return null;\n return c * (9 / 5) + 32;\n}\n\nfunction roundHalfUp(value: number, decimals: number): number {\n const m = 10 ** decimals;\n return Math.round(value * m) / m;\n}\n\nasync function fetchIemAsosObservations(\n station: string,\n fromDate: string,\n toDate: string,\n): Promise<InternationalRow[]> {\n // Lazy minimal port of the research() yearly-chunking loop. For the\n // window-bounded wrapper we just span the [from, to] year range.\n const fromYear = Number.parseInt(fromDate.slice(0, 4), 10);\n const toYear = Number.parseInt(toDate.slice(0, 4), 10);\n const out: InternationalRow[] = [];\n for (let year = fromYear; year <= toYear; year++) {\n const chunks = await downloadIemAsos(station, `${year}-01-01`, `${year}-12-31`, {\n reportType: 3,\n politenessMs: 1000,\n });\n for (const chunk of chunks) {\n const parsed = parseIemCsv(chunk.csv, { observationTypeOverride: \"METAR\" });\n for (const row of parsed) {\n // Filter to the requested window. The canonical Observation field\n // is `precip_1hr_inches` (inches); InternationalRow expects mm —\n // convert at the boundary so the per-day precip rollup is in mm.\n // Phase 21 21-05 fix-iter-2 (codex CRITICAL): pre-rollup filter uses\n // the UTC date prefix against the widened-by-±1-day UTC window.\n // The caller's [fromDate, toDate] is station-LOCAL; observations\n // that fall on the adjacent UTC day but the local target day must\n // be kept so internationalDailyExtremes() can bin them into the\n // correct station-local bucket. Output is clipped back to the\n // caller's [fromDate, toDate] post-rollup using `localDate`.\n const obsDate = row.observed_at.slice(0, 10);\n if (obsDate >= fromDate && obsDate <= toDate) {\n const precipInches = row.precip_1hr_inches ?? null;\n out.push({\n observed_at: row.observed_at,\n temp_c: row.temp_c ?? null,\n precip_mm_1h: precipInches !== null ? precipInches * 25.4 : null,\n source: row.source,\n });\n }\n }\n }\n }\n return out;\n}\n\nasync function fetchAwcObservations(\n station: string,\n fromDate: string,\n toDate: string,\n): Promise<InternationalRow[]> {\n // AWC serves the last 168h; the wrapper still queries even if the\n // requested window straddles that horizon — sparse responses are\n // surfaced via the low_coverage gate.\n const raw = await fetchAwcMetars([station]);\n const out: InternationalRow[] = [];\n for (const r of raw) {\n const obs = awcToObservation(r);\n if (obs === null) continue;\n // Phase 21 21-09 fix-iter-1 (ts-architect HIGH): symmetric date-prefix\n // comparison on both bounds (see fetchIemObservations comment above).\n const obsDate = obs.observed_at.slice(0, 10);\n if (obsDate >= fromDate && obsDate <= toDate) {\n const precipInches = obs.precip_1hr_inches ?? null;\n out.push({\n observed_at: obs.observed_at,\n temp_c: obs.temp_c ?? null,\n precip_mm_1h: precipInches !== null ? precipInches * 25.4 : null,\n source: obs.source,\n });\n }\n }\n return out;\n}\n\nasync function fetchForMode(\n station: string,\n fromDate: string,\n toDate: string,\n mode: DailyExtremesMergeMode,\n): Promise<InternationalRow[]> {\n switch (mode) {\n case \"iem_only\":\n return fetchIemAsosObservations(station, fromDate, toDate);\n case \"awc_only\":\n return fetchAwcObservations(station, fromDate, toDate);\n case \"live_v1\": {\n const [iem, awc] = await Promise.all([\n fetchIemAsosObservations(station, fromDate, toDate),\n fetchAwcObservations(station, fromDate, toDate).catch(() => []),\n ]);\n return [...iem, ...awc];\n }\n default: {\n const _exhaustive: never = mode;\n throw new TypeError(`dailyExtremes: unknown merge mode \"${String(_exhaustive)}\"`);\n }\n }\n}\n\nfunction projectRow(station: string, d: DailyExtreme, isUs: boolean): DailyExtremeRow {\n const lowCoverage = d.nObs < LOW_COVERAGE_THRESHOLD;\n const decimals = isUs ? 0 : 1;\n const precipIn =\n d.precipMm !== null && d.precipMm !== undefined ? roundHalfUp(d.precipMm / 25.4, 2) : null;\n if (lowCoverage) {\n return {\n date: d.localDate,\n station,\n tmin_f: null,\n tmax_f: null,\n tmean_f: null,\n precip_in: precipIn,\n low_coverage: true,\n n_obs: d.nObs,\n };\n }\n return {\n date: d.localDate,\n station,\n tmin_f: d.tempMinF !== null ? roundHalfUp(d.tempMinF, decimals) : null,\n tmax_f: d.tempMaxF !== null ? roundHalfUp(d.tempMaxF, decimals) : null,\n tmean_f: d.tempMeanC !== null ? roundHalfUp(cToF(d.tempMeanC) as number, decimals) : null,\n precip_in: precipIn,\n low_coverage: false,\n n_obs: d.nObs,\n };\n}\n\n/**\n * Compute per-day tmin/tmax/tmean/precip for a station's window.\n *\n * Matches Python `mostlyright.international.daily_extremes` signature.\n * Day-bucketing uses the station's IANA local tz from the STATIONS\n * registry; US ASOS stations get integer-°F precision (Phase 18\n * invariant); other stations get 0.1-precision values.\n *\n * @param station ICAO code (e.g. \"KNYC\")\n * @param fromDate ISO date `YYYY-MM-DD` (inclusive, station-local)\n * @param toDate ISO date `YYYY-MM-DD` (inclusive, station-local)\n * @param opts optional merge mode (default `\"live_v1\"`)\n * @returns array of DailyExtremeRow, one per station-local day\n * @throws Error if station is not in the STATIONS registry\n */\nexport async function dailyExtremes(\n station: string,\n fromDate: string,\n toDate: string,\n opts: DailyExtremesOptions = {},\n): Promise<ReadonlyArray<DailyExtremeRow>> {\n const { tz, isUs } = lookupStation(station);\n const merge = opts.merge ?? \"live_v1\";\n\n // Phase 21 21-05 fix-iter-2 (codex CRITICAL): widen the pre-rollup UTC\n // window by ±1 day so station-local-day observations at tz edges are\n // not silently dropped (e.g. UTC+14 stations have local 23:00 falling\n // on the previous UTC day). The rollup re-projects into the\n // station-local IANA day; we then clip the rollup output back to the\n // caller's [fromDate, toDate] via the station-local `localDate` field\n // so the public surface still returns exactly the requested days.\n const fetchFrom = addUtcDays(fromDate, -1);\n const fetchTo = addUtcDays(toDate, 1);\n const rows = await fetchForMode(station, fetchFrom, fetchTo, merge);\n\n const extremes = internationalDailyExtremes(rows, {\n stationTz: tz,\n precision: isUs ? 1 : 0,\n });\n\n return extremes\n .filter((d) => d.localDate >= fromDate && d.localDate <= toDate)\n .map((d) => projectRow(station.toUpperCase(), d, isUs));\n}\n\nexport type {\n DailyExtremeRow,\n DailyExtremesMergeMode,\n DailyExtremesOptions,\n} from \"./dailyExtremes.types.js\";\n","// Phase 21 21-04 — obs(station, from, to, opts?) public function.\n//\n// Port of Python `tw.weather.obs(station, start, end, source=None,\n// strategy='auto')` — the Phase 7 ingest-planner smart-router. Picks\n// between three fetch strategies based on window size + caller intent:\n//\n// - `exact_window` — one-off ≤7-day window; fetches the exact bytes\n// the caller asked for, no year-padding. Cheapest path.\n// - `warm_cache` — year-aligned cache layout; the same orchestration\n// research() uses. Best for callers that will hit overlapping\n// windows.\n// - `hosted` — precomputed-API seam; raises DataAvailabilityError\n// until v0.2.x ships the hosted ingest endpoint (D-06).\n// - `auto` (default) — routes to `exact_window` when (toDate-fromDate)\n// ≤ 7 days, else `warm_cache`. Threshold matches Python's heuristic.\n//\n// Per D-06, the TS adaptations differ from Python in storage (IndexedDB\n// browser / Node FS server instead of parquet) but match Python in\n// strategy semantics + raised exceptions.\n\nimport { DataAvailabilityError } from \"@mostlyrightmd/core\";\n\nimport { fetchAwcMetars } from \"./_fetchers/awc.js\";\nimport { downloadIemAsos } from \"./_fetchers/iem-asos.js\";\nimport { awcToObservation } from \"./_parsers/awc.js\";\nimport { parseIemCsv } from \"./_parsers/iem.js\";\nimport type { ObsOptions, ObsRow, ObsStrategy } from \"./obs.types.js\";\n\n/** Day count between two ISO YYYY-MM-DD strings (inclusive). */\nfunction daysBetween(fromDate: string, toDate: string): number {\n const from = Date.UTC(\n Number.parseInt(fromDate.slice(0, 4), 10),\n Number.parseInt(fromDate.slice(5, 7), 10) - 1,\n Number.parseInt(fromDate.slice(8, 10), 10),\n );\n const to = Date.UTC(\n Number.parseInt(toDate.slice(0, 4), 10),\n Number.parseInt(toDate.slice(5, 7), 10) - 1,\n Number.parseInt(toDate.slice(8, 10), 10),\n );\n return Math.round((to - from) / (24 * 60 * 60 * 1000)) + 1;\n}\n\n/** Resolve `auto` to the concrete strategy chosen by the smart-router. */\nexport function resolveAutoStrategy(fromDate: string, toDate: string): ObsStrategy {\n // Python heuristic: 7-day or smaller windows route to exact_window\n // (one-off cold fetch ≤ 2 MB estimated payload); larger windows route\n // to warm_cache (year-aligned cache layout, reusable across calls).\n return daysBetween(fromDate, toDate) <= 7 ? \"exact_window\" : \"warm_cache\";\n}\n\n// Field names mirror the canonical Observation interface from\n// `_parsers/awc.ts`. ObsRow uses the same snake_case shape so cross-\n// language code stays symmetric with Python. Precipitation is stored in\n// inches at the source (`precip_1hr_inches`); we surface it as\n// `precip_mm_1h` per the Python ObsRow contract, so the conversion\n// (inches × 25.4 = mm) happens at the projection boundary.\n\nfunction inchesToMm(inches: number | null | undefined): number | null {\n if (inches === null || inches === undefined) return null;\n return inches * 25.4;\n}\n\nfunction mbToInhg(mb: number | null | undefined): number | null {\n if (mb === null || mb === undefined) return null;\n return mb * 0.029529983071445;\n}\n\nfunction fromObservation(o: NonNullable<ReturnType<typeof awcToObservation>>): ObsRow {\n const tempC = o.temp_c ?? null;\n const dewC = o.dewpoint_c ?? null;\n return {\n station: o.station_code,\n observed_at: o.observed_at,\n source: o.source,\n temp_c: tempC,\n temp_f: tempC !== null ? tempC * (9 / 5) + 32 : null,\n dewpoint_c: dewC,\n dewpoint_f: dewC !== null ? dewC * (9 / 5) + 32 : null,\n wind_speed_kts: o.wind_speed_kt ?? null,\n wind_direction_deg: o.wind_dir_degrees ?? null,\n pressure_inhg: mbToInhg(o.sea_level_pressure_mb),\n precip_mm_1h: inchesToMm(o.precip_1hr_inches),\n raw_metar: o.raw_metar ?? null,\n };\n}\n\n/**\n * IEM fetch helper. Phase 21 21-04 fix-iter-3 (codex HIGH): the byte-range\n * URL the upstream IEM ASOS endpoint accepts is honored by `downloadIemAsos`\n * — passing date-bounded `start`/`end` produces a date-bounded payload.\n *\n * - `exact_window` strategy: pass the caller's [fromDate, toDate] verbatim.\n * A 1-day call pulls ~1 day of bytes, not a whole year. Matches Python\n * `obs(strategy=\"exact_window\")` semantics.\n * - `warm_cache` strategy: pad to year-boundaries to maximize cache reuse\n * for callers that hit overlapping windows. Matches Python\n * `obs(strategy=\"warm_cache\")` semantics.\n *\n * Post-fetch trim still runs in both cases — the upstream may include\n * boundary observations slightly outside [fromDate, toDate] depending on\n * METAR reporting cadence.\n */\nasync function fetchIemForWindow(\n station: string,\n fromDate: string,\n toDate: string,\n resolvedStrategy: Exclude<ObsStrategy, \"auto\" | \"hosted\">,\n): Promise<ObsRow[]> {\n const out: ObsRow[] = [];\n\n if (resolvedStrategy === \"exact_window\") {\n // GH #57: `exactStart: true` opts out of `downloadIemAsos`'s default\n // Jan-1 widening + yearly chunking — issues ONE byte-bounded request\n // for `[fromDate, toDate+1day exclusive]`. Without this flag a 1-day\n // call pulled ~734 KB (whole calendar year) instead of ~9.8 KB.\n const chunks = await downloadIemAsos(station, fromDate, toDate, {\n reportType: 3,\n politenessMs: 1000,\n exactStart: true,\n });\n for (const chunk of chunks) {\n const rows = parseIemCsv(chunk.csv, { observationTypeOverride: \"METAR\" });\n for (const r of rows) {\n const d = r.observed_at.slice(0, 10);\n if (d >= fromDate && d <= toDate) out.push(fromObservation(r));\n }\n }\n return out;\n }\n\n // warm_cache: span the [fromYear, toYear] calendar-year range so future\n // calls hitting overlapping windows can reuse cached year-chunks.\n const fromYear = Number.parseInt(fromDate.slice(0, 4), 10);\n const toYear = Number.parseInt(toDate.slice(0, 4), 10);\n for (let year = fromYear; year <= toYear; year++) {\n const chunks = await downloadIemAsos(station, `${year}-01-01`, `${year}-12-31`, {\n reportType: 3,\n politenessMs: 1000,\n });\n for (const chunk of chunks) {\n const rows = parseIemCsv(chunk.csv, { observationTypeOverride: \"METAR\" });\n for (const r of rows) {\n const d = r.observed_at.slice(0, 10);\n if (d >= fromDate && d <= toDate) out.push(fromObservation(r));\n }\n }\n }\n return out;\n}\n\nasync function fetchAwcForWindow(\n station: string,\n fromDate: string,\n toDate: string,\n): Promise<ObsRow[]> {\n const raw = await fetchAwcMetars([station]);\n const out: ObsRow[] = [];\n for (const r of raw) {\n const obs = awcToObservation(r);\n if (obs === null) continue;\n const d = obs.observed_at.slice(0, 10);\n if (d >= fromDate && d <= toDate) out.push(fromObservation(obs));\n }\n return out;\n}\n\nasync function fetchByStrategy(\n station: string,\n fromDate: string,\n toDate: string,\n resolvedStrategy: Exclude<ObsStrategy, \"auto\" | \"hosted\">,\n source: ObsOptions[\"source\"],\n): Promise<ObsRow[]> {\n // Phase 21 21-04 fix-iter-3 (codex HIGH): `resolvedStrategy` now drives\n // the IEM fetch shape. `exact_window` issues a date-bounded request so\n // small calls pull small payloads (matches Python's Phase 7 ingest-\n // planner semantics); `warm_cache` pads to year-boundaries so\n // overlapping callers reuse cached chunks. AWC is always the live\n // 168-hour endpoint regardless of strategy — the request shape doesn't\n // change with strategy in either Python or TS.\n const wantsIem = source === null || source === undefined || source === \"iem\";\n const wantsAwc = source === null || source === undefined || source === \"awc\";\n\n const tasks: Promise<ObsRow[]>[] = [];\n if (wantsIem) tasks.push(fetchIemForWindow(station, fromDate, toDate, resolvedStrategy));\n if (wantsAwc) tasks.push(fetchAwcForWindow(station, fromDate, toDate).catch(() => []));\n\n const results = await Promise.all(tasks);\n return results.flat();\n}\n\n/**\n * Fetch raw observations for a station's window.\n *\n * Matches Python `tw.weather.obs(station, start, end, source=None,\n * strategy='auto')` signature. The strategy enum selects between the\n * smart-router's three concrete fetch paths.\n *\n * @param station ICAO code (e.g. \"KNYC\")\n * @param fromDate ISO date `YYYY-MM-DD` (inclusive)\n * @param toDate ISO date `YYYY-MM-DD` (inclusive)\n * @param opts optional source filter + strategy mode\n * @returns flat array of ObsRow (each row is a single METAR observation)\n * @throws DataAvailabilityError when strategy='hosted' (v0.2.x deferral)\n * @throws TypeError when strategy is not in the accepted enum\n */\nexport async function obs(\n station: string,\n fromDate: string,\n toDate: string,\n opts: ObsOptions = {},\n): Promise<ReadonlyArray<ObsRow>> {\n const strategy = opts.strategy ?? \"auto\";\n const source = opts.source ?? null;\n\n // Phase 21 21-09 fix-iter-1: reject source values that have no TS fetcher\n // wiring loudly rather than silently returning [] (codex+ts-architect\n // CRITICAL). `ghcnh` is a documented Python filter but the GHCNh fetcher\n // is not wired through `fetchByStrategy` in TS yet — surface the gap as\n // DataAvailabilityError(reason=\"model_unavailable\") so consumers see the\n // missing wiring instead of empty rows.\n if (source === \"ghcnh\") {\n throw new DataAvailabilityError({\n reason: \"model_unavailable\",\n source: \"obs.ghcnh\",\n hint:\n \"source='ghcnh' is a valid Python `obs()` filter but the GHCNh \" +\n \"fetcher path is not yet wired in the TypeScript SDK. Use \" +\n \"source='iem' or source='awc' (or omit `source` for merged) until \" +\n \"the TS GHCNh fetcher ships.\",\n });\n }\n\n if (strategy === \"hosted\") {\n throw new DataAvailabilityError({\n reason: \"model_unavailable\",\n source: \"obs-hosted-stub\",\n hint:\n \"hosted ingest API ships in v0.2.x — use strategy='exact_window' \" +\n \"or 'warm_cache' for v1.x. See \" +\n \"https://mostlyright.md/docs/sdk/typescript/ingest-strategies\",\n });\n }\n\n let resolved: Exclude<ObsStrategy, \"auto\" | \"hosted\">;\n if (strategy === \"auto\") {\n resolved = resolveAutoStrategy(fromDate, toDate) as Exclude<ObsStrategy, \"auto\" | \"hosted\">;\n } else if (strategy === \"exact_window\" || strategy === \"warm_cache\") {\n resolved = strategy;\n } else {\n throw new TypeError(\n `obs: unknown strategy \"${String(strategy)}\" — expected one of: auto, exact_window, warm_cache, hosted`,\n );\n }\n\n return fetchByStrategy(station, fromDate, toDate, resolved, source);\n}\n\nexport type {\n ObsOptions,\n ObsRow,\n ObsSourceFilter,\n ObsStrategy,\n} from \"./obs.types.js\";\n","// @mostlyrightmd/markets — placeholder scaffold for TS-W0 Wave 1.\n// Real implementation (Kalshi NHIGH/NLOW resolvers, Polymarket discover/settle) lands in TS-W1+.\n\n/**\n * Placeholder version string from the TS-W0 Wave 1 scaffold. The\n * authoritative package version lives in `package.json#version`\n * (currently `0.1.0-rc.7`); this constant has not been bumped.\n */\nexport const version = \"0.0.0\";\n\n/**\n * Smoke-test export from the TS-W0 Wave 1 scaffold. Returns the literal\n * string `\"hello @mostlyrightmd/markets\"`. Retained so the published\n * package has at least one importable runtime export until the scaffold\n * is removed in a later phase.\n */\nexport function helloMarkets(): string {\n return \"hello @mostlyrightmd/markets\";\n}\n\n// Re-export generated data (NOT auto-generated; hand-maintained barrel).\n// The targets ARE auto-generated by @mostlyrightmd/codegen — see packages-ts/codegen.\nexport * from \"./data/generated/index.js\";\n\n// Kalshi NHIGH/NLOW resolvers (TS-W1 Wave 2).\nexport * from \"./resolvers/index.js\";\n\n// Kalshi settlement helper (TS-W5 Wave 5) — higher-level dispatch by\n// prefix (KHIGH* vs KLOW*).\nexport { kalshiSettlementFor } from \"./kalshi-settlement.js\";\nexport type { KalshiSettlement } from \"./kalshi-settlement.js\";\n\n// NOTE: Polymarket discover/settle (TS-W5 Waves 1-4) live at the\n// `./polymarket` subpath, NOT the root barrel — keeps the IIFE bundle\n// lean (Polymarket is server-side by design; CORS-blocked from\n// browsers per .planning/research/TS-CORS-MATRIX.md). Import with:\n//\n// import { polymarketDiscover, polymarketSettle } from \"@mostlyrightmd/markets/polymarket\";\n","// AUTO-GENERATED by @mostlyrightmd/codegen from schemas/kalshi-settlement-stations.json.\n// DO NOT EDIT — regenerate with: pnpm codegen\n// Last manifest SHA recorded in schemas/EXPORT_MANIFEST.json\n\nexport interface KalshiStation {\n station: string;\n citation: string;\n}\n\nexport const KALSHI_SETTLEMENT_STATIONS: Readonly<Record<string, KalshiStation>> = {\n ATL: {\n citation: \"https://kalshi.com/markets/khighatl (Atlanta Hartsfield-Jackson)\",\n station: \"KATL\",\n },\n AUS: {\n citation: \"https://kalshi.com/markets/khighaus (Austin-Bergstrom; the only Austin station Kalshi cites)\",\n station: \"KAUS\",\n },\n BNA: {\n citation: \"https://kalshi.com/markets/khighbna (Nashville International)\",\n station: \"KBNA\",\n },\n BOS: {\n citation: \"https://kalshi.com/markets/khighbos (Boston Logan)\",\n station: \"KBOS\",\n },\n CHI: {\n citation: \"https://kalshi.com/markets/khighchi (Midway, NOT ORD)\",\n station: \"KMDW\",\n },\n CVG: {\n citation: \"https://kalshi.com/markets/khighcvg (Cincinnati/Northern Kentucky International)\",\n station: \"KCVG\",\n },\n DAL: {\n citation: \"https://kalshi.com/markets/khighdal (DFW, NOT Love Field)\",\n station: \"KDFW\",\n },\n DCA: {\n citation: \"https://kalshi.com/markets/khighdca (Reagan National, NOT Dulles or BWI)\",\n station: \"KDCA\",\n },\n DEN: {\n citation: \"https://kalshi.com/markets/khighden (Denver International)\",\n station: \"KDEN\",\n },\n DTW: {\n citation: \"https://kalshi.com/markets/khighdtw (Detroit Metropolitan)\",\n station: \"KDTW\",\n },\n HOU: {\n citation: \"https://kalshi.com/markets/khighhou (Intercontinental, NOT Hobby; Kalshi cites IAH)\",\n station: \"KIAH\",\n },\n LAX: {\n citation: \"https://kalshi.com/markets/khighlax (LAX international)\",\n station: \"KLAX\",\n },\n MIA: {\n citation: \"https://kalshi.com/markets/khighmia (Miami International)\",\n station: \"KMIA\",\n },\n MSP: {\n citation: \"https://kalshi.com/markets/khighmsp (Minneapolis-St. Paul International)\",\n station: \"KMSP\",\n },\n NYC: {\n citation: \"https://kalshi.com/markets/khighny (Central Park, NOT LGA/JFK)\",\n station: \"KNYC\",\n },\n PHL: {\n citation: \"https://kalshi.com/markets/khighphl (Philadelphia International)\",\n station: \"KPHL\",\n },\n PHX: {\n citation: \"https://kalshi.com/markets/khighphx (Sky Harbor International)\",\n station: \"KPHX\",\n },\n SEA: {\n citation: \"https://kalshi.com/markets/khighsea (SeaTac, NOT BFI)\",\n station: \"KSEA\",\n },\n SFO: {\n citation: \"https://kalshi.com/markets/khighsfo (San Francisco International, NOT OAK)\",\n station: \"KSFO\",\n },\n SLC: {\n citation: \"https://kalshi.com/markets/khighslc (Salt Lake City International)\",\n station: \"KSLC\",\n },\n TLV: {\n citation: \"https://kalshi.com/markets/kxhightlv (Harry Reid/McCarran; settles vs NWS CLILAS)\",\n station: \"KLAS\",\n },\n} as const;\n\nexport const KNOWN_WRONG_STATIONS: ReadonlySet<string> = new Set<string>([\n \"KBWI\",\n \"KDAL\",\n \"KEWR\",\n \"KHOU\",\n \"KIAD\",\n \"KJFK\",\n \"KLGA\",\n \"KOAK\",\n \"KORD\",\n]);\n","// AUTO-GENERATED by @mostlyrightmd/codegen from schemas/polymarket-city-stations.json.\n// DO NOT EDIT — regenerate with: pnpm codegen\n// Last manifest SHA recorded in schemas/EXPORT_MANIFEST.json\n\nexport interface PolymarketCityStation {\n default: string;\n high?: string;\n low?: string;\n [measure: string]: string | undefined;\n}\n\nexport const POLYMARKET_CITY_STATIONS: Readonly<Record<string, PolymarketCityStation>> = {\n amsterdam: {\n default: \"EHAM\",\n },\n ankara: {\n default: \"LTAC\",\n },\n atlanta: {\n default: \"KATL\",\n },\n austin: {\n default: \"KAUS\",\n },\n beijing: {\n default: \"ZBAA\",\n },\n buenos_aires: {\n default: \"SAEZ\",\n },\n busan: {\n default: \"RKPK\",\n },\n cape_town: {\n default: \"FACT\",\n },\n chengdu: {\n default: \"ZUUU\",\n },\n chicago: {\n default: \"KORD\",\n high: \"KORD\",\n low: \"KORD\",\n },\n chongqing: {\n default: \"ZUCK\",\n },\n dallas: {\n default: \"KDAL\",\n },\n denver: {\n default: \"KBKF\",\n },\n guangzhou: {\n default: \"ZGGG\",\n },\n helsinki: {\n default: \"EFHK\",\n },\n hong_kong: {\n default: \"HKO\",\n high: \"HKO\",\n low: \"HKO\",\n },\n houston: {\n default: \"KHOU\",\n },\n istanbul: {\n default: \"LTFM\",\n },\n jeddah: {\n default: \"OEJN\",\n },\n jinan: {\n default: \"ZSJN\",\n },\n karachi: {\n default: \"OPKC\",\n },\n kuala_lumpur: {\n default: \"WMKK\",\n },\n london: {\n default: \"EGLC\",\n },\n los_angeles: {\n default: \"KLAX\",\n high: \"KLAX\",\n low: \"KLAX\",\n },\n lucknow: {\n default: \"VILK\",\n },\n madrid: {\n default: \"LEMD\",\n },\n manila: {\n default: \"RPLL\",\n },\n mexico_city: {\n default: \"MMMX\",\n },\n miami: {\n default: \"KMIA\",\n },\n milan: {\n default: \"LIMC\",\n },\n moscow: {\n default: \"UUWW\",\n },\n munich: {\n default: \"EDDM\",\n },\n nyc: {\n default: \"KLGA\",\n high: \"KLGA\",\n low: \"KLGA\",\n },\n panama_city: {\n default: \"MPMG\",\n },\n paris: {\n default: \"LFPB\",\n },\n qingdao: {\n default: \"ZSQD\",\n },\n san_francisco: {\n default: \"KSFO\",\n },\n sao_paulo: {\n default: \"SBGR\",\n },\n seattle: {\n default: \"KSEA\",\n },\n seoul: {\n default: \"RKSI\",\n },\n shanghai: {\n default: \"ZSPD\",\n },\n shenzhen: {\n default: \"ZGSZ\",\n },\n singapore: {\n default: \"WSSS\",\n },\n taipei: {\n default: \"RCSS\",\n },\n tel_aviv: {\n default: \"LLBG\",\n },\n tokyo: {\n default: \"RJTT\",\n high: \"RJTT\",\n low: \"RJTT\",\n },\n toronto: {\n default: \"CYYZ\",\n },\n warsaw: {\n default: \"EPWA\",\n },\n wellington: {\n default: \"NZWN\",\n },\n wuhan: {\n default: \"ZHHH\",\n },\n zhengzhou: {\n default: \"ZHCC\",\n },\n} as const;\n","// Kalshi NHIGH (daily HIGH temperature) contract resolver.\n//\n// NHIGH markets resolve against the NWS CLI `max_temp_f` value for a\n// specific station on a specific date. `kalshiNhighResolve` is the\n// deterministic mapping from a Kalshi market identifier to the\n// (settlement_source, settlement_station) tuple downstream code uses\n// to pull the right settlement row from the CLI catalog.\n//\n// Ported byte-faithful from packages/markets/src/mostlyright/markets/catalog/kalshi_nhigh.py.\n\nimport { KALSHI_SETTLEMENT_STATIONS } from \"../data/generated/kalshi-stations.js\";\n\nexport interface NHighResolution {\n readonly settlementSource: \"cli.archive\";\n readonly settlementStation: string; // 4-letter ICAO\n readonly cityTicker: string;\n readonly contractDate: string; // YYYY-MM-DD\n}\n\n/**\n * Custom error type for contract-id parsing / validation failures.\n *\n * Mirrors the Python `ValueError`/`TypeError` distinction: in TS we use a\n * named subclass so callers can `instanceof`-check rather than parse\n * error messages.\n */\nexport class ContractIdError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ContractIdError\";\n }\n}\n\n/**\n * Coerce a Date or YYYY-MM-DD string into an ISO date-only string.\n *\n * Rejects Date instances whose UTC hours/minutes/seconds/ms are non-zero —\n * those would silently corrupt downstream settlement-date matching\n * (date-equality is strict).\n *\n * Mirrors the Python `isinstance(settlement_date, datetime)` guard in\n * `kalshi_nhigh.resolve`.\n */\nfunction coerceContractDate(value: Date | string): string {\n if (typeof value === \"string\") {\n // YYYY-MM-DD strict format check.\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n throw new ContractIdError(\n `settlementDate string must be YYYY-MM-DD; got ${JSON.stringify(value)}`,\n );\n }\n // Validate it parses to a real calendar date.\n const parsed = new Date(`${value}T00:00:00Z`);\n if (Number.isNaN(parsed.getTime())) {\n throw new ContractIdError(\n `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`,\n );\n }\n // Round-trip check — guards against e.g. \"2025-02-30\" silently\n // rolling forward.\n const iso = parsed.toISOString().slice(0, 10);\n if (iso !== value) {\n throw new ContractIdError(\n `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`,\n );\n }\n return value;\n }\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new ContractIdError(\"settlementDate is an invalid Date instance\");\n }\n // Reject any Date with a non-zero UTC time component — mirrors\n // Python's `isinstance(settlement_date, datetime)` guard. A Date\n // carries a time component which would break downstream\n // date-equality matching.\n if (\n value.getUTCHours() !== 0 ||\n value.getUTCMinutes() !== 0 ||\n value.getUTCSeconds() !== 0 ||\n value.getUTCMilliseconds() !== 0\n ) {\n throw new ContractIdError(\n `settlementDate must be a UTC date-only Date (H/M/S/ms = 0); got ${value.toISOString()}. Use new Date('YYYY-MM-DDT00:00:00Z') or pass a 'YYYY-MM-DD' string.`,\n );\n }\n return value.toISOString().slice(0, 10);\n }\n throw new ContractIdError(\"settlementDate must be a Date instance or YYYY-MM-DD string\");\n}\n\n/**\n * Resolve a Kalshi NHIGH contract to its settlement source + station.\n *\n * The contract id format is `KHIGH<CITY>` (case-insensitive), where\n * `<CITY>` is a city ticker present in\n * {@link KALSHI_SETTLEMENT_STATIONS}.\n *\n * @param contractId Kalshi market identifier. Case-insensitive.\n * @param settlementDate Calendar date the market settles for. Either a\n * UTC date-only `Date` (H/M/S/ms == 0) or a `YYYY-MM-DD` string.\n * @returns A frozen {@link NHighResolution}.\n * @throws {ContractIdError} The contract id doesn't follow\n * `KHIGH<CITY>`, the city is unknown, or the settlement date is\n * invalid.\n */\nexport function kalshiNhighResolve(\n contractId: string,\n settlementDate: Date | string,\n): NHighResolution {\n if (typeof contractId !== \"string\") {\n throw new ContractIdError(`contractId must be a string; got ${typeof contractId}`);\n }\n\n const contractDate = coerceContractDate(settlementDate);\n\n const cid = contractId.toUpperCase();\n if (!cid.startsWith(\"KHIGH\") || cid.length <= 5) {\n throw new ContractIdError(\n `NHIGH contractId must follow 'KHIGH<CITY>' format; got ${JSON.stringify(contractId)}`,\n );\n }\n const cityTicker = cid.slice(5);\n const station = KALSHI_SETTLEMENT_STATIONS[cityTicker];\n if (station === undefined) {\n const known = Object.keys(KALSHI_SETTLEMENT_STATIONS).sort();\n throw new ContractIdError(\n `unknown city ${JSON.stringify(cityTicker)} (known: ${known.join(\", \")})`,\n );\n }\n\n return Object.freeze({\n settlementSource: \"cli.archive\" as const,\n settlementStation: station.station,\n cityTicker,\n contractDate,\n });\n}\n","// Kalshi NLOW (daily LOW temperature) contract resolver.\n//\n// Mirror of kalshi-nhigh — same station whitelist + same source\n// (cli.archive); only the metric differs. NLOW markets resolve against\n// the NWS CLI `min_temp_f` value for a specific station on a specific\n// date.\n//\n// Ported byte-faithful from packages/markets/src/mostlyright/markets/catalog/kalshi_nlow.py.\n\nimport { KALSHI_SETTLEMENT_STATIONS } from \"../data/generated/kalshi-stations.js\";\nimport { ContractIdError } from \"./kalshi-nhigh.js\";\n\nexport interface NLowResolution {\n readonly settlementSource: \"cli.archive\";\n readonly settlementStation: string; // 4-letter ICAO\n readonly cityTicker: string;\n readonly contractDate: string; // YYYY-MM-DD\n}\n\n/**\n * Coerce a Date or YYYY-MM-DD string into an ISO date-only string.\n *\n * Rejects Date instances whose UTC hours/minutes/seconds/ms are non-zero —\n * those would silently corrupt downstream settlement-date matching.\n *\n * Mirrors the Python `isinstance(settlement_date, datetime)` guard.\n *\n * Note: duplicated here (rather than imported) so kalshi-nlow stays a\n * standalone file matching the Python module split. The two functions\n * are byte-identical by design.\n */\nfunction coerceContractDate(value: Date | string): string {\n if (typeof value === \"string\") {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n throw new ContractIdError(\n `settlementDate string must be YYYY-MM-DD; got ${JSON.stringify(value)}`,\n );\n }\n const parsed = new Date(`${value}T00:00:00Z`);\n if (Number.isNaN(parsed.getTime())) {\n throw new ContractIdError(\n `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`,\n );\n }\n const iso = parsed.toISOString().slice(0, 10);\n if (iso !== value) {\n throw new ContractIdError(\n `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`,\n );\n }\n return value;\n }\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new ContractIdError(\"settlementDate is an invalid Date instance\");\n }\n if (\n value.getUTCHours() !== 0 ||\n value.getUTCMinutes() !== 0 ||\n value.getUTCSeconds() !== 0 ||\n value.getUTCMilliseconds() !== 0\n ) {\n throw new ContractIdError(\n `settlementDate must be a UTC date-only Date (H/M/S/ms = 0); got ${value.toISOString()}. Use new Date('YYYY-MM-DDT00:00:00Z') or pass a 'YYYY-MM-DD' string.`,\n );\n }\n return value.toISOString().slice(0, 10);\n }\n throw new ContractIdError(\"settlementDate must be a Date instance or YYYY-MM-DD string\");\n}\n\n/**\n * Resolve a Kalshi NLOW contract to its settlement source + station.\n *\n * The contract id format is `KLOW<CITY>` (case-insensitive), where\n * `<CITY>` is a city ticker present in\n * {@link KALSHI_SETTLEMENT_STATIONS}.\n *\n * @param contractId Kalshi market identifier. Case-insensitive.\n * @param settlementDate Calendar date the market settles for. Either a\n * UTC date-only `Date` (H/M/S/ms == 0) or a `YYYY-MM-DD` string.\n * @returns A frozen {@link NLowResolution}.\n * @throws {ContractIdError} The contract id doesn't follow\n * `KLOW<CITY>`, the city is unknown, or the settlement date is\n * invalid.\n */\nexport function kalshiNlowResolve(\n contractId: string,\n settlementDate: Date | string,\n): NLowResolution {\n if (typeof contractId !== \"string\") {\n throw new ContractIdError(`contractId must be a string; got ${typeof contractId}`);\n }\n\n const contractDate = coerceContractDate(settlementDate);\n\n const cid = contractId.toUpperCase();\n if (!cid.startsWith(\"KLOW\") || cid.length <= 4) {\n throw new ContractIdError(\n `NLOW contractId must follow 'KLOW<CITY>' format; got ${JSON.stringify(contractId)}`,\n );\n }\n const cityTicker = cid.slice(4);\n const station = KALSHI_SETTLEMENT_STATIONS[cityTicker];\n if (station === undefined) {\n const known = Object.keys(KALSHI_SETTLEMENT_STATIONS).sort();\n throw new ContractIdError(\n `unknown city ${JSON.stringify(cityTicker)} (known: ${known.join(\", \")})`,\n );\n }\n\n return Object.freeze({\n settlementSource: \"cli.archive\" as const,\n settlementStation: station.station,\n cityTicker,\n contractDate,\n });\n}\n","// TS-W5 Wave 5 — kalshiSettlementFor higher-level helper.\n//\n// Dispatch by Kalshi contract-id prefix (KHIGH* vs KLOW*) to the right\n// resolver. Returns the same shape both NHIGH and NLOW resolvers use so\n// downstream consumers don't need to know about the city-suffix split.\n\nimport { type NHighResolution, kalshiNhighResolve } from \"./resolvers/kalshi-nhigh.js\";\nimport { ContractIdError } from \"./resolvers/kalshi-nhigh.js\";\nimport { type NLowResolution, kalshiNlowResolve } from \"./resolvers/kalshi-nlow.js\";\n\nexport type KalshiSettlement = NHighResolution | NLowResolution;\n\n/**\n * Resolve a Kalshi NHIGH or NLOW contract id to its settlement metadata.\n *\n * `KHIGH*` prefixes dispatch to the NHIGH resolver; `KLOW*` prefixes\n * dispatch to NLOW. Anything else raises `ContractIdError`.\n *\n * @example\n * kalshiSettlementFor(\"KHIGHNYC\", \"2025-01-06\")\n * // → { settlementSource: \"cli.archive\", settlementStation: \"KNYC\",\n * // cityTicker: \"NYC\", contractDate: \"2025-01-06\" }\n */\nexport function kalshiSettlementFor(contractId: string, date: string): KalshiSettlement {\n if (typeof contractId !== \"string\" || contractId.length === 0) {\n throw new ContractIdError(\"contractId must be a non-empty string\");\n }\n const upper = contractId.toUpperCase();\n if (upper.startsWith(\"KHIGH\")) return kalshiNhighResolve(upper, date);\n if (upper.startsWith(\"KLOW\")) return kalshiNlowResolve(upper, date);\n throw new ContractIdError(\n `contractId ${JSON.stringify(contractId)} does not start with KHIGH or KLOW`,\n );\n}\n","const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);\n\nlet idbProxyableTypes;\nlet cursorAdvanceMethods;\n// This is a function to prevent it throwing up in node environments.\nfunction getIdbProxyableTypes() {\n return (idbProxyableTypes ||\n (idbProxyableTypes = [\n IDBDatabase,\n IDBObjectStore,\n IDBIndex,\n IDBCursor,\n IDBTransaction,\n ]));\n}\n// This is a function to prevent it throwing up in node environments.\nfunction getCursorAdvanceMethods() {\n return (cursorAdvanceMethods ||\n (cursorAdvanceMethods = [\n IDBCursor.prototype.advance,\n IDBCursor.prototype.continue,\n IDBCursor.prototype.continuePrimaryKey,\n ]));\n}\nconst transactionDoneMap = new WeakMap();\nconst transformCache = new WeakMap();\nconst reverseTransformCache = new WeakMap();\nfunction promisifyRequest(request) {\n const promise = new Promise((resolve, reject) => {\n const unlisten = () => {\n request.removeEventListener('success', success);\n request.removeEventListener('error', error);\n };\n const success = () => {\n resolve(wrap(request.result));\n unlisten();\n };\n const error = () => {\n reject(request.error);\n unlisten();\n };\n request.addEventListener('success', success);\n request.addEventListener('error', error);\n });\n // This mapping exists in reverseTransformCache but doesn't exist in transformCache. This\n // is because we create many promises from a single IDBRequest.\n reverseTransformCache.set(promise, request);\n return promise;\n}\nfunction cacheDonePromiseForTransaction(tx) {\n // Early bail if we've already created a done promise for this transaction.\n if (transactionDoneMap.has(tx))\n return;\n const done = new Promise((resolve, reject) => {\n const unlisten = () => {\n tx.removeEventListener('complete', complete);\n tx.removeEventListener('error', error);\n tx.removeEventListener('abort', error);\n };\n const complete = () => {\n resolve();\n unlisten();\n };\n const error = () => {\n reject(tx.error || new DOMException('AbortError', 'AbortError'));\n unlisten();\n };\n tx.addEventListener('complete', complete);\n tx.addEventListener('error', error);\n tx.addEventListener('abort', error);\n });\n // Cache it for later retrieval.\n transactionDoneMap.set(tx, done);\n}\nlet idbProxyTraps = {\n get(target, prop, receiver) {\n if (target instanceof IDBTransaction) {\n // Special handling for transaction.done.\n if (prop === 'done')\n return transactionDoneMap.get(target);\n // Make tx.store return the only store in the transaction, or undefined if there are many.\n if (prop === 'store') {\n return receiver.objectStoreNames[1]\n ? undefined\n : receiver.objectStore(receiver.objectStoreNames[0]);\n }\n }\n // Else transform whatever we get back.\n return wrap(target[prop]);\n },\n set(target, prop, value) {\n target[prop] = value;\n return true;\n },\n has(target, prop) {\n if (target instanceof IDBTransaction &&\n (prop === 'done' || prop === 'store')) {\n return true;\n }\n return prop in target;\n },\n};\nfunction replaceTraps(callback) {\n idbProxyTraps = callback(idbProxyTraps);\n}\nfunction wrapFunction(func) {\n // Due to expected object equality (which is enforced by the caching in `wrap`), we\n // only create one new func per func.\n // Cursor methods are special, as the behaviour is a little more different to standard IDB. In\n // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the\n // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense\n // with real promises, so each advance methods returns a new promise for the cursor object, or\n // undefined if the end of the cursor has been reached.\n if (getCursorAdvanceMethods().includes(func)) {\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n func.apply(unwrap(this), args);\n return wrap(this.request);\n };\n }\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n return wrap(func.apply(unwrap(this), args));\n };\n}\nfunction transformCachableValue(value) {\n if (typeof value === 'function')\n return wrapFunction(value);\n // This doesn't return, it just creates a 'done' promise for the transaction,\n // which is later returned for transaction.done (see idbObjectHandler).\n if (value instanceof IDBTransaction)\n cacheDonePromiseForTransaction(value);\n if (instanceOfAny(value, getIdbProxyableTypes()))\n return new Proxy(value, idbProxyTraps);\n // Return the same value back if we're not going to transform it.\n return value;\n}\nfunction wrap(value) {\n // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because\n // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.\n if (value instanceof IDBRequest)\n return promisifyRequest(value);\n // If we've already transformed this value before, reuse the transformed value.\n // This is faster, but it also provides object equality.\n if (transformCache.has(value))\n return transformCache.get(value);\n const newValue = transformCachableValue(value);\n // Not all types are transformed.\n // These may be primitive types, so they can't be WeakMap keys.\n if (newValue !== value) {\n transformCache.set(value, newValue);\n reverseTransformCache.set(newValue, value);\n }\n return newValue;\n}\nconst unwrap = (value) => reverseTransformCache.get(value);\n\n/**\n * Open a database.\n *\n * @param name Name of the database.\n * @param version Schema version.\n * @param callbacks Additional callbacks.\n */\nfunction openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {\n const request = indexedDB.open(name, version);\n const openPromise = wrap(request);\n if (upgrade) {\n request.addEventListener('upgradeneeded', (event) => {\n upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);\n });\n }\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event.newVersion, event));\n }\n openPromise\n .then((db) => {\n if (terminated)\n db.addEventListener('close', () => terminated());\n if (blocking) {\n db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));\n }\n })\n .catch(() => { });\n return openPromise;\n}\n/**\n * Delete a database.\n *\n * @param name Name of the database.\n */\nfunction deleteDB(name, { blocked } = {}) {\n const request = indexedDB.deleteDatabase(name);\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event));\n }\n return wrap(request).then(() => undefined);\n}\n\nconst readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];\nconst writeMethods = ['put', 'add', 'delete', 'clear'];\nconst cachedMethods = new Map();\nfunction getMethod(target, prop) {\n if (!(target instanceof IDBDatabase &&\n !(prop in target) &&\n typeof prop === 'string')) {\n return;\n }\n if (cachedMethods.get(prop))\n return cachedMethods.get(prop);\n const targetFuncName = prop.replace(/FromIndex$/, '');\n const useIndex = prop !== targetFuncName;\n const isWrite = writeMethods.includes(targetFuncName);\n if (\n // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.\n !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||\n !(isWrite || readMethods.includes(targetFuncName))) {\n return;\n }\n const method = async function (storeName, ...args) {\n // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(\n const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');\n let target = tx.store;\n if (useIndex)\n target = target.index(args.shift());\n // Must reject if op rejects.\n // If it's a write operation, must reject if tx.done rejects.\n // Must reject with op rejection first.\n // Must resolve with op value.\n // Must handle both promises (no unhandled rejections)\n return (await Promise.all([\n target[targetFuncName](...args),\n isWrite && tx.done,\n ]))[0];\n };\n cachedMethods.set(prop, method);\n return method;\n}\nreplaceTraps((oldTraps) => ({\n ...oldTraps,\n get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),\n has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),\n}));\n\nconst advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance'];\nconst methodMap = {};\nconst advanceResults = new WeakMap();\nconst ittrProxiedCursorToOriginalProxy = new WeakMap();\nconst cursorIteratorTraps = {\n get(target, prop) {\n if (!advanceMethodProps.includes(prop))\n return target[prop];\n let cachedFunc = methodMap[prop];\n if (!cachedFunc) {\n cachedFunc = methodMap[prop] = function (...args) {\n advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));\n };\n }\n return cachedFunc;\n },\n};\nasync function* iterate(...args) {\n // tslint:disable-next-line:no-this-assignment\n let cursor = this;\n if (!(cursor instanceof IDBCursor)) {\n cursor = await cursor.openCursor(...args);\n }\n if (!cursor)\n return;\n cursor = cursor;\n const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);\n ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);\n // Map this double-proxy back to the original, so other cursor methods work.\n reverseTransformCache.set(proxiedCursor, unwrap(cursor));\n while (cursor) {\n yield proxiedCursor;\n // If one of the advancing methods was not called, call continue().\n cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());\n advanceResults.delete(proxiedCursor);\n }\n}\nfunction isIteratorProp(target, prop) {\n return ((prop === Symbol.asyncIterator &&\n instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||\n (prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));\n}\nreplaceTraps((oldTraps) => ({\n ...oldTraps,\n get(target, prop, receiver) {\n if (isIteratorProp(target, prop))\n return iterate;\n return oldTraps.get(target, prop, receiver);\n },\n has(target, prop) {\n return isIteratorProp(target, prop) || oldTraps.has(target, prop);\n },\n}));\n\nexport { deleteDB, openDB, unwrap, wrap };\n","// CacheStore — pluggable key/value contract for the @mostlyrightmd/core cache\n// layer. Three concrete implementations land in TS-W3:\n// - MemoryStore — Map-backed, no persistence (Cloudflare Workers default).\n// - FsStore — node:fs/promises + proper-lockfile (Node default).\n// - IndexedDBStore — idb + Web Locks API (browser; plan 02).\n//\n// `defaultCacheStore()` (plan 02) auto-detects at runtime per\n// TS-SDK-DESIGN §5.4.\n\n/** Cache entry envelope with optional TTL. */\nexport interface CacheEntry<T = unknown> {\n readonly value: T;\n /** Epoch ms when the entry expires. Absence = no expiry. */\n readonly expiresAt?: number;\n}\n\n/** Optional setters for cache writes. */\nexport interface CacheSetOptions {\n /** Time-to-live in milliseconds. Implementations may honor or ignore. */\n readonly ttlMs?: number;\n}\n\n/**\n * Pluggable key/value cache contract used throughout the SDK.\n *\n * All methods are async — concrete implementations may resolve immediately\n * (MemoryStore) or do I/O (FsStore / IndexedDBStore).\n *\n * Semantic contract:\n * - `get<T>(key)` returns the stored value or `null` on miss. NEVER throws\n * on miss.\n * - `set<T>(key, value, opts?)` overwrites. ttlMs is implementation-honored\n * (MemoryStore + IndexedDBStore honor it; FsStore ignores in v0.1).\n * - `delete(key)` is a no-op on miss; returns void.\n * - `withLock<T>(key, fn)` runs `fn` under a key-scoped exclusive lock and\n * releases on settle (resolve OR throw). Nested calls to the same key\n * serialize; calls to different keys MAY run in parallel.\n */\nexport interface CacheStore {\n get<T = unknown>(key: string): Promise<T | null>;\n set<T = unknown>(key: string, value: T, opts?: CacheSetOptions): Promise<void>;\n delete(key: string): Promise<void>;\n withLock<T>(key: string, fn: () => Promise<T>): Promise<T>;\n}\n\n/**\n * Canonical lock identifier for a given cache key.\n *\n * Pure — same key → same lock id. Used by FsStore (proper-lockfile sidecar)\n * and IndexedDBStore (`navigator.locks.request(...)` name).\n */\nexport function lockKeyFor(key: string): string {\n return `mostlyright:cache:lock:${key}`;\n}\n\n/**\n * Canonical cache schema version stamp (Phase 21 21-03).\n *\n * Must match Python `mostlyright.weather.cache._cache_schema_version`\n * (set in Phase 18 18-08 to \"v2-phase18-integer-f\" after the ASOS\n * integer-°F precision fix). Embedded by `versionedCacheStore` into\n * every cache write so pre-bump entries silently re-fetch.\n *\n * Bump this string when a cache-shape change ships; existing cached\n * values invalidate on next read with no operator action required.\n */\nexport const CACHE_SCHEMA_VERSION = \"v2-phase18-integer-f\";\n","// MemoryStore — Map-backed CacheStore for ephemeral runtimes (Cloudflare\n// Workers, jsdom test envs without persistence). NOT shared across\n// processes — per-instance state.\n//\n// Value isolation via `structuredClone`: callers can mutate stored objects\n// after `.set()` without leaking changes back. Honors ttlMs with lazy\n// eviction at `.get()` time.\n//\n// withLock uses a per-key promise chain — pending lock-acquisitions queue\n// behind the current holder and run in FIFO order on settle.\n\nimport type { CacheEntry, CacheSetOptions, CacheStore } from \"./types.js\";\n\n/**\n * In-memory cache. Per-instance state; two MemoryStore instances do NOT\n * share state.\n *\n * - Values cloned via `structuredClone` so post-`set` mutation can't leak.\n * - ttlMs honored with lazy eviction on `get`.\n * - withLock serializes nested calls via a per-key promise chain.\n */\nexport class MemoryStore implements CacheStore {\n readonly #entries = new Map<string, CacheEntry<unknown>>();\n readonly #chain = new Map<string, Promise<unknown>>();\n\n async get<T = unknown>(key: string): Promise<T | null> {\n const e = this.#entries.get(key);\n if (e === undefined) return null;\n if (e.expiresAt !== undefined && Date.now() >= e.expiresAt) {\n this.#entries.delete(key);\n return null;\n }\n // Defensive clone on read too — callers can't mutate stored value via\n // the returned reference either.\n return structuredClone(e.value) as T;\n }\n\n async set<T = unknown>(key: string, value: T, opts?: CacheSetOptions): Promise<void> {\n const cloned = structuredClone(value);\n const entry: CacheEntry<unknown> =\n opts?.ttlMs !== undefined\n ? { value: cloned, expiresAt: Date.now() + opts.ttlMs }\n : { value: cloned };\n this.#entries.set(key, entry);\n }\n\n async delete(key: string): Promise<void> {\n this.#entries.delete(key);\n }\n\n /**\n * Enumerate live (non-expired) keys with the given prefix.\n *\n * TS-W6 Wave 1: `availability()` uses this to count cached observation\n * months and climate years per station. Expired entries are evicted as a\n * side effect (same lazy-eviction policy as `.get`).\n */\n async listKeys(prefix: string): Promise<ReadonlyArray<string>> {\n const now = Date.now();\n const out: string[] = [];\n for (const [key, entry] of this.#entries) {\n if (entry.expiresAt !== undefined && now >= entry.expiresAt) {\n this.#entries.delete(key);\n continue;\n }\n if (key.startsWith(prefix)) {\n out.push(key);\n }\n }\n return Object.freeze(out);\n }\n\n async withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n const prev = this.#chain.get(key) ?? Promise.resolve();\n // Chain `fn` after `prev` regardless of whether `prev` resolved or\n // rejected — the lock holder's failure shouldn't poison the queue.\n const next = prev.then(\n () => fn(),\n () => fn(),\n );\n // Store an absorber as the new tail so a later prev.then() handles\n // both branches without producing an unhandled-rejection warning. The\n // caller still receives the original `next` promise (including any\n // rejection from `fn`).\n const absorbed = next.then(\n () => undefined,\n () => undefined,\n );\n this.#chain.set(key, absorbed);\n absorbed.finally(() => {\n if (this.#chain.get(key) === absorbed) {\n this.#chain.delete(key);\n }\n });\n return next;\n }\n}\n","// IndexedDBStore — idb + Web Locks API CacheStore for browsers.\n//\n// Per TS-CACHE-02, the canonical IndexedDB DB name is `mostlyright-cache-v1`.\n// Object store: `entries`. Schema: key = string, value = CacheEntry<T>.\n//\n// withLock prefers `navigator.locks.request(name, ...)` (Web Locks API,\n// Chrome 69+, Firefox 96+, Safari 15.4+; all production-browser baselines).\n// When `navigator.locks` is unavailable (jsdom default), falls back to the\n// same per-key in-process promise chain used by MemoryStore / FsStore —\n// edge runtimes (Workers without web-locks polyfills) get the in-process\n// guarantee at least.\n//\n// ttlMs is honored via lazy eviction at `get` time (matches FsStore).\n\nimport { type IDBPDatabase, openDB } from \"idb\";\n\nimport type { CacheEntry, CacheSetOptions, CacheStore } from \"./types.js\";\nimport { lockKeyFor } from \"./types.js\";\n\n/** Canonical DB name. Re-exported via the cache barrel. */\nexport const DB_NAME = \"mostlyright-cache-v1\";\n\nconst STORE_NAME = \"entries\";\nconst SCHEMA_VERSION = 1;\n\nexport interface IndexedDBStoreOptions {\n /** Override the DB name. Tests pass unique values per case so they don't pollute each other. */\n readonly dbName?: string;\n}\n\ninterface WebLocksApi {\n request: <T>(\n name: string,\n options: { mode: \"exclusive\" },\n fn: () => Promise<T> | T,\n ) => Promise<T>;\n}\n\nfunction getWebLocks(): WebLocksApi | null {\n if (typeof navigator === \"undefined\") return null;\n const nav = navigator as unknown as { locks?: WebLocksApi };\n return nav.locks ?? null;\n}\n\n/**\n * Browser CacheStore backed by IndexedDB (via idb) + Web Locks API.\n *\n * When `navigator.locks` is unavailable (jsdom, edge runtimes without\n * Web Locks), falls back to a per-key in-process promise chain.\n */\nexport class IndexedDBStore implements CacheStore {\n readonly #dbName: string;\n readonly #dbPromise: Promise<IDBPDatabase>;\n readonly #chain = new Map<string, Promise<unknown>>();\n\n constructor(opts: IndexedDBStoreOptions = {}) {\n this.#dbName = opts.dbName ?? DB_NAME;\n this.#dbPromise = openDB(this.#dbName, SCHEMA_VERSION, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME);\n }\n },\n });\n }\n\n async get<T = unknown>(key: string): Promise<T | null> {\n const db = await this.#dbPromise;\n const entry = (await db.get(STORE_NAME, key)) as CacheEntry<T> | undefined;\n if (entry === undefined) return null;\n if (entry.expiresAt !== undefined && Date.now() >= entry.expiresAt) {\n // Lazy-evict — best-effort; ignore failures.\n try {\n await db.delete(STORE_NAME, key);\n } catch {\n // ignore\n }\n return null;\n }\n return entry.value as T;\n }\n\n async set<T = unknown>(key: string, value: T, opts?: CacheSetOptions): Promise<void> {\n const db = await this.#dbPromise;\n const entry: CacheEntry<T> =\n opts?.ttlMs !== undefined ? { value, expiresAt: Date.now() + opts.ttlMs } : { value };\n await db.put(STORE_NAME, entry, key);\n }\n\n async delete(key: string): Promise<void> {\n const db = await this.#dbPromise;\n await db.delete(STORE_NAME, key);\n }\n\n /**\n * Enumerate keys with the given prefix using IndexedDB's bounded range\n * query. Live keys only — expired entries are lazy-evicted on read by\n * `get()`, so a stale-but-not-yet-evicted entry can appear here; callers\n * who care about expiration should `get()` to confirm.\n *\n * TS-W6 Wave 1: `availability()` uses this to count observation months and\n * climate years for a station.\n */\n async listKeys(prefix: string): Promise<ReadonlyArray<string>> {\n const db = await this.#dbPromise;\n // Build an inclusive lower bound and an exclusive upper bound using the\n // next-codepoint trick. IndexedDB's IDBKeyRange handles string ordering\n // by UTF-16 code units, which matches JS string comparison; we use the\n // Unicode max-codepoint as the upper sentinel so any key starting with\n // `prefix` lands inside the range.\n const range = IDBKeyRange.bound(prefix, `${prefix}`, false, false);\n const keys = (await db.getAllKeys(STORE_NAME, range)) as IDBValidKey[];\n const out: string[] = [];\n for (const k of keys) {\n if (typeof k === \"string\" && k.startsWith(prefix)) {\n out.push(k);\n }\n }\n return Object.freeze(out);\n }\n\n async withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n const locks = getWebLocks();\n if (locks !== null) {\n // Web Locks API — production-browser path. Cross-tab safe.\n return locks.request<T>(lockKeyFor(key), { mode: \"exclusive\" }, () => fn());\n }\n // Fallback for jsdom / edge runtimes without navigator.locks: in-process\n // per-key promise chain (FIFO; matches MemoryStore semantics).\n const prev = this.#chain.get(key) ?? Promise.resolve();\n const next = prev.then(\n () => fn(),\n () => fn(),\n );\n const absorbed = next.then(\n () => undefined,\n () => undefined,\n );\n this.#chain.set(key, absorbed);\n absorbed.finally(() => {\n if (this.#chain.get(key) === absorbed) this.#chain.delete(key);\n });\n return next;\n }\n}\n","// versionedCacheStore — Phase 21 21-03.\n//\n// Schema-version invariant for the TS cache, matching Python's Phase 18\n// 18-08 invariant. Wraps any underlying CacheStore so reads with a stale\n// `_cache_schema_version` field return null (cache miss → re-fetch),\n// matching the parquet-metadata-based invariant on the Python side.\n//\n// Before: TS cache was generic key/value with no version stamping. After\n// Phase 18 lifted the ASOS integer-°F precision fix, pre-Phase-18 user\n// caches (with 0.06°F float values) would silently return stale values\n// on next call instead of re-fetching. Python embeds the version into\n// parquet metadata (pyarrow `kv_metadata`); TS embeds it as a sidecar\n// field in the stored JSON value.\n//\n// Wire-up: `defaultCacheStore()` wraps each concrete store\n// (IndexedDBStore / FsStore / MemoryStore) via this adapter so consumers\n// see the same `CacheStore` interface. The version wrap/unwrap is\n// invisible from the caller's perspective.\n//\n// Bump `CACHE_SCHEMA_VERSION` in `./types.ts` when the next cache-shape\n// change ships; existing cached values silently invalidate.\n\nimport type { CacheEntry, CacheSetOptions, CacheStore } from \"./types.js\";\n\n/** Sentinel field name embedded in every cached value. */\nconst VERSION_FIELD = \"_cache_schema_version\" as const;\n\n/** Wrapper shape stored under each key. */\nexport interface VersionedEntry<T = unknown> {\n readonly value: T;\n readonly _cache_schema_version: string;\n}\n\nfunction isVersionedEntry(v: unknown): v is VersionedEntry<unknown> {\n if (v === null || typeof v !== \"object\") return false;\n if (!(VERSION_FIELD in (v as Record<string, unknown>))) return false;\n return typeof (v as Record<string, unknown>)[VERSION_FIELD] === \"string\";\n}\n\n// Optional extension surface — some concrete stores (MemoryStore,\n// IndexedDBStore) expose `listKeys(prefix)` beyond the CacheStore\n// contract. The adapter forwards it transparently when present so\n// `availability()` and friends keep working.\ninterface ListKeysCapable {\n listKeys(prefix: string): Promise<ReadonlyArray<string>>;\n}\n\nfunction hasListKeys(s: CacheStore): s is CacheStore & ListKeysCapable {\n return typeof (s as Partial<ListKeysCapable>).listKeys === \"function\";\n}\n\nclass VersionedCacheStore implements CacheStore, ListKeysCapable {\n readonly #inner: CacheStore;\n readonly #version: string;\n\n constructor(inner: CacheStore, version: string) {\n if (typeof version !== \"string\" || version.length === 0) {\n throw new TypeError(\"versionedCacheStore: version must be a non-empty string\");\n }\n this.#inner = inner;\n this.#version = version;\n }\n\n /**\n * Test/diagnostics seam: return the underlying store so tests can assert\n * which concrete backend `defaultCacheStore()` selected. NOT a production\n * API — production code MUST use the wrapped store so version\n * invalidation fires on stale reads.\n *\n * @internal\n */\n __peekInner(): CacheStore {\n return this.#inner;\n }\n\n async get<T = unknown>(key: string): Promise<T | null> {\n const raw = await this.#inner.get<unknown>(key);\n if (raw === null) return null;\n if (!isVersionedEntry(raw)) {\n // Pre-21-03 cache entry (no version wrapper). Treat as miss; caller\n // will re-fetch with the new wrapper on the next set.\n return null;\n }\n if (raw._cache_schema_version !== this.#version) {\n // Mismatched schema version — stale. Treat as miss.\n return null;\n }\n return raw.value as T;\n }\n\n async set<T = unknown>(key: string, value: T, opts?: CacheSetOptions): Promise<void> {\n const wrapped: VersionedEntry<T> = {\n value,\n [VERSION_FIELD]: this.#version,\n } as VersionedEntry<T>;\n await this.#inner.set(key, wrapped, opts);\n }\n\n async delete(key: string): Promise<void> {\n await this.#inner.delete(key);\n }\n\n async withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n return this.#inner.withLock(key, fn);\n }\n\n async listKeys(prefix: string): Promise<ReadonlyArray<string>> {\n if (hasListKeys(this.#inner)) {\n return this.#inner.listKeys(prefix);\n }\n return Object.freeze([]);\n }\n}\n\n/**\n * Wrap any CacheStore so reads validate `_cache_schema_version` and\n * writes embed it. The wrapper is transparent — callers continue to use\n * the same `CacheStore` interface.\n *\n * @param inner underlying store (MemoryStore / IndexedDBStore / FsStore)\n * @param version the schema version to embed; non-matching reads miss\n */\nexport function versionedCacheStore(inner: CacheStore, version: string): CacheStore {\n return new VersionedCacheStore(inner, version);\n}\n\n// Re-export the canonical version constant alongside the adapter — the\n// constant lives in `./types.ts` (single source of truth) but is\n// surfaced here too so callers writing\n// `versionedCacheStore(inner, CACHE_SCHEMA_VERSION)` import both from\n// the same module.\nexport { CACHE_SCHEMA_VERSION } from \"./types.js\";\n\n/**\n * Internal helper for tests + diagnostics: expose the raw envelope shape\n * so callers can pre-seed an underlying store with the wrapped shape\n * without round-tripping through this adapter.\n */\nexport function wrapForCache<T>(value: T, version: string): VersionedEntry<T> {\n return { value, [VERSION_FIELD]: version } as VersionedEntry<T>;\n}\n","// AUTO-GENERATED by @mostlyrightmd/codegen from schemas/stations.json.\n// DO NOT EDIT — regenerate with: pnpm codegen\n// Last manifest SHA recorded in schemas/EXPORT_MANIFEST.json\n\nexport interface StationInfo {\n code: string | null;\n ghcnh_id: string | null;\n icao: string;\n name: string | null;\n tz: string;\n latitude: number | null;\n longitude: number | null;\n country: string | null;\n venues: ReadonlyArray<string>;\n}\n\nexport const STATIONS: ReadonlyArray<StationInfo> = [\n {\n code: \"CYYZ\",\n country: \"CA\",\n ghcnh_id: null,\n icao: \"CYYZ\",\n latitude: 43.6777,\n longitude: -79.6248,\n name: \"Toronto Pearson International\",\n tz: \"America/Toronto\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EDDB\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDB\",\n latitude: 52.3667,\n longitude: 13.5033,\n name: \"Berlin Brandenburg\",\n tz: \"Europe/Berlin\",\n venues: [],\n },\n {\n code: \"EDDF\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDF\",\n latitude: 50.0379,\n longitude: 8.5622,\n name: \"Frankfurt am Main\",\n tz: \"Europe/Berlin\",\n venues: [],\n },\n {\n code: \"EDDM\",\n country: \"DE\",\n ghcnh_id: null,\n icao: \"EDDM\",\n latitude: 48.3538,\n longitude: 11.7861,\n name: \"Munich Franz Josef Strauss\",\n tz: \"Europe/Berlin\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EFHK\",\n country: \"FI\",\n ghcnh_id: null,\n icao: \"EFHK\",\n latitude: 60.3172,\n longitude: 24.9633,\n name: \"Helsinki-Vantaa\",\n tz: \"Europe/Helsinki\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EGKK\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGKK\",\n latitude: 51.1481,\n longitude: -0.1903,\n name: \"London Gatwick\",\n tz: \"Europe/London\",\n venues: [],\n },\n {\n code: \"EGLC\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGLC\",\n latitude: 51.5053,\n longitude: 0.0553,\n name: \"London City\",\n tz: \"Europe/London\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EGLL\",\n country: \"GB\",\n ghcnh_id: null,\n icao: \"EGLL\",\n latitude: 51.4706,\n longitude: -0.4619,\n name: \"London Heathrow\",\n tz: \"Europe/London\",\n venues: [],\n },\n {\n code: \"EHAM\",\n country: \"NL\",\n ghcnh_id: null,\n icao: \"EHAM\",\n latitude: 52.3086,\n longitude: 4.7639,\n name: \"Amsterdam Schiphol\",\n tz: \"Europe/Amsterdam\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"EKCH\",\n country: \"DK\",\n ghcnh_id: null,\n icao: \"EKCH\",\n latitude: 55.6181,\n longitude: 12.6561,\n name: \"Copenhagen Kastrup\",\n tz: \"Europe/Copenhagen\",\n venues: [],\n },\n {\n code: \"EPWA\",\n country: \"PL\",\n ghcnh_id: null,\n icao: \"EPWA\",\n latitude: 52.1657,\n longitude: 20.9671,\n name: \"Warsaw Chopin\",\n tz: \"Europe/Warsaw\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ESSA\",\n country: \"SE\",\n ghcnh_id: null,\n icao: \"ESSA\",\n latitude: 59.6519,\n longitude: 17.9186,\n name: \"Stockholm Arlanda\",\n tz: \"Europe/Stockholm\",\n venues: [],\n },\n {\n code: \"FACT\",\n country: \"ZA\",\n ghcnh_id: null,\n icao: \"FACT\",\n latitude: -33.9648,\n longitude: 18.6017,\n name: \"Cape Town International\",\n tz: \"Africa/Johannesburg\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ATL\",\n country: \"US\",\n ghcnh_id: \"USW00013874\",\n icao: \"KATL\",\n latitude: 33.6407,\n longitude: -84.4277,\n name: \"Hartsfield-Jackson Atlanta International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"AUS\",\n country: \"US\",\n ghcnh_id: \"USW00013904\",\n icao: \"KAUS\",\n latitude: 30.1975,\n longitude: -97.6664,\n name: \"Austin-Bergstrom International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"BKF\",\n country: \"US\",\n ghcnh_id: \"USW00093067\",\n icao: \"KBKF\",\n latitude: 39.7019,\n longitude: -104.7517,\n name: \"Buckley Space Force Base (Denver)\",\n tz: \"America/Denver\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"BNA\",\n country: \"US\",\n ghcnh_id: \"USW00013897\",\n icao: \"KBNA\",\n latitude: 36.1245,\n longitude: -86.6782,\n name: \"Nashville International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"BOS\",\n country: \"US\",\n ghcnh_id: \"USW00014739\",\n icao: \"KBOS\",\n latitude: 42.3656,\n longitude: -71.0096,\n name: \"Boston Logan International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"CVG\",\n country: \"US\",\n ghcnh_id: \"USW00093814\",\n icao: \"KCVG\",\n latitude: 39.0488,\n longitude: -84.6678,\n name: \"Cincinnati/Northern Kentucky International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DAL\",\n country: \"US\",\n ghcnh_id: \"USW00013960\",\n icao: \"KDAL\",\n latitude: 32.8481,\n longitude: -96.8512,\n name: \"Dallas Love Field\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"DCA\",\n country: \"US\",\n ghcnh_id: \"USW00013743\",\n icao: \"KDCA\",\n latitude: 38.8512,\n longitude: -77.0402,\n name: \"Washington Reagan National\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DEN\",\n country: \"US\",\n ghcnh_id: \"USW00003017\",\n icao: \"KDEN\",\n latitude: 39.8561,\n longitude: -104.6737,\n name: \"Denver International\",\n tz: \"America/Denver\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DFW\",\n country: \"US\",\n ghcnh_id: \"USW00003927\",\n icao: \"KDFW\",\n latitude: 32.8998,\n longitude: -97.0403,\n name: \"Dallas-Fort Worth International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"DTW\",\n country: \"US\",\n ghcnh_id: \"USW00094847\",\n icao: \"KDTW\",\n latitude: 42.2124,\n longitude: -83.3534,\n name: \"Detroit Metropolitan Wayne County\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"HOU\",\n country: \"US\",\n ghcnh_id: \"USW00012918\",\n icao: \"KHOU\",\n latitude: 29.6454,\n longitude: -95.2789,\n name: \"Houston Hobby\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"IAH\",\n country: \"US\",\n ghcnh_id: \"USW00012960\",\n icao: \"KIAH\",\n latitude: 29.9844,\n longitude: -95.3414,\n name: \"Houston George Bush Intercontinental\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LAS\",\n country: \"US\",\n ghcnh_id: \"USW00023169\",\n icao: \"KLAS\",\n latitude: 36.084,\n longitude: -115.1537,\n name: \"Harry Reid (McCarran) International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LAX\",\n country: \"US\",\n ghcnh_id: \"USW00023174\",\n icao: \"KLAX\",\n latitude: 33.9425,\n longitude: -118.4081,\n name: \"Los Angeles International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"LGA\",\n country: \"US\",\n ghcnh_id: \"USW00014732\",\n icao: \"KLGA\",\n latitude: 40.7772,\n longitude: -73.8726,\n name: \"New York LaGuardia\",\n tz: \"America/New_York\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MDW\",\n country: \"US\",\n ghcnh_id: \"USW00014819\",\n icao: \"KMDW\",\n latitude: 41.7868,\n longitude: -87.7522,\n name: \"Chicago Midway International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"MIA\",\n country: \"US\",\n ghcnh_id: \"USW00012839\",\n icao: \"KMIA\",\n latitude: 25.7959,\n longitude: -80.287,\n name: \"Miami International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"MSP\",\n country: \"US\",\n ghcnh_id: \"USW00014922\",\n icao: \"KMSP\",\n latitude: 44.8848,\n longitude: -93.2223,\n name: \"Minneapolis-St Paul International\",\n tz: \"America/Chicago\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"MSY\",\n country: \"US\",\n ghcnh_id: \"USW00012916\",\n icao: \"KMSY\",\n latitude: 29.9934,\n longitude: -90.258,\n name: \"New Orleans Louis Armstrong International\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"NYC\",\n country: \"US\",\n ghcnh_id: \"USW00094728\",\n icao: \"KNYC\",\n latitude: 40.7789,\n longitude: -73.9692,\n name: \"Central Park, New York\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"OKC\",\n country: \"US\",\n ghcnh_id: \"USW00013967\",\n icao: \"KOKC\",\n latitude: 35.3931,\n longitude: -97.6007,\n name: \"Oklahoma City Will Rogers World\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"ORD\",\n country: \"US\",\n ghcnh_id: \"USW00094846\",\n icao: \"KORD\",\n latitude: 41.9742,\n longitude: -87.9073,\n name: \"Chicago O'Hare International\",\n tz: \"America/Chicago\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"PHL\",\n country: \"US\",\n ghcnh_id: \"USW00013739\",\n icao: \"KPHL\",\n latitude: 39.8721,\n longitude: -75.2411,\n name: \"Philadelphia International\",\n tz: \"America/New_York\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"PHX\",\n country: \"US\",\n ghcnh_id: \"USW00023183\",\n icao: \"KPHX\",\n latitude: 33.4373,\n longitude: -112.0078,\n name: \"Phoenix Sky Harbor International\",\n tz: \"America/Phoenix\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"SAT\",\n country: \"US\",\n ghcnh_id: \"USW00012921\",\n icao: \"KSAT\",\n latitude: 29.5337,\n longitude: -98.4698,\n name: \"San Antonio International\",\n tz: \"America/Chicago\",\n venues: [],\n },\n {\n code: \"SEA\",\n country: \"US\",\n ghcnh_id: \"USW00024233\",\n icao: \"KSEA\",\n latitude: 47.4502,\n longitude: -122.3088,\n name: \"Seattle-Tacoma International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"SFO\",\n country: \"US\",\n ghcnh_id: \"USW00023234\",\n icao: \"KSFO\",\n latitude: 37.6213,\n longitude: -122.379,\n name: \"San Francisco International\",\n tz: \"America/Los_Angeles\",\n venues: [\n \"kalshi\",\n \"polymarket\",\n ],\n },\n {\n code: \"SLC\",\n country: \"US\",\n ghcnh_id: \"USW00024127\",\n icao: \"KSLC\",\n latitude: 40.7884,\n longitude: -111.9778,\n name: \"Salt Lake City International\",\n tz: \"America/Denver\",\n venues: [\n \"kalshi\",\n ],\n },\n {\n code: \"LEBL\",\n country: \"ES\",\n ghcnh_id: null,\n icao: \"LEBL\",\n latitude: 41.2974,\n longitude: 2.0833,\n name: \"Barcelona El Prat\",\n tz: \"Europe/Madrid\",\n venues: [],\n },\n {\n code: \"LEMD\",\n country: \"ES\",\n ghcnh_id: null,\n icao: \"LEMD\",\n latitude: 40.4719,\n longitude: -3.5626,\n name: \"Madrid Barajas\",\n tz: \"Europe/Madrid\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LFPB\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPB\",\n latitude: 48.9694,\n longitude: 2.4414,\n name: \"Paris Le Bourget\",\n tz: \"Europe/Paris\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LFPG\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPG\",\n latitude: 49.0097,\n longitude: 2.5479,\n name: \"Paris Charles de Gaulle\",\n tz: \"Europe/Paris\",\n venues: [],\n },\n {\n code: \"LFPO\",\n country: \"FR\",\n ghcnh_id: null,\n icao: \"LFPO\",\n latitude: 48.7233,\n longitude: 2.3794,\n name: \"Paris Orly\",\n tz: \"Europe/Paris\",\n venues: [],\n },\n {\n code: \"LIMC\",\n country: \"IT\",\n ghcnh_id: null,\n icao: \"LIMC\",\n latitude: 45.6306,\n longitude: 8.7281,\n name: \"Milan Malpensa\",\n tz: \"Europe/Rome\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LIRF\",\n country: \"IT\",\n ghcnh_id: null,\n icao: \"LIRF\",\n latitude: 41.8003,\n longitude: 12.2389,\n name: \"Rome Fiumicino\",\n tz: \"Europe/Rome\",\n venues: [],\n },\n {\n code: \"LLBG\",\n country: \"IL\",\n ghcnh_id: null,\n icao: \"LLBG\",\n latitude: 32.0114,\n longitude: 34.8867,\n name: \"Tel Aviv Ben Gurion\",\n tz: \"Asia/Jerusalem\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LOWW\",\n country: \"AT\",\n ghcnh_id: null,\n icao: \"LOWW\",\n latitude: 48.1103,\n longitude: 16.5697,\n name: \"Vienna International\",\n tz: \"Europe/Vienna\",\n venues: [],\n },\n {\n code: \"LSZH\",\n country: \"CH\",\n ghcnh_id: null,\n icao: \"LSZH\",\n latitude: 47.4647,\n longitude: 8.5492,\n name: \"Zurich\",\n tz: \"Europe/Zurich\",\n venues: [],\n },\n {\n code: \"LTAC\",\n country: \"TR\",\n ghcnh_id: null,\n icao: \"LTAC\",\n latitude: 40.1281,\n longitude: 32.9951,\n name: \"Ankara Esenboga\",\n tz: \"Europe/Istanbul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"LTFM\",\n country: \"TR\",\n ghcnh_id: null,\n icao: \"LTFM\",\n latitude: 41.2753,\n longitude: 28.7519,\n name: \"Istanbul Airport\",\n tz: \"Europe/Istanbul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MMMX\",\n country: \"MX\",\n ghcnh_id: null,\n icao: \"MMMX\",\n latitude: 19.4363,\n longitude: -99.0721,\n name: \"Mexico City Benito Juarez International\",\n tz: \"America/Mexico_City\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"MPMG\",\n country: \"PA\",\n ghcnh_id: null,\n icao: \"MPMG\",\n latitude: 8.9733,\n longitude: -79.5556,\n name: \"Panama City Marcos A. Gelabert (Albrook)\",\n tz: \"America/Panama\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"NZAA\",\n country: \"NZ\",\n ghcnh_id: null,\n icao: \"NZAA\",\n latitude: -37.0081,\n longitude: 174.7917,\n name: \"Auckland\",\n tz: \"Pacific/Auckland\",\n venues: [],\n },\n {\n code: \"NZWN\",\n country: \"NZ\",\n ghcnh_id: null,\n icao: \"NZWN\",\n latitude: -41.3272,\n longitude: 174.8053,\n name: \"Wellington\",\n tz: \"Pacific/Auckland\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OEJN\",\n country: \"SA\",\n ghcnh_id: null,\n icao: \"OEJN\",\n latitude: 21.6796,\n longitude: 39.1565,\n name: \"Jeddah King Abdulaziz International\",\n tz: \"Asia/Riyadh\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OERK\",\n country: \"SA\",\n ghcnh_id: null,\n icao: \"OERK\",\n latitude: 24.9576,\n longitude: 46.6988,\n name: \"Riyadh King Khalid International\",\n tz: \"Asia/Riyadh\",\n venues: [],\n },\n {\n code: \"OMDB\",\n country: \"AE\",\n ghcnh_id: null,\n icao: \"OMDB\",\n latitude: 25.2532,\n longitude: 55.3657,\n name: \"Dubai International\",\n tz: \"Asia/Dubai\",\n venues: [],\n },\n {\n code: \"OPKC\",\n country: \"PK\",\n ghcnh_id: null,\n icao: \"OPKC\",\n latitude: 24.9065,\n longitude: 67.1608,\n name: \"Karachi Jinnah International\",\n tz: \"Asia/Karachi\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"OTHH\",\n country: \"QA\",\n ghcnh_id: null,\n icao: \"OTHH\",\n latitude: 25.2731,\n longitude: 51.608,\n name: \"Doha Hamad International\",\n tz: \"Asia/Qatar\",\n venues: [],\n },\n {\n code: \"RCSS\",\n country: \"TW\",\n ghcnh_id: null,\n icao: \"RCSS\",\n latitude: 25.0694,\n longitude: 121.5519,\n name: \"Taipei Songshan\",\n tz: \"Asia/Taipei\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RCTP\",\n country: \"TW\",\n ghcnh_id: null,\n icao: \"RCTP\",\n latitude: 25.0777,\n longitude: 121.2328,\n name: \"Taipei Taoyuan\",\n tz: \"Asia/Taipei\",\n venues: [],\n },\n {\n code: \"RJAA\",\n country: \"JP\",\n ghcnh_id: null,\n icao: \"RJAA\",\n latitude: 35.7647,\n longitude: 140.3864,\n name: \"Tokyo Narita\",\n tz: \"Asia/Tokyo\",\n venues: [],\n },\n {\n code: \"RJTT\",\n country: \"JP\",\n ghcnh_id: null,\n icao: \"RJTT\",\n latitude: 35.5522,\n longitude: 139.78,\n name: \"Tokyo Haneda\",\n tz: \"Asia/Tokyo\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RKPK\",\n country: \"KR\",\n ghcnh_id: null,\n icao: \"RKPK\",\n latitude: 35.1795,\n longitude: 128.9382,\n name: \"Busan Gimhae International\",\n tz: \"Asia/Seoul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RKSI\",\n country: \"KR\",\n ghcnh_id: null,\n icao: \"RKSI\",\n latitude: 37.4691,\n longitude: 126.4505,\n name: \"Seoul Incheon\",\n tz: \"Asia/Seoul\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"RPLL\",\n country: \"PH\",\n ghcnh_id: null,\n icao: \"RPLL\",\n latitude: 14.5086,\n longitude: 121.0197,\n name: \"Manila Ninoy Aquino International\",\n tz: \"Asia/Manila\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"SAEZ\",\n country: \"AR\",\n ghcnh_id: null,\n icao: \"SAEZ\",\n latitude: -34.8222,\n longitude: -58.5358,\n name: \"Buenos Aires Ezeiza\",\n tz: \"America/Argentina/Buenos_Aires\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"SBGR\",\n country: \"BR\",\n ghcnh_id: null,\n icao: \"SBGR\",\n latitude: -23.4356,\n longitude: -46.4731,\n name: \"São Paulo Guarulhos\",\n tz: \"America/Sao_Paulo\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"UUEE\",\n country: \"RU\",\n ghcnh_id: null,\n icao: \"UUEE\",\n latitude: 55.9728,\n longitude: 37.4147,\n name: \"Moscow Sheremetyevo\",\n tz: \"Europe/Moscow\",\n venues: [],\n },\n {\n code: \"UUWW\",\n country: \"RU\",\n ghcnh_id: null,\n icao: \"UUWW\",\n latitude: 55.5915,\n longitude: 37.2615,\n name: \"Moscow Vnukovo\",\n tz: \"Europe/Moscow\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"VABB\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VABB\",\n latitude: 19.0887,\n longitude: 72.8679,\n name: \"Mumbai Chhatrapati Shivaji\",\n tz: \"Asia/Kolkata\",\n venues: [],\n },\n {\n code: \"VHHH\",\n country: \"HK\",\n ghcnh_id: null,\n icao: \"VHHH\",\n latitude: 22.308,\n longitude: 113.9185,\n name: \"Hong Kong International\",\n tz: \"Asia/Hong_Kong\",\n venues: [],\n },\n {\n code: \"VIDP\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VIDP\",\n latitude: 28.5562,\n longitude: 77.1,\n name: \"Delhi Indira Gandhi\",\n tz: \"Asia/Kolkata\",\n venues: [],\n },\n {\n code: \"VILK\",\n country: \"IN\",\n ghcnh_id: null,\n icao: \"VILK\",\n latitude: 26.7606,\n longitude: 80.8893,\n name: \"Lucknow Chaudhary Charan Singh International\",\n tz: \"Asia/Kolkata\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"VTBS\",\n country: \"TH\",\n ghcnh_id: null,\n icao: \"VTBS\",\n latitude: 13.69,\n longitude: 100.7501,\n name: \"Bangkok Suvarnabhumi\",\n tz: \"Asia/Bangkok\",\n venues: [],\n },\n {\n code: \"WMKK\",\n country: \"MY\",\n ghcnh_id: null,\n icao: \"WMKK\",\n latitude: 2.7456,\n longitude: 101.7099,\n name: \"Kuala Lumpur International\",\n tz: \"Asia/Kuala_Lumpur\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"WSSS\",\n country: \"SG\",\n ghcnh_id: null,\n icao: \"WSSS\",\n latitude: 1.3644,\n longitude: 103.9915,\n name: \"Singapore Changi\",\n tz: \"Asia/Singapore\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"YBBN\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YBBN\",\n latitude: -27.3842,\n longitude: 153.1175,\n name: \"Brisbane\",\n tz: \"Australia/Brisbane\",\n venues: [],\n },\n {\n code: \"YMML\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YMML\",\n latitude: -37.6733,\n longitude: 144.8433,\n name: \"Melbourne Tullamarine\",\n tz: \"Australia/Melbourne\",\n venues: [],\n },\n {\n code: \"YSSY\",\n country: \"AU\",\n ghcnh_id: null,\n icao: \"YSSY\",\n latitude: -33.9461,\n longitude: 151.1772,\n name: \"Sydney Kingsford Smith\",\n tz: \"Australia/Sydney\",\n venues: [],\n },\n {\n code: \"ZBAA\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZBAA\",\n latitude: 40.0801,\n longitude: 116.5846,\n name: \"Beijing Capital\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZGGG\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZGGG\",\n latitude: 23.3924,\n longitude: 113.2988,\n name: \"Guangzhou Baiyun International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZGSZ\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZGSZ\",\n latitude: 22.6393,\n longitude: 113.8108,\n name: \"Shenzhen Bao'an International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZHCC\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZHCC\",\n latitude: 34.5197,\n longitude: 113.8408,\n name: \"Zhengzhou Xinzheng International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZHHH\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZHHH\",\n latitude: 30.7838,\n longitude: 114.2081,\n name: \"Wuhan Tianhe International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSJN\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSJN\",\n latitude: 36.8572,\n longitude: 117.2161,\n name: \"Jinan Yaoqiang International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSPD\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSPD\",\n latitude: 31.1443,\n longitude: 121.8083,\n name: \"Shanghai Pudong\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZSQD\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZSQD\",\n latitude: 36.3614,\n longitude: 120.0867,\n name: \"Qingdao Jiaodong International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZUCK\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZUCK\",\n latitude: 29.7192,\n longitude: 106.6417,\n name: \"Chongqing Jiangbei International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n {\n code: \"ZUUU\",\n country: \"CN\",\n ghcnh_id: null,\n icao: \"ZUUU\",\n latitude: 30.5785,\n longitude: 103.9471,\n name: \"Chengdu Shuangliu International\",\n tz: \"Asia/Shanghai\",\n venues: [\n \"polymarket\",\n ],\n },\n] as const;\n\nexport const STATION_BY_CODE: ReadonlyMap<string, StationInfo> = new Map<string, StationInfo>([\n [\"ATL\", STATIONS[13]!],\n [\"AUS\", STATIONS[14]!],\n [\"BKF\", STATIONS[15]!],\n [\"BNA\", STATIONS[16]!],\n [\"BOS\", STATIONS[17]!],\n [\"CVG\", STATIONS[18]!],\n [\"CYYZ\", STATIONS[0]!],\n [\"DAL\", STATIONS[19]!],\n [\"DCA\", STATIONS[20]!],\n [\"DEN\", STATIONS[21]!],\n [\"DFW\", STATIONS[22]!],\n [\"DTW\", STATIONS[23]!],\n [\"EDDB\", STATIONS[1]!],\n [\"EDDF\", STATIONS[2]!],\n [\"EDDM\", STATIONS[3]!],\n [\"EFHK\", STATIONS[4]!],\n [\"EGKK\", STATIONS[5]!],\n [\"EGLC\", STATIONS[6]!],\n [\"EGLL\", STATIONS[7]!],\n [\"EHAM\", STATIONS[8]!],\n [\"EKCH\", STATIONS[9]!],\n [\"EPWA\", STATIONS[10]!],\n [\"ESSA\", STATIONS[11]!],\n [\"FACT\", STATIONS[12]!],\n [\"HOU\", STATIONS[24]!],\n [\"IAH\", STATIONS[25]!],\n [\"LAS\", STATIONS[26]!],\n [\"LAX\", STATIONS[27]!],\n [\"LEBL\", STATIONS[42]!],\n [\"LEMD\", STATIONS[43]!],\n [\"LFPB\", STATIONS[44]!],\n [\"LFPG\", STATIONS[45]!],\n [\"LFPO\", STATIONS[46]!],\n [\"LGA\", STATIONS[28]!],\n [\"LIMC\", STATIONS[47]!],\n [\"LIRF\", STATIONS[48]!],\n [\"LLBG\", STATIONS[49]!],\n [\"LOWW\", STATIONS[50]!],\n [\"LSZH\", STATIONS[51]!],\n [\"LTAC\", STATIONS[52]!],\n [\"LTFM\", STATIONS[53]!],\n [\"MDW\", STATIONS[29]!],\n [\"MIA\", STATIONS[30]!],\n [\"MMMX\", STATIONS[54]!],\n [\"MPMG\", STATIONS[55]!],\n [\"MSP\", STATIONS[31]!],\n [\"MSY\", STATIONS[32]!],\n [\"NYC\", STATIONS[33]!],\n [\"NZAA\", STATIONS[56]!],\n [\"NZWN\", STATIONS[57]!],\n [\"OEJN\", STATIONS[58]!],\n [\"OERK\", STATIONS[59]!],\n [\"OKC\", STATIONS[34]!],\n [\"OMDB\", STATIONS[60]!],\n [\"OPKC\", STATIONS[61]!],\n [\"ORD\", STATIONS[35]!],\n [\"OTHH\", STATIONS[62]!],\n [\"PHL\", STATIONS[36]!],\n [\"PHX\", STATIONS[37]!],\n [\"RCSS\", STATIONS[63]!],\n [\"RCTP\", STATIONS[64]!],\n [\"RJAA\", STATIONS[65]!],\n [\"RJTT\", STATIONS[66]!],\n [\"RKPK\", STATIONS[67]!],\n [\"RKSI\", STATIONS[68]!],\n [\"RPLL\", STATIONS[69]!],\n [\"SAEZ\", STATIONS[70]!],\n [\"SAT\", STATIONS[38]!],\n [\"SBGR\", STATIONS[71]!],\n [\"SEA\", STATIONS[39]!],\n [\"SFO\", STATIONS[40]!],\n [\"SLC\", STATIONS[41]!],\n [\"UUEE\", STATIONS[72]!],\n [\"UUWW\", STATIONS[73]!],\n [\"VABB\", STATIONS[74]!],\n [\"VHHH\", STATIONS[75]!],\n [\"VIDP\", STATIONS[76]!],\n [\"VILK\", STATIONS[77]!],\n [\"VTBS\", STATIONS[78]!],\n [\"WMKK\", STATIONS[79]!],\n [\"WSSS\", STATIONS[80]!],\n [\"YBBN\", STATIONS[81]!],\n [\"YMML\", STATIONS[82]!],\n [\"YSSY\", STATIONS[83]!],\n [\"ZBAA\", STATIONS[84]!],\n [\"ZGGG\", STATIONS[85]!],\n [\"ZGSZ\", STATIONS[86]!],\n [\"ZHCC\", STATIONS[87]!],\n [\"ZHHH\", STATIONS[88]!],\n [\"ZSJN\", STATIONS[89]!],\n [\"ZSPD\", STATIONS[90]!],\n [\"ZSQD\", STATIONS[91]!],\n [\"ZUCK\", STATIONS[92]!],\n [\"ZUUU\", STATIONS[93]!],\n]);\n\nexport const STATION_BY_ICAO: ReadonlyMap<string, StationInfo> = new Map<string, StationInfo>([\n [\"CYYZ\", STATIONS[0]!],\n [\"EDDB\", STATIONS[1]!],\n [\"EDDF\", STATIONS[2]!],\n [\"EDDM\", STATIONS[3]!],\n [\"EFHK\", STATIONS[4]!],\n [\"EGKK\", STATIONS[5]!],\n [\"EGLC\", STATIONS[6]!],\n [\"EGLL\", STATIONS[7]!],\n [\"EHAM\", STATIONS[8]!],\n [\"EKCH\", STATIONS[9]!],\n [\"EPWA\", STATIONS[10]!],\n [\"ESSA\", STATIONS[11]!],\n [\"FACT\", STATIONS[12]!],\n [\"KATL\", STATIONS[13]!],\n [\"KAUS\", STATIONS[14]!],\n [\"KBKF\", STATIONS[15]!],\n [\"KBNA\", STATIONS[16]!],\n [\"KBOS\", STATIONS[17]!],\n [\"KCVG\", STATIONS[18]!],\n [\"KDAL\", STATIONS[19]!],\n [\"KDCA\", STATIONS[20]!],\n [\"KDEN\", STATIONS[21]!],\n [\"KDFW\", STATIONS[22]!],\n [\"KDTW\", STATIONS[23]!],\n [\"KHOU\", STATIONS[24]!],\n [\"KIAH\", STATIONS[25]!],\n [\"KLAS\", STATIONS[26]!],\n [\"KLAX\", STATIONS[27]!],\n [\"KLGA\", STATIONS[28]!],\n [\"KMDW\", STATIONS[29]!],\n [\"KMIA\", STATIONS[30]!],\n [\"KMSP\", STATIONS[31]!],\n [\"KMSY\", STATIONS[32]!],\n [\"KNYC\", STATIONS[33]!],\n [\"KOKC\", STATIONS[34]!],\n [\"KORD\", STATIONS[35]!],\n [\"KPHL\", STATIONS[36]!],\n [\"KPHX\", STATIONS[37]!],\n [\"KSAT\", STATIONS[38]!],\n [\"KSEA\", STATIONS[39]!],\n [\"KSFO\", STATIONS[40]!],\n [\"KSLC\", STATIONS[41]!],\n [\"LEBL\", STATIONS[42]!],\n [\"LEMD\", STATIONS[43]!],\n [\"LFPB\", STATIONS[44]!],\n [\"LFPG\", STATIONS[45]!],\n [\"LFPO\", STATIONS[46]!],\n [\"LIMC\", STATIONS[47]!],\n [\"LIRF\", STATIONS[48]!],\n [\"LLBG\", STATIONS[49]!],\n [\"LOWW\", STATIONS[50]!],\n [\"LSZH\", STATIONS[51]!],\n [\"LTAC\", STATIONS[52]!],\n [\"LTFM\", STATIONS[53]!],\n [\"MMMX\", STATIONS[54]!],\n [\"MPMG\", STATIONS[55]!],\n [\"NZAA\", STATIONS[56]!],\n [\"NZWN\", STATIONS[57]!],\n [\"OEJN\", STATIONS[58]!],\n [\"OERK\", STATIONS[59]!],\n [\"OMDB\", STATIONS[60]!],\n [\"OPKC\", STATIONS[61]!],\n [\"OTHH\", STATIONS[62]!],\n [\"RCSS\", STATIONS[63]!],\n [\"RCTP\", STATIONS[64]!],\n [\"RJAA\", STATIONS[65]!],\n [\"RJTT\", STATIONS[66]!],\n [\"RKPK\", STATIONS[67]!],\n [\"RKSI\", STATIONS[68]!],\n [\"RPLL\", STATIONS[69]!],\n [\"SAEZ\", STATIONS[70]!],\n [\"SBGR\", STATIONS[71]!],\n [\"UUEE\", STATIONS[72]!],\n [\"UUWW\", STATIONS[73]!],\n [\"VABB\", STATIONS[74]!],\n [\"VHHH\", STATIONS[75]!],\n [\"VIDP\", STATIONS[76]!],\n [\"VILK\", STATIONS[77]!],\n [\"VTBS\", STATIONS[78]!],\n [\"WMKK\", STATIONS[79]!],\n [\"WSSS\", STATIONS[80]!],\n [\"YBBN\", STATIONS[81]!],\n [\"YMML\", STATIONS[82]!],\n [\"YSSY\", STATIONS[83]!],\n [\"ZBAA\", STATIONS[84]!],\n [\"ZGGG\", STATIONS[85]!],\n [\"ZGSZ\", STATIONS[86]!],\n [\"ZHCC\", STATIONS[87]!],\n [\"ZHHH\", STATIONS[88]!],\n [\"ZSJN\", STATIONS[89]!],\n [\"ZSPD\", STATIONS[90]!],\n [\"ZSQD\", STATIONS[91]!],\n [\"ZUCK\", STATIONS[92]!],\n [\"ZUUU\", STATIONS[93]!],\n]);\n","// Snapshot math — settlement-window and market-close arithmetic.\n//\n// Ported from `packages/core/src/mostlyright/snapshot.py` and\n// `packages/core/src/mostlyright/_internal/_pairs.py:market_close_utc`.\n//\n// Key concepts:\n// - LOCAL STANDARD TIME (LST): station's standard UTC offset, DST ignored.\n// Kalshi NHIGH/NLOW contracts define the settlement window in LST.\n// - Settlement window: midnight-midnight LST for a given date.\n// During US daylight saving the clock window is 1:00 AM–1:00 AM next day\n// (EDT), but the UTC bounds are the same year-round.\n// - CLI publication delay: NWS issues the overnight final CLI ~04:00–10:00\n// UTC the day after observation. Default: 10 h after midnight LST.\n\n// ---------------------------------------------------------------------------\n// Station → IANA timezone database\n// ---------------------------------------------------------------------------\n//\n// Used to extract the LOCAL STANDARD TIME UTC offset via a January reference\n// moment. Ported from `mostlyright.snapshot._STATION_TZ`.\n\nexport const _STATION_TZ: Readonly<Record<string, string>> = Object.freeze({\n // Eastern (UTC-5 standard / UTC-4 DST)\n NYC: \"America/New_York\",\n JFK: \"America/New_York\",\n LGA: \"America/New_York\",\n EWR: \"America/New_York\",\n ATL: \"America/New_York\",\n BOS: \"America/New_York\",\n PHL: \"America/New_York\",\n DCA: \"America/New_York\",\n IAD: \"America/New_York\",\n BWI: \"America/New_York\",\n MIA: \"America/New_York\",\n MCO: \"America/New_York\",\n TPA: \"America/New_York\",\n CLT: \"America/New_York\",\n RDU: \"America/New_York\",\n CLE: \"America/New_York\",\n PIT: \"America/New_York\",\n BUF: \"America/New_York\",\n DTW: \"America/Detroit\",\n IND: \"America/Indiana/Indianapolis\",\n CVG: \"America/New_York\",\n CMH: \"America/New_York\",\n SYR: \"America/New_York\",\n ALB: \"America/New_York\",\n BTV: \"America/New_York\",\n ORF: \"America/New_York\",\n RIC: \"America/New_York\",\n GSO: \"America/New_York\",\n CHS: \"America/New_York\",\n SAV: \"America/New_York\",\n JAX: \"America/New_York\",\n RSW: \"America/New_York\",\n PBI: \"America/New_York\",\n FLL: \"America/New_York\",\n // Central (UTC-6 standard / UTC-5 DST)\n ORD: \"America/Chicago\",\n MDW: \"America/Chicago\",\n DFW: \"America/Chicago\",\n DAL: \"America/Chicago\",\n IAH: \"America/Chicago\",\n HOU: \"America/Chicago\",\n MSP: \"America/Chicago\",\n STL: \"America/Chicago\",\n MCI: \"America/Chicago\",\n OMA: \"America/Chicago\",\n MKE: \"America/Chicago\",\n MSY: \"America/Chicago\",\n MEM: \"America/Chicago\",\n BNA: \"America/Chicago\",\n OKC: \"America/Chicago\",\n SAT: \"America/Chicago\",\n AUS: \"America/Chicago\",\n DSM: \"America/Chicago\",\n TUL: \"America/Chicago\",\n LIT: \"America/Chicago\",\n BIR: \"America/Chicago\",\n SDF: \"America/Chicago\",\n HSV: \"America/Chicago\",\n BHM: \"America/Chicago\",\n MOB: \"America/Chicago\",\n BTR: \"America/Chicago\",\n SHV: \"America/Chicago\",\n // Mountain (UTC-7 standard / UTC-6 DST)\n DEN: \"America/Denver\",\n SLC: \"America/Denver\",\n ABQ: \"America/Denver\",\n BOI: \"America/Boise\",\n BZN: \"America/Denver\",\n GJT: \"America/Denver\",\n // Arizona: no DST (UTC-7 always)\n PHX: \"America/Phoenix\",\n TUS: \"America/Phoenix\",\n // Pacific (UTC-8 standard / UTC-7 DST)\n LAX: \"America/Los_Angeles\",\n SFO: \"America/Los_Angeles\",\n SEA: \"America/Los_Angeles\",\n PDX: \"America/Los_Angeles\",\n LAS: \"America/Los_Angeles\",\n SAN: \"America/Los_Angeles\",\n OAK: \"America/Los_Angeles\",\n SJC: \"America/Los_Angeles\",\n SMF: \"America/Los_Angeles\",\n RNO: \"America/Los_Angeles\",\n FAT: \"America/Los_Angeles\",\n SNA: \"America/Los_Angeles\",\n ONT: \"America/Los_Angeles\",\n BUR: \"America/Los_Angeles\",\n // Alaska (UTC-9 standard / UTC-8 DST)\n ANC: \"America/Anchorage\",\n FAI: \"America/Anchorage\",\n JNU: \"America/Juneau\",\n // Hawaii (UTC-10, no DST)\n HNL: \"Pacific/Honolulu\",\n OGG: \"Pacific/Honolulu\",\n KOA: \"Pacific/Honolulu\",\n // International (iter-6 H12): minimal set required to un-skip the\n // case-5 RJTT year-wrap cache behavior test. Python's\n // `mostlyright.snapshot._resolve_tz` falls back to the broader STATIONS\n // registry for intl ICAOs; the TS port hasn't ported that fallback\n // yet (tracked as TS-W6 — exhaustive intl-station tz coverage). This\n // entry closes H12 cleanly without pulling the whole STATIONS map in.\n // ICAO key (RJTT) — international stations have no 3-letter NWS code.\n // Tokyo Haneda — UTC+9 LST, no DST.\n RJTT: \"Asia/Tokyo\",\n});\n\n/** Reference UTC moment in January (no DST in Northern Hemisphere US). */\nexport const _JAN_REF = new Date(Date.UTC(2024, 0, 15, 12, 0, 0));\n\n/** NWS CLI typical publication delay: 10 h after midnight LST. */\nexport const _CLI_PUBLICATION_DELAY_HOURS = 10.0;\n\n/** Kalshi market typical close time (LST). */\nexport const _MARKET_CLOSE_HOUR_LST = 16;\nexport const _MARKET_CLOSE_MINUTE_LST = 30;\n\n// ---------------------------------------------------------------------------\n// LST offset extraction\n// ---------------------------------------------------------------------------\n\nconst _OFFSET_CACHE = new Map<string, number>();\n\n/**\n * Return the LOCAL STANDARD TIME UTC offset (in hours) for an IANA tz,\n * sampled from January 15 2024 12:00 UTC so the result is never affected\n * by DST in the Northern Hemisphere.\n *\n * Implementation: format `_JAN_REF` in the target tz via Intl.DateTimeFormat\n * and diff against the UTC formatted view to recover the offset.\n */\nexport function _lstOffsetHours(stationTz: string): number {\n const cached = _OFFSET_CACHE.get(stationTz);\n if (cached !== undefined) return cached;\n\n // We compute: localComponents(stationTz, _JAN_REF) − utcComponents(_JAN_REF).\n // The difference gives the tz offset in (hours). Negative for west of UTC.\n const fmt = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: stationTz,\n hour12: false,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n const parts = fmt.formatToParts(_JAN_REF);\n const get = (type: string): number => {\n const part = parts.find((p) => p.type === type);\n if (!part) {\n throw new Error(`Intl.DateTimeFormat missing ${type} for tz=${stationTz}`);\n }\n return Number(part.value);\n };\n\n const year = get(\"year\");\n const month = get(\"month\");\n const day = get(\"day\");\n let hour = get(\"hour\");\n const minute = get(\"minute\");\n const second = get(\"second\");\n // Some locales return hour \"24\" instead of \"00\" for midnight; normalize.\n if (hour === 24) hour = 0;\n\n // Compute the timezone's wall-clock for _JAN_REF treated as UTC.\n const localAsUtc = Date.UTC(year, month - 1, day, hour, minute, second);\n const offsetMs = localAsUtc - _JAN_REF.getTime();\n const offsetHours = offsetMs / 3_600_000;\n _OFFSET_CACHE.set(stationTz, offsetHours);\n return offsetHours;\n}\n\n// ---------------------------------------------------------------------------\n// Station code normalization + tz lookup\n// ---------------------------------------------------------------------------\n\nfunction _stationCodeNormalized(station: string): string {\n const s = station.trim().toUpperCase();\n if (s.length === 4 && s.startsWith(\"K\")) {\n return s.substring(1);\n }\n return s;\n}\n\n/**\n * Resolve a station code (NWS 3-letter, ICAO 4-letter) to an IANA tz string.\n * Honors `tzOverride` first, then the built-in `_STATION_TZ` map.\n * Throws if no tz can be resolved.\n */\nexport function _resolveStationTz(station: string, tzOverride?: string): string {\n if (tzOverride) return tzOverride;\n const code = _stationCodeNormalized(station);\n const tz = _STATION_TZ[code];\n if (tz) return tz;\n throw new Error(\n `Unknown station timezone: ${JSON.stringify(code)}. Add it to _STATION_TZ or pass tzOverride=\"America/...\".`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// as_of parsing\n// ---------------------------------------------------------------------------\n\nfunction _parseAsOf(asOf: Date | string): Date {\n if (asOf instanceof Date) {\n if (Number.isNaN(asOf.getTime())) {\n throw new Error(\"Invalid Date passed as asOf\");\n }\n return asOf;\n }\n let s = asOf.trim();\n // Python: bare ISO without tz → assume UTC.\n if (s.endsWith(\"Z\")) {\n // Date.parse handles \"Z\" natively.\n } else if (!/[+-]\\d{2}:?\\d{2}$/.test(s)) {\n // No timezone suffix — treat as UTC.\n s = `${s}Z`;\n }\n const ms = Date.parse(s);\n if (!Number.isFinite(ms)) {\n throw new Error(`Invalid as_of string: ${JSON.stringify(asOf)}`);\n }\n return new Date(ms);\n}\n\n// ---------------------------------------------------------------------------\n// Public surface\n// ---------------------------------------------------------------------------\n\nfunction _pad2(n: number): string {\n return n < 10 ? `0${n}` : `${n}`;\n}\n\nfunction _isoDate(year: number, month: number, day: number): string {\n return `${year}-${_pad2(month)}-${_pad2(day)}`;\n}\n\n/**\n * Return the Kalshi settlement date (YYYY-MM-DD LST) for a UTC moment.\n *\n * Kalshi NHIGH/NLOW contracts cover midnight–midnight LOCAL STANDARD TIME.\n * DST is ignored: the window is always fixed to the standard UTC offset.\n */\nexport function settlementDateFor(\n asOf: Date | string,\n station: string,\n tzOverride?: string,\n): string {\n const utcDt = _parseAsOf(asOf);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n // offsetHours is negative for US stations → lstMs < utcMs.\n const lstMs = utcDt.getTime() + offsetHours * 3_600_000;\n const lst = new Date(lstMs);\n // Use getUTC* because we already shifted the epoch by the LST offset.\n return _isoDate(lst.getUTCFullYear(), lst.getUTCMonth() + 1, lst.getUTCDate());\n}\n\n/**\n * Return UTC start/end of the Kalshi settlement window for a date.\n * The window is midnight-midnight LST, expressed in UTC.\n */\nexport function settlementWindowUtc(\n dateStr: string,\n station: string,\n tzOverride?: string,\n): [Date, Date] {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(dateStr);\n if (!match) {\n throw new Error(`Invalid ISO date for settlement window: ${JSON.stringify(dateStr)}`);\n }\n const [, yStr, mStr, dStr] = match;\n const year = Number(yStr);\n const month = Number(mStr);\n const day = Number(dStr);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n\n // midnight LST = 00:00 LST = (00:00 UTC) − offset (offset is negative)\n // Example: UTC-5 → midnight LST = 05:00 UTC.\n const midnightLstAsUtcMs = Date.UTC(year, month - 1, day, 0, 0, 0);\n const startMs = midnightLstAsUtcMs - offsetHours * 3_600_000;\n const start = new Date(startMs);\n const end = new Date(startMs + 24 * 3_600_000);\n return [start, end];\n}\n\n/**\n * Return the UTC time at which the NWS CLI for a date is expected to be\n * available. Default delay is 10 h after midnight LST on the next day.\n */\nexport function cliAvailableAt(\n dateStr: string,\n station: string,\n delayHours: number = _CLI_PUBLICATION_DELAY_HOURS,\n tzOverride?: string,\n): Date {\n const [, windowEnd] = settlementWindowUtc(dateStr, station, tzOverride);\n return new Date(windowEnd.getTime() + delayHours * 3_600_000);\n}\n\n/**\n * Return the UTC time of the Kalshi market close for a settlement date.\n * Kalshi NHIGH/NLOW markets close at 4:30 PM LST on the day of settlement.\n */\nexport function marketCloseUtc(dateStr: string, station: string, tzOverride?: string): Date {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(dateStr);\n if (!match) {\n throw new Error(`Invalid ISO date for market close: ${JSON.stringify(dateStr)}`);\n }\n const [, yStr, mStr, dStr] = match;\n const year = Number(yStr);\n const month = Number(mStr);\n const day = Number(dStr);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n\n const marketCloseAsUtcMs = Date.UTC(\n year,\n month - 1,\n day,\n _MARKET_CLOSE_HOUR_LST,\n _MARKET_CLOSE_MINUTE_LST,\n 0,\n );\n return new Date(marketCloseAsUtcMs - offsetHours * 3_600_000);\n}\n","// Cache-skip rule predicates — pure functions over inputs.\n//\n// Mirrors `packages/weather/src/mostlyright/weather/cache.py`:\n// - `_is_current_lst_month` / `_is_current_lst_year`\n// - `_is_live_source`\n//\n// Plus one TS-NEW addition required by TS-CACHE-02:\n// - `isWithinVolatileWindow` (30-day volatile-window check for archive\n// endpoints). Python's `cache.py` predates this rule; back-porting to\n// Python is tracked as a CROSS-SDK-SYNC parity ticket.\n//\n// All functions accept an optional `now: Date` test seam — production\n// callers pass `new Date()` once at the call site (plan 06).\n\nimport { STATION_BY_CODE, STATION_BY_ICAO } from \"../../data/generated/stations.js\";\nimport { _lstOffsetHours } from \"../../snapshot.js\";\n\n/** Resolve a station identifier (3-letter code OR 4-letter ICAO) to LST offset hours. */\nfunction _lstOffsetHoursFor(station: string): number {\n const upper = station.trim().toUpperCase();\n const byCode = STATION_BY_CODE.get(upper);\n if (byCode !== undefined) return _lstOffsetHours(byCode.tz);\n const byIcao = STATION_BY_ICAO.get(upper);\n if (byIcao !== undefined) return _lstOffsetHours(byIcao.tz);\n if (upper.length === 4 && upper.startsWith(\"K\")) {\n const stripped = upper.slice(1);\n const retry = STATION_BY_CODE.get(stripped);\n if (retry !== undefined) return _lstOffsetHours(retry.tz);\n }\n throw new RangeError(`unknown station: ${JSON.stringify(station)}`);\n}\n\n/**\n * Compute the station's current LST wall-clock as a UTC Date offset by the\n * LST hour shift. Use `getUTC*` to read fields (we already shifted the epoch).\n */\nfunction _nowLst(station: string, now: Date = new Date()): Date {\n const offsetHours = _lstOffsetHoursFor(station);\n return new Date(now.getTime() + offsetHours * 3_600_000);\n}\n\n/**\n * True iff `(year, month)` is the current LST month for `station`.\n *\n * Mirrors Python `_is_current_lst_month`. The current month is mutable\n * (observations still arriving) — caching it would serve stale data.\n */\nexport function shouldSkipCacheForCurrentLstMonth(\n station: string,\n year: number,\n month: number,\n now?: Date,\n): boolean {\n const lst = _nowLst(station, now);\n return lst.getUTCFullYear() === year && lst.getUTCMonth() + 1 === month;\n}\n\n/**\n * True iff `year` is the current LST year for `station`. Annual analog of\n * the monthly variant — gates the climate cache.\n */\nexport function shouldSkipCacheForCurrentLstYear(\n station: string,\n year: number,\n now?: Date,\n): boolean {\n const lst = _nowLst(station, now);\n return lst.getUTCFullYear() === year;\n}\n\n/**\n * True iff `(year, month)` is a **strictly past** UTC month relative to\n * `now` — i.e. cacheable on the strictest possible temporal axis.\n *\n * iter-12 C14: `shouldSkipCacheForCurrentLstMonth` and `isMonthVolatile`\n * (lives in `meta/src/research.ts`) only catch the *current* LST month\n * and the immediate post-month volatile tail. Both predicates return\n * false for months that lie in the FUTURE relative to `now`, or for the\n * current UTC month when the station's LST is still in the prior UTC\n * month (negative tz offsets near UTC midnight). An empty / partial\n * fetch for such a month would be persisted and later served as\n * \"complete.\" `isWritableMonth` is a stricter additional gate: it\n * requires the (year, month) to be lexicographically less than the\n * UTC current month, so neither future months nor the partial current\n * UTC month are ever cacheable — regardless of any station's LST.\n *\n * Mirrors Python `cache.py:_is_current_lst_month`'s implicit invariant\n * (Python paths use parquet-on-disk which can't be written for future\n * dates because the cache root never spawns those years). TS callers\n * MUST gate cache reads AND writes on this predicate before applying\n * the LST / volatile-window gates.\n */\nexport function isWritableMonth(year: number, month: number, now: Date): boolean {\n const nowYear = now.getUTCFullYear();\n const nowMonth = now.getUTCMonth() + 1; // 1-12\n if (year < nowYear) return true;\n if (year > nowYear) return false;\n return month < nowMonth;\n}\n\n/**\n * True iff `year` is a **strictly past** UTC year relative to `now` —\n * the annual analog of `isWritableMonth`.\n *\n * iter-12 C15: `shouldSkipCacheForCurrentLstYear` only catches the\n * current LST year. It misses (a) future years, which would silently\n * cache empty/incomplete data, and (b) the UTC Jan-1 boundary window\n * where the station's LST is still in the prior calendar year (negative\n * tz offsets) but the UTC year has already rolled over — without this\n * gate the new UTC year, which is mutable, could be written. Stricter\n * additional gate: require `year < now.getUTCFullYear()`. TS callers\n * MUST gate cache reads AND writes on this predicate before applying\n * the LST / volatile-window gates.\n */\nexport function isWritableYear(year: number, now: Date): boolean {\n return year < now.getUTCFullYear();\n}\n\n/**\n * True iff `source` ends with `.live`.\n *\n * Mirrors Python `_is_live_source` byte-equivalently — accepts null /\n * undefined / empty (returns false in all three cases).\n */\nexport function isLiveSource(source: string | null | undefined): boolean {\n return typeof source === \"string\" && source.length > 0 && source.endsWith(\".live\");\n}\n\n/**\n * **TS-NEW** addition per TS-CACHE-02: archive endpoints within `days` days\n * of `archiveAsOf` are treated as volatile (some sources amend their\n * published data for ~30 days post-event). NOT a Python port today — file\n * a CROSS-SDK-SYNC parity ticket if Python adopts it.\n *\n * Returns true iff `eventDate` falls within `[archiveAsOf - days, archiveAsOf]`\n * (inclusive at both endpoints — an event exactly `days` days before\n * `archiveAsOf` is still volatile and MUST be re-fetched).\n *\n * Events AFTER `archiveAsOf` are never volatile by this rule (deltaDays < 0).\n */\nexport function isWithinVolatileWindow(eventDate: string, archiveAsOf: string, days = 30): boolean {\n const e = Date.parse(`${eventDate}T00:00:00Z`);\n const a = Date.parse(`${archiveAsOf}T00:00:00Z`);\n if (!Number.isFinite(e) || !Number.isFinite(a)) {\n throw new RangeError(\n `invalid YYYY-MM-DD: eventDate=${JSON.stringify(eventDate)} archiveAsOf=${JSON.stringify(archiveAsOf)}`,\n );\n }\n const deltaDays = (a - e) / 86_400_000;\n return deltaDays >= 0 && deltaDays <= days;\n}\n","// Cache-key generators — pure functions producing the canonical key strings\n// `cacheKeyForObservations(station, year, month)` and\n// `cacheKeyForClimate(station, year)`. Matches the Python file-path zero-\n// padded month/year scheme (Python: `01.parquet`, TS: `:01`).\n//\n// Station is upper-cased but NOT validated here (validation is the\n// orchestrator's job; this module stays pure).\n//\n// iter-7 H13 / H14: an optional `source` segment was added to\n// `cacheKeyForObservations` so the multi-source observations cache\n// (IEM ASOS + GHCNh) does not collide on the same `(station, year, month)`\n// triplet. Python writes one parquet per month containing the merged\n// observations from all sources; the TS orchestrator caches per-source\n// chunks pre-merge (because the fetchers are independent paths) and\n// disambiguates with the source segment. Omitting `source` preserves the\n// legacy 3-arg key shape — useful for tests that don't care about source\n// (sentinel preloads, fixture replays) and matches the canonical\n// Python-parity contract for callers that have already pre-merged sources.\n\nconst MIN_YEAR = 1900;\nconst MAX_YEAR = 2100;\nconst SOURCE_RE = /^[a-z0-9_-]+$/;\n\n/**\n * Build the canonical observations cache key.\n *\n * Examples:\n * `cacheKeyForObservations(\"KNYC\", 2025, 1)` →\n * `\"mostlyright:v1:observations:KNYC:2025:01\"`.\n * `cacheKeyForObservations(\"KNYC\", 2025, 1, \"iem\")` →\n * `\"mostlyright:v1:observations:KNYC:2025:01:iem\"`.\n *\n * The `source` segment (optional, lowercase alphanumeric / hyphen /\n * underscore) namespaces per-source pre-merge chunks so IEM ASOS and\n * GHCNh writes for the same `(station, year, month)` do not collide.\n * Omit for back-compat (sentinel preloads, fixture replays).\n */\nexport function cacheKeyForObservations(\n station: string,\n year: number,\n month: number,\n source?: string,\n): string {\n if (!Number.isInteger(year) || year < MIN_YEAR || year > MAX_YEAR) {\n throw new RangeError(`year out of range: ${year}`);\n }\n if (!Number.isInteger(month) || month < 1 || month > 12) {\n throw new RangeError(`month out of range: ${month}`);\n }\n const yyyy = String(year).padStart(4, \"0\");\n const mm = String(month).padStart(2, \"0\");\n const base = `mostlyright:v1:observations:${station.toUpperCase()}:${yyyy}:${mm}`;\n if (source === undefined) return base;\n if (typeof source !== \"string\" || !SOURCE_RE.test(source)) {\n throw new RangeError(\n `source must match ${SOURCE_RE.source} (lowercase alnum / hyphen / underscore); got ${JSON.stringify(source)}`,\n );\n }\n return `${base}:${source}`;\n}\n\n/**\n * Build the canonical climate cache key (annual).\n *\n * Example: `cacheKeyForClimate(\"KNYC\", 2025)` →\n * `\"mostlyright:v1:climate:KNYC:2025\"`.\n */\nexport function cacheKeyForClimate(station: string, year: number): string {\n if (!Number.isInteger(year) || year < MIN_YEAR || year > MAX_YEAR) {\n throw new RangeError(`year out of range: ${year}`);\n }\n const yyyy = String(year).padStart(4, \"0\");\n return `mostlyright:v1:climate:${station.toUpperCase()}:${yyyy}`;\n}\n","// Browser/MV3 entry for @mostlyrightmd/core/internal/cache.\n//\n// Iter-8 H15: the iter-1/iter-2 fixes (dynamic `await import('./fs.js')`\n// behind a runtime feature-detect + dropping the FsStore re-export from\n// the barrel) eliminated STATIC references to FsStore from the cache\n// subbundle but NOT dynamic ones. esbuild, when bundling for browser/MV3\n// targets, still follows `await import(\"./fs.js\")` from `default.ts`\n// into the FsStore chunk and pulls `node:crypto`, `node:fs/promises`,\n// `node:os`, `node:path`, and `proper-lockfile` into the bundle —\n// breaking `pnpm size` for `packages-ts/meta/dist/index.mjs`.\n//\n// The architectural fix: package.json conditional exports route Node\n// consumers to `./index.ts` (this file's sibling — keeps FsStore via\n// dynamic import) and browser/MV3 consumers to THIS file. This file has\n// NO reference to `./fs.js` via ANY mechanism — static import, dynamic\n// import, or re-export. esbuild cannot follow what isn't there.\n//\n// Exports MUST mirror `index.ts` exactly, MINUS anything that references\n// FsStore. The runtime priority for `defaultCacheStore` here is:\n// 1. `typeof indexedDB !== \"undefined\"` → IndexedDBStore.\n// 2. else → MemoryStore.\n// (The FsStore branch is unreachable in browser/MV3 bundles by\n// construction — there's no `process.versions?.node` in a service\n// worker anyway, but more importantly the source code is absent so\n// esbuild's static analysis cannot drag the Node-only chunk in.)\n\nimport { IndexedDBStore } from \"./indexeddb.js\";\nimport { MemoryStore } from \"./memory.js\";\nimport { CACHE_SCHEMA_VERSION, type CacheStore } from \"./types.js\";\nimport { versionedCacheStore } from \"./versionedCacheStore.js\";\n\nexport type { CacheStore, CacheSetOptions, CacheEntry } from \"./types.js\";\nexport { lockKeyFor } from \"./types.js\";\nexport { MemoryStore } from \"./memory.js\";\nexport { IndexedDBStore, DB_NAME as INDEXEDDB_DB_NAME } from \"./indexeddb.js\";\nexport type { IndexedDBStoreOptions } from \"./indexeddb.js\";\nexport {\n shouldSkipCacheForCurrentLstMonth,\n shouldSkipCacheForCurrentLstYear,\n isLiveSource,\n isWithinVolatileWindow,\n isWritableMonth,\n isWritableYear,\n} from \"./skip-rules.js\";\nexport { cacheKeyForObservations, cacheKeyForClimate } from \"./keys.js\";\n// Phase 21 21-03 fix-iter-2 (ts-architect CRITICAL): browser entry must\n// also re-export the version adapter + canonical version constant so\n// downstream code that imports them via this path resolves to the same\n// symbols as the Node entry.\nexport {\n versionedCacheStore,\n CACHE_SCHEMA_VERSION as VERSIONED_CACHE_SCHEMA_VERSION,\n} from \"./versionedCacheStore.js\";\nexport { CACHE_SCHEMA_VERSION } from \"./types.js\";\n\n/**\n * Browser/MV3 variant of {@link defaultCacheStore}. Auto-detects the best\n * available CacheStore in a browser/edge environment:\n *\n * 1. IndexedDB present → {@link IndexedDBStore}\n * 2. else → {@link MemoryStore}\n *\n * Phase 21 21-03 fix-iter-2 (ts-architect CRITICAL): the concrete store\n * is wrapped in `versionedCacheStore(CACHE_SCHEMA_VERSION)` so pre-\n * Phase-18 entries silently miss. The Node entry already does this; the\n * browser entry was missed in iter-1 — browser/MV3 consumers were still\n * served stale cache rows. Fix is symmetric.\n *\n * Returns a NEW instance per call.\n *\n * Iter-8 H15: kept async so the signature matches the Node entry's\n * `defaultCacheStore` (which awaits a dynamic import). Callers that\n * `await defaultCacheStore()` work unchanged when the package.json\n * conditional exports flip them between entries.\n */\nexport async function defaultCacheStore(): Promise<CacheStore> {\n const inner = typeof indexedDB !== \"undefined\" ? new IndexedDBStore() : new MemoryStore();\n return versionedCacheStore(inner, CACHE_SCHEMA_VERSION);\n}\n","// Snapshot math — settlement-window and market-close arithmetic.\n//\n// Ported from `packages/core/src/mostlyright/snapshot.py` and\n// `packages/core/src/mostlyright/_internal/_pairs.py:market_close_utc`.\n//\n// Key concepts:\n// - LOCAL STANDARD TIME (LST): station's standard UTC offset, DST ignored.\n// Kalshi NHIGH/NLOW contracts define the settlement window in LST.\n// - Settlement window: midnight-midnight LST for a given date.\n// During US daylight saving the clock window is 1:00 AM–1:00 AM next day\n// (EDT), but the UTC bounds are the same year-round.\n// - CLI publication delay: NWS issues the overnight final CLI ~04:00–10:00\n// UTC the day after observation. Default: 10 h after midnight LST.\n\n// ---------------------------------------------------------------------------\n// Station → IANA timezone database\n// ---------------------------------------------------------------------------\n//\n// Used to extract the LOCAL STANDARD TIME UTC offset via a January reference\n// moment. Ported from `mostlyright.snapshot._STATION_TZ`.\n\nexport const _STATION_TZ: Readonly<Record<string, string>> = Object.freeze({\n // Eastern (UTC-5 standard / UTC-4 DST)\n NYC: \"America/New_York\",\n JFK: \"America/New_York\",\n LGA: \"America/New_York\",\n EWR: \"America/New_York\",\n ATL: \"America/New_York\",\n BOS: \"America/New_York\",\n PHL: \"America/New_York\",\n DCA: \"America/New_York\",\n IAD: \"America/New_York\",\n BWI: \"America/New_York\",\n MIA: \"America/New_York\",\n MCO: \"America/New_York\",\n TPA: \"America/New_York\",\n CLT: \"America/New_York\",\n RDU: \"America/New_York\",\n CLE: \"America/New_York\",\n PIT: \"America/New_York\",\n BUF: \"America/New_York\",\n DTW: \"America/Detroit\",\n IND: \"America/Indiana/Indianapolis\",\n CVG: \"America/New_York\",\n CMH: \"America/New_York\",\n SYR: \"America/New_York\",\n ALB: \"America/New_York\",\n BTV: \"America/New_York\",\n ORF: \"America/New_York\",\n RIC: \"America/New_York\",\n GSO: \"America/New_York\",\n CHS: \"America/New_York\",\n SAV: \"America/New_York\",\n JAX: \"America/New_York\",\n RSW: \"America/New_York\",\n PBI: \"America/New_York\",\n FLL: \"America/New_York\",\n // Central (UTC-6 standard / UTC-5 DST)\n ORD: \"America/Chicago\",\n MDW: \"America/Chicago\",\n DFW: \"America/Chicago\",\n DAL: \"America/Chicago\",\n IAH: \"America/Chicago\",\n HOU: \"America/Chicago\",\n MSP: \"America/Chicago\",\n STL: \"America/Chicago\",\n MCI: \"America/Chicago\",\n OMA: \"America/Chicago\",\n MKE: \"America/Chicago\",\n MSY: \"America/Chicago\",\n MEM: \"America/Chicago\",\n BNA: \"America/Chicago\",\n OKC: \"America/Chicago\",\n SAT: \"America/Chicago\",\n AUS: \"America/Chicago\",\n DSM: \"America/Chicago\",\n TUL: \"America/Chicago\",\n LIT: \"America/Chicago\",\n BIR: \"America/Chicago\",\n SDF: \"America/Chicago\",\n HSV: \"America/Chicago\",\n BHM: \"America/Chicago\",\n MOB: \"America/Chicago\",\n BTR: \"America/Chicago\",\n SHV: \"America/Chicago\",\n // Mountain (UTC-7 standard / UTC-6 DST)\n DEN: \"America/Denver\",\n SLC: \"America/Denver\",\n ABQ: \"America/Denver\",\n BOI: \"America/Boise\",\n BZN: \"America/Denver\",\n GJT: \"America/Denver\",\n // Arizona: no DST (UTC-7 always)\n PHX: \"America/Phoenix\",\n TUS: \"America/Phoenix\",\n // Pacific (UTC-8 standard / UTC-7 DST)\n LAX: \"America/Los_Angeles\",\n SFO: \"America/Los_Angeles\",\n SEA: \"America/Los_Angeles\",\n PDX: \"America/Los_Angeles\",\n LAS: \"America/Los_Angeles\",\n SAN: \"America/Los_Angeles\",\n OAK: \"America/Los_Angeles\",\n SJC: \"America/Los_Angeles\",\n SMF: \"America/Los_Angeles\",\n RNO: \"America/Los_Angeles\",\n FAT: \"America/Los_Angeles\",\n SNA: \"America/Los_Angeles\",\n ONT: \"America/Los_Angeles\",\n BUR: \"America/Los_Angeles\",\n // Alaska (UTC-9 standard / UTC-8 DST)\n ANC: \"America/Anchorage\",\n FAI: \"America/Anchorage\",\n JNU: \"America/Juneau\",\n // Hawaii (UTC-10, no DST)\n HNL: \"Pacific/Honolulu\",\n OGG: \"Pacific/Honolulu\",\n KOA: \"Pacific/Honolulu\",\n // International (iter-6 H12): minimal set required to un-skip the\n // case-5 RJTT year-wrap cache behavior test. Python's\n // `mostlyright.snapshot._resolve_tz` falls back to the broader STATIONS\n // registry for intl ICAOs; the TS port hasn't ported that fallback\n // yet (tracked as TS-W6 — exhaustive intl-station tz coverage). This\n // entry closes H12 cleanly without pulling the whole STATIONS map in.\n // ICAO key (RJTT) — international stations have no 3-letter NWS code.\n // Tokyo Haneda — UTC+9 LST, no DST.\n RJTT: \"Asia/Tokyo\",\n});\n\n/** Reference UTC moment in January (no DST in Northern Hemisphere US). */\nexport const _JAN_REF = new Date(Date.UTC(2024, 0, 15, 12, 0, 0));\n\n/** NWS CLI typical publication delay: 10 h after midnight LST. */\nexport const _CLI_PUBLICATION_DELAY_HOURS = 10.0;\n\n/** Kalshi market typical close time (LST). */\nexport const _MARKET_CLOSE_HOUR_LST = 16;\nexport const _MARKET_CLOSE_MINUTE_LST = 30;\n\n// ---------------------------------------------------------------------------\n// LST offset extraction\n// ---------------------------------------------------------------------------\n\nconst _OFFSET_CACHE = new Map<string, number>();\n\n/**\n * Return the LOCAL STANDARD TIME UTC offset (in hours) for an IANA tz,\n * sampled from January 15 2024 12:00 UTC so the result is never affected\n * by DST in the Northern Hemisphere.\n *\n * Implementation: format `_JAN_REF` in the target tz via Intl.DateTimeFormat\n * and diff against the UTC formatted view to recover the offset.\n */\nexport function _lstOffsetHours(stationTz: string): number {\n const cached = _OFFSET_CACHE.get(stationTz);\n if (cached !== undefined) return cached;\n\n // We compute: localComponents(stationTz, _JAN_REF) − utcComponents(_JAN_REF).\n // The difference gives the tz offset in (hours). Negative for west of UTC.\n const fmt = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: stationTz,\n hour12: false,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n const parts = fmt.formatToParts(_JAN_REF);\n const get = (type: string): number => {\n const part = parts.find((p) => p.type === type);\n if (!part) {\n throw new Error(`Intl.DateTimeFormat missing ${type} for tz=${stationTz}`);\n }\n return Number(part.value);\n };\n\n const year = get(\"year\");\n const month = get(\"month\");\n const day = get(\"day\");\n let hour = get(\"hour\");\n const minute = get(\"minute\");\n const second = get(\"second\");\n // Some locales return hour \"24\" instead of \"00\" for midnight; normalize.\n if (hour === 24) hour = 0;\n\n // Compute the timezone's wall-clock for _JAN_REF treated as UTC.\n const localAsUtc = Date.UTC(year, month - 1, day, hour, minute, second);\n const offsetMs = localAsUtc - _JAN_REF.getTime();\n const offsetHours = offsetMs / 3_600_000;\n _OFFSET_CACHE.set(stationTz, offsetHours);\n return offsetHours;\n}\n\n// ---------------------------------------------------------------------------\n// Station code normalization + tz lookup\n// ---------------------------------------------------------------------------\n\nfunction _stationCodeNormalized(station: string): string {\n const s = station.trim().toUpperCase();\n if (s.length === 4 && s.startsWith(\"K\")) {\n return s.substring(1);\n }\n return s;\n}\n\n/**\n * Resolve a station code (NWS 3-letter, ICAO 4-letter) to an IANA tz string.\n * Honors `tzOverride` first, then the built-in `_STATION_TZ` map.\n * Throws if no tz can be resolved.\n */\nexport function _resolveStationTz(station: string, tzOverride?: string): string {\n if (tzOverride) return tzOverride;\n const code = _stationCodeNormalized(station);\n const tz = _STATION_TZ[code];\n if (tz) return tz;\n throw new Error(\n `Unknown station timezone: ${JSON.stringify(code)}. Add it to _STATION_TZ or pass tzOverride=\"America/...\".`,\n );\n}\n\n// ---------------------------------------------------------------------------\n// as_of parsing\n// ---------------------------------------------------------------------------\n\nfunction _parseAsOf(asOf: Date | string): Date {\n if (asOf instanceof Date) {\n if (Number.isNaN(asOf.getTime())) {\n throw new Error(\"Invalid Date passed as asOf\");\n }\n return asOf;\n }\n let s = asOf.trim();\n // Python: bare ISO without tz → assume UTC.\n if (s.endsWith(\"Z\")) {\n // Date.parse handles \"Z\" natively.\n } else if (!/[+-]\\d{2}:?\\d{2}$/.test(s)) {\n // No timezone suffix — treat as UTC.\n s = `${s}Z`;\n }\n const ms = Date.parse(s);\n if (!Number.isFinite(ms)) {\n throw new Error(`Invalid as_of string: ${JSON.stringify(asOf)}`);\n }\n return new Date(ms);\n}\n\n// ---------------------------------------------------------------------------\n// Public surface\n// ---------------------------------------------------------------------------\n\nfunction _pad2(n: number): string {\n return n < 10 ? `0${n}` : `${n}`;\n}\n\nfunction _isoDate(year: number, month: number, day: number): string {\n return `${year}-${_pad2(month)}-${_pad2(day)}`;\n}\n\n/**\n * Return the Kalshi settlement date (YYYY-MM-DD LST) for a UTC moment.\n *\n * Kalshi NHIGH/NLOW contracts cover midnight–midnight LOCAL STANDARD TIME.\n * DST is ignored: the window is always fixed to the standard UTC offset.\n */\nexport function settlementDateFor(\n asOf: Date | string,\n station: string,\n tzOverride?: string,\n): string {\n const utcDt = _parseAsOf(asOf);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n // offsetHours is negative for US stations → lstMs < utcMs.\n const lstMs = utcDt.getTime() + offsetHours * 3_600_000;\n const lst = new Date(lstMs);\n // Use getUTC* because we already shifted the epoch by the LST offset.\n return _isoDate(lst.getUTCFullYear(), lst.getUTCMonth() + 1, lst.getUTCDate());\n}\n\n/**\n * Return UTC start/end of the Kalshi settlement window for a date.\n * The window is midnight-midnight LST, expressed in UTC.\n */\nexport function settlementWindowUtc(\n dateStr: string,\n station: string,\n tzOverride?: string,\n): [Date, Date] {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(dateStr);\n if (!match) {\n throw new Error(`Invalid ISO date for settlement window: ${JSON.stringify(dateStr)}`);\n }\n const [, yStr, mStr, dStr] = match;\n const year = Number(yStr);\n const month = Number(mStr);\n const day = Number(dStr);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n\n // midnight LST = 00:00 LST = (00:00 UTC) − offset (offset is negative)\n // Example: UTC-5 → midnight LST = 05:00 UTC.\n const midnightLstAsUtcMs = Date.UTC(year, month - 1, day, 0, 0, 0);\n const startMs = midnightLstAsUtcMs - offsetHours * 3_600_000;\n const start = new Date(startMs);\n const end = new Date(startMs + 24 * 3_600_000);\n return [start, end];\n}\n\n/**\n * Return the UTC time at which the NWS CLI for a date is expected to be\n * available. Default delay is 10 h after midnight LST on the next day.\n */\nexport function cliAvailableAt(\n dateStr: string,\n station: string,\n delayHours: number = _CLI_PUBLICATION_DELAY_HOURS,\n tzOverride?: string,\n): Date {\n const [, windowEnd] = settlementWindowUtc(dateStr, station, tzOverride);\n return new Date(windowEnd.getTime() + delayHours * 3_600_000);\n}\n\n/**\n * Return the UTC time of the Kalshi market close for a settlement date.\n * Kalshi NHIGH/NLOW markets close at 4:30 PM LST on the day of settlement.\n */\nexport function marketCloseUtc(dateStr: string, station: string, tzOverride?: string): Date {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(dateStr);\n if (!match) {\n throw new Error(`Invalid ISO date for market close: ${JSON.stringify(dateStr)}`);\n }\n const [, yStr, mStr, dStr] = match;\n const year = Number(yStr);\n const month = Number(mStr);\n const day = Number(dStr);\n const tz = _resolveStationTz(station, tzOverride);\n const offsetHours = _lstOffsetHours(tz);\n\n const marketCloseAsUtcMs = Date.UTC(\n year,\n month - 1,\n day,\n _MARKET_CLOSE_HOUR_LST,\n _MARKET_CLOSE_MINUTE_LST,\n 0,\n );\n return new Date(marketCloseAsUtcMs - offsetHours * 3_600_000);\n}\n","// buildPairs + _obsAggregates + pairsToRows — settlement-day row builder.\n//\n// Byte-faithful TS port of Python\n// `packages/core/src/mostlyright/_internal/_pairs.py::build_pairs` (Mode 1\n// subset — no forecast wiring; all fcst_* columns unconditionally null).\n//\n// The full Python `_select_best_run` / `_aggregate_fcst_temps_*` paths\n// (IEM MOS + Open-Meteo) are intentionally NOT ported here; forecast\n// support lands in TS-W5+. Same scope cut TS-W1 made for `research()`.\n//\n// Type strategy: structural `PairsObservationLike` + `PairsClimateLike`\n// interfaces. The full `Observation` (from weather/_parsers/awc.ts) and\n// `ClimateObservation` (from weather/_parsers/cli.ts) structurally\n// satisfy them — avoids a circular import + matches the Plan 04\n// `ObservationKey` discipline.\n\nimport { marketCloseUtc } from \"../snapshot.js\";\n\n/** Subset of fields `_obsAggregates` reads from each observation row. */\nexport interface PairsObservationLike {\n readonly temp_f?: number | null;\n readonly dewpoint_f?: number | null;\n readonly wind_speed_kt?: number | null;\n readonly wind_gust_kt?: number | null;\n readonly precip_1hr_inches?: number | null;\n}\n\n/** Subset of `ClimateObservation` fields buildPairs reads from each CLI row. */\nexport interface PairsClimateLike {\n readonly high_temp_f: number | null;\n readonly low_temp_f: number | null;\n readonly report_type: string;\n}\n\n/** Aggregated observation summary for one settlement day. */\nexport interface ObsAggregates {\n readonly obs_high_f: number | null;\n readonly obs_low_f: number | null;\n readonly obs_mean_f: number | null;\n readonly obs_mean_dewpoint_f: number | null;\n readonly obs_max_wind_kt: number | null;\n readonly obs_max_gust_kt: number | null;\n readonly obs_total_precip_in: number | null;\n readonly obs_count: number;\n}\n\n/**\n * One settlement-date row — 20 columns, byte-shape-equivalent to Python\n * `build_pairs_row` output. The `fcst_*` columns are unconditionally\n * `null` in TS-W2 (Mode 1 only — forecast wiring is TS-W5+).\n *\n * Object-key order is preserved verbatim so `JSON.stringify` produces\n * column ordering byte-stable across SDKs.\n */\nexport interface PairsRow {\n readonly date: string;\n readonly station: string;\n readonly cli_high_f: number | null;\n readonly cli_low_f: number | null;\n readonly cli_report_type: string | null;\n readonly obs_high_f: number | null;\n readonly obs_low_f: number | null;\n readonly obs_mean_f: number | null;\n readonly obs_mean_dewpoint_f: number | null;\n readonly obs_max_wind_kt: number | null;\n readonly obs_max_gust_kt: number | null;\n readonly obs_total_precip_in: number | null;\n readonly obs_count: number;\n readonly fcst_high_f: null;\n readonly fcst_low_f: null;\n readonly fcst_model: null;\n readonly fcst_issued_at: null;\n readonly fcst_pop_6hr_pct: null;\n readonly fcst_qpf_6hr_in: null;\n readonly market_close_utc: string;\n}\n\nexport interface BuildPairsOptions {\n /** Forwarded to `marketCloseUtc` (rare — used for synthetic test stations). */\n readonly tzOverride?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Aggregation helpers\n// ---------------------------------------------------------------------------\n\nfunction collectNonNull(\n obs: ReadonlyArray<PairsObservationLike>,\n key: keyof PairsObservationLike,\n): number[] {\n const out: number[] = [];\n for (const o of obs) {\n const v = o[key];\n if (typeof v === \"number\" && Number.isFinite(v)) out.push(v);\n }\n return out;\n}\n\nfunction meanOrNull(vs: number[]): number | null {\n if (vs.length === 0) return null;\n let s = 0;\n for (const v of vs) s += v;\n return s / vs.length;\n}\n\nfunction maxOrNull(vs: number[]): number | null {\n if (vs.length === 0) return null;\n let best = vs[0] as number;\n for (let i = 1; i < vs.length; i++) {\n const v = vs[i] as number;\n if (v > best) best = v;\n }\n return best;\n}\n\nfunction minOrNull(vs: number[]): number | null {\n if (vs.length === 0) return null;\n let best = vs[0] as number;\n for (let i = 1; i < vs.length; i++) {\n const v = vs[i] as number;\n if (v < best) best = v;\n }\n return best;\n}\n\nfunction sumOrNull(vs: number[]): number | null {\n if (vs.length === 0) return null;\n let s = 0;\n for (const v of vs) s += v;\n return s;\n}\n\n// ---------------------------------------------------------------------------\n// Aggregator\n// ---------------------------------------------------------------------------\n\n/**\n * Aggregate one day's observation rows into the 8-field `obs_*` summary.\n *\n * Rules (byte-faithful with Python `_obs_aggregates` at `_pairs.py:97-150`):\n * - obs_high_f / obs_low_f / obs_mean_f: max / min / arithmetic mean over\n * non-null `temp_f`. Mean-of-null-only → null.\n * - obs_mean_dewpoint_f: mean over non-null `dewpoint_f`.\n * - obs_max_wind_kt / obs_max_gust_kt: max over non-null wind/gust.\n * - obs_total_precip_in: sum over non-null precip; `null` if NO non-null\n * precip rows (mirrors Python `sum(precips) if precips else None`).\n * - obs_count: total row count, INCLUDING rows where every measure is null.\n *\n * Numeric-stability note: mean is non-associative for floats. Callers MUST\n * pass observations in a deterministic order to preserve byte-equivalent\n * float aggregation. Plan 06's research orchestrator sorts by\n * `(observed_at, source)` before calling this.\n *\n * Returns a `Object.freeze`-d aggregate with key order matching Python.\n */\nexport function _obsAggregates(observations: ReadonlyArray<PairsObservationLike>): ObsAggregates {\n if (observations.length === 0) {\n return Object.freeze({\n obs_high_f: null,\n obs_low_f: null,\n obs_mean_f: null,\n obs_mean_dewpoint_f: null,\n obs_max_wind_kt: null,\n obs_max_gust_kt: null,\n obs_total_precip_in: null,\n obs_count: 0,\n });\n }\n const temps = collectNonNull(observations, \"temp_f\");\n const dewps = collectNonNull(observations, \"dewpoint_f\");\n const winds = collectNonNull(observations, \"wind_speed_kt\");\n const gusts = collectNonNull(observations, \"wind_gust_kt\");\n const precips = collectNonNull(observations, \"precip_1hr_inches\");\n return Object.freeze({\n obs_high_f: maxOrNull(temps),\n obs_low_f: minOrNull(temps),\n obs_mean_f: meanOrNull(temps),\n obs_mean_dewpoint_f: meanOrNull(dewps),\n obs_max_wind_kt: maxOrNull(winds),\n obs_max_gust_kt: maxOrNull(gusts),\n obs_total_precip_in: sumOrNull(precips),\n obs_count: observations.length,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Row + batch builders\n// ---------------------------------------------------------------------------\n\n/**\n * Build one PairsRow for a given (station, date) from its observation +\n * climate inputs. Mode 1 only — fcst_* are unconditionally null.\n *\n * `market_close_utc` is formatted `YYYY-MM-DDTHH:MM:SSZ` (no milliseconds)\n * via `Date.toISOString().slice(0, 19) + \"Z\"` — mirrors Python strftime.\n */\nexport function buildPairsRow(\n dateStr: string,\n station: string,\n observations: ReadonlyArray<PairsObservationLike>,\n climate: PairsClimateLike | null,\n opts: BuildPairsOptions = {},\n): PairsRow {\n const obsAgg = _obsAggregates(observations);\n const closeUtc = marketCloseUtc(dateStr, station, opts.tzOverride);\n const closeIso = `${closeUtc.toISOString().slice(0, 19)}Z`;\n return Object.freeze({\n date: dateStr,\n station,\n cli_high_f: climate ? climate.high_temp_f : null,\n cli_low_f: climate ? climate.low_temp_f : null,\n cli_report_type: climate ? climate.report_type : null,\n obs_high_f: obsAgg.obs_high_f,\n obs_low_f: obsAgg.obs_low_f,\n obs_mean_f: obsAgg.obs_mean_f,\n obs_mean_dewpoint_f: obsAgg.obs_mean_dewpoint_f,\n obs_max_wind_kt: obsAgg.obs_max_wind_kt,\n obs_max_gust_kt: obsAgg.obs_max_gust_kt,\n obs_total_precip_in: obsAgg.obs_total_precip_in,\n obs_count: obsAgg.obs_count,\n fcst_high_f: null,\n fcst_low_f: null,\n fcst_model: null,\n fcst_issued_at: null,\n fcst_pop_6hr_pct: null,\n fcst_qpf_6hr_in: null,\n market_close_utc: closeIso,\n });\n}\n\n/**\n * Build PairsRows for every date in `dates` (input-order preserved).\n *\n * `observationsByDate[date]` and `climateByDate[date]` are looked up\n * defensively — missing keys are treated as empty obs / null climate.\n *\n * Returns a `Object.freeze`-d array.\n */\nexport function buildPairs(\n station: string,\n dates: ReadonlyArray<string>,\n observationsByDate: Readonly<Record<string, ReadonlyArray<PairsObservationLike>>>,\n climateByDate: Readonly<Record<string, PairsClimateLike | null>>,\n opts: BuildPairsOptions = {},\n): ReadonlyArray<PairsRow> {\n const out: PairsRow[] = [];\n for (const date of dates) {\n const obs = observationsByDate[date] ?? [];\n const climate = climateByDate[date] ?? null;\n out.push(buildPairsRow(date, station, obs, climate, opts));\n }\n return Object.freeze(out);\n}\n\n/**\n * Surface-parity alias of `buildPairs` output. Python's `pairs_to_dataframe`\n * converts the list[dict] into a pandas DataFrame indexed by date; TS has\n * no DataFrame, so this is identity. Exists for cross-SDK signature parity\n * per CROSS-SDK-SYNC.md.\n */\nexport function pairsToRows(rows: ReadonlyArray<PairsRow>): ReadonlyArray<PairsRow> {\n return rows;\n}\n","// Phase 21 21-01 — research() composable kwargs surface.\n//\n// Mirrors the Python `research(*, include_forecast, forecast_model,\n// forecast_models, qc, tz_override, sources, source, backend, return_type)`\n// signature so cross-SDK consumers see the same options shape on either\n// side. Per D-03, the `backend` and `return_type` kwargs are accepted in TS\n// but `backend=\"polars\"` raises DataAvailabilityError (no Polars in\n// browser/Node TS — Python only), and `return_type=\"wrapper\"` is a no-op\n// (TS returns plain object arrays; wrappers don't carry semantic value\n// without a separate frame backend to wrap).\n//\n// Snake_case keys match the Python wire format — a cross-language wrapper\n// can pass the same options dict to either SDK without case-folding.\n\n/**\n * Phase 21 21-01 composable-kwargs extension to `ResearchOptions`.\n *\n * All fields are optional; defaults match Python. Each field's runtime\n * validation lives in `validateResearchKwargs()` so the failure surface is\n * lockstep with Python's `_validate_research_kwargs`.\n */\nexport interface ResearchKwargsExtension {\n /**\n * When `true`, attach `fcst_*` columns. Phase 17 wired this end-to-end\n * for IEM MOS; other forecast models require Phase 21 follow-up plans.\n *\n * Default: `false`.\n */\n include_forecast?: boolean;\n\n /**\n * Single forecast model name (e.g. `\"gfs\"`, `\"nbm\"`). Requires\n * `include_forecast=true`. Mutually exclusive with `forecast_models`.\n */\n forecast_model?: string;\n\n /**\n * Multi-model forecast fan-out. Requires `include_forecast=true`.\n * Mutually exclusive with `forecast_model`.\n */\n forecast_models?: ReadonlyArray<string>;\n\n /**\n * When `true`, run QC passes and surface QC columns. Default `false`.\n */\n qc?: boolean;\n\n /**\n * IANA timezone override for stations not in the canonical registry.\n * Rarely needed for the 20-station Phase 1 set (all covered).\n */\n tz_override?: string;\n\n /**\n * D-03: accepted but no-op in TS. `backend=\"polars\"` raises\n * `DataAvailabilityError` (no Polars in browser/Node TS). Default\n * `\"pandas\"` mirrors Python; in TS this is informational only.\n */\n backend?: \"pandas\" | \"polars\";\n\n /**\n * D-03: accepted but no-op in TS. Python returns a `MostlyRightResult`\n * wrapper class when `return_type=\"wrapper\"`; TS returns plain object\n * arrays (no `.attrs` divergence to bridge), so the wrapper would carry\n * no extra signal.\n */\n return_type?: \"frame\" | \"wrapper\";\n}\n\n/**\n * Full set of keys accepted on `ResearchOptions` (any value not in this\n * set raises `TypeError` at validation time — defends against silent\n * typo acceptance).\n *\n * Pre-Phase-21 keys (camelCase + lowercase): the existing TS surface;\n * shipped via Phase 7 / 10 / 17 / 18.\n *\n * Phase 21 21-01 keys (snake_case): the new composable kwargs ported from\n * Python.\n */\nexport const KNOWN_RESEARCH_OPTION_KEYS: ReadonlySet<string> = new Set([\n // Pre-Phase-21 fetcher controls.\n \"signal\",\n \"awcHours\",\n \"iemPolitenessMs\",\n \"ghcnhPolitenessMs\",\n \"cliPolitenessMs\",\n \"now\",\n \"cache\",\n // Pre-Phase-21 selectors.\n \"city\",\n \"contract\",\n \"contracts\",\n \"stationOverride\",\n \"sources\",\n \"source\",\n \"includeTrades\",\n \"onWarning\",\n // Phase 21 21-01: Python-parity composable kwargs.\n \"include_forecast\",\n \"forecast_model\",\n \"forecast_models\",\n \"qc\",\n \"tz_override\",\n \"backend\",\n \"return_type\",\n]);\n\n/**\n * Phase 21 21-01: runtime kwarg validation for `research()`. Lockstep with\n * Python `_validate_research_kwargs`. Throws `TypeError` (NOT\n * `DataAvailabilityError`) for the same reasons Python raises `TypeError`:\n *\n * 1. Unknown option key (silent typo defense).\n * 2. `sources` and `source` are mutually exclusive.\n * 3. `forecast_model` and `forecast_models` are mutually exclusive.\n * 4. `forecast_model`/`forecast_models` require `include_forecast=true`.\n *\n * The `backend=\"polars\"` rejection lives in the calling code, NOT here —\n * it raises `DataAvailabilityError(reason: \"model_unavailable\")` per D-03\n * to match Python's `SourceUnavailableError` shape lockstep.\n */\nexport function validateResearchKwargs(opts: Readonly<Record<string, unknown>>): void {\n // Phase 21 21-09 fix-iter-1 (codex+ts-architect HIGH): \"provided\" must\n // mean the same thing on both sides of the JSON wire. Python `is not\n // None` treats both `null` (from JSON) and absent keys as ABSENT; TS\n // historically used `!== undefined`, which treats explicit `null` as\n // PRESENT — round-tripping a Python `None` through JSON as `null` would\n // falsely trigger a mutually-exclusive TypeError in TS only. Match\n // Python by treating both `null` and `undefined` as absent (`!= null`).\n const present = (v: unknown): boolean => v !== undefined && v !== null;\n\n // (1) Unknown-key defense. Catches typos like `inclide_forecast` that\n // would otherwise silently no-op.\n for (const key of Object.keys(opts)) {\n if (!KNOWN_RESEARCH_OPTION_KEYS.has(key)) {\n throw new TypeError(\n `research(): unknown option key ${JSON.stringify(key)}. ` +\n `Valid keys: ${[...KNOWN_RESEARCH_OPTION_KEYS].sort().join(\", \")}`,\n );\n }\n }\n\n // (2) sources / source mutually exclusive.\n if (present(opts.sources) && present(opts.source)) {\n throw new TypeError(\n \"research(): sources= and source= are mutually exclusive — \" +\n \"use `sources=` for the LIVE_V1 multi-source selector or \" +\n \"`source=` for a single-source query, not both\",\n );\n }\n\n // (3) forecast_model / forecast_models mutually exclusive.\n if (present(opts.forecast_model) && present(opts.forecast_models)) {\n throw new TypeError(\n \"research(): forecast_model= and forecast_models= are mutually exclusive — \" +\n \"use `forecast_models=` for multi-model fan-out or `forecast_model=` for \" +\n \"a single model, not both\",\n );\n }\n\n // (4) forecast_model / forecast_models require include_forecast=true.\n const wantsForecast = present(opts.forecast_model) || present(opts.forecast_models);\n if (wantsForecast && opts.include_forecast !== true) {\n throw new TypeError(\n \"research(): forecast_model=/forecast_models= require include_forecast=true; \" +\n \"the model filter is otherwise silently ignored\",\n );\n }\n}\n","// Phase 21 21-01 additions: composable kwargs ported from Python.\n// `research()` orchestrator — TS-W2 multi-source Mode 1 join.\n//\n// Wires all four observation sources (AWC live, IEM ASOS archive, GHCNh\n// archive, IEM CLI climate) into the canonical `PairsRow` shape via\n// mergeObservations + mergeClimate + buildPairs. Mode 1 only — all\n// `fcst_*` columns are unconditionally null in this phase.\n//\n// Lives in `packages-ts/meta/` so `@mostlyrightmd/core` stays dep-free; this\n// orchestrator imports from both core (snapshot math + station table +\n// merge + pairs) and weather (4 fetchers + 4 parsers).\n//\n// W2 scope: AWC + IEM ASOS + GHCNh + CLI; no cache (TS-W3), no Mode 2\n// (TS-W4), no forecast (TS-W5+), no parallel prefetch (TS-W3+). Fetches\n// are sequential — fine for the parity gate; performance work is later.\n\nimport {\n DataAvailabilityError,\n NotFoundError,\n STATION_BY_CODE,\n STATION_BY_ICAO,\n settlementDateFor,\n} from \"@mostlyrightmd/core\";\nimport {\n type CacheStore,\n cacheKeyForClimate,\n cacheKeyForObservations,\n defaultCacheStore,\n isLiveSource,\n isWithinVolatileWindow,\n isWritableMonth,\n isWritableYear,\n shouldSkipCacheForCurrentLstMonth,\n shouldSkipCacheForCurrentLstYear,\n} from \"@mostlyrightmd/core/internal/cache\";\nimport { mergeClimate, mergeObservations } from \"@mostlyrightmd/core/internal/merge\";\nimport {\n type PairsClimateLike,\n type PairsObservationLike,\n type PairsRow,\n buildPairs,\n} from \"@mostlyrightmd/core/internal/pairs\";\nimport {\n type ClimateObservation,\n type Observation,\n awcToObservation,\n downloadCliRange,\n downloadGhcnh,\n downloadIemAsos,\n fetchAwcMetars,\n parseCliResponse,\n parseGhcnhPsv,\n parseIemCsv,\n} from \"@mostlyrightmd/weather\";\nimport { type ResearchKwargsExtension, validateResearchKwargs } from \"./research.types.js\";\n\n// Re-export PairsRow so callers can `import { research, type PairsRow } from \"mostlyright\"`.\nexport type { PairsRow } from \"@mostlyrightmd/core/internal/pairs\";\n\nconst AWC_MAX_HOURS = 168;\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface ResearchOptions extends ResearchKwargsExtension {\n /** Forward to all underlying fetchers; aborts the whole pipeline. */\n signal?: AbortSignal;\n /** AWC lookback window in hours. Default 168 (AWC max). Clamped by the fetcher. */\n awcHours?: number;\n /** Polite-delay (ms) between successive IEM ASOS year chunks. Default 1000. */\n iemPolitenessMs?: number;\n /** Polite-delay (ms) between successive GHCNh year requests. Default 1000. */\n ghcnhPolitenessMs?: number;\n /** Polite-delay (ms) between successive CLI year requests. Default 1000. */\n cliPolitenessMs?: number;\n /**\n * Reference clock for the AWC-window overlap check (test-only seam).\n * Defaults to `new Date()`. Pass an override to force-include AWC for\n * historical date ranges in unit tests.\n */\n now?: Date;\n /**\n * Pluggable cache backend (TS-W3). When omitted, uses\n * `defaultCacheStore()` (auto-detects IndexedDB → FsStore → MemoryStore).\n * Pass `null` to opt out of caching entirely.\n */\n cache?: CacheStore | null;\n\n // ── Phase 10: composable selectors (mutually exclusive with station). ──\n //\n // Per the Phase 10 v0.2 scope, the validation surface is shipped on\n // both Python and TS; the multi-station / multi-issuer JOIN +\n // trade-attachment is deferred to v0.3. Passing any of the three\n // selectors below currently throws a clear NotImplementedError-like\n // error pointing callers at `discover()` + the station= path until\n // v0.3 lands.\n\n /** Cross-issuer city selector. Returns rows for every station that any\n * issuer settles against (Kalshi + Polymarket + denylist backstops). */\n city?: string;\n /** Single-contract selector. Format: `\"<issuer>:<id>\"` (e.g.\n * `\"kalshi:KXHIGHNYC-25MAY26-T79\"`). Auto-resolves to the contract's\n * canonical settlement station via the Phase 8 catalog. */\n contract?: string;\n /** Multi-contract selector for basis-trade research. */\n contracts?: ReadonlyArray<string>;\n /** Override the contract's canonical settlement station. Emits a\n * StationOverrideWarning via `onWarning?`; output row carries\n * `settlementMismatch: true`. Only valid with `contract` selector. */\n stationOverride?: string;\n /** Mode 1 source subset — dedupe within. Mutually exclusive with `source`. */\n sources?: ReadonlyArray<string>;\n /** Mode 2 single-source pin — error on mismatch. Mutually exclusive with `sources`. */\n source?: string;\n /** Attach per-issuer trade timeseries via @mostlyrightmd/markets/trades.\n * Requires `contract` or `contracts`. */\n includeTrades?: boolean;\n /** Callback receiving Phase 10 StationOverrideWarning (no `warnings.warn()`\n * analogue in JS). */\n onWarning?: (w: import(\"./compose.js\").StationOverrideWarning) => void;\n}\n\n/**\n * Resolve the cache from opts. `null` means opt-out (returns null).\n *\n * Iter-1 H3: `defaultCacheStore()` is now async (FsStore loaded via\n * dynamic import behind a Node feature-detect). Caller already runs\n * inside `research()`'s async path, so awaiting here is free.\n */\nasync function resolveCache(opts: ResearchOptions): Promise<CacheStore | null> {\n if (opts.cache === null) return null;\n if (opts.cache !== undefined) return opts.cache;\n return await defaultCacheStore();\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\ninterface ResolvedStation {\n readonly code: string;\n readonly icao: string;\n readonly tz: string;\n readonly country: string | null;\n readonly ghcnhId: string | null;\n}\n\nfunction normalizeStation(input: string): ResolvedStation {\n const raw = input.trim().toUpperCase();\n if (raw.length === 0) {\n throw new Error(\"station must be a non-empty string\");\n }\n const byIcao = STATION_BY_ICAO.get(raw);\n if (byIcao !== undefined) {\n if (byIcao.code === null) {\n throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);\n }\n return {\n code: byIcao.code,\n icao: byIcao.icao,\n tz: byIcao.tz,\n country: byIcao.country,\n ghcnhId: byIcao.ghcnh_id,\n };\n }\n const byCode = STATION_BY_CODE.get(raw);\n if (byCode !== undefined) {\n if (byCode.code === null) {\n throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);\n }\n return {\n code: byCode.code,\n icao: byCode.icao,\n tz: byCode.tz,\n country: byCode.country,\n ghcnhId: byCode.ghcnh_id,\n };\n }\n if (raw.startsWith(\"K\") && raw.length === 4) {\n const stripped = raw.slice(1);\n const retry = STATION_BY_CODE.get(stripped);\n if (retry !== undefined && retry.code !== null) {\n return {\n code: retry.code,\n icao: retry.icao,\n tz: retry.tz,\n country: retry.country,\n ghcnhId: retry.ghcnh_id,\n };\n }\n }\n throw new Error(\n `unknown station ${JSON.stringify(input)} — not found in STATION_BY_CODE or STATION_BY_ICAO`,\n );\n}\n\nfunction parseIsoDate(s: string): Date {\n if (!DATE_RE.test(s)) {\n throw new Error(`expected YYYY-MM-DD, got ${JSON.stringify(s)}`);\n }\n const [yStr, mStr, dStr] = s.split(\"-\");\n const year = Number(yStr);\n const month = Number(mStr);\n const day = Number(dStr);\n const ms = Date.UTC(year, month - 1, day);\n const d = new Date(ms);\n if (d.getUTCFullYear() !== year || d.getUTCMonth() !== month - 1 || d.getUTCDate() !== day) {\n throw new Error(`invalid calendar date ${JSON.stringify(s)}`);\n }\n return d;\n}\n\nfunction formatDate(d: Date): string {\n const y = d.getUTCFullYear();\n const m = d.getUTCMonth() + 1;\n const day = d.getUTCDate();\n const mm = m < 10 ? `0${m}` : `${m}`;\n const dd = day < 10 ? `0${day}` : `${day}`;\n return `${y}-${mm}-${dd}`;\n}\n\nfunction buildDateList(fromDate: string, toDate: string): ReadonlyArray<string> {\n const from = parseIsoDate(fromDate);\n const to = parseIsoDate(toDate);\n if (from.getTime() > to.getTime()) {\n throw new Error(`fromDate (${fromDate}) must be <= toDate (${toDate})`);\n }\n const dates: string[] = [];\n for (let cursor = from.getTime(); cursor <= to.getTime(); cursor += 24 * 3_600_000) {\n dates.push(formatDate(new Date(cursor)));\n }\n return dates;\n}\n\n/** Plus-one-day in UTC. Used to extend the upper bound so the final LST\n * settlement window's pre-midnight UTC tail observations are captured. */\nfunction plusOneDay(isoDate: string): string {\n const d = parseIsoDate(isoDate);\n return formatDate(new Date(d.getTime() + 24 * 3_600_000));\n}\n\n/** US stations only — GHCNh PSV archive is US-only. International stations\n * have `ghcnh_id: null` AND `country !== \"US\"` in the TS codegen. */\nfunction isUsStation(station: ResolvedStation): boolean {\n return station.country === \"US\";\n}\n\n/** Returns true if any date in `[fromDate, toDate]` is within `hours` of `now`.\n * Mirrors Python `_month_overlaps_awc_window` semantics — defensive\n * short-circuit so we don't hit AWC for purely historical windows. */\nfunction anyDateOverlapsAwc(toDate: string, hours: number, now: Date): boolean {\n const to = parseIsoDate(toDate);\n // Window includes the END of toDate (LST close), so add 24h to the upper bound.\n const toEndMs = to.getTime() + 24 * 3_600_000;\n const nowMs = now.getTime();\n const cutoffMs = nowMs - hours * 3_600_000;\n return toEndMs >= cutoffMs;\n}\n\nfunction observedSettlementDate(observedAt: string, station: string): string | null {\n const ms = Date.parse(observedAt);\n if (!Number.isFinite(ms)) return null;\n try {\n return settlementDateFor(new Date(ms), station);\n } catch {\n return null;\n }\n}\n\n/** Lexicographic-on-`observed_at` sort, stable in `source`. Ensures\n * byte-equivalent float aggregation in `_obsAggregates` (mean is\n * non-associative for floats). */\nfunction sortByObservedAtThenSource(rows: ReadonlyArray<Observation>): Observation[] {\n return [...rows].sort((a, b) => {\n if (a.observed_at < b.observed_at) return -1;\n if (a.observed_at > b.observed_at) return 1;\n if (a.source < b.source) return -1;\n if (a.source > b.source) return 1;\n return 0;\n });\n}\n\n/**\n * True iff the end-of-year ISO date for `year` falls inside the 30-day\n * volatile amendment window relative to `now`. Used to gate archive\n * cache reads/writes for IEM ASOS yearly chunks AND IEM CLI yearly\n * chunks (iter-5 H9). Rationale: rows from a year whose 12-31 boundary\n * is within 30 days of \"now\" may still be amended upstream; caching\n * them would persist soon-to-be-stale values.\n *\n * For `year` strictly less than the current calendar year of `now`, the\n * 12-31 boundary is well past 30 days back → returns false (cacheable).\n * For `year` equal to the current LST year, the year-end is in the\n * future relative to `now` → predicate returns false (the per-year\n * current-LST-year gate handles that case first and is still required).\n * The window only fires for the immediate-post-year window — exactly\n * the case where freshly-archived rows are most likely to be revised.\n */\nfunction isYearVolatile(year: number, now: Date): boolean {\n const yearEnd = `${String(year).padStart(4, \"0\")}-12-31`;\n return isWithinVolatileWindow(yearEnd, formatDate(now), 30);\n}\n\n/**\n * Last calendar day of `(year, month)`. Used as archive-as-of for the\n * per-month volatile-window gate (iter-7 H13). Returns YYYY-MM-DD.\n */\nfunction lastDayOfMonth(year: number, month: number): string {\n // UTC math: day 0 of (month+1) === last day of (month).\n const d = new Date(Date.UTC(year, month, 0));\n return formatDate(d);\n}\n\n/**\n * True iff the end-of-month ISO date for `(year, month)` falls inside the\n * 30-day volatile amendment window relative to `now`. Per-month analog of\n * `isYearVolatile`, used to gate the per-month observations cache\n * (iter-7 H13). Rationale: rows from a month whose final day is within\n * 30 days of \"now\" may still be amended upstream; caching them would\n * persist soon-to-be-stale values. The window only fires for the\n * immediate-post-month window — exactly the case where freshly-archived\n * rows are most likely to be revised.\n */\nfunction isMonthVolatile(year: number, month: number, now: Date): boolean {\n return isWithinVolatileWindow(lastDayOfMonth(year, month), formatDate(now), 30);\n}\n\n/**\n * Enumerate `[year, month]` pairs that overlap `[fromIsoDate, toIsoDate]`\n * (inclusive on both ends). Used by the per-month observations cache\n * (iter-7 H13). Returns pairs in chronological order. Validates the\n * range; throws on inverted input.\n */\nfunction monthsInRange(\n fromIsoDate: string,\n toIsoDate: string,\n): ReadonlyArray<readonly [number, number]> {\n const from = parseIsoDate(fromIsoDate);\n const to = parseIsoDate(toIsoDate);\n if (from.getTime() > to.getTime()) {\n throw new Error(`fromDate (${fromIsoDate}) must be <= toDate (${toIsoDate})`);\n }\n const pairs: Array<readonly [number, number]> = [];\n let y = from.getUTCFullYear();\n let m = from.getUTCMonth() + 1; // 1-12\n const endY = to.getUTCFullYear();\n const endM = to.getUTCMonth() + 1;\n while (y < endY || (y === endY && m <= endM)) {\n pairs.push([y, m]);\n m += 1;\n if (m > 12) {\n m = 1;\n y += 1;\n }\n }\n return pairs;\n}\n\n/**\n * Fetch CLI climate per-year with read-through cache. Yearly chunks are\n * cached at `cacheKeyForClimate(code, year)`. Skip rules:\n * - Current LST year — mutable, never cached.\n * - 30-day volatile amendment window (iter-5 H9) — chunks whose\n * year-end is within 30 days of `now` MUST be re-fetched. The\n * window only fires for the year immediately preceding \"now\"\n * once the calendar rolls over.\n * - Live source (`.live`) — never cached (CLI is archive `iem.cli` →\n * this never fires today; defensive for future).\n *\n * iter-6 C12: cache failures must NEVER discard the in-memory rows.\n * `cache.get` failures degrade to a live fetch (the intent — read-through\n * is a perf optimization, not a correctness requirement). `cache.set`\n * failures AFTER a successful fetch+parse MUST log and continue —\n * persisting to the cache is a best-effort side effect, never a reason\n * to drop already-fetched climate data. The previous broad try/catch in\n * the caller swallowed cache.set throws as \"no CLI data,\" silently\n * corrupting research rows with null cli_* fields.\n */\nasync function fetchCliWithCache(\n fetchIcao: string,\n cacheCode: string,\n fromYear: number,\n toYear: number,\n opts: ResearchOptions,\n cache: CacheStore | null,\n now: Date,\n): Promise<ClimateObservation[]> {\n const acc: ClimateObservation[] = [];\n for (let year = fromYear; year <= toYear; year++) {\n // iter-12 C15: `isWritableYear` is the strictest temporal gate.\n // Any year that isn't STRICTLY in the past UTC-wise (future years\n // or the current UTC year, including the UTC Jan-1 boundary window\n // where a negative-offset station's LST is still in the prior year)\n // is never cacheable — regardless of LST or volatile-window logic.\n // Force a live fetch and skip both reads AND writes for non-writable\n // years.\n const writable = isWritableYear(year, now);\n const skipCurrentYear = shouldSkipCacheForCurrentLstYear(cacheCode, year, now);\n // iter-5 H9: the 30-day volatile amendment window MUST also block\n // cache reads — a hit served from inside the window would re-serve\n // soon-to-be-amended rows. Always prefer a fresh fetch when the\n // window is active.\n const skipVolatile = isYearVolatile(year, now);\n const skip = !writable || skipCurrentYear || skipVolatile;\n\n // --- Cache read (best-effort) -------------------------------------\n // iter-6 C12: a `cache.get` failure must not abort the per-year\n // chunk — fall through to the live fetch. A transient backend hiccup\n // is no reason to refuse climate data we can still fetch fresh.\n if (cache !== null && !skip) {\n let cached: ClimateObservation[] | null = null;\n try {\n cached = await cache.get<ClimateObservation[]>(cacheKeyForClimate(cacheCode, year));\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] CLI cache.get failed for code=${cacheCode} year=${year}; falling back to live fetch:`,\n cacheErr,\n );\n }\n if (cached !== null) {\n acc.push(...cached);\n continue;\n }\n }\n\n // --- Live fetch + parse (errors here ARE fatal to this chunk) -----\n // Abort propagates; other errors bubble to the caller's try/catch\n // which degrades to \"no CLI data\" for the affected years. This is\n // the existing behavior — DO NOT widen the catch to include cache\n // writes (see below).\n const cliOpts: { signal?: AbortSignal; politenessMs?: number } = {};\n if (opts.signal !== undefined) cliOpts.signal = opts.signal;\n if (opts.cliPolitenessMs !== undefined) cliOpts.politenessMs = opts.cliPolitenessMs;\n const cliRaw = await downloadCliRange(fetchIcao, year, year, cliOpts);\n const parsed = parseCliResponse(cliRaw, cacheCode);\n acc.push(...parsed);\n\n // --- Cache write (best-effort, AFTER rows are accumulated) --------\n // iter-6 C12: `cache.set` MUST be wrapped in its own try/catch so a\n // transient write failure cannot discard already-fetched rows. The\n // previous code put cache.set inside the caller's broad CLI try/catch,\n // which silently degraded write failures to \"no climate data\" —\n // returning research rows with null cli_* fields. That's silent data\n // corruption; this guard prevents it.\n const sample = parsed[0]?.source;\n if (cache !== null && !skip && !isLiveSource(sample)) {\n try {\n await cache.set(cacheKeyForClimate(cacheCode, year), parsed);\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] CLI cache.set failed for code=${cacheCode} year=${year}; in-memory rows preserved:`,\n cacheErr,\n );\n }\n }\n }\n return acc;\n}\n\n/**\n * Fetch IEM ASOS observations per-month with read-through cache.\n *\n * iter-7 H13: this previously cached at YEAR granularity using a sentinel\n * `:01:rt=N` key, violating the Python TS-CACHE-02 per-month contract.\n * The Python `read_cache(station, year, month)` / `write_cache(...)`\n * surface uses `(station, year, month)` triplets — one parquet per month\n * containing the merged METAR+SPECI slice. This helper now matches that\n * contract:\n *\n * 1. Enumerate `(year, month)` pairs overlapping the queried range.\n * 2. For each pair, attempt a per-month cache read using the\n * source-namespaced key `cacheKeyForObservations(station, year,\n * month, \"iem\")`.\n * 3. On cache miss, fetch the full year (single IEM HTTP request for\n * `report_type=3` + one for `=4`) — IEM ASOS is yearly-chunked at\n * the source — then partition parsed rows by `(year, month)` and\n * filter back to the requested month (mirrors Python research.py\n * L267-269 month-boundary filter).\n * 4. Apply per-MONTH skip rules:\n * - `shouldSkipCacheForCurrentLstMonth(station, year, month, now)` —\n * mutable current month; never written.\n * - `isMonthVolatile(year, month, now)` — 30-day amendment window\n * gate (iter-5 H9 / iter-7 H13). Within the window, both read\n * AND write are skipped (IEM may publish late-arriving METARs\n * or corrections).\n * 5. Write-through fires only when neither skip rule trips; otherwise\n * the month's rows are returned in-memory but never persisted.\n *\n * Per-year fetch results are cached in a local `yearCache` Map so multiple\n * months within the same year share one HTTP round-trip — this is the\n * critical perf invariant from the previous implementation, preserved\n * across the granularity change.\n *\n * iter-6 C12: mirrors `fetchCliWithCache`'s split-try pattern — cache\n * `get` / `set` failures are logged but never discard the in-memory\n * rows. A cache backend hiccup must not silently drop observations\n * that were successfully fetched + parsed.\n */\nasync function fetchIemAsosWithCache(\n stationCode: string,\n _fromYear: number,\n _extendedToYear: number,\n fromDate: string,\n extendedTo: string,\n opts: ResearchOptions,\n cache: CacheStore | null,\n now: Date,\n): Promise<Observation[]> {\n void _fromYear;\n void _extendedToYear;\n const acc: Observation[] = [];\n\n // Per-call memoization: avoid re-fetching the same (year, reportType)\n // when multiple months in the same year miss the cache.\n const yearByReportType = new Map<string, Observation[]>();\n\n async function fetchYearOnce(year: number, reportType: 3 | 4): Promise<Observation[]> {\n const memoKey = `${year}:${reportType}`;\n const cached = yearByReportType.get(memoKey);\n if (cached !== undefined) return cached;\n const iemOpts: { reportType: 3 | 4; politenessMs: number; signal?: AbortSignal } = {\n reportType,\n politenessMs: opts.iemPolitenessMs ?? 1000,\n };\n if (opts.signal !== undefined) iemOpts.signal = opts.signal;\n const chunks = await downloadIemAsos(stationCode, `${year}-01-01`, `${year}-12-31`, iemOpts);\n const fetched: Observation[] = [];\n for (const chunk of chunks) {\n const parsed = parseIemCsv(chunk.csv, {\n observationTypeOverride: reportType === 3 ? \"METAR\" : \"SPECI\",\n });\n fetched.push(...parsed);\n }\n yearByReportType.set(memoKey, fetched);\n return fetched;\n }\n\n function filterMonth(\n rows: ReadonlyArray<Observation>,\n year: number,\n month: number,\n ): Observation[] {\n const yyyy = String(year).padStart(4, \"0\");\n const mm = String(month).padStart(2, \"0\");\n const prefix = `${yyyy}-${mm}-`;\n const out: Observation[] = [];\n for (const r of rows) {\n if (r.observed_at.startsWith(prefix)) out.push(r);\n }\n return out;\n }\n\n const pairs = monthsInRange(fromDate, extendedTo);\n for (const [year, month] of pairs) {\n const cacheKey = cacheKeyForObservations(stationCode, year, month, \"iem\");\n // iter-12 C14: `isWritableMonth` is the strictest temporal gate.\n // Any month that isn't STRICTLY in the past UTC-wise (future months\n // or the current UTC month, including the UTC-rollover tail where\n // LST is still in the prior UTC month) is never cacheable —\n // regardless of LST or volatile-window logic. Force a live fetch\n // and skip both reads AND writes for non-writable months.\n const writable = isWritableMonth(year, month, now);\n const skipCurrentMonth = shouldSkipCacheForCurrentLstMonth(stationCode, year, month, now);\n const skipVolatile = isMonthVolatile(year, month, now);\n const skipCache = !writable || skipCurrentMonth || skipVolatile;\n\n // --- Cache read (best-effort) -------------------------------------\n // iter-6 C12: a `cache.get` failure must not abort the month — fall\n // through to the live fetch. The cached value combines METAR+SPECI\n // (single per-month entry), so a hit yields both report types.\n let monthRows: Observation[] | null = null;\n if (cache !== null && !skipCache) {\n try {\n const cached = await cache.get<Observation[]>(cacheKey);\n if (cached !== null) monthRows = cached;\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] IEM ASOS cache.get failed for key=${cacheKey}; falling back to live fetch:`,\n cacheErr,\n );\n }\n }\n\n if (monthRows === null) {\n // --- Live fetch + parse (errors here propagate to the caller) ---\n // Fetch both report types for the year (memoized) and partition\n // to this month. Combining METAR+SPECI matches the Python contract\n // (write_cache receives one merged list per month).\n const metar = await fetchYearOnce(year, 3);\n const speci = await fetchYearOnce(year, 4);\n const monthMetar = filterMonth(metar, year, month);\n const monthSpeci = filterMonth(speci, year, month);\n monthRows = [...monthMetar, ...monthSpeci];\n\n // --- Cache write (best-effort, AFTER rows are accumulated) ------\n // iter-6 C12: `cache.set` failures MUST NOT propagate — a\n // transient write failure cannot be allowed to discard rows that\n // were just successfully fetched + parsed. Log and continue; the\n // in-memory `monthRows` is appended to `acc` below regardless.\n const sample = monthRows[0]?.source;\n if (cache !== null && !skipCache && !isLiveSource(sample)) {\n try {\n await cache.set(cacheKey, monthRows);\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] IEM ASOS cache.set failed for key=${cacheKey}; in-memory rows preserved:`,\n cacheErr,\n );\n }\n }\n }\n\n for (const obs of monthRows) {\n const obsDate = obs.observed_at.slice(0, 10);\n if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);\n }\n }\n return acc;\n}\n\n/**\n * Fetch GHCNh archive observations per-month with read-through cache.\n *\n * iter-7 H14: previously the GHCNh path called `downloadGhcnhRange` on\n * every `research()` invocation and never touched the cache. TS-W3\n * requires GHCNh chunks to be cacheable just like IEM ASOS — this helper\n * applies the same per-month contract as `fetchIemAsosWithCache`:\n *\n * 1. Enumerate `(year, month)` pairs overlapping the queried range.\n * 2. For each pair, attempt a per-month cache read using the source-\n * namespaced key `cacheKeyForObservations(station, year, month,\n * \"ghcnh\")`. The `\"ghcnh\"` source segment prevents collision with\n * IEM ASOS writes for the same `(station, year, month)` triplet\n * (iter-7 H13 introduced `\"iem\"` namespacing).\n * 3. On cache miss, fetch the full year via `downloadGhcnh` (single\n * PSV per station-year — NCEI's archive is yearly-chunked at the\n * source) — memoized within the helper so multiple months in the\n * same year share one HTTP round-trip.\n * 4. Per-month skip rules: `shouldSkipCacheForCurrentLstMonth` +\n * `isMonthVolatile` (30-day amendment window). NCEI republishes\n * `GHCNh_<id>_<YEAR>.psv` as new months land, so the same skip\n * logic the IEM helper uses applies here.\n * 5. 404-as-no-data: a `NotFoundError` from `downloadGhcnh` means NCEI\n * has no archive for this station-year (typical for recent partial\n * years or pre-1973 stations). We memoize an empty year and treat\n * every month as cache-eligible-but-empty. The Python range fetcher\n * silently swallows 404 too (research.py L160-166 logs + continues).\n *\n * iter-6 C12: mirrors the split-try pattern — cache `get` / `set`\n * failures are logged but never discard the in-memory rows.\n */\nasync function fetchGhcnhWithCache(\n stationCode: string,\n ghcnhId: string,\n fromDate: string,\n extendedTo: string,\n opts: ResearchOptions,\n cache: CacheStore | null,\n now: Date,\n): Promise<Observation[]> {\n const acc: Observation[] = [];\n\n // Per-call memoization: avoid re-fetching the same year when multiple\n // months in the same year miss the cache. `null` sentinel records a 404\n // (no data) so subsequent months in that year skip the HTTP call too.\n const yearCache = new Map<number, ReadonlyArray<Observation>>();\n\n async function fetchYearOnce(year: number): Promise<ReadonlyArray<Observation>> {\n const cached = yearCache.get(year);\n if (cached !== undefined) return cached;\n const ghcnhOpts: { signal?: AbortSignal } = {};\n if (opts.signal !== undefined) ghcnhOpts.signal = opts.signal;\n let parsed: ReadonlyArray<Observation>;\n try {\n const yr = await downloadGhcnh(ghcnhId, year, ghcnhOpts);\n parsed = parseGhcnhPsv(yr.psv);\n } catch (err) {\n if (err instanceof NotFoundError) {\n // NCEI 404 → no data for this station-year. Mirrors the\n // `downloadGhcnhRange` swallow-404 behavior; memoize empty so\n // subsequent months in this year don't re-hit NCEI.\n parsed = [];\n } else {\n throw err;\n }\n }\n yearCache.set(year, parsed);\n return parsed;\n }\n\n function filterMonth(\n rows: ReadonlyArray<Observation>,\n year: number,\n month: number,\n ): Observation[] {\n const yyyy = String(year).padStart(4, \"0\");\n const mm = String(month).padStart(2, \"0\");\n const prefix = `${yyyy}-${mm}-`;\n const out: Observation[] = [];\n for (const r of rows) {\n if (r.observed_at.startsWith(prefix) && r.station_code === stationCode) out.push(r);\n }\n return out;\n }\n\n const pairs = monthsInRange(fromDate, extendedTo);\n for (const [year, month] of pairs) {\n const cacheKey = cacheKeyForObservations(stationCode, year, month, \"ghcnh\");\n // iter-12 C14: stricter additional temporal gate — see the matching\n // comment in `fetchIemAsosWithCache`. NCEI's archive can return\n // empty data for not-yet-published months; we must NEVER persist a\n // not-strictly-past UTC month as if it were complete.\n const writable = isWritableMonth(year, month, now);\n const skipCurrentMonth = shouldSkipCacheForCurrentLstMonth(stationCode, year, month, now);\n const skipVolatile = isMonthVolatile(year, month, now);\n const skipCache = !writable || skipCurrentMonth || skipVolatile;\n\n // --- Cache read (best-effort) -------------------------------------\n let monthRows: Observation[] | null = null;\n if (cache !== null && !skipCache) {\n try {\n const cached = await cache.get<Observation[]>(cacheKey);\n if (cached !== null) monthRows = cached;\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] GHCNh cache.get failed for key=${cacheKey}; falling back to live fetch:`,\n cacheErr,\n );\n }\n }\n\n if (monthRows === null) {\n // --- Live fetch + parse (errors here propagate to the caller) ---\n const yearRows = await fetchYearOnce(year);\n monthRows = filterMonth(yearRows, year, month);\n\n // --- Cache write (best-effort, AFTER rows are accumulated) ------\n // iter-6 C12: `cache.set` failures MUST NOT propagate. Even an\n // empty month list is written when the year was successfully\n // fetched — it pins the \"no observations for this month\" fact so\n // the next call doesn't re-fetch the year just to discover nothing.\n const sample = monthRows[0]?.source;\n if (cache !== null && !skipCache && !isLiveSource(sample)) {\n try {\n await cache.set(cacheKey, monthRows);\n } catch (cacheErr) {\n // eslint-disable-next-line no-console\n console.warn(\n `[mostlyright] GHCNh cache.set failed for key=${cacheKey}; in-memory rows preserved:`,\n cacheErr,\n );\n }\n }\n }\n\n for (const obs of monthRows) {\n const obsDate = obs.observed_at.slice(0, 10);\n if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);\n }\n }\n return acc;\n}\n\n// ---------------------------------------------------------------------------\n// Public surface\n// ---------------------------------------------------------------------------\n\n/**\n * Build daily research rows for a station + date window.\n *\n * @param station NWS 3-letter code (e.g. \"NYC\") OR 4-letter ICAO (e.g. \"KNYC\").\n * @param fromDate Inclusive start date, ISO YYYY-MM-DD (LST).\n * @param toDate Inclusive end date, ISO YYYY-MM-DD (LST).\n * @param opts See {@link ResearchOptions}.\n *\n * Returns an immutable array of frozen {@link PairsRow}s — one per LST day\n * in `[fromDate, toDate]`. Each row carries:\n * - `cli_*` populated from IEM CLI (final preferred per `mergeClimate`).\n * - `obs_*` daily aggregates over the 3-source merged observations\n * (AWC > IEM > GHCNh per `mergeObservations`).\n * - `fcst_*` unconditionally null (Mode 1).\n * - `market_close_utc` formatted `YYYY-MM-DDTHH:MM:SSZ`.\n *\n * Throws on unknown station, malformed dates, or fromDate > toDate.\n * AbortSignal propagates from underlying fetchers.\n */\nexport async function research(\n station: string,\n fromDate: string,\n toDate: string,\n opts: ResearchOptions = {},\n): Promise<ReadonlyArray<PairsRow>> {\n // ── Phase 21 21-01 kwarg validation (lockstep with Python) ───────────\n //\n // Surfaces unknown-key typos, mutually-exclusive misuse (sources/source,\n // forecast_model/forecast_models), and silent-no-op cases\n // (forecast_model without include_forecast) up-front. Mirrors the Python\n // `_validate_research_kwargs` guard.\n validateResearchKwargs(opts as Readonly<Record<string, unknown>>);\n\n // Phase 21 21-01 D-03: `backend=\"polars\"` is accepted-for-surface but\n // rejected at runtime since TS has no Polars equivalent (bundle-size\n // killer in browser; not viable in Node either without the optional\n // native binding). DataAvailabilityError(reason=\"model_unavailable\")\n // matches the cross-SDK typed-exception contract from 21-09.\n if (opts.backend === \"polars\") {\n throw new DataAvailabilityError({\n reason: \"model_unavailable\",\n hint:\n \"polars backend not available in TypeScript SDK; use Python \" +\n '(mostlyrightmd) for backend=\"polars\". TS returns plain object arrays.',\n source: \"research.backend\",\n });\n }\n // `return_type=\"wrapper\"` is also a D-03 no-op — TS arrays don't have the\n // pandas `.attrs` divergence problem a Python wrapper compensates for, so\n // we accept the kwarg for surface-equivalence and return the plain shape.\n\n // ── Phase 10 selector + cross-arg validation ─────────────────────────\n //\n // The TS signature pre-dates Phase 10's composable kwargs, so the\n // `station` positional is still always passed. The new selectors live\n // on `opts` (city / contract / contracts) and are validated here:\n // exactly one of station / city / contract / contracts is allowed.\n //\n // v0.2 ships only the validation surface; the multi-station JOIN +\n // trade-attachment lands in v0.3. Passing any non-station selector\n // surfaces a clear NotImplementedError-style error so callers can\n // route via discover() + the station-path until v0.3.\n const hasCity = typeof opts.city === \"string\" && opts.city.length > 0;\n const hasContract = typeof opts.contract === \"string\" && opts.contract.length > 0;\n const hasContracts = Array.isArray(opts.contracts) && opts.contracts.length > 0;\n const hasStation = typeof station === \"string\" && station.length > 0;\n const selectorCount =\n Number(hasStation) + Number(hasCity) + Number(hasContract) + Number(hasContracts);\n if (selectorCount === 0) {\n throw new Error(\n \"research(): exactly one of station, opts.city, opts.contract, opts.contracts must be provided\",\n );\n }\n if (selectorCount > 1) {\n const names: string[] = [];\n if (hasStation) names.push(\"station\");\n if (hasCity) names.push(\"city\");\n if (hasContract) names.push(\"contract\");\n if (hasContracts) names.push(\"contracts\");\n throw new Error(`research(): selectors are mutually exclusive; got ${JSON.stringify(names)}`);\n }\n if (opts.sources !== undefined && opts.source !== undefined) {\n throw new Error(\"research(): sources and source are mutually exclusive\");\n }\n // Iter-1 codex HIGH: sources / source validation is shipped in Phase 10\n // v0.2 but the data-selection wiring lands in v0.3. Without this guard\n // the station-path runs the full multi-source merge regardless — silent\n // data-selection corruption.\n if (opts.sources !== undefined || opts.source !== undefined) {\n throw new Error(\n \"research(): sources / source validation surface is shipped in Phase 10 v0.2 \" +\n \"but the data-selection wiring lands in v0.3. For Mode 2 single-source pinning \" +\n \"today, use `researchBySource(station, source, ...)` from @mostlyrightmd/meta.\",\n );\n }\n if (opts.stationOverride !== undefined && !hasContract) {\n throw new Error(\n \"research(): stationOverride requires contract (not standalone station/city/contracts)\",\n );\n }\n if (opts.includeTrades === true && !(hasContract || hasContracts)) {\n throw new Error(\n \"research(): includeTrades requires contract or contracts (station/city selectors have no trade timeseries)\",\n );\n }\n if (hasCity || hasContract || hasContracts) {\n throw new Error(\n \"research(): city/contract/contracts selectors are validated in Phase 10 v0.2 \" +\n \"but the multi-station/multi-issuer JOIN + trade attachment lands in v0.3. \" +\n \"For now, use `discover({city})` to find the station then call \" +\n \"`research(station, fromDate, toDate)` directly.\",\n );\n }\n // ── Backwards-compat station path (existing implementation) ─────────\n const resolved = normalizeStation(station);\n const dates = buildDateList(fromDate, toDate);\n const extendedTo = plusOneDay(toDate);\n\n const fromYear = Number(fromDate.slice(0, 4));\n const toYear = Number(toDate.slice(0, 4));\n const extendedToYear = Number(extendedTo.slice(0, 4));\n\n const baseOpts: { signal?: AbortSignal } = {};\n if (opts.signal !== undefined) baseOpts.signal = opts.signal;\n\n const cache = await resolveCache(opts);\n const cacheNow = opts.now ?? new Date();\n\n // --- IEM CLI climate (per-year) ---------------------------------------\n // Cache strategy: read-through per (station code, year). Skip the current\n // LST year (mutable) and never cache `.live` sources. `iem.cli` is\n // archive → cacheable for completed years. Fetcher takes the ICAO\n // (resolved.icao), cache key uses the 3-letter NWS code (resolved.code).\n let mergedClimate: ReadonlyArray<ClimateObservation> = [];\n try {\n const cliRows = await fetchCliWithCache(\n resolved.icao,\n resolved.code,\n fromYear,\n toYear,\n opts,\n cache,\n cacheNow,\n );\n mergedClimate = mergeClimate(cliRows);\n } catch (err) {\n if (err instanceof DOMException && (err.name === \"AbortError\" || err.name === \"TimeoutError\")) {\n throw err;\n }\n // Degrade to no CLI data — buildPairs emits null cli_* for affected dates.\n }\n\n // --- AWC live observations (short-circuit on stale windows) -----------\n const awcHours = opts.awcHours ?? AWC_MAX_HOURS;\n const awcRows: Observation[] = [];\n if (anyDateOverlapsAwc(toDate, awcHours, opts.now ?? new Date())) {\n const awcOpts: { hours: number; signal?: AbortSignal } = { hours: awcHours };\n if (opts.signal !== undefined) awcOpts.signal = opts.signal;\n const awcRaw = await fetchAwcMetars([resolved.icao], awcOpts);\n for (const m of awcRaw) {\n const obs = awcToObservation(m);\n if (obs !== null) awcRows.push(obs);\n }\n }\n\n // --- IEM ASOS archive observations (per-year × {METAR, SPECI}) --------\n // IEM ASOS expects the 3-letter NWS station code (`station=NYC`),\n // NOT the 4-letter ICAO. Python `_fetchers/iem_asos.py:119` uses\n // `station={station.code}`. Use resolved.code, NOT resolved.icao.\n const iemRows = await fetchIemAsosWithCache(\n resolved.code,\n fromYear,\n extendedToYear,\n fromDate,\n extendedTo,\n opts,\n cache,\n cacheNow,\n );\n\n // --- GHCNh archive observations (US stations only) --------------------\n // iter-7 H14: now wraps `downloadGhcnh` in `fetchGhcnhWithCache` so\n // GHCNh chunks are persisted at the same per-month granularity as IEM\n // ASOS. Repeat `research()` calls for the same range skip NCEI\n // entirely on cache hit. Non-US stations short-circuit before reaching\n // the helper — GHCNh PSVs are US-only.\n let ghcnhRows: Observation[] = [];\n if (isUsStation(resolved) && resolved.ghcnhId !== null && resolved.ghcnhId.length > 0) {\n ghcnhRows = await fetchGhcnhWithCache(\n resolved.code,\n resolved.ghcnhId,\n fromDate,\n extendedTo,\n opts,\n cache,\n cacheNow,\n );\n }\n\n // --- Merge observations + bucket by settlement date -------------------\n const combinedRaw = [...awcRows, ...iemRows, ...ghcnhRows];\n const sorted = sortByObservedAtThenSource(combinedRaw);\n const merged = mergeObservations(sorted);\n\n const observationsByDate: Record<string, PairsObservationLike[]> = {};\n // dates is guaranteed non-empty by buildDateList contract (throws on\n // fromDate > toDate; both validated above).\n const dateLo = dates[0] ?? \"\";\n const dateHi = dates[dates.length - 1] ?? \"\";\n for (const obs of merged) {\n const settleDate = observedSettlementDate(obs.observed_at, resolved.code);\n if (settleDate === null) continue;\n if (settleDate < dateLo || settleDate > dateHi) continue;\n let bucket = observationsByDate[settleDate];\n if (bucket === undefined) {\n bucket = [];\n observationsByDate[settleDate] = bucket;\n }\n bucket.push(obs);\n }\n\n // --- Bucket climate by date (mergeClimate already deduped) ------------\n const climateByDate: Record<string, PairsClimateLike | null> = {};\n for (const cli of mergedClimate) {\n climateByDate[cli.observation_date] = cli;\n }\n\n // --- buildPairs join + return -----------------------------------------\n return buildPairs(resolved.code, dates, observationsByDate, climateByDate);\n}\n","// Mode 2 — source-explicit research() variant.\n//\n// Mirrors packages/core/src/mostlyright/mode2.py. Mode 1 (the existing\n// `research()`) merges AWC > IEM > GHCNh; Mode 2 lets the caller pin\n// observations to a single named source for source-identified\n// training pairs (the workflow Vojtech wanted for backtests that\n// need source-identity invariants).\n//\n// Lives in @mostlyrightmd/meta (alongside `research()`), NOT in\n// @mostlyrightmd/core — `assertSourceIdentity` consumes the\n// @mostlyrightmd/weather `Observation` type, which @mostlyrightmd/core\n// must not depend on (would create a cycle).\n//\n// ── Vocabulary ───────────────────────────────────────────────────────\n// TS narrows what Python widens: at the input boundary, TS accepts\n// ONLY the four canonical dotted-form sources. Bare forms (`iem`,\n// `awc`, `ghcnh`) are NEVER accepted at the API; they only ever\n// appear as parser-emitted PER-ROW source tags. The alias table\n// (`SOURCE_ALIASES`) bridges the boundary: filter rows whose bare\n// tag is in the dotted source's alias set, but NEVER rewrite the\n// per-row source — that would silently corrupt downstream Validator\n// invariants. See Python mode2.py:161-166 for the canonical comment.\n\nimport {\n NotFoundError,\n STATION_BY_CODE,\n STATION_BY_ICAO,\n SourceMismatchError,\n type SourceMismatchRole,\n} from \"@mostlyrightmd/core\";\nimport {\n type Observation,\n awcToObservation,\n downloadGhcnh,\n downloadIemAsos,\n fetchAwcMetars,\n parseGhcnhPsv,\n parseIemCsv,\n} from \"@mostlyrightmd/weather\";\n\nexport type { SourceMismatchRole };\n\n/** Mode 2 canonical source vocabulary. Exactly four dotted values. */\nexport const MODE2_SOURCES = [\"iem.archive\", \"iem.live\", \"awc.live\", \"ghcnh.archive\"] as const;\n\n/**\n * Mode 2 source-identity type. Const-union derived from the\n * `MODE2_SOURCES` tuple-literal (NOT a TS `enum` — `enum` defeats\n * tree-shaking per TS Architect rubric §5).\n */\nexport type Mode2Source = (typeof MODE2_SOURCES)[number];\n\n/**\n * Map each canonical dotted source to the bare parser-emitted tags\n * that satisfy it. Parsers emit bare `iem`/`awc`/`ghcnh` per\n * packages-ts/weather; mostlyright' canonical vocab is dotted. The\n * alias table bridges both at the boundary without rewriting the\n * per-row source — downstream consumers see the truthful\n * parser-emitted tag.\n *\n * Mirrors packages/core/src/mostlyright/mode2.py:55-63.\n */\nexport const SOURCE_ALIASES: ReadonlyMap<Mode2Source, ReadonlySet<string>> = new Map<\n Mode2Source,\n ReadonlySet<string>\n>([\n [\"iem.archive\", new Set([\"iem\", \"iem.archive\"])],\n [\"iem.live\", new Set([\"iem\", \"iem.live\"])],\n [\"awc.live\", new Set([\"awc\", \"awc.live\"])],\n [\"ghcnh.archive\", new Set([\"ghcnh\", \"ghcnh.archive\"])],\n]);\n\n/**\n * Type-guard: narrow an unknown value to {@link Mode2Source}. Returns\n * true iff `value` is one of the four canonical dotted strings.\n * Bare-form inputs (`'iem'`, `'awc'`, `'ghcnh'`) return false — TS\n * narrows what Python widens.\n */\nexport function isMode2Source(value: unknown): value is Mode2Source {\n return typeof value === \"string\" && (MODE2_SOURCES as readonly string[]).includes(value);\n}\n\n/**\n * Throw {@link SourceMismatchError} if any row's `source` field\n * disagrees with the expected source vocabulary. Rows missing the\n * `source` field (undefined / null / non-string) are skipped\n * (matches Python mode2.py:181-182 — `if \"source\" not in df.columns:\n * return`). Empty `rows` passes silently.\n *\n * The `expected` parameter accepts EITHER:\n *\n * - a single string — the most common case; downstream callers\n * can pass `\"iem.archive\"` and the check is `src === \"iem.archive\"`.\n * - a `ReadonlySet<string>` — used by `researchBySource` to pass\n * the {@link SOURCE_ALIASES} entry so bare-form parser tags\n * (`'iem'`) are accepted alongside the dotted canonical form\n * (`'iem.archive'`). Without this, the per-row source-preserved\n * invariant (Python mode2.py:161-166) would force the assertion\n * to fire on every Mode 2 call.\n *\n * @param rows rows to check (any shape with `source?: string`)\n * @param expected the source string OR alias-set the caller asked for\n * @param role role-name vocabulary; defaults to 'observations'\n *\n * @throws SourceMismatchError with `schemaSource` = the expected label\n * (the input string, or `[...accept].sort().join(\"|\")`\n * when an alias-set was passed), `dataSource` =\n * first sorted distinct mismatched source,\n * `role` = the caller-provided role,\n * `catalogWarning` = null.\n */\nexport function assertSourceIdentity<Row extends { source?: string | null | undefined }>(\n rows: ReadonlyArray<Row>,\n expected: string | ReadonlySet<string>,\n role: SourceMismatchRole = \"observations\",\n): void {\n const accept: ReadonlySet<string> =\n typeof expected === \"string\" ? new Set<string>([expected]) : expected;\n const expectedLabel: string =\n typeof expected === \"string\" ? expected : [...accept].sort().join(\"|\");\n\n const distinct = new Set<string>();\n let bad = 0;\n for (const r of rows) {\n const src = r?.source;\n if (typeof src !== \"string\") continue;\n if (!accept.has(src)) {\n distinct.add(src);\n bad += 1;\n }\n }\n if (bad === 0) return;\n const others = [...distinct].sort();\n const first = others[0] ?? \"<unknown>\";\n throw new SourceMismatchError(\n `Mode 2 dispatch requested '${expectedLabel}' but received ${bad} row(s) with other sources: [${others\n .map((s) => `'${s}'`)\n .join(\", \")}]`,\n {\n schemaSource: expectedLabel,\n dataSource: first,\n role,\n catalogWarning: null,\n },\n );\n}\n\n// ---------------------------------------------------------------------------\n// researchBySource — Mode 2 dispatch entry point\n// ---------------------------------------------------------------------------\n\n/** Mode 2 caller-supplied options. Subset of `ResearchOptions` — Mode 2\n * returns observations only, so forecast + climate + cache opts are\n * intentionally excluded. */\nexport interface ResearchBySourceOptions {\n /** Forward to the underlying fetcher; aborts the dispatch. */\n signal?: AbortSignal;\n /** AWC lookback window in hours (clamped by the fetcher). Default 168. */\n awcHours?: number;\n /** Polite-delay (ms) between IEM ASOS yearly chunks. Default 1000. */\n iemPolitenessMs?: number;\n}\n\n/** AWC live serves at most ~168 hours (7 days). Mirrors research.ts. */\nconst AWC_MAX_HOURS = 168;\n\ninterface ResolvedStation {\n readonly code: string;\n readonly icao: string;\n readonly country: string | null;\n readonly ghcnhId: string | null;\n}\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Resolve a station identifier (3-letter NWS code OR 4-letter ICAO)\n * to the full record. Inlined here instead of imported from `research.ts`\n * to keep mode2 self-contained — the ~30-line duplication is cheaper\n * than threading internal helpers through a new module boundary.\n */\nfunction resolveStation(input: string): ResolvedStation {\n const raw = input.trim().toUpperCase();\n if (raw.length === 0) {\n throw new Error(\"station must be a non-empty string\");\n }\n const byIcao = STATION_BY_ICAO.get(raw);\n if (byIcao !== undefined) {\n if (byIcao.code === null) {\n throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);\n }\n return {\n code: byIcao.code,\n icao: byIcao.icao,\n country: byIcao.country,\n ghcnhId: byIcao.ghcnh_id,\n };\n }\n const byCode = STATION_BY_CODE.get(raw);\n if (byCode !== undefined) {\n if (byCode.code === null) {\n throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);\n }\n return {\n code: byCode.code,\n icao: byCode.icao,\n country: byCode.country,\n ghcnhId: byCode.ghcnh_id,\n };\n }\n if (raw.startsWith(\"K\") && raw.length === 4) {\n const stripped = raw.slice(1);\n const retry = STATION_BY_CODE.get(stripped);\n if (retry !== undefined && retry.code !== null) {\n return {\n code: retry.code,\n icao: retry.icao,\n country: retry.country,\n ghcnhId: retry.ghcnh_id,\n };\n }\n }\n throw new Error(\n `unknown station ${JSON.stringify(input)} — not found in STATION_BY_CODE or STATION_BY_ICAO`,\n );\n}\n\nfunction validateDateFormat(label: string, value: string): void {\n if (!DATE_RE.test(value)) {\n throw new Error(`${label} must be YYYY-MM-DD, got ${JSON.stringify(value)}`);\n }\n}\n\n/**\n * Year extracted from a YYYY-MM-DD string. Caller must validate format\n * first via `validateDateFormat`.\n */\nfunction yearOf(isoDate: string): number {\n return Number(isoDate.slice(0, 4));\n}\n\n/**\n * Mode 2 source-explicit observation fetch.\n *\n * Dispatches to a single source's fetcher (no merge) and returns raw\n * {@link Observation}s tagged with that source. Mirrors Python\n * `mostlyright.mode2.research_by_source` (packages/core/src/mostlyright/mode2.py).\n *\n * The four supported sources:\n *\n * - `'iem.archive'` → IEM ASOS historical CSVs (METAR + SPECI).\n * - `'iem.live'` → v0.1.0 parity gap; throws. Use `'iem.archive'`.\n * - `'awc.live'` → AWC live METAR JSON (≤168h lookback).\n * - `'ghcnh.archive'` → NCEI GHCNh PSV (US stations only).\n *\n * The returned rows preserve the parser-emitted per-row `source` field\n * verbatim — NEVER rewritten to the dotted canonical form. Bare tags\n * (`'iem'`, `'awc'`, `'ghcnh'`) survive intact so downstream Validator\n * schemas see the truthful provenance. Mode 2 still calls\n * {@link assertSourceIdentity} internally (defense-in-depth) before\n * returning — using the {@link SOURCE_ALIASES} entry so the bare-form\n * tags pass.\n *\n * @param station NWS 3-letter code (e.g. `\"NYC\"`) OR 4-letter ICAO (e.g. `\"KNYC\"`).\n * @param source One of {@link MODE2_SOURCES}.\n * @param fromDate Inclusive start, ISO `YYYY-MM-DD`.\n * @param toDate Inclusive end, ISO `YYYY-MM-DD`.\n * @param opts See {@link ResearchBySourceOptions}.\n *\n * @returns Frozen array of {@link Observation}s whose `source` is in\n * `SOURCE_ALIASES.get(source)`. Empty array on no data\n * (NOT a throw).\n *\n * @throws Error if `source` is not one of {@link MODE2_SOURCES}.\n * Throws BEFORE any network call — no quota burn\n * on invalid input.\n * @throws Error if `source === 'iem.live'` (v0.1.0 parity gap;\n * v0.2 will add per-month live IEM).\n * @throws Error if `station` is unknown, or dates are malformed.\n * @throws NotFoundError if `source === 'ghcnh.archive'` and `station`\n * is non-US (GHCNh PSV files are US-only).\n * @throws SourceMismatchError if a row's `source` disagrees with the alias\n * set for `source` (defense-in-depth; should\n * never fire under correct fetcher behavior).\n */\nexport async function researchBySource(\n station: string,\n source: Mode2Source,\n fromDate: string,\n toDate: string,\n opts: ResearchBySourceOptions = {},\n): Promise<ReadonlyArray<Observation>> {\n // ── Synchronous-style guards (BEFORE any network call) ────────────\n // Architect rubric: unknown-source rejection MUST run before any\n // fetcher import/call (else invalid input burns API quota).\n if (!isMode2Source(source)) {\n throw new Error(\n `Mode 2 source must be one of ${JSON.stringify(\n MODE2_SOURCES,\n )}; got ${JSON.stringify(source)}`,\n );\n }\n if (source === \"iem.live\") {\n throw new Error(\n \"Mode 2 source 'iem.live' not yet implemented in v0.1.0 \" +\n \"(Parity-Ticket: requires per-month live IEM endpoint not yet ported). \" +\n \"Use 'iem.archive' for historical IEM rows.\",\n );\n }\n validateDateFormat(\"fromDate\", fromDate);\n validateDateFormat(\"toDate\", toDate);\n if (fromDate > toDate) {\n throw new Error(`fromDate (${fromDate}) must be <= toDate (${toDate})`);\n }\n const resolved = resolveStation(station);\n\n const accept = SOURCE_ALIASES.get(source);\n if (accept === undefined) {\n // Unreachable — isMode2Source guard above guarantees a hit.\n throw new Error(`internal: no SOURCE_ALIASES entry for '${source}'`);\n }\n\n // ── Per-source dispatch ──────────────────────────────────────────\n let rows: ReadonlyArray<Observation>;\n switch (source) {\n case \"awc.live\": {\n const awcOpts: { hours: number; signal?: AbortSignal } = {\n hours: opts.awcHours ?? AWC_MAX_HOURS,\n };\n if (opts.signal !== undefined) awcOpts.signal = opts.signal;\n const raw = await fetchAwcMetars([resolved.icao], awcOpts);\n const parsed: Observation[] = [];\n for (const m of raw) {\n const obs = awcToObservation(m);\n if (obs !== null) parsed.push(obs);\n }\n // Filter to the queried [fromDate, toDate] window (inclusive) — match\n // the IEM/GHCNh branches. AWC's lookback (~168h) can return METARs\n // outside the caller's window; per-source Mode 2 callers expect rows\n // strictly inside [fromDate, toDate]. Python mode2.py parity.\n rows = parsed.filter((r) => {\n const d = r.observed_at.slice(0, 10);\n return d >= fromDate && d <= toDate;\n });\n break;\n }\n case \"iem.archive\": {\n const fromYear = yearOf(fromDate);\n const toYear = yearOf(toDate);\n const collected: Observation[] = [];\n for (let year = fromYear; year <= toYear; year++) {\n for (const reportType of [3, 4] as const) {\n const iemOpts: {\n reportType: 3 | 4;\n politenessMs: number;\n signal?: AbortSignal;\n } = {\n reportType,\n politenessMs: opts.iemPolitenessMs ?? 1000,\n };\n if (opts.signal !== undefined) iemOpts.signal = opts.signal;\n const chunks = await downloadIemAsos(\n resolved.code,\n `${year}-01-01`,\n `${year}-12-31`,\n iemOpts,\n );\n for (const chunk of chunks) {\n const parsed = parseIemCsv(chunk.csv, {\n observationTypeOverride: reportType === 3 ? \"METAR\" : \"SPECI\",\n });\n collected.push(...parsed);\n }\n }\n }\n // Filter to the queried [fromDate, toDate] window (inclusive).\n rows = collected.filter((r) => {\n const d = r.observed_at.slice(0, 10);\n return d >= fromDate && d <= toDate;\n });\n break;\n }\n case \"ghcnh.archive\": {\n // GHCNh PSV files are US-only. Non-US stations are advertised by\n // null `ghcnh_id` and country !== \"US\" in the codegen.\n if (resolved.country !== \"US\" || resolved.ghcnhId === null || resolved.ghcnhId.length === 0) {\n throw new NotFoundError(\n `GHCNh archive is US-only; station ${JSON.stringify(station)} ` +\n `(country=${resolved.country ?? \"null\"}, ghcnh_id=${\n resolved.ghcnhId === null ? \"null\" : JSON.stringify(resolved.ghcnhId)\n }) has no GHCNh coverage`,\n );\n }\n const fromYear = yearOf(fromDate);\n const toYear = yearOf(toDate);\n const collected: Observation[] = [];\n for (let year = fromYear; year <= toYear; year++) {\n const ghcnhOpts: { signal?: AbortSignal } = {};\n if (opts.signal !== undefined) ghcnhOpts.signal = opts.signal;\n try {\n const yr = await downloadGhcnh(resolved.ghcnhId, year, ghcnhOpts);\n const parsed = parseGhcnhPsv(yr.psv);\n for (const r of parsed) {\n if (r.station_code === resolved.code) collected.push(r);\n }\n } catch (err) {\n // 404 = no data for this station-year (typical for partial /\n // pre-1973 years). Mirrors Python research.py 404-as-skip.\n if (err instanceof NotFoundError) continue;\n throw err;\n }\n }\n rows = collected.filter((r) => {\n const d = r.observed_at.slice(0, 10);\n return d >= fromDate && d <= toDate;\n });\n break;\n }\n // iem.live is rejected above; the type narrowing here is\n // exhaustive over Mode2Source minus iem.live (which is unreachable).\n }\n\n // ── Filter to the alias set (Python parity: row keep iff parser-tag in alias) ──\n // biome-ignore lint/style/noNonNullAssertion: rows is assigned in every reachable case\n const filtered = rows!.filter((r) => accept.has(r.source));\n\n // ── Defense-in-depth: assertSourceIdentity (Python mode2.py:173-193) ────\n // Empty result still passes (no rows → no mismatch).\n assertSourceIdentity(filtered, accept, \"observations\");\n\n return filtered;\n}\n","// TS-W5 — Polymarket types + security constants.\n//\n// Ports the v0.14.1 + Phase 3.3 Python contract verbatim. Anything that\n// crosses the trust boundary (event description, resolution URL) is\n// validated against the constants here before reaching the HTTP fetch\n// or settlement engine.\n\n/**\n * Resolution-source netlocs we trust. Anything else throws\n * PolymarketEventError. Mirrors Python `RESOLUTION_SOURCE_ALLOWLIST`.\n */\nexport const RESOLUTION_SOURCE_ALLOWLIST: ReadonlySet<string> = new Set([\n \"wunderground.com\",\n \"www.wunderground.com\",\n \"weather.gov\",\n \"www.weather.gov\",\n]);\n\n/**\n * Per-netloc → enum value for the `resolutionSourceType` field on settlement\n * records. `hko` / `cwa` are predeclared for v0.2 (HKO/CWA clients).\n */\nexport const NETLOC_TO_RESOLUTION_TYPE: Readonly<Record<string, string>> = Object.freeze({\n \"wunderground.com\": \"wunderground\",\n \"www.wunderground.com\": \"wunderground\",\n \"weather.gov\": \"noaa_wrh\",\n \"www.weather.gov\": \"noaa_wrh\",\n});\n\n/** Enum values for the `resolutionSourceType` column. */\nexport const POLYMARKET_RESOLUTION_SOURCE_TYPES = Object.freeze([\n \"wunderground\",\n \"noaa_wrh\",\n \"hko\",\n \"cwa\",\n \"other\",\n] as const);\nexport type PolymarketResolutionSourceType = (typeof POLYMARKET_RESOLUTION_SOURCE_TYPES)[number];\n\n/**\n * Event id pattern. Python widened this in codex iter-2 P1: real Gamma IDs\n * are numeric strings (`\"12345\"`), but condition-tag UUIDs + slugs also\n * appear in the wild. Wide enough to accept real Gamma payloads but narrow\n * enough to defend against URL-path injection.\n *\n * The plan called this \"UUID4 regex\" but follows Python's actual behavior:\n * the strict UUID4 form rejected every real Gamma event, breaking the\n * discover → settle round-trip.\n */\nexport const EVENT_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;\n\n/**\n * Max bytes of a Polymarket event description we'll parse. Polymarket\n * descriptions are concise; oversized payloads indicate hostile input\n * (ReDoS defense).\n */\nexport const MAX_DESCRIPTION_BYTES = 16 * 1024;\n\n/**\n * Per-resolution-source publication delay. Settlement refuses to settle\n * until `now - settlementDate >= delay` to avoid settling on values the\n * issuer hasn't published yet.\n *\n * Wunderground typically posts daily extremes ~6h after local midnight;\n * NOAA WRH ~4h. \"other\" gets a conservative 24h fallback.\n */\nexport const SETTLE_DELAY_HOURS: Readonly<Record<string, number>> = Object.freeze({\n wunderground: 6,\n noaa_wrh: 4,\n other: 24,\n});\n\n/**\n * Slug date extractor. Polymarket weather slugs embed the resolution date\n * (e.g. `will-nyc-be-above-80f-on-2026-05-23`). Used by `polymarketSettle`\n * to derive the resolution date from the slug instead of `event.endDate`.\n */\nexport const SLUG_DATE_RE = /(\\d{4})-(\\d{2})-(\\d{2})/g;\n\n/**\n * Markets routed to v0.2 sources. Phase 23: Hong Kong settles against HKO (the\n * Observatory, weather.gov.hk) and Taipei moved RCTP→RCSS (CWA). Both fully\n * defer every measure; the old VHHH/RCTP airport ICAOs are no longer deferred.\n */\nexport const DEFERRED_STATIONS: ReadonlySet<string> = new Set([\"HKO\", \"RCSS\"]);\n\n/** Discovery row shape — one per active weather event. */\nexport interface PolymarketDiscoveryRow {\n readonly eventId: string | null;\n readonly slug: string | null;\n readonly title: string | null;\n readonly city: string | null;\n readonly icao: string | null;\n readonly measure: \"high\" | \"low\" | \"default\" | null;\n readonly endTime: string | null;\n readonly resolutionSourceType: PolymarketResolutionSourceType | null;\n}\n\n/**\n * Native unit of the market's published settlement value. Codex iter-3 P2:\n * international Polymarket markets publish in whole-°C, US in °F. The\n * settle engine returns the resolved value in BOTH units so the caller's\n * comparison against Polymarket's published value uses the matching unit.\n */\nexport type SettlementUnit = \"fahrenheit\" | \"celsius\";\n\n/** Settlement result shape. */\nexport interface PolymarketSettlementResult {\n readonly eventId: string;\n readonly settlementDate: string; // YYYY-MM-DD\n readonly icao: string;\n readonly measure: \"high\" | \"low\" | \"default\";\n /**\n * Resolved temperature in the unit the caller asked for (the `unit`\n * option; defaults to the station's native unit — F for US-registry\n * stations, C for international). Convenience pointer to `resolvedValueF`\n * or `resolvedValueC`.\n */\n readonly resolvedValue: number;\n readonly resolvedValueC: number;\n readonly resolvedValueF: number;\n /** Which unit `resolvedValue` carries. */\n readonly unit: SettlementUnit;\n readonly resolutionSourceType: PolymarketResolutionSourceType;\n readonly dataQualityAlert: string | null;\n}\n\n/** Settlement options. */\nexport interface PolymarketSettleOptions {\n /** Optional description override (live discovery normally supplies this). */\n readonly description?: string;\n /** Reference \"now\" for the finalization-delay check. Defaults to `new Date()`. */\n readonly now?: Date;\n /**\n * Polymarket's published settlement value, if known. The comparison\n * uses whichever unit `unit` is set to. ±1°F (or ±0.6°C) diff emits an\n * alert; values outside that band don't throw.\n */\n readonly polymarketPublishedValue?: number;\n /**\n * Resolved-value unit. Defaults to the station's native unit:\n * °F for the 20 US Kalshi cities; °C for international stations\n * (matches Polymarket's published-bucket convention per\n * .planning/research/INGEST-PLANNER-RESEARCH.md). Codex iter-3 P2.\n */\n readonly unit?: SettlementUnit;\n}\n","// TS-W5 Wave 2 — Tier 0/1/2/3 resolver chain + city catalog.\n//\n// Mirrors Python `_per_event_station.resolve_station_for_event` + the\n// `_derive_city` helper. The resolver chain:\n//\n// - Tier 0 (deferred check): if the resolved ICAO is in DEFERRED_STATIONS\n// (Taipei RCSS, Hong Kong HKO), raise DeferredMarketError. Phase 23: both\n// fully defer every measure — v0.2 will land CWA + weather.gov.hk clients.\n// - Tier 1: explicit `event.city` field if a known city.\n// - Tier 2: derive city from slug + title + tags (lowercase substring\n// match against the catalog, longest-key-first so multi-token cities\n// like \"london_gatwick\" resolve before \"london\").\n// - Tier 3: bail with KeyError — discover() drops the row, settle()\n// surfaces PolymarketSettlementError.\n\nimport { DeferredMarketError } from \"@mostlyrightmd/core\";\n\nimport { POLYMARKET_CITY_STATIONS } from \"../data/generated/polymarket-city-stations.js\";\nimport type { PolymarketEventRaw } from \"./client.js\";\nimport { PolymarketSettlementError } from \"./errors.js\";\nimport { DEFERRED_STATIONS } from \"./types.js\";\n\n/**\n * Detect whether the market resolves on the daily HIGH or LOW from\n * keywords in the event title/slug/name. Distinct from the station-level\n * measure: many cities have one airport for both, but the market still\n * resolves on tmax XOR tmin.\n */\nexport function detectMarketMeasure(event: PolymarketEventRaw): \"high\" | \"low\" | \"default\" {\n const text = [event.title, event.slug, (event as { name?: string }).name]\n .filter((v): v is string => typeof v === \"string\")\n .join(\" \")\n .toLowerCase();\n const hasHigh = /\\b(highest|high|hottest|warmest|max(?:imum)?)\\b/.test(text);\n const hasLow = /\\b(lowest|low|coldest|coolest|min(?:imum)?)\\b/.test(text);\n if (hasLow && !hasHigh) return \"low\";\n if (hasHigh && !hasLow) return \"high\";\n return \"default\";\n}\n\nconst CITY_KEYS_SORTED: ReadonlyArray<string> = Object.freeze(\n Object.keys(POLYMARKET_CITY_STATIONS).sort((a, b) => b.length - a.length),\n);\n\n/**\n * Derive a city key from slug + title + tags. Lowercase substring match\n * against the catalog; longest-first so multi-token cities outrank prefixes.\n * Returns null when no match — caller decides whether to drop or surface.\n */\nexport function deriveCity(event: PolymarketEventRaw): string | null {\n const parts: string[] = [];\n const slug = typeof event.slug === \"string\" ? event.slug.toLowerCase() : \"\";\n const title = typeof event.title === \"string\" ? event.title.toLowerCase() : \"\";\n parts.push(slug, title);\n const tags = event.tags;\n if (Array.isArray(tags)) {\n for (const tag of tags) {\n if (typeof tag === \"string\") parts.push(tag.toLowerCase());\n else if (tag !== null && typeof tag === \"object\") {\n const label = tag.label ?? tag.slug;\n if (typeof label === \"string\") parts.push(label.toLowerCase());\n }\n }\n }\n // Codex iter-5 P2: require word-boundary matches so cities don't false-\n // positive inside ordinary words (e.g. \"comparison\" → \"paris\"). Build a\n // regex per needle with non-word-character boundaries on both sides.\n // Word characters are ASCII letters/digits + underscore; we treat \"-\",\n // \" \", \"/\", \"(\", \")\" etc. as boundaries (covers the slug/title/tag forms\n // we ingest).\n const haystack = ` ${parts.join(\" \")} `;\n for (const key of CITY_KEYS_SORTED) {\n const needles = [key, key.replace(/_/g, \"-\"), key.replace(/_/g, \" \")];\n for (const n of needles) {\n if (n.length === 0) continue;\n // Escape any regex metacharacters in `n` (city keys are alphanum +\n // underscore + hyphen + space; the latter two need no escaping but\n // be defensive).\n const escaped = n.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const re = new RegExp(`(^|[^A-Za-z0-9])${escaped}(?=[^A-Za-z0-9]|$)`);\n if (re.test(haystack)) return key;\n }\n }\n return null;\n}\n\n// Phase 8 — Tier 1.5 URL extraction (POLY-US-03).\n// Wunderground PWS / airport / history URL pattern; captures K-prefix ICAO\n// from CANONICAL settlement paths only — anchor allowlist: /pws/,\n// /dashboard/pws/, /history/daily/, /history/airport/, /weather-station/,\n// /cat/forecasts/. Real Polymarket settlement URLs carry country / state /\n// city slugs between the anchor and the ICAO (e.g.\n// /history/daily/us/ny/new-york-city/KLGA); the `(?:[a-z0-9-]+/)*` segment\n// captures zero or more such intermediate slugs (codex iter-2 +\n// python-architect iter-2 CRITICAL — prior tighter version that required\n// the ICAO to abut the anchor missed every real Polymarket URL).\n//\n// The trailing negative-lookahead `(?![A-Za-z0-9_-])` rejects any\n// alphanumeric / underscore / hyphen follower so we accept common URL\n// terminators (`/`, `?`, `#`, `)`, `.`, `,`, whitespace, EOF) but reject\n// extensions like `KIDS-summer` where `-` would otherwise pass `\\b`\n// (codex iter-2 CRITICAL — original `(?=[/?#\\s]|$)` lookahead missed\n// Markdown-embedded URLs that end with `)`, `.`, etc.).\n// Iter-3 codex CRITICAL: case-insensitive flag let `[a-z0-9-]+/` consume\n// uppercase segments, so `/history/daily/KORD/date/KLAX` extracted `KLAX`\n// (the LAST K-prefix segment) instead of `KORD` (canonical station slot).\n// Fix: drop `i` flag — case-sensitive matching pins ICAO to the canonical\n// station slot. Real Wunderground URLs use lowercase paths + uppercase\n// ICAOs (RFC 3986 + Wunderground convention).\nconst WUNDERGROUND_ICAO_RE =\n /https?:\\/\\/(?:www\\.)?wunderground\\.com\\/(?:dashboard\\/)?(?:pws|history\\/daily|history\\/airport|weather-station|cat\\/forecasts)\\/(?:[a-z0-9-]+\\/)*(K[A-Z]{3})(?![A-Za-z0-9_-])/g;\n\n/**\n * Extract the canonical Wunderground PWS / airport ICAO from `text`.\n *\n * Tier 1.5 of the resolver chain — runs between explicit `event.city`\n * and slug-derive. When a Polymarket event embeds a Wunderground PWS\n * URL, the URL IS the source of truth; no catalog lookup needed.\n *\n * Multi-URL disambiguation: when multiple canonical Wunderground URLs\n * appear, ALL extracted ICAOs MUST agree. Disagreement returns null so\n * the resolver falls through to Tier 2 city-derive (prevents an\n * issuer-side citation URL from silently swapping the settlement station).\n *\n * Returns uppercase ICAO (4 chars, leading K) when a canonical URL is\n * found AND any additional canonical URLs agree. Null otherwise —\n * including the disagreement case.\n */\nexport function extractIcaoFromResolutionSource(text: string | null | undefined): string | null {\n if (typeof text !== \"string\" || text.length === 0) return null;\n // Reset lastIndex on the global regex so multiple matchAll calls are safe\n // (the regex literal is reused across calls).\n const matches = [...text.matchAll(WUNDERGROUND_ICAO_RE)];\n if (matches.length === 0) return null;\n const unique = new Set<string>();\n for (const m of matches) {\n if (m[1] !== undefined) unique.add(m[1].toUpperCase());\n }\n if (unique.size !== 1) return null; // 0 or disagreement → abstain\n return [...unique][0] ?? null;\n}\n\n/**\n * Resolve an event to `{icao, stationMeasure}` using the city catalog.\n *\n * Returns null when no city matches (caller drops the event).\n * Raises DeferredMarketError when the resolution would route to a v0.2\n * source (Taipei RCTP, Hong Kong VHHH for the low-extreme market).\n */\nexport function resolveStationForEvent(\n event: PolymarketEventRaw,\n marketMeasure: \"high\" | \"low\" | \"default\",\n): { city: string; icao: string; stationMeasure: \"high\" | \"low\" | \"default\" } | null {\n // Tier 1: explicit city field — useful for tests / synthetic events.\n let cityKey: string | null = null;\n const explicit = (event as { city?: unknown }).city;\n if (typeof explicit === \"string\") {\n const low = explicit.toLowerCase();\n if (Object.prototype.hasOwnProperty.call(POLYMARKET_CITY_STATIONS, low)) {\n cityKey = low;\n }\n }\n\n // Tier 1.5: URL extraction from description / resolutionSource.\n // The Wunderground URL is the issuer's canonical proof — beats catalog\n // lookup for ICAO. Defer gate still applies so a URL injection cannot\n // silently route an RCTP / HK-low market.\n //\n // Iter-1 TS-architect CRITICAL: the `city` field is owned by the slug-\n // derive layer (matching Python's `_derive_city`-before-resolve pattern in\n // `polymarket_discover`). Tier 1.5 ONLY sets `icao`; `city` is whatever\n // the caller / slug-derive resolved to BEFORE the URL was considered.\n // Returning `findCityForIcao(extractedIcao)` here would drift the TS row's\n // `city` column away from the Python row for the same event — silent\n // per-row source-identity drift, the parity-rubric CRITICAL.\n const desc = typeof event.description === \"string\" ? event.description : \"\";\n const resSrc =\n typeof (event as { resolutionSource?: unknown }).resolutionSource === \"string\"\n ? (event as { resolutionSource: string }).resolutionSource\n : \"\";\n const urlText = `${desc} ${resSrc}`;\n const extractedIcao = extractIcaoFromResolutionSource(urlText);\n if (extractedIcao !== null) {\n // Phase 23: HKO (Hong Kong) and RCSS (Taipei) fully defer — every measure —\n // until their v0.2 source clients (weather.gov.hk / CWA) land.\n if (DEFERRED_STATIONS.has(extractedIcao)) {\n throw new DeferredMarketError(\n `Polymarket market for station ${extractedIcao} is deferred until the v0.2 source client lands (HKO/RCSS)`,\n );\n }\n // City: explicit Tier 1 wins; otherwise slug-derived; otherwise empty.\n // This mirrors Python `polymarket_discover`'s ordering — `_derive_city`\n // populates `event.city` BEFORE `resolve_station_for_event` runs, so the\n // explicit-or-derived city is always the `city` column emitted on the row.\n // discover.ts normalizes empty string back to null at the row boundary\n // to mirror Python's `(ev.get(\"city\") or \"\").lower() or None` semantics.\n const cityForRow = cityKey ?? deriveCity(event) ?? \"\";\n // stationMeasure mirrors Python's _detect_measure(text) result (passed\n // in by the caller as marketMeasure via detectMarketMeasure). Hardcoding\n // \"default\" here would drift from Python for URL-sourced low/high\n // markets (codex iter-2 HIGH).\n return { city: cityForRow, icao: extractedIcao, stationMeasure: marketMeasure };\n }\n\n // Tier 2: scan slug + title + tags.\n if (cityKey === null) {\n cityKey = deriveCity(event);\n }\n if (cityKey === null) return null;\n\n const entry = POLYMARKET_CITY_STATIONS[cityKey];\n if (entry === undefined) return null;\n\n // Station-level measure: cities that split (paris high vs low) follow\n // the market measure; cities without a split use \"default\".\n let stationMeasure: \"high\" | \"low\" | \"default\" = \"default\";\n if (marketMeasure === \"high\" && typeof entry.high === \"string\") stationMeasure = \"high\";\n else if (marketMeasure === \"low\" && typeof entry.low === \"string\") stationMeasure = \"low\";\n\n const icao = entry[stationMeasure] ?? entry.default;\n if (typeof icao !== \"string\") return null;\n\n // Tier 0 deferred-station guard. Phase 23: Hong Kong (HKO) and Taipei (RCSS)\n // fully defer every measure until their v0.2 source clients land\n // (weather.gov.hk for HK, CWA for Taipei).\n if (DEFERRED_STATIONS.has(icao)) {\n throw new DeferredMarketError(\n `Polymarket market for station ${icao} is deferred until the v0.2 source client lands (HKO/RCSS)`,\n );\n }\n\n return { city: cityKey, icao, stationMeasure };\n}\n\n/**\n * Parse the resolution date from a Polymarket weather slug. The LAST\n * YYYY-MM-DD match wins because slugs may carry both a creation date and\n * a resolution date (`created-2026-01-01-resolves-2026-05-23`) — the\n * resolution date is typically rightmost in Polymarket's convention.\n *\n * Mirrors Python `_settlement_date_from_slug` architect iter-1 HIGH-4.\n */\nexport function settlementDateFromSlug(slug: string): string {\n const matches = slug.matchAll(/(\\d{4})-(\\d{2})-(\\d{2})/g);\n let last: RegExpMatchArray | null = null;\n for (const m of matches) last = m;\n if (last === null) {\n throw new PolymarketSettlementError(\n `no resolution date in slug ${JSON.stringify(slug)} (expected YYYY-MM-DD)`,\n );\n }\n const [_, y, m, d] = last;\n const year = Number(y);\n const month = Number(m);\n const day = Number(d);\n // Validate calendar date roundtrips (e.g. reject 2025-02-30).\n const ts = Date.UTC(year, month - 1, day);\n const back = new Date(ts);\n if (\n back.getUTCFullYear() !== year ||\n back.getUTCMonth() !== month - 1 ||\n back.getUTCDate() !== day\n ) {\n throw new PolymarketSettlementError(\n `slug ${JSON.stringify(slug)} carries malformed date ${y}-${m}-${d}`,\n );\n }\n return `${y}-${m}-${d}`;\n}\n","// Phase 8 — per-issuer denylist. Hand-paired with Python\n// `mostlyright.markets.polymarket.KNOWN_WRONG_STATIONS` (NOT codegen;\n// see .planning/phases/08-.../PLAN.md §\"TS Parity\" for rationale —\n// the constant is small enough that exporter wiring would cost more\n// than it saves, and the alphabetized JSON exporter side-effect would\n// conflate it with the cities map).\n//\n// Per-city Map (not flat set) because Polymarket's catalog is multi-city\n// and the \"wrong\" station depends on which city the event is for.\n// Symmetric in spirit to Kalshi's KALSHI_KNOWN_WRONG_STATIONS flat set:\n// Polymarket's per-city granularity is required because (e.g.) KLGA is\n// correct for NYC but wrong for Chicago (where Polymarket uses KORD).\n\nexport const POLYMARKET_KNOWN_WRONG_STATIONS: Readonly<Record<string, ReadonlySet<string>>> =\n Object.freeze({\n // NYC: Polymarket uses KLGA. KNYC/KJFK/KEWR are common wrong answers.\n nyc: new Set([\"KNYC\", \"KJFK\", \"KEWR\"]),\n // Chicago: Polymarket uses KORD. KMDW is the common wrong answer.\n chicago: new Set([\"KMDW\"]),\n // Houston: Phase 23 moved Polymarket to KHOU. KIAH (Kalshi's station) is\n // now the cross-venue wrong answer for a Polymarket Houston market.\n houston: new Set([\"KIAH\"]),\n // Dallas: Phase 23 moved Polymarket to KDAL. KDFW (Kalshi's station) is\n // now the cross-venue wrong answer.\n dallas: new Set([\"KDFW\"]),\n // SF: Polymarket uses KSFO. KOAK is the common wrong answer.\n san_francisco: new Set([\"KOAK\"]),\n });\n","// Phase 10 — composable research() dispatcher (TS port of\n// packages/core/src/mostlyright/_compose.py).\n//\n// Translates the new selectors (`city`, `contract`, `contracts`) into\n// resolution metadata + station lists. Pure logic, no I/O.\n\nimport {\n KALSHI_SETTLEMENT_STATIONS,\n type KalshiStation,\n POLYMARKET_CITY_STATIONS,\n} from \"@mostlyrightmd/markets\";\nimport { POLYMARKET_KNOWN_WRONG_STATIONS } from \"@mostlyrightmd/markets/polymarket\";\n\n/** The four mutually-exclusive selector names. */\nexport const SELECTOR_NAMES = [\"station\", \"city\", \"contract\", \"contracts\"] as const;\nexport type SelectorName = (typeof SELECTOR_NAMES)[number];\n\n/**\n * Kalshi short-ticker → canonical city slug. Real Kalshi tickers use\n * variable-length city suffixes: `KXHIGHNY-...` (NY → NYC),\n * `KXHIGHCHI-...` (CHI → CHI). The `KALSHI_SETTLEMENT_STATIONS` catalog\n * is keyed by the canonical 3-letter slug; this alias normalizes the\n * variable-length Kalshi suffix to the catalog key before lookup.\n */\nconst KALSHI_TICKER_ALIASES: Record<string, string> = {\n NY: \"NYC\",\n};\n\n/**\n * Kalshi-short ↔ Polymarket-long city slug alias. Iter-1 python-architect\n * HIGH: without this, `resolveCity(\"LAX\")` would miss Polymarket's KLAX\n * (keyed as `los_angeles`); `resolveCity(\"chicago\")` would miss Kalshi's\n * KMDW (keyed as `CHI`). Bi-directional probe surfaces the full\n * cross-issuer settlement neighborhood regardless of which slug form\n * the caller passed.\n */\nconst CITY_SLUG_ALIASES: Record<string, readonly [string, string]> = {\n // short_kalshi (lower) → [polymarket_long, kalshi_upper]\n nyc: [\"nyc\", \"NYC\"],\n chi: [\"chicago\", \"CHI\"],\n lax: [\"los_angeles\", \"LAX\"],\n mia: [\"miami\", \"MIA\"],\n den: [\"denver\", \"DEN\"],\n bos: [\"boston\", \"BOS\"],\n aus: [\"austin\", \"AUS\"],\n dca: [\"washington_dc\", \"DCA\"],\n phl: [\"philadelphia\", \"PHL\"],\n sfo: [\"san_francisco\", \"SFO\"],\n sea: [\"seattle\", \"SEA\"],\n atl: [\"atlanta\", \"ATL\"],\n hou: [\"houston\", \"HOU\"],\n dal: [\"dallas\", \"DAL\"],\n phx: [\"phoenix\", \"PHX\"],\n msp: [\"minneapolis\", \"MSP\"],\n dtw: [\"detroit\", \"DTW\"],\n};\n\nconst CITY_SLUG_ALIASES_REVERSE: Record<string, readonly [string, string]> = (() => {\n const out: Record<string, readonly [string, string]> = {};\n for (const [shortLower, [longPoly, kalshiUpper]] of Object.entries(CITY_SLUG_ALIASES)) {\n out[longPoly] = [shortLower, kalshiUpper];\n }\n return out;\n})();\n\n/** Return `[polymarket_slug_lower, kalshi_slug_upper]` for `city`. */\nfunction normalizeCitySlugs(city: string): readonly [string, string] {\n const lower = city.toLowerCase();\n const upper = city.toUpperCase();\n const direct = CITY_SLUG_ALIASES[lower];\n if (direct !== undefined) return direct;\n const reverse = CITY_SLUG_ALIASES_REVERSE[lower];\n if (reverse !== undefined) return [lower, reverse[1]];\n return [lower, upper];\n}\n\n/**\n * Structured warning emitted when `stationOverride` deliberately\n * mismatches the contract's canonical settlement station. The output\n * row carries `settlementMismatch: true`.\n *\n * JS has no `warnings.warn()` analogue; callers receive these via the\n * `onWarning?` callback in ResearchOptions.\n */\nexport interface StationOverrideWarning {\n readonly kind: \"StationOverrideWarning\";\n readonly contractStation: string;\n readonly overrideStation: string;\n readonly message: string;\n}\n\n/** Selector kwargs accepted by research(). Exactly one MUST be provided. */\nexport interface SelectorArgs {\n readonly station?: string;\n readonly city?: string;\n readonly contract?: string;\n readonly contracts?: ReadonlyArray<string>;\n}\n\n/**\n * Validate selector arity. Returns the active selector name; throws when\n * zero or >1 selectors are provided.\n */\nexport function validateSelectors(args: SelectorArgs): SelectorName {\n const provided: SelectorName[] = [];\n if (typeof args.station === \"string\" && args.station.length > 0) provided.push(\"station\");\n if (typeof args.city === \"string\" && args.city.length > 0) provided.push(\"city\");\n if (typeof args.contract === \"string\" && args.contract.length > 0) provided.push(\"contract\");\n if (Array.isArray(args.contracts) && args.contracts.length > 0) provided.push(\"contracts\");\n\n if (provided.length === 0) {\n throw new Error(\n \"research(): exactly one of station, city, contract, contracts must be provided\",\n );\n }\n if (provided.length > 1) {\n throw new Error(\n `research(): selectors are mutually exclusive; got ${JSON.stringify(provided)}`,\n );\n }\n return provided[0] as SelectorName;\n}\n\n/**\n * Resolve a `\"<issuer>:<id>\"` contract id to `[station, issuer]`.\n *\n * Supported: `kalshi:KHIGH<CITY>` / `kalshi:KXHIGH<CITY>-<DATE>-<STRIKE>`\n * and `kalshi:KLOW<CITY>` / `kalshi:KXLOW<CITY>-<DATE>-<STRIKE>`.\n *\n * Polymarket contract resolution requires an event_id → station lookup\n * (via polymarket-discover); Phase 10 v0.2 defers to v0.3 and throws.\n */\nexport function resolveContract(contractId: string): readonly [string, string] {\n if (typeof contractId !== \"string\" || !contractId.includes(\":\")) {\n throw new TypeError(`contract id must be \\`<issuer>:<id>\\`; got ${JSON.stringify(contractId)}`);\n }\n const colonIdx = contractId.indexOf(\":\");\n const issuer = contractId.slice(0, colonIdx).toLowerCase();\n const raw = contractId.slice(colonIdx + 1);\n const rawUpper = raw.toUpperCase();\n\n if (issuer === \"kalshi\") {\n // Strip KX exchange prefix (KXHIGHNYC → KHIGHNYC) and trailing\n // -DATE-STRIKE suffix to recover the legacy KHIGH<CITY> / KLOW<CITY>\n // shape the KALSHI_SETTLEMENT_STATIONS map keys are derived from.\n let normalized = rawUpper;\n if (normalized.startsWith(\"KX\")) {\n normalized = `K${normalized.slice(2)}`;\n }\n const cityOnly = normalized.split(\"-\", 1)[0] ?? \"\";\n let cityTickerRaw: string | null = null;\n if (cityOnly.startsWith(\"KHIGH\") && cityOnly.length > 5) {\n cityTickerRaw = cityOnly.slice(5);\n } else if (cityOnly.startsWith(\"KLOW\") && cityOnly.length > 4) {\n cityTickerRaw = cityOnly.slice(4);\n } else {\n throw new Error(\n `unsupported kalshi contract format: ${JSON.stringify(raw)}; expected KHIGH<CITY>* / KXHIGH<CITY>* / KLOW<CITY>* / KXLOW<CITY>* prefix`,\n );\n }\n // Iter-1 codex HIGH: normalize variable-length Kalshi ticker suffix\n // (NY → NYC, etc.) via the alias table before the catalog lookup.\n const cityTicker = KALSHI_TICKER_ALIASES[cityTickerRaw] ?? cityTickerRaw;\n const entry: KalshiStation | undefined = KALSHI_SETTLEMENT_STATIONS[cityTicker];\n if (entry === undefined) {\n throw new Error(`unknown Kalshi city ticker: ${JSON.stringify(cityTicker)}`);\n }\n return [entry.station, \"kalshi\"] as const;\n }\n if (issuer === \"polymarket\") {\n throw new Error(\n \"polymarket contract resolution requires event_id → station lookup via \" +\n \"polymarketDiscover()/polymarketSettle(); Phase 10 v0.2 defers this to \" +\n \"v0.3. Use `city: 'nyc'` or pass `stationOverride` until then.\",\n );\n }\n throw new Error(\n `unknown issuer prefix: ${JSON.stringify(issuer)}; expected kalshi or polymarket`,\n );\n}\n\n/**\n * Resolve a city slug to all stations any issuer settles against.\n * Returns deduplicated array in stable order: Kalshi → Polymarket default/high/low\n * → Polymarket denylist backstops.\n */\nexport function resolveCity(city: string): readonly string[] {\n if (typeof city !== \"string\" || !city) {\n throw new Error(`city must be a non-empty string; got ${JSON.stringify(city)}`);\n }\n // Iter-1 python-architect HIGH: cross-issuer slug alias surfaces the\n // full settlement neighborhood for either input form.\n const [polySlug, kalshiSlug] = normalizeCitySlugs(city);\n const out: string[] = [];\n\n const kalshi = KALSHI_SETTLEMENT_STATIONS[kalshiSlug];\n if (kalshi !== undefined && !out.includes(kalshi.station)) {\n out.push(kalshi.station);\n }\n const poly = POLYMARKET_CITY_STATIONS[polySlug];\n if (poly !== undefined) {\n for (const measure of [\"default\", \"high\", \"low\"] as const) {\n const st = poly[measure];\n if (typeof st === \"string\" && !out.includes(st)) out.push(st);\n }\n }\n const wrong = POLYMARKET_KNOWN_WRONG_STATIONS[polySlug];\n if (wrong !== undefined) {\n const sortedWrong = [...wrong].sort();\n for (const st of sortedWrong) {\n if (!out.includes(st)) out.push(st);\n }\n }\n if (out.length === 0) {\n throw new Error(`unknown city ${JSON.stringify(city)}; not in kalshi or polymarket catalogs`);\n }\n return out;\n}\n\n/**\n * Return the list of `\"<issuer>:<ticker>\"` markers that settle against\n * `station` for `city`. Empty array when no issuer settles against this\n * station (typically a denylist backstop).\n */\nexport function annotateSettlesFor(station: string, city: string | null): readonly string[] {\n if (city === null) return [];\n // Iter-1 python-architect HIGH: cross-issuer slug alias annotates both\n // issuers regardless of slug form.\n const [polySlug, kalshiSlug] = normalizeCitySlugs(city);\n const out: string[] = [];\n const kalshi = KALSHI_SETTLEMENT_STATIONS[kalshiSlug];\n if (kalshi !== undefined && kalshi.station === station) {\n out.push(`kalshi:${kalshiSlug}`);\n }\n const poly = POLYMARKET_CITY_STATIONS[polySlug];\n if (poly !== undefined) {\n for (const measure of [\"default\", \"high\", \"low\"] as const) {\n if (poly[measure] === station) {\n out.push(`polymarket:${polySlug}`);\n break;\n }\n }\n }\n return out.sort();\n}\n\n/**\n * Build a structured `StationOverrideWarning` payload. Callers receive\n * these via the optional `onWarning?` callback on research options.\n */\nexport function buildOverrideWarning(\n contractStation: string,\n overrideStation: string,\n): StationOverrideWarning {\n return {\n kind: \"StationOverrideWarning\",\n contractStation,\n overrideStation,\n message: `stationOverride=${JSON.stringify(overrideStation)} differs from contract's canonical settlement station ${JSON.stringify(contractStation)}; output row will carry settlementMismatch=true`,\n };\n}\n","// Phase 10 — discover({city}) ergonomic surface (TS port of\n// packages/core/src/mostlyright/discover.py).\n//\n// Pre-research lookup. Shows quants which station settles which issuer's\n// market for a given city so they can pick the right selector before\n// invoking research(). Especially useful for cross-issuer cities like NYC\n// where Kalshi settles against KNYC and Polymarket against KLGA.\n\nimport { annotateSettlesFor, resolveCity } from \"./compose.js\";\n\n/** One row per station in the resolved city neighborhood. */\nexport interface DiscoverRow {\n /** Echo of the input city. */\n readonly city: string;\n /** 4-char K-prefix ICAO. */\n readonly station: string;\n /**\n * `\"<issuer>:<ticker>\"` markers that resolve against this station.\n * Empty array = denylist backstop surfaced for explicit awareness.\n */\n readonly settlesFor: ReadonlyArray<string>;\n}\n\n/** Envelope mirrors the Python `df.attrs` pattern. */\nexport interface DiscoverResult {\n readonly rows: ReadonlyArray<DiscoverRow>;\n readonly city: string;\n readonly source: \"discover\";\n}\n\n/**\n * Return per-station discovery table for `city`.\n *\n * Each row shows one settlement station + the issuer:ticker markers that\n * resolve against it. Stations in the per-city Polymarket denylist also\n * appear with empty `settlesFor` so quants see the full neighborhood\n * before deciding whether to use `stationOverride`.\n *\n * @example\n * const result = discover({ city: \"NYC\" });\n * // rows include {station: \"KNYC\", settlesFor: [\"kalshi:NYC\"]},\n * // {station: \"KLGA\", settlesFor: [\"polymarket:nyc\"]},\n * // {station: \"KJFK\", settlesFor: []}, // denylist\n * // {station: \"KEWR\", settlesFor: []}.\n */\nexport function discover(args: { readonly city: string }): DiscoverResult {\n if (typeof args !== \"object\" || args === null) {\n throw new TypeError(`discover(): args must be an object; got ${typeof args}`);\n }\n const stations = resolveCity(args.city);\n const rows: DiscoverRow[] = stations.map((station) => ({\n city: args.city,\n station,\n settlesFor: annotateSettlesFor(station, args.city),\n }));\n return Object.freeze({\n rows: Object.freeze(rows),\n city: args.city,\n source: \"discover\" as const,\n });\n}\n","// TS-W4 Plan 04 Task 2 — clipOutliers (winsorize) + PHYSICS_BOUNDS.\n//\n// Pure row→row port of Python `mostlyright.preprocessing.clip_outliers` at\n// packages/core/src/mostlyright/preprocessing.py:49-91. The v0.1.0 canonical\n// surface (supersedes the older `transforms.clip_outliers`).\n//\n// Decision tree (mirrors Python preprocessing.py:75-91):\n// 1. opts.bounds set → clip to explicit [lo, hi]\n// 2. PHYSICS_BOUNDS.has(col) → clip to physics defaults\n// 3. else → sigma fallback (mu ± std*sigma)\n//\n// Phase 3.5 review-iter HIGH fixes (preserved here):\n// - Architect iter-1 HIGH: std<=0 in the sigma branch silently collapses\n// every row to the mean. Python raises ValueError; we throw RangeError.\n// - Sigma=0 pass-through: when all values are identical, sample sigma is\n// zero and the clamp [mu, mu] would collapse the column. Pass values\n// through unchanged instead (a TS-side improvement on top of Python).\n//\n// Numeric coercion is STRICT: only `typeof v === 'number' && Number.isFinite(v)`\n// passes through. Strings like '5' do NOT auto-parse. Matches Wave 2/3/04-task1.\n\n/**\n * Physics-based clipping defaults for canonical observation columns.\n *\n * Mirrors Python `mostlyright.preprocessing.PHYSICS_BOUNDS` (preprocessing.py:34-46).\n * Values are `[min, max]` tuples in canonical units (°C for temp, m/s and kt\n * for wind, hPa for pressure, percent for humidity, mm for precip).\n *\n * Both `dew_point_c`/`dewpoint_c` and `wind_dir_deg`/`wind_dir_degrees` are\n * aliased to support legacy + canonical column names.\n */\nexport const PHYSICS_BOUNDS: ReadonlyMap<string, readonly [number, number]> = new Map([\n [\"temp_c\", [-89.0, 57.0] as const],\n [\"dew_point_c\", [-89.0, 35.0] as const],\n [\"dewpoint_c\", [-89.0, 35.0] as const],\n [\"wind_speed_ms\", [0.0, 100.0] as const],\n [\"wind_speed_kt\", [0.0, 200.0] as const],\n [\"wind_dir_deg\", [0.0, 360.0] as const],\n [\"wind_dir_degrees\", [0.0, 360.0] as const],\n [\"slp_hpa\", [870.0, 1085.0] as const],\n [\"sea_level_pressure_mb\", [870.0, 1085.0] as const],\n [\"relative_humidity_pct_2m\", [0.0, 100.0] as const],\n [\"precip_mm_1h\", [0.0, 305.0] as const],\n]);\n\nexport interface ClipOutliersOptions {\n /** Explicit `[lo, hi]` range. Overrides PHYSICS_BOUNDS and sigma fallback. */\n bounds?: readonly [number, number];\n /** Sigma multiplier for the fallback branch. Default 3.0. Must be > 0. */\n std?: number;\n}\n\n/**\n * Winsorize a numeric column.\n *\n * Mirrors Python `mostlyright.preprocessing.clip_outliers`. Returns rows with\n * a derived `{col}_clipped` column; the source `col` is preserved unchanged.\n *\n * Decision tree:\n * - `opts.bounds` set → clip to explicit `[lo, hi]`\n * - `PHYSICS_BOUNDS.has(col)` → clip to physics defaults\n * - else → sigma fallback (`mu ± std*sigma`)\n *\n * **Phase 3.5 review-iter fixes:**\n * - Throws `RangeError` if `std ≤ 0` in the sigma fallback (matches Python\n * `ValueError` at preprocessing.py:84-88; silent dataset corruption\n * otherwise).\n * - Sigma=0 pass-through: when all values are identical, sample sigma is\n * zero and the clamp `[mu, mu]` would collapse the column. Pass values\n * through unchanged instead.\n *\n * @param rows input rows (NOT mutated; pure function)\n * @param col column to clip\n * @param opts optional bounds / std overrides; defaults: PHYSICS_BOUNDS or sigma=3\n * @returns new array of rows, each carrying `{col}_clipped`\n * @throws RangeError if sigma fallback would use `std <= 0` or non-finite std\n */\nexport function clipOutliers<Row extends Record<string, unknown>>(\n rows: ReadonlyArray<Row>,\n col: string,\n opts: ClipOutliersOptions = {},\n): ReadonlyArray<Row & Record<string, number | null>> {\n const std = opts.std ?? 3.0;\n const key = `${col}_clipped`;\n\n // Determine clip range. `passThrough` short-circuits to \"copy value unchanged\"\n // for the sigma=0 / n<2 edge cases (Phase 3.5 review-iter HIGH fix).\n let lo: number;\n let hi: number;\n let passThrough = false;\n\n if (opts.bounds !== undefined) {\n [lo, hi] = opts.bounds;\n } else if (PHYSICS_BOUNDS.has(col)) {\n const b = PHYSICS_BOUNDS.get(col);\n if (b === undefined) {\n // Unreachable (we just checked has()), but the narrowing requires it.\n throw new Error(`PHYSICS_BOUNDS.get(${col}) unexpectedly undefined`);\n }\n [lo, hi] = b;\n } else {\n // Sigma fallback. Architect iter-1 HIGH: std<=0 collapses to mu.\n if (!Number.isFinite(std) || std <= 0) {\n throw new RangeError(\n `clipOutliers: std must be > 0 for the sigma fallback (got ${std}); pass bounds=[lo, hi] or use a physics-default column`,\n );\n }\n // Compute mu + sigma over non-null finite values.\n const vals: number[] = [];\n for (const r of rows) {\n const v = r?.[col];\n if (typeof v === \"number\" && Number.isFinite(v)) vals.push(v);\n }\n if (vals.length < 2) {\n // Not enough values to compute sample sigma → pass-through.\n passThrough = true;\n lo = Number.NEGATIVE_INFINITY;\n hi = Number.POSITIVE_INFINITY;\n } else {\n const mu = vals.reduce((a, b) => a + b, 0) / vals.length;\n const sumSq = vals.reduce((a, b) => a + (b - mu) ** 2, 0);\n const sigma = Math.sqrt(sumSq / (vals.length - 1)); // sample stdev (Bessel n-1)\n if (sigma === 0 || !Number.isFinite(sigma)) {\n // Phase 3.5 review-iter HIGH: pass values through unchanged\n // instead of collapsing to [mu, mu] (NOT NaN, NOT mu).\n passThrough = true;\n lo = Number.NEGATIVE_INFINITY;\n hi = Number.POSITIVE_INFINITY;\n } else {\n lo = mu - std * sigma;\n hi = mu + std * sigma;\n }\n }\n }\n\n const out: Array<Row & Record<string, number | null>> = [];\n for (const r of rows) {\n const v = r?.[col];\n let clipped: number | null;\n if (typeof v === \"number\" && Number.isFinite(v)) {\n clipped = passThrough ? v : Math.min(Math.max(v, lo), hi);\n } else {\n clipped = null;\n }\n out.push({ ...(r as Row), [key]: clipped } as Row & Record<string, number | null>);\n }\n return out;\n}\n","// TS-W4 Plan 06 — crosscheckIemGhcnh: disagreement detection between IEM +\n// GHCNh temperature readings. Mirrors Python\n// `mostlyright.qc.crosscheck_iem_ghcnh` at\n// `packages/core/src/mostlyright/qc.py:191-228`.\n//\n// Inner-joins by composite key `(station, eventTime)`. For matched pairs\n// where both temp_c values are finite numbers and the absolute delta\n// exceeds `opts.tolC` (default 2.0 °C), emits a disagreement row.\n//\n// Threshold is STRICT `>` (NOT `>=`) per Python qc.py:228 —\n// `merged.loc[merged[\"delta_c\"] > tol_c]`. A delta exactly equal to the\n// tolerance produces NO disagreement.\n//\n// Parity-Ticket: Python returns snake_case keys\n// (event_time, temp_c_iem, temp_c_ghcnh, delta_c); TS returns camelCase\n// (eventTime, tempCIem, tempCGhcnh, deltaC) to match the TS-idiom used\n// elsewhere in the codebase (see `obsQcStatus` from Wave 5). Wire-format\n// conversion to snake_case happens at the JSON serializer boundary\n// (TS-W3 Plan 07 `jsonDumps`).\n//\n// Lives at the `@mostlyrightmd/core/qc` subpath (NOT root barrel) to keep\n// the main `@mostlyrightmd/core` bundle under its 25 KB size-limit gate.\n\n/** Options for {@link crosscheckIemGhcnh}. */\nexport interface CrosscheckOptions {\n /**\n * Maximum acceptable absolute delta in °C between paired IEM/GHCNh\n * `temp_c` values. Defaults to `2.0` °C (matches Python\n * `crosscheck_iem_ghcnh(tol_c=2.0)`). A delta strictly greater than\n * `tolC` produces a disagreement row; equality does NOT.\n */\n tolC?: number;\n}\n\n/**\n * Disagreement row emitted by {@link crosscheckIemGhcnh}. Keys are\n * camelCase per the TS-idiom Parity-Ticket; Python's snake_case\n * equivalents are `event_time`, `temp_c_iem`, `temp_c_ghcnh`, `delta_c`.\n */\nexport interface CrosscheckDisagreement {\n readonly station: string;\n readonly eventTime: string;\n readonly tempCIem: number;\n readonly tempCGhcnh: number;\n readonly deltaC: number;\n}\n\n/**\n * Minimal row shape consumed by {@link crosscheckIemGhcnh}. Rows MUST\n * carry `station: string`, `eventTime: string`, and `temp_c: number |\n * null` (or `undefined`/non-finite, which are skipped). Additional keys\n * are allowed and ignored.\n */\ninterface CrosscheckRowIn {\n station?: unknown;\n eventTime?: unknown;\n temp_c?: unknown;\n}\n\n/**\n * Cross-check IEM and GHCNh temperatures; return rows where the two\n * sources disagree above `opts.tolC` (default 2.0 °C).\n *\n * Algorithm:\n * 1. If `iemRows.length === 0 || ghcnhRows.length === 0` → return `[]`\n * (matches Python qc.py:212-215).\n * 2. Validate `station` + `eventTime` present (string) on every input\n * row; throw `Error` on first violation (parity with Python\n * `ValueError` at qc.py:217-220).\n * 3. Build `iemMap: Map<string, IemRow>` keyed by\n * `${row.station}|${row.eventTime}`. On duplicate keys, LAST iem row\n * wins — deterministic but a documented deviation from Python's\n * `pd.merge` (which would cartesian-product duplicates).\n * 4. For each GHCNh row, look up the matching IEM row by composite key.\n * If missing → skip. If either `temp_c` is null / non-finite →\n * skip.\n * 5. If `Math.abs(iem.temp_c - ghcnh.temp_c) > tolC` → emit a\n * disagreement row. STRICT `>` (NOT `>=`).\n *\n * Output array order matches the iteration order of `ghcnhRows`\n * (deterministic, independent of `iemRows` order).\n *\n * Pure: input arrays are NOT mutated.\n *\n * @param iemRows IEM observation rows.\n * @param ghcnhRows GHCNh observation rows.\n * @param opts Tolerance options. `tolC` default = 2.0.\n * @throws Error if any iem or ghcnh row is missing `station` or\n * `eventTime` (or they are not strings).\n */\nexport function crosscheckIemGhcnh(\n iemRows: ReadonlyArray<CrosscheckRowIn>,\n ghcnhRows: ReadonlyArray<CrosscheckRowIn>,\n opts: CrosscheckOptions = {},\n): ReadonlyArray<CrosscheckDisagreement> {\n const tolC = opts.tolC ?? 2.0;\n\n if (iemRows.length === 0 || ghcnhRows.length === 0) return [];\n\n // Validate column presence upfront (parity with Python ValueError).\n for (const r of iemRows) {\n if (typeof r?.station !== \"string\" || typeof r?.eventTime !== \"string\") {\n throw new Error(\n \"crosscheckIemGhcnh: iem rows must carry 'station' (string) and 'eventTime' (string) keys\",\n );\n }\n }\n for (const r of ghcnhRows) {\n if (typeof r?.station !== \"string\" || typeof r?.eventTime !== \"string\") {\n throw new Error(\n \"crosscheckIemGhcnh: ghcnh rows must carry 'station' (string) and 'eventTime' (string) keys\",\n );\n }\n }\n\n // Build iem lookup map. Last-wins on duplicate (station, eventTime).\n const iemMap = new Map<string, CrosscheckRowIn>();\n for (const r of iemRows) {\n const key = `${r.station as string}|${r.eventTime as string}`;\n iemMap.set(key, r);\n }\n\n const out: CrosscheckDisagreement[] = [];\n for (const g of ghcnhRows) {\n const key = `${g.station as string}|${g.eventTime as string}`;\n const i = iemMap.get(key);\n if (i === undefined) continue;\n const iT = typeof i.temp_c === \"number\" && Number.isFinite(i.temp_c) ? i.temp_c : null;\n const gT = typeof g.temp_c === \"number\" && Number.isFinite(g.temp_c) ? g.temp_c : null;\n if (iT === null || gT === null) continue;\n const delta = Math.abs(iT - gT);\n if (delta > tolC) {\n out.push({\n station: g.station as string,\n eventTime: g.eventTime as string,\n tempCIem: iT,\n tempCGhcnh: gT,\n deltaC: delta,\n });\n }\n }\n return out;\n}\n","// mostlyright (meta) — convenience re-export of the three scoped packages.\n// Use this if you want a single `import { research } from \"mostlyright\"` entry point;\n// otherwise import the scoped packages directly.\n//\n// Note: each underlying package exports its own `version` constant; to avoid\n// ambiguous re-exports we expose them under namespaced module objects in\n// addition to a top-level `version` for the meta package itself.\n\nexport { helloCore } from \"@mostlyrightmd/core\";\nexport { helloWeather } from \"@mostlyrightmd/weather\";\nexport { helloMarkets } from \"@mostlyrightmd/markets\";\n\nimport * as core from \"@mostlyrightmd/core\";\nimport * as markets from \"@mostlyrightmd/markets\";\nimport * as weather from \"@mostlyrightmd/weather\";\n\nexport { core, markets, weather };\n\n// TS-W2 Wave 4: full multi-source `research()` orchestrator (AWC + IEM\n// ASOS + GHCNh + CLI). Lives here (NOT in @mostlyrightmd/core) so the core\n// package stays dep-free; the orchestrator pulls in both core + weather.\n// `PairsRow` is the canonical row shape from @mostlyrightmd/core/internal/pairs.\nexport { research, type ResearchOptions, type PairsRow } from \"./research.js\";\n\n// TS-W4 Wave 1: Mode 2 source-explicit dispatch (researchBySource +\n// assertSourceIdentity + Mode2Source const-union). Lives in the meta\n// package alongside research() — the dispatch needs the @mostlyrightmd/weather\n// Observation type and assertSourceIdentity consumes it structurally;\n// @mostlyrightmd/core must NOT depend on weather (cycle).\nexport {\n MODE2_SOURCES,\n SOURCE_ALIASES,\n assertSourceIdentity,\n isMode2Source,\n researchBySource,\n type Mode2Source,\n type ResearchBySourceOptions,\n type SourceMismatchRole,\n} from \"./mode2.js\";\n\n// Phase 10: composable research() dispatcher + discover() ergonomic\n// surface. Lives in @mostlyrightmd/meta because compose.ts pulls in both\n// @mostlyrightmd/core (cache, station registry) and @mostlyrightmd/markets\n// (Kalshi catalog + Polymarket catalog + denylist) — keeping it in\n// core would create a cycle (markets depends on core).\nexport {\n SELECTOR_NAMES,\n annotateSettlesFor,\n buildOverrideWarning,\n resolveCity,\n resolveContract,\n validateSelectors,\n type SelectorArgs,\n type SelectorName,\n type StationOverrideWarning,\n} from \"./compose.js\";\n\nexport { discover, type DiscoverResult, type DiscoverRow } from \"./discover.js\";\n\n// Phase 11 — `mostlyright.live` ticker surface re-exported through the\n// meta package so all three import shapes resolve to the same surface:\n// import { stream, latest } from \"mostlyright\" // meta\n// import { stream } from \"@mostlyrightmd/weather\" // main barrel\n// import { stream } from \"@mostlyrightmd/weather/live\" // subpath\nexport {\n POLITE_FLOORS_S,\n SOURCE_IDENTITY_TAGS,\n SUPPORTED_SOURCES,\n isLiveSource,\n latest,\n sourceTag,\n stream,\n validatePollSeconds,\n validateSource,\n type LatestOptions,\n type LiveObservation,\n type LiveSource,\n type LiveSourceTag,\n type StreamOptions,\n} from \"@mostlyrightmd/weather\";\nexport { LiveStreamError, NoLiveDataError } from \"@mostlyrightmd/core\";\n\n// Phase 21 21-05 — dailyExtremes wrapper surfaced via meta so callers\n// can `import { dailyExtremes } from \"mostlyright\"` matching Python\n// `mostlyright.international.daily_extremes`.\nexport {\n dailyExtremes,\n type DailyExtremeRow,\n type DailyExtremesMergeMode,\n type DailyExtremesOptions,\n} from \"@mostlyrightmd/weather\";\n\n// Phase 21 21-04 — obs(station, from, to, opts?) Phase 7 ingest-planner\n// surface re-exported via meta.\nexport {\n obs,\n type ObsOptions,\n type ObsRow,\n type ObsSourceFilter,\n type ObsStrategy,\n} from \"@mostlyrightmd/weather\";\n\n// Phase 21 21-10 — `preprocessing` lowercase namespace, matching Python\n// `mostlyright.preprocessing`. Surfaces `clipOutliers`, `PHYSICS_BOUNDS`,\n// `iemCrosscheck` under a single namespace so cross-language code reads\n// lexically symmetric:\n//\n// Python: from mostlyright.preprocessing import clip_outliers\n// TS: import { preprocessing } from \"mostlyright\"\n// preprocessing.clipOutliers(...)\n//\n// The PascalCase `Preprocessing` alias below is the deprecated form\n// (TSDoc @deprecated triggers IDE strikethrough); scheduled for removal\n// in v2.0.0. Both aliases point at the same module object.\nimport * as preprocessing from \"@mostlyrightmd/core/preprocessing\";\n\nexport { preprocessing };\n\n/**\n * @deprecated Use the lowercase `preprocessing` namespace instead — it\n * matches the Python `mostlyright.preprocessing` namespace. The\n * PascalCase alias is scheduled for removal in v2.0.0.\n */\nexport const Preprocessing = preprocessing;\n\n/**\n * Placeholder version string for the meta package. The authoritative\n * package version lives in `package.json#version` (currently\n * `0.1.0-rc.7`); this constant has not been bumped. Sibling packages\n * (`@mostlyrightmd/core` / `weather` / `markets`) each export their own\n * `version` constant, exposed here via the namespaced module objects.\n */\nexport const version = \"0.0.0\";\n"],"mappings":"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;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;AACF;AAEO,IAAMC,mBAAoD,oBAAI,IAAyB;EAC5F,CAAC,OAAOD,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;AACxB,CAAC;AAEM,IAAME,mBAAoD,oBAAI,IAAyB;EAC5F,CAAC,QAAQF,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,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MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;MACA;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ,CAAC;EACX;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;IACJ,QAAQ;MACN;IACF;EACF;AACF;AAEO,IAAMC,mBAAoD,oBAAI,IAAyB;EAC5F,CAAC,OAAOD,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,OAAOA,UAAS,EAAE,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;AACxB,CAAC;AAEM,IAAME,mBAAoD,oBAAI,IAAyB;EAC5F,CAAC,QAAQF,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,CAAC,CAAE;EACrB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,CAAC,QAAQA,UAAS,EAAE,CAAE;EACtB,C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","names":["src_exports","STATION_CODE_RE","obs","sleep","DATE_RE","obs","obs","fetchWithRetry","STATION_CODE_RE","buildIemUrl","parseIemCsv","sleep","STATION_CODE_RE","maybeNumber","STATIONS","STATION_BY_CODE","STATION_BY_ICAO","minRow","maxRow","LOW_COVERAGE_THRESHOLD","cToF","roundHalfUp","obs","obs","src_exports","version","coerceContractDate","version","version","target","version","STATIONS","STATION_BY_CODE","STATION_BY_ICAO","isLiveSource","MIN_YEAR","MAX_YEAR","_STATION_TZ","_JAN_REF","_OFFSET_CACHE","_lstOffsetHours","obs","AWC_MAX_HOURS","DATE_RE","normalizeStation","parseIsoDate","isLiveSource","obs","AWC_MAX_HOURS","DATE_RE","obs","version"]}
|