SPB Git

spb/airiskindex

Public

The most methodologically rigorous, fully transparent AI job-exposure index.

feat: scaffold AI Risk Index platform (research corpus, methodology v1 draft, monorepo)

- docs/research: 4-document research corpus grounding v1 (indices, data
  sources, rater API, landscape/evidence), compiled 2026-08-05
- docs/methodology: METHODOLOGY.md v1 draft, changelog, worked example
- packages/scoring: pure deterministic engine (5 weighted dimensions,
  barriers inverted, exposure/substitution/augmentation sub-scores with
  CI bounds), snapshot + fast-check property tests
- packages/db: Prisma schema with rating audit trail and immutable runs
- apps/web: Next.js 14 site + public API v1 (health, methodology,
  occupations)
- apps/worker: BullMQ multi-model rater pipeline (Message Batches,
  versioned rubric prompt v1) + score:recompute
- apps/etl: Python skeleton with O*NET 30.3 downloader
- infra: compose files, deploy.sh, ngrok.yml, env template

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
simon-pierre boucher committed (Aug 5, 2026) Browse files

74 changed files +7,101 −0

74 changed files
added .gitignore +33 −0
@@ -0,0 +1,33 @@
1 +# deps / builds
2 +node_modules/
3 +.next/
4 +dist/
5 +.turbo/
6 +*.tsbuildinfo
7 +
8 +# env & secrets — never commit (infra/.env.example is the template)
9 +.env
10 +.env.*
11 +!.env.example
12 +
13 +# raw data is immutable and fetched by script — never committed (CLAUDE.md §5)
14 +data/raw/*
15 +!data/raw/.gitkeep
16 +
17 +# derived data: only manifests are committed, not payloads
18 +data/derived/**/*.csv
19 +data/derived/**/*.parquet
20 +data/derived/**/*.jsonl
21 +!data/derived/**/manifest.json
22 +
23 +# python (apps/etl)
24 +__pycache__/
25 +.venv/
26 +*.egg-info/
27 +.pytest_cache/
28 +.ruff_cache/
29 +
30 +# misc
31 +.DS_Store
32 +coverage/
33 +playwright-report/
added .prettierrc +6 −0
@@ -0,0 +1,6 @@
1 +{
2 + "semi": true,
3 + "singleQuote": false,
4 + "trailingComma": "all",
5 + "printWidth": 100
6 +}
added CLAUDE.md +211 −0
@@ -0,0 +1,211 @@
1 +# CLAUDE.md — AI Risk Index Platform (www.airiskindex.io)
2 +
3 +This file provides guidance to Claude Code when working in this repository.
4 +
5 +---
6 +
7 +## 1. Project Overview
8 +
9 +**AI Risk Index** (airiskindex.io) is a public platform that scores occupations on their exposure to AI-driven automation, using a **task-based methodology** (not whole-occupation scoring). Every score must be transparent, versioned, and reproducible.
10 +
11 +**Core product promise:** "The most methodologically rigorous, fully transparent AI job-exposure index."
12 +
13 +### The three concepts (never confuse them)
14 +- **Exposure** — AI is technically capable of performing the task
15 +- **Substitution** — AI actually replaces the human performing it
16 +- **Augmentation** — AI assists the human, increasing productivity
17 +
18 +The composite index reports all three as separate sub-scores. Never collapse them into a single undifferentiated "risk" number in code, UI copy, or API responses without also exposing the sub-scores.
19 +
20 +### Scoring model (v1)
21 +Composite score 0–100 per occupation, computed from task-level scores weighted by task importance/frequency (O*NET weights). Dimensions:
22 +
23 +| Dimension | Key | Weight (v1) |
24 +|---|---|---|
25 +| Task automatability | `automatability` | 0.35 |
26 +| Current technical feasibility | `feasibility` | 0.20 |
27 +| Cost of substitution vs. wage | `cost_ratio` | 0.15 |
28 +| Adoption barriers (regulation, liability, human-contact requirement) | `barriers` | 0.20 |
29 +| Sector adoption velocity | `adoption_velocity` | 0.10 |
30 +
31 +- Weights live in `packages/scoring/src/weights.ts` — **never hardcode weights anywhere else**.
32 +- Every scoring release gets a semver version (`INDEX_VERSION` in `packages/scoring/src/version.ts`) and a changelog entry in `docs/methodology/CHANGELOG.md`.
33 +- Scores are stored with confidence intervals (`score_low`, `score`, `score_high`). UI must always be able to display uncertainty.
34 +
35 +### Data sources
36 +- **O*NET 30.x** database dumps (occupations + tasks + importance ratings) → `data/raw/onet/` — 30.3 as of May 2026, 31.0 expected late Aug 2026. ⚠️ 30.x renamed *Technology Skills → Software Skills* and split *Skills* into Essential/Transferable; ETL loaders must target the 30.x schema. License CC BY 4.0 (attribution required).
37 +- ESCO crosswalk for EU/France occupations (ROME codes) → `data/raw/esco/`
38 +- LLM-as-evaluator task ratings (see §6) → `data/derived/ratings/`
39 +- Expert panel (Delphi) validation overrides → `data/derived/expert_overrides/`
40 +
41 +Raw data is **never** edited in place. All transformations go through the ETL pipeline (`apps/etl`).
42 +
43 +---
44 +
45 +## 2. Tech Stack
46 +
47 +- **Monorepo:** pnpm workspaces + Turborepo
48 +- **Language:** TypeScript everywhere (strict mode). Python 3.12 only inside `apps/etl` for data science steps.
49 +- **Frontend:** Next.js 14 (App Router), React 18, Tailwind CSS, shadcn/ui, Recharts for visualizations
50 +- **API:** Next.js route handlers for public API (`/api/v1/*`) + tRPC for internal app calls
51 +- **Database:** PostgreSQL 16 (Prisma ORM). Read-heavy → materialized views for score lookups.
52 +- **Cache:** Redis (score lookups, rate limiting)
53 +- **Jobs:** BullMQ workers in `apps/worker` (score recomputation, LLM rating batches)
54 +- **Auth:** Auth.js (email magic link + OAuth). Public browsing requires no auth.
55 +- **Testing:** Vitest (unit), Playwright (e2e), pytest (ETL)
56 +- **Lint/format:** ESLint + Prettier, ruff for Python. CI fails on warnings.
57 +
58 +## 3. Repository Layout
59 +
60 +```
61 +airiskindex/
62 +├── apps/
63 +│ ├── web/ # Next.js app (site + public API routes)
64 +│ ├── worker/ # BullMQ background workers
65 +│ └── etl/ # Python data pipeline (raw → derived → DB)
66 +├── packages/
67 +│ ├── scoring/ # Pure TS scoring engine — NO I/O, fully deterministic
68 +│ ├── db/ # Prisma schema + client + seeds
69 +│ ├── ui/ # Shared React components
70 +│ └── config/ # Shared eslint/ts/tailwind configs
71 +├── data/
72 +│ ├── raw/ # Immutable source dumps (gitignored, fetched by script)
73 +│ └── derived/ # Pipeline outputs (versioned manifests committed)
74 +├── docs/
75 +│ └── methodology/ # Public methodology doc, changelog, sensitivity analyses
76 +├── infra/ # Deployment scripts, ngrok config, systemd units
77 +└── CLAUDE.md
78 +```
79 +
80 +## 4. Commands
81 +
82 +```bash
83 +pnpm install # install all workspaces
84 +pnpm dev # run web app on :3000 (+ worker with --filter)
85 +pnpm build # turbo build all
86 +pnpm test # vitest across packages
87 +pnpm test:e2e # playwright (requires pnpm dev running)
88 +pnpm lint && pnpm typecheck # must pass before any commit
89 +pnpm db:migrate # prisma migrate dev
90 +pnpm db:seed # seed occupations + demo scores
91 +pnpm score:recompute # full index recomputation (writes new INDEX_VERSION run)
92 +cd apps/etl && make pipeline # full ETL: raw → derived → DB load
93 +```
94 +
95 +Single test file: `pnpm vitest run packages/scoring/src/composite.test.ts`
96 +
97 +## 5. Coding Conventions
98 +
99 +- `packages/scoring` must stay **pure and deterministic**: no network, no DB, no `Date.now()`, no randomness. It takes typed inputs and returns typed scores. This is what makes the methodology auditable.
100 +- Every scoring function has property-based tests (fast-check) + snapshot tests against the published methodology examples in `docs/methodology/examples/`.
101 +- All user-facing risk language follows the tone guide: the product frames results as **adaptation guidance, not doom**. Avoid copy like "your job will disappear"; prefer "X% of tasks in this occupation are highly exposed".
102 +- API responses are versioned (`/api/v1/...`) and include `index_version` in every payload.
103 +- Money/wages: store as integer cents + ISO currency. Percentages: store as 0–1 floats, format only at the UI layer.
104 +- Never commit anything under `data/raw/`. Derived data commits only the manifest JSON (hashes + row counts), not the payloads.
105 +- Migrations: additive-first. Destructive migrations require a `-- DESTRUCTIVE` comment and a manual approval in PR review.
106 +- Accessibility: all charts need a data-table fallback (`<VisuallyHidden>` table) — this is a public-interest tool.
107 +
108 +## 6. LLM-as-Evaluator (task rating pipeline)
109 +
110 +Task automatability ratings are produced by an LLM rater in `apps/worker/src/raters/`, then validated by human experts.
111 +
112 +- Use the Anthropic API via the official SDK. Model names come from the `RATER_MODELS` env var (comma-separated list — **multi-model rating is required**: single-model LLM exposure ratings show up to 19× spread across frontier raters; see `docs/research/01-existing-indices.md` §7.2). Never hardcode model IDs in source.
113 +- Newer models (Sonnet 5 / Opus 5) reject the `temperature` parameter — rating variance comes from the multi-model panel, not sampling temperature.
114 +- Prompts live in `apps/worker/src/raters/prompts/*.md` and are versioned; a prompt change bumps `RATER_PROMPT_VERSION` and invalidates cached ratings.
115 +- Every rating stores: model, prompt version, raw response, parsed score, timestamp. Full audit trail, always.
116 +- Ratings are sampled (5%) for human review; disagreement > 1 point on the 5-point scale flags the task for the expert panel queue.
117 +- Batch jobs must be idempotent and resumable (BullMQ job IDs = deterministic hash of task_id + prompt version).
118 +- For current API details (batch endpoints, rate limits, model names), check https://platform.claude.com/docs (docs.claude.com redirects there) rather than relying on memory. A vetted snapshot lives in `docs/research/03-llm-rater-api.md`.
119 +
120 +## 7. Environments & Deployment
121 +
122 +### Environments
123 +- `local` — developer machine, SQLite optional shortcut is **not** allowed; always Postgres via Docker (`infra/docker-compose.dev.yml`).
124 +- `staging` — node **m3u96b**, exposed via **ngrok** (see below).
125 +- `production` — airiskindex.io (target: VPS/managed later; staging setup is the current deployment).
126 +
127 +### Deploying to node m3u96b (staging/current prod)
128 +
129 +The app currently runs on the self-hosted node `m3u96b` and is exposed publicly through an ngrok tunnel mapped to `www.airiskindex.io`.
130 +
131 +**Process layout on m3u96b:**
132 +- `airiskindex-web.service` (systemd) → `node apps/web/.next/standalone/server.js` on `127.0.0.1:3000`
133 +- `airiskindex-worker.service` → BullMQ worker
134 +- `postgres` + `redis` via Docker Compose (`infra/docker-compose.prod.yml`)
135 +- `ngrok.service` → tunnel `127.0.0.1:3000` → public edge
136 +
137 +**Deploy procedure (scripted in `infra/deploy.sh`):**
138 +```bash
139 +ssh m3u96b
140 +cd /srv/airiskindex
141 +git pull --ff-only origin main
142 +pnpm install --frozen-lockfile
143 +pnpm build
144 +pnpm db:migrate:deploy # prisma migrate deploy (no interactive)
145 +sudo systemctl restart airiskindex-web airiskindex-worker
146 +sudo systemctl status airiskindex-web --no-pager # verify healthy
147 +curl -fsS http://127.0.0.1:3000/api/v1/health # must return {"ok":true,...}
148 +```
149 +
150 +**ngrok configuration (`infra/ngrok.yml`, copied to `/etc/ngrok/ngrok.yml` on the node):**
151 +```yaml
152 +version: 3
153 +agent:
154 + authtoken: ${NGROK_AUTHTOKEN} # from env, never committed
155 +endpoints:
156 + - name: airiskindex
157 + url: https://www.airiskindex.io # requires custom domain configured in ngrok dashboard + CNAME
158 + upstream:
159 + url: 3000
160 +```
161 +- The custom domain must be added in the ngrok dashboard and DNS `CNAME` for `www.airiskindex.io` pointed at the ngrok edge target they provide. Verify with `dig CNAME www.airiskindex.io`.
162 +- ngrok runs as `ngrok.service` (systemd, `Restart=always`). Logs: `journalctl -u ngrok -f`.
163 +- Health rule: after every deploy, hit the public URL, not just localhost — tunnel failures are the most common outage cause.
164 +- Because the app sits behind ngrok, trust `X-Forwarded-*` headers: Next.js config already sets trusted proxy handling; do not remove it. Rate limiting keys off `x-forwarded-for` first IP.
165 +
166 +**Environment variables** (`/srv/airiskindex/.env`, template in `infra/.env.example`):
167 +```
168 +DATABASE_URL=postgresql://...
169 +REDIS_URL=redis://...
170 +ANTHROPIC_API_KEY=... # rater pipeline
171 +RATER_MODELS=... # comma-separated model IDs (multi-model rating panel)
172 +NGROK_AUTHTOKEN=...
173 +NEXTAUTH_URL=https://www.airiskindex.io
174 +NEXTAUTH_SECRET=...
175 +PUBLIC_BASE_URL=https://www.airiskindex.io
176 +```
177 +Never print, log, or commit secrets. `infra/.env.example` lists keys with empty values only.
178 +
179 +### Rollback
180 +```bash
181 +ssh m3u96b
182 +cd /srv/airiskindex
183 +git checkout <previous-tag>
184 +pnpm install --frozen-lockfile && pnpm build
185 +sudo systemctl restart airiskindex-web airiskindex-worker
186 +```
187 +DB rollbacks: only via forward-fix migrations. Nightly `pg_dump` to `/srv/backups` (retained 14 days) — verify the cron is alive when touching infra.
188 +
189 +## 8. Public API rules
190 +
191 +- `/api/v1/occupations` — list/search (paginated, cached 1h)
192 +- `/api/v1/occupations/:code` — full score breakdown incl. sub-scores, CI bounds, task list, `index_version`
193 +- `/api/v1/methodology` — machine-readable weights + version metadata
194 +- Rate limit: 60 req/min unauthenticated, 600 with API key. Return `429` with `Retry-After`.
195 +- Breaking changes require a new `/api/v2` — never mutate v1 response shapes.
196 +
197 +## 9. Methodology Integrity Rules (non-negotiable)
198 +
199 +1. Any change to weights, formulas, or rater prompts ⇒ bump `INDEX_VERSION`, add changelog entry, regenerate `docs/methodology/sensitivity/` outputs.
200 +2. Scores shown anywhere must be traceable to a stored computation run (`score_runs` table) — no ad-hoc numbers.
201 +3. Historical scores are immutable; recomputations create new runs, old runs remain queryable.
202 +4. The public methodology doc (`docs/methodology/METHODOLOGY.md`) is the source of truth; code comments link to its section anchors. If code and doc disagree, stop and flag it — do not silently pick one.
203 +
204 +## 10. When Working in This Repo, Claude Should
205 +
206 +- Run `pnpm typecheck && pnpm lint && pnpm test` before declaring any task done.
207 +- Touch `packages/scoring` only with accompanying tests and a methodology changelog note.
208 +- Ask before: destructive migrations, changing index weights, editing ngrok/systemd config, or anything that alters public API shapes.
209 +- Prefer small, reviewable commits with conventional-commit messages (`feat(scoring): ...`, `fix(api): ...`, `infra(deploy): ...`).
210 +- Keep UI copy aligned with the "adaptation, not doom" tone guide.
211 +- When uncertain about Anthropic API specifics (models, batch API, limits), consult the official docs instead of guessing.
added README.md +29 −0
@@ -0,0 +1,29 @@
1 +# AI Risk Index — airiskindex.io
2 +
3 +The most methodologically rigorous, fully transparent AI job-exposure index.
4 +Task-based scoring of occupations on AI-driven automation exposure, with three
5 +separate sub-scores per occupation — **exposure**, **substitution**, **augmentation** —
6 +each with confidence intervals, versioned methodology, and a public API.
7 +
8 +- Methodology (source of truth): [`docs/methodology/METHODOLOGY.md`](docs/methodology/METHODOLOGY.md)
9 +- Research corpus grounding v1: [`docs/research/`](docs/research/README.md)
10 +- Contributor rules: [`CLAUDE.md`](CLAUDE.md)
11 +
12 +## Quick start
13 +
14 +```bash
15 +pnpm install
16 +docker compose -f infra/docker-compose.dev.yml up -d # postgres 16 + redis
17 +cp infra/.env.example .env # fill values
18 +pnpm db:migrate && pnpm db:seed
19 +pnpm dev # web on :3000
20 +```
21 +
22 +Checks: `pnpm typecheck && pnpm lint && pnpm test`
23 +
24 +## Layout
25 +
26 +Monorepo (pnpm + Turborepo): `apps/web` (Next.js site + public API), `apps/worker`
27 +(BullMQ jobs incl. the multi-model LLM rater), `apps/etl` (Python pipeline),
28 +`packages/scoring` (pure, deterministic scoring engine), `packages/db` (Prisma),
29 +`packages/ui`, `packages/config`. See CLAUDE.md §3.
added apps/etl/Makefile +21 −0
@@ -0,0 +1,21 @@
1 +PYTHON ?= python3
2 +export PYTHONPATH := src
3 +
4 +.PHONY: pipeline download transform load test lint
5 +
6 +pipeline: download transform load
7 +
8 +download:
9 + $(PYTHON) -m airiskindex_etl.download_onet
10 +
11 +transform:
12 + @echo "TODO: raw -> derived transforms (task statements + importance ratings -> data/derived/)"
13 +
14 +load:
15 + @echo "TODO: derived -> Postgres load (occupations + tasks via Prisma-compatible schema)"
16 +
17 +test:
18 + $(PYTHON) -m pytest
19 +
20 +lint:
21 + ruff check .
added apps/etl/pyproject.toml +19 −0
@@ -0,0 +1,19 @@
1 +[project]
2 +name = "airiskindex-etl"
3 +version = "0.1.0"
4 +description = "AI Risk Index data pipeline: raw source dumps -> derived artifacts -> Postgres"
5 +requires-python = ">=3.12"
6 +dependencies = [
7 + "requests>=2.32",
8 + "pandas>=2.2",
9 +]
10 +
11 +[project.optional-dependencies]
12 +dev = ["pytest>=8", "ruff>=0.5"]
13 +
14 +[tool.ruff]
15 +line-length = 100
16 +target-version = "py312"
17 +
18 +[tool.pytest.ini_options]
19 +testpaths = ["tests"]
added apps/etl/src/airiskindex_etl/__init__.py +6 −0
@@ -0,0 +1,6 @@
1 +"""AI Risk Index ETL: raw source dumps -> derived artifacts -> Postgres.
2 +
3 +Raw data is never edited in place (CLAUDE.md §1); every derived artifact gets a
4 +manifest JSON (hashes + row counts) — only manifests are committed.
5 +Source catalogue with verified URLs: docs/research/02-data-sources.md.
6 +"""
added apps/etl/src/airiskindex_etl/download_onet.py +64 −0
@@ -0,0 +1,64 @@
1 +"""Fetch the O*NET database text dump into data/raw/onet/ (immutable).
2 +
3 +O*NET 30.x — CC BY 4.0, attribution required. 30.x renamed
4 +"Technology Skills" -> "Software Skills" and split Skills into
5 +Essential/Transferable (see docs/research/02-data-sources.md §O*NET).
6 +Check https://www.onetcenter.org/database.html for the current release
7 +(31.0 expected late Aug 2026) before bumping ONET_VERSION.
8 +"""
9 +
10 +from __future__ import annotations
11 +
12 +import hashlib
13 +import json
14 +import sys
15 +import zipfile
16 +from pathlib import Path
17 +
18 +import requests
19 +
20 +ONET_VERSION = "30_3"
21 +ONET_URL = f"https://www.onetcenter.org/dl_files/database/db_{ONET_VERSION}_text.zip"
22 +
23 +# Some sources (notably BLS OEWS) block non-browser user agents; use a plain
24 +# descriptive UA with contact info everywhere for consistency and courtesy.
25 +USER_AGENT = "airiskindex-etl/0.1 (https://www.airiskindex.io; data pipeline)"
26 +
27 +REPO_ROOT = Path(__file__).resolve().parents[4]
28 +RAW_DIR = REPO_ROOT / "data" / "raw" / "onet"
29 +
30 +
31 +def download() -> Path:
32 + RAW_DIR.mkdir(parents=True, exist_ok=True)
33 + archive = RAW_DIR / f"db_{ONET_VERSION}_text.zip"
34 + if archive.exists():
35 + print(f"already present: {archive}")
36 + return archive
37 +
38 + print(f"downloading {ONET_URL}")
39 + response = requests.get(ONET_URL, headers={"User-Agent": USER_AGENT}, timeout=300, stream=True)
40 + response.raise_for_status()
41 + with archive.open("wb") as fh:
42 + for chunk in response.iter_content(chunk_size=1 << 20):
43 + fh.write(chunk)
44 +
45 + with zipfile.ZipFile(archive) as zf:
46 + zf.extractall(RAW_DIR)
47 +
48 + manifest = {
49 + "source": ONET_URL,
50 + "version": ONET_VERSION,
51 + "sha256": hashlib.sha256(archive.read_bytes()).hexdigest(),
52 + "files": sorted(p.name for p in RAW_DIR.iterdir()),
53 + }
54 + (RAW_DIR / "manifest.json").write_text(json.dumps(manifest, indent=2))
55 + print(f"downloaded and extracted to {RAW_DIR}")
56 + return archive
57 +
58 +
59 +if __name__ == "__main__":
60 + try:
61 + download()
62 + except requests.RequestException as error:
63 + print(f"download failed: {error}", file=sys.stderr)
64 + sys.exit(1)
added apps/etl/tests/test_smoke.py +7 −0
@@ -0,0 +1,7 @@
1 +from airiskindex_etl.download_onet import ONET_URL, ONET_VERSION, RAW_DIR
2 +
3 +
4 +def test_onet_source_configuration() -> None:
5 + assert ONET_VERSION.startswith("30_")
6 + assert ONET_URL.endswith(f"db_{ONET_VERSION}_text.zip")
7 + assert RAW_DIR.parts[-3:] == ("data", "raw", "onet")
added apps/web/app/api/v1/health/route.ts +11 −0
@@ -0,0 +1,11 @@
1 +import { INDEX_VERSION } from "@airiskindex/scoring";
2 +
3 +export const dynamic = "force-dynamic";
4 +
5 +export function GET(): Response {
6 + return Response.json({
7 + ok: true,
8 + index_version: INDEX_VERSION,
9 + time: new Date().toISOString(),
10 + });
11 +}
added apps/web/app/api/v1/methodology/route.ts +27 −0
@@ -0,0 +1,27 @@
1 +import {
2 + DIMENSIONS,
3 + EXPOSURE_DIMENSIONS,
4 + HIGH_EXPOSURE_THRESHOLD,
5 + INDEX_VERSION,
6 + INVERTED_DIMENSIONS,
7 + WEIGHTS,
8 +} from "@airiskindex/scoring";
9 +
10 +export function GET(): Response {
11 + return Response.json(
12 + {
13 + index_version: INDEX_VERSION,
14 + dimensions: DIMENSIONS,
15 + weights: WEIGHTS,
16 + inverted_dimensions: [...INVERTED_DIMENSIONS],
17 + exposure_dimensions: EXPOSURE_DIMENSIONS,
18 + high_exposure_threshold: HIGH_EXPOSURE_THRESHOLD,
19 + scales: {
20 + ratings: "1-5 per task and dimension, multi-model LLM panel with expert overrides",
21 + scores: "0-100, each with low/score/high confidence bounds",
22 + },
23 + documentation: "https://www.airiskindex.io/methodology",
24 + },
25 + { headers: { "Cache-Control": "public, max-age=3600" } },
26 + );
27 +}
added apps/web/app/api/v1/occupations/[code]/route.ts +70 −0
@@ -0,0 +1,70 @@
1 +import { prisma } from "@airiskindex/db";
2 +import { INDEX_VERSION } from "@airiskindex/scoring";
3 +import type { NextRequest } from "next/server";
4 +
5 +export const dynamic = "force-dynamic";
6 +
7 +export async function GET(
8 + _request: NextRequest,
9 + { params }: { params: { code: string } },
10 +): Promise<Response> {
11 + try {
12 + const occupation = await prisma.occupation.findUnique({
13 + where: { code: params.code },
14 + include: {
15 + tasks: {
16 + select: { id: true, statement: true, importance: true },
17 + orderBy: { id: "asc" },
18 + },
19 + },
20 + });
21 + if (!occupation) {
22 + return Response.json({ error: "not_found" }, { status: 404 });
23 + }
24 +
25 + // Latest run's scores; historical runs stay queryable (CLAUDE.md §9).
26 + const latest = await prisma.occupationScore.findFirst({
27 + where: { occupationCode: occupation.code },
28 + orderBy: { run: { createdAt: "desc" } },
29 + include: { run: true },
30 + });
31 +
32 + return Response.json({
33 + index_version: latest?.run.indexVersion ?? INDEX_VERSION,
34 + occupation: {
35 + code: occupation.code,
36 + title: occupation.title,
37 + description: occupation.description,
38 + esco_uri: occupation.escoUri,
39 + rome_code: occupation.romeCode,
40 + median_wage_cents: occupation.medianWageCents,
41 + wage_currency: occupation.wageCurrency,
42 + },
43 + scores: latest
44 + ? {
45 + run_id: latest.runId,
46 + computed_at: latest.run.createdAt,
47 + substitution: {
48 + low: latest.substitutionLow,
49 + score: latest.substitution,
50 + high: latest.substitutionHigh,
51 + },
52 + exposure: {
53 + low: latest.exposureLow,
54 + score: latest.exposure,
55 + high: latest.exposureHigh,
56 + },
57 + augmentation: {
58 + low: latest.augmentationLow,
59 + score: latest.augmentation,
60 + high: latest.augmentationHigh,
61 + },
62 + highly_exposed_task_share: latest.highlyExposedTaskShare,
63 + }
64 + : null,
65 + tasks: occupation.tasks,
66 + });
67 + } catch {
68 + return Response.json({ error: "database_unavailable" }, { status: 503 });
69 + }
70 +}
added apps/web/app/api/v1/occupations/route.ts +40 −0
@@ -0,0 +1,40 @@
1 +import { prisma } from "@airiskindex/db";
2 +import { INDEX_VERSION } from "@airiskindex/scoring";
3 +import type { NextRequest } from "next/server";
4 +
5 +export const dynamic = "force-dynamic";
6 +
7 +export async function GET(request: NextRequest): Promise<Response> {
8 + const { searchParams } = new URL(request.url);
9 + const q = searchParams.get("q") ?? undefined;
10 + const page = Math.max(1, Number(searchParams.get("page") ?? "1") || 1);
11 + const perPage = Math.min(100, Math.max(1, Number(searchParams.get("per_page") ?? "25") || 25));
12 +
13 + const where = q
14 + ? {
15 + OR: [
16 + { title: { contains: q, mode: "insensitive" as const } },
17 + { code: { startsWith: q } },
18 + ],
19 + }
20 + : {};
21 +
22 + try {
23 + const [total, items] = await Promise.all([
24 + prisma.occupation.count({ where }),
25 + prisma.occupation.findMany({
26 + where,
27 + orderBy: { code: "asc" },
28 + skip: (page - 1) * perPage,
29 + take: perPage,
30 + select: { code: true, title: true },
31 + }),
32 + ]);
33 + return Response.json(
34 + { index_version: INDEX_VERSION, page, per_page: perPage, total, items },
35 + { headers: { "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=600" } },
36 + );
37 + } catch {
38 + return Response.json({ error: "database_unavailable" }, { status: 503 });
39 + }
40 +}
added apps/web/app/globals.css +3 −0
@@ -0,0 +1,3 @@
1 +@tailwind base;
2 +@tailwind components;
3 +@tailwind utilities;
added apps/web/app/layout.tsx +17 −0
@@ -0,0 +1,17 @@
1 +import type { Metadata } from "next";
2 +import type { ReactNode } from "react";
3 +import "./globals.css";
4 +
5 +export const metadata: Metadata = {
6 + title: "AI Risk Index — task-based AI exposure scores for every occupation",
7 + description:
8 + "Transparent, versioned, task-based scores of how occupations are exposed to AI — with separate exposure, substitution and augmentation sub-scores and confidence intervals. Adaptation guidance, not doom.",
9 +};
10 +
11 +export default function RootLayout({ children }: { children: ReactNode }): JSX.Element {
12 + return (
13 + <html lang="en">
14 + <body className="min-h-screen bg-white text-slate-900 antialiased">{children}</body>
15 + </html>
16 + );
17 +}
added apps/web/app/page.tsx +70 −0
@@ -0,0 +1,70 @@
1 +import { ScoreBandPill } from "@airiskindex/ui";
2 +import { INDEX_VERSION } from "@airiskindex/scoring";
3 +
4 +const CONCEPTS = [
5 + {
6 + name: "Exposure",
7 + description:
8 + "AI is technically capable of performing the task. High exposure alone does not mean job loss — it means the occupation's tasks are changing.",
9 + },
10 + {
11 + name: "Substitution",
12 + description:
13 + "AI actually replaces the human performing the task, once cost, adoption and real-world barriers are accounted for.",
14 + },
15 + {
16 + name: "Augmentation",
17 + description:
18 + "AI assists the human, increasing productivity. For most occupations today, measured usage is augmentation, not replacement.",
19 + },
20 +] as const;
21 +
22 +export default function HomePage(): JSX.Element {
23 + return (
24 + <main className="mx-auto max-w-3xl px-6 py-16">
25 + <p className="text-sm font-medium uppercase tracking-wide text-slate-500">
26 + AI Risk Index · methodology {INDEX_VERSION}
27 + </p>
28 + <h1 className="mt-2 text-4xl font-bold tracking-tight">
29 + How is your occupation exposed to AI — task by task?
30 + </h1>
31 + <p className="mt-4 text-lg text-slate-600">
32 + We score occupations from their individual tasks, report three separate sub-scores with
33 + confidence intervals, and publish every weight, prompt and formula. Built for adaptation
34 + planning — not headlines.
35 + </p>
36 +
37 + <div className="mt-8 flex flex-wrap gap-3">
38 + {/* Example occupation from the published methodology example */}
39 + <ScoreBandPill label="Substitution" low={40.9} score={56.7} high={68.4} />
40 + <ScoreBandPill label="Exposure" low={42.0} score={57.1} high={74.1} />
41 + <ScoreBandPill label="Augmentation" low={46.9} score={71.9} high={84.4} />
42 + </div>
43 +
44 + <section className="mt-12 grid gap-6 sm:grid-cols-3">
45 + {CONCEPTS.map((concept) => (
46 + <div key={concept.name} className="rounded-lg border border-slate-200 p-5">
47 + <h2 className="font-semibold">{concept.name}</h2>
48 + <p className="mt-2 text-sm text-slate-600">{concept.description}</p>
49 + </div>
50 + ))}
51 + </section>
52 +
53 + <section className="mt-12 text-sm text-slate-600">
54 + <h2 className="text-base font-semibold text-slate-900">Public API</h2>
55 + <ul className="mt-2 list-inside list-disc space-y-1">
56 + <li>
57 + <code>/api/v1/occupations</code> — search occupations
58 + </li>
59 + <li>
60 + <code>/api/v1/occupations/:code</code> — full score breakdown with sub-scores and CI
61 + bounds
62 + </li>
63 + <li>
64 + <code>/api/v1/methodology</code> — machine-readable weights and version metadata
65 + </li>
66 + </ul>
67 + </section>
68 + </main>
69 + );
70 +}
added apps/web/next-env.d.ts +5 −0
@@ -0,0 +1,5 @@
1 +/// <reference types="next" />
2 +/// <reference types="next/image-types/global" />
3 +
4 +// NOTE: This file should not be edited
5 +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
added apps/web/next.config.mjs +9 −0
@@ -0,0 +1,9 @@
1 +/** @type {import('next').NextConfig} */
2 +const nextConfig = {
3 + // Workspace packages ship TypeScript sources directly.
4 + transpilePackages: ["@airiskindex/scoring", "@airiskindex/db", "@airiskindex/ui"],
5 + // Deployed as `node .next/standalone/server.js` behind ngrok (CLAUDE.md §7).
6 + output: "standalone",
7 +};
8 +
9 +export default nextConfig;
added apps/web/package.json +28 −0
@@ -0,0 +1,28 @@
1 +{
2 + "name": "@airiskindex/web",
3 + "version": "0.0.0",
4 + "private": true,
5 + "scripts": {
6 + "dev": "next dev",
7 + "build": "next build",
8 + "start": "next start",
9 + "typecheck": "tsc --noEmit"
10 + },
11 + "dependencies": {
12 + "@airiskindex/db": "workspace:*",
13 + "@airiskindex/scoring": "workspace:*",
14 + "@airiskindex/ui": "workspace:*",
15 + "next": "^14.2.5",
16 + "react": "^18.3.1",
17 + "react-dom": "^18.3.1"
18 + },
19 + "devDependencies": {
20 + "@types/node": "^20.14.11",
21 + "@types/react": "^18.3.3",
22 + "@types/react-dom": "^18.3.0",
23 + "autoprefixer": "^10.4.19",
24 + "postcss": "^8.4.39",
25 + "tailwindcss": "^3.4.6",
26 + "typescript": "^5.5.4"
27 + }
28 +}
added apps/web/postcss.config.mjs +6 −0
@@ -0,0 +1,6 @@
1 +export default {
2 + plugins: {
3 + tailwindcss: {},
4 + autoprefixer: {},
5 + },
6 +};
added apps/web/tailwind.config.ts +9 −0
@@ -0,0 +1,9 @@
1 +import type { Config } from "tailwindcss";
2 +
3 +export default {
4 + content: ["./app/**/*.{ts,tsx}", "../../packages/ui/src/**/*.{ts,tsx}"],
5 + theme: {
6 + extend: {},
7 + },
8 + plugins: [],
9 +} satisfies Config;
added apps/web/tsconfig.json +10 −0
@@ -0,0 +1,10 @@
1 +{
2 + "extends": "../../packages/config/tsconfig/nextjs.json",
3 + "compilerOptions": {
4 + "paths": {
5 + "@/*": ["./*"]
6 + }
7 + },
8 + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
9 + "exclude": ["node_modules"]
10 +}
added apps/worker/package.json +25 −0
@@ -0,0 +1,25 @@
1 +{
2 + "name": "@airiskindex/worker",
3 + "version": "0.0.0",
4 + "private": true,
5 + "type": "module",
6 + "scripts": {
7 + "dev": "tsx watch src/index.ts",
8 + "start": "tsx src/index.ts",
9 + "score:recompute": "tsx src/scripts/recompute.ts",
10 + "typecheck": "tsc --noEmit"
11 + },
12 + "dependencies": {
13 + "@airiskindex/db": "workspace:*",
14 + "@airiskindex/scoring": "workspace:*",
15 + "@anthropic-ai/sdk": ">=0.32.1 <1",
16 + "bullmq": "^5.8.7",
17 + "ioredis": "^5.4.1",
18 + "zod": "^3.23.8"
19 + },
20 + "devDependencies": {
21 + "@types/node": "^20.14.11",
22 + "tsx": "^4.16.2",
23 + "typescript": "^5.5.4"
24 + }
25 +}
added apps/worker/src/config.ts +15 −0
@@ -0,0 +1,15 @@
1 +/**
2 + * Rater model panel — comma-separated model IDs from the environment, never
3 + * hardcoded (CLAUDE.md §6). Multi-model rating is required: single-model
4 + * exposure ratings show up to 19× spread across frontier raters
5 + * (docs/research/01-existing-indices.md §7.2).
6 + */
7 +export const RATER_MODELS: readonly string[] = (process.env.RATER_MODELS ?? "")
8 + .split(",")
9 + .map((value) => value.trim())
10 + .filter(Boolean);
11 +
12 +/** Bumping this invalidates cached ratings (CLAUDE.md §6). */
13 +export const RATER_PROMPT_VERSION = "v1";
14 +
15 +export const REDIS_URL = process.env.REDIS_URL ?? "redis://127.0.0.1:6379";
added apps/worker/src/index.ts +85 −0
@@ -0,0 +1,85 @@
1 +import { Queue, Worker } from "bullmq";
2 +import IORedis from "ioredis";
3 +import { prisma } from "@airiskindex/db";
4 +import { RATER_MODELS, RATER_PROMPT_VERSION, REDIS_URL } from "./config";
5 +import { ingestBatchResults, isBatchComplete, submitRatingBatch, type RatingTask } from "./raters/batch";
6 +import { ratingJobId } from "./raters/job-id";
7 +
8 +const connection = new IORedis(REDIS_URL, { maxRetriesPerRequest: null });
9 +
10 +export const ratingQueue = new Queue("rating", { connection });
11 +
12 +interface SubmitPayload {
13 + model: string;
14 +}
15 +
16 +interface PollPayload {
17 + model: string;
18 + batchId: string;
19 + jobIdToTaskId: Record<string, string>;
20 +}
21 +
22 +const worker = new Worker(
23 + "rating",
24 + async (job) => {
25 + if (job.name === "submit") {
26 + const { model } = job.data as SubmitPayload;
27 + const tasks = await prisma.task.findMany({
28 + where: {
29 + ratings: { none: { model, promptVersion: RATER_PROMPT_VERSION } },
30 + },
31 + include: { occupation: { select: { title: true } } },
32 + });
33 + if (tasks.length === 0) return { submitted: 0 };
34 +
35 + const ratingTasks: RatingTask[] = tasks.map((task) => ({
36 + taskId: task.id,
37 + occupationTitle: task.occupation.title,
38 + statement: task.statement,
39 + }));
40 + const batchId = await submitRatingBatch(ratingTasks, model);
41 + const jobIdToTaskId = Object.fromEntries(
42 + ratingTasks.map((task) => [ratingJobId(task.taskId, model, RATER_PROMPT_VERSION), task.taskId]),
43 + );
44 + await ratingQueue.add(
45 + "poll",
46 + { model, batchId, jobIdToTaskId } satisfies PollPayload,
47 + { jobId: `poll:${batchId}`, delay: 60_000, attempts: 60, backoff: { type: "fixed", delay: 60_000 } },
48 + );
49 + return { submitted: ratingTasks.length, batchId };
50 + }
51 +
52 + if (job.name === "poll") {
53 + const { model, batchId, jobIdToTaskId } = job.data as PollPayload;
54 + if (!(await isBatchComplete(batchId))) {
55 + throw new Error(`batch ${batchId} still processing`); // retried via backoff
56 + }
57 + return ingestBatchResults(batchId, model, new Map(Object.entries(jobIdToTaskId)));
58 + }
59 +
60 + throw new Error(`unknown job ${job.name}`);
61 + },
62 + { connection },
63 +);
64 +
65 +worker.on("failed", (job, error) => {
66 + console.error(`[worker] job ${job?.name}:${job?.id} failed:`, error.message);
67 +});
68 +
69 +async function enqueueSubmitJobs(): Promise<void> {
70 + if (RATER_MODELS.length < 2) {
71 + console.warn(
72 + "[worker] RATER_MODELS has fewer than 2 models — multi-model rating is required (CLAUDE.md §6).",
73 + );
74 + }
75 + for (const model of RATER_MODELS) {
76 + await ratingQueue.add(
77 + "submit",
78 + { model } satisfies SubmitPayload,
79 + { jobId: `submit:${model}:${RATER_PROMPT_VERSION}` },
80 + );
81 + }
82 +}
83 +
84 +enqueueSubmitJobs().catch((error) => console.error("[worker] enqueue failed:", error));
85 +console.log(`[worker] rating worker up — prompt ${RATER_PROMPT_VERSION}, models: ${RATER_MODELS.join(", ") || "(none)"}`);
added apps/worker/src/raters/batch.ts +121 −0
@@ -0,0 +1,121 @@
1 +import { readFileSync } from "node:fs";
2 +import Anthropic from "@anthropic-ai/sdk";
3 +import { prisma } from "@airiskindex/db";
4 +import { RATER_PROMPT_VERSION } from "../config";
5 +import { ratingJobId } from "./job-id";
6 +import { RATING_OUTPUT_JSON_SCHEMA, ratingResponseSchema } from "./schema";
7 +
8 +// Batch rating flow (docs/research/03-llm-rater-api.md): one Message Batches
9 +// request per task × model, custom_id = deterministic job ID, 1h-cached rubric
10 +// in the system block, structured JSON output. 50% batch discount; results
11 +// retrievable for 29 days.
12 +
13 +const anthropic = new Anthropic();
14 +
15 +const rubric = readFileSync(new URL("./prompts/task-rating-v1.md", import.meta.url), "utf8");
16 +
17 +export interface RatingTask {
18 + taskId: string;
19 + occupationTitle: string;
20 + statement: string;
21 +}
22 +
23 +export async function submitRatingBatch(tasks: RatingTask[], model: string): Promise<string> {
24 + const batch = await anthropic.messages.batches.create({
25 + requests: tasks.map((task) => ({
26 + custom_id: ratingJobId(task.taskId, model, RATER_PROMPT_VERSION),
27 + params: {
28 + model,
29 + max_tokens: 2048,
30 + system: [
31 + {
32 + type: "text" as const,
33 + text: rubric,
34 + cache_control: { type: "ephemeral" as const },
35 + },
36 + ],
37 + messages: [
38 + {
39 + role: "user" as const,
40 + content: `Occupation: ${task.occupationTitle}\nTask statement: ${task.statement}\n\nRate this task per the rubric.`,
41 + },
42 + ],
43 + // Structured outputs (GA). Kept as an untyped extension so the code
44 + // compiles across SDK versions; see docs/research/03-llm-rater-api.md.
45 + ...({
46 + output_config: {
47 + format: { type: "json_schema", schema: RATING_OUTPUT_JSON_SCHEMA },
48 + },
49 + } as Record<string, unknown>),
50 + } as unknown as Anthropic.Messages.MessageCreateParamsNonStreaming,
51 + })),
52 + });
53 + return batch.id;
54 +}
55 +
56 +export async function isBatchComplete(batchId: string): Promise<boolean> {
57 + const batch = await anthropic.messages.batches.retrieve(batchId);
58 + return batch.processing_status === "ended";
59 +}
60 +
61 +/**
62 + * Ingest batch results: store the raw response and the parsed per-dimension
63 + * scores (full audit trail, CLAUDE.md §6). Idempotent via the unique
64 + * (taskId, dimension, model, promptVersion, sampleIndex) constraint.
65 + */
66 +export async function ingestBatchResults(
67 + batchId: string,
68 + model: string,
69 + jobIdToTaskId: ReadonlyMap<string, string>,
70 +): Promise<{ ingested: number; failed: number }> {
71 + let ingested = 0;
72 + let failed = 0;
73 +
74 + for await (const entry of await anthropic.messages.batches.results(batchId)) {
75 + const taskId = jobIdToTaskId.get(entry.custom_id);
76 + if (!taskId || entry.result.type !== "succeeded") {
77 + failed += 1;
78 + continue;
79 + }
80 + const message = entry.result.message;
81 + const textBlock = message.content.find((block) => block.type === "text");
82 + if (!textBlock || textBlock.type !== "text") {
83 + failed += 1;
84 + continue;
85 + }
86 +
87 + const parsed = ratingResponseSchema.safeParse(JSON.parse(textBlock.text));
88 + if (!parsed.success) {
89 + failed += 1;
90 + continue;
91 + }
92 +
93 + for (const [dimension, value] of Object.entries(parsed.data)) {
94 + await prisma.taskRating.upsert({
95 + where: {
96 + taskId_dimension_model_promptVersion_sampleIndex: {
97 + taskId,
98 + dimension,
99 + model,
100 + promptVersion: RATER_PROMPT_VERSION,
101 + sampleIndex: 0,
102 + },
103 + },
104 + update: {},
105 + create: {
106 + taskId,
107 + dimension,
108 + model,
109 + promptVersion: RATER_PROMPT_VERSION,
110 + sampleIndex: 0,
111 + rating: value.rating,
112 + rationale: value.rationale,
113 + rawResponse: JSON.parse(JSON.stringify(message)),
114 + },
115 + });
116 + ingested += 1;
117 + }
118 + }
119 +
120 + return { ingested, failed };
121 +}
added apps/worker/src/raters/job-id.ts +14 −0
@@ -0,0 +1,14 @@
1 +import { createHash } from "node:crypto";
2 +
3 +/**
4 + * Deterministic ID used both as the BullMQ job ID and the Message Batches
5 + * `custom_id`, so retries and batch reconciliation are idempotent
6 + * (CLAUDE.md §6; docs/research/03-llm-rater-api.md). One request rates all
7 + * dimensions of one task with one model.
8 + */
9 +export function ratingJobId(taskId: string, model: string, promptVersion: string): string {
10 + return createHash("sha256")
11 + .update(`${taskId}:${model}:${promptVersion}`)
12 + .digest("hex")
13 + .slice(0, 32);
14 +}
added apps/worker/src/raters/prompts/task-rating-v1.md +70 −0
@@ -0,0 +1,70 @@
1 +# Task rating rubric — v1
2 +
3 +You are an expert rater for the AI Risk Index (airiskindex.io). You rate one
4 +occupational task statement (from O*NET) on six dimensions, each on a 1–5
5 +integer scale. Be calibrated and conservative: rate what current, generally
6 +available AI systems (including tool-using agents) can do **today**, not what
7 +might be possible soon. Justify every rating in one or two sentences grounded
8 +in the task statement itself.
9 +
10 +## Dimensions
11 +
12 +### automatability (1–5)
13 +Could current AI perform this task end-to-end with **at least 50% time saving
14 +at equal quality** (Eloundou et al. threshold)?
15 +- 1 — No meaningful part of the task can be automated today.
16 +- 3 — Roughly half of the task could be automated with significant setup.
17 +- 5 — The full task meets the ≥50%-time-saving-at-equal-quality bar with off-the-shelf systems.
18 +
19 +### feasibility (1–5)
20 +Do **deployed products demonstrably perform this task reliably today**? Distinguish
21 +conceivable from deployable: benchmark results and demos rate lower than
22 +production systems in real organizations.
23 +- 1 — No product does this; research-stage only.
24 +- 3 — Products exist but with material error rates or narrow scope.
25 +- 5 — Mature products perform this reliably in production at scale.
26 +
27 +### cost_ratio (1–5)
28 +Compare the AI cost per task-equivalent (inference + integration + oversight)
29 +to the loaded human wage for the same output.
30 +- 1 — AI is more expensive than the human, all-in.
31 +- 3 — Roughly comparable cost.
32 +- 5 — AI is at least an order of magnitude cheaper.
33 +
34 +### barriers (1–5) — NOTE: higher = MORE protected
35 +Strength of adoption barriers: licensing/authorization requirements, liability
36 +and error-cost asymmetry, regulatory coverage of the automation itself,
37 +human-contact requirement, organizational friction.
38 +- 1 — No meaningful barriers; nothing prevents substitution.
39 +- 3 — Some friction (oversight requirements, customer preference for humans).
40 +- 5 — Hard barriers: a licensed human must legally perform or sign off on the task.
41 +
42 +### adoption_velocity (1–5)
43 +How fast and deep are the sectors where this task occurs actually adopting AI
44 +(agents in production, measured displacement), per public adoption data?
45 +- 1 — Laggard sectors (small firms, physical, low digitization).
46 +- 3 — Middling adoption, pilots common, production rare.
47 +- 5 — Fast, deep adoption (information, finance, professional services patterns).
48 +
49 +### augmentation (1–5)
50 +Independently of replacement: does AI **assist** a human doing this task,
51 +raising their productivity? High augmentation and low automatability can
52 +coexist (assistive drafting for a task requiring human judgment).
53 +- 1 — AI offers no meaningful assistance.
54 +- 3 — Useful assistance on parts of the task.
55 +- 5 — AI transforms productivity on this task while the human stays in the loop.
56 +
57 +## Output
58 +
59 +Return ONLY a JSON object of this shape (no prose outside JSON):
60 +
61 +```json
62 +{
63 + "automatability": { "rating": 1, "rationale": "..." },
64 + "feasibility": { "rating": 1, "rationale": "..." },
65 + "cost_ratio": { "rating": 1, "rationale": "..." },
66 + "barriers": { "rating": 1, "rationale": "..." },
67 + "adoption_velocity": { "rating": 1, "rationale": "..." },
68 + "augmentation": { "rating": 1, "rationale": "..." }
69 +}
70 +```
added apps/worker/src/raters/schema.ts +45 −0
@@ -0,0 +1,45 @@
1 +import { z } from "zod";
2 +import { DIMENSIONS } from "@airiskindex/scoring";
3 +
4 +const RATED_DIMENSIONS = [...DIMENSIONS, "augmentation"] as const;
5 +
6 +const dimensionRating = z.object({
7 + rating: z.number().int().min(1).max(5),
8 + rationale: z.string(),
9 +});
10 +
11 +/** Parsed shape of one rater response (all six dimensions of one task). */
12 +export const ratingResponseSchema = z.object(
13 + Object.fromEntries(RATED_DIMENSIONS.map((dimension) => [dimension, dimensionRating])) as Record<
14 + (typeof RATED_DIMENSIONS)[number],
15 + typeof dimensionRating
16 + >,
17 +);
18 +
19 +export type RatingResponse = z.infer<typeof ratingResponseSchema>;
20 +
21 +/**
22 + * JSON schema for the structured-outputs feature (score as enum — numeric
23 + * min/max is unsupported there; docs/research/03-llm-rater-api.md).
24 + */
25 +export const RATING_OUTPUT_JSON_SCHEMA = {
26 + type: "object",
27 + additionalProperties: false,
28 + required: [...RATED_DIMENSIONS],
29 + properties: Object.fromEntries(
30 + RATED_DIMENSIONS.map((dimension) => [
31 + dimension,
32 + {
33 + type: "object",
34 + additionalProperties: false,
35 + required: ["rating", "rationale"],
36 + properties: {
37 + rating: { enum: [1, 2, 3, 4, 5] },
38 + rationale: { type: "string" },
39 + },
40 + },
41 + ]),
42 + ),
43 +} as const;
44 +
45 +export { RATED_DIMENSIONS };
added apps/worker/src/scripts/recompute.ts +136 −0
@@ -0,0 +1,136 @@
1 +import { prisma } from "@airiskindex/db";
2 +import {
3 + INDEX_VERSION,
4 + scoreOccupation,
5 + type RatingBand,
6 + type TaskInput,
7 + type TaskRatings,
8 +} from "@airiskindex/scoring";
9 +import { RATED_DIMENSIONS } from "../raters/schema";
10 +import { RATER_MODELS, RATER_PROMPT_VERSION } from "../config";
11 +
12 +// Full index recomputation (`pnpm score:recompute`): builds rating bands from
13 +// the multi-model panel (min/mean/max across models — METHODOLOGY.md §3),
14 +// applies expert overrides, scores every occupation, and writes ONE new
15 +// immutable ScoreRun. Historical runs are never mutated (CLAUDE.md §9).
16 +
17 +function bandFromPanel(values: number[]): RatingBand {
18 + const low = Math.min(...values);
19 + const high = Math.max(...values);
20 + const mid = values.reduce((sum, value) => sum + value, 0) / values.length;
21 + return { low, mid, high };
22 +}
23 +
24 +async function main(): Promise<void> {
25 + const occupations = await prisma.occupation.findMany({
26 + include: {
27 + tasks: {
28 + include: {
29 + ratings: { where: { promptVersion: RATER_PROMPT_VERSION } },
30 + overrides: true,
31 + },
32 + },
33 + },
34 + });
35 +
36 + const run = await prisma.scoreRun.create({
37 + data: {
38 + indexVersion: INDEX_VERSION,
39 + raterPromptVersion: RATER_PROMPT_VERSION,
40 + raterModels: [...RATER_MODELS],
41 + },
42 + });
43 +
44 + let scoredOccupations = 0;
45 + let skippedTasks = 0;
46 +
47 + for (const occupation of occupations) {
48 + const inputs: TaskInput[] = [];
49 +
50 + for (const task of occupation.tasks) {
51 + const bands: Partial<Record<(typeof RATED_DIMENSIONS)[number], RatingBand>> = {};
52 + let complete = true;
53 +
54 + for (const dimension of RATED_DIMENSIONS) {
55 + const override = task.overrides.find((entry) => entry.dimension === dimension);
56 + if (override) {
57 + bands[dimension] = {
58 + low: override.ratingLow,
59 + mid: override.ratingMid,
60 + high: override.ratingHigh,
61 + };
62 + continue;
63 + }
64 + const values = task.ratings
65 + .filter((entry) => entry.dimension === dimension)
66 + .map((entry) => entry.rating);
67 + if (values.length === 0) {
68 + complete = false;
69 + break;
70 + }
71 + bands[dimension] = bandFromPanel(values);
72 + }
73 +
74 + if (!complete) {
75 + skippedTasks += 1;
76 + continue;
77 + }
78 + inputs.push({
79 + taskId: task.id,
80 + importance: task.importance ?? undefined,
81 + ratings: bands as TaskRatings,
82 + });
83 + }
84 +
85 + if (inputs.length === 0) continue;
86 +
87 + const scores = scoreOccupation(inputs);
88 + await prisma.$transaction([
89 + prisma.occupationScore.create({
90 + data: {
91 + runId: run.id,
92 + occupationCode: occupation.code,
93 + substitutionLow: scores.substitution.low,
94 + substitution: scores.substitution.score,
95 + substitutionHigh: scores.substitution.high,
96 + exposureLow: scores.exposure.low,
97 + exposure: scores.exposure.score,
98 + exposureHigh: scores.exposure.high,
99 + augmentationLow: scores.augmentation.low,
100 + augmentation: scores.augmentation.score,
101 + augmentationHigh: scores.augmentation.high,
102 + highlyExposedTaskShare: scores.highlyExposedTaskShare,
103 + },
104 + }),
105 + ...scores.tasks.map((task) =>
106 + prisma.taskScore.create({
107 + data: {
108 + runId: run.id,
109 + taskId: task.taskId,
110 + substitutionLow: task.substitution.low,
111 + substitution: task.substitution.score,
112 + substitutionHigh: task.substitution.high,
113 + exposureLow: task.exposure.low,
114 + exposure: task.exposure.score,
115 + exposureHigh: task.exposure.high,
116 + augmentationLow: task.augmentation.low,
117 + augmentation: task.augmentation.score,
118 + augmentationHigh: task.augmentation.high,
119 + },
120 + }),
121 + ),
122 + ]);
123 + scoredOccupations += 1;
124 + }
125 +
126 + console.log(
127 + `Run ${run.id} (index ${INDEX_VERSION}, prompt ${RATER_PROMPT_VERSION}): scored ${scoredOccupations}/${occupations.length} occupations; skipped ${skippedTasks} unrated tasks.`,
128 + );
129 +}
130 +
131 +main()
132 + .catch((error) => {
133 + console.error(error);
134 + process.exitCode = 1;
135 + })
136 + .finally(() => prisma.$disconnect());
added apps/worker/tsconfig.json +4 −0
@@ -0,0 +1,4 @@
1 +{
2 + "extends": "../../packages/config/tsconfig/base.json",
3 + "include": ["src"]
4 +}
added data/derived/expert_overrides/.gitkeep +0 −0
added data/derived/ratings/.gitkeep +0 −0
added data/raw/.gitkeep +0 −0
added docs/methodology/CHANGELOG.md +19 −0
@@ -0,0 +1,19 @@
1 +# Methodology Changelog
2 +
3 +All notable changes to the scoring methodology. Every entry corresponds to an
4 +`INDEX_VERSION` (semver) in `packages/scoring/src/version.ts`.
5 +
6 +## [1.0.0] — UNRELEASED (draft)
7 +
8 +Initial methodology.
9 +
10 +- Task-based scoring on O*NET 30.x task statements, importance-weighted aggregation
11 + to occupations (O*NET-SOC 2019).
12 +- Five dimensions: automatability 0.35, feasibility 0.20, cost_ratio 0.15,
13 + barriers 0.20 (inverted), adoption_velocity 0.10.
14 +- Three sub-scores per occupation: exposure, substitution (headline composite),
15 + augmentation — augmentation rated separately per task, outside the composite.
16 +- Multi-model LLM rater panel (≥2 frontier models via `RATER_MODELS`); confidence
17 + bounds (`score_low`/`score_high`) derived from rater disagreement envelopes.
18 +- 5% human review sample; expert Delphi overrides replace LLM bands where triggered.
19 +- Grounding research: `docs/research/01-…04-*.md` (compiled 2026-08-05).
added docs/methodology/METHODOLOGY.md +173 −0
@@ -0,0 +1,173 @@
1 +# AI Risk Index — Methodology v1.0.0 (DRAFT)
2 +
3 +> Status: **draft, unreleased**. This document is the source of truth for the scoring
4 +> methodology (see CLAUDE.md §9). Code in `packages/scoring` links to the section
5 +> anchors below; if code and this document disagree, stop and flag it.
6 +>
7 +> Related work and evidence base: `docs/research/01-existing-indices.md` (indices),
8 +> `02-data-sources.md` (data), `04-landscape-and-evidence.md` (adoption & barriers evidence).
9 +
10 +---
11 +
12 +## 1. Principles <a name="principles"></a>
13 +
14 +1. **Task-based, not occupation-based.** Occupations are bundles of tasks with very
15 + different AI exposure; whole-occupation scoring (Frey & Osborne 2013) has a poor
16 + empirical record. Occupation scores are always *derived* from task scores
17 + (Arntz, Gregory & Zierahn 2016; Eloundou et al. 2024).
18 +2. **Three concepts, never collapsed** (see §5):
19 + - **Exposure** — AI is technically capable of performing the task.
20 + - **Substitution** — AI actually replaces the human performing it. This is the
21 + headline composite.
22 + - **Augmentation** — AI assists the human, increasing productivity.
23 + Realized labor-market effects concentrate in *automation-classified* usage, not
24 + augmentation (Brynjolfsson, Chandar & Chen 2025), so conflating the three is not
25 + just imprecise — it is empirically wrong.
26 +3. **Uncertainty is part of the score.** Every published score carries
27 + `score_low / score / score_high`. Interval width is driven primarily by
28 + disagreement between independent LLM raters (single-model ratings show up to a
29 + 19× spread in headline statistics across frontier models — Yin, Vu & Persico 2026)
30 + plus human-calibration error on the expert anchor set.
31 +4. **Fully reproducible.** Weights, formulas, prompt versions, rater model IDs and
32 + per-model ratings are all published. Every score traces to a stored
33 + `score_runs` row; historical runs are immutable.
34 +5. **Versioned.** Any change to weights, formulas, or rater prompts bumps
35 + `INDEX_VERSION` (semver) with a changelog entry and regenerated sensitivity outputs.
36 +
37 +## 2. Data <a name="data"></a>
38 +
39 +| Input | Source | Role |
40 +|---|---|---|
41 +| Occupations & task statements | O*NET 30.x (O*NET-SOC 2019 taxonomy), CC BY 4.0 | Unit of analysis (~18k tasks, ~900 data-level occupations) |
42 +| Task importance weights | O*NET Task Ratings (IM scale 1–5) | Aggregation weights (§6) |
43 +| EU/France occupations | ESCO v1.2.x + official ESCO↔O*NET crosswalk; ROME 4.0 | Crosswalked scores (crosswalk loss documented per occupation) |
44 +| Wages | BLS OEWS (latest May release); Eurostat SES; INSEE | `cost_ratio` denominator |
45 +| Adoption data | Census BTOS AI supplement, Ramp AI Index, Anthropic Economic Index (Hugging Face), Challenger reports | `adoption_velocity` inputs (§4.5) |
46 +| Human anchor ratings | Expert Delphi panel (`data/derived/expert_overrides/`) | LLM-rater calibration (§3) |
47 +
48 +Raw dumps are immutable (`data/raw/`, gitignored); all transformations go through
49 +`apps/etl`; derived artifacts commit only manifests (hashes + row counts).
50 +
51 +## 3. Task rating (LLM-as-evaluator) <a name="task-rating"></a>
52 +
53 +Each O*NET task statement is rated on a **5-point scale** per dimension (§4) by a
54 +**panel of ≥2 (target 3) frontier LLMs** (`RATER_MODELS`), using versioned rubric
55 +prompts (`apps/worker/src/raters/prompts/`). Per task × dimension:
56 +
57 +- `rating_mid` = mean of panel ratings;
58 +- `rating_low` / `rating_high` = min / max of panel ratings (rater-disagreement band).
59 +
60 +Rubric anchors follow the citable standards: automatability uses the Eloundou et al.
61 +"≥50% time saving at equal quality" threshold, decomposed into named criteria
62 +(SML-style multi-criterion rubric); ratings require structured justifications and are
63 +stored with model ID, prompt version and raw response (full audit trail).
64 +
65 +**Calibration:** 5% of ratings are sampled for human review; panel-vs-human
66 +disagreement > 1 point routes the task to the expert queue. Expert overrides replace
67 +the LLM band for that task and are flagged in the API output.
68 +
69 +## 4. Dimensions <a name="dimensions"></a>
70 +
71 +Composite weights live in `packages/scoring/src/weights.ts` and are published at
72 +`/api/v1/methodology`. Weights sum to 1.0.
73 +
74 +| Dimension | Key | Weight | Orientation | Rubric anchor (rating of 5 means…) |
75 +|---|---|---|---|---|
76 +| Task automatability | `automatability` | 0.35 | direct | Current AI (incl. tooling/agents) can do the task with ≥50% time saving at equal quality |
77 +| Current technical feasibility | `feasibility` | 0.20 | direct | Deployed products demonstrably perform this task reliably today (not merely conceivable) |
78 +| Cost of substitution vs. wage | `cost_ratio` | 0.15 | direct | AI cost per task-equivalent ≪ loaded human wage for the same output |
79 +| Adoption barriers | `barriers` | 0.20 | **inverted** | Strong barriers: licensing/liability/regulation/human-contact requirements block substitution |
80 +| Sector adoption velocity | `adoption_velocity` | 0.10 | direct | Occupation's dominant sectors adopt AI fast and deep (agents in production, measured displacement) |
81 +
82 +Orientation: a *direct* dimension's higher rating increases substitution pressure; an
83 +*inverted* dimension's higher rating decreases it. `barriers` is the only inverted
84 +dimension in v1. Orientation is encoded once, in `packages/scoring/src/weights.ts`,
85 +next to the weights.
86 +
87 +Additionally, **`augmentation`** is rated per task on the same 5-point scale (does AI
88 +assist the human on this task, raising productivity without replacing them?). It is
89 +**not** part of the substitution composite; it feeds the augmentation sub-score (§5).
90 +
91 +### 4.4 `barriers` components <a name="barriers"></a>
92 +Rated against five named criteria (see research doc 04): licensing/authorization
93 +requirement (0.30), liability & error-cost asymmetry (0.25), regulatory-process
94 +coverage (0.20, jurisdiction-specific), human-contact requirement (0.15),
95 +organizational friction (0.10). Pre-2022 null results (OECD 2021; Acemoglu et al.
96 +2022) show barriers dominate short-run outcomes — hence the 0.20 weight.
97 +
98 +### 4.5 `adoption_velocity` components <a name="adoption-velocity"></a>
99 +Grounded in *measured* adoption, not forecasts: sector AI-use rate employment-weighted
100 +(0.35), sector adoption momentum (0.20), agentic deployment depth (0.20), realized
101 +displacement intensity (0.15), occupation-level usage intensity from the Anthropic
102 +Economic Index (0.10). Refreshed each index release; sources are public and dated.
103 +
104 +## 5. Scoring formulas <a name="formulas"></a>
105 +
106 +All formulas are implemented, pure and deterministic, in `packages/scoring`.
107 +Ratings `r ∈ [1,5]` normalize to pressure `p ∈ [0,1]`:
108 +
109 +```
110 +p = (r − 1) / 4 (direct dimensions)
111 +p = 1 − (r − 1) / 4 (inverted dimensions)
112 +```
113 +
114 +Per task:
115 +
116 +```
117 +substitution_task = 100 · Σ_d w_d · p_d (all five dimensions)
118 +exposure_task = 100 · (w_auto·p_auto + w_feas·p_feas) / (w_auto + w_feas)
119 +augmentation_task = 100 · p_augmentation
120 +```
121 +
122 +Confidence bounds: `score_low` is computed with each dimension's
123 +pressure-minimizing rating bound (for direct dimensions the low rating; for inverted
124 +dimensions the **high** rating), `score_high` symmetrically. Bounds are therefore
125 +worst/best-case envelopes over rater disagreement, and `low ≤ score ≤ high` always
126 +holds.
127 +
128 +## 6. Aggregation to occupations <a name="aggregation"></a>
129 +
130 +Occupation scores are the **importance-weighted mean** of task scores, weights from
131 +O*NET Task Ratings importance (IM, 1–5), normalized to sum to 1 within the
132 +occupation. Applied identically to `low`, `score` and `high`. Tasks lacking
133 +importance ratings receive the occupation-mean importance.
134 +
135 +The API additionally reports the share of tasks with `substitution_task ≥ 70`
136 +("highly exposed task share") — this, not the composite alone, is the preferred
137 +headline in UI copy ("X% of tasks in this occupation are highly exposed").
138 +
139 +## 7. Versioning & runs <a name="versioning"></a>
140 +
141 +- `INDEX_VERSION` (semver) in `packages/scoring/src/version.ts`.
142 +- MAJOR: formula/weight changes. MINOR: data-source version bumps (new O*NET release,
143 + new adoption data). PATCH: recomputation with refreshed adoption inputs, prompt
144 + clarifications that don't change the rubric semantics.
145 +- Every computation writes a `score_runs` row (index version, prompt version, rater
146 + models). Old runs stay queryable forever; the API serves the latest by default and
147 + any run on request.
148 +
149 +## 8. Validation & sensitivity <a name="validation"></a>
150 +
151 +Published with every MAJOR/MINOR release under `docs/methodology/sensitivity/`:
152 +
153 +1. **Convergent validity:** Spearman correlation of our occupation scores against
154 + Felten AIOE, Eloundou β, and ILO WP140 gradients (ρ ≈ 0.84 between independent
155 + modern methodologies is the reference bar).
156 +2. **Rater stability:** cross-model agreement distribution; occupations with the
157 + widest bands flagged in-product.
158 +3. **Outcome tracking:** correlation against the Stanford "Canaries" dashboard
159 + (entry-level employment in exposed occupations) and AEI usage shares — exposure
160 + indices individually explain <11% of realized unemployment risk (Frank, Ahn &
161 + Moro 2025), so we report outcome tracking honestly rather than claiming prediction.
162 +4. **Weight sensitivity:** composite rank stability under ±25% perturbation of each
163 + weight.
164 +
165 +## 9. Known limitations <a name="limitations"></a>
166 +
167 +- LLM raters co-evolve with the technology they measure (the "ruler" problem);
168 + multi-model panels bound but do not eliminate this.
169 +- Sector-level adoption inputs are priors, corrected by occupation-level usage data.
170 +- ESCO/ROME crosswalked scores inherit crosswalk loss; flagged per occupation.
171 +- Scores describe *tasks as currently constituted*; occupations reorganize.
172 +- This index measures exposure and substitution *pressure*, not certainty of job
173 + loss. Product copy follows the adaptation-not-doom tone guide accordingly.
added docs/methodology/examples/example-analyst.json +74 −0
@@ -0,0 +1,74 @@
1 +{
2 + "$comment": "Worked example for METHODOLOGY.md §5-6. Hand-computed with v1 weights (automatability .35, feasibility .20, cost_ratio .15, barriers .20 inverted, adoption_velocity .10). packages/scoring snapshot tests assert against `expected` to 3 decimal places. Any change here requires an INDEX_VERSION bump.",
3 + "occupation": {
4 + "code": "99-9999.00",
5 + "title": "Example Analyst (synthetic)"
6 + },
7 + "tasks": [
8 + {
9 + "taskId": "T1",
10 + "importance": 4,
11 + "ratings": {
12 + "automatability": { "low": 3, "mid": 4, "high": 5 },
13 + "feasibility": { "low": 4, "mid": 4, "high": 5 },
14 + "cost_ratio": { "low": 3, "mid": 3, "high": 4 },
15 + "barriers": { "low": 2, "mid": 2, "high": 3 },
16 + "adoption_velocity": { "low": 3, "mid": 4, "high": 4 },
17 + "augmentation": { "low": 4, "mid": 5, "high": 5 }
18 + }
19 + },
20 + {
21 + "taskId": "T2",
22 + "importance": 3,
23 + "ratings": {
24 + "automatability": { "low": 1, "mid": 2, "high": 2 },
25 + "feasibility": { "low": 2, "mid": 2, "high": 3 },
26 + "cost_ratio": { "low": 2, "mid": 2, "high": 2 },
27 + "barriers": { "low": 4, "mid": 4, "high": 5 },
28 + "adoption_velocity": { "low": 2, "mid": 3, "high": 3 },
29 + "augmentation": { "low": 2, "mid": 3, "high": 4 }
30 + }
31 + },
32 + {
33 + "taskId": "T3",
34 + "importance": 1,
35 + "ratings": {
36 + "automatability": { "low": 5, "mid": 5, "high": 5 },
37 + "feasibility": { "low": 2, "mid": 3, "high": 4 },
38 + "cost_ratio": { "low": 4, "mid": 4, "high": 5 },
39 + "barriers": { "low": 1, "mid": 1, "high": 2 },
40 + "adoption_velocity": { "low": 4, "mid": 5, "high": 5 },
41 + "augmentation": { "low": 1, "mid": 2, "high": 3 }
42 + }
43 + }
44 + ],
45 + "expected": {
46 + "taskScores": [
47 + {
48 + "taskId": "T1",
49 + "substitution": { "low": 55.0, "score": 71.25, "high": 88.75 },
50 + "exposure": { "low": 59.0909, "score": 75.0, "high": 100.0 },
51 + "augmentation": { "low": 75.0, "score": 100.0, "high": 100.0 }
52 + },
53 + {
54 + "taskId": "T2",
55 + "substitution": { "low": 11.25, "score": 27.5, "high": 32.5 },
56 + "exposure": { "low": 9.0909, "score": 25.0, "high": 34.0909 },
57 + "augmentation": { "low": 25.0, "score": 50.0, "high": 75.0 }
58 + },
59 + {
60 + "taskId": "T3",
61 + "substitution": { "low": 73.75, "score": 86.25, "high": 95.0 },
62 + "exposure": { "low": 72.7273, "score": 81.8182, "high": 90.9091 },
63 + "augmentation": { "low": 0.0, "score": 25.0, "high": 50.0 }
64 + }
65 + ],
66 + "occupation": {
67 + "taskWeights": [0.5, 0.375, 0.125],
68 + "substitution": { "low": 40.9375, "score": 56.71875, "high": 68.4375 },
69 + "exposure": { "low": 42.0455, "score": 57.1023, "high": 74.1477 },
70 + "augmentation": { "low": 46.875, "score": 71.875, "high": 84.375 },
71 + "highlyExposedTaskShare": 0.6667
72 + }
73 + }
74 +}
added docs/research/01-existing-indices.md +406 −0
@@ -0,0 +1,406 @@
1 +# Existing AI Job-Exposure / Automation-Risk Indices and Methodologies
2 +
3 +**Research memo — AI Risk Index (airiskindex.io)**
4 +**Date compiled:** 2026-08-05 (web research current to August 2026)
5 +**Scope:** All major academic and industry indices measuring occupational exposure to AI/automation, their methodologies, criticisms, empirical validation, and lessons for the airiskindex.io v1 scoring model.
6 +
7 +---
8 +
9 +## Table of Contents
10 +
11 +1. [First wave: pre-generative-AI automation risk (2013–2019)](#1-first-wave)
12 +2. [Second wave: AI-specific exposure measures (2018–2021)](#2-second-wave)
13 +3. [Third wave: LLM/generative-AI exposure (2023–2024)](#3-third-wave)
14 +4. [Institutional indices: ILO, OECD, IMF (2023–2026)](#4-institutional-indices)
15 +5. [Industry & consultancy estimates](#5-industry-estimates)
16 +6. [Usage-based measures: Anthropic Economic Index & OpenAI (2025–2026)](#6-usage-based-measures)
17 +7. [Fourth wave: 2025–2026 indices and meta-critiques](#7-fourth-wave)
18 +8. [Empirical validation: do exposure scores predict real outcomes?](#8-empirical-validation)
19 +9. [Master comparison table](#9-comparison-table)
20 +10. [Implications for airiskindex.io v1 methodology](#10-implications)
21 +
22 +---
23 +
24 +<a name="1-first-wave"></a>
25 +## 1. First wave: pre-generative-AI automation risk (2013–2019)
26 +
27 +### 1.1 Frey & Osborne — "The Future of Employment" (2013 working paper; 2017 published)
28 +
29 +- **Authors/year:** Carl Benedikt Frey & Michael A. Osborne (Oxford Martin School). Working paper Sept 2013; published in *Technological Forecasting and Social Change* 114 (2017): 254–280.
30 +- **Unit of analysis:** Whole occupations (702 SOC occupations).
31 +- **Methodology:**
32 + - ML experts hand-labeled ~70 occupations as automatable (1) or not (0) at a workshop.
33 + - Identified three "engineering bottlenecks" to computerisation: **perception & manipulation**, **creative intelligence**, **social intelligence**, operationalized via 9 O*NET variables (e.g., finger dexterity, originality, social perceptiveness, persuasion, negotiation, assisting/caring for others, cramped work spaces).
34 + - A **Gaussian process classifier** trained on the 70 labels extrapolated a "probability of computerisation" (0–1) to all 702 occupations.
35 + - Occupations bucketed: high risk (p > 0.7), medium (0.3–0.7), low (p < 0.3).
36 +- **Key numbers:** **47% of US employment** at "high risk" of computerisation "over the next decade or two" (i.e., roughly by 2030). Transportation, logistics, office/administrative support, and production occupations most at risk.
37 +- **Criticisms (extensive):**
38 + - **Occupation-level, not task-level:** treats occupations as monolithic. Arntz et al. (2016) showed within-occupation task heterogeneity slashes the estimate to ~9%.
39 + - **Technical capability ≠ adoption:** no economics (cost, wages, regulation, preferences) in the model.
40 + - **Subjective training labels** from a small expert workshop; only ~70 seed labels drive all 702 predictions.
41 + - **Model-selection sensitivity:** "The Future of Employment Revisited" (arXiv:2104.13747) shows automation forecasts swing heavily with classifier choice on the same labels.
42 + - **Poor ex-post predictive record:** occupations flagged high-risk did not experience differential employment declines through the late 2010s (see §8; Frank, Ahn & Moro 2025 find F&O scores explain <3% of unemployment-risk variation individually).
43 +- **URLs:**
44 + - Published paper: https://doi.org/10.1016/j.techfore.2016.08.019 (PDF mirror: http://reparti.free.fr/freyosborne17.pdf)
45 + - Oxford Martin 2013 working paper: https://www.oxfordmartin.ox.ac.uk/downloads/academic/The_Future_of_Employment.pdf
46 + - Critique (Melbourne Institute): https://melbourneinstitute.unimelb.edu.au/__data/assets/pdf_file/0005/3197111/wp2019n10.pdf
47 + - Critique (model selection): https://arxiv.org/abs/2104.13747
48 +
49 +### 1.2 Arntz, Gregory & Zierahn — OECD task-based critique (2016)
50 +
51 +- **Authors/year:** Melanie Arntz, Terry Gregory, Ulrich Zierahn (ZEW/OECD). *"The Risk of Automation for Jobs in OECD Countries"*, OECD Social, Employment and Migration Working Paper No. 189 (2016); follow-up "Revisiting the risk of automation" in *Economics Letters* 159 (2017).
52 +- **Unit of analysis:** Individual workers' **task bundles** (PIAAC Survey of Adult Skills microdata), not occupation averages.
53 +- **Methodology:** Transferred Frey–Osborne occupation-level risk to individual workers, then re-estimated risk as a function of each worker's *actual reported tasks* (PIAAC), allowing within-occupation heterogeneity. High risk = automatability > 70%.
54 +- **Key numbers:** Only **~9% of jobs across 21 OECD countries** at high risk (US 9%, Germany 12%, Korea 6%) — versus 47% under F&O.
55 +- **Significance:** Founded the **task-based paradigm** that every serious index since has adopted — including airiskindex.io. Key insight: workers in the "same" occupation do different task mixes; scoring must start at the task level.
56 +- **Criticisms:** Still anchored on F&O's original subjective labels; PIAAC task self-reports are coarse; may *understate* risk if task bundles themselves adjust post-automation.
57 +- **URLs:**
58 + - OECD WP 189: https://doi.org/10.1787/5jlz9h56dvq7-en
59 + - Economics Letters 2017: https://doi.org/10.1016/j.econlet.2017.07.001
60 +
61 +### 1.3 Brynjolfsson, Mitchell & Rock — Suitability for Machine Learning (SML) (2017–2018)
62 +
63 +- **Authors/year:** Erik Brynjolfsson, Tom Mitchell, Daniel Rock. "What Can Machine Learning Do? Workforce Implications" (*Science*, 2017); "What Can Machines Learn, and What Does It Mean for Occupations and the Economy?" (*AEA Papers & Proceedings* 108, 2018: 43–47).
64 +- **Unit of analysis:** O*NET tasks (18,156 tasks; ~2,069 Detailed Work Activities), aggregated to ~950 occupations.
65 +- **Methodology:**
66 + - A **23-question rubric** capturing what (then-current, supervised) ML can do — e.g., mapping well-defined inputs to outputs, tolerance for error, no long chains of reasoning, digital data availability, no need for detailed physical manipulation.
67 + - Each task scored 1–5 per question; rubric validated by ML experts, then scaled via CrowdFlower crowd workers; aggregated to task SML then occupation SML (task-importance weighted).
68 +- **Key findings:** (1) ML affects *different* occupations than earlier automation waves; (2) most occupations have at least some high-SML tasks; (3) **almost no occupation is fully automatable**; (4) capturing value requires **task re-bundling / job redesign**.
69 +- **Criticisms:** Rubric was tuned to pre-LLM supervised ML (weak on generation, reasoning, dialogue); crowd ratings noisy; scores never strongly validated against outcomes (explains ~0–3% of unemployment-risk variation individually per Frank et al. 2025).
70 +- **Relevance to us:** Direct methodological ancestor of a **multi-criterion task rubric** — our `automatability` + `feasibility` split echoes SML's separation of "could ML do it" from "is it practical".
71 +- **URLs:**
72 + - AEA P&P: https://www.aeaweb.org/articles?id=10.1257/pandp.20181019
73 + - SSRN: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3224100
74 + - Replication data: https://www.openicpsr.org/openicpsr/project/114436
75 + - WorldSML rubric (Stanford Digital Economy Lab, ongoing): https://digitaleconomy.stanford.edu/research/suitability-for-machine-learning-rubric-worldsml/
76 +
77 +---
78 +
79 +<a name="2-second-wave"></a>
80 +## 2. Second wave: AI-specific exposure measures (2018–2021)
81 +
82 +### 2.1 Webb (2020) — Patent-based exposure
83 +
84 +- **Author/year:** Michael Webb (Stanford). "The Impact of Artificial Intelligence on the Labor Market" (SSRN 3482150, Nov 2019/2020; still unpublished but heavily cited).
85 +- **Unit of analysis:** O*NET task descriptions × patent text; aggregated to occupations.
86 +- **Methodology:**
87 + - Selected AI patents (~16,400) by keyword; dependency-parsed titles to extract **verb–object pairs** (~8,000 pairs, e.g., "diagnose disease", "detect fraud").
88 + - Extracted verb–object pairs from O*NET task statements; scored each task by the frequency with which its verb-object pairs appear in AI patents.
89 + - Occupation score = task-importance-weighted average; reported as **exposure percentiles**.
90 + - **Built-in validation strategy:** applied the same method to *robots* and *software* patents and showed those historical exposure measures predicted realized employment/wage declines in exposed occupations — then applied it to AI.
91 +- **Key findings:** AI (unlike robots/software) exposes **high-skilled, high-wage, older** workers most: e.g., clinical lab technicians, chemical engineers, optometrists, radiologic technicians. Robots hit low-skill physical work; software hit mid-skill routine work.
92 +- **Criticisms:** Patents lag and imperfectly reflect deployable capability (pre-LLM corpus, so misses generative AI entirely); verb-object matching is crude semantics; no distinction between substitution and augmentation.
93 +- **URLs:**
94 + - Paper: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3482150 / https://www.michaelwebb.co/webb_ai.pdf
95 + - Brookings explainer: https://www.brookings.edu/articles/how-patents-can-tell-us-what-jobs-ai-is-poised-to-disrupt/
96 +
97 +### 2.2 Felten, Raj & Seamans — AI Occupational Exposure (AIOE) (2018, 2021, 2023)
98 +
99 +- **Authors/year:** Edward Felten (Princeton), Manav Raj (Wharton), Robert Seamans (NYU). "Occupational, Industry, and Geographic Exposure to Artificial Intelligence: A Novel Dataset and Its Potential Uses", *Strategic Management Journal* 42(12), 2021: 2195–2217. Generative-AI update: "Occupational Heterogeneity in Exposure to Generative AI" (SSRN 4414065, April 2023).
100 +- **Unit of analysis:** 52 O*NET **abilities** (not tasks) × 10 AI application areas; aggregated to occupations (also industry AIIE and county-level geography).
101 +- **Methodology:**
102 + - 10 AI applications from the EFF AI Progress Measurement project (image recognition, language modeling, translation, speech recognition, abstract strategy games, etc.).
103 + - Amazon Mechanical Turk crowd workers rated **relatedness** of each application to each of 52 O*NET abilities → ability-level exposure = sum of relatedness scores.
104 + - Occupation AIOE = weighted sum of ability exposures using O*NET ability **importance and prevalence** weights.
105 + - 2023 generative-AI variant re-weights toward language modeling and image generation: top exposed = telemarketers, then post-secondary teachers (languages, history, law), sociologists, judges.
106 +- **Key properties:** Continuous z-scored index; famously **positively correlated with wages and education** (white-collar exposure) — opposite sign to Frey–Osborne.
107 +- **Criticisms:** Ability-level (even further from tasks than occupations); MTurk relatedness judgments are lay opinions; "exposure" deliberately **neutral between substitution and augmentation** (authors are explicit about this); static.
108 +- **Data:** Public GitHub (occupation-, industry-, geography-level scores): https://github.com/AIOE-Data/AIOE
109 +- **Adoption:** The **IMF** (Cazzaniga et al. 2024) and Pew (2023) analyses build directly on AIOE; the ECB European work (Albanesi et al.) uses AIOE + Webb.
110 +- **URLs:**
111 + - SMJ paper: https://doi.org/10.1002/smj.3286
112 + - SSRN 2021: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3822412
113 + - GenAI variant: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4414065
114 +
115 +### 2.3 Other second-wave measures (brief)
116 +
117 +- **Georgieff & Hyee (OECD 2021), "Artificial intelligence and employment: New cross-country evidence"** — applied Felten-style AIOE to PIAAC across 23 OECD countries; most-exposed: business professionals, managers, chief executives, science/engineering professionals. Found **no negative employment relationship 2012–2019**; in high-computer-use occupations, higher AI exposure correlated with *higher* employment growth. URL: https://doi.org/10.1787/c2c1d276-en
118 +- **Lassébie & Quintini (OECD 2022), "What skills and abilities can automation technologies replicate?"** — expert survey on automatability of ~100 O*NET skills/abilities; basis for OECD Employment Outlook 2023 statement that occupations at highest risk of automation account for **~27% of OECD employment**. URL: https://doi.org/10.1787/646aad77-en ; Employment Outlook 2023 AI chapter: https://www.oecd.org/en/publications/oecd-employment-outlook-2023_08785bba-en.html
119 +- **Tolan et al. (2021, JRC/EC)** — mapped AI research benchmarks to cognitive abilities to tasks; early "capability→ability→occupation" chain, precursor of the 2026 OECD capability-indicator approach.
120 +- **Startup-based exposure ("Follow the money", arXiv 2024)** — measures exposure via commercial AI startup activity mapped to occupations; argues patent/ability measures miss commercialization. URL: https://arxiv.org/abs/2412.04924
121 +
122 +---
123 +
124 +<a name="3-third-wave"></a>
125 +## 3. Third wave: LLM/generative-AI exposure (2023–2024)
126 +
127 +### 3.1 Eloundou, Manning, Mishkin & Rock — "GPTs are GPTs" (OpenAI/Wharton, 2023; *Science* 2024)
128 +
129 +**The single most influential template for airiskindex.io's LLM-as-evaluator pipeline.**
130 +
131 +- **Authors/year:** Tyna Eloundou, Sam Manning, Pamela Mishkin (OpenAI), Daniel Rock (Wharton). arXiv:2303.10130 (Mar 2023); published as "GPTs are GPTs: Labor market impact potential of LLMs", *Science* 384(6702), June 2024: 1306–1308.
132 +- **Unit of analysis:** O*NET task/DWA level (19,265 tasks; 2,087 DWAs), aggregated to 1,016 occupations with task weights; combined with BLS employment/wage data.
133 +- **Exposure rubric (the core innovation):** Exposure = "whether access to an LLM or LLM-powered system would reduce the time required for a human to perform a specific task **by at least 50% while maintaining quality**". Three levels:
134 + - **E0** — no exposure.
135 + - **E1** — direct exposure: LLM alone (via chat/API) achieves the 50% time reduction.
136 + - **E2** — LLM+ exposure: achievable only with additional software/tooling built on the LLM (image input, retrieval, agents, etc.).
137 + - Aggregates: **α = E1** (lower bound), **β = E1 + 0.5·E2** (expected), **ζ = E1 + E2** (upper bound).
138 +- **Raters:** Both human annotators (OpenAI staff, trained on rubric) and **GPT-4 itself as a rater** with the rubric as prompt; human–GPT-4 agreement was high (occupation-level correlations ≈ 0.80), pioneering the LLM-as-evaluator design we plan to use.
139 +- **Key numbers:**
140 + - ~**80% of US workers** have ≥10% of tasks exposed (β); **~19% of workers** have ≥50% of tasks exposed.
141 + - ~1.8% of jobs have >half their tasks E1-exposed; rises to **~46% of jobs** under ζ (with LLM-powered software).
142 + - Exposure **increases with wage and education** (up to a point); science and critical-thinking-intensive skills correlate negatively; programming and writing positively.
143 +- **Criticisms:**
144 + - Measures *potential time savings*, not substitution vs augmentation, adoption, or net employment effect (authors are explicit).
145 + - Rater instability: the flagship statistic is wildly model-dependent (see Yin et al. 2026, §7.2: 2.7%–51.5% across frontier raters).
146 + - Static snapshot of March-2023 GPT-4 capability; the "E2 software will exist" counterfactual is speculative.
147 + - 50%-time-saving threshold is arbitrary; binary-ish levels lose information.
148 +- **URLs:**
149 + - arXiv: https://arxiv.org/abs/2303.10130
150 + - Science: https://www.science.org/doi/10.1126/science.adj0998
151 + - OpenAI page: https://openai.com/index/gpts-are-gpts/
152 + - Follow-up "Extending GPTs Are GPTs to Firms" (AEA P&P 2025): https://www.aeaweb.org/articles?id=10.1257/pandp.20251045
153 +
154 +### 3.2 Goldman Sachs — Briggs & Kodnani (March 2023)
155 +
156 +See §5.1. Methodologically an O*NET task-importance exercise inspired by Eloundou-style exposure; headline "300 million FTE jobs exposed" globally.
157 +
158 +---
159 +
160 +<a name="4-institutional-indices"></a>
161 +## 4. Institutional indices: ILO, OECD, IMF (2023–2026)
162 +
163 +### 4.1 ILO — Gmyrek, Berg & Bescond (2023): "Generative AI and Jobs: A Global Analysis"
164 +
165 +- **Authors/year:** Paweł Gmyrek, Janine Berg, David Bescond. ILO Working Paper 96, August 2023.
166 +- **Methodology:** Scored **ISCO-08** occupation task lists (not O*NET) with **GPT-4 as rater** (multiple prompts, averaged), producing task-level automation-potential scores; distinguished **automation potential** vs **augmentation potential** at occupation level; mapped to global employment via ILO harmonized microdata for 100+ countries, by income group and sex.
167 +- **Key numbers:**
168 + - Only **clerical support work** is highly exposed as a group: **24% of clerical tasks highly exposed**, +58% medium exposure. Other occupational groups: 1–4% of tasks highly exposed.
169 + - Globally, ~**2.3% of employment (~75M jobs)** in the top automation-potential bucket; **13.4% (~427M)** in augmentation potential.
170 + - Exposure concentrated in **high/upper-middle-income countries** (more clerical employment) and **strongly gendered** (clerical work is female-dominated: in high-income countries, several times more female than male employment in the highest-exposure category).
171 +- **Framing:** "Augmentation, not automation, is the most likely impact" — the origin of the transformation-over-replacement institutional narrative.
172 +- **URLs:**
173 + - WP96 PDF: https://www.ilo.org/sites/default/files/2024-07/WP96_web.pdf
174 + - SSRN: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4584219
175 + - Policy companion "Generative AI and Jobs: Policies to Manage the Transition": https://www.ilo.org/publications/generative-ai-and-jobs-policies-manage-transition
176 +
177 +### 4.2 ILO 2024 interim work
178 +
179 +- **"Mind the AI Divide: Shaping a Global Perspective on the Future of Work"** (ILO & World Bank, Aug 2024) — applies the 2023 index with a digital-infrastructure overlay: poor countries are less *exposed* but also less able to *capture augmentation gains* (the "AI divide").
180 +- **Gmyrek, Winkler & Garganta (2024, ILO/World Bank)** — "Buffer or bottleneck? Employment exposure to generative AI and the digital divide in Latin America": 26–38% of LAC jobs exposed; digital access gates both risk and benefit.
181 +- URL hub: https://www.ilo.org/publications (search "generative AI"); LAC paper: https://openknowledge.worldbank.org/handle/10986/41808
182 +
183 +### 4.3 ILO–NASK (2025): "Generative AI and Jobs: A Refined Global Index of Occupational Exposure" — current institutional state of the art
184 +
185 +- **Authors/year:** Paweł Gmyrek, Janine Berg, K. Kamiński, F. Konopczyński, A. Ładna, B. Nafradi, K. Rosłaniec, M. Troszyński (ILO + Poland's NASK). ILO Working Paper 140, May 2025 + Research Brief "Generative AI and jobs: a 2025 update".
186 +- **Methodology (major upgrade over 2023):**
187 + - **Hybrid human+LLM pipeline:** 52,558 human judgments on automation potential of 2,861 tasks (representative sample of 29,753 tasks in the Polish occupational classification), from a survey of 1,640 people (workers/experts), used to calibrate and validate GPT-4o task scoring; then scaled to the full ISCO task universe.
188 + - Replaced the binary automation/augmentation split with a **4-gradient exposure spectrum** (from marginal exposure to highest exposure), acknowledging most jobs are partially transformed.
189 + - Occupation scores mapped to global employment microdata by country income group, sex, and region.
190 +- **Key numbers:**
191 + - **1 in 4 jobs worldwide (25% of global employment)** has measurable GenAI exposure; **34% in high-income countries**.
192 + - Highest-gradient (transformation most likely) ≈ 3–4% of global employment, still concentrated in clerical work.
193 + - Gender gap persists: in high-income countries, ~**9.6% of female employment** vs ~3.5% of male employment in the top exposure gradient.
194 + - Headline framing: "**transformation, not replacement**".
195 +- **Criticisms:** LLM-rater dependence (flagged by Yin et al. 2026); Polish task-survey generalizability; exposure ≠ adoption in low-connectivity countries (self-acknowledged).
196 +- **URLs:**
197 + - WP140 PDF: https://www.ilo.org/sites/default/files/2025-05/WP140_web.pdf
198 + - WP140 interactive: https://webapps.ilo.org/static/english/intserv/working-papers/wp140/index.html
199 + - 2025 update brief: https://www.ilo.org/publications/generative-ai-and-jobs-2025-update (PDF: https://www.ilo.org/sites/default/files/2025-05/Research%20brief_GenAI%202025%20Update.pdf)
200 + - Press release: https://www.ilo.org/resource/news/one-four-jobs-risk-being-transformed-genai-new-ilo–nask-global-index-shows
201 +
202 +### 4.4 OECD (2023–2026)
203 +
204 +- **Employment Outlook 2023 (AI chapters):** using Lassébie–Quintini expert-based measure, occupations at highest automation risk = **~27% of employment** across OECD; "no signs of slowing labour demand (yet)" in AI-exposed occupations. URLs: https://www.oecd.org/en/publications/oecd-employment-outlook-2023_08785bba-en/full-report/artificial-intelligence-and-jobs-no-signs-of-slowing-labour-demand-yet_5aebe670.html
205 +- **AI case studies & job quality (2023–2024):** firm case studies in finance/manufacturing across 8 countries: 23% of firms reported AI reduced employment in affected roles; wages mostly unchanged. Georgieff (2024), "Artificial intelligence and wage inequality": https://www.oecd.org/en/publications/artificial-intelligence-and-wage-inequality_bf98a45c-en.html
206 +- **OECD AI Capability Indicators (2025):** 5-year effort, 50+ experts; 9 capability domains (Language; Social interaction; Problem solving; Creativity; Metacognition & critical thinking; Knowledge/learning/memory; Vision; Manipulation; Robotic intelligence) each on an ordinal capability scale. URL: https://www.oecd.org/en/publications/introducing-the-oecd-ai-capability-indicators_be745f04-en.html
207 +- **The OECD AI Exposure Measure (2025/2026):** maps occupations' required capability *levels* in each of the 9 domains against AI's *current attained level* per the Capability Indicators → exposure = overlap. Explicitly designed to be **forward-looking, transparent, and updateable** as AI capability levels advance — the first institutional index architected for versioned re-scoring (same philosophy as our `INDEX_VERSION`). URL: https://www.oecd.org/en/publications/the-oecd-ai-exposure-measure_f3da0f0a-en.html
208 +- **Skills in the AI age (OECD AI Papers No. 60, July 2026):** applies the exposure measure to skills demand. URL: https://www.oecd.org/content/dam/oecd/en/publications/reports/2026/07/skills-in-the-ai-age_e8d8c1e6/972bd15e-en.pdf
209 +
210 +### 4.5 IMF — Cazzaniga et al. (2024) and the AI Preparedness Index
211 +
212 +- **Authors/year:** Mauro Cazzaniga, Florence Jaumotte, Longji Li, Giovanni Melina, Augustus Panton, Carlo Pizzinelli, Emma Rockall, Marina M. Tavares. "Gen-AI: Artificial Intelligence and the Future of Work", IMF Staff Discussion Note SDN/2024/001, January 2024.
213 +- **Methodology:** Takes Felten's **AIOE** and adds a **potential complementarity index (C-AIOE)** (from Pizzinelli et al. 2023, IMF WP/23/216): occupations scored on shielding factors — required physical presence, human interaction, legal/social responsibility (judges are exposed *and* complemented; telemarketers exposed and *not*). Splits employment into: high exposure + high complementarity (augmentation likely) vs high exposure + low complementarity (displacement risk) vs low exposure.
214 +- **Key numbers:** **~40% of global employment exposed** to AI (**60% advanced economies, 40% emerging, 26% low-income**). In AEs, roughly half of exposed jobs are high-complementarity. Women and college-educated more exposed but better positioned for gains.
215 +- **AI Preparedness Index (AIPI):** country-level (174 economies) readiness across digital infrastructure, human capital & labor policies, innovation & integration, regulation & ethics — the macro complement to occupational exposure. Dashboard: https://www.imf.org/external/datamapper/AIPI@AIPI
216 +- **Criticisms:** Inherits all AIOE limitations; complementarity ratings are judgment calls; country mapping via ISCO crosswalks is coarse.
217 +- **URLs:**
218 + - SDN PDF: https://www.imf.org/-/media/files/publications/sdn/2024/english/sdnea2024001.pdf
219 + - eLibrary: https://www.elibrary.imf.org/view/journals/006/2024/001/006.2024.issue-001-en.xml
220 + - Pizzinelli et al. WP/23/216 (C-AIOE): https://www.imf.org/en/Publications/WP/Issues/2023/10/04/Labor-Market-Exposure-to-AI-Cross-country-Differences-and-Distributional-Implications-539656
221 + - Follow-up: "Exposure to Artificial Intelligence and Occupational Mobility" (WP/24/116): https://www.imf.org/-/media/files/publications/wp/2024/english/wpiea2024116-print-pdf.pdf
222 +
223 +---
224 +
225 +<a name="5-industry-estimates"></a>
226 +## 5. Industry & consultancy estimates
227 +
228 +### 5.1 Goldman Sachs (Briggs & Kodnani, March 2023)
229 +
230 +- "The Potentially Large Effects of Artificial Intelligence on Economic Growth". O*NET task-level judgment of automatable share per occupation (26 US, 24 European task categories importance/complexity weighted).
231 +- **Key numbers:** ~**2/3 of US/European occupations partially exposed**; generative AI could substitute up to **25% of current work** = **300M FTE jobs** globally exposed; +7% global GDP over 10 years. Most exposed: office/admin support (46% of tasks automatable), legal (44%), architecture/engineering (37%).
232 +- **Criticism:** binary "automatable share" judgments, no adoption model; the 300M number is routinely misquoted as "job losses".
233 +- URLs: https://www.goldmansachs.com/insights/articles/generative-ai-could-raise-global-gdp-by-7-percent ; follow-up US labor analysis: https://www.goldmansachs.com/insights/articles/how-will-ai-affect-the-us-labor-market
234 +
235 +### 5.2 McKinsey Global Institute (June 2023, updated)
236 +
237 +- "The economic potential of generative AI: the next productivity frontier". Proprietary work-activity/capability model (~2,100 work activities, 850 occupations).
238 +- **Key numbers:** GenAI + existing tech could automate activities absorbing **60–70% of employees' time**; genAI value $2.6–4.4T/yr; **half of today's work activities automated between 2030 and 2060 (midpoint ~2045)** — pulled forward ~a decade vs pre-genAI estimate.
239 +- **Criticism:** proprietary/black-box capability ratings; "time automatable" ≠ jobs; adoption scenarios highly assumption-driven.
240 +- URL: https://www.mckinsey.com/capabilities/tech-and-ai/our-insights/the-economic-potential-of-generative-ai-the-next-productivity-frontier
241 +
242 +### 5.3 Pew Research Center (Kochhar, July 2023)
243 +
244 +- "Which U.S. Workers Are More Exposed to AI on Their Jobs?" — Felten-style ability-importance approach on O*NET.
245 +- **Key numbers:** **19% of US workers in most-exposed jobs** vs 23% in least-exposed (2022). Most-exposed jobs pay *more* ($33/hr vs $20/hr); exposure higher for women, Asian, college-educated workers. Notably, workers in exposed industries did **not** feel their jobs at risk.
246 +- URL: https://www.pewresearch.org/social-trends/2023/07/26/which-u-s-workers-are-more-exposed-to-ai-on-their-jobs/
247 +
248 +### 5.4 PwC Global AI Jobs Barometer (2024, 2025, 2026)
249 +
250 +- Analyzes ~**1 billion job ads** worldwide + firm financials. 2025 edition ("The Fearless Future"): industries most exposed to AI saw productivity growth nearly **4x** (7%→27%); **56% wage premium** for AI-skilled workers (up from 25%); skills in AI-exposed occupations changing **66% faster**; employment *still growing* even in highly automatable roles. 2026 edition: labor market splitting into "two distinct paths", rewarding human skills.
251 +- **Value to us:** the best large-scale *demand-side* signal (vacancies), useful to calibrate `adoption_velocity`.
252 +- URLs: https://www.pwc.com/gx/en/issues/artificial-intelligence/job-barometer/2025/report.pdf ; 2026 PR: https://www.pwc.com/gx/en/news-room/press-releases/2026/pwc-2026-ai-jobs-barometer.html
253 +
254 +---
255 +
256 +<a name="6-usage-based-measures"></a>
257 +## 6. Usage-based measures: Anthropic Economic Index & OpenAI (2025–2026)
258 +
259 +The decisive innovation of 2025–26: replacing *predicted* exposure with **observed AI usage** mapped to the same O*NET task taxonomy. This is the empirical anchor airiskindex.io should exploit for `adoption_velocity` and to validate `automatability`.
260 +
261 +### 6.1 Anthropic Economic Index (AEI) — all releases to August 2026
262 +
263 +**Methodology (constant across releases):** Clio, a privacy-preserving analysis pipeline in which Claude classifies large samples of real Claude.ai/API conversations against the **O*NET task taxonomy (~20,000 tasks)** and SOC occupations, plus interaction-mode classification (**automation** = full delegation/directive; **augmentation** = iterative collaboration, learning, validation). All aggregated data released openly on Hugging Face.
264 +
265 +| Release | Report | Data & model | Headline findings |
266 +|---|---|---|---|
267 +| **Feb 10, 2025** (paper arXiv:2503.04761, Handa et al., "Which Economic Tasks are Performed with AI?") | Launch report | 4M+ Claude.ai conversations | **36% of occupations** used AI for ≥25% of their tasks; only ~4% for ≥75%. Usage concentrated in **software development & writing** (computer/math ≈ 37% of conversations); peaks in **mid-to-high-wage** occupations, low at both wage extremes. **57% augmentation / 43% automation**. |
268 +| **Mar 27, 2025** | v2 (Claude 3.7 Sonnet) | New conversations + cluster-level data | Usage patterns stable; extended-thinking usage concentrated in technical tasks; released bottom-up task clusters. |
269 +| **Sep 15, 2025** (arXiv:2511.15080) | "Uneven geographic and enterprise adoption" | 1P API + geographic breakdowns; **Anthropic AI Usage Index (AUI)** = country share of usage ÷ share of working-age population | US 21.6% of usage; per-capita leaders Israel, Singapore, Australia, NZ, S. Korea. **+1% GDP/capita ↔ +0.7% AUI** (US states: 1.8% elasticity). DC highest state AUI (3.82). **Automation rose 27%→39%** of conversations since Dec 2024, surpassing augmentation for the first time; API usage even more automation-heavy. |
270 +| **Jan 15, 2026** | "New building blocks" (economic primitives) | 1M Claude.ai + 1M 1P API transcripts (Sonnet 4.5); Nov 2025 data | Five **primitives**: task complexity, human/AI skill level, use case, AI autonomy, task success. College-level tasks: **12x estimated speedup** but 66% success rate vs 70% for simpler tasks; revised aggregate productivity estimate **+1.2 pp/yr** (down from 1.8 after reliability adjustment). Augmentation back above automation on Claude.ai (52% vs 45%). |
271 +| **Mar 24, 2026** | "Learning curves" | Feb 2026 data (Opus 4.5/4.6) | Claude.ai task mix **de-concentrating** (top-10 tasks 24%→19%) while API concentrates (28%→33%). **49% of jobs in sample** now see Claude used for ≥25% of tasks (up from 36% in Jan 2025). 6-month+ tenure users: +10% conversation success; usage value ≈ $48–49/hr wage-equivalent tasks. |
272 +| **Apr 2026** | AEI **Survey** launched | 9,700 Claude users, linked usage+perceptions | See below. |
273 +| **Jun 26, 2026** | "Cadences" | Apr–Jun 2026, hourly sampling; artifact classifier | 93% of conversations produce artifacts (explanations 17%, documents/reports 15%). Higher-wage occupations' conversations consume 2.07x tokens. Survey: >⅓ of users expect AI to handle most of their work tasks within 12 months; only 10% rate own job loss likely; heavier automation users are *more* optimistic. Women use Claude less in automated modes (−0.33 SD). |
274 +
275 +- **Data availability (all releases):** https://huggingface.co/datasets/Anthropic/EconomicIndex (per-release folders `release_2025_03_27/`, `release_2025_09_15/`, etc., with documentation + replication notebooks; R package `aieconindex` on CRAN).
276 +- **Index hub:** https://www.anthropic.com/economic-index — reports: https://www.anthropic.com/news/the-anthropic-economic-index ; https://www.anthropic.com/research/economic-index-geography ; https://www.anthropic.com/research/economic-index-primitives ; https://www.anthropic.com/research/economic-index-march-2026-report ; https://www.anthropic.com/research/economic-index-june-2026-report
277 +- **Limitations (self-acknowledged):** Claude users ≠ workforce (selection bias toward developers/knowledge workers); conversation ≠ completed work; O*NET classification by LLM inherits classifier error; per-provider view only.
278 +
279 +### 6.2 OpenAI — "How People Use ChatGPT" (Chatterji et al., NBER w34255, Sept 2025)
280 +
281 +- Aaron Chatterji, Tom Cunningham, David Deming, Zoë Hitzig, Christopher Ong, Carl Shan, Kevin Wadman. Privacy-preserving classification of a representative sample of ChatGPT consumer conversations, Nov 2022–Jul 2025 (~10% of world adult population using ChatGPT).
282 +- **Findings:** non-work usage grew from 53%→>70% of messages; work usage concentrated in **decision support** (advice, writing, information) rather than task execution; work usage highest among educated, high-paid professionals. Three-quarters of work messages: writing, information seeking, decision support.
283 +- **Relevance:** independent replication that *realized* usage is augmentation-tilted and knowledge-work-concentrated — cross-provider triangulation for `adoption_velocity`.
284 +- URLs: https://www.nber.org/papers/w34255 (PDF: https://www.nber.org/system/files/working_papers/w34255.pdf)
285 +
286 +---
287 +
288 +<a name="7-fourth-wave"></a>
289 +## 7. Fourth wave: 2025–2026 indices and meta-critiques
290 +
291 +### 7.1 Stanford Digital Economy Lab — "Canaries in the Coal Mine?" (Brynjolfsson, Chandar & Chen, Aug/Nov 2025)
292 +
293 +- **The most important realized-effects paper to date.** Uses **ADP payroll microdata** (millions of workers, monthly) linked to occupational AI-exposure measures (Eloundou/GPTs-are-GPTs based, cross-checked with Anthropic Economic Index automation/augmentation shares).
294 +- **Six facts**, headline: since late 2022, **early-career workers (22–25) in the most AI-exposed occupations saw a ~13–16% relative employment decline** (16% in the Nov 2025 revision, controlling for firm-level shocks), while older workers in the same occupations and less-exposed young workers kept growing. Adjustment happens via **employment, not wages**. Declines concentrated where AEI data says AI **automates** rather than augments. Entry-level hiring is the "canary".
295 +- **Live monitoring:** "Canaries Dashboard" — https://digitaleconomy.stanford.edu/project/indicators/canaries-dashboard/
296 +- **URLs:** paper page https://digitaleconomy.stanford.edu/publications/canaries-in-the-coal-mine ; PDF (Nov 2025) https://digitaleconomy.stanford.edu/app/uploads/2025/11/CanariesintheCoalMine_Nov25.pdf ; SIEPR WP: https://siepr.stanford.edu/publications/working-paper/canaries-coal-mine-six-facts-about-recent-employment-effects-artificial
297 +
298 +### 7.2 Yin, Vu & Persico (2026) — multi-model instability of LLM-rated exposure ("When the ruler is made of the thing it measures")
299 +
300 +- NBER WP 35110, "How (un)stable are LLM occupational exposure scores? Evidence from multi-model replication"; VoxEU column May 2026.
301 +- Replicated the Eloundou rubric with **four frontier models on identical O*NET data**: share of US occupations with >50% of tasks at high direct exposure = **2.7% (Gemini 2.5) … 3.8% (GPT-4) … 20.3% (GPT-5) … 51.5% (Claude 4.5)** — a **19x spread**. Management occupations: >80% high-exposure under Claude, <20% under Gemini. Downstream diff-in-diff employment estimates **flip sign** across raters. Bias is systematic per model, doesn't wash out with sample size, and co-evolves with the technology being measured (feedback channel).
302 +- **Recommendation (directly applicable to us):** any LLM-rated exposure analysis must report results from **≥2–3 different frontier models**; convergence ⇒ robust, divergence ⇒ model artifact.
303 +- URL: https://cepr.org/voxeu/columns/when-ruler-made-thing-it-measures-multi-model-evidence-ai-occupational-exposure
304 +
305 +### 7.3 "AI Exposure Scores: what they measure, what they miss, and what comes next" (Lund, Euyang, Munyikwa & Fadaee, arXiv June 2026)
306 +
307 +- Field review. Diagnoses a **structural gap** (static scores can't answer dynamic who/when/where policy questions) and a **coordination gap** (policy still cites static 2023 GPTs-are-GPTs numbers despite methodological advances). Surveys five successor families: **dynamic/benchmark-based measures, ensembles, task-framework extensions, worker-centered metrics, adoption/usage data**. Recommends moving "from prediction to preparedness".
308 +- URL: https://arxiv.org/abs/2606.23633
309 +
310 +### 7.4 Other notable 2025–2026 entries
311 +
312 +- **Iceberg Index (Chopra et al., MIT + Oak Ridge National Laboratory, arXiv 2510.25137, late 2025):** skills-centered simulation — 151M US workers, 923 occupations, 32,000+ skills, ~3,000 counties; catalogued 13,000+ AI tools; agent-based simulation (AgentTorch on Frontier supercomputer). Visible "surface" tech-sector exposure = 2.2% of wage bill (~$211B); full skill-overlap exposure = **11.7% of US wage bill (~$1.2T)**. Explicitly technical exposure, not displacement. URLs: https://arxiv.org/abs/2510.25137 ; https://iceberg.mit.edu/report.pdf
313 +- **Yale Budget Lab — "Evaluating the Impact of AI on the Labor Market: Current State of Affairs" (Gimbel, Kinder, Kendall & Lee, Oct 2025, updated):** occupational-mix dissimilarity analysis; finds **no broad acceleration** in labor-market compositional change attributable to AI 33 months post-ChatGPT — important null-result counterweight to Canaries. URL: https://budgetlab.yale.edu/research/evaluating-impact-ai-labor-market-current-state-affairs
314 +- **UK task-based GenAI exposure index (arXiv 2507.22748, 2025):** novel LLM-scored task index applied to UK SOC codes — example of the national-adaptation pattern relevant to our ESCO/ROME crosswalk. URL: https://arxiv.org/abs/2507.22748
315 +- **OAIES / capability-staged exposure (2025–2026):** scores O*NET task automatable share at discrete **AI capability stages** (pre-LLM ML → early LLMs → multimodal → reasoning → agentic), multi-model rated (GPT-4o + Claude 3.5); cross-methodology Spearman ρ = 0.84 against independent scores. Overview: https://www.emergentmind.com/topics/ai-exposed-occupations ; theory-based variant (Moravec-paradox index): https://arxiv.org/abs/2510.13369
316 +- **"AI and jobs: A review of theory, estimates, and evidence" (arXiv 2509.15265, 2025):** comprehensive literature review; useful bibliography. URL: https://arxiv.org/abs/2509.15265
317 +- **Agentic-AI exposure analyses (2026):** e.g., "Agentic AI and Occupational Displacement" (arXiv 2604.00186) extends task exposure to autonomous multi-step agents across regions. URL: https://arxiv.org/abs/2604.00186
318 +- **"The Jagged Global Economy" (arXiv 2607.05404, 2026):** frontier-AI benchmark performance mapped to national economies — capability-grounded, benchmark-updated exposure. URL: https://arxiv.org/abs/2607.05404
319 +
320 +---
321 +
322 +<a name="8-empirical-validation"></a>
323 +## 8. Empirical validation: do exposure scores predict real outcomes?
324 +
325 +**Bottom line: individually, classic exposure scores are weak predictors; ensembles + adoption data + post-2022 windows perform much better. Realized effects so far are concentrated (entry-level, automation-tilted tasks, online freelancing), not economy-wide.**
326 +
327 +1. **Frank, Ahn & Moro (PNAS Nexus, April 2025), "AI exposure predicts unemployment risk"** — built occupation-level *unemployment risk* from US unemployment-insurance claims (2010–2020); tested 10 exposure scores. **Every individual score performs poorly** (best single: Arntz automation probability, R² = 0.107; most < 3%; Frey–Osborne, SML, Felten, Webb all weak alone). An **ensemble of all scores** explains 29.8% (75.5% with education/skill/region controls) — +18 pp over baseline. Lesson: **no single score suffices; combine dimensions**. URL: https://pmc.ncbi.nlm.nih.gov/articles/PMC11983276/ (arXiv:2308.02624)
328 +2. **Acemoglu, Autor, Hazell & Restrepo (JOLE 2022), "AI and Jobs: Evidence from Online Vacancies"** — AI-exposed establishments (Burning Glass) post more AI vacancies and *reduce* non-AI hiring, but **no detectable aggregate occupation-level employment effects** through 2018. URL: https://jadhazell.github.io/website/AI_And_Jobs.pdf
329 +3. **Brynjolfsson, Chandar & Chen (2025) "Canaries"** — first large-scale realized-effect finding: −13–16% relative employment for early-career workers in most-exposed occupations; effects load on **automation-classified** (AEI) usage, not augmentation. (§7.1)
330 +4. **Hui, Reshef & Zhou (2024), "The Short-Term Effects of Generative AI on Online Labor Markets"** — after ChatGPT, exposed freelancers (writing-heavy) on a large platform saw ~2% fewer jobs and ~5% lower earnings; top performers not spared. VoxEU: https://cepr.org/voxeu/columns/artificial-intelligence-and-its-short-term-effects-employment
331 +5. **Hampole, Papanikolaou, Schmidt & Seegmiller (NBER w33509, 2025), "Artificial Intelligence and the Labor Market"** — vacancy-based measure of firm AI adoption; AI adoption predicts declining demand for exposed occupations within adopting firms, with reallocation toward AI-complementary roles. URL: https://www.nber.org/papers/w33509
332 +6. **Humlum & Vestergaard (2025), "Large Language Models, Small Labor Market Effects" (Denmark, NBER w33777)** — despite rapid ChatGPT adoption among exposed workers, **no detectable effects on earnings or hours** in 2023–24 administrative data; average time savings ~3%. Counterweight showing adoption ≠ displacement in the short run. URL: https://www.nber.org/papers/w33777
333 +7. **Wage-growth cross-section (2019 vs 2023, arXiv 2312.04714 & follow-ups):** one-unit higher AI exposure ↔ **−6.5 pp wage growth**, explaining ~34% of cross-sectional variation post-ChatGPT; late-2022→early-2025 CPS analyses find high-exposure occupations losing 5.6–8.5 pp employment per 10-point exposure. URL: https://arxiv.org/abs/2312.04714
334 +8. **Georgieff & Hyee (OECD 2021) / Employment Outlook 2023:** 2012–2019 — no negative employment relationship; **exposure without adoption predicts nothing** pre-2022. (§2.3, §4.4)
335 +9. **Yale Budget Lab (2025–2026):** aggregate occupational mix shifting no faster than historical benchmarks (§7.4) — realized effects are **cohort- and task-specific, not (yet) aggregate**.
336 +
337 +**Synthesis for validation design:** (a) validate at task/cohort level, not aggregate; (b) test against unemployment/UI-claim risk and entry-level hiring, not just employment stocks; (c) use ensembles; (d) treat pre-2022 null results as evidence about *adoption gating*, which is exactly what our `barriers` and `adoption_velocity` dimensions model.
338 +
339 +---
340 +
341 +<a name="9-comparison-table"></a>
342 +## 9. Master comparison table
343 +
344 +| Index / Study | Year | Approach | Unit of analysis | Scale / output | Substitution vs augmentation split? | Validation status | Data availability |
345 +|---|---|---|---|---|---|---|---|
346 +| **Frey & Osborne** | 2013/2017 | Expert labels + Gaussian process classifier on O*NET bottleneck variables | Occupation (702 SOC) | P(computerisation) 0–1 | No | Poor ex-post; explains <3% of unemployment risk alone | Scores in paper appendix (public) |
347 +| **Arntz, Gregory & Zierahn (OECD)** | 2016/2017 | F&O risk re-estimated on individual PIAAC task bundles | Worker/task bundle | P(automation) 0–1 | No | Best single predictor in Frank et al. ensemble (R²=0.107) | PIAAC public; scores replicable |
348 +| **Brynjolfsson, Mitchell & Rock (SML)** | 2017/2018 | 23-question rubric, crowd-rated | O*NET task (18k) → occupation | SML 1–5 | Implicit (redesign framing) | Weak alone | openICPSR replication archive |
349 +| **Webb** | 2020 | Patent–task text overlap (verb–object pairs) | Task → occupation | Exposure percentile | No | Historical validation on robots/software; AI portion pre-LLM | Author site / SSRN |
350 +| **Felten, Raj & Seamans (AIOE)** | 2021 (genAI 2023) | 10 AI apps × 52 abilities, MTurk relatedness, importance-weighted | Ability → occupation (+industry, county) | Continuous z-score | No (explicitly neutral) | Weak alone; base of IMF/Pew analyses | GitHub (AIOE-Data/AIOE) |
351 +| **Eloundou et al. "GPTs are GPTs"** | 2023/2024 | Rubric (≥50% time saving), human + GPT-4 raters | O*NET task/DWA → occupation | E0/E1/E2; α, β, ζ shares 0–1 | No (time-savings only) | Predicts Canaries cohort effects; rater-unstable (19x across models) | arXiv appendix; rubric public |
352 +| **Goldman Sachs (Briggs & Kodnani)** | 2023 | Task-importance share judged automatable | Occupation | % tasks automatable | Partial (25% substitution assumption) | n/a | Report only (proprietary) |
353 +| **McKinsey MGI** | 2023 | Proprietary activity–capability model | Work activity (~2,100) | % of work time automatable; adoption scenarios | Partial | n/a | Report only (proprietary) |
354 +| **Pew Research (Kochhar)** | 2023 | Felten-style ability importance | Occupation | High/medium/low exposure | No | n/a | Report + appendix |
355 +| **ILO Gmyrek et al. WP96** | 2023 | GPT-4-rated ISCO task scores | ISCO task → occupation → global employment | Automation vs augmentation potential, 0–1 | **Yes** | n/a | Scores in WP annexes |
356 +| **IMF Cazzaniga et al. (AIOE + C-AIOE)** | 2024 | AIOE + complementarity shielding index | Occupation → country employment | Exposure × complementarity quadrants | **Yes** (complementarity) | n/a | AIPI dashboard; WP data |
357 +| **ILO–NASK refined index (WP140)** | 2025 | Hybrid: 52,558 human ratings calibrating GPT-4o, 4-gradient scale | Task → ISCO occupation → 100+ countries | 4 exposure gradients | **Yes** (gradient) | n/a | WP + interactive tool |
358 +| **OECD AI Exposure Measure** | 2025/2026 | Occupation capability requirements vs OECD AI Capability Indicators (9 domains, expert-set levels) | Ability-domain → occupation | Capability-overlap exposure; versioned as AI levels advance | No | New | OECD publication + indicators |
359 +| **PwC AI Jobs Barometer** | 2024–2026 | ~1B job ads; demand-side | Vacancy/occupation/industry | Growth, wage premium, skill-change rates | No | Is itself outcome data | Annual reports |
360 +| **Anthropic Economic Index** | 2025–2026 (6 releases) | Observed Claude usage classified to O*NET tasks (Clio); AUI; primitives | Conversation → task → occupation, geo | Usage shares; automation vs augmentation %; complexity/success | **Yes** (measured, not predicted) | Is itself adoption data; used in Canaries | **Hugging Face (open)** |
361 +| **OpenAI / Chatterji et al.** | 2025 | Observed ChatGPT usage classification | Message → task category | Usage shares by intent | Partial (Asking/Doing/Expressing) | Is itself adoption data | NBER paper (aggregates) |
362 +| **Canaries in the Coal Mine (Stanford DEL)** | 2025 | ADP payroll × exposure scores (realized effects) | Worker-level panel | Employment effects by age × exposure | Uses AEI automation/augmentation | **Is the validation** | Dashboard public; ADP restricted |
363 +| **Iceberg Index (MIT/ORNL)** | 2025 | Skill-level tool coverage + agent-based simulation | 32k skills → 923 occupations → counties | % of wage bill exposed ($) | No | New | iceberg.mit.edu; arXiv |
364 +| **Yin, Vu & Persico (multi-model replication)** | 2026 | Meta: Eloundou rubric × 4 frontier raters | Task → occupation | Rater-dispersion bounds | n/a | Meta-validation | NBER WP 35110 |
365 +| **Frank, Ahn & Moro (ensemble)** | 2025 | Ensemble of 10 exposure scores vs UI-claims risk | Occupation × state × month | Unemployment-risk R² | No | **Is the validation** | PNAS Nexus (open access) |
366 +
367 +---
368 +
369 +<a name="10-implications"></a>
370 +## 10. Implications for airiskindex.io v1 methodology
371 +
372 +Mapping the literature onto our five dimensions (weights from `packages/scoring/src/weights.ts`) and our exposure/substitution/augmentation triad.
373 +
374 +### Cross-cutting lessons (apply to the whole index)
375 +
376 +1. **Task-based is settled science** (Arntz 2016 → everyone since). Our O*NET task-level scoring with importance/frequency weights is the correct v1 backbone. Keep occupation scores as *derived*, never primary.
377 +2. **Never collapse to one number without sub-scores.** Felten's "neutral exposure", ILO's automation/augmentation split, IMF's complementarity, and AEI's measured automation:augmentation ratio all show the field converging on our exposure/substitution/augmentation triad. This is a genuine differentiator — most indices still publish one headline number and get misquoted (Goldman's "300M jobs lost" problem). Our tone rule ("adaptation, not doom") is empirically supported: realized effects so far are cohort-specific task reallocation, not mass unemployment (Yale Budget Lab; Humlum & Vestergaard) — with real, measurable pain at the entry level (Canaries).
378 +3. **LLM-as-evaluator is standard but fragile — multi-model rating is now table stakes.** Yin et al. (2026): 19x spread in headline statistics across frontier raters on identical data; Claude-family raters score exposure *highest* of all models. Concrete requirements for `apps/worker/src/raters/`:
379 + - Rate every task with **≥2 (ideally 3) different frontier models** (`RATER_MODEL` must become a list or we add `RATER_MODEL_SECONDARY`); store per-model scores; publish cross-model agreement per occupation.
380 + - Report **confidence intervals derived from rater disagreement** — this slots directly into our existing `score_low`/`score`/`score_high` schema.
381 + - Calibrate LLM ratings against a **human-rated anchor set** (ILO–NASK's 52,558-judgment survey design is the gold standard; our expert Delphi overrides in `data/derived/expert_overrides/` serve this role — sample deliberately across the exposure spectrum, not just flagged disagreements).
382 + - Prompt-version everything (we already do) and re-rate on model change — because the instrument co-evolves with the phenomenon (the "ruler" problem).
383 +4. **Anchor thresholds in explicit, documented rubrics.** Eloundou's "≥50% time saving at equal quality" is the citable standard; SML's 23 questions show multi-criterion rubrics beat single judgments. Our prompts should decompose ratings into named criteria and require structured justifications (auditable per §6 of CLAUDE.md).
384 +5. **Validate against outcomes, and say so publicly.** Frank et al. (2025): single scores explain <11% of unemployment risk; ensembles ~30–75%. We should (a) benchmark our composite against the public AEI usage data, PwC vacancy signals, and the Canaries dashboard; (b) publish a `docs/methodology/sensitivity/` correlation report against AIOE, GPTs-are-GPTs β, and ILO WP140 scores each release. Spearman ρ ≈ 0.84 between independent modern methodologies is the bar for "capturing the same signal".
385 +
386 +### Dimension-by-dimension
387 +
388 +| Dimension (weight) | Lessons from the literature |
389 +|---|---|
390 +| **`automatability` (0.35)** | This is Eloundou's E1/E2 construct + SML's rubric. Use a multi-criterion, multi-model LLM rubric with a time-savings-at-quality threshold; keep 5-point scale (matches ILO gradient practice and our expert-review disagreement rule). Distinguish "LLM alone" (E1) from "LLM + tooling/agents" (E2) — capability-staged variants (OAIES; OECD capability levels) show staging by AI generation makes scores updateable rather than obsolete. Critically: rate **automation vs augmentation potential separately per task** (ILO 2023/2025) so the composite's sub-scores are computed, not asserted. |
391 +| **`feasibility` (0.20)** | Separating "conceivable" from "deployable now" is what F&O failed to do and what killed their forecast. Ground feasibility in *observed evidence*: AEI task-success rates (66–70% by complexity — Jan 2026 primitives), benchmark-linked measures (OECD AI Capability Indicators' 9 domains with attained levels; "Jagged Global Economy"), and Iceberg's tool-catalogue approach (is there an actual product performing this skill?). Feasibility should decay-adjust automatability: high automatability + low current success rate ⇒ wide CI, lower composite. |
392 +| **`cost_ratio` (0.15)** | Least developed dimension in the literature — a genuine gap we can own. Only Webb (implicitly, via wages), Goldman (25% substitution assumption), and AEI's $/hr wage-equivalent task values touch it. Use O*NET-linked BLS wages (as AEI does: $48–49/hr average task value) vs API/inference cost per task-equivalent; store as integer cents per our conventions. Note Acemoglu's caution ("so-so automation"): low cost ratio can drive adoption even with mediocre quality — interact with feasibility. |
393 +| **`barriers` (0.20)** | Directly validated by IMF's C-AIOE complementarity (physical presence, human contact, legal responsibility) — the reason judges are exposed but safe and telemarketers are not. Pre-2022 null results (OECD 2021; Acemoglu et al. 2022) prove barriers dominate short-run outcomes. Operationalize the IMF/Pizzinelli shielding factors at task level: regulation/licensing, liability, required physical presence, human-contact preference, data confidentiality. Humlum & Vestergaard (Denmark) show even *adoption* without workflow redesign yields ~3% time savings — organizational barriers belong here too. |
394 +| **`adoption_velocity` (0.10)** | The 2025–26 revolution: use **measured** adoption, not guesses. Sources: AEI Hugging Face releases (task-level usage shares, automation:augmentation ratio, AUI by geography — open data, quarterly cadence), PwC Jobs Barometer (vacancy-side skill change 66% faster in exposed occupations), OpenAI usage paper. Sector velocity is empirically uneven (API vs consumer concentration diverging; GDP-elasticity of adoption 0.7) — justify per-sector velocity scores with these citations. Design for **time-series updates**: AEI shows adoption shares move 10+ pp in a year (automation 27%→39%→45–52% oscillation), so this dimension must re-score every `INDEX_VERSION`. |
395 +
396 +### Positioning / product implications
397 +
398 +- **Transparency is our moat and the field's known weakness:** McKinsey/Goldman are black boxes; even academic scores rarely ship rater-level data. We publish weights (`/api/v1/methodology`), prompts (versioned), per-model ratings, and CIs — no major index does all four.
399 +- **Versioning is becoming an explicit norm** (OECD exposure measure designed to be updateable; AEI releases dated datasets). Our `INDEX_VERSION` + immutable `score_runs` architecture matches best practice; cite OECD/AEI precedent in METHODOLOGY.md.
400 +- **CI bounds have empirical semantics now:** rater disagreement (Yin et al.) + human-LLM calibration error (ILO–NASK) + feasibility uncertainty (AEI success rates) are the three quantifiable components of `score_low`/`score_high`.
401 +- **EU/France (ESCO/ROME) crosswalk:** ILO WP140 (ISCO-based) and the UK index (arXiv 2507.22748) are the reference patterns for adapting O*NET-trained scores to other taxonomies; document crosswalk loss explicitly.
402 +- **Watch list for future versions:** agentic-AI exposure extensions (arXiv 2604.00186), benchmark-grounded dynamic scores (OECD capability levels; "Jagged Global Economy"), worker-centered metrics (Lund et al. taxonomy), and the Canaries dashboard as a rolling validation target.
403 +
404 +---
405 +
406 +*Compiled via WebSearch, Tavily, WebFetch, and OpenAlex queries, 2026-08-05. All URLs verified live at compile time unless noted. This document feeds `docs/methodology/METHODOLOGY.md` §Related Work.*
added docs/research/02-data-sources.md +129 −0
Large diff (129 lines) is not loaded by default to keep the page fast.Load diff
added docs/research/03-llm-rater-api.md +329 −0
Large diff (329 lines) is not loaded by default to keep the page fast.Load diff
added docs/research/04-landscape-and-evidence.md +307 −0
Large diff (307 lines) is not loaded by default to keep the page fast.Load diff
added docs/research/README.md +41 −0
Large diff (41 lines) is not loaded by default to keep the page fast.Load diff
added eslint.config.mjs +25 −0
Large diff (25 lines) is not loaded by default to keep the page fast.Load diff
added infra/.env.example +20 −0
Large diff (20 lines) is not loaded by default to keep the page fast.Load diff
added infra/deploy.sh +19 −0
Large diff (19 lines) is not loaded by default to keep the page fast.Load diff
added infra/docker-compose.dev.yml +19 −0
Large diff (19 lines) is not loaded by default to keep the page fast.Load diff
added infra/docker-compose.prod.yml +23 −0
Large diff (23 lines) is not loaded by default to keep the page fast.Load diff
added infra/ngrok.yml +11 −0
Large diff (11 lines) is not loaded by default to keep the page fast.Load diff
added package.json +29 −0
Large diff (29 lines) is not loaded by default to keep the page fast.Load diff
added packages/config/package.json +6 −0
Large diff (6 lines) is not loaded by default to keep the page fast.Load diff
added packages/config/tsconfig/base.json +16 −0
Large diff (16 lines) is not loaded by default to keep the page fast.Load diff
added packages/config/tsconfig/nextjs.json +13 −0
Large diff (13 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/package.json +30 −0
Large diff (30 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/prisma/schema.prisma +150 −0
Large diff (150 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/prisma/seed.ts +46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/src/index.ts +12 −0
Large diff (12 lines) is not loaded by default to keep the page fast.Load diff
added packages/db/tsconfig.json +4 −0
Large diff (4 lines) is not loaded by default to keep the page fast.Load diff
added packages/scoring/package.json +24 −0
Large diff (24 lines) is not loaded by default to keep the page fast.Load diff
added packages/scoring/src/composite.test.ts +71 −0
Large diff (71 lines) is not loaded by default to keep the page fast.Load diff
added packages/scoring/src/composite.ts +60 −0
Large diff (60 lines) is not loaded by default to keep the page fast.Load diff
added packages/scoring/src/index.ts +18 −0
Large diff (18 lines) is not loaded by default to keep the page fast.Load diff
added packages/scoring/src/properties.test.ts +118 −0
Large diff (118 lines) is not loaded by default to keep the page fast.Load diff
added packages/scoring/src/task.ts +99 −0
Large diff (99 lines) is not loaded by default to keep the page fast.Load diff
added packages/scoring/src/types.ts +51 −0
Large diff (51 lines) is not loaded by default to keep the page fast.Load diff
added packages/scoring/src/version.ts +5 −0
Large diff (5 lines) is not loaded by default to keep the page fast.Load diff
added packages/scoring/src/weights.ts +30 −0
Large diff (30 lines) is not loaded by default to keep the page fast.Load diff
added packages/scoring/tsconfig.json +4 −0
Large diff (4 lines) is not loaded by default to keep the page fast.Load diff
added packages/ui/package.json +25 −0
Large diff (25 lines) is not loaded by default to keep the page fast.Load diff
added packages/ui/src/index.ts +1 −0
Large diff (1 lines) is not loaded by default to keep the page fast.Load diff
added packages/ui/src/score-band.tsx +23 −0
Large diff (23 lines) is not loaded by default to keep the page fast.Load diff
added packages/ui/tsconfig.json +8 −0
Large diff (8 lines) is not loaded by default to keep the page fast.Load diff
added pnpm-lock.yaml +3425 −0
Large diff (3,425 lines) is not loaded by default to keep the page fast.Load diff
added pnpm-workspace.yaml +3 −0
Large diff (3 lines) is not loaded by default to keep the page fast.Load diff
added turbo.json +19 −0
Large diff (20 lines) is not loaded by default to keep the page fast.Load diff
Menu

Theme