ASSEMBLY-WP-0009-T06: predictions v1 (P-2026-001…004), growth-aligned band ranges; runner cites registered predictions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 14913@bnt-lap001
Assistant-Session: add7ef69-b901-4756-9a67-34cf1125d96f
This commit is contained in:
tegwick 2026-10-13 16:01:15 +02:00
parent 8ce35a3597
commit 141c5d9e5c
7 changed files with 282 additions and 8 deletions

View file

@ -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

View file

@ -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).

View file

@ -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)

View file

@ -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"})

View file

@ -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()

View file

@ -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 |

View file

@ -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.