Skip to content

Forecast & the swarm

The W(M, T, R) survival forecast — the engine behind the IDE's forecast card — ported line for line into both libraries, plus the client for the bundled swarm: a stratified council of 256 professional agents and a society simulator.

The W(M, T, R) model

An entity's viability W is a Cobb–Douglas blend of three capitals:

W = M^αM · T^αT · R^αR

M is material capital, T effective productive time, R relational capital (family, belief, and a bell-shaped spatial term in floor space per resident). Each year, Poisson-arriving shocks with Normal severities hit the capitals; a Cox-style hazard h = 0.02 · (W/W₀)⁻² accumulates survival; each Monte Carlo path ends classified grew / stabilized / declined / collapsed (collapse = below 30 % of W₀ for five consecutive years, by default).

The random stream is Mulberry32 — the same generator, bit for bit, as Scelo IDE and the swarm. wmtr(..., seed=42) in Python, R, or the IDE's forecast card produce identical tables; both packages assert this against a fixture generated by the TypeScript engine, to 1e-9.

Running a forecast

The engine is local — no server needed.

r = sc.wmtr("pension scheme with a weakening sponsor covenant")
r.survival                # 0.5399…  — mean survival at the horizon
r.outcome_fractions       # {'grew': 0.0, 'stabilized': 0.7, 'declined': 0.3, …}
r.drivers                 # exact decomposition of Δln W into M, T, R
r.table                   # year-by-year Table: mean_W, percentile band, survival
sc.sensitivity("…scenario…")          # the same forecast under mild / moderate / severe
sc.wmtr(alphaM=0.5, shock="severe", horizon=60, nPaths=1000)   # explicit dials
sc.wmtr(sc.sample("wmtr-scenarios"))  # a scenario row from a file
r <- sc_wmtr("pension scheme with a weakening sponsor covenant")
r$survival
r$outcome_fractions
r$drivers
r$table
sc_sensitivity("…scenario…")
sc_wmtr(alphaM = 0.5, shock = "severe", horizon = 60, nPaths = 1000)
sc_wmtr(sc_sample("wmtr-scenarios"))
WMTR · moderate · 30y · 200 paths · survival@horizon 0.540 · W/W₀ 0.96 · dominant stabilized · driver M
    year    mean_W     p10_W     p25_W  ...  survival    mean_M    mean_T    mean_R
0      0  0.724584  0.724584  0.724584  ...  1.000000  1.000000  0.430000  0.757362
...
30    30  0.696157  0.585848  0.640730  ...  0.539900  0.762330  0.376571  0.972046
— WMTR forecast · moderate shocks · 30y · 200 paths · seed 7830
  basis: α = (0.35, 0.25, 0.4) · w = (0.4, 0.3, 0.3) · shock moderate
  · Outcomes: grew 0% · stabilized 70% · declined 30% · collapsed 0% (dominant: stabilized).
  · Drivers (Σ = mean Δln W = -0.0480): M -0.1130 · T -0.0346 · R +0.0996; dominant M.
    Exact Cobb-Douglas decomposition, accumulated per path then averaged.
  · W = M^αM·T^αT·R^αR; hazard h = 0.02·(W/W₀)^−2; shocks ~ Poisson(λ) with Normal
    severities clipped to (0.01, 0.9). Same Mulberry32 stream as Scelo IDE / the swarm.

Scenario text is parsed, deterministically. Word-bounded cues set the shock environment (pandemic, crisis, severe → severe; calm, benign → mild), the capital weights (pension scheme leans relational; reserving leans material; rural village re-weights and widens the floor space; urban tightens it), and the horizon (long-term → 60y). The seed is a hash of the text itself, so the same sentence always gives the same forecast — and the note prints every derived parameter. Nothing is a language model: the parser is a fixed cue list, the same one the IDE uses.

driver_contributions(r) decomposes ln W_T − ln W_0 exactly into the three capitals' contributions (they sum to the net, to machine precision); apply_intervention(params, "pFamily", "increase", "large") shifts one dial by 0.07 / 0.20 for what-if runs; classify, derive_config, wmtr_params and even mulberry32 itself are exported for when you want the pieces.

The swarm

Scelo IDE 0.1.6+ bundles the swarm and starts it with the app on 127.0.0.1:3010; from a checkout, bun run dev:swarm runs the same server. The client functions speak to its HTTP API with the standard library / base R only.

sc.swarm_status()                          # is it up, which model providers
c = sc.council("raise the guarantee fee to 80bps", subset=32)
c.summary                                  # trust / distrust / consensus / risks
c.votes                                    # one row per agent: profession, MBTI, stance
c.interventions                            # the parameter changes the council proposes
sc.intervene(c.run_id, "pFamily", "increase", "small")   # apply one, re-convene
sc.justify(c.run_id, "agent_017")          # why that agent voted that way
soc = sc.society("a 3-month fuel shortage", size=200)    # one row per simulated person
aug = sc.augment(book, "the same shortage")  # sim_* columns joined to your own rows
sc.connect("http://other-host:3010")       # or export SCELO_SWARM_URL
sc_swarm_status()
c <- sc_council("raise the guarantee fee to 80bps", subset = 32)
c$summary; c$votes; c$interventions
sc_intervene(c$run_id, "pFamily", "increase", "small")
sc_justify(c$run_id, "agent_017")
soc <- sc_society("a 3-month fuel shortage", size = 200)
aug <- sc_augment(book, "the same shortage")
sc_connect("http://other-host:3010")
  • The council is a stratified sample of the full 256-agent body — 8 professions × 16 MBTI types × 2 genders — deliberating with the WMTR evidence injected. subset=32 convenes 32 of them; results carry the votes, the consensus score, the ranked risks, and the interventions they propose, each traceable to agents you can ask to justify themselves.
  • The society simulates a population's response to a scenario, one row per person (20–2000 of them), with the macro roll-up — workdays lost, GDP drag, excess mortality, insurer claims — in the notes. The echoed seed reproduces a run exactly.
  • Augment matches your own rows to the simulated cohort on the age / sex / comorbidity each row states (a row that states none gets the whole cohort's figures; sim_bucket_match says which) and joins sim_* outcome columns on (capped at 100,000 rows, the IDE's limit).

When the swarm is not running

The client does not pretend. Every swarm call fails with one clear message — cannot reach the swarm at http://127.0.0.1:3010. Start Scelo IDE (it bundles the swarm) or run bun run dev:swarm, or connect(url) — and nothing else in the libraries depends on it. wmtr and sensitivity are local and always work.

Function list

wmtr run_wmtr derive_config wmtr_params classify sensitivity driver_contributions dominant_driver apply_intervention mulberry32 · connect swarm_url swarm_status council council_run intervene justify chat_log swarm_wmtr society augment — each with the sc_ twin in R (WmtrParams is the Python dataclass; sc_wmtr_params() the R constructor).