diff --git a/AGENTS.md b/AGENTS.md index 5fa6956..d1d3a6c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,6 +32,7 @@ cd simulations/gadm02 && python3 -B grounded_readings.py # readings under grou cd simulations/gadm02 && python3 -B band_probe.py # scenarios across the adoption band B1–B4 cd simulations/gadm02 && python3 -B legitimacy.py # sentiment and legitimacy loop across the band cd simulations/gadm02 && python3 -B jurisdictions.py # jurisdiction archetypes J1–J4 across the band +cd simulations/gadm02 && python3 -B predictions_v1.py # band ranges for the prediction register (P-2026-*) ``` Reality checkpoints (ASSEMBLY-WP-0010): scheduled by activity-core and diff --git a/checkpoints/predictions.md b/checkpoints/predictions.md index 21faac1..7310992 100644 --- a/checkpoints/predictions.md +++ b/checkpoints/predictions.md @@ -5,9 +5,8 @@ observations recorded against them at later checkpoints. **Never edit or delete an entry.** Revised predictions are added as new entries that reference the entry they supersede. -The first substantive predictions (band ranges per anchor for catalog -indicators) are written by ASSEMBLY-WP-0009-T06. Until then, checkpoints -record observations only. +Predictions v1 (P-2026-001 … 004) were registered by ASSEMBLY-WP-0009-T06 on +2026-10-13. ## Entry format @@ -22,4 +21,69 @@ record observations only. ## Entries -(none yet) +### P-2026-001 — L1 firms using AI (US), horizons 2027-10, 2028-10, 2030-10 +- Registered: 2026-10-13, model GADM-0.4, by ASSEMBLY-WP-0009-T06 + (`simulations/gadm02/predictions_v1.py`, `results/predictions_v1.md`) +- Proxy mapping: BTOS Q7 share ∝ K_Ω near the aligned time t*. Model time is + aligned by growth: t* is where K_Ω's one-year ratio equals the observed 1.46 + (17.3 % → 23.8 %, 2025 period 24 → 2026 period 20). Base 23.8 %. Archetypes + J2 and J3, legitimacy loop off and on. +- Band (value in %, 2027-10 / 2028-10 / 2030-10): + B1 30.8–34.5* / 36.7–42.9* / 45.2–52.8* · + B2 34.1–34.6 / 40.7–44.0 / 44.4–58.6 · + B3 33.6–34.4 / 37.5–42.8 / 36.2–53.5 · + B4 33.2–34.6 / 34.2–41.1 / 31.4–44.4 + (* some B1 runs cannot reach the observed growth rate at all) +- What separates the anchors: 2027 hardly does (all ~31–35 %). By 2030, B4 + predicts a plateau or decline (peak and backlash), B1–B2 continued growth. + A value below ~31 % in 2027-10 is outside every anchor. +- Observations: + - (none yet) + +### P-2026-002 — L2 enterprises using AI (EU27), reference years 2026, 2027, 2029 +- Registered: 2026-10-13, model GADM-0.4, by ASSEMBLY-WP-0009-T06 +- Proxy mapping: Eurostat isoc_eb_ai share (10+ employees) ∝ K_Ω near t*; + aligned to the observed one-year ratio 1.48 (13.48 → 19.95, 2024 → 2025). + Base 19.95 %. Archetype J1, loop off and on. +- Band (value in %, 2026 / 2027 / 2029): + B1 20.7 / 21.4–21.5 / 22.8–23.1* · + B2 25.4–27.4 / 30.4–34.1 / 39.5–45.3* · + B3 29.2–29.4 / 37.7–37.9 / 52.0–52.3 · + B4 29.3–29.5 / 39.4–39.6 / 60.6–60.7 + (* under J1 levers, B1 and B2 cannot reach the observed 2025 growth: the + observed EU growth already argues against "J1 at B1–B2") +- Observations: + - (none yet; the 2026 value is expected around mid-2027) + +### P-2026-003 — L2 enterprises using AI (DE), reference years 2026, 2027, 2029 +- Registered: 2026-10-13, model GADM-0.4, by ASSEMBLY-WP-0009-T06 +- Proxy mapping: as P-2026-002; observed one-year ratio 1.31 (19.75 → 25.97). + Base 25.97 %. Archetype J1, loop off and on. +- Band (value in %, 2026 / 2027 / 2029): + B1 26.9–27.0 / 27.9–28.0 / 29.7–30.1* · + B2 33.0–34.0 / 39.6–41.2 / 51.5–53.3* · + B3 33.8–33.9 / 40.8–41.0 / 52.1–53.0 · + B4 34.0 / 42.1–42.4 / 57.7–58.2 +- Observations: + - (none yet) + +### P-2026-004 — L6 public sentiment toward AI (US, EU), direction to 2028 +- Registered: 2026-10-13, model GADM-0.4, by ASSEMBLY-WP-0009-T06 +- Proxy mapping: sign only. Σ falling ↔ a rising share of "more concerned than + excited" (Pew, US) or of negative views (Eurobarometer, EU). Magnitudes are + not mapped (Σ is uncalibrated). +- Band (Δ Σ from now to 2028-10, loop on): US B1 −0.07 · B2 −0.12 · B3 −0.18 · + B4 −0.18 (loop off: 0). EU all anchors −0.01 … +0.03 (flat). +- Claim tested: the legitimacy loop of `models/AdoptionBand.md` § 3. If US + sentiment improves clearly while adoption grows as in P-2026-001, the loop as + specified is not supported; if it worsens, the loop is supported and the + faster anchors gain weight. EU sentiment should move less than US sentiment. +- Observations: + - (none yet; baseline values to be collected at the first review, L6 manual) + +### Not registered in v1 +- **L3 (AR):** no adoption base value (manual source missing), so no + quantitative prediction. Qualitatively, the J2 path predicts the same + pattern as the US: fast growth now, and falling sentiment by 2028. +- **L5 (US investment)** and level predictions: wait for calibration + (ASSEMBLY-WP-0013). diff --git a/checkpoints/run_checkpoint.py b/checkpoints/run_checkpoint.py index e5d4ada..c382b27 100644 --- a/checkpoints/run_checkpoint.py +++ b/checkpoints/run_checkpoint.py @@ -113,16 +113,37 @@ def assessment(observations: list[dict]) -> tuple[str | None, str | None]: return text, None +def registered() -> dict[tuple[str, str], list[str]]: + """(indicator, jurisdiction) -> prediction ids, from the headings in predictions.md.""" + out: dict[tuple[str, str], list[str]] = {} + path = HERE / "predictions.md" + if not path.exists(): + return out + for m in re.finditer(r"^### (P-\d{4}-\d{3}) — (\w+) [^(\n]*\(([^)]+)\)", path.read_text(), re.M): + for jur in (j.strip() for j in m.group(3).split(",")): + out.setdefault((m.group(2), jur), []).append(m.group(1)) + return out + + +GEO_ALIAS = {"EU27_2020": "EU27", "EU": "EU27"} + + +def band_position(preds: dict, indicator: str, jur: str) -> str: + ids = preds.get((indicator, jur)) or preds.get((indicator, GEO_ALIAS.get(jur, jur)), []) + return f"see {', '.join(ids)} (judged at review)" if ids else "no prediction registered" + + def render(day: date, kind: str, phase: str, obs: list[dict], assess: str | None, assess_note: str | None, data_rel: str) -> str: rows, missing = [], [] + preds = registered() for o in obs: if o["status"] == "ok": geo = (o.get("labels") or {}).get("geo") jur = geo or o["jurisdiction"] pub = (o.get("published") or "")[:10] or "—" rows.append(f"| {o['indicator']} {o['label']} | {jur} | {o['value']} | {o.get('unit') or ''} | " - f"{o['period']} | {pub} | [source]({o['url']}) | no prediction registered |") + f"{o['period']} | {pub} | [source]({o['url']}) | {band_position(preds, o['indicator'], jur)} |") else: missing.append(f"- {o['indicator']} {o['label']} ({o['jurisdiction']}): {o['status']}" f" — {o.get('note') or ''} Source: {o.get('source') or '—'}") @@ -135,8 +156,10 @@ def render(day: date, kind: str, phase: str, obs: list[dict], assess: str | None "|---|---|---|---|---|---|---|---|", *rows, "", "## Assessment", ""] lines += [assess] if assess else [f"_No automated assessment: {assess_note}_"] lines += ["", "## Not found / manual", "", *(missing or ["- none"]), "", - "## Follow-ups", "", "- Predictions are not yet registered (ASSEMBLY-WP-0009-T06); band positions", - " are reported once the register has entries.", ""] + "## Follow-ups", "", + "- Band positions against registered predictions (`predictions.md`) are judged at review" + if preds else "- No predictions registered yet; band positions follow once the register has entries.", + ""] return "\n".join(lines) diff --git a/checkpoints/test_run_checkpoint.py b/checkpoints/test_run_checkpoint.py index 3734545..db087d9 100644 --- a/checkpoints/test_run_checkpoint.py +++ b/checkpoints/test_run_checkpoint.py @@ -27,6 +27,14 @@ class TestWindow(unittest.TestCase): self.assertIsNone(rc.active_phase(date(2026, 10, 17), "light", sched)) +class TestPredictions(unittest.TestCase): + def test_registered_predictions_are_cited(self): + preds = rc.registered() + self.assertIn("P-2026-001", rc.band_position(preds, "L1", "US")) + self.assertIn("P-2026-002", rc.band_position(preds, "L2", "EU27_2020")) + self.assertEqual(rc.band_position(preds, "L9", "US"), "no prediction registered") + + class TestNumberGuard(unittest.TestCase): def test_numbers_extraction(self): self.assertEqual(rc._numbers("EU at 19.95 % in 2025, US 23.8."), {"19.95", "2025", "23.8"}) diff --git a/simulations/gadm02/predictions_v1.py b/simulations/gadm02/predictions_v1.py new file mode 100644 index 0000000..8640026 --- /dev/null +++ b/simulations/gadm02/predictions_v1.py @@ -0,0 +1,122 @@ +"""Band ranges for pre-registered predictions v1 (ASSEMBLY-WP-0009-T06). + +Model time is aligned to the data by growth, not by level: for each run, t* +is the model time at which K_Omega's one-year ratio equals the observed +one-year ratio of the adoption indicator. The prediction for horizon h is +indicator(base) * K_Omega(t* + h) / K_Omega(t*). This assumes only that the +indicator is proportional to K_Omega near t*; level calibration is WP-0013. +For each band anchor the range spans the legitimacy loop off/on and the +jurisdiction's nearest archetypes (models/AdoptionBand.md § 4.4). If a run +cannot reach the observed growth at any time, it is aligned at its fastest +year and flagged. + +Usage: python3 -B simulations/gadm02/predictions_v1.py +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +sys.dont_write_bytecode = True + +import gadm04 as g # noqa: E402 +import grounding as gr # noqa: E402 +from jurisdictions import ARCHETYPES # noqa: E402 + +OUT = Path(__file__).resolve().parent / "results" +ANCHORS = list(gr.BAND) +NEAREST = {"US": ("J2", "J3"), "EU": ("J1",), "DE": ("J1",), "AR": ("J2",)} +# observed adoption indicator: (base value, base period, observed one-year ratio, source) +OBS = { + "US": (23.8, "BTOS 2026 period 20", (23.8 / 17.3) ** (1 / 0.85), + "L1 BTOS Q7: 17.3 % (2025 period 24) → 23.8 % (2026 period 20), 22 biweekly periods"), + "EU": (19.95, "2025", 19.95 / 13.48, "L2 Eurostat isoc_eb_ai: 13.48 (2024) → 19.95 (2025)"), + "DE": (25.97, "2025", 25.97 / 19.75, "L2 Eurostat isoc_eb_ai: 19.75 (2024) → 25.97 (2025)"), +} +HORIZONS = {"US": {"2027-10": 1.0, "2028-10": 2.0, "2030-10": 4.0}, + "EU": {"ref. year 2026": 1.0, "ref. year 2027": 2.0, "ref. year 2029": 4.0}, + "DE": {"ref. year 2026": 1.0, "ref. year 2027": 2.0, "ref. year 2029": 4.0}} +STEP = 0.1 # years per recorded row + + +def runs(anchor: str, jur: str, var: dict) -> list[list[dict]]: + vp, vi = var[anchor] + out = [] + for j in NEAREST[jur]: + _t, lev, s0 = ARCHETYPES[j] + for sent in (0.0, 1.0): + out.append(g.simulate({**vp, **lev, "sent": sent}, {**vi, "Sig": s0, "Lg": s0}, + T=40, record_every=1)) + return out + + +def align(rows: list[dict], ratio: float) -> tuple[int, bool]: + """Index of the first row whose one-year K_Omega ratio falls to the observed ratio.""" + k = int(round(1 / STEP)) + rs = [rows[i + k]["Ko"] / rows[i]["Ko"] for i in range(len(rows) - k)] + if max(rs) < ratio: + return rs.index(max(rs)), False + peak = rs.index(max(rs)) + for i in range(peak, len(rs)): + if rs[i] <= ratio: + return i, True + return len(rs) - 1, False + + +def ranges(var: dict) -> dict: + res = {} + for jur, (base, _bp, ratio, _src) in OBS.items(): + for a in ANCHORS: + rs = runs(a, jur, var) + al = [align(r, ratio) for r in rs] + for h, t in HORIZONS[jur].items(): + vals, sig = [], [] + for r, (i, _ok) in zip(rs, al): + j = i + int(round(t / STEP)) + vals.append(base * r[j]["Ko"] / r[i]["Ko"]) + sig.append(r[j]["Sig"] - r[i]["Sig"]) + res[(jur, a, h)] = {"val": (min(vals), max(vals)), "sig": (min(sig), max(sig)), + "ok": all(ok for _i, ok in al), + "tstar": (min(i for i, _ in al) * STEP, max(i for i, _ in al) * STEP)} + return res + + +def main() -> None: + OUT.mkdir(exist_ok=True) + var = {a: gr.variant(gr.BAND[a]) for a in ANCHORS} + res = ranges(var) + lines = [f"# {g.MODEL_VERSION}: band ranges for predictions v1", "", + "Generated by `simulations/gadm02/predictions_v1.py` (ASSEMBLY-WP-0009-T06).", + "Model time aligned by observed one-year growth of the adoption indicator; range per", + "anchor spans loop off/on and the nearest archetypes. * = at least one run cannot reach", + "the observed growth rate (anchor too slow for the data).", "", "## Observed base", "", + "| Jurisdiction | Base | One-year ratio | Source |", "|---|---|---|---|"] + for jur, (base, bp, ratio, src) in OBS.items(): + lines.append(f"| {jur} | {base} % ({bp}) | {ratio:.2f} | {src} |") + lines += ["", "## Adoption indicator, predicted value (%)", "", + "| Jurisdiction | Horizon | " + " | ".join(ANCHORS) + " |", "|---|---|" + "---|" * len(ANCHORS)] + for jur in OBS: + for h in HORIZONS[jur]: + lines.append(f"| {jur} | {h} | " + " | ".join( + f"{res[(jur, a, h)]['val'][0]:.1f}–{res[(jur, a, h)]['val'][1]:.1f}" + + ("" if res[(jur, a, h)]["ok"] else "*") for a in ANCHORS) + " |") + lines += ["", "## Aligned model time t* (years after anchor start)", "", + "| Jurisdiction | " + " | ".join(ANCHORS) + " |", "|---|" + "---|" * len(ANCHORS)] + for jur in OBS: + h0 = next(iter(HORIZONS[jur])) + lines.append(f"| {jur} | " + " | ".join( + "{:.1f}–{:.1f}".format(*res[(jur, a, h0)]["tstar"]) for a in ANCHORS) + " |") + lines += ["", "## Δ sentiment Σ from t* (loop-on runs; loop off = 0)", "", + "| Jurisdiction | Horizon | " + " | ".join(ANCHORS) + " |", "|---|---|" + "---|" * len(ANCHORS)] + for jur in OBS: + for h in HORIZONS[jur]: + lines.append(f"| {jur} | {h} | " + " | ".join( + f"{res[(jur, a, h)]['sig'][0]:+.2f}…{res[(jur, a, h)]['sig'][1]:+.2f}" for a in ANCHORS) + " |") + lines.append("") + (OUT / "predictions_v1.md").write_text("\n".join(lines)) + print("\n".join(lines)) + + +if __name__ == "__main__": + main() diff --git a/simulations/gadm02/results/predictions_v1.md b/simulations/gadm02/results/predictions_v1.md new file mode 100644 index 0000000..c464050 --- /dev/null +++ b/simulations/gadm02/results/predictions_v1.md @@ -0,0 +1,50 @@ +# GADM-0.4: band ranges for predictions v1 + +Generated by `simulations/gadm02/predictions_v1.py` (ASSEMBLY-WP-0009-T06). +Model time aligned by observed one-year growth of the adoption indicator; range per +anchor spans loop off/on and the nearest archetypes. * = at least one run cannot reach +the observed growth rate (anchor too slow for the data). + +## Observed base + +| Jurisdiction | Base | One-year ratio | Source | +|---|---|---|---| +| US | 23.8 % (BTOS 2026 period 20) | 1.46 | L1 BTOS Q7: 17.3 % (2025 period 24) → 23.8 % (2026 period 20), 22 biweekly periods | +| EU | 19.95 % (2025) | 1.48 | L2 Eurostat isoc_eb_ai: 13.48 (2024) → 19.95 (2025) | +| DE | 25.97 % (2025) | 1.31 | L2 Eurostat isoc_eb_ai: 19.75 (2024) → 25.97 (2025) | + +## Adoption indicator, predicted value (%) + +| Jurisdiction | Horizon | B1 | B2 | B3 | B4 | +|---|---|---|---|---|---| +| US | 2027-10 | 30.8–34.5* | 34.1–34.6 | 33.6–34.4 | 33.2–34.6 | +| US | 2028-10 | 36.7–42.9* | 40.7–44.0 | 37.5–42.8 | 34.2–41.1 | +| US | 2030-10 | 45.2–52.8* | 44.4–58.6 | 36.2–53.5 | 31.4–44.4 | +| EU | ref. year 2026 | 20.7–20.7* | 25.4–27.4* | 29.2–29.4 | 29.3–29.5 | +| EU | ref. year 2027 | 21.4–21.5* | 30.4–34.1* | 37.7–37.9 | 39.4–39.6 | +| EU | ref. year 2029 | 22.8–23.1* | 39.5–45.3* | 52.0–52.3 | 60.6–60.7 | +| DE | ref. year 2026 | 26.9–27.0* | 33.0–34.0* | 33.8–33.9 | 34.0–34.0 | +| DE | ref. year 2027 | 27.9–28.0* | 39.6–41.2* | 40.8–41.0 | 42.1–42.4 | +| DE | ref. year 2029 | 29.7–30.1* | 51.5–53.3* | 52.1–53.0 | 57.7–58.2 | + +## Aligned model time t* (years after anchor start) + +| Jurisdiction | B1 | B2 | B3 | B4 | +|---|---|---|---|---| +| US | 0.0–0.6 | 1.0–1.3 | 1.3–1.6 | 1.4–2.2 | +| EU | 0.0–9.6 | 0.0–0.0 | 0.7–0.9 | 1.1–1.5 | +| DE | 0.0–9.6 | 0.0–0.4 | 1.6–1.8 | 2.5–2.9 | + +## Δ sentiment Σ from t* (loop-on runs; loop off = 0) + +| Jurisdiction | Horizon | B1 | B2 | B3 | B4 | +|---|---|---|---|---|---| +| US | 2027-10 | -0.03…+0.00 | -0.06…+0.00 | -0.09…+0.00 | -0.10…+0.00 | +| US | 2028-10 | -0.07…+0.00 | -0.12…+0.00 | -0.18…+0.00 | -0.18…+0.00 | +| US | 2030-10 | -0.13…+0.00 | -0.24…+0.00 | -0.31…+0.00 | -0.30…+0.00 | +| EU | ref. year 2026 | +0.00…+0.00 | +0.00…+0.01 | +0.00…+0.01 | +0.00…+0.00 | +| EU | ref. year 2027 | +0.00…+0.01 | +0.00…+0.02 | +0.00…+0.01 | +0.00…+0.00 | +| EU | ref. year 2029 | +0.00…+0.02 | +0.00…+0.03 | +0.00…+0.01 | -0.01…+0.00 | +| DE | ref. year 2026 | +0.00…+0.00 | +0.00…+0.01 | +0.00…+0.00 | -0.00…+0.00 | +| DE | ref. year 2027 | +0.00…+0.01 | +0.00…+0.02 | +0.00…+0.00 | -0.01…+0.00 | +| DE | ref. year 2029 | +0.00…+0.02 | +0.00…+0.03 | -0.00…+0.00 | -0.03…+0.00 | diff --git a/workplans/ASSEMBLY-WP-0009-adoption-band-jurisdictions.md b/workplans/ASSEMBLY-WP-0009-adoption-band-jurisdictions.md index 7fd0836..28ac8e1 100644 --- a/workplans/ASSEMBLY-WP-0009-adoption-band-jurisdictions.md +++ b/workplans/ASSEMBLY-WP-0009-adoption-band-jurisdictions.md @@ -103,7 +103,7 @@ Run `gsa.py` per anchor. Do the dominant drivers change across the band? ```task id: ASSEMBLY-WP-0009-T06 -status: todo +status: done priority: medium state_hub_task_id: "ebac215f-d624-5320-8c28-dfc54f642133" ``` @@ -111,3 +111,9 @@ state_hub_task_id: "ebac215f-d624-5320-8c28-dfc54f642133" Write the first prediction register entry for the next checkpoints (handed to ASSEMBLY-WP-0010): indicators, jurisdictions, horizons, and band ranges per anchor. + +**Done 2026-10-13:** `checkpoints/predictions.md` P-2026-001 … 004 (L1 US, +L2 EU27 and DE, L6 direction US/EU), generated by `predictions_v1.py`. Model +time is aligned by observed growth, not level. The checkpoint runner cites +registered predictions in the band-position column. Finding: under J1 levers, +B1–B2 cannot reproduce the observed 2025 EU growth.