mostlyright 0.1.0-rc.7

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.
@@ -0,0 +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","../../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/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.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","../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\nexport const version = \"0.0.0\";\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 type ForecastNwpOptions,\n type IemMosModel,\n type IemMosOptions,\n type IemMosRow,\n type IemMosSource,\n type NwpModel,\n} from \"./forecasts/index.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 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 {\n IemMosModel,\n IemMosOptions,\n IemMosRow,\n IemMosSource,\n} 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([\n \"nbe\",\n \"gfs\",\n \"lav\",\n \"met\",\n \"ecm\",\n]);\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/** Pick the right NBE runtime-hour set based on the requested range. */\nfunction runtimeHoursFor(\n model: IemMosModel,\n fromDt: Date,\n toDt: Date,\n): 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(\n (validDt.getTime() - issuedDt.getTime()) / 3_600_000,\n );\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(\n `iemMosForecasts: from/to dates must be ISO YYYY-MM-DD; got ${iso}`,\n );\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 const rows: IemMosRow[] = [];\n const dayMs = 86_400_000;\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 const url = `${IEM_MOS_URL}?station=${encodeURIComponent(\n station,\n )}&model=${encodeURIComponent(model)}&runtime=${encodeURIComponent(\n rt.toISOString(),\n )}`;\n const resp = await fetchFn(url);\n if (resp.status === 404) continue;\n if (!resp.ok) {\n throw new Error(\n `iemMosForecasts: HTTP ${resp.status} on ${url}`,\n );\n }\n const payload = (await resp.json()) as { data?: RawMosRow[] };\n for (const raw of payload.data ?? []) {\n const projected = parseRow(raw, station, model, retrievedAt);\n if (projected !== null) rows.push(projected);\n }\n }\n }\n return rows;\n}\n\nexport const __internal__ = {\n runtimeHoursFor,\n parseRow,\n NBE_CYCLE_CUTOVER,\n};\n","// Phase 17 PLAN-11 — TS `forecastNwp()` stub.\n//\n// Per CONTEXT decision 7: TS NWP is deferred to v1.1. 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 — v1.1 lands the execution body.\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 * Fetch a gridded NWP forecast — **v1.0 stub, deferred to v1.1**.\n *\n * Per Phase 17 CONTEXT decision 7, the TS NWP execution body is deferred\n * to v1.1 because no production-ready browser GRIB2 decoder exists in\n * May 2026. This function signature is stable so callers can ship code\n * today; v1.1 lands the fetch + decode wiring as a runtime upgrade with\n * no signature break.\n *\n * @throws `Error('forecastNwp: TS NWP deferred to v1.1 ...')`.\n */\nexport async function forecastNwp(\n _station: string,\n _model: NwpModel,\n _opts: ForecastNwpOptions = {},\n): Promise<never> {\n throw new Error(\n \"forecastNwp: TS NWP deferred to v1.1 per CONTEXT decision 7. \" +\n \"Browser GRIB2 decode is not production-ready in May 2026; the v1.0 \" +\n \"TS forecast surface ships iemMosForecasts() only. Use the Python \" +\n \"SDK's mostlyright.forecast_nwp() for NWP in v1.0.\",\n );\n}\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\nexport const version = \"0.0.0\";\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} 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 atlanta: {\n default: \"KATL\",\n },\n auckland: {\n default: \"NZAA\",\n },\n austin: {\n default: \"KAUS\",\n },\n bangkok: {\n default: \"VTBS\",\n },\n barcelona: {\n default: \"LEBL\",\n },\n beijing: {\n default: \"ZBAA\",\n },\n berlin: {\n default: \"EDDB\",\n },\n boston: {\n default: \"KBOS\",\n },\n brisbane: {\n default: \"YBBN\",\n },\n buenos_aires: {\n default: \"SAEZ\",\n },\n chicago: {\n default: \"KORD\",\n high: \"KORD\",\n low: \"KORD\",\n },\n copenhagen: {\n default: \"EKCH\",\n },\n dallas: {\n default: \"KDFW\",\n },\n delhi: {\n default: \"VIDP\",\n },\n denver: {\n default: \"KDEN\",\n },\n detroit: {\n default: \"KDTW\",\n },\n doha: {\n default: \"OTHH\",\n },\n dubai: {\n default: \"OMDB\",\n },\n frankfurt: {\n default: \"EDDF\",\n },\n helsinki: {\n default: \"EFHK\",\n },\n hong_kong: {\n default: \"VHHH\",\n high: \"VHHH\",\n low: \"VHHH\",\n },\n houston: {\n default: \"KIAH\",\n },\n london: {\n default: \"EGLL\",\n },\n london_gatwick: {\n default: \"EGKK\",\n },\n los_angeles: {\n default: \"KLAX\",\n high: \"KLAX\",\n low: \"KLAX\",\n },\n madrid: {\n default: \"LEMD\",\n },\n melbourne: {\n default: \"YMML\",\n },\n miami: {\n default: \"KMIA\",\n },\n milan: {\n default: \"LIMC\",\n },\n minneapolis: {\n default: \"KMSP\",\n },\n moscow: {\n default: \"UUEE\",\n },\n mumbai: {\n default: \"VABB\",\n },\n munich: {\n default: \"EDDM\",\n },\n nyc: {\n default: \"KLGA\",\n high: \"KLGA\",\n low: \"KLGA\",\n },\n paris: {\n default: \"LFPG\",\n high: \"LFPG\",\n low: \"LFPB\",\n },\n paris_orly: {\n default: \"LFPO\",\n },\n philadelphia: {\n default: \"KPHL\",\n },\n phoenix: {\n default: \"KPHX\",\n },\n riyadh: {\n default: \"OERK\",\n },\n rome: {\n default: \"LIRF\",\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 singapore: {\n default: \"WSSS\",\n },\n stockholm: {\n default: \"ESSA\",\n },\n sydney: {\n default: \"YSSY\",\n },\n taipei: {\n default: \"RCTP\",\n },\n tokyo: {\n default: \"RJTT\",\n high: \"RJTT\",\n low: \"RJTT\",\n },\n tokyo_narita: {\n default: \"RJAA\",\n },\n vienna: {\n default: \"LOWW\",\n },\n warsaw: {\n default: \"EPWA\",\n },\n washington_dc: {\n default: \"KDCA\",\n },\n wellington: {\n default: \"NZWN\",\n },\n zurich: {\n default: \"LSZH\",\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","// 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","// 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}\n\nexport const STATIONS: ReadonlyArray<StationInfo> = [\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\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 },\n] as const;\n\nexport const STATION_BY_CODE: ReadonlyMap<string, StationInfo> = new Map<string, StationInfo>([\n [\"ATL\", STATIONS[10]!],\n [\"AUS\", STATIONS[11]!],\n [\"BOS\", STATIONS[12]!],\n [\"DCA\", STATIONS[13]!],\n [\"DEN\", STATIONS[14]!],\n [\"DFW\", STATIONS[15]!],\n [\"EDDB\", STATIONS[0]!],\n [\"EDDF\", STATIONS[1]!],\n [\"EDDM\", STATIONS[2]!],\n [\"EFHK\", STATIONS[3]!],\n [\"EGKK\", STATIONS[4]!],\n [\"EGLL\", STATIONS[5]!],\n [\"EHAM\", STATIONS[6]!],\n [\"EKCH\", STATIONS[7]!],\n [\"EPWA\", STATIONS[8]!],\n [\"ESSA\", STATIONS[9]!],\n [\"HOU\", STATIONS[16]!],\n [\"LAS\", STATIONS[17]!],\n [\"LAX\", STATIONS[18]!],\n [\"LEBL\", STATIONS[30]!],\n [\"LEMD\", STATIONS[31]!],\n [\"LFPB\", STATIONS[32]!],\n [\"LFPG\", STATIONS[33]!],\n [\"LFPO\", STATIONS[34]!],\n [\"LIMC\", STATIONS[35]!],\n [\"LIRF\", STATIONS[36]!],\n [\"LOWW\", STATIONS[37]!],\n [\"LSZH\", STATIONS[38]!],\n [\"MDW\", STATIONS[19]!],\n [\"MIA\", STATIONS[20]!],\n [\"MSP\", STATIONS[21]!],\n [\"MSY\", STATIONS[22]!],\n [\"NYC\", STATIONS[23]!],\n [\"NZAA\", STATIONS[39]!],\n [\"NZWN\", STATIONS[40]!],\n [\"OERK\", STATIONS[41]!],\n [\"OKC\", STATIONS[24]!],\n [\"OMDB\", STATIONS[42]!],\n [\"OTHH\", STATIONS[43]!],\n [\"PHL\", STATIONS[25]!],\n [\"PHX\", STATIONS[26]!],\n [\"RCTP\", STATIONS[44]!],\n [\"RJAA\", STATIONS[45]!],\n [\"RJTT\", STATIONS[46]!],\n [\"RKSI\", STATIONS[47]!],\n [\"SAEZ\", STATIONS[48]!],\n [\"SAT\", STATIONS[27]!],\n [\"SBGR\", STATIONS[49]!],\n [\"SEA\", STATIONS[28]!],\n [\"SFO\", STATIONS[29]!],\n [\"UUEE\", STATIONS[50]!],\n [\"VABB\", STATIONS[51]!],\n [\"VHHH\", STATIONS[52]!],\n [\"VIDP\", STATIONS[53]!],\n [\"VTBS\", STATIONS[54]!],\n [\"WSSS\", STATIONS[55]!],\n [\"YBBN\", STATIONS[56]!],\n [\"YMML\", STATIONS[57]!],\n [\"YSSY\", STATIONS[58]!],\n [\"ZBAA\", STATIONS[59]!],\n [\"ZSPD\", STATIONS[60]!],\n]);\n\nexport const STATION_BY_ICAO: ReadonlyMap<string, StationInfo> = new Map<string, StationInfo>([\n [\"EDDB\", STATIONS[0]!],\n [\"EDDF\", STATIONS[1]!],\n [\"EDDM\", STATIONS[2]!],\n [\"EFHK\", STATIONS[3]!],\n [\"EGKK\", STATIONS[4]!],\n [\"EGLL\", STATIONS[5]!],\n [\"EHAM\", STATIONS[6]!],\n [\"EKCH\", STATIONS[7]!],\n [\"EPWA\", STATIONS[8]!],\n [\"ESSA\", STATIONS[9]!],\n [\"KATL\", STATIONS[10]!],\n [\"KAUS\", STATIONS[11]!],\n [\"KBOS\", STATIONS[12]!],\n [\"KDCA\", STATIONS[13]!],\n [\"KDEN\", STATIONS[14]!],\n [\"KDFW\", STATIONS[15]!],\n [\"KHOU\", STATIONS[16]!],\n [\"KLAS\", STATIONS[17]!],\n [\"KLAX\", STATIONS[18]!],\n [\"KMDW\", STATIONS[19]!],\n [\"KMIA\", STATIONS[20]!],\n [\"KMSP\", STATIONS[21]!],\n [\"KMSY\", STATIONS[22]!],\n [\"KNYC\", STATIONS[23]!],\n [\"KOKC\", STATIONS[24]!],\n [\"KPHL\", STATIONS[25]!],\n [\"KPHX\", STATIONS[26]!],\n [\"KSAT\", STATIONS[27]!],\n [\"KSEA\", STATIONS[28]!],\n [\"KSFO\", STATIONS[29]!],\n [\"LEBL\", STATIONS[30]!],\n [\"LEMD\", STATIONS[31]!],\n [\"LFPB\", STATIONS[32]!],\n [\"LFPG\", STATIONS[33]!],\n [\"LFPO\", STATIONS[34]!],\n [\"LIMC\", STATIONS[35]!],\n [\"LIRF\", STATIONS[36]!],\n [\"LOWW\", STATIONS[37]!],\n [\"LSZH\", STATIONS[38]!],\n [\"NZAA\", STATIONS[39]!],\n [\"NZWN\", STATIONS[40]!],\n [\"OERK\", STATIONS[41]!],\n [\"OMDB\", STATIONS[42]!],\n [\"OTHH\", STATIONS[43]!],\n [\"RCTP\", STATIONS[44]!],\n [\"RJAA\", STATIONS[45]!],\n [\"RJTT\", STATIONS[46]!],\n [\"RKSI\", STATIONS[47]!],\n [\"SAEZ\", STATIONS[48]!],\n [\"SBGR\", STATIONS[49]!],\n [\"UUEE\", STATIONS[50]!],\n [\"VABB\", STATIONS[51]!],\n [\"VHHH\", STATIONS[52]!],\n [\"VIDP\", STATIONS[53]!],\n [\"VTBS\", STATIONS[54]!],\n [\"WSSS\", STATIONS[55]!],\n [\"YBBN\", STATIONS[56]!],\n [\"YMML\", STATIONS[57]!],\n [\"YSSY\", STATIONS[58]!],\n [\"ZBAA\", STATIONS[59]!],\n [\"ZSPD\", STATIONS[60]!],\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 type { CacheStore } from \"./types.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\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 * 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 if (typeof indexedDB !== \"undefined\") return new IndexedDBStore();\n return new MemoryStore();\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","// `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 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\";\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 {\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 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/** Markets routed to v0.2 sources (CWA/HKO clients). */\nexport const DEFERRED_STATIONS: ReadonlySet<string> = new Set([\"VHHH\", \"RCTP\"]);\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 RCTP, Hong Kong VHHH) AND the market is low-extreme, raise\n// DeferredMarketError. The Python contract defers only HK-LOW + Taipei\n// because v0.2 will land CWA + HKO 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 if (extractedIcao === \"RCTP\") {\n throw new DeferredMarketError(\n `Polymarket market for station ${extractedIcao} is deferred until the v0.2 CWA client lands`,\n );\n }\n if (extractedIcao === \"VHHH\" && marketMeasure === \"low\") {\n throw new DeferredMarketError(\n `Polymarket low-extreme market for station ${extractedIcao} is deferred until the v0.2 HKO client lands`,\n );\n }\n if (DEFERRED_STATIONS.has(extractedIcao) && marketMeasure === \"default\") {\n throw new DeferredMarketError(\n `Polymarket market for deferred station ${extractedIcao} (measure=default) requires v0.2 client`,\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. Taipei (RCTP) defers all markets;\n // Hong Kong (VHHH) defers only the low market because HKO is the issuer\n // for the daily low. High markets at HK resolve via standard METAR.\n if (icao === \"RCTP\") {\n throw new DeferredMarketError(\n `Polymarket market for station ${icao} is deferred until the v0.2 CWA client lands`,\n );\n }\n if (icao === \"VHHH\" && marketMeasure === \"low\") {\n throw new DeferredMarketError(\n `Polymarket low-extreme market for station ${icao} is deferred until the v0.2 HKO client lands`,\n );\n }\n if (DEFERRED_STATIONS.has(icao) && marketMeasure === \"default\") {\n // Conservative default fallback for deferred stations.\n throw new DeferredMarketError(\n `Polymarket market for deferred station ${icao} (measure=default) requires v0.2 client`,\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: Polymarket uses KIAH. KHOU is the common wrong answer.\n houston: new Set([\"KHOU\"]),\n // Dallas: Polymarket uses KDFW. KDAL is the common wrong answer.\n dallas: new Set([\"KDAL\"]),\n // SF: Polymarket uses KSFO. KOAK is the common wrong answer.\n san_francisco: new Set([\"KOAK\"]),\n // DC: Polymarket uses KDCA. KIAD/KBWI are common wrong answers.\n washington_dc: new Set([\"KIAD\", \"KBWI\"]),\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","// 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\nexport const version = \"0.0.0\";\n"],"mappings":"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SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;EACA;IACE,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,UAAU;IACV,WAAW;IACX,MAAM;IACN,IAAI;EACN;AACF;AAEO,IAAMC,mBAAoD,oBAAI,IAAyB;EAC5F,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,QAAQ,SAAS,CAAC,CAAE;EACrB,CAAC,QAAQ,SAAS,CAAC,CAAE;EACrB,CAAC,QAAQ,SAAS,CAAC,CAAE;EACrB,CAAC,QAAQ,SAAS,CAAC,CAAE;EACrB,CAAC,QAAQ,SAAS,CAAC,CAAE;EACrB,CAAC,QAAQ,SAAS,CAAC,CAAE;EACrB,CAAC,QAAQ,SAAS,CAAC,CAAE;EACrB,CAAC,QAAQ,SAAS,CAAC,CAAE;EACrB,CAAC,QAAQ,SAAS,CAAC,CAAE;EACrB,CAAC,QAAQ,SAAS,CAAC,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,OAAO,SAAS,EAAE,CAAE;EACrB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SAAS,EAAE,CAAE;EACtB,CAAC,QAAQ,SA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","names":["src_exports","STATION_CODE_RE","sleep","DATE_RE","fetchWithRetry","STATION_CODE_RE","buildIemUrl","parseIemCsv","sleep","STATION_CODE_RE","src_exports","version","coerceContractDate","version","version","target","STATION_BY_CODE","STATION_BY_ICAO","isLiveSource","MIN_YEAR","MAX_YEAR","_STATION_TZ","_JAN_REF","_OFFSET_CACHE","_lstOffsetHours","AWC_MAX_HOURS","DATE_RE","normalizeStation","parseIsoDate","isLiveSource","AWC_MAX_HOURS","DATE_RE","version"]}