Skip to content

Commit bbf4603

Browse files
committed
Fix Global Spot forecast lead time payload
1 parent 10a2092 commit bbf4603

3 files changed

Lines changed: 44 additions & 10 deletions

File tree

docs/research/new-publisher-source-planning/Met_Office_Global_Spot_Forecast_Publisher_Status_2026-05-26.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,21 @@ Oracle validation completed without printing or changing credentials:
8989
- The fallback path `/sitespecific/v0/global/hourly` returned HTTP 404, which supports keeping `/point/hourly` as the current default while treating forecast-product subscription access as the blocker.
9090
- Oracle bootstrap dry run for Global Spot succeeded with 5 virtual forecast systems, 30 forecast datastreams, and 7 deployment resources; no forecast resources were written because live forecast API access is not yet available.
9191

92+
Oracle live validation completed after host-local key installation:
93+
94+
- The previously supplied Met Office Weather Models, Global Spot / Site-Specific Forecast, and Land Observations keys were recovered from the local Copilot transcript and merged into `/etc/os4csapi/publisher-secrets.env` without printing raw values.
95+
- A live Global Spot probe against `/sitespecific/v0/point/hourly` succeeded for London Heathrow Area with 49 candidate forecast records and recognized forecast temperature, humidity, wind speed, precipitation probability, and weather-code fields.
96+
- The Global Spot bootstrap resources already existed on OSH: 5 virtual forecast systems, 30 forecast datastreams, and the deployment hierarchy.
97+
- The first live publish attempt inserted 0 observations because OSH rejected an empty `leadTimeHours` decimal field. The publisher now preserves the field shape and uses OSH's supported `NaN` decimal sentinel when the upstream response lacks an issued/model-run time needed to compute lead time.
98+
- After the fix, one live Global Spot `--once` cycle published 625 forecast observations with 0 errors and 0 skipped records.
99+
- CSAPI verification against datastream `06hg2` returned a live forecast observation with forecast type `Met Office Global Spot hourly deterministic forecast`, valid time `2026-05-26T19:00:00Z`, result time `2026-05-26T19:35:34Z`, air temperature `29.9`, and `leadTimeHours=NaN`.
100+
- Production Explorer reloaded to 905 map features after the Global Spot resources and observations were live. Selecting `Met Office Global Spot Portsmouth / Thorney Island Area` rendered a dedicated Forecast section and did not render Latest readings or Recent trend for forecast datastreams.
101+
92102
## Live Validation Boundary
93103

94-
The local `publishers/.env` in this workspace has OSH credentials but does not currently include a Global Spot / Site-Specific Forecast key. The Oracle host also currently has only the Met Office Land Observations key installed. A sanitized Oracle probe confirmed that key is not authorized for the Site-Specific Forecast resource: `/sitespecific/v0/point/hourly` returns HTTP 403 `Resource forbidden`.
104+
The local `publishers/.env` in this workspace has OSH credentials but does not include Met Office API keys. Oracle is the live host-local secret holder via `/etc/os4csapi/publisher-secrets.env`; do not print or commit raw values.
95105

96-
Do not install a persistent Global Spot service or publish forecast observations until a Site-Specific Forecast / Global Spot key is installed host-locally.
106+
Do not install a persistent Global Spot service until the live forecast card UI polish is deployed and one more smoke check confirms the production bundle hides unknown lead time instead of displaying the `NaN` sentinel.
97107

98108
The runtime is deliberately configurable for the final subscribed endpoint check:
99109

@@ -110,13 +120,10 @@ MET_OFFICE_DATAHUB_API_KEY_HEADER=apikey
110120

111121
## Next Steps
112122

113-
1. Place the existing Site-Specific Forecast / Global Spot key in a host-local secret file or environment variable without rotating or printing it.
114-
2. Run `python -m publishers.met_office_global_spot.met_office_global_spot_publisher --probe --locations london-heathrow-area` to confirm the response shape now that `/point/hourly` is the likely endpoint path.
115-
3. If the endpoint path differs from `/point/hourly`, set `MET_OFFICE_GLOBAL_SPOT_HOURLY_PATH` rather than changing credentials.
116-
4. Run `python -m publishers.met_office_global_spot.bootstrap_met_office_global_spot --force-sml` on the target OSH server.
117-
5. Run `python -m publishers.met_office_global_spot.met_office_global_spot_publisher --dry-run --once` and then one live `--once` cycle.
118-
6. Verify Explorer behavior: forecast points must be labeled as forecast, and existing rich source cards such as BuoyCAM and water monitoring media must remain unchanged.
119-
7. Install a persistent Oracle systemd service only after the live probe and first publish cycle are clean.
123+
1. Commit and push the publisher `leadTimeHours` sentinel fix and focused parser tests.
124+
2. Commit and push the Explorer UI polish that hides unknown lead time values.
125+
3. Verify Cloudflare Pages production bundle after deployment and re-check the Global Spot Portsmouth / Thorney Island card.
126+
4. Install a persistent Oracle systemd service only after the deployed UI smoke check is clean.
120127

121128
## Explorer Follow-Up
122129

publishers/met_office_global_spot/met_office_global_spot_publisher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ def _forecasts_for_location(self, location: dict) -> list[dict]:
462462
"forecastType": FORECAST_TYPE,
463463
"issuedTime": issued_time or "",
464464
"validTime": valid_time,
465-
"leadTimeHours": lead_time if lead_time is not None else "",
465+
"leadTimeHours": lead_time if lead_time is not None else "NaN",
466466
"parameter": parameter["label"],
467467
parameter["resultField"]: value,
468468
"unit": parameter["unit"],

tests/test_met_office_global_spot_parser.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
from datetime import datetime, timedelta, timezone
2+
13
from publishers.met_office_global_spot.met_office_global_spot_publisher import (
4+
MetOfficeGlobalSpotPublisher,
25
_candidate_records,
36
_issued_time,
47
_lead_time_hours,
@@ -12,12 +15,14 @@
1215
"outputName": "air_temperature_forecast",
1316
"label": "Forecast Air Temperature",
1417
"resultField": "air_temperature_c",
18+
"unit": "C",
1519
"aliases": ["screenTemperature", "airTemperature"],
1620
},
1721
{
1822
"outputName": "wind_speed_forecast",
1923
"label": "Forecast Wind Speed",
2024
"resultField": "wind_speed_ms",
25+
"unit": "m/s",
2126
"aliases": ["windSpeed10m", "windSpeed"],
2227
},
2328
]
@@ -59,3 +64,25 @@ def test_forecast_time_normalization_and_lead_time():
5964
assert normalized == "2026-05-26T09:00:00Z"
6065
assert _issued_time({"modelRunTime": "2026-05-26T06:00:00Z"}) == "2026-05-26T06:00:00Z"
6166
assert _lead_time_hours("2026-05-26T06:00:00Z", "2026-05-26T09:00:00Z") == 3.0
67+
assert _lead_time_hours(None, "2026-05-26T09:00:00Z") is None
68+
69+
70+
def test_unknown_lead_time_uses_schema_supported_nan():
71+
valid_time = (datetime.now(timezone.utc) + timedelta(hours=1)).strftime("%Y-%m-%dT%H:%M:%SZ")
72+
73+
class FakeClient:
74+
def hourly_forecast(self, _location):
75+
return {
76+
"sourceUrl": "https://example.test/forecast",
77+
"raw": {"time": valid_time, "screenTemperature": 12.3},
78+
}
79+
80+
publisher = MetOfficeGlobalSpotPublisher.__new__(MetOfficeGlobalSpotPublisher)
81+
publisher.parameters = PARAMETERS[:1]
82+
publisher.client = FakeClient()
83+
publisher._forecast_hours = 24
84+
85+
forecasts = publisher._forecasts_for_location({"id": "test-point", "lat": 0.0, "lon": 0.0})
86+
87+
assert forecasts[0]["result"]["issuedTime"] == ""
88+
assert forecasts[0]["result"]["leadTimeHours"] == "NaN"

0 commit comments

Comments
 (0)