Skip to content

feat: add Japan JMA forecast capability - #75

Merged
IceCodeNew merged 1 commit into
masterfrom
codex/pr5-japan
Jul 20, 2026
Merged

feat: add Japan JMA forecast capability#75
IceCodeNew merged 1 commit into
masterfrom
codex/pr5-japan

Conversation

@IceCodeNew

@IceCodeNew IceCodeNew commented Jul 20, 2026

Copy link
Copy Markdown
Owner

What changed

Add Japan Meteorological Agency forecast support with validated office codes, response normalization, Japanese source-language metadata, and provider registry/configuration wiring.

Open-Meteo remains the primary weather provider; JMA supplies local forecast context for Japan.

Validation

  • Full test suite and coverage passed
  • Ruff, formatting, ty, secret, and workflow checks passed

Stacked on codex/pr4-singapore.

Summary by CodeRabbit

  • New Features

    • Added Japan Meteorological Agency forecast support for Japan.
    • Added optional per-location JMA forecast-area codes.
    • Added configuration for the JMA data service URL.
    • Japan defaults now use Open-Meteo with JMA supplementation when configured.
  • Documentation

    • Clarified regional provider defaults, provider ordering, and JMA setup requirements.
    • Documented fallback behavior when JMA location details are unavailable.
  • Bug Fixes

    • Improved validation and normalization of JMA forecast-area codes.
    • Prevented location-specific JMA settings from being persisted in geocoding caches.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@IceCodeNew, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45bfd85b-8af8-4c09-8acc-260656f23d32

📥 Commits

Reviewing files that changed from the base of the PR and between dbb155c and 1338493.

📒 Files selected for processing (17)
  • README.md
  • docs/design.md
  • docs/notes.md
  • env.example
  • tests/test_capabilities.py
  • tests/test_cli.py
  • tests/test_config.py
  • tests/test_geocoding.py
  • tests/test_regional_weather.py
  • weather_briefing/capabilities.py
  • weather_briefing/cli.py
  • weather_briefing/config.py
  • weather_briefing/data/provider_defaults.json
  • weather_briefing/geocoding.py
  • weather_briefing/models.py
  • weather_briefing/regional_weather.py
  • weather_briefing/registries.py
📝 Walkthrough

Walkthrough

This change adds JMA Japan forecast support, including office-code validation, location and environment configuration, regional forecast parsing, provider ordering rules, CLI construction, fallback handling, caching behavior, documentation, and test coverage.

Changes

Japan weather provider

Layer / File(s) Summary
Configuration and location contracts
weather_briefing/models.py, weather_briefing/config.py, weather_briefing/geocoding.py, weather_briefing/registries.py, weather_briefing/data/provider_defaults.json, tests/test_config.py, tests/test_geocoding.py, README.md, docs/design.md, docs/notes.md, env.example
Adds normalized six-digit jma_office_code metadata, JMA defaults and environment settings, local-provider ordering validation, cache propagation rules, and corresponding documentation and tests.
JMA regional forecast provider
weather_briefing/regional_weather.py, tests/test_regional_weather.py
Adds JMA JSON retrieval, Tokyo-time parsing, date-specific forecast extraction, Japanese output metadata, snapshot construction, and error handling.
CLI provider wiring and selection
weather_briefing/cli.py, tests/test_cli.py
Registers and builds jma-jp, passes office codes through context construction, skips unavailable explicit JMA selections, and tests primary and supplement behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant _weather_context_provider
  participant _build_jma
  participant JMAJapanForecastProvider
  participant JMAForecastAPI
  participant WeatherContextSnapshot
  _weather_context_provider->>_build_jma: pass jma_office_code
  _build_jma->>JMAJapanForecastProvider: construct with jma_base_url and office code
  JMAJapanForecastProvider->>JMAForecastAPI: request /{office_code}.json
  JMAForecastAPI-->>JMAJapanForecastProvider: forecast JSON
  JMAJapanForecastProvider->>WeatherContextSnapshot: return normalized forecast snapshot
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding Japan JMA forecast capability.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/pr5-japan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.78%. Comparing base (c51f110) to head (1338493).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff            @@
##           master      #75    +/-   ##
========================================
  Coverage   99.77%   99.78%            
========================================
  Files          45       45            
  Lines        8123     8394   +271     
  Branches      456      488    +32     
========================================
+ Hits         8105     8376   +271     
  Misses         13       13            
  Partials        5        5            

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@IceCodeNew
IceCodeNew requested a review from Copilot July 20, 2026 09:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Jul 20, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 37 rules

Grey Divider


Action required

1. JMA runs for non-JP ✓ Resolved 🐞 Bug ≡ Correctness
Description
_weather_context_provider() will include jma-jp whenever jma_office_code is present, even if
ResolvedLocation.country_code is explicitly non-JP, which can attach Japan forecasts to non-Japan
locations and violates the documented “non-JP country_code wins” boundary.
Code

weather_briefing/cli.py[R488-494]

+    main_names = [name for name in names if name not in LOCAL_WEATHER_CAPABILITY_PROVIDERS]
+    supplement_names = [
+        name
+        for name in names
+        if name in LOCAL_WEATHER_CAPABILITY_PROVIDERS
+        and (name != WeatherProviderName.JMA_JAPAN or location.jma_office_code is not None)
+    ]
Relevance

⭐⭐⭐ High

Team enforces country_code-driven regional defaults; local supplements shouldn’t override known
non-region locations (see JP/SG logic).

PR-#74
PR-#76

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code currently only checks for jma_office_code when deciding whether to include jma-jp,
while the documented boundary and default region logic both state that a known non-JP country_code
must override the office-code hint.

weather_briefing/cli.py[482-531]
docs/notes.md[7-10]
weather_briefing/config.py[230-249]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
JMA is documented as a Japan-only supplement and the notes explicitly state that if geocoding resolves a non-Japan `country_code`, that fact must take precedence and a mistakenly configured office code must not rewrite the region. However, when users explicitly include `jma-jp` in `WEATHER_PROVIDERS`, `_weather_context_provider()` currently enables JMA solely based on `jma_office_code != None`, regardless of `country_code`.

## Issue Context
- Defaults already avoid enabling JMA when `country_code` is known and not JP.
- The gap appears in the explicit-provider path and can produce misleading mixed-region context.

## Fix Focus Areas
- weather_briefing/cli.py[482-531]
- weather_briefing/config.py[230-249]
- docs/notes.md[7-10]

## What to change
- In `_weather_context_provider()`, strengthen the JMA supplement filter to require `(location.country_code in (None, 'JP'))` in addition to `location.jma_office_code is not None`.
- Consider emitting a clear warning log when `jma-jp` is configured but skipped due to `country_code` being a known non-JP value.
- Add a regression test for an explicit provider list containing `jma-jp` with `country_code='US'` (or any non-JP), ensuring JMA is not built/used.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. JP office code misapplied ✓ Resolved 🐞 Bug ≡ Correctness
Description
All JP locations default to including the JMA supplement, but JMAJapanForecastProvider.fetch()
ignores latitude/longitude and always requests the single configured office code (default
130000/Tokyo), so non-Tokyo JP locations (and multiple JP locations) will silently attach the wrong
JMA forecast lines to the briefing.
Code

weather_briefing/config.py[R214-219]

        "mainland_china"
        if location.is_mainland_china
        else location.country_code
-        if location.country_code == "SG"
+        if location.country_code in {"SG", "JP"}
        else "other"
    )
Relevance

⭐⭐⭐ High

Team often accepts config/correctness contract fixes; risk of wrong JP forecasts likely addressed.

PR-#52
PR-#29

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The repo now defaults JP locations to include jma-jp, but the JMA provider’s request URL is built
solely from a single global office code (default 130000), meaning the JMA supplement does not vary
with the location’s coordinates and can therefore be wrong for non-Tokyo JP locations.

weather_briefing/data/provider_defaults.json[10-17]
weather_briefing/config.py[209-220]
weather_briefing/config.py[447-453]
weather_briefing/regional_weather.py[75-124]
docs/design.md[55-58]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
JP locations automatically get the `jma-jp` supplement, but the JMA adapter is configured with one global `JMA_OFFICE_CODE` (default `130000`) and does not derive the office from the requested coordinates. This makes JMA output incorrect for many JP locations and cannot support multiple JP locations in different forecast office regions.

## Issue Context
- `weather_providers_for()` now treats `JP` as a region default, so every JP ResolvedLocation gets `("open-meteo", "jma-jp")` unless overridden.
- `Settings.from_env()` defaults `JMA_OFFICE_CODE` to `130000`.
- `JMAJapanForecastProvider.fetch()` uses only `office_code` to build the request URL and does not use `latitude/longitude`.
- The design doc explicitly states `130000` is only valid for Tokyo and office code must match the location’s forecast region.

## Fix Focus Areas
- weather_briefing/config.py[209-220]
- weather_briefing/config.py[447-453]
- weather_briefing/data/provider_defaults.json[10-17]
- weather_briefing/regional_weather.py[75-124]
- docs/design.md[55-58]

## What to change
Implement one of the following (in order of robustness):
1) **Per-location configuration**: add an optional per-location JMA office code (e.g., `locations.json` field) and pass it through to `_build_weather_provider(..., location=...)` so each JP location can fetch the correct office.
2) **Coordinate-based resolution**: map coordinates/administrative area to a JMA office code using maintained reference data, then request that office.
3) **Fail-safe default behavior** (minimum): do not auto-append `jma-jp` for JP unless the office code is explicitly configured for the location; alternatively remove the global default `130000` so misconfiguration is surfaced rather than silently using Tokyo.

Also ensure the resulting behavior works with multiple configured JP locations.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Noisy dated supplement warnings 🐞 Bug ◔ Observability ⭐ New
Description
When CapabilityProviderSet.fetch_all() is called with a forecast_date, it now invokes supplements
with that date; supplements that don’t support dated fetches raise WeatherContextError and are
skipped, but LoggedWeatherContextProvider logs them as WARNING failures. This produces misleading
warning noise during otherwise successful dated forecasts (e.g., Singapore with nea-sg configured).
Code

weather_briefing/capabilities.py[R109-116]

        snapshots = [await self.fetch(latitude, longitude, forecast_date=forecast_date)]
-        if forecast_date is not None:
-            return tuple(snapshots)
        from .weather_context import WeatherContextError

        for provider in self.supplements:
            try:
-                snapshots.append(await _fetch_context(provider, latitude, longitude, None))
+                snapshots.append(await _fetch_context(provider, latitude, longitude, forecast_date))
            except (WeatherContextError, ValueError):
                continue
Relevance

⭐⭐ Medium

No prior decision on warning noise for ignored WeatherContextError; repo values log
hygiene/redaction (PR #49).

PR-#49

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR change makes fetch_all() always call supplements with the provided forecast_date. For
supplements that aren’t dated-capable, fetch_weather_context() throws WeatherContextError, which
LoggedWeatherContextProvider logs at WARNING before re-raising; fetch_all() then catches and
ignores it, so the run succeeds but warnings are emitted.

weather_briefing/capabilities.py[101-117]
weather_briefing/weather_context.py[162-205]
weather_briefing/weather_context.py[778-792]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Dated forecasts now attempt to call supplement providers with `forecast_date`. For non-dated supplements, `fetch_weather_context()` raises `WeatherContextError` (unsupported target date). Even though `CapabilityProviderSet.fetch_all()` catches and ignores this error, `LoggedWeatherContextProvider` logs it at WARNING first, creating misleading “failed” warnings for an expected/benign skip.

### Issue Context
This is introduced by changing `CapabilityProviderSet.fetch_all()` to pass `forecast_date` through to supplements (needed for dated-capable supplements like JMA). The fix should preserve dated-capable supplement execution while avoiding WARNING logs for the expected “does not support target forecast dates” path.

### Fix Focus Areas
- weather_briefing/capabilities.py[101-117]
- weather_briefing/weather_context.py[162-215]
- weather_briefing/weather_context.py[778-792]

### Suggested implementation direction
- In `LoggedWeatherContextProvider.fetch(...)`, before logging “call started”, detect `forecast_date is not None` AND underlying provider is not a `DatedWeatherContextProvider`.
 - Either: raise `WeatherContextError` without emitting a WARNING (optionally log at DEBUG/INFO as a skip), or
 - Downgrade logging level specifically for this expected unsupported-date error.
This keeps real provider failures visible while removing expected warning noise on dated forecasts with non-dated supplements (e.g., NEA).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Dated JMA supplement skipped ✓ Resolved 🐞 Bug ≡ Correctness
Description
When JP defaults append jma-jp as a supplement, run forecast --date won’t fetch any supplements
because CapabilityProviderSet.fetch_all() returns immediately for non-None forecast_date, so JMA
context is omitted from dated forecasts unless jma-jp is made the primary provider.
Code

weather_briefing/config.py[R235-239]

+    region = _weather_region(location)
+    providers = reference_string_tuple("provider_defaults.json", "weather_provider_order", region)
+    if region == "JP" and location.jma_office_code is None:
+        return tuple(provider for provider in providers if provider != WeatherProviderName.JMA_JAPAN)
+    return providers
Relevance

⭐⭐⭐ High

Similar bug fixed: PR #76 removed forecast_date early-return by routing dated fetch via shared
boundary.

PR-#76
PR-#74
PR-#50

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
JP defaults include jma-jp after open-meteo when jma_office_code is present, but dated runs
flow through CapabilityProviderSet.fetch_all(forecast_date=...), which returns before iterating
supplements, so jma-jp is never invoked even though it supports fetch_for_date().

weather_briefing/config.py[230-239]
weather_briefing/data/provider_defaults.json[10-17]
weather_briefing/service.py[332-349]
weather_briefing/capabilities.py[101-119]
weather_briefing/regional_weather.py[106-114]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
For dated forecast runs (`forecast_date != None`), `CapabilityProviderSet.fetch_all()` currently skips all supplements. With the new JP default provider order (`open-meteo`, then `jma-jp`), this means JMA is never called in `run forecast --date`, even though `JMAJapanForecastProvider` implements `fetch_for_date()`.

### Issue Context
- Default JP order includes `jma-jp` as a supplement when `jma_office_code` is configured.
- `BriefingService` passes the CLI `--date` value down as `forecast_date`.
- `CapabilityProviderSet.fetch_all()` short-circuits for dated calls, so supplements cannot contribute.

### Fix Focus Areas
- weather_briefing/capabilities.py[101-119]
- weather_briefing/service.py[332-349]
- weather_briefing/regional_weather.py[106-114]
- weather_briefing/config.py[230-239]

### Recommended fix
1. Update `CapabilityProviderSet.fetch_all()` to still *attempt* fetching supplements when `forecast_date` is provided.
  - For each supplement, call `_fetch_context(provider, latitude, longitude, forecast_date)`.
  - Continue swallowing expected failures (e.g., `WeatherContextError` / `ValueError`) so supplements that don’t support dated forecasts (like NEA) are naturally skipped.
2. Add/adjust tests in `tests/test_capabilities.py` to cover:
  - A dated-capable supplement (e.g., JMA) is included in `fetch_all(..., forecast_date=...)`.
  - A non-dated supplement is skipped (still best-effort) without failing the whole fetch.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Silent JMA skip ✓ Resolved 🐞 Bug ◔ Observability
Description
When WEATHER_PROVIDERS explicitly includes "jma-jp" but the location lacks jma_office_code (or has a
known non-JP country_code), _weather_context_provider() drops JMA from supplements without emitting
any warning, so the effective runtime provider set no longer matches the configured provider list.
This makes it easy to ship with JMA unintentionally inactive while the run still succeeds via
Open‑Meteo.
Code

weather_briefing/cli.py[R488-497]

+    main_names = [name for name in names if name not in LOCAL_WEATHER_CAPABILITY_PROVIDERS]
+    supplement_names = [
+        name
+        for name in names
+        if name in LOCAL_WEATHER_CAPABILITY_PROVIDERS
+        and (
+            name != WeatherProviderName.JMA_JAPAN
+            or (location.jma_office_code is not None and location.country_code in {None, "JP"})
+        )
+    ]
Relevance

⭐⭐⭐ High

Team previously added logs to avoid silent provider fallback; likely to want warning when configured
provider skipped (PR19).

PR-#19

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The CLI constructs names from weather_providers_for(...) (which returns explicit configuration
unchanged) and then filters supplement_names to include jma-jp only when office-code/country
preconditions hold; when they don’t, JMA is omitted and the provider-order log line is built from
the filtered lists, so no log indicates an explicitly requested provider was skipped. Tests assert
this skip behavior for an explicit ("open-meteo","jma-jp") configuration.

weather_briefing/cli.py[482-543]
weather_briefing/config.py[230-239]
tests/test_cli.py[1168-1202]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`_weather_context_provider()` filters `supplement_names` and can silently omit `jma-jp` even when it was explicitly requested via `WEATHER_PROVIDERS`. This creates a mismatch between configured vs active providers with no operator-visible signal.

## Issue Context
This only applies to mixed configurations where at least one primary provider remains (e.g., `open-meteo,jma-jp`). The current behavior is covered by tests that assert JMA is skipped.

## Fix Focus Areas
- weather_briefing/cli.py[482-543]

## Suggested fix
1. Detect when `WeatherProviderName.JMA_JAPAN` is present in `names` but not present in the computed `supplement_names`.
2. If `settings.weather_providers is not None` (explicit ordering) and JMA was skipped, emit a `_LOGGER.warning(...)` explaining the reason (missing `jma_office_code` and/or non-JP `country_code`).
3. Keep the existing skip behavior (do not raise), just add a clear warning so misconfiguration is observable.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (4)
6. JMA lacks dated fetch ✓ Resolved 🐞 Bug ≡ Correctness
Description
JMAJapanForecastProvider only implements fetch() but the run forecast --date path requires a
DatedWeatherContextProvider, so configuring jma-jp as the primary provider will raise
WeatherContextError when a target date is requested.
Code

weather_briefing/regional_weather.py[R81-133]

+class JMAJapanForecastProvider:
+    """Fetch Japan Meteorological Agency prefecture forecast JSON."""
+
+    language_support = JMA_LANGUAGE_SUPPORT
+    output_language = language_support.default
+
+    def __init__(
+        self,
+        client: httpx.AsyncClient,
+        *,
+        base_url: str = "https://www.jma.go.jp/bosai/forecast/data/forecast",
+        office_code: str | None,
+    ) -> None:
+        """Configure JMA forecast data for one forecast office."""
+        normalized_office_code = normalize_jma_office_code(office_code)
+        if normalized_office_code is None:
+            raise ValueError("JMA office code must contain six digits")
+        self._client = client
+        self._base_url = base_url.rstrip("/")
+        self._office_code = normalized_office_code
+
+    async def fetch(self, latitude: float, longitude: float) -> WeatherContextSnapshot:
+        """Fetch and normalize JMA's daily and time-series forecast."""
+        try:
+            response = await self._client.get(
+                f"{self._base_url}/{self._office_code}.json",
+                extensions=api_call_extensions("jma-jp", "forecast"),
+            )
+            response.raise_for_status()
+            payload = response.json()
+            if not isinstance(payload, list) or not payload or not isinstance(payload[0], dict):
+                raise RegionalWeatherProviderError("JMA forecast must be a non-empty array")
+            report = payload[0]
+            time_series = report.get("timeSeries")
+            if not isinstance(time_series, list) or not time_series:
+                raise RegionalWeatherProviderError("JMA forecast contains no time series")
+            forecast_lines = _jma_forecast_lines(time_series)
+            if not forecast_lines:
+                raise RegionalWeatherProviderError("JMA forecast contains no usable entries")
+            observed_at = _parse_japan_time(report.get("reportDatetime"))
+        except RegionalWeatherProviderError:
+            raise
+        except (httpx.HTTPError, KeyError, TypeError, ValueError) as exc:
+            raise RegionalWeatherProviderError(f"JMA forecast failed: {_safe_regional_error(exc)}") from None
+        return WeatherContextSnapshot(
+            source_id="weather:jma-jp",
+            source_name="Japan Meteorological Agency",
+            source_url="https://www.jma.go.jp/bosai/forecast/",
+            observed_at=observed_at,
+            weather_forecast=forecast_lines,
+            output_language=self.output_language,
+        )
+
Relevance

⭐⭐⭐ High

Repo recently tightened target-date provider contract; non-dated providers should fail/guard in
forecast --date paths.

PR-#76
PR-#50
PR-#71

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The orchestration layer rejects any provider without the dated-provider interface when
forecast_date is set, but the new JMA provider only defines fetch(). Tests also confirm jma-jp
can be selected as the primary provider, making the failure reachable.

weather_briefing/weather_context.py[778-792]
weather_briefing/regional_weather.py[81-133]
tests/test_cli.py[1330-1347]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`run forecast --date` calls `fetch_weather_context(..., forecast_date!=None)`, which requires the selected primary weather provider to implement the dated-provider protocol. `JMAJapanForecastProvider` currently only implements `fetch()`, so when `jma-jp` is configured as the primary provider, `--date` fails with `WeatherContextError`.

## Issue Context
- The CLI/tests explicitly allow `jma-jp` to be used as the primary provider.
- The orchestration layer requires a `fetch_for_date()` implementation for date-specific forecasts.

## Fix Focus Areas
- weather_briefing/regional_weather.py[81-133]
- weather_briefing/weather_context.py[778-792]
- tests/test_cli.py[1330-1347]

## What to change
- Add `fetch_for_date(self, latitude, longitude, forecast_date)` to `JMAJapanForecastProvider` so it satisfies `DatedWeatherContextProvider`.
- Either:
 - Properly select forecast entries for `forecast_date` from JMA payload (preferred if the API provides per-day/time definitions), or
 - If true per-date selection is not feasible yet, fail explicitly inside `fetch_for_date()` with a clear, provider-specific error message explaining JMA does not support target dates (instead of failing the protocol check), and consider preventing `jma-jp` from being the sole primary provider when `--date` is requested.
- Add/adjust a regression test covering `--date` behavior when `jma-jp` is primary.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Leaked AsyncClient in test ✓ Resolved 🐞 Bug ☼ Reliability
Description
tests/test_regional_weather.py constructs an httpx.AsyncClient() in a synchronous test and never
closes it because the provider constructor raises first, which can emit unclosed-client warnings.
Close the client explicitly (or make the test async and use an async context manager) to avoid
resource leaks during the test run.
Code

tests/test_regional_weather.py[R101-104]

+def test_jma_provider_rejects_invalid_office_code() -> None:
+    with pytest.raises(ValueError, match="six digits"):
+        JMAJapanForecastProvider(httpx.AsyncClient(), office_code="13000")
+
Relevance

⭐⭐⭐ High

Tests emphasize correct client lifecycle; PR #15 explicitly fixed/avoided client lifecycle issues
and ResourceWarnings.

PR-#15

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The test currently constructs an AsyncClient without a context manager, unlike the other tests in
the same file that correctly close AsyncClient via async with ... as client:.

tests/test_regional_weather.py[37-38]
tests/test_regional_weather.py[101-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`test_jma_provider_rejects_invalid_office_code()` creates an `httpx.AsyncClient()` but never closes it because `JMAJapanForecastProvider(...)` raises during initialization. This can leave an unclosed client and produce warnings during tests.

## Issue Context
Other tests in the same module use `async with httpx.AsyncClient(...) as client:` to ensure proper cleanup.

## Fix Focus Areas
- tests/test_regional_weather.py[101-104]

## Suggested fix
Option A (minimal change, keep sync test):
- Create the client in a variable and ensure it is closed in a `finally` block by running `await client.aclose()` (which requires making the test async) or by using `anyio.run(client.aclose)`.

Option B (cleanest):
- Change the test to `async def ...` and wrap the client in `async with httpx.AsyncClient() as client:` before calling the constructor (even though it raises).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. JMA skipped with coords ✓ Resolved 🐞 Bug ≡ Correctness
Description
A location with a valid jma_office_code will not get JP defaults (and thus won’t auto-append
jma-jp) if country_code is None, because weather_providers_for() only selects the JP
region when location.country_code == 'JP'. The "name + coordinates" fast-path intentionally skips
geocoding and sets country_code=None, so jma_office_code alone currently does not enable JMA as
documented.
Code

weather_briefing/config.py[R214-223]

        "mainland_china"
        if location.is_mainland_china
        else location.country_code
-        if location.country_code == "SG"
+        if location.country_code in {"SG", "JP"}
        else "other"
    )
-    return reference_string_tuple("provider_defaults.json", "weather_provider_order", region)
+    providers = reference_string_tuple("provider_defaults.json", "weather_provider_order", region)
+    if location.country_code == "JP" and location.jma_office_code is None:
+        return tuple(provider for provider in providers if provider != WeatherProviderName.JMA_JAPAN)
+    return providers
Relevance

⭐⭐ Medium

No historical evidence for JP inference from jma_office_code when country_code=None; coords
fast-path (PR29) keeps country_code unset.

PR-#29

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code path that skips geocoding when name+coords are provided sets country_code=None, and the
default provider selection only checks country_code for JP; therefore, even with jma_office_code
present, the location is classified as other and won’t receive the JP default provider order that
includes jma-jp.

README.md[18-18]
README.md[44-44]
weather_briefing/geocoding.py[364-383]
weather_briefing/config.py[209-223]
weather_briefing/data/provider_defaults.json[2-17]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`jma_office_code` is intended to enable JMA for Japan locations, but the default provider selection logic only considers `country_code` when choosing the JP provider order. Locations that bypass geocoding (name+coords) end up with `country_code=None`, so JMA defaults are not applied even if `jma_office_code` is present.

## Issue Context
- README states that providing both name and coordinates skips geocoding requests.
- That path constructs `ResolvedLocation(..., country_code=None, ...)`.
- `weather_providers_for()` chooses region `JP` only when `location.country_code in {'SG','JP'}`.

## Fix Focus Areas
- weather_briefing/config.py[209-223]
- weather_briefing/geocoding.py[364-384]
- weather_briefing/data/provider_defaults.json[2-17]
- README.md[18-18]
- README.md[44-44]

### Implementation direction
- In `weather_providers_for()`, treat `location.jma_office_code is not None` as sufficient to classify the location into the `JP` defaults (or append `jma-jp` to the default list) when `configured is None`.
 - Example: if `location.jma_office_code` is set and `location.country_code` is `None`, set `region = 'JP'` (or post-process the provider list to include `jma-jp`).
- Keep the existing safeguard that *removes* `jma-jp` when office code is missing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Missing JMA decision note ✓ Resolved 📘 Rule violation ⌂ Architecture
Description
The PR introduces the JMAJapanForecastProvider and JP default provider behavior, which is a
non-obvious architectural choice (local supplement provider + office-code boundary) but there is no
corresponding rationale/trade-offs/boundaries entry in docs/notes.md. This violates the
requirement to document such decisions for maintainability and future reevaluation.
Code

weather_briefing/regional_weather.py[R75-124]

+class JMAJapanForecastProvider:
+    """Fetch Japan Meteorological Agency prefecture forecast JSON."""
+
+    output_language = "ja"
+
+    def __init__(
+        self,
+        client: httpx.AsyncClient,
+        *,
+        base_url: str = "https://www.jma.go.jp/bosai/forecast/data/forecast",
+        office_code: str = "130000",
+    ) -> None:
+        """Configure JMA forecast data for one forecast office."""
+        if not office_code.isdigit():
+            raise ValueError("JMA office code must contain digits")
+        self._client = client
+        self._base_url = base_url.rstrip("/")
+        self._office_code = office_code
+
+    async def fetch(self, latitude: float, longitude: float) -> WeatherContextSnapshot:
+        """Fetch and normalize JMA's daily and time-series forecast."""
+        try:
+            response = await self._client.get(
+                f"{self._base_url}/{self._office_code}.json",
+                extensions=api_call_extensions("jma-jp", "forecast"),
+            )
+            response.raise_for_status()
+            payload = response.json()
+            if not isinstance(payload, list) or not payload or not isinstance(payload[0], dict):
+                raise RegionalWeatherProviderError("JMA forecast must be a non-empty array")
+            report = payload[0]
+            time_series = report.get("timeSeries")
+            if not isinstance(time_series, list) or not time_series:
+                raise RegionalWeatherProviderError("JMA forecast contains no time series")
+            forecast_lines = _jma_forecast_lines(time_series)
+            if not forecast_lines:
+                raise RegionalWeatherProviderError("JMA forecast contains no usable entries")
+            observed_at = _parse_japan_time(report.get("reportDatetime"))
+        except RegionalWeatherProviderError:
+            raise
+        except (httpx.HTTPError, KeyError, TypeError, ValueError) as exc:
+            raise RegionalWeatherProviderError(f"JMA forecast failed: {type(exc).__name__}") from None
+        return WeatherContextSnapshot(
+            source_id="weather:jma-jp",
+            source_name="Japan Meteorological Agency",
+            source_url="https://www.jma.go.jp/bosai/forecast/",
+            observed_at=observed_at,
+            weather_forecast=tuple(forecast_lines),
+            output_language=self.output_language,
+        )
Relevance

⭐⭐ Medium

Notes.md is curated, but no clear precedent enforcing notes entry for every new provider decision.

PR-#62
PR-#54

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
docs/notes.md explicitly states it is where key architectural choices’
rationale/trade-offs/boundaries should be recorded, but it contains no entry corresponding to the
newly added JMA provider decision. The PR adds a new provider implementation and documents new
JP/JMA behavior in docs/design.md, making the missing docs/notes.md entry a compliance
violation.

Rule 2141673: Document non-obvious architectural decisions in docs/notes.md
weather_briefing/regional_weather.py[75-124]
docs/design.md[55-58]
docs/notes.md[1-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
This PR adds Japan JMA forecast as a local supplement provider (`jma-jp`) with configuration constraints (notably `JMA_OFFICE_CODE` and its default), which is a non-obvious architectural decision. Per compliance, the rationale, trade-offs, and operating boundaries/assumptions for this decision must be documented in `docs/notes.md`, but no matching entry exists.

## Issue Context
- The design doc describes the new behavior, but `docs/notes.md` is the required place for the underlying rationale/trade-offs/boundaries.
- Include at minimum:
 - **Decision**: JMA as a supplement (not replacing Open-Meteo), JP default provider order, and the office-code configuration model.
 - **Rationale**: why JMA supplement is used and why office-code is configured per location/region.
 - **Trade-offs**: e.g., added config complexity vs better local context; failure isolation behavior.
 - **Operating boundaries/assumptions**: e.g., default `130000` only fits Tokyo; when/why users must override; expected failure modes.

## Fix Focus Areas
- docs/notes.md[1-4]
- weather_briefing/regional_weather.py[75-124]
- docs/design.md[55-58]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

10. Real GPS coordinates in tests 📘 Rule violation ⛨ Security ⭐ New
Description
Multiple modified tests embed realistic latitude/longitude pairs (e.g., Tokyo and Osaka). This can
be mistaken for private user location data and violates the requirement to keep source/tests free of
hard-coded private data-like values.
Code

tests/test_geocoding.py[506]

+    outside = await resolver.resolve(LocationSpec("outside", "Tokyo", 35.7, 139.7, jma_office_code="130000"))
Relevance

⭐ Low

PR #29 privacy review allows public sample coordinates in committed tests/examples; pattern already
accepted.

PR-#29

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2141693 prohibits hard-coded values that could represent private user location
data. The changed tests include realistic GPS coordinates (e.g., 35.7, 139.7 and 34.7, 135.5)
which are not clearly dummy/sanitized.

Rule 2141693: Do not hard-code private user data in source code or tests
tests/test_geocoding.py[505-507]
tests/test_config.py[148-169]
tests/test_config.py[184-194]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Tests include realistic GPS coordinates (e.g., Tokyo/Osaka). Compliance requires avoiding hard-coded values that could be interpreted as private user location data.

## Issue Context
These coordinates are not required for the semantics under test (e.g., “outside mainland China bounds”, JMA office-code validation). Replace with clearly dummy/sanitized coordinates (e.g., `(0.0, 0.0)` or `(1.0, 1.0)`) and keep the tests’ intent unchanged.

## Fix Focus Areas
- tests/test_geocoding.py[505-513]
- tests/test_config.py[150-169]
- tests/test_config.py[184-195]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Misleading JMA env comment ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
env.example states “Set the local forecast office code” next to JMA_BASE_URL, but the implementation
requires the office code to be configured per location in locations.json as jma_office_code. This
mismatch can mislead operators into looking for (or inventing) a non-existent environment variable
for the office code.
Code

env.example[R60-61]

+# Japan Meteorological Agency forecast data. Set the local forecast office code.
+JMA_BASE_URL=https://www.jma.go.jp/bosai/forecast/data/forecast
Relevance

⭐⭐⭐ High

Team updates env.example for accuracy/ops clarity (env.example fixes merged in PRs #9, #47).

PR-#9
PR-#47

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The env file comment references setting an office code near JMA_BASE_URL, while the README
documents the office code as a locations.json field (jma_office_code) used to enable JMA per
location.

env.example[57-62]
README.md[41-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`env.example` implies the JMA forecast office code is configured alongside `JMA_BASE_URL`, but the codebase actually expects a per-location `jma_office_code` field in `locations.json`.

## Issue Context
README already documents `jma_office_code` as a `locations.json` field for Japan locations.

## Fix Focus Areas
- env.example[57-61]

## Suggested fix
Update the comment to explicitly say the office code is set in `locations.json` (e.g., "Office code is configured per location via locations.json:jma_office_code"), or remove the office-code phrase from `env.example` to avoid confusion.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. JMA missing code aborts ✓ Resolved 🐞 Bug ☼ Reliability
Description
When WEATHER_PROVIDERS explicitly includes jma-jp but a location has no jma_office_code,
_build_weather_provider() raises ValueError during provider composition, preventing the run from
proceeding (even though supplements are intended to be best-effort). This is because explicit
provider lists bypass the JP default filtering and still attempt to build JMA without a code.
Code

weather_briefing/cli.py[R604-609]

+    if name == WeatherProviderName.JMA_JAPAN:
+        return JMAJapanForecastProvider(
+            client,
+            base_url=settings.jma_base_url,
+            office_code=jma_office_code or _missing_jma_office_code(),
+        )
Relevance

⭐ Low

Team prefers fail-fast config errors; PR52/71 validate provider contracts and raise ValueError on
missing required config.

PR-#52
PR-#71

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The CLI builder path unconditionally raises when building JMA without an office code, while
configuration explicitly returns the configured provider list without removing jma-jp when
jma_office_code is absent, contradicting the documented “missing office code falls back to
Open-Meteo” behavior.

weather_briefing/cli.py[585-609]
weather_briefing/cli.py[651-653]
weather_briefing/config.py[209-223]
README.md[44-44]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Explicitly configured provider order can include `jma-jp` for locations that do not set `locations.json:jma_office_code`. Today this fails during provider construction (`ValueError`), so the briefing/run aborts before the supplement “expected failure” boundary can apply.

## Issue Context
- `_build_weather_provider()` currently requires a non-None `jma_office_code` and calls `_missing_jma_office_code()` when absent.
- `weather_providers_for()` returns `configured` unchanged, so an explicit `WEATHER_PROVIDERS=open-meteo,jma-jp` will still attempt to build JMA for every location.

## Fix Focus Areas
- weather_briefing/cli.py[482-555]
- weather_briefing/cli.py[585-610]
- weather_briefing/config.py[209-223]

### Implementation direction (one acceptable approach)
- Treat missing `jma_office_code` as “supplement unavailable” instead of a composition-time hard error.
 - Option A (preferred): in `_weather_context_provider()`, filter `supplement_names` to drop `jma-jp` when `location.jma_office_code is None` (regardless of explicit vs default provider ordering), so JMA simply isn’t constructed.
 - Option B: return a lightweight provider object for JMA that raises `ValueError` *on fetch* (not construction) when office code is missing; this will then be skipped by `CapabilityProviderSet.fetch_all()` for supplements.
- If you want strictness when `jma-jp` is the *only* configured provider, keep the hard failure in that specific “primary provider” scenario (but avoid failing when it’s a supplement behind Open-Meteo).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread weather_briefing/regional_weather.py
Comment thread weather_briefing/config.py Outdated
@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

Comment thread tests/test_regional_weather.py Outdated
Comment thread env.example Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6e53a71

@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

Comment thread weather_briefing/config.py Outdated
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 165a42b

@IceCodeNew
IceCodeNew force-pushed the codex/pr4-singapore branch 4 times, most recently from 667144d to 32194ec Compare July 20, 2026 15:06
Base automatically changed from codex/pr4-singapore to master July 20, 2026 15:12
@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 711823b

@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

Comment thread weather_briefing/regional_weather.py
Comment thread weather_briefing/cli.py
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 02c0d2d

@IceCodeNew

Copy link
Copy Markdown
Owner Author

/agentic_review

Comment thread weather_briefing/cli.py
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit fa77c12

@IceCodeNew
IceCodeNew marked this pull request as ready for review July 20, 2026 16:20
@qodo-code-review

qodo-code-review Bot commented Jul 20, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Add JMA Japan forecast provider with per-location office codes

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🐞 Bug fix 🕐 40+ Minutes

Grey Divider

AI Description

• Add JMA Japan forecast provider with per-location six-digit office codes.
• Wire JMA into provider defaults, ordering validation, and CLI provider registry.
• Fix dated supplement fetching; extend geocoding behavior and add tests/docs.
Diagram

graph TD
  L["locations.json (jma_office_code)"] --> S["Settings/config"] --> P["CLI provider wiring"] --> CPS["CapabilityProviderSet"]
  CPS --> OM["Open-Meteo"]
  CPS --> JMA["JMA Japan forecast"]
  CPS --> NEA["NEA nowcast"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Auto-derive office code from coordinates
  • ➕ Removes per-location manual configuration burden
  • ➕ Enables JMA defaults for Japan without extra fields
  • ➖ Requires a curated geo/administrative mapping dataset or a reliable lookup API
  • ➖ Higher risk of silently selecting wrong forecast office; difficult to validate correctness
2. Persist JMA office code in geocoding cache
  • ➕ Less duplication between location config and resolved/cached location records
  • ➕ Could reduce repeated configuration churn for stable locations
  • ➖ Mixes user-configurable runtime behavior into a cache intended for geographic facts
  • ➖ Makes it harder to change office code without invalidating cache; risk of stale/wrong office mapping
3. Always enable JMA for JP using a single default office
  • ➕ Zero-config experience for Japan users
  • ➖ Incorrect for most locations outside the default office (e.g., Tokyo)
  • ➖ Violates the project’s explicitness/safety posture; harder to detect misconfiguration

Recommendation: Keep the PR’s explicit per-location jma_office_code gating as implemented. It prioritizes correctness (no silent Tokyo-default leakage), preserves the existing “Open-Meteo primary + local supplements” model, and keeps caches free of per-run/per-location configuration. If adoption friction becomes an issue later, consider adding an optional curated lookup table as a separate, explicitly-reviewed reference dataset.

Files changed (17) +636 / -48

Enhancement (5) +275 / -26
cli.pyWire JMA provider into CLI building and capability metadata +57/-7

Wire JMA provider into CLI building and capability metadata

• Adds JMA provider capabilities metadata (fixed Japanese language support) and integrates it into the weather provider builder. Implements JMA availability gating based on 'jma_office_code' and country code, logging a warning when an explicitly configured JMA supplement is skipped. Propagates 'jma_office_code' into provider construction for both primary and supplements.

weather_briefing/cli.py

config.pyAdd JMA settings, provider defaults, and local-provider ordering validation +46/-13

Add JMA settings, provider defaults, and local-provider ordering validation

• Adds 'jma_base_url' to settings/env loading and parses optional 'jma_office_code' from 'locations.json' with strict six-digit ASCII validation. Generalizes provider ordering validation to ensure all local capability providers appear after all primary providers. Updates provider default selection so JP only includes 'jma-jp' when a JMA office code is configured (or when country is unknown but office code exists).

weather_briefing/config.py

geocoding.pyPropagate jma_office_code through resolution while keeping it out of cache +18/-3

Propagate jma_office_code through resolution while keeping it out of cache

• Ensures resolved locations carry 'jma_office_code' from the current LocationSpec across both cached and fresh geocoding paths. Explicitly removes 'jma_office_code' from geocoding cache records, keeping caches limited to geographic facts.

weather_briefing/geocoding.py

models.pyAdd JMA office-code normalization and enforce invariants in location models +18/-2

Add JMA office-code normalization and enforce invariants in location models

• Introduces 'normalize_jma_office_code()' enforcing six-digit ASCII digits. Adds 'jma_office_code' to 'LocationSpec' and 'ResolvedLocation', normalized in '__post_init__' alongside language normalization.

weather_briefing/models.py

regional_weather.pyImplement JMA Japan forecast provider and response normalization helpers +136/-1

Implement JMA Japan forecast provider and response normalization helpers

• Adds 'JMAJapanForecastProvider' which fetches forecast JSON by office code, normalizes output into 'WeatherContextSnapshot', and reports failures via 'RegionalWeatherProviderError'. Introduces Japan time parsing and forecast line extraction that supports optional date targeting via 'timeDefines'.

weather_briefing/regional_weather.py

Bug fix (1) +1 / -3
capabilities.pyAllow supplements to fetch for a requested forecast date +1/-3

Allow supplements to fetch for a requested forecast date

• Removes the early return that previously prevented supplements from running when 'forecast_date' is provided. Passes 'forecast_date' into supplement fetches so dated-capable supplements can contribute context.

weather_briefing/capabilities.py

Tests (5) +339 / -15
test_capabilities.pyEnsure dated supplements can participate in forecast-date fetches +26/-1

Ensure dated supplements can participate in forecast-date fetches

• Adds coverage that a supplement implementing 'fetch_for_date()' is included when 'forecast_date' is specified. Keeps coverage for skipping failed or non-dated supplements.

tests/test_capabilities.py

test_cli.pyTest JMA wiring, gating, and registry builder behavior +89/-0

Test JMA wiring, gating, and registry builder behavior

• Adds tests covering explicit JMA provider skipping when office code is missing or country is known non-JP, and rejection when JMA is the only provider without an office code. Verifies the JMA builder path and that JMA can be used as a primary provider with Japanese output metadata.

tests/test_cli.py

test_config.pyTest provider ordering rules and jma_office_code loading/validation +75/-6

Test provider ordering rules and jma_office_code loading/validation

• Generalizes the “local supplements last” constraint to include both NEA and JMA, and allows multiple local supplements after primaries. Adds provider defaults tests for JP with/without office code (including the country-code-unknown case), plus parsing and invariant validation for 'jma_office_code' from 'locations.json'.

tests/test_config.py

test_geocoding.pyVerify jma_office_code is not cached and is overridden by runtime config +22/-7

Verify jma_office_code is not cached and is overridden by runtime config

• Extends cached resolution tests to ensure 'jma_office_code' behaves like 'summary_language': applied from the current LocationSpec, not persisted in geocoding cache. Adds assertions that cache JSON does not contain 'jma_office_code'.

tests/test_geocoding.py

test_regional_weather.pyAdd comprehensive tests for JMA provider parsing and error handling +127/-1

Add comprehensive tests for JMA provider parsing and error handling

• Covers JMA office-code validation, response shape validation, transport error wrapping, timestamp fallback, forecast-line normalization, multi-area preservation, and date-target selection via 'timeDefines'. Validates Japanese output language and normalized snapshot fields.

tests/test_regional_weather.py

Documentation (3) +6 / -4
README.mdDocument Japan default provider behavior and optional JMA office code +2/-2

Document Japan default provider behavior and optional JMA office code

• Updates regional provider defaults to include Japan’s JMA supplement behavior and clarifies ordering rules for local capability providers. Documents 'locations.json:jma_office_code' as the opt-in switch for JMA usage.

README.md

design.mdExtend design notes for JMA as a Japan local supplement +1/-1

Extend design notes for JMA as a Japan local supplement

• Explains JMA’s role as a supplement (like NEA) and the requirement for an explicit six-digit office code per Japan location. Clarifies provider ordering constraints and expected failure isolation behavior for local capability providers.

docs/design.md

notes.mdCodify supplement boundaries and JMA office-code rationale +3/-1

Codify supplement boundaries and JMA office-code rationale

• Generalizes the supplement ordering rule to all local capability providers. Adds explicit rationale for JMA’s office-code-only API, safety fallback behavior, and why automatic guessing is intentionally avoided.

docs/notes.md

Other (3) +15 / -0
env.exampleAdd JMA base URL configuration +2/-0

Add JMA base URL configuration

• Introduces 'JMA_BASE_URL' with the default JMA forecast endpoint and notes that 'locations.json:jma_office_code' is required per location.

env.example

provider_defaults.jsonIntroduce JP default provider order including jma-jp +4/-0

Introduce JP default provider order including jma-jp

• Adds Japan (“JP”) defaults to use Open-Meteo followed by the JMA supplement.

weather_briefing/data/provider_defaults.json

registries.pyRegister jma-jp and formalize local capability provider set +9/-0

Register jma-jp and formalize local capability provider set

• Adds 'jma-jp' to the weather provider enum and introduces 'LOCAL_WEATHER_CAPABILITY_PROVIDERS' to centralize “local supplement” classification (NEA + JMA).

weather_briefing/registries.py

Comment thread weather_briefing/config.py
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit dbb155c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
weather_briefing/cli.py (1)

488-497: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Provider-specific availability check hardcoded into core orchestration.

jma_available and its name != WeatherProviderName.JMA_JAPAN or jma_available check add a second provider-name-keyed conditional to _weather_context_provider (alongside the pre-existing QWeather-JWT check later in the same function). As more local providers gain per-location availability rules, this function will keep growing if name == ... branches instead of composing per-provider checks.

♻️ Suggested extraction of a small availability registry
+_LOCAL_PROVIDER_AVAILABILITY: dict[str, Callable[[ResolvedLocation], bool]] = {
+    WeatherProviderName.JMA_JAPAN: lambda location: (
+        location.jma_office_code is not None and location.country_code in {None, "JP"}
+    ),
+}
+
+
+def _local_provider_available(name: str, location: ResolvedLocation) -> bool:
+    check = _LOCAL_PROVIDER_AVAILABILITY.get(name)
+    return check is None or check(location)
+
+
 def _weather_context_provider(
     settings: Settings,
     client: httpx.AsyncClient,
     location: ResolvedLocation,
 ) -> CapabilityProviderSet:
     names = weather_providers_for(location, settings.weather_providers)
-    jma_available = location.jma_office_code is not None and location.country_code in {None, "JP"}
-    if settings.weather_providers is not None and WeatherProviderName.JMA_JAPAN in names and not jma_available:
-        reason = "missing-jma-office-code" if location.jma_office_code is None else "known-non-japan-country"
-        _LOGGER.warning("Skipping explicit JMA provider reason=%s", reason)
+    unavailable_local = [
+        name for name in names if name in LOCAL_WEATHER_CAPABILITY_PROVIDERS and not _local_provider_available(name, location)
+    ]
+    if settings.weather_providers is not None and unavailable_local:
+        _LOGGER.warning("Skipping unavailable local provider names=%s", ",".join(unavailable_local))
     main_names = [name for name in names if name not in LOCAL_WEATHER_CAPABILITY_PROVIDERS]
     supplement_names = [
         name
         for name in names
-        if name in LOCAL_WEATHER_CAPABILITY_PROVIDERS and (name != WeatherProviderName.JMA_JAPAN or jma_available)
+        if name in LOCAL_WEATHER_CAPABILITY_PROVIDERS and _local_provider_available(name, location)
     ]

Note: this changes the warning message format (loses the specific missing-jma-office-code/known-non-japan-country reason), so the existing tests (test_unavailable_jma_skips_explicit_supplement) would need updating if adopted — treat as illustrative, not a drop-in fix.

As per coding guidelines, "keep core data platform-neutral, place vendor or delivery syntax in adapters ... and prefer composition and thin subclasses over copied request logic or growing conditionals."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@weather_briefing/cli.py` around lines 488 - 497, Refactor
`_weather_context_provider` to remove the JMA-specific `jma_available`
calculation and provider-name conditional from core orchestration. Introduce or
reuse a composable provider availability registry/check associated with each
local provider, and use it when building `supplement_names` while preserving
unavailable-provider filtering and warning behavior; update affected tests,
including `test_unavailable_jma_skips_explicit_supplement`, for the revised
abstraction.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/notes.md`:
- Around line 7-9: Clarify the JMA statement so it applies only when JMA is
configured as a supplement alongside a global primary provider, while preserving
the standalone configuration behavior where jma-jp may be the sole or primary
weather provider. Update the JMA rationale in the notes without changing the
provider orchestration rules or configuration semantics.

---

Nitpick comments:
In `@weather_briefing/cli.py`:
- Around line 488-497: Refactor `_weather_context_provider` to remove the
JMA-specific `jma_available` calculation and provider-name conditional from core
orchestration. Introduce or reuse a composable provider availability
registry/check associated with each local provider, and use it when building
`supplement_names` while preserving unavailable-provider filtering and warning
behavior; update affected tests, including
`test_unavailable_jma_skips_explicit_supplement`, for the revised abstraction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5f61b173-dd72-4b42-b6bb-166c64a94add

📥 Commits

Reviewing files that changed from the base of the PR and between c51f110 and dbb155c.

📒 Files selected for processing (15)
  • README.md
  • docs/design.md
  • docs/notes.md
  • env.example
  • tests/test_cli.py
  • tests/test_config.py
  • tests/test_geocoding.py
  • tests/test_regional_weather.py
  • weather_briefing/cli.py
  • weather_briefing/config.py
  • weather_briefing/data/provider_defaults.json
  • weather_briefing/geocoding.py
  • weather_briefing/models.py
  • weather_briefing/regional_weather.py
  • weather_briefing/registries.py

Comment thread docs/notes.md Outdated
@IceCodeNew
IceCodeNew marked this pull request as draft July 20, 2026 16:43
@IceCodeNew
IceCodeNew marked this pull request as ready for review July 20, 2026 16:46
@IceCodeNew
IceCodeNew merged commit baf5ca8 into master Jul 20, 2026
18 checks passed
@IceCodeNew
IceCodeNew deleted the codex/pr5-japan branch July 20, 2026 16:48
@@ -107,13 +107,11 @@ async def fetch_all(
) -> tuple[WeatherContextSnapshot, ...]:
"""Fetch primary context and skip expected supplement failures."""
snapshots = [await self.fetch(latitude, longitude, forecast_date=forecast_date)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Noisy dated supplement warnings 🐞 Bug ◔ Observability

When CapabilityProviderSet.fetch_all() is called with a forecast_date, it now invokes supplements
with that date; supplements that don’t support dated fetches raise WeatherContextError and are
skipped, but LoggedWeatherContextProvider logs them as WARNING failures. This produces misleading
warning noise during otherwise successful dated forecasts (e.g., Singapore with nea-sg configured).
Agent Prompt
### Issue description
Dated forecasts now attempt to call supplement providers with `forecast_date`. For non-dated supplements, `fetch_weather_context()` raises `WeatherContextError` (unsupported target date). Even though `CapabilityProviderSet.fetch_all()` catches and ignores this error, `LoggedWeatherContextProvider` logs it at WARNING first, creating misleading “failed” warnings for an expected/benign skip.

### Issue Context
This is introduced by changing `CapabilityProviderSet.fetch_all()` to pass `forecast_date` through to supplements (needed for dated-capable supplements like JMA). The fix should preserve dated-capable supplement execution while avoiding WARNING logs for the expected “does not support target forecast dates” path.

### Fix Focus Areas
- weather_briefing/capabilities.py[101-117]
- weather_briefing/weather_context.py[162-215]
- weather_briefing/weather_context.py[778-792]

### Suggested implementation direction
- In `LoggedWeatherContextProvider.fetch(...)`, before logging “call started”, detect `forecast_date is not None` AND underlying provider is not a `DatedWeatherContextProvider`.
  - Either: raise `WeatherContextError` without emitting a WARNING (optionally log at DEBUG/INFO as a skip), or
  - Downgrade logging level specifically for this expected unsupported-date error.
This keeps real provider failures visible while removing expected warning noise on dated forecasts with non-dated supplements (e.g., NEA).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 1338493

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants