mostlyright 1.0.1 → 1.1.1
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.
- package/dist/{chunk-6ERO2BIY.mjs → chunk-TREZXL75.mjs} +32 -21
- package/dist/chunk-TREZXL75.mjs.map +1 -0
- package/dist/{iem-5RVPI3TY.mjs → iem-IO2HIL5V.mjs} +2 -2
- package/dist/index.bundle.mjs +2 -2
- package/dist/index.bundle.mjs.map +1 -1
- package/dist/index.global.js +40 -21
- package/dist/index.global.js.map +1 -1
- package/package.json +13 -4
- package/dist/chunk-6ERO2BIY.mjs.map +0 -1
- /package/dist/{iem-5RVPI3TY.mjs.map → iem-IO2HIL5V.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mostlyright",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Convenience meta-package for the mostlyright TypeScript SDK. Re-exports @mostlyrightmd/core + @mostlyrightmd/weather + @mostlyrightmd/markets so a single `import { research } from 'mostlyright'` works.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"homepage": "https://mostlyright.md/docs/sdk/",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/mostlyrightmd/mostlyright-sdk.git",
|
|
10
|
+
"directory": "packages-ts/meta"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/mostlyrightmd/mostlyright-sdk/issues"
|
|
14
|
+
},
|
|
6
15
|
"type": "module",
|
|
7
16
|
"main": "./dist/index.cjs",
|
|
8
17
|
"module": "./dist/index.mjs",
|
|
@@ -18,9 +27,9 @@
|
|
|
18
27
|
"dist"
|
|
19
28
|
],
|
|
20
29
|
"dependencies": {
|
|
21
|
-
"@mostlyrightmd/markets": "1.
|
|
22
|
-
"@mostlyrightmd/weather": "1.
|
|
23
|
-
"@mostlyrightmd/core": "1.
|
|
30
|
+
"@mostlyrightmd/markets": "1.1.1",
|
|
31
|
+
"@mostlyrightmd/weather": "1.1.1",
|
|
32
|
+
"@mostlyrightmd/core": "1.1.1"
|
|
24
33
|
},
|
|
25
34
|
"devDependencies": {
|
|
26
35
|
"msw": "2.6.6",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../core/src/internal/convert.ts","../../weather/src/_parsers/awc.ts","../../weather/src/_parsers/iem.ts"],"sourcesContent":["// Unit conversions for the mostlyright SDK.\n//\n// Ported from `packages/core/src/mostlyright/_internal/_convert.py`.\n//\n// CRITICAL: No rounding anywhere. Store float64 as-is. The Python module's\n// \"no _go_round / no round / no math.floor(x + 0.5)\" rule applies here too —\n// every formula returns the raw float64 result.\n\n// ---------------------------------------------------------------------------\n// Constants (exact-by-definition where possible)\n// ---------------------------------------------------------------------------\n\nexport const KT_TO_MPH = 1.15078;\n/** Exact: 1 knot = 1852 m / 3600 s. */\nexport const KT_TO_MS = 1852.0 / 3600.0;\n/** Exact by definition (mile → kilometre). */\nexport const MI_TO_KM = 1.609344;\n/** Exact by definition (mile → metre). */\nexport const MI_TO_M = 1609.344;\n/** Exact by definition (foot → metre). */\nexport const FT_TO_M = 0.3048;\n/** Exact by definition (inch → millimetre). */\nexport const IN_TO_MM = 25.4;\n/** WMO standard conversion factor (hectopascal → inHg). */\nexport const HPA_TO_INHG = 0.0295299875;\n\n// August–Roche–Magnus approximation (Alduchov & Eskridge 1996)\nexport const MAGNUS_A = 17.625;\n/** Celsius. */\nexport const MAGNUS_B = 243.04;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction finiteOrNull(v: number | null | undefined): number | null {\n if (v === null || v === undefined) return null;\n return Number.isFinite(v) ? v : null;\n}\n\n// ---------------------------------------------------------------------------\n// Public conversions\n// ---------------------------------------------------------------------------\n\nexport function ktToMs(kt: number | null): number | null {\n const v = finiteOrNull(kt);\n return v === null ? null : v * KT_TO_MS;\n}\n\nexport function ktToMph(kt: number | null): number | null {\n const v = finiteOrNull(kt);\n return v === null ? null : v * KT_TO_MPH;\n}\n\nexport function miToKm(mi: number | null): number | null {\n const v = finiteOrNull(mi);\n return v === null ? null : v * MI_TO_KM;\n}\n\nexport function miToM(mi: number | null): number | null {\n const v = finiteOrNull(mi);\n return v === null ? null : v * MI_TO_M;\n}\n\nexport function ftToM(ft: number | null): number | null {\n const v = finiteOrNull(ft);\n return v === null ? null : v * FT_TO_M;\n}\n\nexport function inchesToMm(inches: number | null): number | null {\n const v = finiteOrNull(inches);\n return v === null ? null : v * IN_TO_MM;\n}\n\nexport function celsiusToFahrenheit(c: number | null): number | null {\n const v = finiteOrNull(c);\n return v === null ? null : (v * 9) / 5 + 32;\n}\n\nexport function fahrenheitToCelsius(f: number | null): number | null {\n const v = finiteOrNull(f);\n return v === null ? null : ((v - 32) * 5) / 9;\n}\n\nexport function hpaToInhg(hpa: number | null): number | null {\n const v = finiteOrNull(hpa);\n return v === null ? null : v * HPA_TO_INHG;\n}\n\n/**\n * Compute relative humidity (%) from temperature and dewpoint (both Celsius)\n * via the August–Roche–Magnus approximation. Result is clamped to [0, 100];\n * non-finite or null inputs return null.\n */\nexport function computeRelativeHumidity(tempC: number | null, dewpC: number | null): number | null {\n const t = finiteOrNull(tempC);\n const td = finiteOrNull(dewpC);\n if (t === null || td === null) return null;\n // exp can overflow at extreme inputs; guard for safety.\n const numer = Math.exp((MAGNUS_A * td) / (MAGNUS_B + td));\n const denom = Math.exp((MAGNUS_A * t) / (MAGNUS_B + t));\n if (!Number.isFinite(numer) || !Number.isFinite(denom) || denom === 0) {\n return null;\n }\n const rh = (100.0 * numer) / denom;\n return Math.max(0.0, Math.min(rh, 100.0));\n}\n\n/**\n * Compute feels-like temperature in Fahrenheit (full NWS algorithm).\n *\n * - Wind chill at or below 50°F with wind > 3 mph.\n * - Heat index at or above 80°F with known RH.\n * - Plain temp otherwise. No rounding.\n *\n * `windKt` is in knots (matches Python signature); null is treated as zero.\n * `rh` is relative humidity in [0, 100]; null disables the heat-index branch.\n */\nexport function computeFeelsLike(\n tempF: number | null,\n windKt: number | null,\n rh: number | null,\n): number | null {\n const t = finiteOrNull(tempF);\n if (t === null) return null;\n\n let wMph = 0.0;\n if (windKt !== null && windKt !== undefined) {\n const w = Number.isFinite(windKt) ? windKt * KT_TO_MPH : null;\n if (w === null || !Number.isFinite(w)) return null;\n wMph = w;\n }\n\n // Treat non-finite rh as missing (don't feed NaN into heat index).\n let rhSafe: number | null = rh;\n if (rhSafe !== null && rhSafe !== undefined && !Number.isFinite(rhSafe)) {\n rhSafe = null;\n }\n\n // Wind chill (NWS): valid for temp <= 50°F and wind > 3 mph\n if (t <= 50.0 && wMph > 3.0) {\n return 35.74 + 0.6215 * t - 35.75 * wMph ** 0.16 + 0.4275 * t * wMph ** 0.16;\n }\n\n // Heat index (NWS): requires known RH\n if (t >= 80.0 && rhSafe !== null) {\n const h = rhSafe;\n // Step 1: Steadman simplified\n const simple = 0.5 * (t + 61.0 + (t - 68.0) * 1.2 + h * 0.094);\n if ((simple + t) / 2.0 < 80.0) {\n return simple;\n }\n\n // Step 2: Rothfusz regression\n let hi =\n -42.379 +\n 2.04901523 * t +\n 10.14333127 * h -\n 0.22475541 * t * h -\n 0.00683783 * t * t -\n 0.05481717 * h * h +\n 0.00122874 * t * t * h +\n 0.00085282 * t * h * h -\n 0.00000199 * t * t * h * h;\n\n // Step 3: NWS adjustments\n if (h < 13.0 && t >= 80.0 && t <= 112.0) {\n hi -= ((13.0 - h) / 4.0) * Math.sqrt((17.0 - Math.abs(t - 95.0)) / 17.0);\n } else if (h > 85.0 && t >= 80.0 && t <= 87.0) {\n hi += ((h - 85.0) / 10.0) * ((87.0 - t) / 5.0);\n }\n\n return hi;\n }\n\n return t;\n}\n","// AWC METAR parser — maps raw AWC JSON record to `Observation` row.\n//\n// Ported byte-faithfully from\n// `packages/weather/src/mostlyright/weather/_awc.py::awc_to_observation`.\n//\n// Output `source` is `\"awc\"` — matching the row-level enum\n// (`source: \"awc\" | \"iem\" | \"ghcnh\"` per\n// `packages-ts/core/src/schemas/generated/observation_qc.v1.ts`) and the\n// Python parser (`packages/weather/src/mostlyright/weather/_awc.py:320`).\n// The `.live` / `.archive` suffix is a CATALOG / orchestrator source-id\n// (a higher layer concern), NOT a row-level field.\n//\n// Bounds + conversion constants are imported from `@mostlyrightmd/core/internal/{bounds,convert}`\n// (subpath exports added in TS-W1 iter-1 HIGH 4). Any change to the\n// constants in core propagates here automatically — no drift.\n\nimport {\n MAX_RAW_METAR_LEN,\n MAX_VISIBILITY_MILES,\n MAX_WX_CODES_LEN,\n SKY_BASE_MAX_FT,\n SLP_MAX_MB,\n SLP_MIN_MB,\n STATION_CODE_RE,\n TEMP_MAX_C,\n TEMP_MIN_C,\n WIND_DIR_BOUNDS,\n WIND_GUST_MAX,\n WIND_SPEED_MAX,\n boundedFloat,\n boundedFloatMin,\n boundedInt,\n} from \"@mostlyrightmd/core/internal/bounds\";\nimport { celsiusToFahrenheit, hpaToInhg } from \"@mostlyrightmd/core/internal/convert\";\n\nimport type { AwcMetarRaw } from \"../_fetchers/awc.js\";\n\nconst [WIND_DIR_LO, WIND_DIR_HI] = WIND_DIR_BOUNDS;\n\n// ---------------------------------------------------------------------------\n// Observation row schema (matches specs/observation.json field set)\n// ---------------------------------------------------------------------------\n\n/**\n * Single observation row, matching `specs/observation.json`.\n *\n * Notes on null vs unknown:\n * - Every field defaults to `null` if the upstream record omits it,\n * fails bounds, or fails type parsing. This mirrors the Python\n * parser (no exceptions on bad input — only on missing required keys).\n * - `source` is always the string literal `\"awc\"` for AWC-sourced rows\n * (the row-level enum is `\"awc\" | \"iem\" | \"ghcnh\"`; the `.live`/`.archive`\n * suffix is a CATALOG-layer source-id, not a row field).\n * - `observed_at` is ISO 8601 UTC with `Z` suffix.\n */\nexport interface Observation {\n readonly station_code: string;\n readonly observed_at: string;\n readonly observation_type: \"METAR\" | \"SPECI\";\n /**\n * Per-row source tag. Widened in TS-W2 Plan 01 to cover all 3 row-level\n * observation sources (AWC live, IEM ASOS archive, GHCNh archive). Each\n * parser still emits its own literal — AWC emits `\"awc\"`, IEM ASOS emits\n * `\"iem\"`, GHCNh emits `\"ghcnh\"` — but the shared `Observation` contract\n * accepts all three so `mergeObservations` (TS-W2 Plan 04) sees one row\n * shape across sources. Matches Python `schema.observation.v1.source` enum.\n */\n readonly source: \"awc\" | \"iem\" | \"ghcnh\";\n readonly temp_c: number | null;\n readonly dewpoint_c: number | null;\n readonly temp_f: number | null;\n readonly dewpoint_f: number | null;\n readonly wind_dir_degrees: number | null;\n readonly wind_speed_kt: number | null;\n readonly wind_gust_kt: number | null;\n readonly altimeter_inhg: number | null;\n readonly sea_level_pressure_mb: number | null;\n readonly sky_cover_1: string | null;\n readonly sky_base_1_ft: number | null;\n readonly sky_cover_2: string | null;\n readonly sky_base_2_ft: number | null;\n readonly sky_cover_3: string | null;\n readonly sky_base_3_ft: number | null;\n readonly sky_cover_4: string | null;\n readonly sky_base_4_ft: number | null;\n readonly visibility_miles: number | null;\n readonly weather_codes: string | null;\n readonly precip_1hr_inches: number | null;\n readonly peak_wind_gust_kt: number | null;\n readonly peak_wind_dir: number | null;\n readonly peak_wind_time: string | null;\n readonly snow_depth_inches: number | null;\n readonly qc_field: number | null;\n readonly raw_metar: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Public helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Strip the leading `K` from 4-letter CONUS ICAO codes to get the\n * 3-letter NWS station code (`KNYC` → `NYC`). Non-K-prefixed codes\n * pass through unchanged after `.toUpperCase()`.\n */\nexport function icaoToStationCode(icao: string): string {\n const upper = icao.trim().toUpperCase();\n if (upper.startsWith(\"K\") && upper.length === 4) {\n return upper.slice(1);\n }\n return upper;\n}\n\n/**\n * Map AWC cloud-cover token to the standard abbreviation set.\n * - Accepts {CLR, SKC, FEW, SCT, BKN, OVC, VV}\n * - `CAVOK` → `CLR`\n * - everything else (incl. null/undefined) → null\n */\nexport function mapCloudCover(cover: string | null | undefined): string | null {\n if (cover === null || cover === undefined) return null;\n const upper = String(cover).toUpperCase();\n if (\n upper === \"CLR\" ||\n upper === \"SKC\" ||\n upper === \"FEW\" ||\n upper === \"SCT\" ||\n upper === \"BKN\" ||\n upper === \"OVC\" ||\n upper === \"VV\"\n ) {\n return upper;\n }\n if (upper === \"CAVOK\") {\n return \"CLR\";\n }\n return null;\n}\n\n/**\n * Parse AWC visibility — handles all forms documented in the Python parser:\n * - numeric (`10`) → `10`\n * - \"10+\" → `10` (trailing plus = \"or more\")\n * - \"1/2\" → `0.5`\n * - \"2 1/4\" → `2.25`\n * - \"M1/4\" → `0.25` (METAR \"less than\" prefix)\n * - bad input / empty / \"null\" → `null`\n *\n * All results are clamped at `MAX_VISIBILITY_MILES` (99.99).\n */\nexport function parseAwcVisibility(vis: string | number | null | undefined): number | null {\n if (vis === null || vis === undefined) return null;\n\n // Numeric pass-through (must be finite).\n if (typeof vis === \"number\") {\n if (!Number.isFinite(vis)) return null;\n return Math.min(vis, MAX_VISIBILITY_MILES);\n }\n\n const s = String(vis);\n if (s === \"\" || s === \"null\") return null;\n\n // \"10+\" → 10\n if (s.endsWith(\"+\")) {\n const n = Number(s.slice(0, -1));\n if (!Number.isFinite(n)) return null;\n return Math.min(n, MAX_VISIBILITY_MILES);\n }\n\n // Mixed number \"2 1/4\" → 2.25 (space + slash both present)\n if (s.includes(\" \") && s.includes(\"/\")) {\n const parts = s.split(\" \");\n if (parts.length !== 2) return null;\n const whole = Number(parts[0]);\n const frac = (parts[1] as string).split(\"/\");\n if (frac.length !== 2) return null;\n const num = Number(frac[0]);\n const den = Number(frac[1]);\n if (!(Number.isFinite(whole) && Number.isFinite(num) && Number.isFinite(den) && den !== 0)) {\n return null;\n }\n return Math.min(whole + num / den, MAX_VISIBILITY_MILES);\n }\n\n // Simple fraction \"1/2\" or \"M1/4\"\n if (s.includes(\"/\")) {\n let trimmed = s;\n if (trimmed.startsWith(\"M\") || trimmed.startsWith(\"m\")) {\n trimmed = trimmed.slice(1);\n }\n const frac = trimmed.split(\"/\");\n if (frac.length !== 2) return null;\n const num = Number(frac[0]);\n const den = Number(frac[1]);\n if (!(Number.isFinite(num) && Number.isFinite(den) && den !== 0)) return null;\n return Math.min(num / den, MAX_VISIBILITY_MILES);\n }\n\n // Plain numeric string.\n const n = Number(s);\n if (!Number.isFinite(n)) return null;\n return Math.min(n, MAX_VISIBILITY_MILES);\n}\n\n// ---------------------------------------------------------------------------\n// METAR remarks parsers (peak wind + T-group)\n// ---------------------------------------------------------------------------\n\n// `PK WND dddss/hhmm` (direction may be 2-3 digits; AWC payloads use 3+2)\nconst PK_WND_RE = /PK WND (\\d{3})(\\d{2,3})\\/(\\d{4})/;\n\n// T-group in METAR remarks: T{s}{SSS}{s}{DDD}\n// s=0 positive, s=1 negative. SSS/DDD = tenths of °C.\nconst TGROUP_RE = /\\bT([01])(\\d{3})([01])(\\d{3})\\b/;\n\nfunction parsePeakWind(rawMetar: string | null): {\n dir: number | null;\n speed: number | null;\n time: string | null;\n} {\n if (!rawMetar) return { dir: null, speed: null, time: null };\n const m = PK_WND_RE.exec(rawMetar);\n if (!m) return { dir: null, speed: null, time: null };\n const dir = Number.parseInt(m[1] as string, 10);\n const spd = Number.parseInt(m[2] as string, 10);\n const time = m[3] as string;\n if (!(dir >= 0 && dir <= 360) || spd < 0) {\n return { dir: null, speed: null, time: null };\n }\n return { dir, speed: spd, time };\n}\n\nfunction parseTGroup(rawMetar: string | null): { tempC: number | null; dewpC: number | null } {\n if (!rawMetar) return { tempC: null, dewpC: null };\n // T-group is a remarks-only element — search only after RMK to avoid\n // false positives on body group patterns.\n const rmkIdx = rawMetar.indexOf(\"RMK\");\n if (rmkIdx < 0) return { tempC: null, dewpC: null };\n const m = TGROUP_RE.exec(rawMetar.slice(rmkIdx));\n if (!m) return { tempC: null, dewpC: null };\n const tSign = m[1] === \"1\" ? -1 : 1;\n const tVal = (Number.parseInt(m[2] as string, 10) / 10.0) * tSign;\n const dSign = m[3] === \"1\" ? -1 : 1;\n const dVal = (Number.parseInt(m[4] as string, 10) / 10.0) * dSign;\n return { tempC: tVal, dewpC: dVal };\n}\n\n// ---------------------------------------------------------------------------\n// Safe numeric coercion (mirror Python `_safe_int`, `_safe_float`, `_safe_precip`)\n// ---------------------------------------------------------------------------\n\nfunction safeInt(v: unknown): number | null {\n if (v === null || v === undefined) return null;\n const f = typeof v === \"number\" ? v : Number(v);\n if (!Number.isFinite(f)) return null;\n // Python uses banker's `round()`; for non-negative half-values the\n // difference would matter, but observation payloads contain integral\n // counts (wind speed, sky base feet, qc bitmask). Use Math.round which\n // matches \"round half away from zero\" — adequate for the integer\n // domains we coerce here.\n return Math.round(f);\n}\n\nfunction safeFloat(v: unknown): number | null {\n if (v === null || v === undefined) return null;\n const f = typeof v === \"number\" ? v : Number(v);\n return Number.isFinite(f) ? f : null;\n}\n\nfunction safePrecip(v: unknown): number | null {\n if (v === null || v === undefined) return null;\n if (typeof v === \"string\" && v.trim().toUpperCase() === \"T\") {\n // Python returns 0.0 for trace; the task spec mentions \"0.005 (Python\n // trace convention)\" but the actual Python source maps \"T\" → 0.0 (see\n // `_safe_precip` in `_awc.py`). We match Python source byte-faithfully.\n return 0.0;\n }\n return safeFloat(v);\n}\n\nfunction cloudLayer(layer: unknown): { cover: string | null; base: number | null } {\n if (layer === null || typeof layer !== \"object\") return { cover: null, base: null };\n const obj = layer as { cover?: unknown; base?: unknown };\n const base = boundedInt(safeInt(obj.base), 0, SKY_BASE_MAX_FT);\n return { cover: mapCloudCover(obj.cover as string | null | undefined), base };\n}\n\n// ---------------------------------------------------------------------------\n// Main parser\n// ---------------------------------------------------------------------------\n\n/**\n * Convert one raw AWC METAR record into the canonical observation row.\n *\n * Returns `null` if the record is missing `icaoId` or `obsTime`, or if the\n * station code can't be resolved via `icaoToStationCode` + STATION_CODE_RE,\n * or if `obsTime` produces an out-of-range date.\n *\n * Otherwise returns a fully-typed `Observation` with every field either a\n * validated value or `null`. Never throws.\n */\nexport function awcToObservation(m: AwcMetarRaw): Observation | null {\n // --- Required keys -----------------------------------------------------\n const icaoId = m.icaoId;\n if (typeof icaoId !== \"string\" || icaoId === \"\") return null;\n\n const obsTime = m.obsTime;\n if (typeof obsTime !== \"number\" || !Number.isFinite(obsTime)) return null;\n\n const stationCode = icaoToStationCode(icaoId);\n if (!STATION_CODE_RE.test(stationCode)) return null;\n\n // --- observed_at -------------------------------------------------------\n const dt = new Date(obsTime * 1000);\n if (Number.isNaN(dt.getTime())) return null;\n const year = dt.getUTCFullYear();\n if (!(year >= 1970 && year <= 2100)) return null;\n // ISO 8601 with second precision + \"Z\" suffix (matches Python\n // `%Y-%m-%dT%H:%M:%SZ`). `Date.toISOString` emits ms which we strip.\n const observedAt = `${dt.toISOString().slice(0, 19)}Z`;\n\n // --- observation_type --------------------------------------------------\n const metarType = typeof m.metarType === \"string\" ? m.metarType.toUpperCase() : \"METAR\";\n const observationType: \"METAR\" | \"SPECI\" = metarType === \"SPECI\" ? \"SPECI\" : \"METAR\";\n\n // --- Wind direction ----------------------------------------------------\n let wdir: number | null = null;\n const rawWdir = m.wdir;\n if (rawWdir !== null && rawWdir !== undefined) {\n if (typeof rawWdir === \"number\") {\n wdir = boundedInt(Math.trunc(rawWdir), WIND_DIR_LO, WIND_DIR_HI);\n } else if (rawWdir !== \"VRB\") {\n const parsed = Number(rawWdir);\n if (Number.isFinite(parsed)) {\n wdir = boundedInt(Math.trunc(parsed), WIND_DIR_LO, WIND_DIR_HI);\n }\n }\n // \"VRB\" → wdir stays null (Python leaves it None; task spec says\n // \"0 (matches Python convention)\" but the Python source on disk\n // leaves it None — we follow the source-of-truth).\n }\n\n // --- Wind speed / gust -------------------------------------------------\n const wspd = boundedInt(safeInt(m.wspd), 0, WIND_SPEED_MAX);\n const wgst = boundedInt(safeInt(m.wgst), 0, WIND_GUST_MAX);\n\n // --- Altimeter (hPa input → inHg output) -------------------------------\n const altim = hpaToInhg(safeFloat(m.altim));\n\n // --- Sea-level pressure (already mb/hPa) -------------------------------\n let slp = safeFloat(m.slp);\n if (slp !== null && !(slp >= SLP_MIN_MB && slp <= SLP_MAX_MB)) {\n slp = null;\n }\n\n // --- Cloud layers (up to 4) --------------------------------------------\n const clouds = m.clouds ?? [];\n const c0 = clouds[0] !== undefined ? cloudLayer(clouds[0]) : { cover: null, base: null };\n const c1 = clouds[1] !== undefined ? cloudLayer(clouds[1]) : { cover: null, base: null };\n const c2 = clouds[2] !== undefined ? cloudLayer(clouds[2]) : { cover: null, base: null };\n const c3 = clouds[3] !== undefined ? cloudLayer(clouds[3]) : { cover: null, base: null };\n\n // --- Raw METAR (truncate) ----------------------------------------------\n let rawMetar: string | null = null;\n if (typeof m.rawOb === \"string\") {\n rawMetar = m.rawOb.slice(0, MAX_RAW_METAR_LEN);\n }\n\n // --- Weather codes (truncate) ------------------------------------------\n let weatherCodes: string | null = null;\n if (typeof m.wxString === \"string\") {\n weatherCodes = m.wxString.slice(0, MAX_WX_CODES_LEN);\n }\n\n // --- Temperature + dewpoint (T-group overrides body group) -------------\n let tempC = safeFloat(m.temp);\n let dewpC = safeFloat(m.dewp);\n const { tempC: tgTemp, dewpC: tgDewp } = parseTGroup(rawMetar);\n if (tgTemp !== null) tempC = tgTemp;\n if (tgDewp !== null) dewpC = tgDewp;\n tempC = boundedFloat(tempC, TEMP_MIN_C, TEMP_MAX_C);\n dewpC = boundedFloat(dewpC, TEMP_MIN_C, TEMP_MAX_C);\n const tempF = celsiusToFahrenheit(tempC);\n const dewpointF = celsiusToFahrenheit(dewpC);\n\n // --- Peak wind ---------------------------------------------------------\n const pk = parsePeakWind(rawMetar);\n const pkDir = boundedInt(pk.dir, WIND_DIR_LO, WIND_DIR_HI);\n const pkSpd = boundedInt(pk.speed, 0, WIND_GUST_MAX);\n\n // --- Precip + QC -------------------------------------------------------\n const precip = boundedFloatMin(safePrecip(m.precip), 0.0);\n const qcField = safeInt(m.qcField);\n\n return {\n station_code: stationCode,\n observed_at: observedAt,\n observation_type: observationType,\n source: \"awc\",\n temp_c: tempC,\n dewpoint_c: dewpC,\n temp_f: tempF,\n dewpoint_f: dewpointF,\n wind_dir_degrees: wdir,\n wind_speed_kt: wspd,\n wind_gust_kt: wgst,\n altimeter_inhg: altim,\n sea_level_pressure_mb: slp,\n sky_cover_1: c0.cover,\n sky_base_1_ft: c0.base,\n sky_cover_2: c1.cover,\n sky_base_2_ft: c1.base,\n sky_cover_3: c2.cover,\n sky_base_3_ft: c2.base,\n sky_cover_4: c3.cover,\n sky_base_4_ft: c3.base,\n visibility_miles: parseAwcVisibility(m.visib),\n weather_codes: weatherCodes,\n precip_1hr_inches: precip,\n peak_wind_gust_kt: pkSpd,\n peak_wind_dir: pkDir,\n peak_wind_time: pk.time,\n snow_depth_inches: null, // AWC doesn't carry snow-depth in METARs\n qc_field: qcField,\n raw_metar: rawMetar,\n };\n}\n","// IEM METAR CSV parser — string-body in, Observation rows out.\n//\n// Byte-faithful TS port of Python\n// `packages/weather/src/mostlyright/weather/_iem.py::iem_to_observation`\n// + `parse_iem_file`. The TS port consumes the CSV body in-memory (a\n// string returned by `downloadIemAsos`); the Python version walks a file\n// handle but the semantics are identical.\n//\n// IEM provides pre-parsed METAR fields in US/METAR-native units (°F, kt,\n// mi, inHg). We emit Observation dicts matching the canonical\n// `schema.observation.v1` shape (30 fields, source=\"iem\").\n//\n// Key port details:\n// - Comment lines starting with `#` are stripped BEFORE the CSV header\n// is consumed (mirrors Python's `filtered = (line for line in f if\n// not line.startswith(\"#\"))`).\n// - `M` and empty string both parse as `null` for numeric fields.\n// - `T` (trace) parses as `0.0` for `p01i` only; on other numeric\n// fields, T is non-numeric and yields `null`.\n// - Out-of-bounds Celsius consistency rule: when derived `temp_c` is\n// nulled by bounds, the raw `temp_f` is ALSO nulled (Python L170-171).\n// - All 4 key vars (raw `tmpf`, raw `dwpf`, `wind_speed`, `slp`) missing\n// → row skipped (returns `null` from `iemToObservation`).\n// - Output dict-key order matches Python verbatim so `JSON.stringify`\n// produces byte-stable output across SDKs (downstream diff tooling).\n//\n// CSV implementation: hand-rolled split-on-comma. IEM's `format=comma`\n// endpoint does NOT quote fields with embedded commas (the Python parser\n// confirms this empirically — it uses csv.DictReader with default\n// dialect and works). A hand-rolled split keeps the bundle-size gate\n// happy (no papaparse dep) and matches the TS Architect rubric §2\n// (bundle size) + the plan's \"DO NOT add deps\" note.\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 STATION_CODE_RE,\n TEMP_MAX_C,\n TEMP_MIN_C,\n WIND_DIR_BOUNDS,\n WIND_GUST_MAX,\n WIND_SPEED_MAX,\n boundedFloat,\n boundedFloatMin,\n boundedInt,\n} from \"@mostlyrightmd/core/internal/bounds\";\nimport { fahrenheitToCelsius } from \"@mostlyrightmd/core/internal/convert\";\n\nimport { type Observation, icaoToStationCode, mapCloudCover } from \"./awc.js\";\n\nconst [WIND_DIR_LO, WIND_DIR_HI] = WIND_DIR_BOUNDS;\n\n// Match Python `_TS_RE = re.compile(r\"^(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2})$\")`.\nconst TS_RE = /^(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2})$/;\n\nconst VALID_OBS_TYPES = new Set<string>([\"METAR\", \"SPECI\"]);\n\nexport type IemObservationTypeOverride = \"METAR\" | \"SPECI\";\n\nexport interface IemToObservationOptions {\n /**\n * Force `observation_type` to this value regardless of metar text.\n * Used by callers that issue separate report_type=3/4 fetches and\n * therefore know the type per-batch without inspecting the raw text.\n */\n observationTypeOverride?: IemObservationTypeOverride;\n}\n\n/** Raw IEM CSV row (header → cell value), all strings before parsing. */\nexport type IemCsvRow = Record<string, string>;\n\n/**\n * Parse an IEM numeric cell into a finite float. `\"M\"` (IEM's missing\n * sentinel) and empty strings yield `null`; non-finite values yield `null`.\n *\n * Mirrors Python `_safe_float`.\n */\nfunction safeFloat(val: string): number | null {\n if (val === \"\" || val === \"M\") return null;\n const f = Number(val);\n return Number.isFinite(f) ? f : null;\n}\n\n/**\n * Parse an IEM numeric cell into a rounded integer via `safeFloat`.\n * `null` propagates. Mirrors Python `_safe_int` (round → int).\n */\nfunction safeInt(val: string): number | null {\n const f = safeFloat(val);\n return f === null ? null : Math.round(f);\n}\n\n/**\n * Parse the IEM precipitation cell. `M` / empty → `null`; `T` (trace) → `0.0`;\n * numeric values pass through `safeFloat`. Mirrors Python `_parse_precip`.\n */\nfunction parsePrecip(val: string): number | null {\n if (val === \"\" || val === \"M\") return null;\n if (val.trim().toUpperCase() === \"T\") return 0.0;\n return safeFloat(val);\n}\n\n/**\n * Parse IEM timestamp (`YYYY-MM-DD HH:MM`) into RFC3339 / ISO 8601 UTC.\n * Returns `null` for missing (`\"\"`/`M`), malformed, calendar-invalid, or\n * out-of-year-range inputs. Mirrors Python `_parse_timestamp`.\n */\nfunction parseTimestamp(val: string): string | null {\n if (val === \"\" || val === \"M\") return null;\n const trimmed = val.trim();\n const m = TS_RE.exec(trimmed);\n if (m === null) return null;\n // RegExp matched — groups 1..5 are guaranteed defined.\n const year = Number.parseInt(m[1] as string, 10);\n const month = Number.parseInt(m[2] as string, 10);\n const day = Number.parseInt(m[3] as string, 10);\n const hour = Number.parseInt(m[4] as string, 10);\n const minute = Number.parseInt(m[5] as string, 10);\n // Range checks BEFORE Date.UTC round-trip (rejects Feb-30 / hour-25 inputs\n // that Date.UTC would otherwise silently roll forward).\n if (year < MIN_YEAR || year > MAX_YEAR) return null;\n if (month < 1 || month > 12) return null;\n if (day < 1 || day > 31) return null;\n if (hour > 23 || minute > 59) return null;\n // Calendar validity via UTC round-trip — rejects \"2025-02-30 12:00\" etc.\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 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 // Format as `YYYY-MM-DDTHH:MM:00Z` byte-faithful with Python's f-string.\n return `${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:00Z`;\n}\n\n/**\n * Parse `YYYY-MM-DD HH:MM` peak-wind-time into the bare `HHMM` form Python\n * emits. Returns `null` on `\"\"`/`M`/malformed. Mirrors Python\n * `_parse_peak_wind_time`.\n */\nfunction parsePeakWindTime(val: string): string | null {\n if (val === \"\" || val === \"M\") return null;\n const trimmed = val.trim();\n const parts = trimmed.split(\" \");\n if (parts.length !== 2) return null;\n const timeParts = (parts[1] as string).split(\":\");\n if (timeParts.length !== 2) return null;\n const h = Number.parseInt(timeParts[0] as string, 10);\n const min = Number.parseInt(timeParts[1] as string, 10);\n if (!Number.isFinite(h) || !Number.isFinite(min)) return null;\n if (h < 0 || h > 23 || min < 0 || min > 59) return null;\n const hh = h < 10 ? `0${h}` : String(h);\n const mm = min < 10 ? `0${min}` : String(min);\n return `${hh}${mm}`;\n}\n\n/**\n * Detect METAR vs SPECI from the raw METAR text first word. Empty / `M` →\n * `\"METAR\"` (safe default). Mirrors Python `_detect_obs_type`.\n */\nfunction detectObsType(metar: string): \"METAR\" | \"SPECI\" {\n if (metar === \"\" || metar === \"M\") return \"METAR\";\n const words = metar.trim().split(/\\s+/, 1);\n if (words.length > 0 && words[0] === \"SPECI\") return \"SPECI\";\n return \"METAR\";\n}\n\n/**\n * Convert one IEM CSV row (header→cell dict) into the canonical\n * Observation row. Returns `null` if the row should be skipped:\n * - station missing/M, or doesn't match STATION_CODE_RE after K-strip\n * - timestamp unparseable / out-of-range\n * - ALL 4 key vars (raw tmpf, raw dwpf, wind_speed, slp) missing\n *\n * Throws on bad `observationTypeOverride` (Python L152-156 parity).\n *\n * Output dict-key order is preserved verbatim — matters for downstream\n * byte-stable JSON snapshot diffs.\n */\nexport function iemToObservation(\n row: IemCsvRow,\n opts: IemToObservationOptions = {},\n): Observation | null {\n // --- Station code ------------------------------------------------------\n const stationRaw = row.station ?? \"\";\n if (stationRaw === \"\" || stationRaw === \"M\") return null;\n const stationCode = icaoToStationCode(stationRaw);\n if (!STATION_CODE_RE.test(stationCode)) return null;\n\n // --- Timestamp ---------------------------------------------------------\n const observedAt = parseTimestamp(row.valid ?? \"\");\n if (observedAt === null) return null;\n\n // --- Observation type (caller override or auto-detect) ----------------\n const override = opts.observationTypeOverride;\n if (override !== undefined && !VALID_OBS_TYPES.has(override)) {\n throw new Error(\n `Invalid observation_type_override: ${JSON.stringify(\n override,\n )}. Must be one of ${[...VALID_OBS_TYPES].join(\", \")}`,\n );\n }\n const metarText = row.metar ?? \"\";\n const observationType: \"METAR\" | \"SPECI\" =\n override !== undefined ? override : detectObsType(metarText);\n\n // --- Temperature (IEM gives °F; derive °C; bounds-check on °C) ---------\n const rawTempF = safeFloat(row.tmpf ?? \"\");\n const rawDewpF = safeFloat(row.dwpf ?? \"\");\n const tempC = boundedFloat(fahrenheitToCelsius(rawTempF), TEMP_MIN_C, TEMP_MAX_C, {\n field: \"temp_c\",\n });\n const dewpC = boundedFloat(fahrenheitToCelsius(rawDewpF), TEMP_MIN_C, TEMP_MAX_C, {\n field: \"dewpoint_c\",\n });\n // Consistency rule (Python L170-171): if derived °C is out of bounds,\n // the raw °F is also bogus — null both sides.\n const tempF: number | null = tempC !== null ? rawTempF : null;\n const dewpF: number | null = dewpC !== null ? rawDewpF : null;\n\n // --- Wind (already in knots) ------------------------------------------\n const windDir = boundedInt(safeInt(row.drct ?? \"\"), WIND_DIR_LO, WIND_DIR_HI);\n const windSpeed = boundedInt(safeInt(row.sknt ?? \"\"), 0, WIND_SPEED_MAX);\n const windGust = boundedInt(safeInt(row.gust ?? \"\"), 0, WIND_GUST_MAX);\n\n // --- Pressure ----------------------------------------------------------\n const altim = safeFloat(row.alti ?? \"\"); // inHg (passthrough)\n let slp = safeFloat(row.mslp ?? \"\"); // mb / hPa\n if (slp !== null && !(slp >= SLP_MIN_MB && slp <= SLP_MAX_MB)) {\n slp = null;\n }\n\n // --- Visibility (statute miles; clamp at MAX_VISIBILITY_MILES) --------\n let vis = safeFloat(row.vsby ?? \"\");\n if (vis !== null) {\n if (vis < 0) vis = null;\n else if (vis > MAX_VISIBILITY_MILES) vis = MAX_VISIBILITY_MILES;\n }\n\n // --- Sky covers + base heights (4 columns; IEM gives feet) ------------\n const skyCovers: Array<string | null> = [];\n const skyBases: Array<number | null> = [];\n for (let i = 1; i <= 4; i += 1) {\n const coverRaw = row[`skyc${i}`] ?? \"\";\n const baseRaw = row[`skyl${i}`] ?? \"\";\n const cover = coverRaw !== \"\" && coverRaw !== \"M\" ? mapCloudCover(coverRaw) : null;\n const base = boundedInt(safeInt(baseRaw), 0, SKY_BASE_MAX_FT);\n skyCovers.push(cover);\n skyBases.push(base);\n }\n\n // --- Weather codes (truncate to MAX_WX_CODES_LEN) ---------------------\n const wxRaw = row.wxcodes ?? \"\";\n const weatherCodes: string | null =\n wxRaw !== \"\" && wxRaw !== \"M\" ? wxRaw.slice(0, MAX_WX_CODES_LEN) : null;\n\n // --- Precipitation ('T' = trace → 0.0; clamp at 0.0) ------------------\n const precip = boundedFloatMin(parsePrecip(row.p01i ?? \"\"), 0.0);\n\n // --- Snow depth (clamp at 0.0) ----------------------------------------\n const snow = boundedFloatMin(safeFloat(row.snowdepth ?? \"\"), 0.0);\n\n // --- Peak wind ---------------------------------------------------------\n const pkGust = boundedInt(safeInt(row.peak_wind_gust ?? \"\"), 0, WIND_GUST_MAX);\n const pkDir = boundedInt(safeInt(row.peak_wind_drct ?? \"\"), WIND_DIR_LO, WIND_DIR_HI);\n const pkTime = parsePeakWindTime(row.peak_wind_time ?? \"\");\n\n // --- Raw METAR (truncate) ---------------------------------------------\n const rawMetar: string | null =\n metarText !== \"\" && metarText !== \"M\" ? metarText.slice(0, MAX_RAW_METAR_LEN) : null;\n\n // --- Skip-row gate: ALL 4 key vars missing → drop the row -------------\n // Mirror Python L222-224: uses the RAW values (raw_temp_f, raw_dewp_f,\n // wind_speed bounded, slp bounded). Note slp uses post-bounds null so a\n // pressure outside [870, 1084] also counts as missing for skip purposes.\n if (rawTempF === null && rawDewpF === null && windSpeed === null && slp === null) {\n return null;\n }\n\n // Return literal preserves Python's iem_to_observation key order\n // exactly — 30 fields. JSON.stringify ordering matters for downstream\n // byte-stable diff tooling.\n return {\n station_code: stationCode,\n observed_at: observedAt,\n observation_type: observationType,\n source: \"iem\",\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: windSpeed,\n wind_gust_kt: windGust,\n altimeter_inhg: altim,\n sea_level_pressure_mb: slp,\n sky_cover_1: skyCovers[0] as string | null,\n sky_base_1_ft: skyBases[0] as number | null,\n sky_cover_2: skyCovers[1] as string | null,\n sky_base_2_ft: skyBases[1] as number | null,\n sky_cover_3: skyCovers[2] as string | null,\n sky_base_3_ft: skyBases[2] as number | null,\n sky_cover_4: skyCovers[3] as string | null,\n sky_base_4_ft: skyBases[3] as number | null,\n visibility_miles: vis,\n weather_codes: weatherCodes,\n precip_1hr_inches: precip,\n peak_wind_gust_kt: pkGust,\n peak_wind_dir: pkDir,\n peak_wind_time: pkTime,\n snow_depth_inches: snow,\n qc_field: null, // IEM CSV doesn't carry a QC field — schema-stable null\n raw_metar: rawMetar,\n };\n}\n\n/**\n * Parse a full IEM CSV body into Observation rows.\n *\n * - Lines beginning with `#` are dropped before the header is consumed\n * (mirror Python `filtered = (line for line in f if not line.startswith(\"#\"))`).\n * - The first non-comment line is the header; subsequent lines are data.\n * - Rows that `iemToObservation` rejects are silently dropped (parser\n * never throws on bad data; only `observationTypeOverride` validation\n * throws — that's a programmer-error path).\n *\n * Returns an empty array for empty / header-only / all-comments input.\n */\nexport function parseIemCsv(\n csvBody: string,\n opts: IemToObservationOptions = {},\n): ReadonlyArray<Observation> {\n if (csvBody === \"\") return [];\n // Normalize line endings + split. Drop empty trailing lines so trailing\n // newlines don't yield phantom rows.\n const lines = csvBody.split(/\\r?\\n/).filter((line) => !line.startsWith(\"#\"));\n // Skip empty lines (incl. the final newline tail).\n const nonEmpty = lines.filter((line) => line.length > 0);\n if (nonEmpty.length === 0) return [];\n const headerLine = nonEmpty[0] as string;\n const header = headerLine.split(\",\");\n const out: Observation[] = [];\n for (let i = 1; i < nonEmpty.length; i += 1) {\n const line = nonEmpty[i] as string;\n const cells = line.split(\",\");\n const row: IemCsvRow = {};\n for (let c = 0; c < header.length; c += 1) {\n const key = header[c] as string;\n row[key] = (cells[c] ?? \"\").trim();\n }\n const obs = iemToObservation(row, opts);\n if (obs !== null) out.push(obs);\n }\n return out;\n}\n"],"mappings":"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","names":["n","WIND_DIR_LO","WIND_DIR_HI","safeFloat","safeInt"]}
|
|
File without changes
|