Skip to content

Seasonal forecast and ENSO labelling (0.6.0) - #84

Merged
bbrunckh merged 74 commits into
devfrom
seasonal-forecast
Nov 10, 2026
Merged

bbrunckh merged 74 commits into
devfrom
seasonal-forecast

Conversation

@bbrunckh

Copy link
Copy Markdown
Collaborator

Summary

Adds seasonal forecasts and ENSO labelling to Steps 1 to 3, plus supporting cleanup. Version 0.6.0.

Seasonal forecast

  • Step 2 forecast flyout: local ECMWF SEAS5 runs for 1-, 3- and 6-month windows, with skill flags, member placement against reference years, and an ENSO-marked month strip.
  • Step 3 runs policies on forecast members and reference years. All shock-responsive transfer designs work on a forecast window.
  • Forecast results merged into one chart per question in Steps 2 and 3; batch tables and rows added.
  • Method: docs/34_seasonal_forecast.md, docs/43_shock_responsive_sp.md (Section 7).

ENSO

  • Display label only (El Nino, La Nina, Neutral, Mixed) on years and forecast months. No ENSO in estimation or Step 2 settings.
  • Marks on the Step 1 interview chart and weather map, Steps 2 and 3 distribution charts and Diagnostics; exports and batch labels.
  • Method: docs/35_enso.md.

Other

  • Every return period now uses the Weibull plotting rule (R/utils_return_period.R).
  • Fixes: config import restores Step 2 and 3 settings, Step 2 run status, dropped repeat Run clicks, Weather stats click, axe target-size on period checkboxes.
  • Housekeeping: R/ files renamed to <role>_<domain>_<topic>.R, legacy batch/ and dev/archive removed, plans archived to review/archive/.

Checks

  • Full suite passes except deploy-contract, which passes after the manifest regeneration in this PR.
  • dev/docs_check.R and dev/agents_check.R pass. Browser and real-data checks done by the maintainer.
  • Not run: BFA before/after and benchmarks (no output-moving or speed claims).

Notes

  • Large diff (299 files, mostly the file renames and removed legacy code).
  • Production forecast runs show on Connect once this is deployed (wise-app-data published runs).

Split the Simulation settings flyout into Simulation settings, Climate
projections and Seasonal forecast. The forecast flyout has an include
checkbox, run select, 1/3/6 month window length and a month-strip target
window picker, with a lag readout, predictor support list and a reason
shown when the forecast is unavailable.

UI only: no forecast data is wired and nothing reaches the simulation
run. The run source returns nothing unless
options(wiseapp.forecast_scaffold = TRUE) supplies a placeholder run.

Also add roadmap/seasonal_forecast.md and a toggle_icon argument to
config_flyout_block().
Derive the forecast lag from the fitted weather variables (longest ref_end)
instead of a placeholder, and widen the month strip up to 6 observed months.
Add a forecast scenario display key, a forecast-first position in the
expected scenario labels, and forecast fields in the run signature that are
present only when the forecast is on. Runs carry product and lead_unit.

Add a forecast colour and return it from the name-to-colour lookups in the
simulation and policy comparison charts. Replace the stale note that said
anomalies are anchored to the historical period. Add the design-stage
methods page docs/34_seasonal_forecast.md and a test that the fc_* inputs
travel in exported configurations.

Nothing here reaches a simulation run: the forecast stays unavailable until
the run catalogue is published. Roadmap updated to the remaining work.
…ement

Draft-2 reader: catalogue skips invalid runs, context months come from the run file.
Forecast weather builder runs one window query for all members (82 s to under 1 s on BFA).
Fix int_month fixed effect with forecast targets and stop on NA forecast predictions.
Fix n_forecast_members tibble warning.
Evaluate observed weather for the target months of each reference year, place members
in that distribution, and show it in the headline cards and a placement chart.
Hide annual adverse-year sections for a forecast-only run.
Replace the redundant label regexes next to .step2_is_forecast_scenario().
Derive the group key, run signature and prepared-weather cache key from .forecast_identity().
Compute the forecast group once and fix the weather_consumer indentation.
…est shape

Catalogue reads the optional skill file (checksum verified) and context_months as count or list.
Flyout, headline cards and basis strip report the skill of each target month, worst flag first.
Fixture writes a synthetic skill table and the pipeline's manifest shape.
… forecast work

Add the Step 3 design note, the batch gaps and the decisions needed to the roadmap.
…out without data

Remove the forecast scaffold option; controls tests mock the catalogue.
Document the batch forecast tables and the seasonality assumptions.
…per-variable skill

The catalogue and manifest of the first real BFA run list one adjustment per
variable (t;r) and the skill table has a variable column. Accept ratio, add r to
the forecast variables, report the worst skill flag over the model's variables.
Bench gets a WX variable switch; docs and roadmap record the acceptance.
…on members and reference years, policy view, batch rows

Static levers and regular transfers run on a forecast window; a shock-responsive
transfer is rejected. Annual views read annual scenarios only; a new section shows
members and same-window reference years without and with the policy. Batch writes
the policy arm of the forecast table and policy_effect summary rows.
Each Results section, the cards and the return-period table get a second
group for the forecast target months under the all-months group:
Historical for the same months (the same-window reference years) against
the forecast members, on a shared outcome axis, with its own deviation
baseline. Adverse levels show one Forecast row with the member band;
exceedance places the median member on the historical same-month curve;
the member spread is an across-member component. The ensemble-spread
toggles set both the climate-model and the member band. Step 3 shows the
same groups for baseline and policy and drops its separate forecast
section. Removes the placement chart, the scope toggle and the unused
forecast policy chart and text helpers.
Each Step 2 Results chart now carries the annual and target-month series,
ordered Forecast, Historical (Nov - Jan), Historical (annual), SSPs, with one
note per section. Each Historical series keeps its own baseline. The exceedance
chart marks the median member on the target-month curve. The four
forecast_window_* figure exports are removed; the climate_* figures include the
forecast. Headline cards and Step 3 keep their two groups.

Also includes the pending Step 2 sidebar rework (forecast and projection
controls, period presets) and related CSS and tests.
Move the step2-forecast-toggle class from the conditionalPanel to a child div:
a class on the panel breaks its hidden state under shiny >= 1.14. The stage-log
test now counts the fifth wise_user_error() call, the forecast catalogue error.
The streamed forecast partial now reports the group as landed without tables,
because Results never reads a forecast group's annual tables. Headline placement
now reads the window series (step2_window_placement()) instead of aggregating
the same members and reference years a second time. Outputs are unchanged.
Step 3 distribution, adverse and exceedance charts now carry the annual and
target-month series with baseline and policy, in the Step 2 row order, with one
note per section. The forecast lines read against the no-policy target-month
curve, with stacked labels and a dot on the curve. The three policy_window_*
figure exports, .step2_groups() and the axis-range and margin helpers are
removed. Headline cards keep their two groups.
Align ARCHITECTURE, the methods pages and the roadmap with the one-chart Results
layout, the forecast variables and policy support. Remove code names from the
body of the climate scenarios page, and refresh the docs index and comments.
A forecast member or same-window historical year reads the mean trigger weather
of the target months against the same months of the historical years, and pays
once per window, with the annual rules otherwise. A member's payment means "if
this member's weather happened", not a payment on the forecast.

Forecast scenarios run the annual code on a copy of the channels holding a window
plan, so annual scenarios are unchanged. Impact triggers, national gates,
loss-share budgets, dynamic targeting and anticipatory payments are rejected with
a forecast. Payout figures stay out of the annual shock tables: Step 3 adds a
card led by the population in triggered locations, and batch adds shock_window
summary rows. Notes, methods pages and batch docs are updated.
Merge the review and Step 3 design notes into roadmap/seasonal_forecast.md. History stays in git.
New R/fct_enso.R (soft loader, fitted window, exposure-weighted year labels,
sample coverage, phase summary, forecast outlook), tests and a small fixture
pair. No existing R/ file changes. Progress in roadmap/enso.md updated.
…ch rows

The Run button is re-rendered after each run and its click count resets, so a repeated click produced an identical event and was ignored. Manual run events now carry a sequence number. Add a batch test for a shock transfer on a forecast window and update the forecast roadmap.
- Keep the calibration caveat in the forecast popover for runs that are not
  experimental, and point to the skill flags.
- Update the methods page status and the roadmap: runs from November 2026 are
  published, so they show on Posit Connect once this branch is deployed.
- Regenerate docs/index.yml.
…rences

Rename 35 R files and 28 matching tests (fct_data_*, fct_model_*, fct_stepN_*,
fct_decomp_*, fct_policy_sp_*, fct_echart_builders, utils_step1), update every
reference in code comments, tests, docs, skills and review notes, fix stale
module names in headers, and document the naming rule in ARCHITECTURE.md.
No code logic changed; fct_run_simulation() keeps its function name.
One file per theme: survey stats, model specification, estimation, uncertainty and validation, hazard data, policy scenarios. Indexed in roadmap/README.md.
Remove the forecast restrictions on shock-responsive transfers: national gate,
dynamic targeting layers, impact triggers, loss-linked budget and anticipatory
payments now run on forecast members and same-window reference years.

- Window reference level: household mean prediction over the target months of
  all reference years; loss events are scored per survey round and location.
- Worst-affected locations need at least 10 same-window historical years.
- Loss-linked budget is scaled by L/12 for a window of L months.
- Anticipatory: a location is paid when at least forecast_member_share_pct of
  members pass its trigger (new field, default 50); each member and reference
  year scores that decision with its own weather.
- Window leakage, false-positive and false-negative rates, loss offset and
  anticipatory figures in a Step 3 card and the shock_window batch rows.
- Remove .sp_forecast_shock_problem(); update methods pages, batch table docs
  and the roadmap.
…st is shown

In the Step 2 and 3 annual distribution chart, a forecast-only view now draws the dashed line at the mean of the same-window Historical row and labels it with the window, instead of the annual mean. Charts with a climate scenario keep the annual line. Adds a test and updates the Step 2 caption.
Move roadmap/seasonal_forecast.md and roadmap/enso.md to review/archive/ with
the state, decisions and the checks left before the PR into dev. Point code
comments, ARCHITECTURE.md and dev/parity_enso.R at the new path, and update
the roadmap and review indexes. Add NEWS.md for 0.6.0 with the full
shock-responsive transfer support on a forecast window.
The button is re-rendered when the selection changes, which resets its click
count, so the second first-click produced an identical event and did not
fire. Add a click sequence, as Step 2 and Step 3 already do.
…NSO relevance sentence

A binned variable is a factor, so the 'Weather and ENSO' sentence skipped it and
never showed for the default binned temperature.
The job's closing complete notice overwrote success, so the configuration
import pipeline never saw Step 2 finish and stalled.
Earlier stages re-render the forecast window and Step 3 controls, which reset
values applied at the start. Apply a step's saved settings again while it
settles and wait until the server reads them back before the step runs. The
return-period seeding observer no longer rewrites an unchanged default over a
restored value.
One plotting position, p = m / (n + 1), and quantile type 6 for levels,
wherever an empirical sample gets a probability of exceedance or a "1 in N":
the return-period table, exceedance charts, forecast placement and window
position, the forecast 1-in-10 level, the Step 3 decomposition at a return
period and the Step 3 return-period trigger thresholds. Replaces Hazen, type 7
and the c / N share (Eq. 4.3 is now c / (N + 1)).

- New R/utils_return_period.R (rp_positions, rp_quantile, rp_of_level,
  rp_group_quantile, rp_exceedance_rows); rank_interp() removed; the
  duplicated exceedance rows in mod_2_02_results.R use the shared helper.
- Far tail: "beyond every reference year (rarer than 1 in n+1)" and "worse
  than n-1 of n reference years" instead of an interpolated 1-in-X.
- A 1-in-10 forecast level needs 9 reference years; fewer gives NA.
- Levels at a given return period are slightly more adverse; the worst of
  30 years is 1 in 31 (was 1 in 60).
- Docs 32/33/34/43/44/45, batch tables doc, NEWS and the Decision log
  (BFA before/after) updated.
- Tests: helper tests with known answers; changed expectations are the
  Hazen / type 7 values moving to the Weibull values.
- manifest.json not regenerated (test-deploy-contract fails until the PR).
- step3_window_views(): sort the reference values before rp_quantile(),
  which expects ascending input; the Step 3 forecast card "members worse than
  the historical 1-in-10" used a level that depended on year order.
- Forecast placement text and exceedance overlay share one rule: an
  unresolved, not-beyond median member reads "worse than n_worse of n
  reference years", using the strict count, so tied worst years are right.
- docs/32: Eq. 4.3 round trip is exact at whole ranks, approximate between;
  drop the Hazen comparison from the methods page (kept in NEWS).
- Tests: unsorted reference years, tied most adverse years.
- value_y_axis(): axisLine onZero = FALSE, so the y axis no longer sits at
  x = 0 when the weather range spans zero.
- Rug: one mark per 1/500 of the range instead of one per household; tens of
  thousands of marks were rendered progressively and stopped part way, so
  only the first rows (often one survey round) showed. Opacity 0.5.
The overlay label sat right-aligned at the median-member line, so a line near
the left edge of the log axis (a far-tail member, now with the longer rank
text) pushed it off the chart. It now runs away from the nearer edge, read
from the chart's log x-axis bounds; without bounds it stays right-aligned.
…arkLine x

ECharts rounds a marker's axis value to 2 decimals by default, so the exceedance line at p = 0.0562 was drawn at 0.06 while the dot (markPoint) stayed at 0.0562. Set precision = 12, as the violin Historical-mean line already does.
…tale smoke legend check, cancel superseded runs
@bbrunckh
bbrunckh merged commit 8c08575 into dev Nov 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant