Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bc5759f
fix(weather): wire forecast cache + weight-aware throttle + variable …
Jun 5, 2026
9610183
feat(weather): implement NWP fields and fix GFS precip twin crash (#63)
Jun 5, 2026
7f334d5
fix: revert root pyproject.toml to upstream base deps
Jun 5, 2026
b89b724
fix: update uv.lock after pyproject.toml reversion
Jun 5, 2026
701899b
fix(pairs): discriminate OM/IEM forecasts by source, not issued_at (#67)
helloiamvu Jun 6, 2026
9ca5b48
docs(pairs): update module docstring to reflect source-based forecast…
helloiamvu Jun 6, 2026
3f78689
docs(research): clarify research() returns daily rows, not hourly obs…
helloiamvu Jun 6, 2026
5d9187b
fix(pairs): preserve OM pop/qpf after source routing (#67, codex P2)
helloiamvu Jun 6, 2026
cd458c6
fix(pairs): keep legacy source-less OM shape classified as OM (#67, c…
helloiamvu Jun 6, 2026
b26a508
fix(pairs): preserve OM issued_at provenance after source routing (#6…
helloiamvu Jun 6, 2026
12582ee
fix(pairs): exclude after-close OM runs from aggregation (#67, codex …
helloiamvu Jun 6, 2026
237c437
Merge PR #69: fix OM/IEM forecast discrimination by source (#67)
helloiamvu Jun 6, 2026
b6c98aa
Merge PR #70: clarify research() daily granularity (#52)
helloiamvu Jun 6, 2026
fbabe8c
release(v1.5.3): Open-Meteo forecast-join correctness (#67) + researc…
helloiamvu Jun 6, 2026
dabb364
Merge PR #66: Open-Meteo forecast cache wiring + weight throttle + va…
helloiamvu Jun 6, 2026
5c34d72
fix(research): cache full-month OM partitions, not request subrange (…
helloiamvu Jun 6, 2026
1fcbfdf
Merge PR #68: NWP cloud_cover_pct/visibility_m/cloud_ceiling_m + GFS …
helloiamvu Jun 6, 2026
224c055
fix(codegen): wire schema.forecast_nwp.v1 into TS codegen (#63, codex…
helloiamvu Jun 6, 2026
2e2989c
release(v1.6.0): NWP fields (#63) + OM rate-limiting (#64) + forecast…
helloiamvu Jun 6, 2026
2e0c2a4
fix(open_meteo): restamp source/retrieved_at attrs after chunk concat…
helloiamvu Jun 6, 2026
3cffc0b
fix(forecast_nwp): fail loud on genuinely-ambiguous GRIB duplicates (…
helloiamvu Jun 6, 2026
75a52f2
fix(open_meteo): Single-Runs polite delay uses fixed horizon, not win…
helloiamvu Jun 6, 2026
5e4e3c1
release(ts): bump TS packages to 1.6.0 for ForecastNwpV1 export parit…
helloiamvu Jun 6, 2026
cff13b8
fix(core): require mostlyrightmd-weather>=1.6.0 in research extra (#6…
helloiamvu Jun 6, 2026
8f6396d
fix(open_meteo): chunk all date-window endpoints, not just Previous-R…
helloiamvu Jun 6, 2026
bcad693
test(packaging): allow >=1.6.0 weather floor in research extra (#64, …
helloiamvu Jun 6, 2026
422925f
fix(pairs): coerce OM Timestamp valid_at/issued_at before window comp…
helloiamvu Jun 6, 2026
52876b0
fix(pairs): accept canonical temp_c field in OM aggregation (#67, cod…
helloiamvu Jun 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
359 changes: 359 additions & 0 deletions .briefs/cloud-cover-deep-research.md

Large diffs are not rendered by default.

277 changes: 277 additions & 0 deletions .briefs/github-issue-63-nwp-fields-review.md

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions .briefs/github-issue-pairs-source-misclassification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# `_pairs.py` source column incorrectly set for Open-Meteo rows

## How Discovered
Found by Gemini 2.5 Pro during adversarial review of PR #65 (Open-Meteo rate limiting). The review scope was cache wiring + throttling, but the reviewer traced the data flow downstream and identified a pre-existing bug in the pairs join.

## Problem

In `packages/core/src/mostlyright/_internal/_pairs.py`, `build_pairs_row()` separates IEM MOS and Open-Meteo forecast records using the **presence of `issued_at`**:

```python
iem_records = [r for r in forecasts if r.get("issued_at")]
om_records = [r for r in forecasts if not r.get("issued_at")]
```

This split is incorrect. **Phase 20 Open-Meteo Previous Runs records carry a derived `issued_at`** (cycle math: `valid_at - publish_lag`, floored to model cycle hours). Open-Meteo records with `issued_at` set get classified as IEM records.

### Impact

When both sources are requested (`forecast_source=["iem_mos", "open_meteo"]`):

1. Open-Meteo records are mixed into the IEM MOS pool
2. Run selection may pick an Open-Meteo cycle as the "best" IEM run
3. IEM-specific aggregation processes Open-Meteo rows (different column names)
4. **Data corruption:** incorrect temperature/precipitation values in output pairs

Bug is masked when only `forecast_source="open_meteo"` is used (all records end up in `iem_records` but `_select_best_run` still picks the only available run).

## Proposed Fix

Replace the `issued_at` presence check with explicit source field inspection:

```python
iem_records = [
r for r in forecasts
if not r.get("source", "").startswith("open_meteo")
]
om_records = [
r for r in forecasts
if r.get("source", "").startswith("open_meteo")
]
```

Every record carries a `source` field (`"iem_mos"` for IEM, `"open_meteo.previous_runs"` / etc. for Open-Meteo) — unambiguous.

## Secondary Issue

The fallback block uses IEM column names. OM records from `_fetch_open_meteo_range` carry `temperature_f` / `pop_6hr_pct` / `qpf_6hr_in` (converted from Celsius), but the fallback looks for `precipitation_probability_pct`. Needs column name compatibility handling.

## Test Cases Needed

1. **Mixed source classification** — both IEM MOS and OM records; verify OM records (with `issued_at`) are NOT placed in `iem_records`
2. **Column name compatibility** — OM records from research path produce correct `fcst_high`/`fcst_low`/`fcst_pop`/`fcst_qpf`
3. **Single source regression** — `iem_mos` only and `open_meteo` only still correct
59 changes: 59 additions & 0 deletions .briefs/implementation_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Implementation Plan: NWP Fields & Cloud Cover (Issue #63)

Fix the latent GFS precipitation duplicate-record crash and implement three new weather forecast columns (`cloud_cover_pct`, `visibility_m`, and `cloud_ceiling_m`) for HRRR and GFS models.

## Proposed Changes

### Core component (schema)

#### [MODIFY] [forecast_nwp.py](file:///Users/zach/.openclaw/workspace-chad/mostlyright-sdk/packages/core/src/mostlyright/core/schemas/forecast_nwp.py)
- Add columns:
- `cloud_cover_pct` (float64, %, nullable)
- `visibility_m` (float64, meters, nullable)
- `cloud_ceiling_m` (float64, meters, nullable)

### Weather component (fetchers & models)

#### [MODIFY] [forecast_nwp.py](file:///Users/zach/.openclaw/workspace-chad/mostlyright-sdk/packages/weather/src/mostlyright/weather/forecast_nwp.py)
- Implement `_pick_record(group)` helper to filter duplicate records (prioritizing instantaneous over window-aggregated and breaking ties by `record_no`).
- Update `_extract_records` to call `_pick_record` and log a warning instead of raising `GribIntegrityError` when `len(group) > 1`.
- Add short-name lookups directly to `_GRIB_VAR_TO_CFGRIB_NAME`:
- `("TCDC", "entire atmosphere"): "tcc"`
- `("VIS", "surface"): "vis"`
- `("HGT", "cloud ceiling"): "gh"`
- Register new columns in `nullable_numeric_cols` and `_empty_dataframe`.

#### [MODIFY] [gfs.py](file:///Users/zach/.openclaw/workspace-chad/mostlyright-sdk/packages/weather/src/mostlyright/weather/_fetchers/_nwp_grids/gfs.py)
- Add to `VARIABLE_MAP`:
- `"cloud_cover_pct": ("TCDC", "entire atmosphere")`
- `"visibility_m": ("VIS", "surface")`
- `"cloud_ceiling_m": ("HGT", "cloud ceiling")`

#### [MODIFY] [hrrr.py](file:///Users/zach/.openclaw/workspace-chad/mostlyright-sdk/packages/weather/src/mostlyright/weather/_fetchers/_nwp_grids/hrrr.py)
- Add to `VARIABLE_MAP`:
- `"cloud_cover_pct": ("TCDC", "entire atmosphere")`
- `"visibility_m": ("VIS", "surface")`
- `"cloud_ceiling_m": ("HGT", "cloud ceiling")`

#### [MODIFY] [rules_nwp.py](file:///Users/zach/.openclaw/workspace-chad/mostlyright-sdk/packages/weather/src/mostlyright/weather/qc/rules_nwp.py)
- Add QC rules to `RULES_NWP_NCEP`:
- `cloud_cover_pct` $\in [0, 100]$
- `visibility_m` $\ge 0$
- `cloud_ceiling_m` $\ge 0$

### Test component

#### [MODIFY] [test_forecast_nwp.py](file:///Users/zach/.openclaw/workspace-chad/mostlyright-sdk/packages/weather/tests/test_forecast_nwp.py)
- Add `TestDisambiguationHeuristics` to test `_pick_record` logic with synthetic indices.
- Add GFS live smoke test to ensure no crashes.
- Add test coverage for new fields.

## Verification Plan

### Automated Tests
- `uv run pytest -m "not live" -q`
- `uv run pytest -k "test_forecast_nwp_live" -q` (smoke live check for HRRR + GFS)
- `uv run ruff check --fix . && uv run ruff format .`

### Manual Verification
- Verify generated schemas JSON files under `schemas/json/`.
Loading
Loading