Skip to content

Reference

Changelog

Mostly Right follows Keep a Changelog and Semantic Versioning. This page mirrors CHANGELOG.md in the SDK repo on every release tag.

The canonical source: CHANGELOG.md in mostlyrightmd/mostlyright-sdk.

[1.15.0] — 2026-07-10 — General-first re-layering: the label axis, thin markets delegators, panels, and the contributor registry

Section titled “[1.15.0] — 2026-07-10 — General-first re-layering: the label axis, thin markets delegators, panels, and the contributor registry”

Minor release, dual-SDK (PyPI 1.15.0 + npm 1.15.0 in lockstep). The SDK is now general-first: the venue-free core dataset() becomes an alignment engine with an explicit label axis (label="cli" | "daily_extremes" | None | <your own DataFrame>), and all venue knowledge moves into thin markets delegators (markets.kalshi.dataset(ticker) / markets.polymarket.dataset(event_id) with contract-shaped outcome= targets). Everything you run today keeps returning the same bytes — the default dataset() / research() output is byte-identical to 1.14.0 and the live parity gate ran 5/5 byte-green — but one loud deprecation notice starts this release:

⚠️ D-22 bridge — bare dataset() now emits a FutureWarning. Calling dataset() without an explicit label= still returns the cli_* settlement columns byte-identically, but warns once: “dataset() label default changes from ‘cli’ to None in 2.0; pass label=‘cli’ to keep settlement labels or label=None to silence.” The default flips to label=None (features-only) at 2.0, after a ≥2-minor bridge window. Say what you mean today: label="cli" keeps settlement labels forever, label=None opts into the future default now. research() is unchanged and never warns — the legacy alias keeps its implicit cli label until it retires at 2.0; its output is byte-stable. Note for -W error users: our own CI/suites do not run warnings-as-errors — promoting warnings to errors is a user opt-in; if you do, a bare dataset() raises and the fix is the same explicit label=. Parity byte-stability is guaranteed by explicit label="cli" pins in the parity gate, not by a warnings filter. See docs/migration-1.15.md.

  • dataset(label=...) — the label axis (both SDKs). Four shapes: label="cli" (NWS CLI settlement columns, today’s bytes), label="daily_extremes" (WU/NOAA-WRH-style extremes with native daily_extremes_{tmax_c,tmin_c,tmean_c,n_obs,source_tmax,source_tmin} columns + WR-05 low-coverage nulling, row-preserved), label=None (features-only — works for any catalog station worldwide, no market required), and BYO: pass your own label DataFrame and the SDK enforces the leakage discipline for you (settlement-calendar LST bucketing, LEFT-join row preservation, collision guard against reserved obs_/cli_/wu_/noaa_wrh_ prefixes, publication discipline via labels_as_known="settled" | "publication_lag"). Misalignment errors loudly with the typed LabelAlignmentError (reason enum), never silently. research() and dataset() are now distinct thin wrappers over one shared body — the alias split that lets dataset() warn while research() stays silent.
  • Thin markets delegators (both SDKs). markets.kalshi.dataset(ticker, from_date, to_date, outcome=...) routes to core dataset(label="cli", ...) (Kalshi NHIGH/NLOW settle on the CLI product); markets.polymarket.dataset(event_id, ..., outcome=...) routes to label="daily_extremes" (Polymarket resolves on WU/NOAA-WRH extremes, NOT CLI — today’s cli_* frame was the wrong ground truth for Polymarket models). outcome=True appends a binary label_outcome column via NET-NEW strike parsers: Kalshi -T<strike> thresholds settle inclusive (value >= strike — the strike degree itself is YES, matching “{n}° or above”), -B<lo>-<hi> ranges are inclusive on both endpoints, and both parsers accept negative-temperature strikes (a leading minus binds to the strike integer: T-5, B-5--3). Polymarket above/below/between mirrors the same inclusive rule. Malformed tickers raise typed KalshiTickerError / PolymarketStrikeError.
  • Multi-station panels: dataset(stations=[...]) (both SDKs). Long-format output (station column first, caller order preserved), per-station settlement calendars and per-station label-recipe resolution (mixed-venue panels OK); rows/columns are exactly the concat of the per-station single calls; nested per-station provenance in attrs["stations"].
  • Experimental contributor registry (D-24, both SDKs). The documented mostlyright.experimental.contributor decorator (TS: @mostlyrightmd/meta/experimental registerContributor) registers third-party post-join feature contributors — public but experimental/semver-exempt until 2.0 (first registration warns with ExperimentalFeatureWarning). The labels-only firewall is unconditional: contributors can never emit label-reserved columns (obs_/cli_/wu_/noaa_wrh_ prefixes and label/date/station exacts). Contributor failure semantics: NaN block + RuntimeWarning, the call succeeds — fail-loud is reserved for missing extras and firewall violations.
  • dataset(features=[...]) selector (both SDKs). Replaces the accreting include_* flags with one explicit list ("forecasts", "satellite", "cwop", "trades"); built on the same registry as third-party contributors.
  • Fetch-provenance ledger (D-25 — ships dark). Every re-fetchable station-pool cache write (obs monthly, climate annual, IEM MOS runtime) now appends a best-effort JSONL record — (source, station, window, fetched_at UTC, row_count, order-independent row-window SHA-256) — to $MOSTLYRIGHT_CACHE_DIR/provenance/, per-source shards, filelock-guarded, size-bounded with single-backup rotation. Read it back with mostlyright.provenance.history(station, window=None). Zero read-path impact; a ledger hiccup can never fail a fetch. This is the audit trail Phase 33’s reconcile() will use to attribute upstream restatements. Python-only for now (TS is browser/server-agnostic — PT-3205 tracks the server-side port).
  • TS subpath surfaces: @mostlyrightmd/meta/experimental, @mostlyrightmd/meta/panels, @mostlyrightmd/meta/markets-dataset; the D-22 warn latch fires once per process; meta size budget 40→44 KB, weather stays within 27 KB.
  • Bare dataset() without label=FutureWarning (the D-22 bridge above). Bytes unchanged this release; default flips to label=None at 2.0. research() never warns.
  • include_forecast/include_satellite/include_cwop/include_trades= — deprecated aliases of features=[...] (DeprecationWarning, byte-identical, removal no earlier than 2 minors out).
  • contract= / contracts= / include_trades= on core dataset() — venue knowledge is leaving the core (DeprecationWarning, byte-identical below the warning, ≥2 minors); use markets.kalshi.dataset / markets.polymarket.dataset.

Same-phase parity (32-05): the label axis (label: "cli" | "daily_extremes" | null | LabelRow[]), the alias split + once-per-process warn latch, the experimental registry with the same unconditional labels-only firewall, panels, and the markets delegators + inclusive strike parsers all ship in npm 1.15.0. Known divergence: the fetch-provenance ledger is Python-only (PT-3205, server-side TS follow-up).

[1.14.0] — 2026-07-09 — Observation-grain dataset(): per-report training rows joined to daily labels

Section titled “[1.14.0] — 2026-07-09 — Observation-grain dataset(): per-report training rows joined to daily labels”

Minor release, dual-SDK (PyPI 1.14.0 + npm 1.14.0 in lockstep). Purely additive: dataset()/research() gain a granularity="observation" mode that returns the merged per-report METAR/SPECI rows LEFT-joined to every daily column the same call produces (labels + covariates), and obs() per-report rows gain a settlement_date/settlementDate column. The default granularity="daily" output is byte-identical to 1.13.0 — parity untouched. The one behavioral thing to know is the pseudo-replication guardrail: on an observation-grain frame, daily labels repeat across every report row of the same settlement day, so cross-validation MUST group by settlement_date. See docs/migration-1.14.md.

  • dataset(granularity="daily"|"observation") + research() alias (both SDKs, D-17/D-18). granularity="observation" returns the per-report observation rows — identical to the obs(granularity="observation") row set, REUSED verbatim through the shared list path (no second copy of the trim/merge/settlement logic) — LEFT-joined by LST settlement day to all daily columns: cli_* (incl. cli_report_type), obs_*, fcst_* when include_forecast, and sat_*/cwop_* when the covariate flags are on. The join is fan-out-proof: the daily side is unique per settlement day by construction, row count is preserved (asserted), and label-less days keep NaN/null daily columns — rows are never dropped or fanned. granularity values other than "daily"/"observation" (e.g. "hourly") are rejected loudly, up front, before any fetch. granularity="daily" remains the default and is byte-identical to 1.13.0. Python: mostlyright.dataset(..., granularity="observation"); TS: dataset({ granularity: "observation" }).
  • obs() per-report rows carry settlement_date / settlementDate (both SDKs, D-19). Every obs(granularity="observation") report row now stamps the ISO LST settlement day (via snapshot.settlement_date_for / settlementDateFor), never a naive observed_at[:10] UTC slice — a UTC-midnight-spanning report is bucketed to the correct local settlement day (KLAX vectors verified byte-for-byte across both SDKs). A new optional field; existing rows are otherwise unchanged. tz_override / tzOverride is threaded through the settlement machinery on this path.
  • Pseudo-replication guardrail docs (D-20). docs/source-identity.md gains an “observation-grain composer and pseudo-replication” subsection: because each settlement day’s labels repeat across all of that day’s report rows, splitting cross-validation by row leaks the label. The documented pattern is GroupKFold (or equivalent) keyed on settlement_date. docs/migration-1.14.md carries the same headline for anyone adopting the observation grain.
  • TS obs() station-identifier routing (review r3): IEM now fetched with the NWS code and AWC with the ICAO via registry resolution — ICAO input no longer yields empty/divergent IEM rows at observation grain; unknown stations raise typed errors.

  • Dotted source pins now resolve at observation grain in both SDKs (review-loop hardening). A dotted source pin (e.g. iem.asos) that previously produced a silent empty [] in TS / a ValueError in Python on the observation path is now correctly resolved and threaded through, and the observation-grain frame’s provenance carries the dotted pin. tz_override / tzOverride is likewise threaded into the daily side of the observation-grain composer (incl. market_close_utc), so both sides of the LEFT join agree on the settlement day.

  • Ships in the same phase (D-21). dataset({ granularity }) + settlementDate mirror the Python semantics: "daily" default returns the unchanged PairsRow[]; "observation" returns the per-report rows joined to every daily PairsRow column by settlementDate. camelCase where the TS surface already camelCases (settlementDate, tzOverride); daily columns keep their wire-parity snake_case PairsRow names. The composer is a pure object join — browser/MV3-safe, no Node APIs, within existing bundle budgets (no size-limit bumps). Note the existing Phase-30 default divergence persists: Python obs() defaults to granularity="daily", TS obs() defaults to "observation"; the dataset() default is "daily" in both.

[1.13.0] — 2026-07-09 — Observation-API unification: dataset(), climate(), covariates, one mode

Section titled “[1.13.0] — 2026-07-09 — Observation-API unification: dataset(), climate(), covariates, one mode”

Minor release, dual-SDK (PyPI 1.13.0 + npm 1.13.0 in lockstep). Unifies the observation API onto a single dataset()/research() surface, ships a standalone climate() settlement table, adds an opt-in labels-only covariate seam, and starts the deprecation train for the numbered-mode era. Every existing entry point is byte-stable at runtime this release; see docs/migration-1.13.md — headline: migrating a pinned feature is a retrain event, not a find-replace.

  • dataset() — the new canonical name for the training-pairs join (both SDKs). research() is retained as an alias bound to the same callable (dataset is research); no deprecation warning on research() this release, byte-identical output. Python: mostlyright.dataset(...); TS: import { dataset } from "mostlyright".
  • obs(granularity="observation") — per-report observation grain (Python). Returns the merged per-report METAR/SPECI rows (window-trimmed, no daily bucketing) with per-row source identity preserved and observation_type/raw_metar surfaced. Unpinned frames carry the new frame-identity tag merged.live_v1, accepted only by the new schema.observation.merged.v1 — the frozen schema.observation.v1 rejects it, so fused frames can never masquerade as single-source. The default granularity="daily" output is byte-identical to 1.12.1. TS obs() gains the same option with a deliberate divergence: its default is "observation" (preserving existing TS per-report behavior); "daily" throws a documented not-yet-ported error (parity ticket filed). Observation-grain frames stamp retrieved_at provenance.
  • climate() — standalone NWS CLI settlement-label table (both SDKs). The daily cli_high_f/cli_low_f labels previously visible only inside the dataset() join are now a first-class, source-identified domain table in the JOIN column vocabulary, window-trimmed (no whole-year leak), frame tag cli.archive with truthful per-row endpoint tag iem. Python: mostlyright.weather.climate(...); TS: import { climate } from "@mostlyrightmd/weather/climate" (lean subpath — keeps the root barrel inside its size budget). TS climate() resolves NWS↔ICAO station codes through the station registry so the station column matches Python cross-SDK.
  • dataset(include_satellite=, include_cwop=) — opt-in covariate composition (Python). LEFT-joins per-LST-settlement-day sat_* / cwop_* covariate columns onto the composed training frame behind flags defaulting False. Labels-only firewall: label columns are byte-identical flags-on vs flags-off, row count/order unchanged, missing days are NaN (never dropped), and satellite/CWOP can never become observation sources — the four parity-firewall files are untouched and grep-gated. CWOP covariates are QC-clean-only and read-only (no APRS sockets); satellite covariates count only scans that contribute a usable value (sat_scan_count). Requires the [satellite] / [cwop] extras; missing extras fail loud with a typed error. Adding a covariate to a trained model is itself a retrain event — see the migration guide. TS parity for the covariate flags is tracked as a CROSS-SDK-SYNC ticket (browser runtime cannot run local extraction).
  • docs/source-identity.md — the single cross-vertical kwarg contract page: source= is always provenance, delivery= is always local|hosted, grain is vertical-defined, frame-vs-row identity, the labels-only firewall, and the train/trade symmetry example.
  • Output-knob unification (D-06): backend= + return_type= is the one output convention. obs() gains backend/return_type; return_type="list" added across the dispatch layer. TS return_type accepts "dataframe" as the canonical value (the historical "frame" remains an alias). Every function’s no-argument default return is byte-stable.
  • Numbered-mode vocabulary retired across docstrings, runtime strings, and docs (source must be one of ..., source-pinned dispatch requested ...); forecast surfaces are now described as IEM MOS vs per-NWP-model. TS neutral names SOURCE_PINNED_SOURCES/SourcePinnedSource/ isSourcePinnedSource are canonical.
  • TS weather bundle budget raised 26 → 27 KB (TS-BUNDLE-01) to admit the station-format guard + pinned-AWC fail-loud hardening; measured 26.23 KB.

All deprecations warn but behave identically; removal target is ≥2 minor releases out. Production pipelines running -W error (Python) will surface these as errors — see the migration guide before tightening warning filters.

  • research_by_source() → use obs(source=..., granularity= "observation"). Caveat: this is NOT a drop-in swap for pinned features — the new path returns pre-merge full single-source coverage, a different (typically larger) row composition than research_by_source’s post-merge filter. Retrain/re-backtest before switching production strategies (docs/migration-1.13.md).
  • mostlyright.mode2 import pathobs(...) from mostlyright.weather and assert_source_identity from mostlyright.core (its new canonical home, re-exported).
  • as_dataframe= on dataset()/research(), research_by_source(), and obs()return_type= (True"dataframe", False"list"; an explicit return_type= wins).
  • TS MODE2_SOURCES/Mode2Source/isMode2Source → the SOURCE_PINNED_* names (old identifiers remain as aliases binding the same values; removal at 2.0).
  • TS station-format guard on every exported weather network surface (including the hosted satellite client): malformed station input now fails loud before any URL is built or network dispatched.
  • TS pinned source: "awc" queries fail loud on fetch errors instead of silently returning an empty array (unpinned merge path unchanged).
  • Parity case-4 fixture (KMIA rolling year) re-baselined 2026-07-09: upstream restated 3 of 365 days after the May capture (mean aggregates only; observation counts, settlement labels, and extremes byte-identical), and the v0.14.1 hosted data plane the original fixtures were captured from has since been decommissioned, making a fresh v0.14.1 re-capture permanently impossible. The prior v0.14.1-captured fixture is preserved in git history; provenance documented in tests/fixtures/parity/README.md. Cases 1/2/3/5 remain v0.14.1-captured and byte-green.

[1.12.1] — 2026-07-08 — #107 follow-ups: cross-SDK consistency + version metadata

Section titled “[1.12.1] — 2026-07-08 — #107 follow-ups: cross-SDK consistency + version metadata”

Patch release, dual-SDK. Resolves the P3 follow-ups from the 1.12.0 review loop (#107). No behavior change on any default path.

  • __version__ now derived from dist metadata in all three Python packages via importlib.metadata (was hardcoded and stale: core/weather reported 0.1.0rc1, markets 0.0.1, while dists shipped 1.12.x). Falls back to 0.0.0+unknown in metadata-less source trees. DataVersion tokens computed via for_research() without an explicit sdk_version now reflect the true SDK version (opt-in surface; not wired into research(), no cache/settlement impact).
  • Cross-SDK unknown-source error text unified on the research() surface: both SDKs now emit research(): source must be one of [...], exact-message-pinned in both test suites. researchBySource’s Mode 2 source must be one of text is unchanged.
  • TS sources: null honors the Phase 21 null-as-absent wire contract (was throwing the v0.3 deferred error for a JSON-round-tripped Python sources=None); removed a dead TS mutual-exclusion check whose only reachable firing was that same wire-contract bug, and the mirrored unreachable Python ValueError block.
  • Pinned-source provenance threads explicitly through the wrapper path (wrap_result(source=...), mirroring mode2.research_by_source) — defense-in-depth; unpinned wrapper calls byte-identical.
  • TS public barrel now re-exports RESEARCH_SOURCE_ALIASES, RESEARCH_OBSERVATION_SOURCES, isResearchObservationSource.
  • Coverage: research(source=) under as_dataframe=False, polars wrapper (skip-guarded), empty-after-pin attrs; TS sources: null regression; barrel surface test.
  • Weather TS bundle growth root-caused (hosted-shim root-barrel re-export, a tree-shaking blocker — not data growth); shrink deferred to its own PR since it contracts the root-barrel API (#107).

[1.12.0] — 2026-07-08 — research(source=) pin + Mode 2 window fix

Section titled “[1.12.0] — 2026-07-08 — research(source=) pin + Mode 2 window fix”

Minor release, dual-SDK (PyPI 1.12.0 + npm 1.12.0 in lockstep).

  • research(source=...) single-source pin (both SDKs). The singular source= kwarg is now functional (was NotImplementedError since Phase 10): observation rows are filtered to the requested source’s alias-accept set after the multi-source merge and before daily obs_* aggregation. Accepts the full Mode 2 vocabulary (iem, iem.archive, iem.live, awc, awc.live, ghcnh, ghcnh.archive) — byte-identical alias tables across Python (mostlyright.mode2._SOURCE_ALIASES) and TS (RESEARCH_SOURCE_ALIASES). Unknown sources raise before any network fetch. cli_* settlement columns are unaffected (NWS CLI is independent of the observation pin); the pinned DataFrame carries df.attrs["source"]. Inherited v0.1 limitation documented: the AWC > IEM > GHCNh merge runs before the pin filter, so pinning a lower-priority source yields only rows the merge did not supersede. sources=[...] (plural, multi-source subset) remains v0.3; its error message no longer claims source= is unwired. The source=None default path is byte-identical to 1.11.0 (parity gate green). TS source: null follows the Phase 21 wire contract (null-as-absent). Follow-up nits: #107.
  • mode2.research_by_source returned whole calendar months instead of the requested window. The month-granular parquet cache flows through _fetch_observations_range unfiltered, so a KNYC Jan 6–12 query returned all 865 January rows (a Jan 28–Feb 3 query returned two full months) — silent out-of-window leakage into backtest frames, including the README’s own train = research_by_source(...) example. Rows are now trimmed to [from_date, to_date] by UTC date-part, inclusive, matching the TS port’s shipped filter exactly (cross-SDK lockstep); the +1-day fetch extension remains as a fetch-width knob only. Every return shape is fixed (DataFrame, as_dataframe=False, wrapper/backends). Callers needing the last LST day’s pre-midnight UTC tail should extend to_date by one day.
  • Earnings venue-rule compound-word under-count (D-30). Hyphenated compounds (e.g. supply-chain for chain) were dropped from BOTH venue counts by an inverted guard; they now count on both venues per each issuer’s actual rule. Adds the per-occurrence compound_type axis to schema.earnings_fact.v1 (standalone/open/hyphenated/closed/affix_derivation — additive, nullable): closed compounds (wildfire for fire) are Kalshi-No + Polymarket human-review candidates, affix derivations (joyful for joy) count for neither venue, and an unknown value fails loud in every venue tally, at the FactLedger durable-write boundary, and through the catalog read boundary. Live-path parity: the streaming classifier emits one delta per (term, compound_type); its engine-relative timestamp crosses the SSE wire as offset_seconds (never as the wallclock spoken_at), and the stream consumer fail-louds on any naive/numeric temporal field.

[1.11.0] — 2026-07-02 — Earnings-mention markets engine + SDK surface (Phase 27)

Section titled “[1.11.0] — 2026-07-02 — Earnings-mention markets engine + SDK surface (Phase 27)”

Minor release. Adds the earnings-mention markets vertical (Kalshi KXEARNINGSMENTION{TICKER} + Polymarket “will &lt;company> say &lt;word>”) — both the [earnings] engine module and the thin SDK/consumer surface. Additive only; no public API removed or changed.

  • Earnings-mention markets SDK surface (Phase 27). The client/SDK half of the earnings-audio pipeline for Kalshi KXEARNINGSMENTION{TICKER} and Polymarket “will &lt;company> say &lt;word>” markets. No public API removed or changed; all additive.

    • schema.earnings_transcript.v1 + schema.earnings_fact.v1 — canonical transcript/fact schemas with the fail-closed Kalshi-count rule (validate_kalshi_counted_occurrence), role-source provenance, the six venue-rule dimensions, and additive live-streaming fields (is_final/spoken_at/stream_seq, resolution_status="provisional").
    • mostlyright.markets.earnings — resolver parsing a market id into the frozen {ticker, target_word, event_date, resolution_rule} tuple (KXEARNINGSMENTION / KXMENTIONEARN / KXBRKEM roots), Polymarket derive with a webcast-host allowlist, provider-fingerprint naming, point-in-time alignment (align_event_date), and leakage guards (assert_earnings_no_leakage, plus the live-stream firewall assert_not_backtest_source / assert_settlement_source).
    • EarningsAdapter (mostlyright.weather.catalog.earnings) — thin consumer with earnings.live / earnings.hosted sources (byte-identical rows) and a live stream() async generator over the hosted SSE feed.
    • Codegen registries (webcast-provider fingerprints, ticker→HQ-timezone, earnings-calendar seed) exported through the byte-deterministic drift gate.
    • TypeScript parity (@mostlyrightmd/markets, @mostlyrightmd/weather): earnings resolver, hosted-consumer fetcher, PIT helper, and a browser/MV3 EventSource live consumer.
    • New [earnings] optional extra on mostlyrightmd-weather shipping the engine module (mostlyright.weather.earnings): the transcript-anchored role parser + fact builder with the fail-closed, roster-anchored Kalshi-count guarantee (an occurrence is Kalshi-counted only when the speaker is a named participant on the prepared-remarks roster, so a mis-parsed transcript line can never fabricate a countable executive mention), the append-only, audio-free transcript/fact parquet ledger, faster-whisper STT (lazy; av + CTranslate2), live-HLS capture, the streaming transcriber, and the in-process segment bus.

    The hosted earnings backend (deployed capture/STT/serving + SSE) and the operator-gated real-call de-risk spike ship in a later phase; earnings.live is BYO-audio and earnings.hosted fills its reserved seam then.

[1.10.1] — 2026-06-29 — CWOP adapter patch (pre-release review fixes)

Section titled “[1.10.1] — 2026-06-29 — CWOP adapter patch (pre-release review fixes)”

Patch release. Supersedes 1.10.0, which was published before the CWOP review loop completed and shipped the defects fixed below. No public API changes; upgrade is recommended for anyone on 1.10.0.

  • Packaging (import break): mostlyrightmd-weather now floors its core dependency at mostlyrightmd>=1.10.0,<2.0 so mostlyright.weather.cwop’s NoCWOPDataError resolves on a partial upgrade. Previously a stale-core install could ImportError at cwop import time.
  • APRS parser (_aprs.py): numeric fields are only coerced when raw.isascii() and raw.isdigit() (Unicode-digit strings no longer slip through int()); positionless _ weather packets retain their weather payload when the timestamp fails to parse; _closest_to rejects future timestamps and applies a 10-minute skew tolerance.
  • QC (qc/_cwop.py): qc_reliability_score is floored at max(0.05, mean) so a fully-penalized station never reports an exactly-zero (divide-unsafe) reliability.
  • Cache (_cache.py): write_cwop_cache pins an explicit pyarrow schema for byte-stable schema.cwop.v1 round-trips.
  • Scan (_scan.py): the early-exit station counter counts only placeable in-radius stations, so the cap is applied against the true candidate set.
  • Docs: corrected the [1.10.0] CHANGELOG signatures for the scan, nearby, snapshot, and stream entry points.

[1.10.0] — 2026-06-21 — CWOP adapter (PWS live stream + backtest cache)

Section titled “[1.10.0] — 2026-06-21 — CWOP adapter (PWS live stream + backtest cache)”

Adds the Citizen Weather Observer Program (APRS-IS) adapter (mostlyright.weather.cwop) as a standalone live data source for Personal Weather Stations. Deliberately isolated from the parity-critical core — CWOP data never enters research(), merge/observations.py, or live/_sources.py.

  • mostlyright.weather.cwop public surface:
    • nearby(station, *, radius_km=25.0, listen_seconds=60) — resolve an ICAO/NWS station code to coordinates and discovery-listen for nearby CWOP stations (delegates to scan())
    • scan(lat, lon, radius_km=25.0, *, listen_seconds=60, min_reports=1) — discover CWOP stations near a point by listening on APRS-IS with a radius filter (cache-then-augment, early-exit once min_reports stations are seen)
    • stream(station, *, qc=True) — async generator yielding QC-scored CWOPObservations (deduped by (station_id, observed_at))
    • snapshot(station, *, duration_seconds=60, qc=True, persist=False) — collect a fixed window into a schema.cwop.v1 DataFrame; persist=True also writes the backtest parquet cache
    • latest(station) — most-recent observation for a single station
    • history(station, from_date, to_date, *, qc_status) — backtest replay from persisted cache, returns schema.cwop.v1 DataFrame
    • persist_observations(observations) — explicit write path for custom ingestion loops
  • 6-layer QC — weighted geometric mean (0.0–1.0): range (0.20), temporal consistency (0.20), indoor detection (0.15), buddy/ASOS check (0.20), solar-radiation bias (0.10), reliability (0.15). < 0.3 → dropped, 0.3–0.7 → flagged, > 0.7 → clean. History-dependent layers (indoor ≥24 h, solar ≥7 d) pass until enough data accumulates.
  • Persistence — monthly parquet at $HOME/.mostlyright/cache/cwop/{station}/{year}/{month}.parquet (honors MOSTLYRIGHT_CACHE_DIR). filelock-guarded read-modify-write merge, dedup by (station_id, observed_at) first-seen-wins. No current-month skip (CWOP is ephemeral — unlike re-fetchable AWC/IEM, the current month is the only retention opportunity).
  • In-house APRS parser (weather/_aprs.py) — MIT-licensed, no GPLv2 aprslib dependency; pure stdlib (socket + re).
  • NoCWOPDataError — subclass of NoLiveDataError; raised instead of returning []/None.
  • schema.cwop.v1 — new schema, accepts source tags "cwop.live" and "cwop.cache" via _registered_sources. schema.observation.v1 observation_type enum is untouched (parity firewall).
  • [cwop] extra — pulls pandas for snapshot()/history() DataFrames.
  • Parity firewall holds. CWOP is not registered in research(), merge/observations.py (SOURCE_PRIORITY), or live/_sources.py. Models access CWOP only through cwop.history() — a DataFrame the strategy joins itself, never the settlement join.
  • Python-only release. No public TS API surface change; npm packages bump to 1.10.0 in lockstep with no functional change.

[1.9.0] — 2026-06-20 — Global NATIVE satellite ring (live path)

Section titled “[1.9.0] — 2026-06-20 — Global NATIVE satellite ring (live path)”

Extends the Phase 25 GOES extractor to entire-world coverage at native-L2 fidelity on the live/self-parse path — one SDK, no fork. Each instrument family is a distinct, leakage-safe source so a model trained on one never silently reconciles against another. The anonymous ring is landed and routed: GOES + Himawari + VIIRS (no key). EUMETSAT Meteosat SEVIRI landed its CGMS projection + OAuth boundary (golden-vector verified, synthetic-fixture-tested) but its live Data-Store GRIB file reader is a forward seam — so Meteosat is reachable by explicit satellite= and gated out of auto-routing (Europe/Africa falls back to the honest VIIRS fill) until the reader lands. GMGSI mosaic was dropped (lower fidelity, off-brand for a source-identity SDK). Hosted delivery stays a Phase-27 seam.

  • Multi-source contract + the native ring. Generalized the GOES-hardcoded Phase-25 surface into a per-source contract: a schema.satellite.v1 source SET {noaa_goes, jma_himawari, noaa_viirs, eumetsat_meteosat}, per-source product mini-registries, an extended satellite= enum (goes16/17/18/19, himawari8/9, viirs-npp/n20/n21, meteosat-0deg/iodc), cache-path validation for all sources, and a source→handler dispatch table each adapter self-registers into (so a later-wave halt never strands an earlier adapter). Phase-25 noaa_goes behavior is byte-unchanged.
    • noaa_goes GOES-West (goes18 operational PACUS, goes17 archive) — config-swap on the existing GOES path, same noaa_goes source identity (a single model can union East+West). Native Americas/Pacific incl. Hawaii. Anonymous.
    • jma_himawari — Himawari-8/9 AHI L2 single-pixel extraction from the anonymous noaa-himawari8/9 buckets. Native Asia-Pacific. Geostationary fixed-grid.
    • noaa_viirs — VIIRS (SNPP/NOAA-20/21) polar L2 SWATH nearest-neighbor extraction (vectorized haversine / 3D unit-sphere nav, numpy-only, antimeridian- and pole-safe; max-distance gate) from the anonymous noaa-nesdis-{snpp,n20,n21}-pds buckets. Global incl. poles, ~2 overpasses/day. Rows carry sat_lon_used=null (no single sub-satellite point) — the row contract was generalized to make sat_lon_used nullable for swath sources.
    • Row-contract generalizationsat_lon_used is now nullable (geostationary sources populate it; the VIIRS polar swath leaves it null), pixel_row/pixel_col documented as native grid OR swath line/sample, and source-generic notes — so a VIIRS-shaped row and a Phase-25 GOES row both validate.
    • eumetsat_meteosat — Meteosat SEVIRI Cloud Mask (MSG-CLM, Core tier / CC-BY-4.0) single-pixel extraction from the keyed EUMETSAT Data Store (the only keyed source in the ring). Native Europe/Africa/Indian-Ocean. Geostationary via the CGMS LRIT/HRIT projection (integer CFAC/LFAC/COFF/LOFF decode; “Meteosat sweeps y” — latitude drives the line; sub-sat /45.5°E), distinct from GOES scan-angle and Himawari 2D geolocation; sat_lon_used populated. The projection is verified against independent golden navigation vectors (published CGMS constants + a forward↔inverse round-trip, not module self-consistency): sub-satellite (0,0)→(col 1856, line 1856) 1-based, EGLL → (col 1866, line 3399), off-disk → loud raise. Single OAuth boundary (M4): one credential precedence (env EUMETSAT_CONSUMER_KEY/_SECRETeumdac stored creds), one injectable Data-Store client (bearer token via HTTP Basic client_credentials at api.eumetsat.int/token); missing creds raise SourceUnavailableError only when the live path is invoked, with the eumdac set-credentials / env-var hint. Live file-reading is a forward seam: the real Data-Store MSG-CLM product is GRIB and reading the CGMS nav/pixels off it is not yet wired (Phase 26 ships SEVIRI synthetic-fixture-verified only); _eumetsat_extract._read_nav reads the synthetic fixtures’ nav and any standard CGMS attributes, and otherwise raises a precise “live file navigation is not yet wired … forward seam” signal. So Meteosat is gated out of auto-routing (auto_route_target=False) — an explicit satellite="meteosat-0deg" still dispatches to the handler; the band auto-routes to the VIIRS fill. _open_dataset opens with mask_and_scale=False (the extractor is the single decode site) ready for the wired reader.
  • Auto-routing when satellite= is omitted (Wave 5). satellite() now defaults satellite=/product= to None and auto-routes by the first resolved station’s coverage region over the self-registered handlers — GOES for the Americas/Pacific (with a GOES-East/West longitude split), Himawari for Asia-Pacific, VIIRS for the poles and any band no landed geostationary source covers yet. MODEST: one (source, satellite, product) triple per call, paired with the source’s cheap default product. Explicit satellite= bypasses routing entirely (Phase-25 parity). Unknown station under auto-routing raises a clear ValueError. The Europe/Africa/Indian-Ocean band was reserved as a clean Meteosat extension point — Wave 6 landed the eumetsat_meteosat handler + MSG-CLM product + projection, but because the live Data-Store GRIB reader is an unwired forward seam, Meteosat is gated out of auto-routing (auto_route_target=False) and that band currently falls back to the honest VIIRS global fill (a key-holding user auto-routing over Europe gets real VIIRS data, not a confusing unwired-reader raise). An explicit satellite="meteosat-0deg" still dispatches to the Meteosat handler. The routing decision is key-free; flip auto_route_target=True when the live reader lands and the band auto-routes to Meteosat with no router change.
  • Packaging. The [satellite] extra now pins h5py>=3.0 (real HDF5 reads) and adds eumdac>=3.1 (the EUMETSAT Data-Store client, used only on the keyed Meteosat path).
  • Docs. docs/satellite.md + the README satellite section rewritten for the native ring: source-identity table, coverage map, auto-routing (with the honest Meteosat forward-seam note — projection landed, live reader pending, gated out of auto-routing), live-vs-hosted (delivery="hosted" raises a “Phase 27” error — no api.mostlyright.md anywhere), and the EUMETSAT key-setup section (EUMETSAT_CONSUMER_KEY/_SECRET env vars → eumdac set-credentials → GCP Secret Manager; Core-tier CC-BY-4.0 note).
  • Keyless build + tests. GOES/Himawari/VIIRS are anonymous (no key). EUMETSAT is the only keyed source, and its build + unit tests are fully keyless (mocked Data-Store transport + synthetic SEVIRI fixtures + independent golden navigation vectors, ≥80% coverage on the keyless extractor + transport); the real Data-Store fetch is a single CI-excluded @pytest.mark.live test.
  • CI-guard. Every new test module importing the [satellite] extra is skip-guarded so the base no-extra fast-suite/pandas-3/polars/coverage-gate jobs still pass (new tests SKIP, 0 failures); a base-suite acceptance test asserts no extra is bound at module scope.
  • Python-only. No public-API change touches the TS surface this release; the npm packages bump to 1.9.0 in lockstep with no functional change.
  • Cross-region auto-route warning now fires on a GOES-East/West satellite split. When satellite= is omitted and a multi-station list straddles the GOES East/West boundary (both noaa_goes but a different platform — e.g. an East station plus a mid-Pacific one), the collapse warning previously compared only source and stayed silent, so a genuinely off-disk station could be dropped with no signal. The guard now compares the full (source, satellite) route and names the divergent target.

[1.8.0] — 2026-06-19 — GOES ABI L2 satellite ingest (free local tier)

Section titled “[1.8.0] — 2026-06-19 — GOES ABI L2 satellite ingest (free local tier)”

Minor release: a new optional extra mostlyrightmd-weather[satellite] adds local-first GOES-16/19 ABI Level-2 single-pixel extraction at station locations, mirroring the [nwp] extra and the forecast_nwp() pipeline shape.

  • weather.satellite() fetcher + [satellite] optional extra (#78, Phase 25). Reads anonymous public NOAA NODD buckets (AWS noaa-goes16/19, GCP mirror) directly — no hosted backend — and returns a leakage-safe DataFrame of single-pixel ABI L2 derived products extracted at a station’s ICAO lat/lon. Covers GOES-16 and GOES-19 (the GOES-East lineage) across 10 products / 18 variables: ABI-L2-ACMC (cloud/clear-sky mask — the primary CONUS product), LSTC, TPWC, ACHAC, CTPC, AODC, DSIC (stability indices), DSRF (downward shortwave radiation — full-disk, gated behind a one-time warning), and the 3D vertical profiles LVMPC / LVTPC.
    • schema.satellite.v1 with source identity "noaa_goes" (shared by live self-extraction and the future paid adapter so a cross-delivery model reconciles), a per-row delivery lineage enum {live, hosted} (informational only, NOT source-identity), qc_status (annotate-never-drop: clean/flagged/suspect), ICAO ^[A-Z]{4}$ station identity, and event-time (scan_start/end) vs knowledge-time (as_of) wiring through KnowledgeView so satellite is leakage-safe in research()/backtests.
    • Whole-file S3/GCS transport (single full-object read into memory, not byte-range), a parquet cache tier at ~/.mostlyright/cache/satellite/..., and a fleet-ready backfill CLI (python -m mostlyright.weather.satellite backfill) with crash-safe resume, an AWS/GCP --mirror selector, and an on-demand throughput probe.
    • Lazy-imports the heavy deps (boto3/s3fs/gcsfs/h5netcdf/xarray) inside the public function — the module imports cleanly without the extra and raises SourceUnavailableError with a pip install mostlyrightmd-weather[satellite] hint when it is absent.
  • Dual version bump: PyPI 1.8.0 (mostlyrightmd, mostlyrightmd-weather, mostlyrightmd-markets) and npm vts-1.8.0 (@mostlyrightmd/core, @mostlyrightmd/weather, @mostlyrightmd/markets, mostlyright). This release is Python-only — the TypeScript satellite reader is a deferred parity ticket (future h5wasm impl) per CROSS-SDK-SYNC.md; the npm 1.8.0 carries no satellite surface and bumps for version parity only.
  • Known follow-ups (tracked, non-blocking — the bulk backfill is the not-yet-run path): wire cached-partition reads into satellite(cache=True); have the backfill write finalized (schema-shaped) rows; add dead-PID/stale-lock recovery to the backfill progress lock.

[1.7.0] — 2026-06-12 — GEFS/CFS ensemble member selection

Section titled “[1.7.0] — 2026-06-12 — GEFS/CFS ensemble member selection”

Minor release: forecast_nwp() gains a member= ensemble-member selector for GEFS and CFS.

  • member= ensemble selector on forecast_nwp() for GEFS + CFS (#75, closes #74). docs/forecasts.md documented member= for GEFS but the public function never exposed it — the path builders (ge{member} / CFS 6hrly_grib_{member}) supported member selection all along. forecast_nwp(station, "gefs", member="p05") now fetches a specific ensemble member; valid members are validated against the closed enums (GEFS: c00 control + p01..p30 perturbations + avg/spr statistical products; CFS: 01..04) with a loud ValueError for any other model or out-of-enum value, raised before the [nwp] extra imports. member=None (default) stays byte-identical to v1.6.0 behavior. Works on both the single-cycle path and cycle_range_start/cycle_range_end backfills. The output schema is unchanged (no member column — selector only; tracked as future work). TS twin: ForecastNwpOptions gains readonly member?: string (signature-forward; TS NWP execution remains v2.0+).
  • mostlyrightmd[research] extra now pins mostlyrightmd-weather>=1.7.0 (was >=1.6.0): the core forecast_nwp() wrapper threads member= to the weather impl, a kwarg introduced in weather 1.7.0. Default (member=None) calls remain call-compatible with older weather installs — the wrapper only passes the kwarg when explicitly set.
  • Dual version bump: PyPI 1.7.0 (mostlyrightmd, mostlyrightmd-weather, mostlyrightmd-markets) and npm vts-1.7.0 (@mostlyrightmd/core, @mostlyrightmd/weather, @mostlyrightmd/markets, mostlyright).

[1.6.0] — 2026-06-06 — Open-Meteo forecast-join correctness, NWP fields, OM rate-limiting + research() docs

Section titled “[1.6.0] — 2026-06-06 — Open-Meteo forecast-join correctness, NWP fields, OM rate-limiting + research() docs”

Minor release bundling two correctness fixes and two feature PRs.

  • NWP forecast fields cloud_cover_pct, visibility_m, cloud_ceiling_m for HRRR/GFS (#68, closes #63). Adds three nullable float64 columns to schema.forecast_nwp.v1 (additive, backward-compatible) with per-model GRIB maps for HRRR + GFS and QC range predicates. The change also resolves a pre-existing latent GFS bug: (variable, level) keys that resolve to two .idx records (GFS TCDC:entire atmosphere instantaneous-vs-averaged, and the APCP:surface twin) tripped the GribIntegrityError ambiguity guard on the default forecast_nwp(station, "gfs") path — deterministic record disambiguation now picks the instantaneous record (lowest record_no tiebreak) while keeping the loud-fail guard for genuinely unexpected duplicates. TS twin: schema.forecast_nwp.v1 is now wired into the TS codegen so @mostlyrightmd/core ships a ForecastNwpV1 type + ajv validator with the new columns.
  • Open-Meteo forecast cache wiring + weight-aware throttle + variable trim (#66, closes #64). The Phase 20 forecast cache is now wired into research(..., forecast_source="open_meteo") — previous-runs data is immutable, so repeated backtests serve from disk instead of re-hitting the free-tier rate limit. The research path also requests only the 3 variables the pairs join consumes (down from 18, cutting weighted call cost ~1.8×) and chunks long windows. Cache partitions are written on full-month boundaries so a subrange request can’t leave a partition incomplete (a later same-month window is served complete from disk, not silently truncated).
  • build_pairs_row() misclassified Open-Meteo forecasts as IEM MOS when a derived issued_at was present (#69, fixes #67). Phase 20+ Open-Meteo rows carry a derived issued_at, so the old issued_at-presence discriminator routed them into the IEM MOS aggregation path — silently nulling forecast temperatures and polluting IEM run-selection when both sources were combined. Records are now split by the authoritative source field (open_meteo* → Open-Meteo, else IEM; legacy source-less/issued_at-less rows stay Open-Meteo for backward compatibility). The fix also preserves Open-Meteo pop_6hr_pct / qpf_6hr_in and fcst_issued_at provenance through the source-routed path, and — critically — excludes Open-Meteo rows from runs issued after market close from the temp/POP/QPF aggregation (not just the timestamp), closing a lookahead-leakage path. The IEM-MOS byte-equivalent parity gate is unaffected.
  • Clarified that research() returns daily rows, not hourly observations (#70, addresses #52). The Returns docstring now states that research() yields one daily settlement-summary row per date (obs_* are settlement-window aggregates), points to weather.obs() for an observation-only daily frame, and notes that sub-daily / raw_metar access is a Sprint 0.5+ item.
  • Dual version bump: PyPI 1.6.0 (mostlyrightmd, mostlyrightmd-weather, mostlyrightmd-markets) and npm vts-1.6.0 (@mostlyrightmd/core, @mostlyrightmd/weather, @mostlyrightmd/markets, mostlyright). The TS twin gains the ForecastNwpV1 type + ajv validator for the new NWP columns ([#63] parity); the Open-Meteo rate-limiting ([#64]) and forecast-join ([#67]) changes are Python-internal and tracked for TS via CROSS-SDK-SYNC.md.

[1.5.2] — 2026-06-01 — Fetcher correctness fixes + configurable HTTP retries/timeout

Section titled “[1.5.2] — 2026-06-01 — Fetcher correctness fixes + configurable HTTP retries/timeout”

Patch release: four bug fixes across the Python and TypeScript SDKs — fractional integer-field handling in Open-Meteo, an exact-window observation fetch that no longer over-fetches a whole year, bounded-parallel IEM MOS forecast fetches, and env-var overrides for HTTP retry/timeout.

  • Open-Meteo integer-schema fields dropped rows on fractional values (#59 Python + TypeScript parity mirror). When the upstream API returned a fractional value in a nominally-integer column (e.g. cloud_cover of 12.5), pandas’ safe Int64 cast raised and rows could be silently dropped. The fetcher now rounds before the cast; the TypeScript fetcher mirrors this with banker’s rounding (round half to even) so integer-schema fields (wind_dir_deg, cloud_cover_pct, freezing_level_m, visibility_m, weather_code) stay byte-equivalent to the Python output and valid against schema.forecast.station.v1.
  • obs(strategy="exact_window") over-fetched a whole calendar year (TypeScript) (#60). The TS IEM ASOS fetcher always widened the start to Jan 1 (the cache-key shape needed by archive callers), so a 1-day exact-window lookup pulled ~734 KB instead of ~9.8 KB. A new internal exactStart path issues a single date-bounded request for [from, to+1 day), matching the Python _exact_fetch behavior. The default year-padded path is unchanged for warm_cache / research() callers.
  • IEM MOS forecast fetches ran serially (TypeScript) (#61). Independent runtime-cycle requests now fan out with a bounded concurrency pool (cap 8) instead of one-at-a-time awaits — most of the speedup on typical short windows while staying polite on large historical ranges. Output is byte-identical to the serial path (input ordering preserved); the pool fails fast on a non-404 HTTP error.
  • Configurable HTTP retries and timeout via environment variables (Python) (#62). MOSTLYRIGHT_HTTP_MAX_RETRIES and MOSTLYRIGHT_HTTP_TIMEOUT let batch callers tune IEM 429 backoff without monkey-patching site-packages. Defaults are unchanged (3 retries, 60 s). Invalid values fall back to the defaults with a warning: MAX_RETRIES requires a minimum of 1 (0 would issue no request at all), and the timeout rejects non-positive and non-finite (nan/inf) values.
  • Dual publish: PyPI 1.5.2 (mostlyrightmd, mostlyrightmd-weather, mostlyrightmd-markets) carries the Python Open-Meteo rounding fix [#59] + HTTP env overrides [#62]; npm vts-1.5.2 (@mostlyrightmd/core, @mostlyrightmd/weather, @mostlyrightmd/markets, mostlyright) carries the TypeScript Open-Meteo rounding mirror [#59], the exact-window fetch fix [#60], and the bounded-parallel MOS fetch [#61].

[1.5.1] — 2026-05-31 — Open-Meteo Single-Runs HTTP 400 fix + ingest parallelization

Section titled “[1.5.1] — 2026-05-31 — Open-Meteo Single-Runs HTTP 400 fix + ingest parallelization”
  • Open-Meteo Single-Runs API rejected with HTTP 400 (#50 Python, #53 TypeScript — cross-SDK parity). The Single-Runs endpoint (single-runs-api.open-meteo.com/v1/forecast) returns HTTP 400 when sent start_date/end_date, so every Single-Runs request (training mode + issued_at) was failing against the live API. Both fetchers were sending the date params unconditionally. Fix mirrors fetch_open_meteo in both SDKs: for the Single-Runs endpoint send run= only, then clip the full run-horizon response to the requested [from_date, to_date] window (end-inclusive day) after parsing. All other endpoints (Previous-Runs, Live, Seamless) keep their date params unchanged. TDD regression tests in both SDKs assert the URL omits start_date/end_date + carries run=, and that a multi-day horizon is trimmed to the requested day.
  • Phase 24 — ingest performance: parallelized forecast extraction (#49). Forecast extraction now runs per-station work in parallel during ingest. Python-internal performance change only — byte-identical output, no public-API or behavior change. No TypeScript counterpart.
  • Dual publish: PyPI 1.5.1 (mostlyrightmd, mostlyrightmd-weather, mostlyrightmd-markets) carries the Python Single-Runs fix [#50] + the ingest parallelization [#49]; npm vts-1.5.1 (@mostlyrightmd/core, @mostlyrightmd/weather, @mostlyrightmd/markets, mostlyright) carries the TypeScript Single-Runs fix [#53].

[1.5.0] — 2026-05-30 — Phase 23: Polymarket settlement catalog refresh

Section titled “[1.5.0] — 2026-05-30 — Phase 23: Polymarket settlement catalog refresh”

Changed — Phase 23: Polymarket settlement catalog refresh (BREAKING, pre-1.0)

Section titled “Changed — Phase 23: Polymarket settlement catalog refresh (BREAKING, pre-1.0)”
  • Registry expanded to 94 stations (29 US + 65 international). Added 28 records to reconcile the catalog to Polymarket’s authoritative 51-city settlement roster: 4 US (KLGA NYC, KORD Chicago, KDAL Dallas, KBKF Denver) + 24 international (21 net-new cities — Ankara, Busan, Cape Town, Chengdu, Chongqing, Guangzhou, Istanbul, Jeddah, Jinan, Karachi, Kuala Lumpur, Lucknow, Manila, Mexico City, Panama City, Qingdao, Shenzhen, Tel Aviv, Toronto, Wuhan, Zhengzhou — plus move targets EGLC, UUWW, RCSS).
  • mostlyright.stations._POLYMARKET_ICAOS is now an explicit enumerated set (50 stations), not country-derived. Polymarket no longer settles every international station, so “non-US ⇒ polymarket” was retired. filter_by_venue("polymarket") equals the registry-resident stations of the 51-city map (the 51st, Hong Kong, settles against HKO — the Observatory, no airport ICAO, v0.2-deferred).
  • 7 station moves (Polymarket only; old ICAOs kept as bare records): Dallas KDFWKDAL, Denver KDENKBKF, London EGLLEGLC, Moscow UUEEUUWW, Taipei RCTPRCSS, Paris default LFPGLFPB, Hong Kong VHHHHKO.
  • Houston cross-venue conflict. Polymarket moved to KHOU (Hobby) while Kalshi stays KIAH (Intercontinental) — a new cross-issuer divergence alongside NYC (KNYC vs KLGA) and Chicago (KMDW vs KORD). Guarded by tests/test_cross_issuer_station_identity.py.
  • 27 stale Polymarket cities untagged (station records retained as bare weather stations): 6 US (Boston, DC, Minneapolis, Philadelphia, Phoenix, Detroit — Detroit stays kalshi) + 21 international.
  • Deferred sources retargeted. DEFERRED_STATIONS {VHHH, RCTP}{HKO, RCSS}; Hong Kong now fully defers every measure (previously HK-high routed via VHHH METAR). Taipei defers via RCSS (CWA). v0.2 lands the weather.gov.hk + CWA clients.
  • Kalshi roster frozen_KALSHI_ICAOS (21) unchanged; no kalshi tag removed. v0.14.1 parity gate stays green.
  • TS ships lockstep via codegen (schemas/stations.json, polymarket-city-stations.json@mostlyrightmd/core + @mostlyrightmd/markets).
  • Note: new non-K station metadata uses authoritative public airport data; live IEM/AWC coverage per ICAO should be confirmed (@pytest.mark.live) before advertising these as live markets.

[1.4.0] — 2026-05-28 — Phase 22: Station-model refactor + Las Vegas (Kalshi TLV)

Section titled “[1.4.0] — 2026-05-28 — Phase 22: Station-model refactor + Las Vegas (Kalshi TLV)”

Changed — Phase 22: Station-model refactor (BREAKING, pre-1.0)

Section titled “Changed — Phase 22: Station-model refactor (BREAKING, pre-1.0)”
  • Unified, venue-agnostic station catalog in core. New mostlyright.stations module exposes StationCatalog (CATALOG, Station) with get(), filter_by_venue(), and filter_by_country(). A station is a physical fact; prediction-market participation is a venues tag (frozenset[str]) on StationInfo, not a separate module or a US/international split.
  • Registry expanded to 66 stations (25 US + 41 international). Added the five Kalshi settlement stations the v0.14.1 set had wrong — KIAH (Houston Intercontinental, not KHOU), KDTW, KCVG, KBNA, KSLC. The four non-settlement US weather stations (KHOU/KMSY/KOKC/KSAT) remain queryable with an empty venues tag.
  • KLAS (Las Vegas, ticker TLV) tagged as a Kalshi settlement station (issue #39). Kalshi trades KXHIGHTLV/KXLOWTLV, settling against the NWS CLILAS report; the station was already in the registry but carried no venues tag.
  • Venue tags follow each issuer’s actual settlement map, not country. Kalshi and Polymarket settle several shared cities against different stations (NYC: KNYC vs KLGA; Chicago: KMDW vs KORD), so filter_by_venue("kalshi") ≠ “every US station”. kalshi=21, polymarket=56. Markets derives its settlement universe by filtering the catalog; contract tests pin the tags against the citation + city-station maps.
  • StationInfo.kalshi_traded is now derived from venue tags instead of hard-coded True.
  • TradewindsErrorMostlyRightError and TradewindsResultMostlyRightResult across Python and TypeScript — no deprecation alias (pre-1.0). The base error code string changed TRADEWINDS_ERRORMOSTLYRIGHT_ERROR. The TRADEWINDS_CACHE_DIR env-var migration shim is unaffected (separate v0.3-removal track).
  • schemas/stations.json now exports the venues tag for TS codegen parity.

[1.3.1] — 2026-05-28 — GHCNh integer-°F precision fix

Section titled “[1.3.1] — 2026-05-28 — GHCNh integer-°F precision fix”
  • GHCNh temp_f false precision for U.S. ASOS stations (#36, closes #16). GHCNh-sourced observations emitted back-converted temp_f (e.g. 51.08°F for temp_c=10.6°C) via celsius_to_fahrenheit. The raw METAR preserved in the GHCNh PSV REM column carries the T-group remark (e.g. T01060067), marking the row as integer-°F native; temp_f (and dewpoint_f) is now recovered as round(temp_c * 9/5 + 32) — matching the AWC (PREC-01) and IEM (PREC-02) paths. Non-T-group rows (synoptic / international) keep the legacy celsius_to_fahrenheit path. temp_c is unchanged. Closes the Phase 18 GHCNh deferral.
  • Observation cache invalidation for the GHCNh change. Bumped _CACHE_SCHEMA_VERSION (v2-phase18-integer-fv3-ghcnh-integer-f) so existing observation caches re-parse instead of serving stale GHCNh temp_f to upgraded users.
  • Affects mostlyrightmd-weather only. No TypeScript changes (the TS GHCNh adapter does not yet exist), so npm packages are not part of this release.

[1.3.0] — 2026-05-28 — Phase 20: Open-Meteo Forecast Source Integration

Section titled “[1.3.0] — 2026-05-28 — Phase 20: Open-Meteo Forecast Source Integration”

Added — Phase 20: Open-Meteo Forecast Source Integration (leakage-safe, 36-model)

Section titled “Added — Phase 20: Open-Meteo Forecast Source Integration (leakage-safe, 36-model)”
  • Open-Meteo as third forecast source (Phase 20). 36 models across NCEP / ECMWF / DWD / Météo-France / JMA-KMA-CMA-BoM / UKMO-MetNo / GEM Canada providers. New mostlyright.weather.fetch_open_meteo() Python API and @mostlyrightmd/weather openMeteoForecasts() TS API ship in lockstep.
  • schema.forecast.station.v1 unified per-station forecast schema covering IEM MOS shared core + Open-Meteo extras (apparent_temp_c, shortwave_radiation_wm2, cape_jkg, cloud_cover_pct, surface_pressure_hpa, freezing_level_m, etc.). schema.forecast.iem_mos.v1 retained as back-compat alias.
  • research(forecast_source="open_meteo") + list form forecast_source=["iem_mos", "open_meteo"] — cross-source training data without silent merge; every row carries explicit source identity (open_meteo.previous_runs, open_meteo.single_run, open_meteo.live).
  • IssuedAtMissingError + OpenMeteoSeamlessLeakageError typed exceptions in both Python (mostlyright.core.exceptions) and TypeScript (@mostlyrightmd/core/exceptions). Payload keys snake_case for cross-SDK MCP parity; both carry origin_issue="Tarabcak/mostlyright#70".
  • ~/.mostlyright/cache/v1/forecasts/{source}/{model}/{station}/{YYYY}/{MM}.parquet cache tier with filelock + per-source partitioning. Live + seamless sources never cached.
  • 5-fixture OM-08 leakage regression suite (Python + TS) reproducing the exact Tarabcak/mostlyright#70 case. Headline assertion: NYC 2024-06-01 23:00Z _previous_day1issued_at = 2024-05-31T18:00Z (provably ≤ the 17:00Z snapshot).
  • docs/forecast-sources.md comparing IEM MOS / NWP gridded / Open-Meteo across models, latency, units, leakage-safety. Includes Python + TS quickstart examples for the new forecast_source="open_meteo" surface.
  • The Open-Meteo Historical Forecast (seamless) endpoint is BANNED for training data. fetch_open_meteo(mode="seamless") raises OpenMeteoSeamlessLeakageError unless allow_leakage=True is passed. Even then, LeakageDetector rejects the rows when as_of is asserted. Origin: Tarabcak/mostlyright#70 — legacy /forecast_series proxied the seamless feed without preserving issued_at, silently using post-snapshot 18Z runs in h13 EDT training data. Apparent +6pp lift on the MCAGE-OM benchmark was partly attributable to this leakage and not reproducible live.
  • Live mode (mode="live") derives issued_at via conservative cycle-math fallback floor_to_cycle(now − publish_lag(model), cycles) per Phase 20 D-06. Open-Meteo’s Metadata API integration (for authoritative last_run_initialisation_time) is a follow-up — the public HTTPS URL was not documented as of 2026-05-28.
  • Distribution: lockstep PyPI (mostlyrightmd, mostlyrightmd-weather) + npm (@mostlyrightmd/weather, @mostlyrightmd/core).
  • NwpNotAvailableError typed subclass in @mostlyrightmd/core. Raised by forecastNwp() instead of the generic DataAvailabilityError. Carries typed .station and .model properties so consumers get instanceof-based dispatch + IDE autocomplete instead of parsing the hint string. Back-compat preserved — NwpNotAvailableError extends DataAvailabilityError with reason="model_unavailable", so existing catch (e instanceof DataAvailabilityError) paths continue to work unchanged. 6 new tests cover the subclass + back-compat catch paths.
  • docs/nwp-forecasts.md — dedicated TypeScript-focused page documenting the v1.x forecastNwp() deferral. Explains why GRIB2 decode is browser-impractical today (eccodes / cfgrib are native-only; WASM port has prohibitive bundle size), lists the recommended catch pattern, points at iemMosForecasts() for the 7 MOS-covered stations and the Python SDK for everything else, lists all 24 signature-supported NwpModel literals, and tracks the v2.0+ roadmap.
  • TS package READMEs now visibly call out the stub surfaces (forecastNwp, climateGaps) instead of burying them in the docs site. @mostlyrightmd/weather README includes a “What’s NOT in the TypeScript SDK (yet)” section; mostlyright meta-package README ships a decision-matrix table showing which functions are wired today vs deferred to v2.0+.
  • forecastNwp() hint string now points at docs/nwp-forecasts.md instead of the inline docs/forecasts.md#typescript-lane anchor. The dedicated page surfaces better in search + IDE link previews than an in-page anchor.

[1.2.0] — 2026-05-27 — Phase 21: TypeScript SDK Parity Completion

Section titled “[1.2.0] — 2026-05-27 — Phase 21: TypeScript SDK Parity Completion”

Closes the 11 audit-identified deltas between the Python and TypeScript SDKs from the 2026-05-27 cross-SDK audit. The TS SDK is now surface-equivalent to Python across research() kwargs, the typed-exception hierarchy, the cache versioning invariant, the obs() ingest-planner port, the dailyExtremes() wrapper, the markets trades shape, the forecastNwp() stub messaging, the codegen description propagation, the preprocessing namespace, and the climateGaps() structured error.

  • DataAvailabilityError typed exception in BOTH SDKs (closes Issue #26). Shared 6-value reason enum (model_unavailable | out_of_window | cache_miss | source_404 | source_5xx | rate_limited) lets consumers branch on e.reason instead of string-matching the error message. Subclasses TradewindsError — existing except TradewindsError handlers continue to work. Three Python and four TS raise sites migrated; SourceUnavailableError remains in place for back-compat at other call sites.
  • research() composable kwargs surface — both SDKs (TSPARITY-01). The TS research() accepts the same 9 kwargs as Python: include_forecast, forecast_model, forecast_models, qc, tz_override, sources, source, backend, return_type. Per D-03, backend="polars" raises DataAvailabilityError(reason="model_unavailable") in TS (no Polars in browser/Node); return_type="wrapper" is accepted as no-op. Both SDKs now reject mutually-exclusive misuse (sources+source, forecast_model+forecast_models, forecast model without include_forecast=True) as TypeError before any network fetch. The TS validator treats null and undefined uniformly as absent so JSON None ↔ null ↔ undefined round-trip matches Python is not None semantics.
  • TS obs(station, from, to, opts?) (TSPARITY-04) — port of Python tw.weather.obs() Phase 7 ingest-planner. Strategy dispatch: auto (window-size heuristic), exact_window (date-bounded fetch — small calls pull small payloads), warm_cache (year-padded fetch for overlapping callers), hosted (raises DataAvailabilityError). source="ghcnh" raises DataAvailabilityError (TS GHCNh fetcher port deferred) rather than silently returning [].
  • TS dailyExtremes(station, from, to, opts?) (TSPARITY-05) — fetch+rollup wrapper matching Python mostlyright.international.daily_extremes(). Day-bucketing uses the station’s IANA local tz from the STATIONS registry; pre-rollup fetch widens by ±1 UTC day so tz-edge observations are captured; post-rollup output clipped to caller’s [fromDate, toDate] by station-local date. US ASOS stations get integer-°F precision (Phase 18 invariant); other stations get 0.1-precision values.
  • TS versionedCacheStore adapter (TSPARITY-03) — wraps any CacheStore so reads with mismatched/missing _cache_schema_version return null (cache miss → re-fetch). Matches Python’s Phase 18 18-08 parquet kv_metadata version stamp ("v2-phase18-integer-f"). Wired into BOTH defaultCacheStore() entries (Node + browser). Pre-bump cached values silently re-fetch on next call.
  • TS mostlyright.preprocessing namespace (TSPARITY-10) — lowercase namespace alias matching Python’s mostlyright.preprocessing. The PascalCase Preprocessing alias is deprecated but kept for back-compat; removal target v2.
  • TS validator schema descriptions via codegen (TSPARITY-08) — ColumnSpec.notes → JSON Schema description → TS interface TSDoc /** ... */ comments. Behavior was already shipped via json-schema-to-typescript; Phase 21 added a regression test (with ts.transpileModule() parse check) to guard against future codegen-pipeline regressions.
  • TS parity gate scaffold (TSPARITY-02) — packages-ts/meta/tests/parity/README.md documents the 5-fixture row-equivalence assertion shape. Operator-triggered msw recording capture is the remaining gate-activation step.
  • Markets trades shape parity tests (TSPARITY-06) — three potential divergences from the 21-06 plan locked in via packages-ts/markets/tests/parity/trades-shape.parity.test.ts: Kalshi candle bucket label, Polymarket pagination default, and equal-timestamp trade-ID tiebreaker (assertion: both SDKs preserve HTTP order — no in-SDK sort).
  • TS forecastNwp() improved stub messaging (TSPARITY-07) — raises DataAvailabilityError(reason="model_unavailable") with a hint pointing operators at iemMosForecasts() workaround for stations with MOS coverage. New banner in docs/forecasts.md.
  • TS climateGaps() structured error (TSPARITY-11) — raises ClimateGapsNotImplementedError (subclass of DataAvailabilityError) with a hint explaining the server-only architectural constraint. New docs/climate-gaps.md page.
  • research() validation now fires BEFORE backend dispatch + network fetch on both SDKs. A typo in kwargs no longer hits live APIs / mutates the cache before raising.
  • SourceUnavailableError migration sites — three Python raise sites moved to DataAvailabilityError(reason="model_unavailable"): obs(strategy="hosted") (was NotImplementedError), catalog.get_adapter(unknown) (was SourceUnavailableError), forecasts.forecast_nwp() ImportError fallback (was SourceUnavailableError).
  • obs(strategy="exact_window") now issues a date-bounded IEM request instead of pulling whole calendar years. A 1-day call pulls ~1 day of bytes (matching Python’s Phase 7 ingest-planner semantics).
  • README + package metadata: SEO copy sweep across top-level README, every per-package README, every pyproject.toml/package.json description, and (newly) keywords arrays on the npm packages. Repositions the SDK as a universal public-data interface — weather + prediction-markets are today’s adapters; SEC filings, Federal Reserve series, court filings, FDA approvals, and equities structured data are named in the roadmap.
  • README package tables: added equities, courts, and fda to the planned-adapters list on both the Python and TypeScript sides so the roadmap is legible at a glance.
  • GitHub repo: description repositioned as a public-data SDK with weather + prediction-markets as launch adapters and a named adapter roadmap (EDGAR, FRED, court filings, FDA, equities). Discovery topics expanded to include public-data, financial-data, sec-edgar, and fred.
  • TS cache returned raw concrete storedefaultCacheStore() in both Node (default.ts) and browser (index.browser.ts) entries now wraps the inner store in versionedCacheStore(CACHE_SCHEMA_VERSION) so pre-Phase-18 ASOS 0.06°F-precision cache rows silently miss instead of being served stale. Iter-1/iter-2 review fix.
  • TS dailyExtremes() tz-edge silent row loss — pre-fix the filter compared UTC date prefix against station-local date strings asymmetrically, dropping local-day observations that fell on adjacent UTC days. Iter-2 fix widens pre-rollup UTC fetch by ±1 day; clip post-rollup by station-local localDate matching Python parity.
  • TS obs(source="ghcnh"|"cli") silent empty rows — pre-fix the type allowed those values but fetchByStrategy had no branch, so the call silently returned []. Iter-1 fix: "cli" removed (not a valid Python source filter); "ghcnh" raises DataAvailabilityError upfront.
  • README: switch PyPI download badges from Pepy/Shields to Badgen with PyPIStats detail links because Pepy has not indexed the mostlyrightmd* projects and Shields can surface upstream rate limits for fresh packages.

Metadata-only republish. No runtime API or source-code behavior changes.

  • PyPI + npm: republish the already-merged registry metadata so latest package artifacts include website, docs, repository, issue-tracker, and changelog links.
  • PyPI + npm: keep the broadened core descriptions that position the SDK as prediction-market research over public data, not only weather research.

Patch release. Closes plan 18-09 (parity fixture re-capture), plan 18-11c Task 2 (TS parity fixture re-export), and refreshes registry-visible metadata. Runtime SDK APIs and source behavior are unchanged.

  • README: add PyPI/npm monthly download badges, keep the public-data positioning beyond weather-only, and surface package-level download badges for stable Python and TypeScript packages.
  • PyPI metadata: add Homepage, Documentation, Repository, Issues, and Changelog URLs to all three Python distributions; broaden the core mostlyrightmd package description beyond weather-only.
  • npm metadata: add homepage, repository, and issue-tracker fields to all four published TypeScript packages; broaden the @mostlyrightmd/core description beyond weather-only.
  • Docs: refresh the Sphinx landing description to position the SDK as public-data prediction-market research, not only weather settlement research.

(The v1.1.1 number was burned by a concurrent registry-metadata refresh published from a separate branch. This v1.1.2 release ships the parity work originally targeted for v1.1.1.)

  • Parity fixture re-capture (plan 18-09): All 5 cases re-captured against post-Phase-18 research() via live network. Outcome: all 5 parquet bytes IDENTICAL to the pre-Phase-18 v0.14.1 baseline. The chosen cases (Jan 2025 → Nov 2025) fall outside AWC’s 168h archive window, so they source from IEM/GHCNh — whose temp_f paths Phase 18 does NOT change. Future parity cases that exercise the AWC live window WILL surface the integer-°F shift; this release confirms the existing 5 cases remain valid baselines.
  • TS parity fixture re-export (plan 18-11c Task 2): Re-ran tests/fixtures/parity/export_for_ts.py against the new parquets; produced byte-identical TS JSONs since the source parquets didn’t shift.
  • Only artifact change: tests/fixtures/parity/expected_dtypes.json column ordering — schema-ordered now (date, station, cli_*, obs_*, fcst_*, market_close_utc) instead of alphabetical. Test reads as dict — equality assertion preserved.
  • Parity HARD GATE remains GREEN. uv run pytest tests/test_parity.py -m live -v → 5 PASSED in ~58s.
  • No behavior change. Drop-in patch for any 1.1.0 caller.
  • See .planning/phases/18-precision-fix-asos-integer-fahrenheit/18-09-PARITY-DELTA.md for per-case shasums + why-no-shift explanation.

Phase 18 release. Recovers native integer-°F precision for U.S. ASOS stations and fixes the temp_f false-precision bug surfaced in issue #16. ASOS sensors observe in integer °F; the Tgroup in METAR remarks (T########) is a downstream encoding of the whole-°F internal value, not an independent precision tier. Pre-Phase-18 code back-converted Tgroup tenths-°C → °F via celsius_to_fahrenheit(), producing artifacts like 80.06°F where the native reading was 80°F. After this release, temp_f is integer-valued for ASOS rows recovered from Tgroup; non-Tgroup (international) stations keep the legacy float path.

  • PyPI + npm: shared parse_tgroup / parseTgroup helper at _internal/tgroup.py / _internal/tgroup.ts — single source of truth for ASOS Tgroup parsing across AWC + IEM parsers.
  • PyPI + npm: AWC integer-°F recovery — when raw METAR contains a Tgroup, temp_f / dewpoint_f are emitted as the recovered integer °F (e.g. T02670122temp_f=80.0, NOT 80.06).
  • PyPI + npm: IEM Tgroup override of temp_c / dewpoint_c — when raw METAR contains a Tgroup, override the back-derived tenths with the source-truth Tgroup tenths. Critical: temp_f stays as raw_tmpf (NOT derived from temp_c); the two fields are different coded views of the same integer-°F sensor reading.
  • PyPI: parquet observation cache embeds _cache_schema_version = "v2-phase18-integer-f" in file metadata + auto-invalidate on read mismatch. Existing pre-Phase-18 user caches silently re-fetch on next call. (packages/weather/src/mostlyright/weather/cache.py)
  • PyPI: new property test (hypothesis) covering every integer °F in [-50, 140] round-trip through Tgroup tenths-°C; new 12-station AWC↔IEM cross-source consistency test.
  • PyPI: new live anti-regression test at packages/weather/tests/live/test_12_station_asos_integer_f.py covering KLGA / KJFK / KEWR / KBOS / KORD / KDFW / KLAX / KMIA / KDEN / KSEA / KATL / KPHX (gated behind -m live; run pre-publish).
  • npm: 5 new TypeScript test files (25 new tests + 12 skipped live) covering the same invariants as the Python suite — Tgroup helper unit tests, AWC integer-°F recovery, IEM Tgroup-override + consistency, exhaustive 191-value round-trip, 12-station live anti-regression.
  • npm: internationalDailyExtremes Phase 18 lattice-rationale comment documenting how callers should pick precision=1 for US-ASOS-lattice data vs precision=0 for international.
  • PyPI: schema description language updated in observation.json / daily_extreme.json / synoptic_extremes.json — “0.1°C precision for US stations” was overstated; the tenths-°C is a Tgroup-encoded display precision, not independent source resolution.
  • PyPI: _ghcnh.py carries a Phase 18 boundary-marker comment above the celsius_to_fahrenheit(temp_c) path documenting the deferral pending NCEI native-units documentation.
  • PyPI: mostlyright.international.daily_extremes precision-rationale comment documents how US-ASOS-lattice inputs interact with the precision = 1 if is_us else 0 branch.
  • PyPI: test_international.py synthetic non-lattice temperatures replaced with realistic integer-°F-lattice values ([10.0, 11.1, 12.2, 9.4] from [50, 52, 54, 49]°F); a separate dedicated HALF_UP-mechanics test preserves the rounding-semantics coverage.
  • Docs: docs/live-streaming.md alert example casts temp_f via int() (Python) / Math.round() (TS) so the rendered example never surfaces back-conversion artifacts. New “Note on temp_f precision (Phase 18)” section.
  • Parity fixtures NOT re-captured in this release. Plan 18-09 requires live network calls + operator-approval checkpoint and is deferred. The tests/test_parity.py HARD GATE still compares against the pre-Phase-18 baseline; users who rely on byte-equivalent parity vs mostlyright==0.14.1 should pin to 1.0.x until 18-09 ships.
  • TS cache schema-version field deferred. The TS cache is a generic key/value abstraction (Memory/Fs/IndexedDB) used for many caller value shapes; adding a version field across all CacheEntry consumers requires a separate refactor. TS callers that hit a pre-Phase-18 IndexedDB / FsStore cache may see stale tempF=80.06 values until they invalidate manually.
  • Existing user Python caches auto-invalidate on next research() call via the new _cache_schema_version mechanism — one round of slow first-read, no manual user action required.
  • Two-iteration two-reviewer panel per REVIEW-DISCIPLINE.md. Iter-1: Python Architect PASS; codex high + TS Architect REVISE (both independently surfaced the same CRITICAL — IEM Tgroup-override missing the tmpf-validity gate from Python _iem.py:184-188). Iter-2: all three PASS after the gate was restored and 3 regression tests added.

Hotfix release. Every live IEM MOS forecast fetch (NBE / GFS / LAV / MET / ECM) via either SDK has been broken end-to-end since the fetcher shipped — the IEM /api/1/mos.json endpoint validates the model query param against an uppercase-only regex and rejects lowercase values with HTTP 422. Both Python and TypeScript SDKs sent lowercase. This release fixes both sides in lockstep.

  • PyPI: mostlyright.weather._fetchers._iem_mos.fetch_iem_mos now sends the model query param uppercased (e.g. "NBE", not "nbe") to match IEM’s regex ^(AVN|GFS|ETA|NAM|NBS|NBE|ECM|LAV|MEX)$. Closes #17.
  • npm: @mostlyrightmd/weather iemMosForecasts(...) applies the same uppercase fix to the TS fetcher. Same root cause; same one-character fix on both sides.
  • No API changes. pip install mostlyrightmd==1.0.1 and npm install @mostlyrightmd/[email protected] are drop-in patches for any caller already on 1.0.0.
  • The regression was invisible to the unit suites because both used opaque HTTP mocks (MagicMock / vi.fn) that never inspected the actual query string. The new regression tests use httpx.MockTransport (Python) and a recording fetchFn (TS) to assert against the wire-level URL.

First stable release. Promotes the 0.1.x line to SemVer-stable: the public API is committed to backward compatibility within the 1.x major.

  • Code of conduct (Contributor Covenant 2.1) and SECURITY.md disclosure policy
  • Status badges in the root README (PyPI version, npm version, license, docs)
  • Root README rewritten as user-facing copy. Positions the SDK as the public-data SDK for quants and AI agents, with weather + prediction-markets data shipping today and SEC filings (EDGAR) + Federal Reserve economic data (FRED) on the roadmap.
  • Cross-package pin bound from >=0.1.0,<0.2 to >=1.0.0,<2.0 on all 3 PyPI distros. Lockstep contract is now anchored to the 1.x major line.
  • No source-code or API changes vs. 0.1.4. pip install mostlyrightmd==1.0.0 and pip install mostlyrightmd==0.1.4 produce identical runtime behavior.

Coordinated release on PyPI + npm: refreshes registry-visible copy and graduates the TypeScript SDK from 0.1.0-rc.* to its 0.1.0 final.

  • PyPI: mostlyrightmd, mostlyrightmd-weather, mostlyrightmd-markets bumped 0.1.3 → 0.1.4. No source-code or API changes. Per-package descriptions and READMEs cleaned for public consumption.
  • npm: @mostlyrightmd/core, @mostlyrightmd/weather, @mostlyrightmd/markets, and the unscoped mostlyright meta-package shipped 0.1.0 (first non-rc release). All four moved from the @next dist-tag to latest.
  • CHANGELOG header corrected.

Initial production releases on PyPI. Versions 0.1.0 through 0.1.3 shipped on the same day during a staggered first-publish sequence: each release iterated on trusted-publisher registration for one PyPI package at a time, with the prior versions re-publishing alongside.

  • mostlyrightmd — versions [0.1.0, 0.1.1, 0.1.2, 0.1.3]
  • mostlyrightmd-weather — versions [0.1.2, 0.1.3] (versions 0.1.0 and 0.1.1 were not published due to a publisher-registration typo at the time; the gap is intentional and harmless because the package-extra resolves transitively to the newest available version)
  • mostlyrightmd-markets — version [0.1.3] (first available version on PyPI; the 0.1.0–0.1.2 gap mirrors the weather story above)
  • mostlyright.research(station, from_date, to_date) — the canonical observation × climate join, byte-equivalent to mostlyright==0.14.1’s client.pairs() on the captured parity fixtures
  • mostlyright.weather — direct fetchers for AWC, IEM ASOS, IEM CLI, GHCNh, NWS CLI
  • mostlyright.markets.catalog.kalshi_nhigh / kalshi_nlow — Kalshi NHIGH/NLOW contract resolvers
  • mostlyright.markets.polymarket — Polymarket discovery + settlement helpers
  • mostlyright.core — schemas, validators, temporal-safety primitives (KnowledgeView, LeakageDetector), source-identity invariants (SourceMismatchError)
  • Local parquet cache at ~/.mostlyright/cache/ (Python) — deterministic, file-locked, year-aligned
  • Three-package PyPI layout: mostlyrightmd (core) + mostlyrightmd-weather + mostlyrightmd-markets
  • PyPI distribution names migrated from mostlyright* to mostlyrightmd*. The Python import path remains import mostlyright. The PyPI install name move avoids version-shadowing the legacy mostlyright==0.14.1 package.
  • npm scope migrated from @mostlyright/* to @mostlyrightmd/*. The unscoped mostlyright meta-package is unchanged.
  • Cache directory renamed from ~/.tradewinds/cache/ to ~/.mostlyright/cache/. Environment variable renamed from TRADEWINDS_CACHE_DIR to MOSTLYRIGHT_CACHE_DIR. Back-compat shim accepts both names during the 0.1.x line; the legacy name will be removed at v0.3.

Workspace + three-package layout. SDK foundations ported from the predecessor codebase (tradewinds*).