SPB Git

spb/airiskindex Public

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

TypeScript 88% Python 6.1% SQL 2.7% CSS 1.2% JavaScript 0.9% Shell 0.8%

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 5 days ago (Aug 5, 2026)

Showing 74 changed files with +7,101 and −0

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
@@ -0,0 +1,129 @@
1 +# Raw Data Sources for the AI Risk Index ETL Pipeline
2 +
3 +**Research date:** 2026-08-05 · **Author:** Claude (web research pass)
4 +**Scope:** every raw source `apps/etl` needs, with current versions, exact URLs, licensing, schema notes, and how each feeds `data/raw/``data/derived/`.
5 +
6 +> ⚠️ **Headline finding:** the project docs assume **O*NET 29.x**; the current production release is **O*NET 30.3 (May 2026)**, and **O*NET 31.0 lands in late August 2026** with a modernized Content Model. The 30.x series **renamed key files** (Technology Skills → *Software Skills*; Skills split into *Essential Skills* / *Transferable Skills*), which is a breaking change for any ETL written against 29.x file names. Pin one release per `INDEX_VERSION` and record it in the manifest.
7 +
8 +---
9 +
10 +## Master table
11 +
12 +| # | Source | Current version / vintage | Primary URL | Format | License | Update cadence | Pipeline role |
13 +|---|--------|---------------------------|-------------|--------|---------|----------------|---------------|
14 +| 1 | O*NET Database | **30.3** (May 2026); 31.0 due late Aug 2026 | https://www.onetcenter.org/database.html | Excel/CSV/JSON, SQL (MySQL/SQL Server/Oracle), text | CC BY 4.0 (attribution: USDOL/ETA) | Quarterly (Feb/May/Aug/Nov-Dec) | Core: occupations, tasks, ratings → `data/raw/onet/` |
15 +| 2 | O*NET Web Services | API v2.0 (serves 30.3) | https://services.onetcenter.org/ | REST/JSON | CC BY 4.0 + ToS | Always-current | Optional live lookups; not for bulk ETL |
16 +| 3 | SOC 2018 taxonomy | 2018 (next revision ~2028) | https://www.bls.gov/soc/2018/ | XLSX/PDF | Public domain | ~Decennial | Occupation code spine (via O*NET-SOC 2019) |
17 +| 4 | ESCO classification | **v1.2.1** (2025-12-10) | https://esco.ec.europa.eu/en/use-esco/download | CSV, SKOS/RDF, API | Free reuse (Decision 2011/833/EU) w/ attribution | ~Annual point releases | EU/FR occupation layer → `data/raw/esco/` |
18 +| 5 | ESCO↔O*NET crosswalk (official) | 2022 report; CSV updated 2023-08 (ESCO v1.1 base) | https://esco.ec.europa.eu/en/use-esco/other-crosswalks | CSV | Same as ESCO | Irregular | Map O*NET-SOC → ESCO → `data/raw/esco/` |
19 +| 6 | ROME 4.0 (France Travail) | Update of 2026-06-18; next Oct 2026 | https://www.data.gouv.fr/datasets/repertoire-operationnel-des-metiers-et-des-emplois-rome | CSV/XLSX (+ API) | Licence Ouverte 2.0 | ≥2×/year | FR occupation layer → `data/raw/rome/` |
20 +| 7 | BLS OEWS wages+employment | **May 2025** (released 2026-05-15) | https://www.bls.gov/oes/tables.htm | XLSX in ZIP | Public domain | Annual (~May) | `cost_ratio` wages, employment weights → `data/raw/bls/oews/` |
21 +| 8 | BLS Employment Projections | **2024–34** (released 2025-08-28); 2025–35 due ~Sep 2026 | https://www.bls.gov/emp/data/occupational-data.htm | XLSX | Public domain | Annual | Adoption-velocity / outlook context → `data/raw/bls/ep/` |
22 +| 9 | Eurostat SES earnings | Ref. year **2022** (4-yearly; next 2026 ref, pub ~2028) | https://ec.europa.eu/eurostat/databrowser/view/earn_ses_hourly | TSV/SDMX API | CC BY 4.0 | Every 4 years | EU wages by ISCO-08 → `data/raw/eurostat/` |
23 +| 10 | Eurostat LFS employment | Annual, latest 2025 | https://ec.europa.eu/eurostat/databrowser/view/lfsa_egai2d | TSV/SDMX API | CC BY 4.0 | Annual | EU employment by ISCO 2-digit → `data/raw/eurostat/` |
24 +| 11 | INSEE salaires (France) | 2023 consolidated (Base Tous salariés, DSN) | https://www.insee.fr/fr/statistiques (Base Tous salariés) | XLSX/CSV | Open (Insee reuse) | Annual (~2-yr lag) | FR wages by PCS → `data/raw/insee/` |
25 +| 12 | Census BTOS AI supplement | Collected 2025-11-17→2026-02-08; released **2026-04-23** | https://www.census.gov/hfp/btos/data_downloads | XLSX/CSV | Public domain (experimental) | Biweekly core; supplements episodic | `adoption_velocity` (US, by sector/state/size) → `data/raw/btos/` |
26 +| 13 | Eurostat ICT-in-enterprises AI | Survey year **2025** (19.95% EU firms use AI) | Datasets `isoc_eb_ai`, `isoc_eb_ain2` | TSV/SDMX API | CC BY 4.0 | Annual (Dec/Jan) | `adoption_velocity` (EU, by NACE) → `data/raw/eurostat/` |
27 +| 14 | Ramp AI Index | June 2026 (55.0% adoption) | https://ramp.com/data | CSV download + charts | Free; check Ramp terms | Monthly | High-frequency US adoption signal → `data/raw/ramp/` |
28 +| 15 | Anthropic Economic Index | 6th release (**2026-06-26**, "Cadences") | https://huggingface.co/datasets/Anthropic/EconomicIndex | CSV (HF dataset) | Data CC-BY; code MIT | ~Quarterly | Observed task-level AI usage; rater calibration → `data/raw/aei/` |
29 +| 16 | Stanford AI Index | **2026 edition** (9th, Apr 2026, 423 pp) | https://hai.stanford.edu/assets/files/ai_index_report_2026.pdf | PDF + public data appendix | Free (attribution) | Annual (~April) | Context stats for methodology doc / barriers |
30 +| 17 | Felten AIOE | 2021 + GenAI variants | https://github.com/AIOE-Data/AIOE | XLSX | Free w/ citation | Static | Benchmark exposure scores → `data/raw/benchmarks/` |
31 +| 18 | Eloundou "GPTs are GPTs" | Science 2024 replication | https://github.com/openai/GPTs-are-GPTs | CSV | MIT | Static | Benchmark exposure scores → `data/raw/benchmarks/` |
32 +| 19 | Webb (2020) AI exposure | 2020 | https://www.michaelwebb.co (on request) | — | On request | Static | Optional benchmark (no public bulk file) |
33 +| 20 | ILOSTAT | Continuous; incl. "employment by GenAI exposure" tables | https://ilostat.ilo.org | CSV/API | CC BY 4.0 | Continuous | Intl. harmonized employment; ILO GenAI exposure scores |
34 +
35 +---
36 +
37 +## 1. O*NET Database (core input)
38 +
39 +- **Current release:** **O*NET 30.3**, May 2026 (source: https://www.onetcenter.org/db_releases.html). Release train since the project was scoped: 29.2 (Feb 2025) → 29.3 (May 2025) → 30.0 (Aug 2025) → 30.1 (Dec 2025) → 30.2 (Feb 2026, new four-level Job Zones) → 30.3 (May 2026, **modernized Content Model** + Specific Interests) → **31.0 expected late August 2026**.
40 +- **Downloads:** https://www.onetcenter.org/database.html — formats: tabular (Excel/CSV/JSON), SQL loads for MySQL/PostgreSQL/MariaDB, SQL Server, Oracle, plus RDF. Full Excel archive: `https://www.onetcenter.org/dl_files/database/db_30_3_excel.zip` (individual files linked from the same page; per-format data dictionary at https://www.onetcenter.org/dictionary/30.3/excel/).
41 +- **Files the ETL needs (30.3 row counts):**
42 + - `Occupation Data` — 1,016 rows (O*NET-SOC code, title, description)
43 + - `Task Statements` — 18,796 rows (task_id, task, task type, incumbents responding)
44 + - `Task Ratings` — 161,559 rows (importance IM, relevance RL, frequency FT scales, with N, SE, CI bounds — feeds our task weights **and** our own CI propagation)
45 + - `Tasks to DWAs`, `DWA Reference`, `IWA Reference` — task ↔ detailed/intermediate work activity links
46 + - `Work Activities` — 73,308 rows (GWA ratings)
47 + - `Abilities` — 92,976 · `Knowledge` — 59,004 · `Work Context` — 297,676
48 + - **Renamed in 30.x:** `Technology Skills`**`Software Skills`** (31,821 rows); the old `Skills` file is now **`Essential Skills`** (17,880) + **`Transferable Skills`** (44,700). ETL loaders and any code referencing "Technology Skills" must be updated.
49 +- **License:** **CC BY 4.0**. Required attribution: credit the "O*NET 30.3 Database by the U.S. Department of Labor, Employment and Training Administration (USDOL/ETA), used under the CC BY 4.0 license." Put this in the public methodology page and API `/api/v1/methodology` metadata.
50 +- **Taxonomy:** O*NET-SOC **2019** taxonomy (built on SOC 2018): 1,016 occupation titles of which **923 are data-collection-level**; code format `XX-XXXX.XX` (SOC 6-digit + 2-digit O*NET suffix). ~891 occupations already updated in 2026 YTD.
51 +- **O*NET Web Services:** register (free) at https://services.onetcenter.org/developer/signup; API v2.0 reference at https://services.onetcenter.org/reference. **No hard rate limit**, but ToS gives per-second/per-day guidance; on `429`, retry after ≥200 ms. Always serves the latest DB — good for spot checks, **not** for reproducible scoring (use pinned dumps).
52 +- **SOC 2018:** 867 detailed occupations → 459 broad → 98 minor → 23 major groups; code `XX-XXXX`. Definitions/structure files at https://www.bls.gov/soc/2018/ (XLSX). Public domain.
53 +- **Pipeline:** fetch script downloads the pinned release ZIP into `data/raw/onet/db_30_3/`, verifies SHA-256, records release + hash in the derived manifest. Never edit in place.
54 +
55 +## 2. ESCO + crosswalks + ROME (EU/France layer)
56 +
57 +- **ESCO v1.2.1** (last update 2025-12-10). Portal: https://esco.ec.europa.eu/en · downloads (per-language CSV + "language independent" files, SKOS/RDF, Local API): https://esco.ec.europa.eu/en/use-esco/download (free account/selection flow). ~3,000 occupations mapped to ISCO-08; **13,939 skills** in v1.2.1. Web API: https://esco.ec.europa.eu/en/use-esco (base `https://ec.europa.eu/esco/api`).
58 +- **License:** free reuse under Commission Decision 2011/833/EU; must publish the ESCO acknowledgement statement (see FAQ: https://esco.ec.europa.eu/en/about-esco/faq).
59 +- **ESCO↔O*NET official crosswalk** (co-created EC + USDOL, AI-assisted with human validation):
60 + - Page: https://esco.ec.europa.eu/en/use-esco/other-crosswalks (also listed at https://www.onetcenter.org/crosswalks.html)
61 + - CSV: `https://esco.ec.europa.eu/system/files/2023-08/ONET_%28Occupations%29_0_updated.csv`
62 + - Two published variants: (a) exact/narrow/broad/close matches (QA'd, USDOL-validated); (b) same + "related" matches (**lower quality — not validated**; exclude from scoring joins by default).
63 + - Technical report: `https://esco.ec.europa.eu/system/files/2022-12/ONET%20ESCO%20Technical%20Report.pdf`. Built against **ESCO v1.1 / O*NET-SOC 2019** (the O*NET Web Services crosswalk endpoint still states ESCO v1.1.0) — re-verify concept URIs against v1.2.1 during ETL; unmatched URIs go to a QA report.
64 + - Bonus: **ESCO↔NACE crosswalk** now available: `https://esco.ec.europa.eu/system/files/2026-02/ESCO-NACE%20rev.%202.1%20crosswalk.xlsx` (useful for joining Eurostat sector-level AI adoption to occupations).
65 +- **ESCO↔ROME:** no single public EC file. France Travail maintains ROME↔ESCO correspondence tables under the EURES obligation (each member state maps its national classification to ESCO); the ROME open-data bundle includes correspondence referentials, and EURES member-state mapping tables are listed on the ESCO portal ("EURES Countries Mapping Tables"). Validate coverage during ETL and fall back to ROME→ISCO-08→ESCO if a direct table is missing for some fiches.
66 +- **ROME 4.0 (France Travail):**
67 + - Open data: https://www.data.gouv.fr/datasets/repertoire-operationnel-des-metiers-et-des-emplois-rome — multiple referential files (arborescence, compétences, contextes, mobilité). **Licence Ouverte / Open Licence 2.0.** Last update 2026-06-18; **next update announced for Oct 2026**; ≥2 updates/year.
68 + - Also via API on https://francetravail.io (ROME 4.0 APIs, OAuth key) and mirrored on https://www.francetravail.org/opendata/.
69 + - Code format: 1 letter + 4 digits (e.g., `M1607`); ~600 fiches métiers organized by 14 domaines.
70 + - Pipeline: `data/raw/rome/` with the data.gouv.fr resource URLs + version date in the manifest.
71 +
72 +## 3. Wage data
73 +
74 +- **BLS OEWS — May 2025** (released **2026-05-15**; next: May 2026 data in spring 2027). ~830 SOC occupations; employment, mean/median hourly & annual wages, wage percentiles (10/25/50/75/90), by nation/state/MSA/industry.
75 + - Tables hub: https://www.bls.gov/oes/tables.htm → ZIPs `oesm25nat.zip` (national), `oesm25st.zip` (states), `oesm25ma.zip` (metro), national-by-industry files (served from `https://www.bls.gov/oes/special-requests/…`; BLS blocks non-browser user agents — use a browser UA in the fetch script). Field layout documented in each ZIP's `field_descriptions` sheet.
76 + - License: US government work, public domain. Cadence: annual.
77 + - Pipeline: national file feeds `cost_ratio` (wage denominator, stored as **integer cents + USD** per repo convention) and employment weights; join key = SOC 2018 6-digit → O*NET-SOC 2019 (strip `.XX` suffix / use O*NET-SOC↔SOC crosswalk).
78 +- **Eurostat SES (Structure of Earnings Survey):** 4-yearly, latest reference year **2022** (published 2024–25; next ref-year 2026 published ~2028). Datasets: `earn_ses_hourly` (and monthly/annual variants) — mean/median hourly earnings by **ISCO-08 2-digit** × NACE × country. Databrowser: https://ec.europa.eu/eurostat/databrowser/view/earn_ses_hourly · bulk via SDMX API `https://ec.europa.eu/eurostat/api/dissemination/sdmx/2.1/data/earn_ses_hourly?format=TSV`. License CC BY 4.0. Occupation resolution is only 2-digit ISCO — EU `cost_ratio` will be coarser than US; flag in methodology.
79 +- **France:** INSEE *Base Tous salariés* (from DSN, formerly DADS): salaire net **EQTP** by PCS (up to 4-digit), sector, sex; latest consolidated 2023 (see Insee Première n°1938 for 2021: https://www.insee.fr/fr/statistiques/6799523; séries longues: https://www.insee.fr/fr/statistiques/8660332). DARES publishes wage/employment "portraits statistiques des métiers" by FAP. Requires PCS↔ROME/ISCO crosswalk (INSEE publishes PCS↔ISCO tables) — France-specific wage joins are a v2 concern.
80 +
81 +## 4. Employment counts & projections
82 +
83 +- **OEWS employment** (same May 2025 files as §3) — primary US employment weights.
84 +- **BLS Employment Projections 2024–34** (released 2025-08-28; **2025–35 edition expected ~Sept 2026** — recheck before ingesting): https://www.bls.gov/emp/data/occupational-data.htm — "All occupational tables in a single file (XLSX)", National Employment Matrix 2024/2034, occupational separations, plus the new **skills data tables** (importance of skills by occupation). Public domain. Feeds adoption-velocity priors and UI "outlook" context. State-level: https://projectionscentral.org/longterm (REST + download).
85 +- **Eurostat LFS:** `lfsa_egai2d` — employed persons by detailed occupation (ISCO-08 2-digit), annual: https://ec.europa.eu/eurostat/databrowser/view/lfsa_egai2d (SDMX API as above). CC BY 4.0.
86 +- **ILOSTAT:** https://ilostat.ilo.org — harmonized employment by ISCO level 2 (annual/quarterly) across countries, CSV bulk + API, CC BY 4.0. Notably now publishes **"Employment by sex and generative AI exposure"** tables (based on the ILO/Gmyrek GenAI occupational exposure scores) — both a benchmark and a ready-made employment-by-exposure aggregate.
87 +
88 +## 5. AI adoption data (`adoption_velocity` dimension)
89 +
90 +- **US Census BTOS AI supplement:** third AI supplement collected **2025-11-17 → 2026-02-08**, released **2026-04-23** (press: https://www.census.gov/newsroom/press-releases/2026/btos-apr-23.html). Measures firm AI use overall and — new this cycle — **by worker tasks and business functions**, split by NAICS sector, state, and firm size. Data hub: https://www.census.gov/hfp/btos/data (Downloads tab: https://www.census.gov/hfp/btos/data_downloads, incl. historical; API tab available). Core biweekly BTOS also carries a recurring "AI use in last two weeks" item (wording revised Nov 2025 — treat as a series break). ~1.2M-business sample; experimental data product; public domain.
91 +- **Eurostat — AI in enterprises:** datasets **`isoc_eb_ai`** (enterprises using AI technologies) and **`isoc_eb_ain2`** (by purpose/technology × NACE), survey year **2025**: 19.95% of EU enterprises use AI (55.03% of large firms); Statistics Explained article (updated Dec 2025, next Dec 2026): https://ec.europa.eu/eurostat/statistics-explained/index.php?title=Use_of_artificial_intelligence_in_enterprises. Join to occupations via sector (NACE) using the ESCO-NACE crosswalk (§2). CC BY 4.0.
92 +- **Ramp AI Index (Ramp Economics Lab):** https://ramp.com/data — monthly AI adoption among US firms from card/bill-pay spend of 70k+ businesses (June 2026: **55.0%**, +0.8 pp MoM), broken out by size and sector, downloadable; methodology: https://econlab.substack.com/p/how-ramp-data-works. Now shifting to *intensity* tracking. Free/open resource; confirm redistribution terms before committing derived aggregates.
93 +- **Anthropic Economic Index:** HF dataset **`Anthropic/EconomicIndex`** (https://huggingface.co/datasets/Anthropic/EconomicIndex). Release folders: `release_2025_02_10` (initial O*NET task mappings, automation vs augmentation), `release_2025_03_27` (cluster-level, thinking-mode fractions per O*NET task), `release_2025_09_15` (geography + 1P API), `release_2026_01_15` ("economic primitives"), `release_2026_03_24` ("learning curves"), `release_2026_06_26` ("Cadences", monthly aggregates), plus `labor_market_impacts/`. **Data CC-BY (repo metadata lists MIT for code).** This is the single most direct empirical input for calibrating our LLM-rater `automatability`/`feasibility` scores against observed usage — it is keyed to **O*NET task statements**, same spine as ours.
94 +- **Stanford AI Index 2026** (9th edition, April 2026, 423 pp): report PDF https://hai.stanford.edu/assets/files/ai_index_report_2026.pdf; public data appendix downloadable from the HAI AI Index page. Key 2026 stats: 53% population-level GenAI adoption; $172B est. US consumer surplus. Use for methodology narrative and `barriers`/`adoption_velocity` context, not row-level joins. Annual.
95 +
96 +## 6. Published occupation-level exposure benchmarks
97 +
98 +These are **validation benchmarks** (`data/raw/benchmarks/`), not scoring inputs — our methodology must remain independently reproducible.
99 +
100 +| Dataset | What it is | URL / file | License |
101 +|---|---|---|---|
102 +| **Felten–Raj–Seamans AIOE** | AI Occupational Exposure by 6-digit SOC (10 AI applications × 52 O*NET abilities); + Language-Modeling and Image-Generation GenAI variants, industry (AIIE) & geography | https://github.com/AIOE-Data/AIOE → `AIOE_DataAppendix.xlsx`, `Language Modeling AIOE and AIIE.xlsx`, `Image Generation AIOE and AIIE.xlsx` | Free; citation required (SMJ 42(12):2195–2217, 2021) |
103 +| **Eloundou et al. "GPTs are GPTs"** (Science 2024) | Task- and occupation-level LLM exposure (α=E1, β=E1+0.5·E2, γ=E1+E2; human + GPT-4 ratings) on O*NET tasks | https://github.com/openai/GPTs-are-GPTs → `occ_level.csv` (occupation), task-level in `data/` | MIT |
104 +| **Webb (2020)** | Patent-text-based AI exposure by SOC | https://www.michaelwebb.co/webb_ai.pdf — **data on request only** (no public bulk file) | On request |
105 +| **Anthropic AEI task exposure** | Observed Claude usage mapped to O*NET tasks (see §5) | HF `Anthropic/EconomicIndex` | CC-BY |
106 +| **ILO GenAI exposure (Gmyrek et al.)** | ISCO-08 occupation GenAI exposure gradients, used in ILOSTAT employment tables | via https://ilostat.ilo.org | CC BY 4.0 |
107 +| Context | Yale Budget Lab compared 7 exposure metrics (large disagreement on *most*-exposed, agreement on *least*-exposed); Brookings (Mar 2026) methodology uses AIOE + OEWS | brookings.edu methods PDF | — |
108 +
109 +Use for the sensitivity analyses in `docs/methodology/sensitivity/` (rank correlations of our composite vs AIOE / GPTs-are-GPTs / AEI).
110 +
111 +---
112 +
113 +## Ingestion order & manifest plan
114 +
115 +Order respects join dependencies (occupation spine first, then attributes, then joins):
116 +
117 +1. **SOC 2018 structure** (`data/raw/soc/`) — code spine, 867 detailed occupations.
118 +2. **O*NET 30.3 full dump** (`data/raw/onet/db_30_3/`) — pin release; load order: Occupation Data → Task Statements → Task Ratings → Tasks-to-DWAs/IWAs → Work Activities → Abilities/Knowledge → Software/Essential/Transferable Skills → Work Context. *Decision needed before build:* stay on 30.3 or wait for 31.0 (late Aug 2026) — either way, `INDEX_VERSION` notes the O*NET release.
119 +3. **OEWS May 2025** (`data/raw/bls/oews/`) — wages (→ integer cents) + employment; join on SOC 6-digit.
120 +4. **BLS EP 2024–34** (`data/raw/bls/ep/`) — projections + skills tables.
121 +5. **ESCO v1.2.1** (`data/raw/esco/classification/`) + **ESCO↔O*NET crosswalk** (`data/raw/esco/crosswalk/`) — validate URI coverage vs v1.2.1; exclude "related" matches from scoring joins.
122 +6. **ROME 4.0** (`data/raw/rome/`) + ROME↔ESCO correspondence; fallback path ROME→ISCO-08→ESCO.
123 +7. **Eurostat**: `earn_ses_hourly` (SES 2022), `lfsa_egai2d` (LFS), `isoc_eb_ai(n2)` (AI adoption) via SDMX API pulls (`data/raw/eurostat/`).
124 +8. **Adoption signals**: BTOS AI supplement, Ramp AI Index, AEI releases (`data/raw/btos|ramp|aei/`).
125 +9. **Benchmarks**: AIOE, GPTs-are-GPTs, ILO GenAI exposure (`data/raw/benchmarks/`) — validation only.
126 +
127 +**Manifest rules** (per repo convention — raw payloads are gitignored, manifests committed): for every fetched artifact record `{source, version_label, source_url, fetch_date, sha256, row_count, license, attribution_string}` in `data/derived/manifests/<source>.json`. The fetch script must fail loudly if a pinned URL 404s or the hash changes (BLS and O*NET replace files in place across releases). Re-verify before each `INDEX_VERSION` bump: O*NET quarterly page, OEWS annual page (mid-May), EP annual page (late Aug/Sep), ESCO portal (point releases), ROME (Oct 2026), BTOS supplement announcements, AEI HF repo.
128 +
129 +**Attribution block for the public methodology page:** O*NET (USDOL/ETA, CC BY 4.0) · ESCO (© European Union, ESCO acknowledgement statement) · ROME (France Travail, Licence Ouverte 2.0) · BLS/Census (public domain, cite program + vintage) · Eurostat (CC BY 4.0) · Anthropic Economic Index (CC-BY) · benchmark papers cited per their requirements.
added docs/research/03-llm-rater-api.md +329 −0
@@ -0,0 +1,329 @@
1 +# LLM Rater — Anthropic API Reference (August 2026)
2 +
3 +Practical, current reference for the task-rating pipeline in `apps/worker/src/raters/` (CLAUDE.md §6).
4 +Scope: model choice, Message Batches API, structured outputs, prompt caching, rate limits, TypeScript SDK
5 +patterns, and a concrete cost estimate for rating ~18,000 O*NET task statements × 3 samples.
6 +
7 +All facts verified against the official docs on **2026-08-05**. Note: `https://docs.claude.com/en/api/overview`
8 +(the URL in CLAUDE.md §6) now 301-redirects to `https://platform.claude.com/docs/en/api/overview` — the docs
9 +moved to `platform.claude.com`.
10 +
11 +Sources:
12 +
13 +- Models overview — https://platform.claude.com/docs/en/about-claude/models/overview
14 +- Pricing — https://platform.claude.com/docs/en/about-claude/pricing
15 +- Batch processing — https://platform.claude.com/docs/en/build-with-claude/batch-processing
16 +- Rate limits — https://platform.claude.com/docs/en/api/rate-limits
17 +- Structured outputs — https://platform.claude.com/docs/en/build-with-claude/structured-outputs
18 +- Prompt caching — https://platform.claude.com/docs/en/build-with-claude/prompt-caching
19 +
20 +---
21 +
22 +## 1. Current model lineup and pricing (per MTok, standard API)
23 +
24 +| Model | ID (set via `RATER_MODEL`) | Context / max output | Input | Output | Batch input | Batch output | Cache read |
25 +|---|---|---|---|---|---|---|---|
26 +| Claude Fable 5 | `claude-fable-5` | 1M / 128K | $10 | $50 | $5 | $25 | $1.00 |
27 +| Claude Opus 5 | `claude-opus-5` | 1M / 128K | $5 | $25 | $2.50 | $12.50 | $0.50 |
28 +| Claude Opus 4.8 | `claude-opus-4-8` | 1M / 128K | $5 | $25 | $2.50 | $12.50 | $0.50 |
29 +| Claude Sonnet 5 (intro, **through 2026-08-31**) | `claude-sonnet-5` | 1M / 128K | $2 | $10 | $1 | $5 | $0.20 |
30 +| Claude Sonnet 5 (from 2026-09-01) | `claude-sonnet-5` | 1M / 128K | $3 | $15 | $1.50 | $7.50 | $0.30 |
31 +| Claude Sonnet 4.6 | `claude-sonnet-4-6` | 1M / 128K | $3 | $15 | $1.50 | $7.50 | $0.30 |
32 +| Claude Haiku 4.5 | `claude-haiku-4-5` | 200K / 64K | $1 | $5 | $0.50 | $2.50 | $0.10 |
33 +
34 +Cache writes: 1.25× base input (5-min TTL) or 2× base input (1-hour TTL). Cache multipliers **stack with the
35 +batch discount** (confirmed in the pricing doc: "These multipliers stack with other pricing modifiers,
36 +including the Batch API discount"). All model IDs from the 4.6 generation onward are dateless pinned
37 +snapshots — no date suffix to append.
38 +
39 +Tokenizer note: models from Opus 4.7 onward (incl. Fable 5, Opus 5, Sonnet 5) use a tokenizer that yields
40 +~30% more tokens for the same text than Sonnet 4.6/Haiku 4.5. Budget token estimates per model, not globally.
41 +
42 +### Which tier for large-scale 5-point task rating?
43 +
44 +For a structured, rubric-guided 5-point rating with a short rationale (a classification-plus-justification
45 +task, not open-ended reasoning):
46 +
47 +- **Haiku 4.5** — cheapest ($0.50/$2.50 batch), fast, supports structured outputs. Likely adequate for the
48 + bulk of clear-cut tasks, but weakest calibration on ambiguous tasks — expect more >1-point disagreements
49 + flowing into the expert-panel queue (which costs human time).
50 +- **Claude Sonnet 5****recommended default.** Near-Opus quality on judgment tasks; at the introductory
51 + price ($1/$5 batch through Aug 31, 2026) the full 54k-rating job costs ~$160–250 (see §7) — the marginal
52 + cost over Haiku is trivial relative to the human-review pipeline it feeds. Caveat: adaptive thinking is on
53 + by default and thinking tokens bill as output; for this task set `thinking: {type: "disabled"}` (accepted
54 + on Sonnet 5) or keep it on with `output_config: {effort: "low"}` and budget extra output tokens.
55 +- **Opus 5 / Fable 5** — overkill for the volume run. Total quality of the index is bounded by the
56 + methodology and human validation, not by Opus-vs-Sonnet deltas on a 5-point scale. Best use: (a) rate the
57 + expert-panel calibration subset with Opus 5 as a second opinion, or (b) adjudicate flagged disagreements.
58 +
59 +Practical plan: pilot ~500 tasks on both Haiku 4.5 and Sonnet 5, compare agreement with the human 5% sample,
60 +and pick per the disagreement rate. Sampling-variance note for the 3-samples design: **Sonnet 5, Opus 5, and
61 +Opus 4.7+ reject non-default `temperature`/`top_p`/`top_k` (400 error)** — you cannot set temperature for
62 +the 3 samples on those models; between-sample variance is whatever the model naturally produces. Haiku 4.5
63 +and Sonnet 4.6 still accept temperature.
64 +
65 +## 2. Message Batches API
66 +
67 +Docs: https://platform.claude.com/docs/en/build-with-claude/batch-processing
68 +
69 +- **Endpoints:** `POST /v1/messages/batches` (create), `GET /v1/messages/batches/{id}` (poll),
70 + `GET <results_url>` (stream `.jsonl` results), `POST /v1/messages/batches/{id}/cancel`, `GET /v1/messages/batches` (list).
71 +- **Discount:** flat **50% off both input and output tokens**, all models, all features (vision, tools,
72 + structured outputs, prompt caching all supported inside batches).
73 +- **Limits:** max **100,000 requests or 256 MB** per batch, whichever comes first. Each request needs a
74 + `custom_id` matching `^[a-zA-Z0-9_-]{1,64}$`. `max_tokens` must be ≥ 1 (`max_tokens: 0` cache pre-warming
75 + is rejected inside batches).
76 +- **Timing:** most batches finish < 1 hour; hard **24-hour expiration** — requests not processed by then
77 + come back as `expired` (not billed) and must be resubmitted.
78 +- **Results:** available at `results_url` once `processing_status === "ended"`; delivered as JSONL, **in
79 + arbitrary order — always key by `custom_id`, never by position**. Results are downloadable for **29 days**
80 + after batch creation; persist them to `data/derived/ratings/` + DB immediately.
81 +- **Per-request result types:** `succeeded` (has `.result.message`), `errored` (invalid request → fix and
82 + resubmit; server error → safe to retry; not billed), `canceled` (not billed), `expired` (not billed —
83 + resubmit).
84 +- **Extended output:** Opus 5/4.8/4.7/4.6, Sonnet 5/4.6 support up to 300k output tokens in batches via the
85 + `output-300k-2026-03-24` beta header (not needed for 500-token ratings).
86 +- **No server-side idempotency key on batch create** — dedupe is your job (below).
87 +
88 +### Marrying batches with BullMQ
89 +
90 +Our invariant (CLAUDE.md §6): BullMQ job ID = deterministic hash of `task_id + prompt_version`. Extend to
91 +`hash(task_id + prompt_version + sample_index)` since each task is rated 3×. Recommended architecture:
92 +
93 +1. **`rating-request` rows, not per-rating jobs.** Persist one DB row per (task, prompt_version, sample)
94 + with status `pending`. A hex SHA-256 (truncated to 32–48 chars) of `taskId:promptVersion:sampleIdx`
95 + satisfies both the BullMQ job-ID and the batch `custom_id` charset — **use the same string for both**, so
96 + a batch result maps 1:1 to a job/row.
97 +2. **`batch-submitter` job** (BullMQ, repeatable or triggered): collects up to 100k `pending` rows, calls
98 + `batches.create()`, stores `batch_id` on the rows *before* flipping them to `submitted` — if the process
99 + dies after create but before persist, on restart list recent batches and reconcile by `custom_id` rather
100 + than re-creating (this is the idempotency seam; the API will happily accept duplicate custom_ids across
101 + batches and bill you twice).
102 +3. **`batch-poller` job** with BullMQ job ID = `poll:${batchId}` (deterministic → re-enqueue is a no-op),
103 + repeat/delay ~60s until `processing_status === "ended"`.
104 +4. **`batch-ingester`** streams results, and per result: store raw JSON response, parsed score, model,
105 + prompt version, timestamp (full audit trail); `errored`(server)/`expired` → flip row back to `pending`
106 + so the next submitter run resubmits; `errored`(invalid_request) → dead-letter for inspection.
107 +5. A prompt-version bump changes every hash → new custom_ids → old cached rows are naturally invalidated,
108 + exactly matching the CLAUDE.md rule.
109 +
110 +The whole 54,000-rating run fits in **one batch** (well under 100k requests and 256 MB), even at the Start
111 +tier queue limit (200k requests in processing queue).
112 +
113 +## 3. Structured outputs for the 5-point rating
114 +
115 +**Structured outputs is GA** (no beta header; the old `structured-outputs-2025-11-13` header and top-level
116 +`output_format` request param are deprecated transition shims). Two mechanisms:
117 +
118 +1. **JSON outputs**`output_config: {format: {type: "json_schema", schema: {...}}}` constrains the
119 + response text to schema-valid JSON.
120 +2. **Strict tool use**`strict: true` on a tool definition; guarantees `tool_use.input` validates.
121 +
122 +**Recommendation: use JSON outputs (`output_config.format`), not tool-forced JSON.** Rationale:
123 +
124 +- It's the purpose-built mechanism for "the response *is* the structured object" — no fake tool, no
125 + `tool_choice` forcing, one fewer moving part in the audit trail.
126 +- Guaranteed-valid JSON with `required` fields → the "parsed score" column can be extracted without retry
127 + loops.
128 +- Works with the **Batches API**, streaming, and thinking. (Incompatible with citations and prefilling —
129 + neither is used here.)
130 +- Schema limits that matter to us: `enum` is supported (use `"score": {"enum": [1,2,3,4,5]}` — do **not**
131 + use `minimum`/`maximum`, numeric range constraints are unsupported); no `minLength`/`maxLength` on the
132 + rationale string (enforce length in the prompt); `additionalProperties: false` is mandatory on every
133 + object.
134 +- First use of a schema pays a one-time grammar-compilation latency; compiled grammars are cached 24h —
135 + irrelevant inside a batch run that reuses one schema 54,000×. Note that **changing `output_config.format`
136 + invalidates the prompt cache**, so treat the schema like the rubric: versioned with `RATER_PROMPT_VERSION`.
137 +
138 +Suggested schema:
139 +
140 +```json
141 +{
142 + "type": "object",
143 + "properties": {
144 + "score": { "type": "integer", "enum": [1, 2, 3, 4, 5] },
145 + "rationale": { "type": "string", "description": "2-3 sentence justification citing the rubric" },
146 + "confidence": { "type": "string", "enum": ["low", "medium", "high"] }
147 + },
148 + "required": ["score", "rationale", "confidence"],
149 + "additionalProperties": false
150 +}
151 +```
152 +
153 +Structured outputs injects a system-prompt preamble explaining the format (small, fixed token overhead per
154 +request).
155 +
156 +## 4. Prompt caching for the shared rubric
157 +
158 +Layout: `tools``system``messages` renders in that order and caching is a **byte-exact prefix match**.
159 +Put the ~2k-token rubric in `system` with `cache_control` on its last block; the per-task variable content
160 +(task statement, occupation context) goes in the user message, after the breakpoint:
161 +
162 +```ts
163 +system: [
164 + { type: "text", text: RUBRIC_V3, // frozen per RATER_PROMPT_VERSION — no timestamps, no task data
165 + cache_control: { type: "ephemeral", ttl: "1h" } },
166 +],
167 +messages: [{ role: "user", content: `Occupation: ${occ}\nTask: ${taskStatement}\nRate this task.` }]
168 +```
169 +
170 +Key facts:
171 +
172 +- **Pricing:** cache read = 0.1× base input; write = 1.25× (5-min TTL) or 2× (1-hour TTL). **Stacks with the
173 + batch discount** → a cached rubric token inside a batch costs 0.05× base input.
174 +- **Inside batches use the 1-hour TTL** (official recommendation): batch requests process concurrently over
175 + up to an hour, so 5-min entries can lapse between hits. Caveat: cache hits inside a batch are
176 + best-effort — parallel workers may each miss; treat the §7 "with caching" numbers as the optimistic bound.
177 +- **Minimum cacheable prefix is model-dependent and non-monotonic:** 512 tokens (Opus 5/Fable 5), 1,024
178 + (Opus 4.8, Sonnet 5, Sonnet 4.6), 2,048 (Opus 4.7), **4,096 (Haiku 4.5, Opus 4.6)**. **A 2k-token rubric
179 + silently will not cache on Haiku 4.5** — no error, just `cache_creation_input_tokens: 0`. If Haiku is
180 + chosen, either accept uncached input (still cheap) or grow the cached prefix ≥4,096 tokens (e.g. include
181 + the scoring examples/anchors in the system block).
182 +- Cost math for the rubric alone (Sonnet 5 intro, batch, 54k requests, 2,000 tokens):
183 + uncached = 54,000 × 2,000 × $1/MTok = **$108**; cached (1 write + 54k reads at 0.05×) ≈ 108M × $0.10/MTok
184 +**$10.80**. ~10× saving on the shared-prefix portion.
185 +- Verify via `usage.cache_read_input_tokens` in each batch result; zero across the run means a silent
186 + invalidator (non-deterministic serialization, per-request content above the breakpoint).
187 +
188 +## 5. Rate limits relevant to batch rating throughput
189 +
190 +Docs: https://platform.claude.com/docs/en/api/rate-limits — organizations sit on Start / Build / Scale /
191 +Custom tiers (auto-assigned by usage history; monthly spend caps of $500 / $1,000 / $200,000).
192 +
193 +**Message Batches API has its own limits, shared across all models** (separate from Messages ITPM/OTPM):
194 +
195 +| Tier | API requests/min | Max batch requests in processing queue | Max requests per batch |
196 +|---|---|---|---|
197 +| Start | 1,000 | 200,000 | 100,000 |
198 +| Build | 2,000 | 300,000 | 100,000 |
199 +| Scale | 4,000 | 500,000 | 100,000 |
200 +
201 +Implications for us: 54,000 ratings fit in a single batch at any tier; even a full-index recompute with
202 +several prompt versions in flight stays under the Start-tier queue (200k). Batch throughput inside the
203 +queue is demand-based, not tier-based — under load, more requests may hit the 24h expiry; the ingester's
204 +resubmit path (§2) handles that.
205 +
206 +For any **synchronous** rating path (e.g. on-demand re-rate of a single task): limits are per-model RPM +
207 +ITPM/OTPM (e.g. Start tier, Sonnet 5: 1,000 RPM / 2M ITPM / 400k OTPM). **Cache reads do not count toward
208 +ITPM** on current models, so the cached rubric also multiplies effective sync throughput. Opus 5 and
209 +Sonnet 5 each have rate-limit buckets separate from the combined Opus 4.x / Sonnet 4.x pools. On 429, honor
210 +`retry-after`; the SDK does this automatically (default 2 retries).
211 +
212 +## 6. TypeScript SDK (`@anthropic-ai/sdk`) patterns
213 +
214 +The SDK auto-retries 408/409/429/5xx with exponential backoff (`maxRetries` default 2; timeout default
215 +10 min, in **milliseconds** on TS). Sketch of the worker pieces (model from `RATER_MODEL`, never hardcoded):
216 +
217 +```ts
218 +import Anthropic from "@anthropic-ai/sdk";
219 +import { createHash } from "node:crypto";
220 +
221 +const client = new Anthropic(); // ANTHROPIC_API_KEY from env
222 +const MODEL = process.env.RATER_MODEL!;
223 +const PROMPT_VERSION = process.env.RATER_PROMPT_VERSION!;
224 +
225 +export const ratingId = (taskId: string, sample: number) =>
226 + createHash("sha256").update(`${taskId}:${PROMPT_VERSION}:${sample}`).digest("hex").slice(0, 48);
227 +// valid as BullMQ job ID *and* batch custom_id (^[a-zA-Z0-9_-]{1,64}$)
228 +
229 +// --- batch-submitter job ---
230 +export async function submitBatch(rows: PendingRating[]) {
231 + const batch = await client.messages.batches.create({
232 + requests: rows.map((r) => ({
233 + custom_id: r.id, // = ratingId(...)
234 + params: {
235 + model: MODEL,
236 + max_tokens: 1024, // headroom over the ~500-token rating
237 + system: [{ type: "text" as const, text: RUBRIC,
238 + cache_control: { type: "ephemeral" as const, ttl: "1h" as const } }],
239 + output_config: { format: { type: "json_schema", schema: RATING_SCHEMA } },
240 + messages: [{ role: "user" as const, content: r.taskPrompt }],
241 + },
242 + })),
243 + });
244 + await db.markSubmitted(rows.map((r) => r.id), batch.id); // persist batch_id BEFORE returning
245 + return batch.id;
246 +}
247 +
248 +// --- batch-poller job (BullMQ delayed/repeatable, jobId: `poll:${batchId}`) ---
249 +export async function pollBatch(batchId: string): Promise<boolean> {
250 + const batch = await client.messages.batches.retrieve(batchId);
251 + return batch.processing_status === "ended"; // else re-schedule in ~60s
252 +}
253 +
254 +// --- batch-ingester job ---
255 +export async function ingestResults(batchId: string) {
256 + for await (const result of await client.messages.batches.results(batchId)) {
257 + switch (result.result.type) {
258 + case "succeeded": {
259 + const msg = result.result.message;
260 + const text = msg.content.find((b) => b.type === "text")?.text ?? "";
261 + await db.storeRating({
262 + customId: result.custom_id, // → task_id + prompt_version + sample
263 + model: msg.model,
264 + promptVersion: PROMPT_VERSION,
265 + rawResponse: JSON.stringify(msg), // full audit trail (CLAUDE.md §6)
266 + parsed: JSON.parse(text), // schema-guaranteed {score, rationale, confidence}
267 + usage: msg.usage, // incl. cache_read_input_tokens for cost telemetry
268 + ratedAt: new Date().toISOString(),
269 + });
270 + break;
271 + }
272 + case "errored":
273 + if (result.result.error.type === "invalid_request") await db.deadLetter(result.custom_id, result.result);
274 + else await db.markPending(result.custom_id); // server error — next submitter run retries
275 + break;
276 + case "expired":
277 + case "canceled":
278 + await db.markPending(result.custom_id);
279 + break;
280 + }
281 + }
282 +}
283 +```
284 +
285 +Notes: results arrive in arbitrary order — key everything by `custom_id`. For sync one-off calls, prefer
286 +`client.messages.parse()` with `zodOutputFormat(...)` from `@anthropic-ai/sdk/helpers/zod` (typed
287 +`parsed_output`); for batches, validate the JSON against the same Zod schema at ingest time. Handle errors
288 +with typed classes (`Anthropic.RateLimitError`, `Anthropic.APIError`), never string-matching.
289 +
290 +## 7. Cost estimate: 18,000 tasks × 3 samples = 54,000 ratings
291 +
292 +Assumptions: rubric 2,000 tokens (shared, cacheable), per-task suffix ~300 tokens (statement + occupation
293 +context + instruction), completion ~500 tokens. Totals: **input 124.2M tokens** (108M rubric + 16.2M
294 +variable), **output 27M tokens**. "With caching" = 1h-TTL cache, optimistic ~100% hit rate (real batch runs
295 +will land between the last two columns), cache read = 0.1× input, stacked with the 50% batch discount
296 +(0.05× net). Prices per §1.
297 +
298 +| Model | Sync, no cache | Batch only (50%) | Batch + rubric caching |
299 +|---|---:|---:|---:|
300 +| Haiku 4.5 ($1/$5) | $259 | **$130** | ≈$130 (2k rubric **below Haiku's 4,096-token cache minimum** — won't cache; pad rubric ≥4k to reach ≈$92) |
301 +| Sonnet 5 — intro thru 2026-08-31 ($2/$10) | $518 | $259 | **≈$162** ($10.80 cached rubric + $16.20 variable input + $135 output) |
302 +| Sonnet 5 / Sonnet 4.6 — standard ($3/$15) | $778 | $389 | **≈$243** ($16.20 + $24.30 + $202.50) |
303 +| Opus 5 ($5/$25) | $1,296 | $648 | **≈$405** ($27 + $40.50 + $337.50) |
304 +| Fable 5 ($10/$50) | $2,592 | $1,296 | ≈$810 (not recommended for this workload) |
305 +
306 +Takeaways:
307 +
308 +- **Output tokens dominate** once caching is on (83% of the Sonnet cost). Keep rationales tight in the
309 + prompt, and control thinking (Sonnet 5/Opus 5 think by default; thinking bills as output — disable it or
310 + set `effort: "low"` or the 500-token completion assumption breaks).
311 +- The entire volume run costs **$130–$405** depending on model — negligible against the human-expert
312 + pipeline. This argues for Sonnet 5 (or even an Opus 5 second-pass on flagged tasks) over Haiku
313 + penny-pinching; if the run happens before **2026-09-01**, Sonnet 5's intro pricing makes it ~$162.
314 +- A full re-run per `INDEX_VERSION`/`RATER_PROMPT_VERSION` bump is affordable, which supports the
315 + methodology-integrity rule that recomputations create new immutable runs.
316 +
317 +## Where the docs contradict / update CLAUDE.md assumptions
318 +
319 +1. **Docs URL moved:** CLAUDE.md §6 points to `https://docs.claude.com/en/api/overview`; that 301-redirects
320 + to `https://platform.claude.com/docs/en/api/overview`. Update the reference.
321 +2. **Temperature-based sampling is gone on current models:** if the "3 samples per task" design assumed
322 + `temperature > 0` resampling, note that Sonnet 5 / Opus 5 / Opus 4.7+ **reject** non-default
323 + `temperature`/`top_p`/`top_k` with a 400. Variance across samples is natural model stochasticity only
324 + (or use Haiku 4.5 / Sonnet 4.6, which still accept temperature).
325 +3. **`output_format` param is deprecated** — any prototype code using it should move to
326 + `output_config: {format: ...}` (GA, no beta header).
327 +4. No contradiction on the audit-trail/idempotency requirements — the Batch API's `custom_id` +
328 + result-streaming model fits the deterministic-hash design directly; the only gap is that batch **create**
329 + has no server-side idempotency key, so the submitter must persist `batch_id` transactionally (§2.2).
added docs/research/04-landscape-and-evidence.md +307 −0
@@ -0,0 +1,307 @@
1 +# 04 — Competitive Landscape & Real-World Evidence (through August 2026)
2 +
3 +Research memo for the AI Risk Index (airiskindex.io). Compiled 2026-08-05 from live web research.
4 +Purpose: (a) map existing public AI-job-risk tools, (b) assemble the 2024–2026 empirical record on AI's
5 +labor-market effects, and (c) propose how this evidence parameterizes the `barriers` and
6 +`adoption_velocity` scoring dimensions (weights 0.20 / 0.10 in v1, `packages/scoring/src/weights.ts`).
7 +
8 +---
9 +
10 +## Part A — Competitive landscape of public AI-job-risk tools
11 +
12 +### A.1 willrobotstakemyjob.com (the incumbent)
13 +
14 +- **Since 2017.** Tagline: "Find out how likely your job is to be automated — based on real data and user votes."
15 +- **Methodology:** Automation-risk probabilities produced "using a similar method" to **Frey & Osborne (2013)**
16 + ("The Future of Employment", Gaussian process classifier over 702 occupations, the famous "47% of US
17 + employment at risk" paper), re-estimated "with the most up-to-date data available", plus BLS occupation
18 + data (employment, wages, growth). Since 2019 it collects **user poll votes** on perceived risk; in 2021 it
19 + blended BLS data + polls + automation probability into a 0–10 "job score".
20 + Source: https://willrobotstakemyjob.com/about
21 +- **Critical weakness:** the underlying model is pre-LLM computerization/robotics-era work. Frey–Osborne
22 + scored *whole occupations* on physical/routine automatability (manual dexterity, cramped workspace,
23 + fine arts, social perceptiveness...), which inverts under generative AI: it rates cognitive/office work as
24 + relatively safe and misses exactly the exposure the 2024–2026 evidence shows (translators, writers,
25 + customer service, junior developers). Single doom-number framing ("X% probability of automation"),
26 + no exposure/substitution/augmentation distinction, no confidence intervals, no versioning, no API.
27 +- **Traffic (why it still matters):** ~71.3K visits in May 2026 per Semrush (down ~20% MoM), world rank
28 + ~#50,395; ~42% of traffic from Google organic, ~39% direct. Other estimators give 45K–100K+/month.
29 + Sources: https://www.semrush.com/website/willrobotstakemyjob.com/overview/ , https://hypestat.com/info/willrobotstakemyjob.com
30 +- **Monetization:** ads; no API or paid tier found.
31 +
32 +### A.2 Other public lookup tools (US/EN)
33 +
34 +| Tool | Data / method | Output | Weaknesses | Pricing/API |
35 +|---|---|---|---|---|
36 +| **willrobotstakemyjob.com** (2017) | Frey–Osborne-style re-estimation + BLS + user polls | Single automation % + 0–10 "job score" | Pre-LLM model, whole-occupation doom number, no sub-scores, no versioning | Free, ads; no API |
37 +| **replacedbyrobot.info** ("2026 AI Automation Risk Database") | Claims BLS + O\*NET over "57,000+ occupations" (job titles, not SOC codes) | "2 risk scores per job" (AI + robotics) | Opaque method, ad-heavy, title-level pseudo-precision | Free, AdSense; no API |
38 +| **aijobimpactcalculator.com** (2026, by Digital Signet) | ILO 2025 GenAI exposure gradient (4 bands) + Brookings 2024 task rubric on O\*NET 30.2 tasks + BLS EP 2024-34 + WEF FoJ 2025; static, pre-computed; published methodology & revision history | Exposure band + top-5 tasks tagged Displaceable/Changing/Growing + "what's growing" panel | Band-level only (4 bands), no composite score, no CI, US-centric | Free; no API |
39 +| **replacemeter.com** | Undisclosed (looks LLM-generated per submitted job title) | Letter grades: "AI Resilience" %, "Adaptability" % | No methodology page, arbitrary-title scoring, no provenance | Free |
40 +| **tripleten.com/tools/what-jobs-will-ai-replace** | LLM analysis of user-entered title/industry; "most recent AI research data" (uncited) | 0–100% automation risk + skills advice + career alternatives | Lead-gen for a bootcamp; unreproducible | Free (lead-gen) |
41 +| **ailayoffs.live** | Aggregates layoffs.fyi, Goldman, McKinsey, WEF; "Oxford research + real layoff data" risk checker | Live displacement counters + risk score | Doom-counter framing, mixes projections with counts | Free |
42 +| **ailayofftracker.com** / **founderreports.com/ai-layoffs-tracker** / **skillsyncer.com/layoffs-tracker** | Curated AI-cited layoff announcements (Challenger, TechCrunch sourcing) | Event lists, totals | Event trackers, not occupation scores | Free |
43 +| **techjacksolutions.com/job-displacement-trends** | Mash-up: Anthropic Economic Index %, Gartner, BLS growth, "WifiTalents" | Per-occupation risk ranges (e.g., customer service 67–80%) | Mixes usage shares with risk %, low-quality sources alongside good ones | Free |
44 +| **Stanford Canaries Dashboard** (digitaleconomy.stanford.edu) | ADP payroll microdata, 4.6M workers, 730+ occupations, continuously updated | Employment trends by age × AI exposure | Research dashboard, not per-occupation risk lookup — but the credibility benchmark | Free |
45 +
46 +Academic/institutional exposure indices that power many of these (no consumer UI of their own):
47 +**Felten–Raj–Seamans AIOE** (AI application ↔ 52 O\*NET abilities; https://sites.bu.edu/tpri/2021/06/02/occupational-industry-and-geographic-exposure-to-artificial-intelligence-a-novel-dataset-and-its-potential-uses),
48 +**ILO Global Index of Occupational Exposure to GenAI** (ISCO-08, 4 gradient bands; https://webapps.ilo.org/static/english/intserv/working-papers/wp140/index.html),
49 +**Microsoft "AI applicability score"** (Tomlinson et al. 2025, 200K Copilot conversations mapped to O\*NET work
50 +activities — top: interpreters/translators (98% activity overlap), historians, passenger attendants, sales reps,
51 +writers, customer service reps; https://www.microsoft.com/en-us/research/publication/working-with-ai-measuring-the-occupational-implications-of-generative-ai),
52 +**OpenAI "GPTs are GPTs"** (Eloundou et al., Science 2024), **Pew 2023 O\*NET work-activity classification**
53 +(https://www.pewresearch.org/social-trends/2023/07/26/2023-ai-and-jobs-methodology-for-onet-analysis).
54 +
55 +### A.3 French / EU equivalents
56 +
57 +- **jobimpact.aidoption.fr** — "Exposition IA du marché de l'emploi français": 532 ROME occupations,
58 + treemap (surface = jobs, color = 0–10 exposure), France Travail ROME + DARES data, **scored by Claude**
59 + (LLM-as-rater — directly comparable to our §6 pipeline, but with no audit trail or versioning). Has a /us/ twin.
60 +- **transitions-ia.fr** ("IA & Métiers France" observatory) — ROME 4.0 + INSEE EEC 2024 + DARES BMO 2024;
61 + task-level exposure score per métier. https://otakuch.github.io/transitions-ia.fr/
62 +- **job-guard.com** — French-language "votre métier va-t-il disparaître ?" test; editorial/affiliate quality.
63 +- **Observatoire des Emplois Menacés et Émergents + Coface study** (Nov 2025, covered by Les Échos): ~16% of
64 + French jobs at risk; white-collar metropolitan jobs most exposed (Paris ~19%, Lyon/Toulouse 18%);
65 + legal/accounting, publishing/press, IT programming/consulting, insurance, finance >25% of jobs exposed.
66 + https://www.lesechos.fr/monde/europe/ia-le-grand-bouleversement-a-venir-du-marche-du-travail-2221760
67 +- No credible official FR/EU consumer lookup exists (France Travail offers only e-learning content) → the
68 + ESCO/ROME crosswalk in our roadmap targets an **empty niche**.
69 +
70 +### A.4 SEO landscape
71 +
72 +- Queries like "will AI take my job (2026)" are dominated not by tools but by **listicle/content marketing**:
73 + Nucamp, NovoResume, Careerminds, AI Weekly, Shawn Kanungo — all citing the same WEF 92M-displaced /
74 + 170M-created / +78M-net figure, plus Goldman's −16K net jobs/month (Apr 2026). Tool sites rank on
75 + "AI job risk calculator" / "will robots take my job" variants; willrobotstakemyjob.com still owns its
76 + brand query with ~42% organic share of its traffic.
77 +- Opportunity: nothing ranking today combines (1) task-level methodology, (2) sub-scores with uncertainty,
78 + (3) live evidence (Challenger/adoption data), (4) versioned transparency, (5) non-doom adaptation framing.
79 + aijobimpactcalculator.com is the closest philosophical competitor (source-cited, anti-doom, "what's growing"
80 + panel, "how to argue with this" page) but is static, band-level, and has no composite index, no API, no EU coverage.
81 +
82 +---
83 +
84 +## Part B — Real-world evidence, 2024 → August 2026
85 +
86 +### B.1 Entry-level employment effects ("Canaries" line of evidence)
87 +
88 +- **Brynjolfsson, Chandar & Chen (Stanford/ADP), "Canaries in the Coal Mine?"** (Aug 2025, rev. Nov 13 2025):
89 + since gen-AI diffusion, workers **aged 22–25 in the most AI-exposed occupations saw a ~16% relative
90 + employment decline** (software devs 22–25 down ~20% from late-2022 peak), controlling for firm-level shocks;
91 + older workers in the same occupations stable/growing. Adjustment via **employment, not wages**. Declines
92 + **concentrated where AI automates rather than augments** (per Anthropic Economic Index task classification).
93 + https://digitaleconomy.stanford.edu/publication/canaries-in-the-coal-mine-six-facts-about-the-recent-employment-effects-of-artificial-intelligence/
94 +- **Follow-up note (Feb 9, 2026), "Canaries, Interest Rates, and Timing":** interest rates don't explain the
95 + *differential* entry-level decline in AI-exposed occupations.
96 + https://digitaleconomy.stanford.edu/news/canaries-interest-rates-and-timinga-more-on-recent-drivers-of-employment-changes-for-young-workers
97 +- **Canaries Dashboard** (2026): continuous monitoring, 4.6M workers, 730+ occupations; Brynjolfsson June 2026:
98 + "Whatever it is, it's not going away."
99 + https://digitaleconomy.stanford.edu/project/indicators/canaries-dashboard/ ,
100 + https://fortune.com/2026/06/27/what-is-ai-impact-entry-level-jobs-stanford-adp-canaries-brynjolfsson-richardson/
101 +- **UK corroboration:** Adzuna — UK entry-level vacancies **−32% since ChatGPT launch (Nov 2022 → Jun 2025)**;
102 + entry-level share of market 28.9% → 25%. Graduate vacancies **−42.1% YoY in May 2026** (worse than any
103 + pandemic month). Indeed (Jun 2025): toughest graduate market since 2018, grad roles −33% YoY.
104 + https://www.theguardian.com/business/2025/jun/30/uk-entry-level-jobs-chatgpt-launch-adzuna ,
105 + https://www.adzuna.co.uk/job-market-report
106 +- **Grad unemployment by major (NY Fed data, 2025–26):** recent CS grads **6.1%** unemployment,
107 + computer engineering **7.5%** — above the all-grad 4.8% average and above history/philosophy majors;
108 + entry-level SWE postings ~−30% YoY (Handshake 2025); CS enrollment fell >10% in 2025–26.
109 + https://interviewchamp.ai/learn/why-cs-new-grad-unemployment-hit-6-percent-2025 ,
110 + https://www.finalroundai.com/blog/computer-science-graduates-face-worst-job-market-in-decades
111 +- **Caveats to keep the index honest:** LinkedIn Economic Graph (Apr 2026) notes hiring −20% since 2022 but
112 + says it has *not* seen AI as the demonstrable cause (rates, post-2022 normalization overlap); Brookings 2025
113 + ("No AI Jobs Apocalypse, For Now") finds aggregate data doesn't yet show mass displacement.
114 +
115 +### B.2 Layoffs attributed to AI (Challenger, Gray & Christmas)
116 +
117 +- 2023: Challenger begins tracking "AI" as a stated layoff reason. **2025 full year: 54,836** AI-attributed cuts
118 + (~5% of layoffs). **2026 is the discontinuity:**
119 + - Jan 2026: AI = 7% of cuts → Mar: 25% (AI becomes **#1 cited reason for the first time**) → Apr: 21,490 cuts,
120 + 26% → **May: 38,579 cuts, 40% of all cuts — highest monthly total ever recorded** → Jun: still #1.
121 + - **H1 2026: 101,743 AI-cited cuts (~23% of all cuts), nearly 2× all of 2025.** AI #1 reason 4 consecutive
122 + months (Mar–Jun). Tech sector: 139,156 H1 cuts, +83% YoY, ~31% of all layoffs.
123 + - Sources: https://www.challengergray.com/blog/challenger-report-may-job-cuts-rise-16-from-april-highest-may-total-since-2020 ,
124 + https://www.challengergray.com/blog/challenger-report-april-job-cuts-rise-38-from-march-ytd-cuts-down-50 ,
125 + https://www.techtimes.com/articles/319588/20260703/ai-leads-us-job-cuts-record-4th-month-tech-claims-31-h1-layoffs.htm ,
126 + https://www.businessinsider.com/challenger-ai-layoffs-economy-jobs-2026-6 (Challenger itself: "not a jobpocalypse")
127 +- **Goldman Sachs (Apr 2026):** AI eliminating ~25,000 US jobs/month, creating ~9,000 → **net −16,000/month**.
128 +- **Named events:** Amazon **30,000 corporate cuts** (14K Oct 2025 + 16K Jan 2026, largest in its history; AI/
129 + automation cited as an efficiency driver; internal docs reportedly project 600K roles automated by 2033)
130 + https://www.geekwire.com/2025/amazon-reportedly-set-to-lay-off-30000-corporate-employees-in-massive-workforce-cut/ ;
131 + Salesforce cut **~4,000 customer-support roles** as AI agents absorbed workload (Benioff: "I need less heads",
132 + Sep 2025) https://fortune.com/2025/09/02/salesforce-ceo-billionaire-marc-benioff-ai-agents-jobs-layoffs-customer-service-sales/ ;
133 + HP 4,000–6,000 (Nov 2025, AI-cited).
134 +- **Counter-signal (reversal risk):** Klarna replaced ~700 support agents with AI (chatbot = work of 700), then
135 + **rehired humans through 2025–26** after CSAT dropped on complex/emotional cases → hybrid model. Quality,
136 + not cost, was the binding constraint. https://www.digitalapplied.com/blog/klarna-reverses-ai-layoffs-replacing-700-workers-backfired
137 +
138 +### B.3 Sector-specific substitution evidence
139 +
140 +- **Translation — the most-displaced occupation to date.** Microsoft applicability rank #1 (98% activity
141 + overlap). Society of Authors survey (2024): **36% of translators lost work to GenAI; 43% report income
142 + declines; 77% expect negative future income**. Individual accounts (Blood in the Machine, mid-2025): 15-yr
143 + technical translator down from six figures to €8K/yr; Quebec FR-EN translator −60% income in 2024.
144 + https://www.theguardian.com/books/2024/apr/16/survey-finds-generative-ai-proving-major-threat-to-the-work-of-translators ,
145 + https://www.bloodinthemachine.com/p/ai-killed-my-job-translators
146 +- **Customer support:** Salesforce −4,000; Klarna cycle (above); fintech cuts in May 2026 mostly AI-cited
147 + (Challenger). High exposure AND high realized substitution — but Klarna shows a quality floor.
148 +- **Software engineering:** bifurcated. Junior/entry roles: −20% employment (ages 22–25, Canaries), entry
149 + postings −30% (Handshake), CS grad unemployment 6.1%; yet BLS still projects **+15% developer growth
150 + 2024–34 (~129,200 openings/yr)** and senior demand holds. BCG (2026) classifies SWE as "amplified/divergent"
151 + rather than substituted. https://www.bcg.com/publications/2026/ai-will-reshape-more-jobs-than-it-replaces
152 +
153 +### B.4 Adoption statistics (the `adoption_velocity` evidence base)
154 +
155 +- **Census BTOS (official, firm-weighted):** AI use in *core production* 3.8% (Sep 2023) → 4.6% (early 2024) →
156 + ~10% (Sep–late 2025, doubling in ~18 months). Question broadened Nov 2025 to "any business function":
157 + **17–20% of firms Dec 2025–May 2026; 20–23% expect use within 6 months.**
158 + https://www.census.gov/library/stories/2026/05/ai-use-businesses.html
159 +- **BTOS AI Supplement (Nov 2025–Jan 2026):** **18% of firms firm-weighted = 32% employment-weighted**;
160 + very large firms in **Information / Professional Services / Finance: 50–60% (60–70% employment-weighted)**;
161 + 57% of adopters use AI in ≤3 business functions (top: sales & marketing 52%, strategy/biz-dev 45%, IT 41%);
162 + workers use AI in tasks at 23% of firms (41% employment-weighted).
163 + https://www.census.gov/library/working-papers/2026/adrm/CES-WP-26-25.html
164 +- **Fed monitoring note (Apr 2026)** reconciles the scales: BTOS firms 18%; **employment-weighted SBU 78%**;
165 + **41% of the labor force uses GenAI for work** (RPS, Nov 2025; +9.7pp YoY); 50% uses it outside work.
166 + https://www.federalreserve.gov/econres/notes/feds-notes/monitoring-ai-adoption-in-the-u-s-economy-20260403.html
167 +- **Ramp AI Index (paid adoption, 70K+ firms' card/bill spend):** businesses **paying** for AI crossed **50.4%
168 + in March 2026** (35% a year earlier; 46.8% Jan 2026); jump driven by "late majority" manufacturing/retail;
169 + VC-backed startups ~80%. Vendor race: OpenAI 35.2% vs Anthropic 30.6% of businesses (Apr 2026).
170 + https://ramp.com/data/ai-index , https://ramp.com/data/april-2026-ai-index
171 +- **McKinsey State of AI (Nov 2025):** **88% of orgs use AI in ≥1 function**, but ~two-thirds still piloting;
172 + **62% experimenting with agents, 23% scaling agents in ≥1 function, ≤10% scaling within a single function**;
173 + only **39% report any enterprise EBIT impact; ~6% are "high performers"** (>5% EBIT from AI).
174 + https://www.mckinsey.com/capabilities/quantumblack/our-insights/the-state-of-ai
175 +- **Agentic reality check:** MIT NANDA: 95% of GenAI pilots show no P&L impact; Gartner: >40% of agentic
176 + projects to be cancelled by end-2027; S&P Global/McKinsey: 31% of enterprises run ≥1 agent in production —
177 + **banking/insurance 47% vs healthcare 18% and government 14%**. Gartner: 80% of enterprise apps shipped
178 + in Q1 2026 embed an agent (vs 33% in 2024).
179 +- **France anchor:** 35% of French firms >10 employees use AI (OPIEC 2025) — EU lags US enterprise adoption.
180 +
181 +### B.5 Wage effects & augmentation-vs-substitution
182 +
183 +- **Humlum & Vestergaard (NBER w33777; Denmark, 25,000 workers × 7,000 workplaces, 11 exposed occupations,
184 + admin-linked):** AI chatbots → **precisely-estimated null on earnings and hours** (CIs rule out >1–2% average
185 + effects; occupation-level >6%); avg time savings only **2.8–3%**; >80% of saved time reallocated to other
186 + work; **8.4% of workers gained NEW tasks created by AI** (e.g., checking AI output); minimal pass-through
187 + of gains to wages. https://www.nber.org/papers/w33777 , https://www.andershumlum.com/s/chatbots_july25.pdf
188 + → Through 2024, *within-worker* wage effects ≈ 0; displacement shows up at hiring margins first (B.1).
189 +- **PwC Global AI Jobs Barometer 2026 (1B+ job ads, 27 countries):** **62% average wage premium for jobs
190 + requiring AI skills** (118% in consumer markets, 16% in government); AI-skill jobs growing 69% vs 9% market;
191 + firms most able to use AI: headcount growth 53% vs 36% and wage growth 24% vs 17% vs least-exposed —
192 + "two distinct labour-market paths". https://www.pwc.com/gx/en/news-room/press-releases/2026/pwc-2026-ai-jobs-barometer.html
193 +- **Anthropic Economic Index (Claude usage, task-mapped to O\*NET):** initial report ~**57% augmentation /
194 + 43% automation**; by Nov 2025, Claude.ai = **52% augmentation / 45% automation** (automation briefly led
195 + mid-2025); **enterprise API traffic is automation-dominant** (back-office workflows: email, document
196 + processing, CRM, scheduling). Directive/full-delegation usage rising. Reports:
197 + https://www.anthropic.com/research/anthropic-economic-index-january-2026-report ,
198 + https://www.anthropic.com/research/economic-index-march-2026-report ,
199 + https://www.anthropic.com/research/economic-index-june-2026-report
200 + → Validates keeping automation-vs-augmentation as *the* substitution discriminator (Canaries fact:
201 + employment declines concentrate in automation-dominant occupations).
202 +- **BCG (Jan 2026, Revelio 1,500 roles):** point estimate **10–15% of US jobs vulnerable over 4–5 years**;
203 + distinguishes substituted vs "divergent" (demand-expansion offsets 0.5–1.0) vs amplified roles.
204 +
205 +### B.6 Regulation & barriers (the `barriers` evidence base)
206 +
207 +- **EU AI Act — timeline moved under our feet:** GPAI obligations applied Aug 2, 2025. The **Annex III
208 + high-risk obligations (incl. ALL employment/HR AI: CV screening, targeted job ads, promotion/termination
209 + decisions, worker monitoring — Annex III pt. 4) were due Aug 2, 2026 but the "Digital Omnibus" political
210 + agreement postpones them to Dec 2, 2027.** Requirements when live: risk management, bias testing, logging,
211 + human oversight, conformity assessment. Net effect: EU employment-AI adoption friction persists but the
212 + binding date slipped ~16 months (a *barriers-lowering* event for 2026–27 velocity in the EU).
213 + https://ogletree.com/insights-resources/blog-posts/eu-nears-approval-of-agreement-to-delay-rules-for-ai-use-in-employment-decisions/ ,
214 + https://accessfinancial.com/eu-ai-act-recruitment-high-risk-hiring-2026/
215 +- **US states:** Illinois **HB 3773** effective **Jan 1, 2026** (AI discrimination in employment = civil-rights
216 + violation; notice required; zip-code-proxy ban). Colorado's AI Act (SB 24-205) delayed to Jun 30, 2026, then
217 + **replaced by narrower SB 26-189 (May 2026)**. NYC Local Law 144 (bias audits for hiring tools) ongoing.
218 + Pattern: US regulates *AI deciding about workers*, not AI *replacing* workers.
219 + https://ogletree.com/insights-resources/blog-posts/illinois-steps-up-ai-regulation-in-employment-key-takeaways-for-employers/
220 +- **Professional licensing & liability as adoption brakes (occupation-level, measurable):**
221 + - Legal: AI can't be licensed/disbarred/sworn; legal-specific AI tools show **17–34% error rates**; **700+
222 + court cases worldwide involve AI hallucinations** with sanctions → yet legal-professional GenAI adoption
223 + still jumped **31% → 69% between 2025 and 2026** (54% of firms give no training, 43% no policy).
224 + https://www.americanbar.org/groups/law_practice/resources/law-technology-today/2026/whats-really-holding-law-firms-back-from-embracing-ai/
225 + - Healthcare: litigious + risk-averse, FDA draft guidance (Jan 2025) on AI credibility in drug decisions;
226 + physician AI utilization nonetheless 38% (2023) → 72% (2026), concentrated in *administrative* tasks —
227 + augmentation inside a licensing moat.
228 + - Customer-facing quality floors: Klarna reversal (B.2) = empirical "human-contact requirement" barrier.
229 +- **Barrier taxonomy the evidence supports:** (1) statutory/licensing monopoly on the task; (2) liability &
230 + error-cost asymmetry (hallucination sanctions, malpractice); (3) regulated-process requirements (EU AI Act
231 + Annex III, state notice/audit laws); (4) human-contact/quality preference (Klarna); (5) organizational
232 + friction (MIT 95% pilot failure; McKinsey: only 6% high performers; workflow redesign is the differentiator).
233 +
234 +### B.7 Sector adoption-velocity differentials (who's fast, who's slow, why)
235 +
236 +| Sector | Signal (2025–26) | Why |
237 +|---|---|---|
238 +| Information / Tech | BTOS large-firm use 50–60%; >90% of tech companies use AI in ≥1 function | Digital-native tasks, no licensing, in-house skills |
239 +| Finance & insurance | BTOS top-3 sector; agents in production: banking/insurance 47% (S&P); NVIDIA State of AI: strongest ROI | Structured data, measurable use cases; regulation shapes but doesn't block |
240 +| Professional services (legal, consulting, accounting) | Legal GenAI 31%→69% in one year | High exposure; liability slows *delegation*, not *use* |
241 +| Telecom / Retail & CPG | Agentic adoption 48% / 47% (NVIDIA) | Customer-ops scale economics |
242 +| Manufacturing / logistics | Fastest %-growth in AI job postings; Ramp "late majority" surge drove the 50% crossing (Mar 2026) | Started low; predictive maintenance, quality control |
243 +| Healthcare | Physician use 38%→72% but agents-in-production only 18%; admin-first | Licensing, liability, FDA; augmentation-dominant |
244 +| Government / public sector | Agents 14%; lowest AI wage premium (16%) | Procurement, accountability, unionization |
245 +| Construction / trades | ~1.4% adoption (laggard anecdote); BTOS small-firm use <20% | Physical, unstructured, small-firm dominated |
246 +
247 +Cross-cutting velocity facts: firm size is the strongest adoption predictor (BTOS: <20% for ≤4-employee firms);
248 +employment-weighted adoption ≈ 2× firm-weighted; paid adoption (Ramp 50.4%) runs far ahead of production-grade
249 +deployment (31% ≥1 agent in production) which runs ahead of measured P&L impact (39% any EBIT effect).
250 +
251 +---
252 +
253 +## Inputs for `barriers` & `adoption_velocity` scoring
254 +
255 +### adoption_velocity (weight 0.10) — proposed parameterization
256 +
257 +Score each occupation's dominant sector(s) (via BLS OES industry-occupation matrix) on observable adoption,
258 +not vendor hype. Candidate sub-indicators, each normalizable to 0–1 with a public, refreshable source:
259 +
260 +1. **Sector AI-use rate, employment-weighted** — BTOS bi-weekly sector series + AI Supplement
261 + (CES-WP-26-25). Anchors: Information ≈ 0.9 · construction ≈ 0.1.
262 +2. **Sector adoption momentum** — 12-month delta in BTOS use rate and/or Ramp AI Index sector series
263 + (captures manufacturing/retail late-majority acceleration, Mar 2026).
264 +3. **Agentic deployment depth** — % of sector firms with agents in production (S&P Global/McKinsey:
265 + banking 47% … government 14%). Agents, not chatbots, are the substitution-relevant margin.
266 +4. **Realized displacement intensity** — Challenger AI-cited cuts by industry, trailing 12m, scaled by sector
267 + employment (chemicals, fintech, tech score high in 2026); optionally corroborated by Canaries Dashboard
268 + 22–25 employment trend for the occupation itself.
269 +5. **Occupation-level usage intensity** — Anthropic Economic Index share of usage mapped to the occupation's
270 + O\*NET tasks, split augmentation vs automation (automation share feeds substitution, not just velocity).
271 +
272 +Suggested v1 formula: `adoption_velocity = 0.35·(1) + 0.20·(2) + 0.20·(3) + 0.15·(4) + 0.10·(5)`, with the
273 +caveat documented in METHODOLOGY.md that (1)–(3) are sector-level priors and (4)–(5) are occupation-level
274 +correctors. Update cadence: quarterly (BTOS bi-weekly, Ramp monthly, Challenger monthly, AEI ~quarterly) —
275 +each refresh = new `INDEX_VERSION` patch run.
276 +
277 +### barriers (weight 0.20) — proposed parameterization
278 +
279 +Score as *adoption friction* (high barriers ⇒ lower net risk), with five components:
280 +
281 +1. **Licensing/authorization requirement (0–1):** does task sign-off legally require a licensed human
282 + (law, medicine, engineering PE, aviation, finance advice)? Source: O\*NET Job Zone + state licensing DBs.
283 + Evidence: legal/medical augment-don't-substitute pattern (B.6).
284 +2. **Liability & error-cost asymmetry (0–1):** cost of a wrong AI output (malpractice, sanctions, safety).
285 + Proxy: occupation's litigation exposure + documented AI-error sanction record (700+ hallucination cases).
286 +3. **Regulatory-process coverage (0–1):** is the occupation's automation itself regulated? EU AI Act Annex III
287 + (now Dec 2027), Illinois HB 3773, Colorado SB 26-189, NYC LL144 — maintain a dated rule table per
288 + jurisdiction; **this component is jurisdiction-specific** (US vs EU scores diverge; the Omnibus delay is a
289 + worked example of a barrier score *dropping* between index versions).
290 +4. **Human-contact requirement (0–1):** reuse O\*NET work-context variables ("contact with others",
291 + "deal with external customers", physical proximity). Empirical anchor: Klarna reversal; CSAT floors.
292 +5. **Organizational/implementation friction (0–1):** sector pilot-failure and scaling rates (MIT 95%,
293 + McKinsey 6% high performers, Gartner >40% agent-project cancellations) — a global dampener that decays
294 + over index versions as deployment matures.
295 +
296 +Suggested v1 formula: `barriers = 0.30·licensing + 0.25·liability + 0.20·regulatory + 0.15·human_contact + 0.10·org_friction`.
297 +
298 +### Positioning implications (for public copy, "adaptation not doom")
299 +
300 +- The 2026 record supports **differentiated, hedged claims**: entry-level exposure is real and measured
301 + (−16% relative, Stanford/ADP; Challenger 101,743 H1-2026 AI-cited cuts) while incumbent wage/hours effects
302 + are so far null (Denmark) and AI-skill premia are large (+62%, PwC). That *is* our three-concept split —
303 + exposure ≠ substitution ≠ augmentation — now empirically vindicated; no competitor surfaces it.
304 +- Publish a "what would change this score" section per occupation (aijobimpactcalculator.com's
305 + "how to argue with this" page is the only competitor doing epistemic honesty — match and exceed it).
306 +- Every headline stat above has a reversal or caveat attached (Klarna, LinkedIn attribution caution,
307 + Brookings "no apocalypse yet") — cite these in-product to keep the tone guide credible.
added docs/research/README.md +41 −0
@@ -0,0 +1,41 @@
1 +# Research Corpus — AI Risk Index (compiled 2026-08-05)
2 +
3 +Comprehensive web research to ground the v1 methodology, ETL pipeline, LLM rater, and product positioning. Each document carries full source URLs; claims are dated.
4 +
5 +| Doc | Contents |
6 +|---|---|
7 +| [01-existing-indices.md](01-existing-indices.md) | 20+ AI job-exposure indices & studies (Frey–Osborne → Eloundou → ILO/IMF/OECD → Anthropic Economic Index → 2025–26 frontier), comparison table, validation literature, implications for our 5 dimensions |
8 +| [02-data-sources.md](02-data-sources.md) | 20 raw data sources with verified URLs, versions, licenses, cadences: O*NET, ESCO/ROME, OEWS wages, employment projections, AI-adoption surveys, benchmark exposure datasets; ingestion & manifest plan |
9 +| [03-llm-rater-api.md](03-llm-rater-api.md) | Current Anthropic model lineup/pricing, Message Batches API, structured outputs (GA), prompt caching, BullMQ integration pattern, cost estimates for the full rating job |
10 +| [04-landscape-and-evidence.md](04-landscape-and-evidence.md) | Competitor analysis (US + FR/EU), measured 2024–26 labor-market effects, adoption stats, regulatory barriers; proposed parameterization of `adoption_velocity` and `barriers` |
11 +
12 +## Headline takeaways
13 +
14 +**Methodology (01)**
15 +- Single-number occupation scores have a poor empirical record: individual indices explain <11% of realized unemployment risk; ensembles reach 30–75% (Frank et al., PNAS Nexus 2025). Our sub-score + CI design is the right call.
16 +- Yin et al. (2026) found a **19× spread** in Eloundou-style exposure headlines depending on which frontier LLM rates the tasks (2.7%–51.5%). ⇒ Rate with multiple models (or multiple samples) and derive `score_low/score_high` from rater disagreement; publish rater identity per run.
17 +- Rate **automation vs. augmentation separately per task** (ILO WP140 and the Anthropic Economic Index both discriminate on this; the Stanford "Canaries" employment effects concentrate in automation-exposed occupations).
18 +- `cost_ratio` is an under-researched dimension no published index operationalizes well — a differentiation opportunity.
19 +
20 +**Data (02)**
21 +- ⚠️ **O*NET is at 30.3 (May 2026); 31.0 lands late Aug 2026.** CLAUDE.md §1 says 29.x — update it. Breaking schema change in 30.x: *Technology Skills → Software Skills*, Skills split into Essential/Transferable.
22 +- ESCO v1.2.1 + official ESCO↔O*NET crosswalk CSV (built on v1.1, revalidate URIs); ROME 4.0 on data.gouv.fr (Licence Ouverte).
23 +- Wages: OEWS May 2025 (released 2026-05-15; BLS blocks non-browser user agents — set UA in fetch scripts).
24 +- Anthropic Economic Index on Hugging Face (6 releases, CC-BY, keyed to O*NET tasks) is the best empirical calibration source for the rater and for `adoption_velocity`.
25 +
26 +**Rater pipeline (03)**
27 +- Recommended rater: **Sonnet 5** (pilot vs Haiku 4.5 against the 5% human-review sample). Full job (~18k tasks × 3 samples) fits in **one** Message Batch; ~$130–$250 with batch discount + prompt caching.
28 +- Structured outputs is GA — use JSON schema with the 5-point score as an `enum`, not tool-forcing.
29 +- ⚠️ `temperature` is rejected on Sonnet 5/Opus 5 — the 3-sample variance design must rely on model/prompt diversity instead of sampling temperature. Haiku 4.5 has a 4,096-token cache minimum (a 2k rubric won't cache there).
30 +- Docs moved: docs.claude.com → platform.claude.com.
31 +
32 +**Positioning (04)**
33 +- No competitor combines task-level scoring, sub-scores with CIs, versioned methodology, EU/France coverage, and a public API. Incumbent willrobotstakemyjob.com (~71k visits/mo) still runs pre-LLM Frey–Osborne with a single doom number. The French market is nearly empty.
34 +- Evidence base for tone guide ("adaptation, not doom"): effects so far are concentrated (entry-level, automation-exposed occupations: −16% relative employment ages 22–25), not economy-wide; augmentation usage still dominates in AEI data.
35 +- `adoption_velocity` and `barriers` can each be built from 4–5 refreshable public sources (BTOS, Ramp, Challenger, AEI; licensing/liability/AI-Act coverage) — concrete formulas proposed in doc 04 §final.
36 +
37 +## Decisions this research forces (flag before implementation)
38 +
39 +1. Bump CLAUDE.md's O*NET reference 29.x → 30.3/31.0 and plan ETL for the 30.x schema renames.
40 +2. Choose the rater-variance mechanism (multi-model panel vs. multi-prompt) given no `temperature` on Sonnet 5.
41 +3. Decide whether v1 rates automation and augmentation as two separate task-level LLM ratings (research strongly says yes; affects prompt design, cost ×2, and the composite formula).
added eslint.config.mjs +25 −0
@@ -0,0 +1,25 @@
1 +import js from "@eslint/js";
2 +import tseslint from "typescript-eslint";
3 +
4 +export default tseslint.config(
5 + {
6 + ignores: [
7 + "**/node_modules/**",
8 + "**/.next/**",
9 + "**/dist/**",
10 + "**/.turbo/**",
11 + "apps/etl/**",
12 + "**/next-env.d.ts",
13 + ],
14 + },
15 + js.configs.recommended,
16 + ...tseslint.configs.recommended,
17 + {
18 + rules: {
19 + "@typescript-eslint/no-unused-vars": [
20 + "error",
21 + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
22 + ],
23 + },
24 + },
25 +);
added infra/.env.example +20 −0
@@ -0,0 +1,20 @@
1 +# Template only — real values live in /srv/airiskindex/.env (never committed).
2 +DATABASE_URL=
3 +REDIS_URL=
4 +ANTHROPIC_API_KEY=
5 +RATER_MODELS=
6 +# Optional cross-provider rater panel keys
7 +OPENAI_API_KEY=
8 +XAI_API_KEY=
9 +MISTRAL_API_KEY=
10 +GEMINI_API_KEY=
11 +DASHSCOPE_API_KEY=
12 +DEEPINFRA_API_KEY=
13 +CEREBRAS_API_KEY=
14 +PERPLEXITY_API_KEY=
15 +DEEPSEEK_API_KEY=
16 +KIMI_API_KEY=
17 +NGROK_AUTHTOKEN=
18 +NEXTAUTH_URL=
19 +NEXTAUTH_SECRET=
20 +PUBLIC_BASE_URL=
added infra/deploy.sh +19 −0
@@ -0,0 +1,19 @@
1 +#!/usr/bin/env bash
2 +# Deploy to m3u96b (staging/current prod) — CLAUDE.md §7. Run ON the node.
3 +set -euo pipefail
4 +
5 +cd /srv/airiskindex
6 +
7 +git pull --ff-only origin main
8 +pnpm install --frozen-lockfile
9 +pnpm build
10 +pnpm db:migrate:deploy
11 +
12 +sudo systemctl restart airiskindex-web airiskindex-worker
13 +sudo systemctl status airiskindex-web --no-pager
14 +
15 +# Localhost health, then the PUBLIC URL — tunnel failures are the most common outage cause.
16 +curl -fsS http://127.0.0.1:3000/api/v1/health
17 +curl -fsS https://www.airiskindex.io/api/v1/health
18 +
19 +echo "deploy OK"
added infra/docker-compose.dev.yml +19 −0
@@ -0,0 +1,19 @@
1 +services:
2 + postgres:
3 + image: postgres:16
4 + environment:
5 + POSTGRES_USER: airiskindex
6 + POSTGRES_PASSWORD: airiskindex
7 + POSTGRES_DB: airiskindex
8 + ports:
9 + - "127.0.0.1:5432:5432"
10 + volumes:
11 + - pgdata:/var/lib/postgresql/data
12 +
13 + redis:
14 + image: redis:7
15 + ports:
16 + - "127.0.0.1:6379:6379"
17 +
18 +volumes:
19 + pgdata:
added infra/docker-compose.prod.yml +23 −0
@@ -0,0 +1,23 @@
1 +# Production data services on m3u96b (CLAUDE.md §7). App processes run under
2 +# systemd (airiskindex-web/worker), not in Docker.
3 +services:
4 + postgres:
5 + image: postgres:16
6 + restart: always
7 + environment:
8 + POSTGRES_USER: ${POSTGRES_USER:?}
9 + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?}
10 + POSTGRES_DB: airiskindex
11 + ports:
12 + - "127.0.0.1:5432:5432"
13 + volumes:
14 + - pgdata:/var/lib/postgresql/data
15 +
16 + redis:
17 + image: redis:7
18 + restart: always
19 + ports:
20 + - "127.0.0.1:6379:6379"
21 +
22 +volumes:
23 + pgdata:
added infra/ngrok.yml +11 −0
@@ -0,0 +1,11 @@
1 +# Copied to /etc/ngrok/ngrok.yml on m3u96b (CLAUDE.md §7).
2 +# Custom domain must be configured in the ngrok dashboard + DNS CNAME for
3 +# www.airiskindex.io. Verify with: dig CNAME www.airiskindex.io
4 +version: 3
5 +agent:
6 + authtoken: ${NGROK_AUTHTOKEN}
7 +endpoints:
8 + - name: airiskindex
9 + url: https://www.airiskindex.io
10 + upstream:
11 + url: 3000
added package.json +29 −0
@@ -0,0 +1,29 @@
1 +{
2 + "name": "airiskindex",
3 + "private": true,
4 + "packageManager": "pnpm@9.6.0",
5 + "engines": {
6 + "node": ">=20"
7 + },
8 + "scripts": {
9 + "dev": "turbo dev --filter=@airiskindex/web",
10 + "build": "turbo build",
11 + "test": "turbo test",
12 + "test:e2e": "pnpm --filter @airiskindex/web test:e2e",
13 + "lint": "eslint .",
14 + "typecheck": "turbo typecheck",
15 + "format": "prettier --write .",
16 + "db:migrate": "pnpm --filter @airiskindex/db db:migrate",
17 + "db:migrate:deploy": "pnpm --filter @airiskindex/db db:migrate:deploy",
18 + "db:seed": "pnpm --filter @airiskindex/db db:seed",
19 + "score:recompute": "pnpm --filter @airiskindex/worker score:recompute"
20 + },
21 + "devDependencies": {
22 + "@eslint/js": "^9.8.0",
23 + "eslint": "^9.8.0",
24 + "prettier": "^3.3.3",
25 + "turbo": "^2.0.9",
26 + "typescript": "^5.5.4",
27 + "typescript-eslint": "^8.0.0"
28 + }
29 +}
added packages/config/package.json +6 −0
@@ -0,0 +1,6 @@
1 +{
2 + "name": "@airiskindex/config",
3 + "version": "0.0.0",
4 + "private": true,
5 + "description": "Shared tsconfig presets for the monorepo"
6 +}
added packages/config/tsconfig/base.json +16 −0
@@ -0,0 +1,16 @@
1 +{
2 + "$schema": "https://json.schemastore.org/tsconfig",
3 + "compilerOptions": {
4 + "strict": true,
5 + "target": "ES2022",
6 + "lib": ["ES2022"],
7 + "module": "Preserve",
8 + "moduleResolution": "Bundler",
9 + "esModuleInterop": true,
10 + "skipLibCheck": true,
11 + "forceConsistentCasingInFileNames": true,
12 + "resolveJsonModule": true,
13 + "isolatedModules": true,
14 + "noEmit": true
15 + }
16 +}
added packages/config/tsconfig/nextjs.json +13 −0
@@ -0,0 +1,13 @@
1 +{
2 + "$schema": "https://json.schemastore.org/tsconfig",
3 + "extends": "./base.json",
4 + "compilerOptions": {
5 + "lib": ["dom", "dom.iterable", "ES2022"],
6 + "jsx": "preserve",
7 + "module": "esnext",
8 + "moduleResolution": "bundler",
9 + "allowJs": true,
10 + "incremental": true,
11 + "plugins": [{ "name": "next" }]
12 + }
13 +}
added packages/db/package.json +30 −0
@@ -0,0 +1,30 @@
1 +{
2 + "name": "@airiskindex/db",
3 + "version": "0.0.0",
4 + "private": true,
5 + "type": "module",
6 + "main": "./src/index.ts",
7 + "types": "./src/index.ts",
8 + "exports": {
9 + ".": {
10 + "types": "./src/index.ts",
11 + "default": "./src/index.ts"
12 + }
13 + },
14 + "scripts": {
15 + "postinstall": "prisma generate",
16 + "db:migrate": "prisma migrate dev",
17 + "db:migrate:deploy": "prisma migrate deploy",
18 + "db:seed": "tsx prisma/seed.ts",
19 + "typecheck": "tsc --noEmit"
20 + },
21 + "dependencies": {
22 + "@prisma/client": "^5.17.0"
23 + },
24 + "devDependencies": {
25 + "@types/node": "^20.14.11",
26 + "prisma": "^5.17.0",
27 + "tsx": "^4.16.2",
28 + "typescript": "^5.5.4"
29 + }
30 +}
added packages/db/prisma/schema.prisma +150 −0
@@ -0,0 +1,150 @@
1 +// AI Risk Index — database schema.
2 +// Methodology integrity (CLAUDE.md §9): every published score traces to a
3 +// ScoreRun; runs are immutable — recomputations create new runs.
4 +
5 +generator client {
6 + provider = "prisma-client-js"
7 +}
8 +
9 +datasource db {
10 + provider = "postgresql"
11 + url = env("DATABASE_URL")
12 +}
13 +
14 +model Occupation {
15 + code String @id // O*NET-SOC 2019, e.g. "15-1252.00"
16 + title String
17 + description String?
18 + // Crosswalks (METHODOLOGY.md §2); crosswalk loss flagged per occupation
19 + escoUri String?
20 + romeCode String?
21 + crosswalkNote String?
22 + // Wages as integer cents + ISO currency (CLAUDE.md §5)
23 + medianWageCents Int?
24 + wageCurrency String @default("USD")
25 + employment Int?
26 +
27 + tasks Task[]
28 + scores OccupationScore[]
29 +}
30 +
31 +model Task {
32 + id String @id // O*NET Task ID
33 + occupationCode String
34 + statement String
35 + /// O*NET Task Ratings importance (IM, 1–5)
36 + importance Float?
37 +
38 + occupation Occupation @relation(fields: [occupationCode], references: [code])
39 + ratings TaskRating[]
40 + overrides ExpertOverride[]
41 + scores TaskScore[]
42 +
43 + @@index([occupationCode])
44 +}
45 +
46 +/// One LLM rating of one task on one dimension by one model — full audit
47 +/// trail (CLAUDE.md §6): model, prompt version, raw response, parsed score.
48 +model TaskRating {
49 + id String @id @default(cuid())
50 + taskId String
51 + /// DimensionKey from packages/scoring, or "augmentation"
52 + dimension String
53 + model String
54 + promptVersion String
55 + sampleIndex Int @default(0)
56 + rating Int // 1–5
57 + rationale String?
58 + rawResponse Json
59 + ratedAt DateTime @default(now())
60 +
61 + task Task @relation(fields: [taskId], references: [id])
62 +
63 + @@unique([taskId, dimension, model, promptVersion, sampleIndex])
64 + @@index([taskId, dimension])
65 + @@index([promptVersion])
66 +}
67 +
68 +/// Expert (Delphi) override: replaces the LLM rating band for a task ×
69 +/// dimension. Flagged in API output (METHODOLOGY.md §3).
70 +model ExpertOverride {
71 + id String @id @default(cuid())
72 + taskId String
73 + dimension String
74 + ratingLow Int
75 + ratingMid Float
76 + ratingHigh Int
77 + reviewer String
78 + note String?
79 + createdAt DateTime @default(now())
80 +
81 + task Task @relation(fields: [taskId], references: [id])
82 +
83 + @@unique([taskId, dimension])
84 +}
85 +
86 +/// Immutable computation run. Old runs remain queryable forever.
87 +model ScoreRun {
88 + id String @id @default(cuid())
89 + indexVersion String
90 + raterPromptVersion String
91 + raterModels String[]
92 + notes String?
93 + createdAt DateTime @default(now())
94 +
95 + occupationScores OccupationScore[]
96 + taskScores TaskScore[]
97 +
98 + @@index([indexVersion])
99 +}
100 +
101 +model OccupationScore {
102 + id String @id @default(cuid())
103 + runId String
104 + occupationCode String
105 + substitutionLow Float
106 + substitution Float
107 + substitutionHigh Float
108 + exposureLow Float
109 + exposure Float
110 + exposureHigh Float
111 + augmentationLow Float
112 + augmentation Float
113 + augmentationHigh Float
114 + highlyExposedTaskShare Float
115 +
116 + run ScoreRun @relation(fields: [runId], references: [id])
117 + occupation Occupation @relation(fields: [occupationCode], references: [code])
118 +
119 + @@unique([runId, occupationCode])
120 + @@index([occupationCode])
121 +}
122 +
123 +model TaskScore {
124 + id String @id @default(cuid())
125 + runId String
126 + taskId String
127 + substitutionLow Float
128 + substitution Float
129 + substitutionHigh Float
130 + exposureLow Float
131 + exposure Float
132 + exposureHigh Float
133 + augmentationLow Float
134 + augmentation Float
135 + augmentationHigh Float
136 +
137 + run ScoreRun @relation(fields: [runId], references: [id])
138 + task Task @relation(fields: [taskId], references: [id])
139 +
140 + @@unique([runId, taskId])
141 + @@index([taskId])
142 +}
143 +
144 +model ApiKey {
145 + id String @id @default(cuid())
146 + hashedKey String @unique
147 + name String
148 + rateLimitPerMin Int @default(600)
149 + createdAt DateTime @default(now())
150 +}
added packages/db/prisma/seed.ts +46 −0
@@ -0,0 +1,46 @@
1 +import { readFileSync } from "node:fs";
2 +import { PrismaClient } from "@prisma/client";
3 +
4 +// Seeds the synthetic methodology example occupation so the app and API have
5 +// demo data before the real O*NET ETL runs. Real data: `cd apps/etl && make pipeline`.
6 +const prisma = new PrismaClient();
7 +
8 +const example = JSON.parse(
9 + readFileSync(new URL("../../../docs/methodology/examples/example-analyst.json", import.meta.url), "utf8"),
10 +);
11 +
12 +async function main(): Promise<void> {
13 + const { code, title } = example.occupation;
14 + await prisma.occupation.upsert({
15 + where: { code },
16 + update: { title },
17 + create: {
18 + code,
19 + title,
20 + description:
21 + "Synthetic occupation from the published methodology example. Not real O*NET data.",
22 + },
23 + });
24 +
25 + for (const task of example.tasks) {
26 + await prisma.task.upsert({
27 + where: { id: `${code}-${task.taskId}` },
28 + update: { importance: task.importance },
29 + create: {
30 + id: `${code}-${task.taskId}`,
31 + occupationCode: code,
32 + statement: `Synthetic task ${task.taskId} (see docs/methodology/examples/)`,
33 + importance: task.importance,
34 + },
35 + });
36 + }
37 +
38 + console.log(`Seeded example occupation ${code} with ${example.tasks.length} tasks.`);
39 +}
40 +
41 +main()
42 + .catch((error) => {
43 + console.error(error);
44 + process.exitCode = 1;
45 + })
46 + .finally(() => prisma.$disconnect());
added packages/db/src/index.ts +12 −0
@@ -0,0 +1,12 @@
1 +import { PrismaClient } from "@prisma/client";
2 +
3 +// Singleton to avoid exhausting connections under Next.js dev hot-reload.
4 +const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
5 +
6 +export const prisma = globalForPrisma.prisma ?? new PrismaClient();
7 +
8 +if (process.env.NODE_ENV !== "production") {
9 + globalForPrisma.prisma = prisma;
10 +}
11 +
12 +export * from "@prisma/client";
added packages/db/tsconfig.json +4 −0
@@ -0,0 +1,4 @@
1 +{
2 + "extends": "../config/tsconfig/base.json",
3 + "include": ["src", "prisma"]
4 +}
added packages/scoring/package.json +24 −0
@@ -0,0 +1,24 @@
1 +{
2 + "name": "@airiskindex/scoring",
3 + "version": "1.0.0",
4 + "private": true,
5 + "type": "module",
6 + "main": "./src/index.ts",
7 + "types": "./src/index.ts",
8 + "exports": {
9 + ".": {
10 + "types": "./src/index.ts",
11 + "default": "./src/index.ts"
12 + }
13 + },
14 + "scripts": {
15 + "test": "vitest run",
16 + "typecheck": "tsc --noEmit"
17 + },
18 + "devDependencies": {
19 + "@types/node": "^20.14.11",
20 + "fast-check": "^3.20.0",
21 + "typescript": "^5.5.4",
22 + "vitest": "^2.0.4"
23 + }
24 +}
added packages/scoring/src/composite.test.ts +71 −0
@@ -0,0 +1,71 @@
1 +import { readFileSync } from "node:fs";
2 +import { describe, expect, it } from "vitest";
3 +import { HIGH_EXPOSURE_THRESHOLD, scoreOccupation } from "./index";
4 +
5 +// Published worked example — METHODOLOGY.md §5-6. Changing it requires an INDEX_VERSION bump.
6 +const example = JSON.parse(
7 + readFileSync(new URL("../../../docs/methodology/examples/example-analyst.json", import.meta.url), "utf8"),
8 +);
9 +
10 +const BAND_KEYS = ["substitution", "exposure", "augmentation"] as const;
11 +
12 +describe("published methodology example (example-analyst.json)", () => {
13 + const result = scoreOccupation(example.tasks);
14 +
15 + it("matches the expected task scores", () => {
16 + expect(result.tasks).toHaveLength(example.expected.taskScores.length);
17 + for (const [index, expected] of example.expected.taskScores.entries()) {
18 + const actual = result.tasks[index];
19 + expect(actual.taskId).toBe(expected.taskId);
20 + for (const key of BAND_KEYS) {
21 + expect(actual[key].low).toBeCloseTo(expected[key].low, 3);
22 + expect(actual[key].score).toBeCloseTo(expected[key].score, 3);
23 + expect(actual[key].high).toBeCloseTo(expected[key].high, 3);
24 + }
25 + }
26 + });
27 +
28 + it("matches the expected occupation scores", () => {
29 + for (const key of BAND_KEYS) {
30 + expect(result[key].low).toBeCloseTo(example.expected.occupation[key].low, 3);
31 + expect(result[key].score).toBeCloseTo(example.expected.occupation[key].score, 3);
32 + expect(result[key].high).toBeCloseTo(example.expected.occupation[key].high, 3);
33 + }
34 + });
35 +
36 + it("reports the highly exposed task share", () => {
37 + expect(HIGH_EXPOSURE_THRESHOLD).toBe(70);
38 + expect(result.highlyExposedTaskShare).toBeCloseTo(
39 + example.expected.occupation.highlyExposedTaskShare,
40 + 3,
41 + );
42 + });
43 +});
44 +
45 +describe("scoreOccupation input handling", () => {
46 + it("rejects an empty task list", () => {
47 + expect(() => scoreOccupation([])).toThrow(RangeError);
48 + });
49 +
50 + it("fills missing importance with the occupation mean", () => {
51 + const [t1, t2, t3] = example.tasks;
52 + const withMissing = [t1, { ...t2, importance: undefined }, t3];
53 + const result = scoreOccupation(withMissing);
54 + // t2 gets mean(4, 1) = 2.5 → weights 4/7.5, 2.5/7.5, 1/7.5
55 + const expected =
56 + (4 / 7.5) * 71.25 + (2.5 / 7.5) * 27.5 + (1 / 7.5) * 86.25;
57 + expect(result.substitution.score).toBeCloseTo(expected, 3);
58 + });
59 +
60 + it("rejects out-of-range ratings", () => {
61 + const bad = JSON.parse(JSON.stringify(example.tasks[0]));
62 + bad.ratings.automatability.mid = 6;
63 + expect(() => scoreOccupation([bad])).toThrow(RangeError);
64 + });
65 +
66 + it("rejects inverted bands (low > high)", () => {
67 + const bad = JSON.parse(JSON.stringify(example.tasks[0]));
68 + bad.ratings.feasibility = { low: 4, mid: 3, high: 2 };
69 + expect(() => scoreOccupation([bad])).toThrow(RangeError);
70 + });
71 +});
added packages/scoring/src/composite.ts +60 −0
@@ -0,0 +1,60 @@
1 +import { scoreTask } from "./task";
2 +import type { OccupationScores, ScoreBand, TaskInput, TaskScores } from "./types";
3 +import { INDEX_VERSION } from "./version";
4 +
5 +/** Substitution score at or above which a task counts as "highly exposed" — METHODOLOGY.md §6. */
6 +export const HIGH_EXPOSURE_THRESHOLD = 70;
7 +
8 +/**
9 + * Aggregate task scores to an occupation: importance-weighted mean, weights
10 + * normalized within the occupation — METHODOLOGY.md §6. Pure and deterministic.
11 + */
12 +export function scoreOccupation(tasks: readonly TaskInput[]): OccupationScores {
13 + if (tasks.length === 0) {
14 + throw new RangeError("scoreOccupation: at least one task is required");
15 + }
16 +
17 + const taskScores = tasks.map(scoreTask);
18 +
19 + const provided = tasks
20 + .map((task) => task.importance)
21 + .filter((value): value is number => value !== undefined);
22 + for (const value of provided) {
23 + if (!Number.isFinite(value) || value < 0) {
24 + throw new RangeError(`scoreOccupation: importance must be a non-negative number, got ${value}`);
25 + }
26 + }
27 + // Tasks lacking an importance rating receive the occupation-mean importance (§6).
28 + const fallback =
29 + provided.length > 0 ? provided.reduce((sum, value) => sum + value, 0) / provided.length : 1;
30 + const raw = tasks.map((task) => task.importance ?? fallback);
31 + const rawSum = raw.reduce((sum, value) => sum + value, 0);
32 + const weights = rawSum > 0 ? raw.map((value) => value / rawSum) : raw.map(() => 1 / raw.length);
33 +
34 + const aggregate = (pick: (task: TaskScores) => ScoreBand): ScoreBand => {
35 + let low = 0;
36 + let score = 0;
37 + let high = 0;
38 + for (const [index, task] of taskScores.entries()) {
39 + const weight = weights[index];
40 + const band = pick(task);
41 + low += weight * band.low;
42 + score += weight * band.score;
43 + high += weight * band.high;
44 + }
45 + return { low, score, high };
46 + };
47 +
48 + const highlyExposedTaskShare =
49 + taskScores.filter((task) => task.substitution.score >= HIGH_EXPOSURE_THRESHOLD).length /
50 + taskScores.length;
51 +
52 + return {
53 + indexVersion: INDEX_VERSION,
54 + substitution: aggregate((task) => task.substitution),
55 + exposure: aggregate((task) => task.exposure),
56 + augmentation: aggregate((task) => task.augmentation),
57 + highlyExposedTaskShare,
58 + tasks: taskScores,
59 + };
60 +}
added packages/scoring/src/index.ts +18 −0
@@ -0,0 +1,18 @@
1 +export { HIGH_EXPOSURE_THRESHOLD, scoreOccupation } from "./composite";
2 +export { pressure, scoreTask } from "./task";
3 +export type {
4 + OccupationScores,
5 + RatingBand,
6 + ScoreBand,
7 + TaskInput,
8 + TaskRatings,
9 + TaskScores,
10 +} from "./types";
11 +export { INDEX_VERSION } from "./version";
12 +export {
13 + DIMENSIONS,
14 + type DimensionKey,
15 + EXPOSURE_DIMENSIONS,
16 + INVERTED_DIMENSIONS,
17 + WEIGHTS,
18 +} from "./weights";
added packages/scoring/src/properties.test.ts +118 −0
@@ -0,0 +1,118 @@
1 +import fc from "fast-check";
2 +import { describe, expect, it } from "vitest";
3 +import type { RatingBand, TaskInput, TaskRatings } from "./index";
4 +import { scoreOccupation, scoreTask, WEIGHTS } from "./index";
5 +
6 +const ratingBand = fc
7 + .tuple(
8 + fc.integer({ min: 1, max: 5 }),
9 + fc.integer({ min: 1, max: 5 }),
10 + fc.integer({ min: 1, max: 5 }),
11 + )
12 + .map(([a, b, c]): RatingBand => {
13 + const [low, mid, high] = [a, b, c].sort((x, y) => x - y);
14 + return { low, mid, high };
15 + });
16 +
17 +const taskRatings = fc.record<TaskRatings>({
18 + automatability: ratingBand,
19 + feasibility: ratingBand,
20 + cost_ratio: ratingBand,
21 + barriers: ratingBand,
22 + adoption_velocity: ratingBand,
23 + augmentation: ratingBand,
24 +});
25 +
26 +const taskInput = fc.record<TaskInput>({
27 + taskId: fc.hexaString({ minLength: 1, maxLength: 8 }),
28 + importance: fc.double({ min: 0.1, max: 5, noNaN: true }),
29 + ratings: taskRatings,
30 +});
31 +
32 +const occupationTasks = fc.array(taskInput, { minLength: 1, maxLength: 8 });
33 +
34 +const flat = (rating: number): RatingBand => ({ low: rating, mid: rating, high: rating });
35 +
36 +describe("scoring invariants (property-based)", () => {
37 + it("weights sum to 1", () => {
38 + const sum = Object.values(WEIGHTS).reduce((total, weight) => total + weight, 0);
39 + expect(sum).toBeCloseTo(1, 10);
40 + });
41 +
42 + it("all score bands satisfy 0 ≤ low ≤ score ≤ high ≤ 100", () => {
43 + fc.assert(
44 + fc.property(occupationTasks, (tasks) => {
45 + const result = scoreOccupation(tasks);
46 + const bands = [
47 + result.substitution,
48 + result.exposure,
49 + result.augmentation,
50 + ...result.tasks.flatMap((task) => [task.substitution, task.exposure, task.augmentation]),
51 + ];
52 + for (const band of bands) {
53 + expect(band.low).toBeGreaterThanOrEqual(-1e-9);
54 + expect(band.score).toBeGreaterThanOrEqual(band.low - 1e-9);
55 + expect(band.high).toBeGreaterThanOrEqual(band.score - 1e-9);
56 + expect(band.high).toBeLessThanOrEqual(100 + 1e-9);
57 + }
58 + expect(result.highlyExposedTaskShare).toBeGreaterThanOrEqual(0);
59 + expect(result.highlyExposedTaskShare).toBeLessThanOrEqual(1);
60 + }),
61 + );
62 + });
63 +
64 + it("is deterministic", () => {
65 + fc.assert(
66 + fc.property(occupationTasks, (tasks) => {
67 + expect(JSON.stringify(scoreOccupation(tasks))).toBe(JSON.stringify(scoreOccupation(tasks)));
68 + }),
69 + );
70 + });
71 +
72 + it("substitution increases with automatability and decreases with barriers", () => {
73 + const flatRating = fc.integer({ min: 1, max: 5 });
74 + fc.assert(
75 + fc.property(
76 + fc.record({
77 + feasibility: flatRating,
78 + cost_ratio: flatRating,
79 + adoption_velocity: flatRating,
80 + augmentation: flatRating,
81 + }),
82 + fc.integer({ min: 1, max: 4 }),
83 + (rest, rating) => {
84 + const withDims = (automatability: number, barriers: number): number =>
85 + scoreTask({
86 + taskId: "t",
87 + ratings: {
88 + automatability: flat(automatability),
89 + feasibility: flat(rest.feasibility),
90 + cost_ratio: flat(rest.cost_ratio),
91 + barriers: flat(barriers),
92 + adoption_velocity: flat(rest.adoption_velocity),
93 + augmentation: flat(rest.augmentation),
94 + },
95 + }).substitution.score;
96 + expect(withDims(rating + 1, 3)).toBeGreaterThan(withDims(rating, 3));
97 + expect(withDims(3, rating + 1)).toBeLessThan(withDims(3, rating));
98 + },
99 + ),
100 + );
101 + });
102 +
103 + it("is invariant to uniform scaling of importance weights", () => {
104 + fc.assert(
105 + fc.property(occupationTasks, fc.double({ min: 0.5, max: 10, noNaN: true }), (tasks, k) => {
106 + const scaled = tasks.map((task) => ({
107 + ...task,
108 + importance: (task.importance ?? 1) * k,
109 + }));
110 + const a = scoreOccupation(tasks);
111 + const b = scoreOccupation(scaled);
112 + expect(b.substitution.score).toBeCloseTo(a.substitution.score, 6);
113 + expect(b.exposure.score).toBeCloseTo(a.exposure.score, 6);
114 + expect(b.augmentation.score).toBeCloseTo(a.augmentation.score, 6);
115 + }),
116 + );
117 + });
118 +});
added packages/scoring/src/task.ts +99 −0
@@ -0,0 +1,99 @@
1 +import type { RatingBand, ScoreBand, TaskInput, TaskScores } from "./types";
2 +import {
3 + DIMENSIONS,
4 + type DimensionKey,
5 + EXPOSURE_DIMENSIONS,
6 + INVERTED_DIMENSIONS,
7 + WEIGHTS,
8 +} from "./weights";
9 +
10 +function assertRating(value: number, context: string): void {
11 + if (!Number.isFinite(value) || value < 1 || value > 5) {
12 + throw new RangeError(`${context}: rating must be within [1, 5], got ${value}`);
13 + }
14 +}
15 +
16 +function assertBand(band: RatingBand, context: string): void {
17 + assertRating(band.low, `${context}.low`);
18 + assertRating(band.mid, `${context}.mid`);
19 + assertRating(band.high, `${context}.high`);
20 + if (band.low > band.mid || band.mid > band.high) {
21 + throw new RangeError(`${context}: band must satisfy low ≤ mid ≤ high`);
22 + }
23 +}
24 +
25 +interface PressureBand {
26 + low: number;
27 + mid: number;
28 + high: number;
29 +}
30 +
31 +/** METHODOLOGY.md §5: rating (1–5) → substitution pressure in [0, 1]. */
32 +export function pressure(dimension: DimensionKey, rating: number): number {
33 + const p = (rating - 1) / 4;
34 + return INVERTED_DIMENSIONS.has(dimension) ? 1 - p : p;
35 +}
36 +
37 +/**
38 + * Pressure bounds for a rating band. For inverted dimensions the rating's
39 + * HIGH bound minimizes pressure, so the bounds swap — this is what keeps
40 + * low ≤ score ≤ high true for every dimension orientation.
41 + */
42 +function pressureBand(dimension: DimensionKey, band: RatingBand): PressureBand {
43 + const inverted = INVERTED_DIMENSIONS.has(dimension);
44 + return {
45 + low: pressure(dimension, inverted ? band.high : band.low),
46 + mid: pressure(dimension, band.mid),
47 + high: pressure(dimension, inverted ? band.low : band.high),
48 + };
49 +}
50 +
51 +function weightedScore(
52 + pressures: ReadonlyMap<DimensionKey, PressureBand>,
53 + dimensions: readonly DimensionKey[],
54 + bound: keyof PressureBand,
55 +): number {
56 + let total = 0;
57 + let weightSum = 0;
58 + for (const dimension of dimensions) {
59 + const band = pressures.get(dimension);
60 + if (!band) throw new RangeError(`missing pressure for dimension "${dimension}"`);
61 + total += WEIGHTS[dimension] * band[bound];
62 + weightSum += WEIGHTS[dimension];
63 + }
64 + return (100 * total) / weightSum;
65 +}
66 +
67 +function toScoreBand(compute: (bound: keyof PressureBand) => number): ScoreBand {
68 + return { low: compute("low"), score: compute("mid"), high: compute("high") };
69 +}
70 +
71 +/** Score a single task — formulas in METHODOLOGY.md §5. Pure and deterministic. */
72 +export function scoreTask(input: TaskInput): TaskScores {
73 + const pressures = new Map<DimensionKey, PressureBand>();
74 + for (const dimension of DIMENSIONS) {
75 + const band = input.ratings[dimension];
76 + if (!band) {
77 + throw new RangeError(`task ${input.taskId}: missing rating for dimension "${dimension}"`);
78 + }
79 + assertBand(band, `task ${input.taskId}.${dimension}`);
80 + pressures.set(dimension, pressureBand(dimension, band));
81 + }
82 +
83 + const augmentation = input.ratings.augmentation;
84 + if (!augmentation) {
85 + throw new RangeError(`task ${input.taskId}: missing augmentation rating`);
86 + }
87 + assertBand(augmentation, `task ${input.taskId}.augmentation`);
88 +
89 + return {
90 + taskId: input.taskId,
91 + substitution: toScoreBand((bound) => weightedScore(pressures, DIMENSIONS, bound)),
92 + exposure: toScoreBand((bound) => weightedScore(pressures, EXPOSURE_DIMENSIONS, bound)),
93 + augmentation: {
94 + low: (100 * (augmentation.low - 1)) / 4,
95 + score: (100 * (augmentation.mid - 1)) / 4,
96 + high: (100 * (augmentation.high - 1)) / 4,
97 + },
98 + };
99 +}
added packages/scoring/src/types.ts +51 −0
@@ -0,0 +1,51 @@
1 +import type { DimensionKey } from "./weights";
2 +
3 +/**
4 + * A 1–5 rating band. low/high span the disagreement across the multi-model
5 + * rater panel (or an expert override) — METHODOLOGY.md §3.
6 + */
7 +export interface RatingBand {
8 + low: number;
9 + mid: number;
10 + high: number;
11 +}
12 +
13 +/**
14 + * Per-task ratings: the five composite dimensions plus the separate
15 + * augmentation rating (not part of the substitution composite).
16 + */
17 +export type TaskRatings = Record<DimensionKey | "augmentation", RatingBand>;
18 +
19 +export interface TaskInput {
20 + taskId: string;
21 + /**
22 + * O*NET Task Ratings importance (IM, 1–5). Omitted → occupation-mean
23 + * importance — METHODOLOGY.md §6.
24 + */
25 + importance?: number;
26 + ratings: TaskRatings;
27 +}
28 +
29 +/** A 0–100 score with confidence bounds; low ≤ score ≤ high always holds. */
30 +export interface ScoreBand {
31 + low: number;
32 + score: number;
33 + high: number;
34 +}
35 +
36 +export interface TaskScores {
37 + taskId: string;
38 + substitution: ScoreBand;
39 + exposure: ScoreBand;
40 + augmentation: ScoreBand;
41 +}
42 +
43 +export interface OccupationScores {
44 + indexVersion: string;
45 + substitution: ScoreBand;
46 + exposure: ScoreBand;
47 + augmentation: ScoreBand;
48 + /** Share of tasks with substitution score ≥ HIGH_EXPOSURE_THRESHOLD — METHODOLOGY.md §6. */
49 + highlyExposedTaskShare: number;
50 + tasks: TaskScores[];
51 +}
added packages/scoring/src/version.ts +5 −0
@@ -0,0 +1,5 @@
1 +/**
2 + * Index version (semver). Bump on any change to weights, formulas, or rater
3 + * prompts, with a docs/methodology/CHANGELOG.md entry — METHODOLOGY.md §7.
4 + */
5 +export const INDEX_VERSION = "1.0.0-draft.1";
added packages/scoring/src/weights.ts +30 −0
@@ -0,0 +1,30 @@
1 +/**
2 + * Composite dimension weights — the single source of truth (CLAUDE.md §1:
3 + * never hardcode weights anywhere else). METHODOLOGY.md §4.
4 + */
5 +export const DIMENSIONS = [
6 + "automatability",
7 + "feasibility",
8 + "cost_ratio",
9 + "barriers",
10 + "adoption_velocity",
11 +] as const;
12 +
13 +export type DimensionKey = (typeof DIMENSIONS)[number];
14 +
15 +export const WEIGHTS: Record<DimensionKey, number> = {
16 + automatability: 0.35,
17 + feasibility: 0.2,
18 + cost_ratio: 0.15,
19 + barriers: 0.2,
20 + adoption_velocity: 0.1,
21 +};
22 +
23 +/**
24 + * Dimensions where a higher rating means LESS substitution pressure
25 + * (stronger barriers protect the task) — METHODOLOGY.md §4 "orientation".
26 + */
27 +export const INVERTED_DIMENSIONS: ReadonlySet<DimensionKey> = new Set(["barriers"]);
28 +
29 +/** Dimensions composing the exposure sub-score — METHODOLOGY.md §5. */
30 +export const EXPOSURE_DIMENSIONS: readonly DimensionKey[] = ["automatability", "feasibility"];
added packages/scoring/tsconfig.json +4 −0
@@ -0,0 +1,4 @@
1 +{
2 + "extends": "../config/tsconfig/base.json",
3 + "include": ["src"]
4 +}
added packages/ui/package.json +25 −0
@@ -0,0 +1,25 @@
1 +{
2 + "name": "@airiskindex/ui",
3 + "version": "0.0.0",
4 + "private": true,
5 + "type": "module",
6 + "main": "./src/index.ts",
7 + "types": "./src/index.ts",
8 + "exports": {
9 + ".": {
10 + "types": "./src/index.ts",
11 + "default": "./src/index.ts"
12 + }
13 + },
14 + "scripts": {
15 + "typecheck": "tsc --noEmit"
16 + },
17 + "peerDependencies": {
18 + "react": "^18.3.1"
19 + },
20 + "devDependencies": {
21 + "@types/react": "^18.3.3",
22 + "react": "^18.3.1",
23 + "typescript": "^5.5.4"
24 + }
25 +}
added packages/ui/src/index.ts +1 −0
@@ -0,0 +1 @@
1 +export { ScoreBandPill, type ScoreBandProps } from "./score-band";
added packages/ui/src/score-band.tsx +23 −0
@@ -0,0 +1,23 @@
1 +export interface ScoreBandProps {
2 + label: string;
3 + low: number;
4 + score: number;
5 + high: number;
6 +}
7 +
8 +/**
9 + * Displays a 0–100 score with its confidence interval. The UI must always be
10 + * able to display uncertainty (CLAUDE.md §1) — never render `score` without
11 + * offering the band.
12 + */
13 +export function ScoreBandPill({ label, low, score, high }: ScoreBandProps): JSX.Element {
14 + return (
15 + <span className="inline-flex items-baseline gap-2 rounded-full border border-slate-300 px-3 py-1 text-sm">
16 + <span className="font-medium text-slate-700">{label}</span>
17 + <span className="text-lg font-semibold tabular-nums">{score.toFixed(0)}</span>
18 + <span className="text-xs text-slate-500 tabular-nums">
19 + {low.toFixed(0)}–{high.toFixed(0)}
20 + </span>
21 + </span>
22 + );
23 +}
added packages/ui/tsconfig.json +8 −0
@@ -0,0 +1,8 @@
1 +{
2 + "extends": "../config/tsconfig/base.json",
3 + "compilerOptions": {
4 + "lib": ["dom", "dom.iterable", "ES2022"],
5 + "jsx": "react-jsx"
6 + },
7 + "include": ["src"]
8 +}
added pnpm-lock.yaml +3425 −0
@@ -0,0 +1,3425 @@
1 +lockfileVersion: '9.0'
2 +
3 +settings:
4 + autoInstallPeers: true
5 + excludeLinksFromLockfile: false
6 +
7 +importers:
8 +
9 + .:
10 + devDependencies:
11 + '@eslint/js':
12 + specifier: ^9.8.0
13 + version: 9.39.5
14 + eslint:
15 + specifier: ^9.8.0
16 + version: 9.39.5(jiti@1.21.7)
17 + prettier:
18 + specifier: ^3.3.3
19 + version: 3.9.6
20 + turbo:
21 + specifier: ^2.0.9
22 + version: 2.10.8
23 + typescript:
24 + specifier: ^5.5.4
25 + version: 5.9.3
26 + typescript-eslint:
27 + specifier: ^8.0.0
28 + version: 8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)
29 +
30 + apps/web:
31 + dependencies:
32 + '@airiskindex/db':
33 + specifier: workspace:*
34 + version: link:../../packages/db
35 + '@airiskindex/scoring':
36 + specifier: workspace:*
37 + version: link:../../packages/scoring
38 + '@airiskindex/ui':
39 + specifier: workspace:*
40 + version: link:../../packages/ui
41 + next:
42 + specifier: ^14.2.5
43 + version: 14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
44 + react:
45 + specifier: ^18.3.1
46 + version: 18.3.1
47 + react-dom:
48 + specifier: ^18.3.1
49 + version: 18.3.1(react@18.3.1)
50 + devDependencies:
51 + '@types/node':
52 + specifier: ^20.14.11
53 + version: 20.19.43
54 + '@types/react':
55 + specifier: ^18.3.3
56 + version: 18.3.31
57 + '@types/react-dom':
58 + specifier: ^18.3.0
59 + version: 18.3.7(@types/react@18.3.31)
60 + autoprefixer:
61 + specifier: ^10.4.19
62 + version: 10.5.4(postcss@8.5.25)
63 + postcss:
64 + specifier: ^8.4.39
65 + version: 8.5.25
66 + tailwindcss:
67 + specifier: ^3.4.6
68 + version: 3.4.19(tsx@4.23.6)
69 + typescript:
70 + specifier: ^5.5.4
71 + version: 5.9.3
72 +
73 + apps/worker:
74 + dependencies:
75 + '@airiskindex/db':
76 + specifier: workspace:*
77 + version: link:../../packages/db
78 + '@airiskindex/scoring':
79 + specifier: workspace:*
80 + version: link:../../packages/scoring
81 + '@anthropic-ai/sdk':
82 + specifier: '>=0.32.1 <1'
83 + version: 0.115.0(zod@3.25.76)
84 + bullmq:
85 + specifier: ^5.8.7
86 + version: 5.81.3
87 + ioredis:
88 + specifier: ^5.4.1
89 + version: 5.11.1
90 + zod:
91 + specifier: ^3.23.8
92 + version: 3.25.76
93 + devDependencies:
94 + '@types/node':
95 + specifier: ^20.14.11
96 + version: 20.19.43
97 + tsx:
98 + specifier: ^4.16.2
99 + version: 4.23.6
100 + typescript:
101 + specifier: ^5.5.4
102 + version: 5.9.3
103 +
104 + packages/config: {}
105 +
106 + packages/db:
107 + dependencies:
108 + '@prisma/client':
109 + specifier: ^5.17.0
110 + version: 5.22.0(prisma@5.22.0)
111 + devDependencies:
112 + '@types/node':
113 + specifier: ^20.14.11
114 + version: 20.19.43
115 + prisma:
116 + specifier: ^5.17.0
117 + version: 5.22.0
118 + tsx:
119 + specifier: ^4.16.2
120 + version: 4.23.6
121 + typescript:
122 + specifier: ^5.5.4
123 + version: 5.9.3
124 +
125 + packages/scoring:
126 + devDependencies:
127 + '@types/node':
128 + specifier: ^20.14.11
129 + version: 20.19.43
130 + fast-check:
131 + specifier: ^3.20.0
132 + version: 3.23.2
133 + typescript:
134 + specifier: ^5.5.4
135 + version: 5.9.3
136 + vitest:
137 + specifier: ^2.0.4
138 + version: 2.1.9(@types/node@20.19.43)
139 +
140 + packages/ui:
141 + devDependencies:
142 + '@types/react':
143 + specifier: ^18.3.3
144 + version: 18.3.31
145 + react:
146 + specifier: ^18.3.1
147 + version: 18.3.1
148 + typescript:
149 + specifier: ^5.5.4
150 + version: 5.9.3
151 +
152 +packages:
153 +
154 + '@alloc/quick-lru@5.2.0':
155 + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
156 + engines: {node: '>=10'}
157 +
158 + '@anthropic-ai/sdk@0.115.0':
159 + resolution: {integrity: sha512-BJrFIVyjNuU8lfDyIJTvlRYzgQg+zEl78BxE7fq8esULsGz9IRQvGtW5spq3tydmtjQb/GFdooKGdGsetpx+lQ==}
160 + hasBin: true
161 + peerDependencies:
162 + zod: ^3.25.0 || ^4.0.0
163 + peerDependenciesMeta:
164 + zod:
165 + optional: true
166 +
167 + '@babel/runtime@7.29.7':
168 + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
169 + engines: {node: '>=6.9.0'}
170 +
171 + '@esbuild/aix-ppc64@0.21.5':
172 + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
173 + engines: {node: '>=12'}
174 + cpu: [ppc64]
175 + os: [aix]
176 +
177 + '@esbuild/aix-ppc64@0.28.1':
178 + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==}
179 + engines: {node: '>=18'}
180 + cpu: [ppc64]
181 + os: [aix]
182 +
183 + '@esbuild/android-arm64@0.21.5':
184 + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
185 + engines: {node: '>=12'}
186 + cpu: [arm64]
187 + os: [android]
188 +
189 + '@esbuild/android-arm64@0.28.1':
190 + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==}
191 + engines: {node: '>=18'}
192 + cpu: [arm64]
193 + os: [android]
194 +
195 + '@esbuild/android-arm@0.21.5':
196 + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
197 + engines: {node: '>=12'}
198 + cpu: [arm]
199 + os: [android]
200 +
201 + '@esbuild/android-arm@0.28.1':
202 + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==}
203 + engines: {node: '>=18'}
204 + cpu: [arm]
205 + os: [android]
206 +
207 + '@esbuild/android-x64@0.21.5':
208 + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
209 + engines: {node: '>=12'}
210 + cpu: [x64]
211 + os: [android]
212 +
213 + '@esbuild/android-x64@0.28.1':
214 + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==}
215 + engines: {node: '>=18'}
216 + cpu: [x64]
217 + os: [android]
218 +
219 + '@esbuild/darwin-arm64@0.21.5':
220 + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
221 + engines: {node: '>=12'}
222 + cpu: [arm64]
223 + os: [darwin]
224 +
225 + '@esbuild/darwin-arm64@0.28.1':
226 + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==}
227 + engines: {node: '>=18'}
228 + cpu: [arm64]
229 + os: [darwin]
230 +
231 + '@esbuild/darwin-x64@0.21.5':
232 + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
233 + engines: {node: '>=12'}
234 + cpu: [x64]
235 + os: [darwin]
236 +
237 + '@esbuild/darwin-x64@0.28.1':
238 + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==}
239 + engines: {node: '>=18'}
240 + cpu: [x64]
241 + os: [darwin]
242 +
243 + '@esbuild/freebsd-arm64@0.21.5':
244 + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
245 + engines: {node: '>=12'}
246 + cpu: [arm64]
247 + os: [freebsd]
248 +
249 + '@esbuild/freebsd-arm64@0.28.1':
250 + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==}
251 + engines: {node: '>=18'}
252 + cpu: [arm64]
253 + os: [freebsd]
254 +
255 + '@esbuild/freebsd-x64@0.21.5':
256 + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
257 + engines: {node: '>=12'}
258 + cpu: [x64]
259 + os: [freebsd]
260 +
261 + '@esbuild/freebsd-x64@0.28.1':
262 + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==}
263 + engines: {node: '>=18'}
264 + cpu: [x64]
265 + os: [freebsd]
266 +
267 + '@esbuild/linux-arm64@0.21.5':
268 + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
269 + engines: {node: '>=12'}
270 + cpu: [arm64]
271 + os: [linux]
272 +
273 + '@esbuild/linux-arm64@0.28.1':
274 + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==}
275 + engines: {node: '>=18'}
276 + cpu: [arm64]
277 + os: [linux]
278 +
279 + '@esbuild/linux-arm@0.21.5':
280 + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
281 + engines: {node: '>=12'}
282 + cpu: [arm]
283 + os: [linux]
284 +
285 + '@esbuild/linux-arm@0.28.1':
286 + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==}
287 + engines: {node: '>=18'}
288 + cpu: [arm]
289 + os: [linux]
290 +
291 + '@esbuild/linux-ia32@0.21.5':
292 + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
293 + engines: {node: '>=12'}
294 + cpu: [ia32]
295 + os: [linux]
296 +
297 + '@esbuild/linux-ia32@0.28.1':
298 + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==}
299 + engines: {node: '>=18'}
300 + cpu: [ia32]
301 + os: [linux]
302 +
303 + '@esbuild/linux-loong64@0.21.5':
304 + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
305 + engines: {node: '>=12'}
306 + cpu: [loong64]
307 + os: [linux]
308 +
309 + '@esbuild/linux-loong64@0.28.1':
310 + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==}
311 + engines: {node: '>=18'}
312 + cpu: [loong64]
313 + os: [linux]
314 +
315 + '@esbuild/linux-mips64el@0.21.5':
316 + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
317 + engines: {node: '>=12'}
318 + cpu: [mips64el]
319 + os: [linux]
320 +
321 + '@esbuild/linux-mips64el@0.28.1':
322 + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==}
323 + engines: {node: '>=18'}
324 + cpu: [mips64el]
325 + os: [linux]
326 +
327 + '@esbuild/linux-ppc64@0.21.5':
328 + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
329 + engines: {node: '>=12'}
330 + cpu: [ppc64]
331 + os: [linux]
332 +
333 + '@esbuild/linux-ppc64@0.28.1':
334 + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==}
335 + engines: {node: '>=18'}
336 + cpu: [ppc64]
337 + os: [linux]
338 +
339 + '@esbuild/linux-riscv64@0.21.5':
340 + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
341 + engines: {node: '>=12'}
342 + cpu: [riscv64]
343 + os: [linux]
344 +
345 + '@esbuild/linux-riscv64@0.28.1':
346 + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==}
347 + engines: {node: '>=18'}
348 + cpu: [riscv64]
349 + os: [linux]
350 +
351 + '@esbuild/linux-s390x@0.21.5':
352 + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
353 + engines: {node: '>=12'}
354 + cpu: [s390x]
355 + os: [linux]
356 +
357 + '@esbuild/linux-s390x@0.28.1':
358 + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==}
359 + engines: {node: '>=18'}
360 + cpu: [s390x]
361 + os: [linux]
362 +
363 + '@esbuild/linux-x64@0.21.5':
364 + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
365 + engines: {node: '>=12'}
366 + cpu: [x64]
367 + os: [linux]
368 +
369 + '@esbuild/linux-x64@0.28.1':
370 + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==}
371 + engines: {node: '>=18'}
372 + cpu: [x64]
373 + os: [linux]
374 +
375 + '@esbuild/netbsd-arm64@0.28.1':
376 + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==}
377 + engines: {node: '>=18'}
378 + cpu: [arm64]
379 + os: [netbsd]
380 +
381 + '@esbuild/netbsd-x64@0.21.5':
382 + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
383 + engines: {node: '>=12'}
384 + cpu: [x64]
385 + os: [netbsd]
386 +
387 + '@esbuild/netbsd-x64@0.28.1':
388 + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==}
389 + engines: {node: '>=18'}
390 + cpu: [x64]
391 + os: [netbsd]
392 +
393 + '@esbuild/openbsd-arm64@0.28.1':
394 + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==}
395 + engines: {node: '>=18'}
396 + cpu: [arm64]
397 + os: [openbsd]
398 +
399 + '@esbuild/openbsd-x64@0.21.5':
400 + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
401 + engines: {node: '>=12'}
402 + cpu: [x64]
403 + os: [openbsd]
404 +
405 + '@esbuild/openbsd-x64@0.28.1':
406 + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==}
407 + engines: {node: '>=18'}
408 + cpu: [x64]
409 + os: [openbsd]
410 +
411 + '@esbuild/openharmony-arm64@0.28.1':
412 + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==}
413 + engines: {node: '>=18'}
414 + cpu: [arm64]
415 + os: [openharmony]
416 +
417 + '@esbuild/sunos-x64@0.21.5':
418 + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
419 + engines: {node: '>=12'}
420 + cpu: [x64]
421 + os: [sunos]
422 +
423 + '@esbuild/sunos-x64@0.28.1':
424 + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==}
425 + engines: {node: '>=18'}
426 + cpu: [x64]
427 + os: [sunos]
428 +
429 + '@esbuild/win32-arm64@0.21.5':
430 + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
431 + engines: {node: '>=12'}
432 + cpu: [arm64]
433 + os: [win32]
434 +
435 + '@esbuild/win32-arm64@0.28.1':
436 + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==}
437 + engines: {node: '>=18'}
438 + cpu: [arm64]
439 + os: [win32]
440 +
441 + '@esbuild/win32-ia32@0.21.5':
442 + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
443 + engines: {node: '>=12'}
444 + cpu: [ia32]
445 + os: [win32]
446 +
447 + '@esbuild/win32-ia32@0.28.1':
448 + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==}
449 + engines: {node: '>=18'}
450 + cpu: [ia32]
451 + os: [win32]
452 +
453 + '@esbuild/win32-x64@0.21.5':
454 + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
455 + engines: {node: '>=12'}
456 + cpu: [x64]
457 + os: [win32]
458 +
459 + '@esbuild/win32-x64@0.28.1':
460 + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==}
461 + engines: {node: '>=18'}
462 + cpu: [x64]
463 + os: [win32]
464 +
465 + '@eslint-community/eslint-utils@4.10.1':
466 + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==}
467 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
468 + peerDependencies:
469 + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
470 +
471 + '@eslint-community/regexpp@4.12.2':
472 + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
473 + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
474 +
475 + '@eslint/config-array@0.21.2':
476 + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==}
477 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
478 +
479 + '@eslint/config-helpers@0.4.2':
480 + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
481 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
482 +
483 + '@eslint/core@0.17.0':
484 + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
485 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
486 +
487 + '@eslint/eslintrc@3.3.6':
488 + resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==}
489 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
490 +
491 + '@eslint/js@9.39.5':
492 + resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==}
493 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
494 +
495 + '@eslint/object-schema@2.1.7':
496 + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
497 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
498 +
499 + '@eslint/plugin-kit@0.4.1':
500 + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
501 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
502 +
503 + '@humanfs/core@0.19.2':
504 + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
505 + engines: {node: '>=18.18.0'}
506 +
507 + '@humanfs/node@0.16.8':
508 + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==}
509 + engines: {node: '>=18.18.0'}
510 +
511 + '@humanfs/types@0.15.0':
512 + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==}
513 + engines: {node: '>=18.18.0'}
514 +
515 + '@humanwhocodes/module-importer@1.0.1':
516 + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
517 + engines: {node: '>=12.22'}
518 +
519 + '@humanwhocodes/retry@0.4.3':
520 + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
521 + engines: {node: '>=18.18'}
522 +
523 + '@ioredis/commands@1.10.0':
524 + resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==}
525 +
526 + '@jridgewell/gen-mapping@0.3.13':
527 + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
528 +
529 + '@jridgewell/resolve-uri@3.1.2':
530 + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
531 + engines: {node: '>=6.0.0'}
532 +
533 + '@jridgewell/sourcemap-codec@1.5.5':
534 + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
535 +
536 + '@jridgewell/trace-mapping@0.3.31':
537 + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
538 +
539 + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
540 + resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==}
541 + cpu: [arm64]
542 + os: [darwin]
543 +
544 + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4':
545 + resolution: {integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==}
546 + cpu: [x64]
547 + os: [darwin]
548 +
549 + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4':
550 + resolution: {integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==}
551 + cpu: [arm64]
552 + os: [linux]
553 +
554 + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4':
555 + resolution: {integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==}
556 + cpu: [arm]
557 + os: [linux]
558 +
559 + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4':
560 + resolution: {integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==}
561 + cpu: [x64]
562 + os: [linux]
563 +
564 + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4':
565 + resolution: {integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==}
566 + cpu: [x64]
567 + os: [win32]
568 +
569 + '@napi-rs/lzma-linux-x64-gnu@1.5.1':
570 + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==}
571 + engines: {node: ^22.20 || ^24.12 || >=25}
572 + cpu: [x64]
573 + os: [linux]
574 +
575 + '@next/env@14.2.35':
576 + resolution: {integrity: sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==}
577 +
578 + '@next/swc-darwin-arm64@14.2.33':
579 + resolution: {integrity: sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==}
580 + engines: {node: '>= 10'}
581 + cpu: [arm64]
582 + os: [darwin]
583 +
584 + '@next/swc-darwin-x64@14.2.33':
585 + resolution: {integrity: sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==}
586 + engines: {node: '>= 10'}
587 + cpu: [x64]
588 + os: [darwin]
589 +
590 + '@next/swc-linux-arm64-gnu@14.2.33':
591 + resolution: {integrity: sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==}
592 + engines: {node: '>= 10'}
593 + cpu: [arm64]
594 + os: [linux]
595 +
596 + '@next/swc-linux-arm64-musl@14.2.33':
597 + resolution: {integrity: sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==}
598 + engines: {node: '>= 10'}
599 + cpu: [arm64]
600 + os: [linux]
601 +
602 + '@next/swc-linux-x64-gnu@14.2.33':
603 + resolution: {integrity: sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==}
604 + engines: {node: '>= 10'}
605 + cpu: [x64]
606 + os: [linux]
607 +
608 + '@next/swc-linux-x64-musl@14.2.33':
609 + resolution: {integrity: sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==}
610 + engines: {node: '>= 10'}
611 + cpu: [x64]
612 + os: [linux]
613 +
614 + '@next/swc-win32-arm64-msvc@14.2.33':
615 + resolution: {integrity: sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==}
616 + engines: {node: '>= 10'}
617 + cpu: [arm64]
618 + os: [win32]
619 +
620 + '@next/swc-win32-ia32-msvc@14.2.33':
621 + resolution: {integrity: sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==}
622 + engines: {node: '>= 10'}
623 + cpu: [ia32]
624 + os: [win32]
625 +
626 + '@next/swc-win32-x64-msvc@14.2.33':
627 + resolution: {integrity: sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==}
628 + engines: {node: '>= 10'}
629 + cpu: [x64]
630 + os: [win32]
631 +
632 + '@nodelib/fs.scandir@2.1.5':
633 + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
634 + engines: {node: '>= 8'}
635 +
636 + '@nodelib/fs.stat@2.0.5':
637 + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
638 + engines: {node: '>= 8'}
639 +
640 + '@nodelib/fs.walk@1.2.8':
641 + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
642 + engines: {node: '>= 8'}
643 +
644 + '@prisma/client@5.22.0':
645 + resolution: {integrity: sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==}
646 + engines: {node: '>=16.13'}
647 + peerDependencies:
648 + prisma: '*'
649 + peerDependenciesMeta:
650 + prisma:
651 + optional: true
652 +
653 + '@prisma/debug@5.22.0':
654 + resolution: {integrity: sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==}
655 +
656 + '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2':
657 + resolution: {integrity: sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==}
658 +
659 + '@prisma/engines@5.22.0':
660 + resolution: {integrity: sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==}
661 +
662 + '@prisma/fetch-engine@5.22.0':
663 + resolution: {integrity: sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==}
664 +
665 + '@prisma/get-platform@5.22.0':
666 + resolution: {integrity: sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==}
667 +
668 + '@rollup/rollup-android-arm-eabi@4.62.4':
669 + resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==}
670 + cpu: [arm]
671 + os: [android]
672 +
673 + '@rollup/rollup-android-arm64@4.62.4':
674 + resolution: {integrity: sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==}
675 + cpu: [arm64]
676 + os: [android]
677 +
678 + '@rollup/rollup-darwin-arm64@4.62.4':
679 + resolution: {integrity: sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==}
680 + cpu: [arm64]
681 + os: [darwin]
682 +
683 + '@rollup/rollup-darwin-x64@4.62.4':
684 + resolution: {integrity: sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==}
685 + cpu: [x64]
686 + os: [darwin]
687 +
688 + '@rollup/rollup-freebsd-arm64@4.62.4':
689 + resolution: {integrity: sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==}
690 + cpu: [arm64]
691 + os: [freebsd]
692 +
693 + '@rollup/rollup-freebsd-x64@4.62.4':
694 + resolution: {integrity: sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==}
695 + cpu: [x64]
696 + os: [freebsd]
697 +
698 + '@rollup/rollup-linux-arm-gnueabihf@4.62.4':
699 + resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==}
700 + cpu: [arm]
701 + os: [linux]
702 +
703 + '@rollup/rollup-linux-arm-musleabihf@4.62.4':
704 + resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==}
705 + cpu: [arm]
706 + os: [linux]
707 +
708 + '@rollup/rollup-linux-arm64-gnu@4.62.4':
709 + resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==}
710 + cpu: [arm64]
711 + os: [linux]
712 +
713 + '@rollup/rollup-linux-arm64-musl@4.62.4':
714 + resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==}
715 + cpu: [arm64]
716 + os: [linux]
717 +
718 + '@rollup/rollup-linux-loong64-gnu@4.62.4':
719 + resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==}
720 + cpu: [loong64]
721 + os: [linux]
722 +
723 + '@rollup/rollup-linux-loong64-musl@4.62.4':
724 + resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==}
725 + cpu: [loong64]
726 + os: [linux]
727 +
728 + '@rollup/rollup-linux-ppc64-gnu@4.62.4':
729 + resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==}
730 + cpu: [ppc64]
731 + os: [linux]
732 +
733 + '@rollup/rollup-linux-ppc64-musl@4.62.4':
734 + resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==}
735 + cpu: [ppc64]
736 + os: [linux]
737 +
738 + '@rollup/rollup-linux-riscv64-gnu@4.62.4':
739 + resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==}
740 + cpu: [riscv64]
741 + os: [linux]
742 +
743 + '@rollup/rollup-linux-riscv64-musl@4.62.4':
744 + resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==}
745 + cpu: [riscv64]
746 + os: [linux]
747 +
748 + '@rollup/rollup-linux-s390x-gnu@4.62.4':
749 + resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==}
750 + cpu: [s390x]
751 + os: [linux]
752 +
753 + '@rollup/rollup-linux-x64-gnu@4.62.4':
754 + resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==}
755 + cpu: [x64]
756 + os: [linux]
757 +
758 + '@rollup/rollup-linux-x64-musl@4.62.4':
759 + resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==}
760 + cpu: [x64]
761 + os: [linux]
762 +
763 + '@rollup/rollup-openbsd-x64@4.62.4':
764 + resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==}
765 + cpu: [x64]
766 + os: [openbsd]
767 +
768 + '@rollup/rollup-openharmony-arm64@4.62.4':
769 + resolution: {integrity: sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==}
770 + cpu: [arm64]
771 + os: [openharmony]
772 +
773 + '@rollup/rollup-win32-arm64-msvc@4.62.4':
774 + resolution: {integrity: sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==}
775 + cpu: [arm64]
776 + os: [win32]
777 +
778 + '@rollup/rollup-win32-ia32-msvc@4.62.4':
779 + resolution: {integrity: sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==}
780 + cpu: [ia32]
781 + os: [win32]
782 +
783 + '@rollup/rollup-win32-x64-gnu@4.62.4':
784 + resolution: {integrity: sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==}
785 + cpu: [x64]
786 + os: [win32]
787 +
788 + '@rollup/rollup-win32-x64-msvc@4.62.4':
789 + resolution: {integrity: sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==}
790 + cpu: [x64]
791 + os: [win32]
792 +
793 + '@stablelib/base64@1.0.1':
794 + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==}
795 +
796 + '@swc/counter@0.1.3':
797 + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
798 +
799 + '@swc/helpers@0.5.5':
800 + resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
801 +
802 + '@turbo/darwin-64@2.10.8':
803 + resolution: {integrity: sha512-po+7rfJfUnFXjWlcoN2RwhErgzCdRtBc1T26vYPcywHlggmCQiQe1uWaE4j+BibI2uY9/2pDoFzMN0rmSaPFOw==}
804 + cpu: [x64]
805 + os: [darwin]
806 +
807 + '@turbo/darwin-arm64@2.10.8':
808 + resolution: {integrity: sha512-+zB2btDJ00lnPRuqOvpVvgl4x34k/djZQGZTTCfjn7JgNCl8QFY5Njo5+dqkY1g/+9gbbsnAvWm9CmJg9ebcXA==}
809 + cpu: [arm64]
810 + os: [darwin]
811 +
812 + '@turbo/linux-64@2.10.8':
813 + resolution: {integrity: sha512-K1dxqiVisyN7cViVsfQLs6xscQbYuI8aO2nbUhFURDACgEDfZRdP/b4CCxeosBJpcMfhYyiibWqJorCnvz9kKg==}
814 + cpu: [x64]
815 + os: [android, linux]
816 +
817 + '@turbo/linux-arm64@2.10.8':
818 + resolution: {integrity: sha512-Gi77ibVnrE1fEmvr+/wBD/yvRqhwp/RQuCp2+//lv1U1wNFFyVg0V7Wj8FG9FXPFAw5QHReo8rxc9+wBSDZjzA==}
819 + cpu: [arm64]
820 + os: [android, linux]
821 +
822 + '@turbo/windows-64@2.10.8':
823 + resolution: {integrity: sha512-znnLO1haJPYTHoKMKwlAvlkjRiYbbhBzME6wIGaMd+fwir23U6jVd1ecaTWWi1fbnRVqxMfgDBKseQ/hLKb83g==}
824 + cpu: [x64]
825 + os: [win32]
826 +
827 + '@turbo/windows-arm64@2.10.8':
828 + resolution: {integrity: sha512-VN30vh3b3Czh2WzYHNTfF1FE0YMZ5aHsLO8dBMGHJewA6792wX6iJR8ZxlzFW6WdOu0gEAKIvlYhfyT81Wkm4Q==}
829 + cpu: [arm64]
830 + os: [win32]
831 +
832 + '@types/estree@1.0.9':
833 + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
834 +
835 + '@types/json-schema@7.0.15':
836 + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
837 +
838 + '@types/node@20.19.43':
839 + resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==}
840 +
841 + '@types/prop-types@15.7.15':
842 + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
843 +
844 + '@types/react-dom@18.3.7':
845 + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
846 + peerDependencies:
847 + '@types/react': ^18.0.0
848 +
849 + '@types/react@18.3.31':
850 + resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==}
851 +
852 + '@typescript-eslint/eslint-plugin@8.66.0':
853 + resolution: {integrity: sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==}
854 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
855 + peerDependencies:
856 + '@typescript-eslint/parser': ^8.66.0
857 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
858 + typescript: '>=4.8.4 <6.1.0'
859 +
860 + '@typescript-eslint/parser@8.66.0':
861 + resolution: {integrity: sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==}
862 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
863 + peerDependencies:
864 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
865 + typescript: '>=4.8.4 <6.1.0'
866 +
867 + '@typescript-eslint/project-service@8.66.0':
868 + resolution: {integrity: sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==}
869 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
870 + peerDependencies:
871 + typescript: '>=4.8.4 <6.1.0'
872 +
873 + '@typescript-eslint/scope-manager@8.66.0':
874 + resolution: {integrity: sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==}
875 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
876 +
877 + '@typescript-eslint/tsconfig-utils@8.66.0':
878 + resolution: {integrity: sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==}
879 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
880 + peerDependencies:
881 + typescript: '>=4.8.4 <6.1.0'
882 +
883 + '@typescript-eslint/type-utils@8.66.0':
884 + resolution: {integrity: sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==}
885 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
886 + peerDependencies:
887 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
888 + typescript: '>=4.8.4 <6.1.0'
889 +
890 + '@typescript-eslint/types@8.66.0':
891 + resolution: {integrity: sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==}
892 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
893 +
894 + '@typescript-eslint/typescript-estree@8.66.0':
895 + resolution: {integrity: sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==}
896 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
897 + peerDependencies:
898 + typescript: '>=4.8.4 <6.1.0'
899 +
900 + '@typescript-eslint/utils@8.66.0':
901 + resolution: {integrity: sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==}
902 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
903 + peerDependencies:
904 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
905 + typescript: '>=4.8.4 <6.1.0'
906 +
907 + '@typescript-eslint/visitor-keys@8.66.0':
908 + resolution: {integrity: sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==}
909 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
910 +
911 + '@vitest/expect@2.1.9':
912 + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
913 +
914 + '@vitest/mocker@2.1.9':
915 + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==}
916 + peerDependencies:
917 + msw: ^2.4.9
918 + vite: ^5.0.0
919 + peerDependenciesMeta:
920 + msw:
921 + optional: true
922 + vite:
923 + optional: true
924 +
925 + '@vitest/pretty-format@2.1.9':
926 + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==}
927 +
928 + '@vitest/runner@2.1.9':
929 + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==}
930 +
931 + '@vitest/snapshot@2.1.9':
932 + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==}
933 +
934 + '@vitest/spy@2.1.9':
935 + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==}
936 +
937 + '@vitest/utils@2.1.9':
938 + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
939 +
940 + acorn-jsx@5.3.2:
941 + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
942 + peerDependencies:
943 + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
944 +
945 + acorn@8.18.0:
946 + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
947 + engines: {node: '>=0.4.0'}
948 + hasBin: true
949 +
950 + ajv@6.15.0:
951 + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
952 +
953 + ansi-styles@4.3.0:
954 + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
955 + engines: {node: '>=8'}
956 +
957 + any-promise@1.3.0:
958 + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
959 +
960 + anymatch@3.1.3:
961 + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
962 + engines: {node: '>= 8'}
963 +
964 + arg@5.0.2:
965 + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
966 +
967 + argparse@2.0.1:
968 + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
969 +
970 + assertion-error@2.0.1:
971 + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
972 + engines: {node: '>=12'}
973 +
974 + autoprefixer@10.5.4:
975 + resolution: {integrity: sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==}
976 + engines: {node: ^10 || ^12 || >=14}
977 + hasBin: true
978 + peerDependencies:
979 + postcss: ^8.1.0
980 +
981 + balanced-match@1.0.2:
982 + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
983 +
984 + balanced-match@4.0.4:
985 + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
986 + engines: {node: 18 || 20 || >=22}
987 +
988 + baseline-browser-mapping@2.11.12:
989 + resolution: {integrity: sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==}
990 + engines: {node: '>=6.0.0'}
991 + hasBin: true
992 +
993 + binary-extensions@2.3.0:
994 + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
995 + engines: {node: '>=8'}
996 +
997 + brace-expansion@1.1.18:
998 + resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==}
999 +
1000 + brace-expansion@5.0.9:
1001 + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
1002 + engines: {node: 20 || >=22}
1003 +
1004 + braces@3.0.3:
1005 + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
1006 + engines: {node: '>=8'}
1007 +
1008 + browserslist@4.28.7:
1009 + resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==}
1010 + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
1011 + hasBin: true
1012 +
1013 + bullmq@5.81.3:
1014 + resolution: {integrity: sha512-Q7uEH2G92rVjX3Yl2qw3+6VEO15uxehC6DfeTHQBQTehddcRDSDuquD9zuvDjxiCffXsPUaBwrRlvlbVcgKs7g==}
1015 + engines: {node: '>=12.22.0'}
1016 + peerDependencies:
1017 + redis: '>=5.0.0'
1018 + peerDependenciesMeta:
1019 + redis:
1020 + optional: true
1021 +
1022 + busboy@1.6.0:
1023 + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
1024 + engines: {node: '>=10.16.0'}
1025 +
1026 + cac@6.7.14:
1027 + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
1028 + engines: {node: '>=8'}
1029 +
1030 + callsites@3.1.0:
1031 + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
1032 + engines: {node: '>=6'}
1033 +
1034 + camelcase-css@2.0.1:
1035 + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
1036 + engines: {node: '>= 6'}
1037 +
1038 + caniuse-lite@1.0.30001806:
1039 + resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==}
1040 +
1041 + chai@5.3.3:
1042 + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
1043 + engines: {node: '>=18'}
1044 +
1045 + chalk@4.1.2:
1046 + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
1047 + engines: {node: '>=10'}
1048 +
1049 + check-error@2.1.3:
1050 + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
1051 + engines: {node: '>= 16'}
1052 +
1053 + chokidar@3.6.0:
1054 + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
1055 + engines: {node: '>= 8.10.0'}
1056 +
1057 + client-only@0.0.1:
1058 + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
1059 +
1060 + cluster-key-slot@1.1.1:
1061 + resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==}
1062 + engines: {node: '>=0.10.0'}
1063 +
1064 + color-convert@2.0.1:
1065 + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
1066 + engines: {node: '>=7.0.0'}
1067 +
1068 + color-name@1.1.4:
1069 + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
1070 +
1071 + commander@4.1.1:
1072 + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
1073 + engines: {node: '>= 6'}
1074 +
1075 + concat-map@0.0.1:
1076 + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
1077 +
1078 + cron-parser@4.9.0:
1079 + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
1080 + engines: {node: '>=12.0.0'}
1081 + deprecated: v4 is no longer maintained, upgrade to v5
1082 +
1083 + cross-spawn@7.0.6:
1084 + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
1085 + engines: {node: '>= 8'}
1086 +
1087 + cssesc@3.0.0:
1088 + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
1089 + engines: {node: '>=4'}
1090 + hasBin: true
1091 +
1092 + csstype@3.2.3:
1093 + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
1094 +
1095 + debug@4.4.3:
1096 + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
1097 + engines: {node: '>=6.0'}
1098 + peerDependencies:
1099 + supports-color: '*'
1100 + peerDependenciesMeta:
1101 + supports-color:
1102 + optional: true
1103 +
1104 + deep-eql@5.0.2:
1105 + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
1106 + engines: {node: '>=6'}
1107 +
1108 + deep-is@0.1.4:
1109 + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
1110 +
1111 + denque@2.1.0:
1112 + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
1113 + engines: {node: '>=0.10'}
1114 +
1115 + detect-libc@2.1.2:
1116 + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
1117 + engines: {node: '>=8'}
1118 +
1119 + didyoumean@1.2.2:
1120 + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
1121 +
1122 + dlv@1.1.3:
1123 + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
1124 +
1125 + electron-to-chromium@1.5.401:
1126 + resolution: {integrity: sha512-H6ViHN68nGYlChEvlIU67fn8O2/tpbWQPwck98yaJmh+08LSvHiydzDQ6oXNccLU3kNRVIRS9A4mA7CG+i6fLQ==}
1127 +
1128 + es-errors@1.3.0:
1129 + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
1130 + engines: {node: '>= 0.4'}
1131 +
1132 + es-module-lexer@1.7.0:
1133 + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
1134 +
1135 + esbuild@0.21.5:
1136 + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
1137 + engines: {node: '>=12'}
1138 + hasBin: true
1139 +
1140 + esbuild@0.28.1:
1141 + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==}
1142 + engines: {node: '>=18'}
1143 + hasBin: true
1144 +
1145 + escalade@3.2.0:
1146 + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
1147 + engines: {node: '>=6'}
1148 +
1149 + escape-string-regexp@4.0.0:
1150 + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
1151 + engines: {node: '>=10'}
1152 +
1153 + eslint-scope@8.4.0:
1154 + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
1155 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1156 +
1157 + eslint-visitor-keys@3.4.3:
1158 + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
1159 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1160 +
1161 + eslint-visitor-keys@4.2.1:
1162 + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
1163 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1164 +
1165 + eslint-visitor-keys@5.0.1:
1166 + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
1167 + engines: {node: ^20.19.0 || ^22.13.0 || >=24}
1168 +
1169 + eslint@9.39.5:
1170 + resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==}
1171 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1172 + hasBin: true
1173 + peerDependencies:
1174 + jiti: '*'
1175 + peerDependenciesMeta:
1176 + jiti:
1177 + optional: true
1178 +
1179 + espree@10.4.0:
1180 + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
1181 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1182 +
1183 + esquery@1.7.0:
1184 + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
1185 + engines: {node: '>=0.10'}
1186 +
1187 + esrecurse@4.3.0:
1188 + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
1189 + engines: {node: '>=4.0'}
1190 +
1191 + estraverse@5.3.0:
1192 + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
1193 + engines: {node: '>=4.0'}
1194 +
1195 + estree-walker@3.0.3:
1196 + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
1197 +
1198 + esutils@2.0.3:
1199 + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
1200 + engines: {node: '>=0.10.0'}
1201 +
1202 + expect-type@1.4.0:
1203 + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
1204 + engines: {node: '>=12.0.0'}
1205 +
1206 + fast-check@3.23.2:
1207 + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==}
1208 + engines: {node: '>=8.0.0'}
1209 +
1210 + fast-deep-equal@3.1.3:
1211 + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
1212 +
1213 + fast-glob@3.3.3:
1214 + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
1215 + engines: {node: '>=8.6.0'}
1216 +
1217 + fast-json-stable-stringify@2.1.0:
1218 + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
1219 +
1220 + fast-levenshtein@2.0.6:
1221 + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
1222 +
1223 + fast-sha256@1.3.0:
1224 + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==}
1225 +
1226 + fastq@1.20.1:
1227 + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
1228 +
1229 + fdir@6.5.0:
1230 + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
1231 + engines: {node: '>=12.0.0'}
1232 + peerDependencies:
1233 + picomatch: ^3 || ^4
1234 + peerDependenciesMeta:
1235 + picomatch:
1236 + optional: true
1237 +
1238 + file-entry-cache@8.0.0:
1239 + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
1240 + engines: {node: '>=16.0.0'}
1241 +
1242 + fill-range@7.1.1:
1243 + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
1244 + engines: {node: '>=8'}
1245 +
1246 + find-up@5.0.0:
1247 + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
1248 + engines: {node: '>=10'}
1249 +
1250 + flat-cache@4.0.1:
1251 + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
1252 + engines: {node: '>=16'}
1253 +
1254 + flatted@3.4.4:
1255 + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==}
1256 +
1257 + fraction.js@5.3.4:
1258 + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
1259 +
1260 + fsevents@2.3.3:
1261 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
1262 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
1263 + os: [darwin]
1264 +
1265 + function-bind@1.1.2:
1266 + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
1267 +
1268 + glob-parent@5.1.2:
1269 + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
1270 + engines: {node: '>= 6'}
1271 +
1272 + glob-parent@6.0.2:
1273 + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
1274 + engines: {node: '>=10.13.0'}
1275 +
1276 + globals@14.0.0:
1277 + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
1278 + engines: {node: '>=18'}
1279 +
1280 + graceful-fs@4.2.11:
1281 + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
1282 +
1283 + has-flag@4.0.0:
1284 + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
1285 + engines: {node: '>=8'}
1286 +
1287 + hasown@2.0.4:
1288 + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
1289 + engines: {node: '>= 0.4'}
1290 +
1291 + ignore@5.3.2:
1292 + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
1293 + engines: {node: '>= 4'}
1294 +
1295 + ignore@7.0.6:
1296 + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
1297 + engines: {node: '>= 4'}
1298 +
1299 + import-fresh@3.3.1:
1300 + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
1301 + engines: {node: '>=6'}
1302 +
1303 + imurmurhash@0.1.4:
1304 + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
1305 + engines: {node: '>=0.8.19'}
1306 +
1307 + ioredis@5.11.1:
1308 + resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==}
1309 + engines: {node: '>=12.22.0'}
1310 +
1311 + is-binary-path@2.1.0:
1312 + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
1313 + engines: {node: '>=8'}
1314 +
1315 + is-core-module@2.16.2:
1316 + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==}
1317 + engines: {node: '>= 0.4'}
1318 +
1319 + is-extglob@2.1.1:
1320 + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
1321 + engines: {node: '>=0.10.0'}
1322 +
1323 + is-glob@4.0.3:
1324 + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
1325 + engines: {node: '>=0.10.0'}
1326 +
1327 + is-number@7.0.0:
1328 + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
1329 + engines: {node: '>=0.12.0'}
1330 +
1331 + isexe@2.0.0:
1332 + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
1333 +
1334 + jiti@1.21.7:
1335 + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
1336 + hasBin: true
1337 +
1338 + js-tokens@4.0.0:
1339 + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
1340 +
1341 + js-yaml@4.3.1:
1342 + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
1343 + hasBin: true
1344 +
1345 + json-buffer@3.0.1:
1346 + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
1347 +
1348 + json-schema-to-ts@3.1.1:
1349 + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==}
1350 + engines: {node: '>=16'}
1351 +
1352 + json-schema-traverse@0.4.1:
1353 + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
1354 +
1355 + json-stable-stringify-without-jsonify@1.0.1:
1356 + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
1357 +
1358 + keyv@4.5.4:
1359 + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
1360 +
1361 + levn@0.4.1:
1362 + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
1363 + engines: {node: '>= 0.8.0'}
1364 +
1365 + lilconfig@3.1.3:
1366 + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
1367 + engines: {node: '>=14'}
1368 +
1369 + lines-and-columns@1.2.4:
1370 + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
1371 +
1372 + locate-path@6.0.0:
1373 + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
1374 + engines: {node: '>=10'}
1375 +
1376 + lodash.merge@4.6.2:
1377 + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
1378 +
1379 + loose-envify@1.4.0:
1380 + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
1381 + hasBin: true
1382 +
1383 + loupe@3.2.1:
1384 + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
1385 +
1386 + luxon@3.7.2:
1387 + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
1388 + engines: {node: '>=12'}
1389 +
1390 + magic-string@0.30.21:
1391 + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
1392 +
1393 + merge2@1.4.1:
1394 + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
1395 + engines: {node: '>= 8'}
1396 +
1397 + micromatch@4.0.8:
1398 + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
1399 + engines: {node: '>=8.6'}
1400 +
1401 + minimatch@10.2.6:
1402 + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
1403 + engines: {node: 18 || 20 || >=22}
1404 +
1405 + minimatch@3.1.5:
1406 + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
1407 +
1408 + ms@2.1.3:
1409 + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
1410 +
1411 + msgpackr-extract@3.0.4:
1412 + resolution: {integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==}
1413 + hasBin: true
1414 +
1415 + msgpackr@2.0.5:
1416 + resolution: {integrity: sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA==}
1417 +
1418 + mz@2.7.0:
1419 + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
1420 +
1421 + nanoid@3.3.17:
1422 + resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==}
1423 + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
1424 + hasBin: true
1425 +
1426 + natural-compare@1.4.0:
1427 + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
1428 +
1429 + next@14.2.35:
1430 + resolution: {integrity: sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==}
1431 + engines: {node: '>=18.17.0'}
1432 + hasBin: true
1433 + peerDependencies:
1434 + '@opentelemetry/api': ^1.1.0
1435 + '@playwright/test': ^1.41.2
1436 + react: ^18.2.0
1437 + react-dom: ^18.2.0
1438 + sass: ^1.3.0
1439 + peerDependenciesMeta:
1440 + '@opentelemetry/api':
1441 + optional: true
1442 + '@playwright/test':
1443 + optional: true
1444 + sass:
1445 + optional: true
1446 +
1447 + node-abort-controller@3.1.1:
1448 + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
1449 +
1450 + node-gyp-build-optional-packages@5.2.2:
1451 + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
1452 + hasBin: true
1453 +
1454 + node-releases@2.0.52:
1455 + resolution: {integrity: sha512-MRlTqhAfoMx/4mhEbPo3Hi02g9LJZaJkka69V6h67Cb1gjrAG0jsTE4CZX1eptNx+VCAwJmfpnDIF4P0Nh1A7A==}
1456 + engines: {node: '>=18'}
1457 +
1458 + normalize-path@3.0.0:
1459 + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
1460 + engines: {node: '>=0.10.0'}
1461 +
1462 + object-assign@4.1.1:
1463 + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
1464 + engines: {node: '>=0.10.0'}
1465 +
1466 + object-hash@3.0.0:
1467 + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
1468 + engines: {node: '>= 6'}
1469 +
1470 + optionator@0.9.4:
1471 + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
1472 + engines: {node: '>= 0.8.0'}
1473 +
1474 + p-limit@3.1.0:
1475 + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
1476 + engines: {node: '>=10'}
1477 +
1478 + p-locate@5.0.0:
1479 + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
1480 + engines: {node: '>=10'}
1481 +
1482 + parent-module@1.0.1:
1483 + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
1484 + engines: {node: '>=6'}
1485 +
1486 + path-exists@4.0.0:
1487 + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
1488 + engines: {node: '>=8'}
1489 +
1490 + path-key@3.1.1:
1491 + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
1492 + engines: {node: '>=8'}
1493 +
1494 + path-parse@1.0.7:
1495 + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
1496 +
1497 + pathe@1.1.2:
1498 + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
1499 +
1500 + pathval@2.0.1:
1501 + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
1502 + engines: {node: '>= 14.16'}
1503 +
1504 + picocolors@1.1.1:
1505 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
1506 +
1507 + picomatch@2.3.2:
1508 + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
1509 + engines: {node: '>=8.6'}
1510 +
1511 + picomatch@4.0.5:
1512 + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
1513 + engines: {node: '>=12'}
1514 +
1515 + pify@2.3.0:
1516 + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
1517 + engines: {node: '>=0.10.0'}
1518 +
1519 + pirates@4.0.7:
1520 + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
1521 + engines: {node: '>= 6'}
1522 +
1523 + postcss-import@15.1.0:
1524 + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
1525 + engines: {node: '>=14.0.0'}
1526 + peerDependencies:
1527 + postcss: ^8.0.0
1528 +
1529 + postcss-js@4.1.0:
1530 + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==}
1531 + engines: {node: ^12 || ^14 || >= 16}
1532 + peerDependencies:
1533 + postcss: ^8.4.21
1534 +
1535 + postcss-load-config@6.0.1:
1536 + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
1537 + engines: {node: '>= 18'}
1538 + peerDependencies:
1539 + jiti: '>=1.21.0'
1540 + postcss: '>=8.0.9'
1541 + tsx: ^4.8.1
1542 + yaml: ^2.4.2
1543 + peerDependenciesMeta:
1544 + jiti:
1545 + optional: true
1546 + postcss:
1547 + optional: true
1548 + tsx:
1549 + optional: true
1550 + yaml:
1551 + optional: true
1552 +
1553 + postcss-nested@6.2.0:
1554 + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
1555 + engines: {node: '>=12.0'}
1556 + peerDependencies:
1557 + postcss: ^8.2.14
1558 +
1559 + postcss-selector-parser@6.1.4:
1560 + resolution: {integrity: sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==}
1561 + engines: {node: '>=4'}
1562 +
1563 + postcss-value-parser@4.2.0:
1564 + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
1565 +
1566 + postcss@8.4.31:
1567 + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
1568 + engines: {node: ^10 || ^12 || >=14}
1569 +
1570 + postcss@8.5.25:
1571 + resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==}
1572 + engines: {node: ^10 || ^12 || >=14}
1573 +
1574 + prelude-ls@1.2.1:
1575 + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
1576 + engines: {node: '>= 0.8.0'}
1577 +
1578 + prettier@3.9.6:
1579 + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==}
1580 + engines: {node: '>=14'}
1581 + hasBin: true
1582 +
1583 + prisma@5.22.0:
1584 + resolution: {integrity: sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==}
1585 + engines: {node: '>=16.13'}
1586 + hasBin: true
1587 +
1588 + punycode@2.3.1:
1589 + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
1590 + engines: {node: '>=6'}
1591 +
1592 + pure-rand@6.1.0:
1593 + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
1594 +
1595 + queue-microtask@1.2.3:
1596 + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
1597 +
1598 + react-dom@18.3.1:
1599 + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
1600 + peerDependencies:
1601 + react: ^18.3.1
1602 +
1603 + react@18.3.1:
1604 + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
1605 + engines: {node: '>=0.10.0'}
1606 +
1607 + read-cache@1.0.0:
1608 + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
1609 +
1610 + readdirp@3.6.0:
1611 + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
1612 + engines: {node: '>=8.10.0'}
1613 +
1614 + redis-errors@1.2.0:
1615 + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
1616 + engines: {node: '>=4'}
1617 +
1618 + redis-parser@3.0.0:
1619 + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
1620 + engines: {node: '>=4'}
1621 +
1622 + resolve-from@4.0.0:
1623 + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
1624 + engines: {node: '>=4'}
1625 +
1626 + resolve@1.22.12:
1627 + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==}
1628 + engines: {node: '>= 0.4'}
1629 + hasBin: true
1630 +
1631 + reusify@1.1.0:
1632 + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
1633 + engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
1634 +
1635 + rollup@4.62.4:
1636 + resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==}
1637 + engines: {node: '>=18.0.0', npm: '>=8.0.0'}
1638 + hasBin: true
1639 +
1640 + run-parallel@1.2.0:
1641 + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
1642 +
1643 + scheduler@0.23.2:
1644 + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
1645 +
1646 + semver@7.8.5:
1647 + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
1648 + engines: {node: '>=10'}
1649 + hasBin: true
1650 +
1651 + shebang-command@2.0.0:
1652 + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
1653 + engines: {node: '>=8'}
1654 +
1655 + shebang-regex@3.0.0:
1656 + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
1657 + engines: {node: '>=8'}
1658 +
1659 + siginfo@2.0.0:
1660 + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
1661 +
1662 + source-map-js@1.2.1:
1663 + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
1664 + engines: {node: '>=0.10.0'}
1665 +
1666 + stackback@0.0.2:
1667 + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
1668 +
1669 + standard-as-callback@2.1.0:
1670 + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
1671 +
1672 + standardwebhooks@1.0.0:
1673 + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==}
1674 +
1675 + std-env@3.10.0:
1676 + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
1677 +
1678 + streamsearch@1.1.0:
1679 + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
1680 + engines: {node: '>=10.0.0'}
1681 +
1682 + strip-json-comments@3.1.1:
1683 + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
1684 + engines: {node: '>=8'}
1685 +
1686 + styled-jsx@5.1.1:
1687 + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
1688 + engines: {node: '>= 12.0.0'}
1689 + peerDependencies:
1690 + '@babel/core': '*'
1691 + babel-plugin-macros: '*'
1692 + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0'
1693 + peerDependenciesMeta:
1694 + '@babel/core':
1695 + optional: true
1696 + babel-plugin-macros:
1697 + optional: true
1698 +
1699 + sucrase@3.35.1:
1700 + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
1701 + engines: {node: '>=16 || 14 >=14.17'}
1702 + hasBin: true
1703 +
1704 + supports-color@7.2.0:
1705 + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
1706 + engines: {node: '>=8'}
1707 +
1708 + supports-preserve-symlinks-flag@1.0.0:
1709 + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
1710 + engines: {node: '>= 0.4'}
1711 +
1712 + tailwindcss@3.4.19:
1713 + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==}
1714 + engines: {node: '>=14.0.0'}
1715 + hasBin: true
1716 +
1717 + thenify-all@1.6.0:
1718 + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
1719 + engines: {node: '>=0.8'}
1720 +
1721 + thenify@3.3.1:
1722 + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
1723 +
1724 + tinybench@2.9.0:
1725 + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
1726 +
1727 + tinyexec@0.3.2:
1728 + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
1729 +
1730 + tinyglobby@0.2.17:
1731 + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
1732 + engines: {node: '>=12.0.0'}
1733 +
1734 + tinypool@1.1.1:
1735 + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
1736 + engines: {node: ^18.0.0 || >=20.0.0}
1737 +
1738 + tinyrainbow@1.2.0:
1739 + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
1740 + engines: {node: '>=14.0.0'}
1741 +
1742 + tinyspy@3.0.2:
1743 + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
1744 + engines: {node: '>=14.0.0'}
1745 +
1746 + to-regex-range@5.0.1:
1747 + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
1748 + engines: {node: '>=8.0'}
1749 +
1750 + ts-algebra@2.0.0:
1751 + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==}
1752 +
1753 + ts-api-utils@2.5.0:
1754 + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
1755 + engines: {node: '>=18.12'}
1756 + peerDependencies:
1757 + typescript: '>=4.8.4'
1758 +
1759 + ts-interface-checker@0.1.13:
1760 + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
1761 +
1762 + tslib@2.8.1:
1763 + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
1764 +
1765 + tsx@4.23.6:
1766 + resolution: {integrity: sha512-D/YYGUDqKlLvXhM5fBBbiENaGICxLfU4viHnZEkgmgplnDFa+Kczy34VV7AmLJgdzisv0I/J3zitfC26JH3GXg==}
1767 + engines: {node: '>=18.0.0'}
1768 + hasBin: true
1769 +
1770 + turbo@2.10.8:
1771 + resolution: {integrity: sha512-9+8YX5QOkGXzZxcIykTHgaooRHGMWO+jfdyRK0o+rN0U7hBIig2MrJ8r/aNzIPDPhdA73SGb0O+tIztaModTMg==}
1772 + hasBin: true
1773 +
1774 + type-check@0.4.0:
1775 + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
1776 + engines: {node: '>= 0.8.0'}
1777 +
1778 + typescript-eslint@8.66.0:
1779 + resolution: {integrity: sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==}
1780 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1781 + peerDependencies:
1782 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
1783 + typescript: '>=4.8.4 <6.1.0'
1784 +
1785 + typescript@5.9.3:
1786 + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
1787 + engines: {node: '>=14.17'}
1788 + hasBin: true
1789 +
1790 + undici-types@6.21.0:
1791 + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
1792 +
1793 + update-browserslist-db@1.2.3:
1794 + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
1795 + hasBin: true
1796 + peerDependencies:
1797 + browserslist: '>= 4.21.0'
1798 +
1799 + uri-js@4.4.1:
1800 + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
1801 +
1802 + util-deprecate@1.0.2:
1803 + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
1804 +
1805 + vite-node@2.1.9:
1806 + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==}
1807 + engines: {node: ^18.0.0 || >=20.0.0}
1808 + hasBin: true
1809 +
1810 + vite@5.4.21:
1811 + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
1812 + engines: {node: ^18.0.0 || >=20.0.0}
1813 + hasBin: true
1814 + peerDependencies:
1815 + '@types/node': ^18.0.0 || >=20.0.0
1816 + less: '*'
1817 + lightningcss: ^1.21.0
1818 + sass: '*'
1819 + sass-embedded: '*'
1820 + stylus: '*'
1821 + sugarss: '*'
1822 + terser: ^5.4.0
1823 + peerDependenciesMeta:
1824 + '@types/node':
1825 + optional: true
1826 + less:
1827 + optional: true
1828 + lightningcss:
1829 + optional: true
1830 + sass:
1831 + optional: true
1832 + sass-embedded:
1833 + optional: true
1834 + stylus:
1835 + optional: true
1836 + sugarss:
1837 + optional: true
1838 + terser:
1839 + optional: true
1840 +
1841 + vitest@2.1.9:
1842 + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==}
1843 + engines: {node: ^18.0.0 || >=20.0.0}
1844 + hasBin: true
1845 + peerDependencies:
1846 + '@edge-runtime/vm': '*'
1847 + '@types/node': ^18.0.0 || >=20.0.0
1848 + '@vitest/browser': 2.1.9
1849 + '@vitest/ui': 2.1.9
1850 + happy-dom: '*'
1851 + jsdom: '*'
1852 + peerDependenciesMeta:
1853 + '@edge-runtime/vm':
1854 + optional: true
1855 + '@types/node':
1856 + optional: true
1857 + '@vitest/browser':
1858 + optional: true
1859 + '@vitest/ui':
1860 + optional: true
1861 + happy-dom:
1862 + optional: true
1863 + jsdom:
1864 + optional: true
1865 +
1866 + which@2.0.2:
1867 + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
1868 + engines: {node: '>= 8'}
1869 + hasBin: true
1870 +
1871 + why-is-node-running@2.3.0:
1872 + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
1873 + engines: {node: '>=8'}
1874 + hasBin: true
1875 +
1876 + word-wrap@1.2.5:
1877 + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
1878 + engines: {node: '>=0.10.0'}
1879 +
1880 + yocto-queue@0.1.0:
1881 + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
1882 + engines: {node: '>=10'}
1883 +
1884 + zod@3.25.76:
1885 + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
1886 +
1887 +snapshots:
1888 +
1889 + '@alloc/quick-lru@5.2.0': {}
1890 +
1891 + '@anthropic-ai/sdk@0.115.0(zod@3.25.76)':
1892 + dependencies:
1893 + json-schema-to-ts: 3.1.1
1894 + standardwebhooks: 1.0.0
1895 + optionalDependencies:
1896 + zod: 3.25.76
1897 +
1898 + '@babel/runtime@7.29.7': {}
1899 +
1900 + '@esbuild/aix-ppc64@0.21.5':
1901 + optional: true
1902 +
1903 + '@esbuild/aix-ppc64@0.28.1':
1904 + optional: true
1905 +
1906 + '@esbuild/android-arm64@0.21.5':
1907 + optional: true
1908 +
1909 + '@esbuild/android-arm64@0.28.1':
1910 + optional: true
1911 +
1912 + '@esbuild/android-arm@0.21.5':
1913 + optional: true
1914 +
1915 + '@esbuild/android-arm@0.28.1':
1916 + optional: true
1917 +
1918 + '@esbuild/android-x64@0.21.5':
1919 + optional: true
1920 +
1921 + '@esbuild/android-x64@0.28.1':
1922 + optional: true
1923 +
1924 + '@esbuild/darwin-arm64@0.21.5':
1925 + optional: true
1926 +
1927 + '@esbuild/darwin-arm64@0.28.1':
1928 + optional: true
1929 +
1930 + '@esbuild/darwin-x64@0.21.5':
1931 + optional: true
1932 +
1933 + '@esbuild/darwin-x64@0.28.1':
1934 + optional: true
1935 +
1936 + '@esbuild/freebsd-arm64@0.21.5':
1937 + optional: true
1938 +
1939 + '@esbuild/freebsd-arm64@0.28.1':
1940 + optional: true
1941 +
1942 + '@esbuild/freebsd-x64@0.21.5':
1943 + optional: true
1944 +
1945 + '@esbuild/freebsd-x64@0.28.1':
1946 + optional: true
1947 +
1948 + '@esbuild/linux-arm64@0.21.5':
1949 + optional: true
1950 +
1951 + '@esbuild/linux-arm64@0.28.1':
1952 + optional: true
1953 +
1954 + '@esbuild/linux-arm@0.21.5':
1955 + optional: true
1956 +
1957 + '@esbuild/linux-arm@0.28.1':
1958 + optional: true
1959 +
1960 + '@esbuild/linux-ia32@0.21.5':
1961 + optional: true
1962 +
1963 + '@esbuild/linux-ia32@0.28.1':
1964 + optional: true
1965 +
1966 + '@esbuild/linux-loong64@0.21.5':
1967 + optional: true
1968 +
1969 + '@esbuild/linux-loong64@0.28.1':
1970 + optional: true
1971 +
1972 + '@esbuild/linux-mips64el@0.21.5':
1973 + optional: true
1974 +
1975 + '@esbuild/linux-mips64el@0.28.1':
1976 + optional: true
1977 +
1978 + '@esbuild/linux-ppc64@0.21.5':
1979 + optional: true
1980 +
1981 + '@esbuild/linux-ppc64@0.28.1':
1982 + optional: true
1983 +
1984 + '@esbuild/linux-riscv64@0.21.5':
1985 + optional: true
1986 +
1987 + '@esbuild/linux-riscv64@0.28.1':
1988 + optional: true
1989 +
1990 + '@esbuild/linux-s390x@0.21.5':
1991 + optional: true
1992 +
1993 + '@esbuild/linux-s390x@0.28.1':
1994 + optional: true
1995 +
1996 + '@esbuild/linux-x64@0.21.5':
1997 + optional: true
1998 +
1999 + '@esbuild/linux-x64@0.28.1':
2000 + optional: true
2001 +
2002 + '@esbuild/netbsd-arm64@0.28.1':
2003 + optional: true
2004 +
2005 + '@esbuild/netbsd-x64@0.21.5':
2006 + optional: true
2007 +
2008 + '@esbuild/netbsd-x64@0.28.1':
2009 + optional: true
2010 +
2011 + '@esbuild/openbsd-arm64@0.28.1':
2012 + optional: true
2013 +
2014 + '@esbuild/openbsd-x64@0.21.5':
2015 + optional: true
2016 +
2017 + '@esbuild/openbsd-x64@0.28.1':
2018 + optional: true
2019 +
2020 + '@esbuild/openharmony-arm64@0.28.1':
2021 + optional: true
2022 +
2023 + '@esbuild/sunos-x64@0.21.5':
2024 + optional: true
2025 +
2026 + '@esbuild/sunos-x64@0.28.1':
2027 + optional: true
2028 +
2029 + '@esbuild/win32-arm64@0.21.5':
2030 + optional: true
2031 +
2032 + '@esbuild/win32-arm64@0.28.1':
2033 + optional: true
2034 +
2035 + '@esbuild/win32-ia32@0.21.5':
2036 + optional: true
2037 +
2038 + '@esbuild/win32-ia32@0.28.1':
2039 + optional: true
2040 +
2041 + '@esbuild/win32-x64@0.21.5':
2042 + optional: true
2043 +
2044 + '@esbuild/win32-x64@0.28.1':
2045 + optional: true
2046 +
2047 + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@1.21.7))':
2048 + dependencies:
2049 + eslint: 9.39.5(jiti@1.21.7)
2050 + eslint-visitor-keys: 3.4.3
2051 +
2052 + '@eslint-community/regexpp@4.12.2': {}
2053 +
2054 + '@eslint/config-array@0.21.2':
2055 + dependencies:
2056 + '@eslint/object-schema': 2.1.7
2057 + debug: 4.4.3
2058 + minimatch: 3.1.5
2059 + transitivePeerDependencies:
2060 + - supports-color
2061 +
2062 + '@eslint/config-helpers@0.4.2':
2063 + dependencies:
2064 + '@eslint/core': 0.17.0
2065 +
2066 + '@eslint/core@0.17.0':
2067 + dependencies:
2068 + '@types/json-schema': 7.0.15
2069 +
2070 + '@eslint/eslintrc@3.3.6':
2071 + dependencies:
2072 + ajv: 6.15.0
2073 + debug: 4.4.3
2074 + espree: 10.4.0
2075 + globals: 14.0.0
2076 + ignore: 5.3.2
2077 + import-fresh: 3.3.1
2078 + js-yaml: 4.3.1
2079 + minimatch: 3.1.5
2080 + strip-json-comments: 3.1.1
2081 + transitivePeerDependencies:
2082 + - supports-color
2083 +
2084 + '@eslint/js@9.39.5': {}
2085 +
2086 + '@eslint/object-schema@2.1.7': {}
2087 +
2088 + '@eslint/plugin-kit@0.4.1':
2089 + dependencies:
2090 + '@eslint/core': 0.17.0
2091 + levn: 0.4.1
2092 +
2093 + '@humanfs/core@0.19.2':
2094 + dependencies:
2095 + '@humanfs/types': 0.15.0
2096 +
2097 + '@humanfs/node@0.16.8':
2098 + dependencies:
2099 + '@humanfs/core': 0.19.2
2100 + '@humanfs/types': 0.15.0
2101 + '@humanwhocodes/retry': 0.4.3
2102 +
2103 + '@humanfs/types@0.15.0': {}
2104 +
2105 + '@humanwhocodes/module-importer@1.0.1': {}
2106 +
2107 + '@humanwhocodes/retry@0.4.3': {}
2108 +
2109 + '@ioredis/commands@1.10.0': {}
2110 +
2111 + '@jridgewell/gen-mapping@0.3.13':
2112 + dependencies:
2113 + '@jridgewell/sourcemap-codec': 1.5.5
2114 + '@jridgewell/trace-mapping': 0.3.31
2115 +
2116 + '@jridgewell/resolve-uri@3.1.2': {}
2117 +
2118 + '@jridgewell/sourcemap-codec@1.5.5': {}
2119 +
2120 + '@jridgewell/trace-mapping@0.3.31':
2121 + dependencies:
2122 + '@jridgewell/resolve-uri': 3.1.2
2123 + '@jridgewell/sourcemap-codec': 1.5.5
2124 +
2125 + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
2126 + optional: true
2127 +
2128 + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4':
2129 + optional: true
2130 +
2131 + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4':
2132 + optional: true
2133 +
2134 + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4':
2135 + optional: true
2136 +
2137 + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4':
2138 + optional: true
2139 +
2140 + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4':
2141 + optional: true
2142 +
2143 + '@napi-rs/lzma-linux-x64-gnu@1.5.1':
2144 + optional: true
2145 +
2146 + '@next/env@14.2.35': {}
2147 +
2148 + '@next/swc-darwin-arm64@14.2.33':
2149 + optional: true
2150 +
2151 + '@next/swc-darwin-x64@14.2.33':
2152 + optional: true
2153 +
2154 + '@next/swc-linux-arm64-gnu@14.2.33':
2155 + optional: true
2156 +
2157 + '@next/swc-linux-arm64-musl@14.2.33':
2158 + optional: true
2159 +
2160 + '@next/swc-linux-x64-gnu@14.2.33':
2161 + optional: true
2162 +
2163 + '@next/swc-linux-x64-musl@14.2.33':
2164 + optional: true
2165 +
2166 + '@next/swc-win32-arm64-msvc@14.2.33':
2167 + optional: true
2168 +
2169 + '@next/swc-win32-ia32-msvc@14.2.33':
2170 + optional: true
2171 +
2172 + '@next/swc-win32-x64-msvc@14.2.33':
2173 + optional: true
2174 +
2175 + '@nodelib/fs.scandir@2.1.5':
2176 + dependencies:
2177 + '@nodelib/fs.stat': 2.0.5
2178 + run-parallel: 1.2.0
2179 +
2180 + '@nodelib/fs.stat@2.0.5': {}
2181 +
2182 + '@nodelib/fs.walk@1.2.8':
2183 + dependencies:
2184 + '@nodelib/fs.scandir': 2.1.5
2185 + fastq: 1.20.1
2186 +
2187 + '@prisma/client@5.22.0(prisma@5.22.0)':
2188 + optionalDependencies:
2189 + prisma: 5.22.0
2190 +
2191 + '@prisma/debug@5.22.0': {}
2192 +
2193 + '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': {}
2194 +
2195 + '@prisma/engines@5.22.0':
2196 + dependencies:
2197 + '@prisma/debug': 5.22.0
2198 + '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2
2199 + '@prisma/fetch-engine': 5.22.0
2200 + '@prisma/get-platform': 5.22.0
2201 +
2202 + '@prisma/fetch-engine@5.22.0':
2203 + dependencies:
2204 + '@prisma/debug': 5.22.0
2205 + '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2
2206 + '@prisma/get-platform': 5.22.0
2207 +
2208 + '@prisma/get-platform@5.22.0':
2209 + dependencies:
2210 + '@prisma/debug': 5.22.0
2211 +
2212 + '@rollup/rollup-android-arm-eabi@4.62.4':
2213 + optional: true
2214 +
2215 + '@rollup/rollup-android-arm64@4.62.4':
2216 + optional: true
2217 +
2218 + '@rollup/rollup-darwin-arm64@4.62.4':
2219 + optional: true
2220 +
2221 + '@rollup/rollup-darwin-x64@4.62.4':
2222 + optional: true
2223 +
2224 + '@rollup/rollup-freebsd-arm64@4.62.4':
2225 + optional: true
2226 +
2227 + '@rollup/rollup-freebsd-x64@4.62.4':
2228 + optional: true
2229 +
2230 + '@rollup/rollup-linux-arm-gnueabihf@4.62.4':
2231 + optional: true
2232 +
2233 + '@rollup/rollup-linux-arm-musleabihf@4.62.4':
2234 + optional: true
2235 +
2236 + '@rollup/rollup-linux-arm64-gnu@4.62.4':
2237 + optional: true
2238 +
2239 + '@rollup/rollup-linux-arm64-musl@4.62.4':
2240 + optional: true
2241 +
2242 + '@rollup/rollup-linux-loong64-gnu@4.62.4':
2243 + optional: true
2244 +
2245 + '@rollup/rollup-linux-loong64-musl@4.62.4':
2246 + optional: true
2247 +
2248 + '@rollup/rollup-linux-ppc64-gnu@4.62.4':
2249 + optional: true
2250 +
2251 + '@rollup/rollup-linux-ppc64-musl@4.62.4':
2252 + optional: true
2253 +
2254 + '@rollup/rollup-linux-riscv64-gnu@4.62.4':
2255 + optional: true
2256 +
2257 + '@rollup/rollup-linux-riscv64-musl@4.62.4':
2258 + optional: true
2259 +
2260 + '@rollup/rollup-linux-s390x-gnu@4.62.4':
2261 + optional: true
2262 +
2263 + '@rollup/rollup-linux-x64-gnu@4.62.4':
2264 + optional: true
2265 +
2266 + '@rollup/rollup-linux-x64-musl@4.62.4':
2267 + optional: true
2268 +
2269 + '@rollup/rollup-openbsd-x64@4.62.4':
2270 + optional: true
2271 +
2272 + '@rollup/rollup-openharmony-arm64@4.62.4':
2273 + optional: true
2274 +
2275 + '@rollup/rollup-win32-arm64-msvc@4.62.4':
2276 + optional: true
2277 +
2278 + '@rollup/rollup-win32-ia32-msvc@4.62.4':
2279 + optional: true
2280 +
2281 + '@rollup/rollup-win32-x64-gnu@4.62.4':
2282 + optional: true
2283 +
2284 + '@rollup/rollup-win32-x64-msvc@4.62.4':
2285 + optional: true
2286 +
2287 + '@stablelib/base64@1.0.1': {}
2288 +
2289 + '@swc/counter@0.1.3': {}
2290 +
2291 + '@swc/helpers@0.5.5':
2292 + dependencies:
2293 + '@swc/counter': 0.1.3
2294 + tslib: 2.8.1
2295 +
2296 + '@turbo/darwin-64@2.10.8':
2297 + optional: true
2298 +
2299 + '@turbo/darwin-arm64@2.10.8':
2300 + optional: true
2301 +
2302 + '@turbo/linux-64@2.10.8':
2303 + optional: true
2304 +
2305 + '@turbo/linux-arm64@2.10.8':
2306 + optional: true
2307 +
2308 + '@turbo/windows-64@2.10.8':
2309 + optional: true
2310 +
2311 + '@turbo/windows-arm64@2.10.8':
2312 + optional: true
2313 +
2314 + '@types/estree@1.0.9': {}
2315 +
2316 + '@types/json-schema@7.0.15': {}
2317 +
2318 + '@types/node@20.19.43':
2319 + dependencies:
2320 + undici-types: 6.21.0
2321 +
2322 + '@types/prop-types@15.7.15': {}
2323 +
2324 + '@types/react-dom@18.3.7(@types/react@18.3.31)':
2325 + dependencies:
2326 + '@types/react': 18.3.31
2327 +
2328 + '@types/react@18.3.31':
2329 + dependencies:
2330 + '@types/prop-types': 15.7.15
2331 + csstype: 3.2.3
2332 +
2333 + '@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)':
2334 + dependencies:
2335 + '@eslint-community/regexpp': 4.12.2
2336 + '@typescript-eslint/parser': 8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)
2337 + '@typescript-eslint/scope-manager': 8.66.0
2338 + '@typescript-eslint/type-utils': 8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)
2339 + '@typescript-eslint/utils': 8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)
2340 + '@typescript-eslint/visitor-keys': 8.66.0
2341 + eslint: 9.39.5(jiti@1.21.7)
2342 + ignore: 7.0.6
2343 + natural-compare: 1.4.0
2344 + ts-api-utils: 2.5.0(typescript@5.9.3)
2345 + typescript: 5.9.3
2346 + transitivePeerDependencies:
2347 + - supports-color
2348 +
2349 + '@typescript-eslint/parser@8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)':
2350 + dependencies:
2351 + '@typescript-eslint/scope-manager': 8.66.0
2352 + '@typescript-eslint/types': 8.66.0
2353 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@5.9.3)
2354 + '@typescript-eslint/visitor-keys': 8.66.0
2355 + debug: 4.4.3
2356 + eslint: 9.39.5(jiti@1.21.7)
2357 + typescript: 5.9.3
2358 + transitivePeerDependencies:
2359 + - supports-color
2360 +
2361 + '@typescript-eslint/project-service@8.66.0(typescript@5.9.3)':
2362 + dependencies:
2363 + '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@5.9.3)
2364 + '@typescript-eslint/types': 8.66.0
2365 + debug: 4.4.3
2366 + typescript: 5.9.3
2367 + transitivePeerDependencies:
2368 + - supports-color
2369 +
2370 + '@typescript-eslint/scope-manager@8.66.0':
2371 + dependencies:
2372 + '@typescript-eslint/types': 8.66.0
2373 + '@typescript-eslint/visitor-keys': 8.66.0
2374 +
2375 + '@typescript-eslint/tsconfig-utils@8.66.0(typescript@5.9.3)':
2376 + dependencies:
2377 + typescript: 5.9.3
2378 +
2379 + '@typescript-eslint/type-utils@8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)':
2380 + dependencies:
2381 + '@typescript-eslint/types': 8.66.0
2382 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@5.9.3)
2383 + '@typescript-eslint/utils': 8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)
2384 + debug: 4.4.3
2385 + eslint: 9.39.5(jiti@1.21.7)
2386 + ts-api-utils: 2.5.0(typescript@5.9.3)
2387 + typescript: 5.9.3
2388 + transitivePeerDependencies:
2389 + - supports-color
2390 +
2391 + '@typescript-eslint/types@8.66.0': {}
2392 +
2393 + '@typescript-eslint/typescript-estree@8.66.0(typescript@5.9.3)':
2394 + dependencies:
2395 + '@typescript-eslint/project-service': 8.66.0(typescript@5.9.3)
2396 + '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@5.9.3)
2397 + '@typescript-eslint/types': 8.66.0
2398 + '@typescript-eslint/visitor-keys': 8.66.0
2399 + debug: 4.4.3
2400 + minimatch: 10.2.6
2401 + semver: 7.8.5
2402 + tinyglobby: 0.2.17
2403 + ts-api-utils: 2.5.0(typescript@5.9.3)
2404 + typescript: 5.9.3
2405 + transitivePeerDependencies:
2406 + - supports-color
2407 +
2408 + '@typescript-eslint/utils@8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)':
2409 + dependencies:
2410 + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@1.21.7))
2411 + '@typescript-eslint/scope-manager': 8.66.0
2412 + '@typescript-eslint/types': 8.66.0
2413 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@5.9.3)
2414 + eslint: 9.39.5(jiti@1.21.7)
2415 + typescript: 5.9.3
2416 + transitivePeerDependencies:
2417 + - supports-color
2418 +
2419 + '@typescript-eslint/visitor-keys@8.66.0':
2420 + dependencies:
2421 + '@typescript-eslint/types': 8.66.0
2422 + eslint-visitor-keys: 5.0.1
2423 +
2424 + '@vitest/expect@2.1.9':
2425 + dependencies:
2426 + '@vitest/spy': 2.1.9
2427 + '@vitest/utils': 2.1.9
2428 + chai: 5.3.3
2429 + tinyrainbow: 1.2.0
2430 +
2431 + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.43))':
2432 + dependencies:
2433 + '@vitest/spy': 2.1.9
2434 + estree-walker: 3.0.3
2435 + magic-string: 0.30.21
2436 + optionalDependencies:
2437 + vite: 5.4.21(@types/node@20.19.43)
2438 +
2439 + '@vitest/pretty-format@2.1.9':
2440 + dependencies:
2441 + tinyrainbow: 1.2.0
2442 +
2443 + '@vitest/runner@2.1.9':
2444 + dependencies:
2445 + '@vitest/utils': 2.1.9
2446 + pathe: 1.1.2
2447 +
2448 + '@vitest/snapshot@2.1.9':
2449 + dependencies:
2450 + '@vitest/pretty-format': 2.1.9
2451 + magic-string: 0.30.21
2452 + pathe: 1.1.2
2453 +
2454 + '@vitest/spy@2.1.9':
2455 + dependencies:
2456 + tinyspy: 3.0.2
2457 +
2458 + '@vitest/utils@2.1.9':
2459 + dependencies:
2460 + '@vitest/pretty-format': 2.1.9
2461 + loupe: 3.2.1
2462 + tinyrainbow: 1.2.0
2463 +
2464 + acorn-jsx@5.3.2(acorn@8.18.0):
2465 + dependencies:
2466 + acorn: 8.18.0
2467 +
2468 + acorn@8.18.0: {}
2469 +
2470 + ajv@6.15.0:
2471 + dependencies:
2472 + fast-deep-equal: 3.1.3
2473 + fast-json-stable-stringify: 2.1.0
2474 + json-schema-traverse: 0.4.1
2475 + uri-js: 4.4.1
2476 +
2477 + ansi-styles@4.3.0:
2478 + dependencies:
2479 + color-convert: 2.0.1
2480 +
2481 + any-promise@1.3.0: {}
2482 +
2483 + anymatch@3.1.3:
2484 + dependencies:
2485 + normalize-path: 3.0.0
2486 + picomatch: 2.3.2
2487 +
2488 + arg@5.0.2: {}
2489 +
2490 + argparse@2.0.1: {}
2491 +
2492 + assertion-error@2.0.1: {}
2493 +
2494 + autoprefixer@10.5.4(postcss@8.5.25):
2495 + dependencies:
2496 + browserslist: 4.28.7
2497 + caniuse-lite: 1.0.30001806
2498 + fraction.js: 5.3.4
2499 + picocolors: 1.1.1
2500 + postcss: 8.5.25
2501 + postcss-value-parser: 4.2.0
2502 +
2503 + balanced-match@1.0.2: {}
2504 +
2505 + balanced-match@4.0.4: {}
2506 +
2507 + baseline-browser-mapping@2.11.12: {}
2508 +
2509 + binary-extensions@2.3.0: {}
2510 +
2511 + brace-expansion@1.1.18:
2512 + dependencies:
2513 + balanced-match: 1.0.2
2514 + concat-map: 0.0.1
2515 +
2516 + brace-expansion@5.0.9:
2517 + dependencies:
2518 + balanced-match: 4.0.4
2519 +
2520 + braces@3.0.3:
2521 + dependencies:
2522 + fill-range: 7.1.1
2523 +
2524 + browserslist@4.28.7:
2525 + dependencies:
2526 + baseline-browser-mapping: 2.11.12
2527 + caniuse-lite: 1.0.30001806
2528 + electron-to-chromium: 1.5.401
2529 + node-releases: 2.0.52
2530 + update-browserslist-db: 1.2.3(browserslist@4.28.7)
2531 +
2532 + bullmq@5.81.3:
2533 + dependencies:
2534 + cron-parser: 4.9.0
2535 + ioredis: 5.11.1
2536 + msgpackr: 2.0.5
2537 + node-abort-controller: 3.1.1
2538 + semver: 7.8.5
2539 + tslib: 2.8.1
2540 + transitivePeerDependencies:
2541 + - supports-color
2542 +
2543 + busboy@1.6.0:
2544 + dependencies:
2545 + streamsearch: 1.1.0
2546 +
2547 + cac@6.7.14: {}
2548 +
2549 + callsites@3.1.0: {}
2550 +
2551 + camelcase-css@2.0.1: {}
2552 +
2553 + caniuse-lite@1.0.30001806: {}
2554 +
2555 + chai@5.3.3:
2556 + dependencies:
2557 + assertion-error: 2.0.1
2558 + check-error: 2.1.3
2559 + deep-eql: 5.0.2
2560 + loupe: 3.2.1
2561 + pathval: 2.0.1
2562 +
2563 + chalk@4.1.2:
2564 + dependencies:
2565 + ansi-styles: 4.3.0
2566 + supports-color: 7.2.0
2567 +
2568 + check-error@2.1.3: {}
2569 +
2570 + chokidar@3.6.0:
2571 + dependencies:
2572 + anymatch: 3.1.3
2573 + braces: 3.0.3
2574 + glob-parent: 5.1.2
2575 + is-binary-path: 2.1.0
2576 + is-glob: 4.0.3
2577 + normalize-path: 3.0.0
2578 + readdirp: 3.6.0
2579 + optionalDependencies:
2580 + fsevents: 2.3.3
2581 +
2582 + client-only@0.0.1: {}
2583 +
2584 + cluster-key-slot@1.1.1: {}
2585 +
2586 + color-convert@2.0.1:
2587 + dependencies:
2588 + color-name: 1.1.4
2589 +
2590 + color-name@1.1.4: {}
2591 +
2592 + commander@4.1.1: {}
2593 +
2594 + concat-map@0.0.1: {}
2595 +
2596 + cron-parser@4.9.0:
2597 + dependencies:
2598 + luxon: 3.7.2
2599 +
2600 + cross-spawn@7.0.6:
2601 + dependencies:
2602 + path-key: 3.1.1
2603 + shebang-command: 2.0.0
2604 + which: 2.0.2
2605 +
2606 + cssesc@3.0.0: {}
2607 +
2608 + csstype@3.2.3: {}
2609 +
2610 + debug@4.4.3:
2611 + dependencies:
2612 + ms: 2.1.3
2613 +
2614 + deep-eql@5.0.2: {}
2615 +
2616 + deep-is@0.1.4: {}
2617 +
2618 + denque@2.1.0: {}
2619 +
2620 + detect-libc@2.1.2:
2621 + optional: true
2622 +
2623 + didyoumean@1.2.2: {}
2624 +
2625 + dlv@1.1.3: {}
2626 +
2627 + electron-to-chromium@1.5.401: {}
2628 +
2629 + es-errors@1.3.0: {}
2630 +
2631 + es-module-lexer@1.7.0: {}
2632 +
2633 + esbuild@0.21.5:
2634 + optionalDependencies:
2635 + '@esbuild/aix-ppc64': 0.21.5
2636 + '@esbuild/android-arm': 0.21.5
2637 + '@esbuild/android-arm64': 0.21.5
2638 + '@esbuild/android-x64': 0.21.5
2639 + '@esbuild/darwin-arm64': 0.21.5
2640 + '@esbuild/darwin-x64': 0.21.5
2641 + '@esbuild/freebsd-arm64': 0.21.5
2642 + '@esbuild/freebsd-x64': 0.21.5
2643 + '@esbuild/linux-arm': 0.21.5
2644 + '@esbuild/linux-arm64': 0.21.5
2645 + '@esbuild/linux-ia32': 0.21.5
2646 + '@esbuild/linux-loong64': 0.21.5
2647 + '@esbuild/linux-mips64el': 0.21.5
2648 + '@esbuild/linux-ppc64': 0.21.5
2649 + '@esbuild/linux-riscv64': 0.21.5
2650 + '@esbuild/linux-s390x': 0.21.5
2651 + '@esbuild/linux-x64': 0.21.5
2652 + '@esbuild/netbsd-x64': 0.21.5
2653 + '@esbuild/openbsd-x64': 0.21.5
2654 + '@esbuild/sunos-x64': 0.21.5
2655 + '@esbuild/win32-arm64': 0.21.5
2656 + '@esbuild/win32-ia32': 0.21.5
2657 + '@esbuild/win32-x64': 0.21.5
2658 +
2659 + esbuild@0.28.1:
2660 + optionalDependencies:
2661 + '@esbuild/aix-ppc64': 0.28.1
2662 + '@esbuild/android-arm': 0.28.1
2663 + '@esbuild/android-arm64': 0.28.1
2664 + '@esbuild/android-x64': 0.28.1
2665 + '@esbuild/darwin-arm64': 0.28.1
2666 + '@esbuild/darwin-x64': 0.28.1
2667 + '@esbuild/freebsd-arm64': 0.28.1
2668 + '@esbuild/freebsd-x64': 0.28.1
2669 + '@esbuild/linux-arm': 0.28.1
2670 + '@esbuild/linux-arm64': 0.28.1
2671 + '@esbuild/linux-ia32': 0.28.1
2672 + '@esbuild/linux-loong64': 0.28.1
2673 + '@esbuild/linux-mips64el': 0.28.1
2674 + '@esbuild/linux-ppc64': 0.28.1
2675 + '@esbuild/linux-riscv64': 0.28.1
2676 + '@esbuild/linux-s390x': 0.28.1
2677 + '@esbuild/linux-x64': 0.28.1
2678 + '@esbuild/netbsd-arm64': 0.28.1
2679 + '@esbuild/netbsd-x64': 0.28.1
2680 + '@esbuild/openbsd-arm64': 0.28.1
2681 + '@esbuild/openbsd-x64': 0.28.1
2682 + '@esbuild/openharmony-arm64': 0.28.1
2683 + '@esbuild/sunos-x64': 0.28.1
2684 + '@esbuild/win32-arm64': 0.28.1
2685 + '@esbuild/win32-ia32': 0.28.1
2686 + '@esbuild/win32-x64': 0.28.1
2687 +
2688 + escalade@3.2.0: {}
2689 +
2690 + escape-string-regexp@4.0.0: {}
2691 +
2692 + eslint-scope@8.4.0:
2693 + dependencies:
2694 + esrecurse: 4.3.0
2695 + estraverse: 5.3.0
2696 +
2697 + eslint-visitor-keys@3.4.3: {}
2698 +
2699 + eslint-visitor-keys@4.2.1: {}
2700 +
2701 + eslint-visitor-keys@5.0.1: {}
2702 +
2703 + eslint@9.39.5(jiti@1.21.7):
2704 + dependencies:
2705 + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@1.21.7))
2706 + '@eslint-community/regexpp': 4.12.2
2707 + '@eslint/config-array': 0.21.2
2708 + '@eslint/config-helpers': 0.4.2
2709 + '@eslint/core': 0.17.0
2710 + '@eslint/eslintrc': 3.3.6
2711 + '@eslint/js': 9.39.5
2712 + '@eslint/plugin-kit': 0.4.1
2713 + '@humanfs/node': 0.16.8
2714 + '@humanwhocodes/module-importer': 1.0.1
2715 + '@humanwhocodes/retry': 0.4.3
2716 + '@types/estree': 1.0.9
2717 + ajv: 6.15.0
2718 + chalk: 4.1.2
2719 + cross-spawn: 7.0.6
2720 + debug: 4.4.3
2721 + escape-string-regexp: 4.0.0
2722 + eslint-scope: 8.4.0
2723 + eslint-visitor-keys: 4.2.1
2724 + espree: 10.4.0
2725 + esquery: 1.7.0
2726 + esutils: 2.0.3
2727 + fast-deep-equal: 3.1.3
2728 + file-entry-cache: 8.0.0
2729 + find-up: 5.0.0
2730 + glob-parent: 6.0.2
2731 + ignore: 5.3.2
2732 + imurmurhash: 0.1.4
2733 + is-glob: 4.0.3
2734 + json-stable-stringify-without-jsonify: 1.0.1
2735 + lodash.merge: 4.6.2
2736 + minimatch: 3.1.5
2737 + natural-compare: 1.4.0
2738 + optionator: 0.9.4
2739 + optionalDependencies:
2740 + jiti: 1.21.7
2741 + transitivePeerDependencies:
2742 + - supports-color
2743 +
2744 + espree@10.4.0:
2745 + dependencies:
2746 + acorn: 8.18.0
2747 + acorn-jsx: 5.3.2(acorn@8.18.0)
2748 + eslint-visitor-keys: 4.2.1
2749 +
2750 + esquery@1.7.0:
2751 + dependencies:
2752 + estraverse: 5.3.0
2753 +
2754 + esrecurse@4.3.0:
2755 + dependencies:
2756 + estraverse: 5.3.0
2757 +
2758 + estraverse@5.3.0: {}
2759 +
2760 + estree-walker@3.0.3:
2761 + dependencies:
2762 + '@types/estree': 1.0.9
2763 +
2764 + esutils@2.0.3: {}
2765 +
2766 + expect-type@1.4.0: {}
2767 +
2768 + fast-check@3.23.2:
2769 + dependencies:
2770 + pure-rand: 6.1.0
2771 +
2772 + fast-deep-equal@3.1.3: {}
2773 +
2774 + fast-glob@3.3.3:
2775 + dependencies:
2776 + '@nodelib/fs.stat': 2.0.5
2777 + '@nodelib/fs.walk': 1.2.8
2778 + glob-parent: 5.1.2
2779 + merge2: 1.4.1
2780 + micromatch: 4.0.8
2781 +
2782 + fast-json-stable-stringify@2.1.0: {}
2783 +
2784 + fast-levenshtein@2.0.6: {}
2785 +
2786 + fast-sha256@1.3.0: {}
2787 +
2788 + fastq@1.20.1:
2789 + dependencies:
2790 + reusify: 1.1.0
2791 +
2792 + fdir@6.5.0(picomatch@4.0.5):
2793 + optionalDependencies:
2794 + picomatch: 4.0.5
2795 +
2796 + file-entry-cache@8.0.0:
2797 + dependencies:
2798 + flat-cache: 4.0.1
2799 +
2800 + fill-range@7.1.1:
2801 + dependencies:
2802 + to-regex-range: 5.0.1
2803 +
2804 + find-up@5.0.0:
2805 + dependencies:
2806 + locate-path: 6.0.0
2807 + path-exists: 4.0.0
2808 +
2809 + flat-cache@4.0.1:
2810 + dependencies:
2811 + flatted: 3.4.4
2812 + keyv: 4.5.4
2813 +
2814 + flatted@3.4.4: {}
2815 +
2816 + fraction.js@5.3.4: {}
2817 +
2818 + fsevents@2.3.3:
2819 + optional: true
2820 +
2821 + function-bind@1.1.2: {}
2822 +
2823 + glob-parent@5.1.2:
2824 + dependencies:
2825 + is-glob: 4.0.3
2826 +
2827 + glob-parent@6.0.2:
2828 + dependencies:
2829 + is-glob: 4.0.3
2830 +
2831 + globals@14.0.0: {}
2832 +
2833 + graceful-fs@4.2.11: {}
2834 +
2835 + has-flag@4.0.0: {}
2836 +
2837 + hasown@2.0.4:
2838 + dependencies:
2839 + function-bind: 1.1.2
2840 +
2841 + ignore@5.3.2: {}
2842 +
2843 + ignore@7.0.6: {}
2844 +
2845 + import-fresh@3.3.1:
2846 + dependencies:
2847 + parent-module: 1.0.1
2848 + resolve-from: 4.0.0
2849 +
2850 + imurmurhash@0.1.4: {}
2851 +
2852 + ioredis@5.11.1:
2853 + dependencies:
2854 + '@ioredis/commands': 1.10.0
2855 + cluster-key-slot: 1.1.1
2856 + debug: 4.4.3
2857 + denque: 2.1.0
2858 + redis-errors: 1.2.0
2859 + redis-parser: 3.0.0
2860 + standard-as-callback: 2.1.0
2861 + transitivePeerDependencies:
2862 + - supports-color
2863 +
2864 + is-binary-path@2.1.0:
2865 + dependencies:
2866 + binary-extensions: 2.3.0
2867 +
2868 + is-core-module@2.16.2:
2869 + dependencies:
2870 + hasown: 2.0.4
2871 +
2872 + is-extglob@2.1.1: {}
2873 +
2874 + is-glob@4.0.3:
2875 + dependencies:
2876 + is-extglob: 2.1.1
2877 +
2878 + is-number@7.0.0: {}
2879 +
2880 + isexe@2.0.0: {}
2881 +
2882 + jiti@1.21.7: {}
2883 +
2884 + js-tokens@4.0.0: {}
2885 +
2886 + js-yaml@4.3.1:
2887 + dependencies:
2888 + argparse: 2.0.1
2889 +
2890 + json-buffer@3.0.1: {}
2891 +
2892 + json-schema-to-ts@3.1.1:
2893 + dependencies:
2894 + '@babel/runtime': 7.29.7
2895 + ts-algebra: 2.0.0
2896 +
2897 + json-schema-traverse@0.4.1: {}
2898 +
2899 + json-stable-stringify-without-jsonify@1.0.1: {}
2900 +
2901 + keyv@4.5.4:
2902 + dependencies:
2903 + json-buffer: 3.0.1
2904 +
2905 + levn@0.4.1:
2906 + dependencies:
2907 + prelude-ls: 1.2.1
2908 + type-check: 0.4.0
2909 +
2910 + lilconfig@3.1.3: {}
2911 +
2912 + lines-and-columns@1.2.4: {}
2913 +
2914 + locate-path@6.0.0:
2915 + dependencies:
2916 + p-locate: 5.0.0
2917 +
2918 + lodash.merge@4.6.2: {}
2919 +
2920 + loose-envify@1.4.0:
2921 + dependencies:
2922 + js-tokens: 4.0.0
2923 +
2924 + loupe@3.2.1: {}
2925 +
2926 + luxon@3.7.2: {}
2927 +
2928 + magic-string@0.30.21:
2929 + dependencies:
2930 + '@jridgewell/sourcemap-codec': 1.5.5
2931 +
2932 + merge2@1.4.1: {}
2933 +
2934 + micromatch@4.0.8:
2935 + dependencies:
2936 + braces: 3.0.3
2937 + picomatch: 2.3.2
2938 +
2939 + minimatch@10.2.6:
2940 + dependencies:
2941 + brace-expansion: 5.0.9
2942 +
2943 + minimatch@3.1.5:
2944 + dependencies:
2945 + brace-expansion: 1.1.18
2946 +
2947 + ms@2.1.3: {}
2948 +
2949 + msgpackr-extract@3.0.4:
2950 + dependencies:
2951 + node-gyp-build-optional-packages: 5.2.2
2952 + optionalDependencies:
2953 + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.4
2954 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.4
2955 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.4
2956 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.4
2957 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.4
2958 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.4
2959 + optional: true
2960 +
2961 + msgpackr@2.0.5:
2962 + optionalDependencies:
2963 + msgpackr-extract: 3.0.4
2964 +
2965 + mz@2.7.0:
2966 + dependencies:
2967 + any-promise: 1.3.0
2968 + object-assign: 4.1.1
2969 + thenify-all: 1.6.0
2970 +
2971 + nanoid@3.3.17: {}
2972 +
2973 + natural-compare@1.4.0: {}
2974 +
2975 + next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
2976 + dependencies:
2977 + '@next/env': 14.2.35
2978 + '@swc/helpers': 0.5.5
2979 + busboy: 1.6.0
2980 + caniuse-lite: 1.0.30001806
2981 + graceful-fs: 4.2.11
2982 + postcss: 8.4.31
2983 + react: 18.3.1
2984 + react-dom: 18.3.1(react@18.3.1)
2985 + styled-jsx: 5.1.1(react@18.3.1)
2986 + optionalDependencies:
2987 + '@next/swc-darwin-arm64': 14.2.33
2988 + '@next/swc-darwin-x64': 14.2.33
2989 + '@next/swc-linux-arm64-gnu': 14.2.33
2990 + '@next/swc-linux-arm64-musl': 14.2.33
2991 + '@next/swc-linux-x64-gnu': 14.2.33
2992 + '@next/swc-linux-x64-musl': 14.2.33
2993 + '@next/swc-win32-arm64-msvc': 14.2.33
2994 + '@next/swc-win32-ia32-msvc': 14.2.33
2995 + '@next/swc-win32-x64-msvc': 14.2.33
2996 + transitivePeerDependencies:
2997 + - '@babel/core'
2998 + - babel-plugin-macros
2999 +
3000 + node-abort-controller@3.1.1: {}
3001 +
3002 + node-gyp-build-optional-packages@5.2.2:
3003 + dependencies:
3004 + detect-libc: 2.1.2
3005 + optional: true
3006 +
3007 + node-releases@2.0.52: {}
3008 +
3009 + normalize-path@3.0.0: {}
3010 +
3011 + object-assign@4.1.1: {}
3012 +
3013 + object-hash@3.0.0: {}
3014 +
3015 + optionator@0.9.4:
3016 + dependencies:
3017 + deep-is: 0.1.4
3018 + fast-levenshtein: 2.0.6
3019 + levn: 0.4.1
3020 + prelude-ls: 1.2.1
3021 + type-check: 0.4.0
3022 + word-wrap: 1.2.5
3023 +
3024 + p-limit@3.1.0:
3025 + dependencies:
3026 + yocto-queue: 0.1.0
3027 +
3028 + p-locate@5.0.0:
3029 + dependencies:
3030 + p-limit: 3.1.0
3031 +
3032 + parent-module@1.0.1:
3033 + dependencies:
3034 + callsites: 3.1.0
3035 +
3036 + path-exists@4.0.0: {}
3037 +
3038 + path-key@3.1.1: {}
3039 +
3040 + path-parse@1.0.7: {}
3041 +
3042 + pathe@1.1.2: {}
3043 +
3044 + pathval@2.0.1: {}
3045 +
3046 + picocolors@1.1.1: {}
3047 +
3048 + picomatch@2.3.2: {}
3049 +
3050 + picomatch@4.0.5: {}
3051 +
3052 + pify@2.3.0: {}
3053 +
3054 + pirates@4.0.7: {}
3055 +
3056 + postcss-import@15.1.0(postcss@8.5.25):
3057 + dependencies:
3058 + postcss: 8.5.25
3059 + postcss-value-parser: 4.2.0
3060 + read-cache: 1.0.0
3061 + resolve: 1.22.12
3062 +
3063 + postcss-js@4.1.0(postcss@8.5.25):
3064 + dependencies:
3065 + camelcase-css: 2.0.1
3066 + postcss: 8.5.25
3067 +
3068 + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.25)(tsx@4.23.6):
3069 + dependencies:
3070 + lilconfig: 3.1.3
3071 + optionalDependencies:
3072 + jiti: 1.21.7
3073 + postcss: 8.5.25
3074 + tsx: 4.23.6
3075 +
3076 + postcss-nested@6.2.0(postcss@8.5.25):
3077 + dependencies:
3078 + postcss: 8.5.25
3079 + postcss-selector-parser: 6.1.4
3080 +
3081 + postcss-selector-parser@6.1.4:
3082 + dependencies:
3083 + cssesc: 3.0.0
3084 + util-deprecate: 1.0.2
3085 +
3086 + postcss-value-parser@4.2.0: {}
3087 +
3088 + postcss@8.4.31:
3089 + dependencies:
3090 + nanoid: 3.3.17
3091 + picocolors: 1.1.1
3092 + source-map-js: 1.2.1
3093 +
3094 + postcss@8.5.25:
3095 + dependencies:
3096 + nanoid: 3.3.17
3097 + picocolors: 1.1.1
3098 + source-map-js: 1.2.1
3099 +
3100 + prelude-ls@1.2.1: {}
3101 +
3102 + prettier@3.9.6: {}
3103 +
3104 + prisma@5.22.0:
3105 + dependencies:
3106 + '@prisma/engines': 5.22.0
3107 + optionalDependencies:
3108 + fsevents: 2.3.3
3109 +
3110 + punycode@2.3.1: {}
3111 +
3112 + pure-rand@6.1.0: {}
3113 +
3114 + queue-microtask@1.2.3: {}
3115 +
3116 + react-dom@18.3.1(react@18.3.1):
3117 + dependencies:
3118 + loose-envify: 1.4.0
3119 + react: 18.3.1
3120 + scheduler: 0.23.2
3121 +
3122 + react@18.3.1:
3123 + dependencies:
3124 + loose-envify: 1.4.0
3125 +
3126 + read-cache@1.0.0:
3127 + dependencies:
3128 + pify: 2.3.0
3129 +
3130 + readdirp@3.6.0:
3131 + dependencies:
3132 + picomatch: 2.3.2
3133 +
3134 + redis-errors@1.2.0: {}
3135 +
3136 + redis-parser@3.0.0:
3137 + dependencies:
3138 + redis-errors: 1.2.0
3139 +
3140 + resolve-from@4.0.0: {}
3141 +
3142 + resolve@1.22.12:
3143 + dependencies:
3144 + es-errors: 1.3.0
3145 + is-core-module: 2.16.2
3146 + path-parse: 1.0.7
3147 + supports-preserve-symlinks-flag: 1.0.0
3148 +
3149 + reusify@1.1.0: {}
3150 +
3151 + rollup@4.62.4:
3152 + dependencies:
3153 + '@types/estree': 1.0.9
3154 + optionalDependencies:
3155 + '@napi-rs/lzma-linux-x64-gnu': 1.5.1
3156 + '@rollup/rollup-android-arm-eabi': 4.62.4
3157 + '@rollup/rollup-android-arm64': 4.62.4
3158 + '@rollup/rollup-darwin-arm64': 4.62.4
3159 + '@rollup/rollup-darwin-x64': 4.62.4
3160 + '@rollup/rollup-freebsd-arm64': 4.62.4
3161 + '@rollup/rollup-freebsd-x64': 4.62.4
3162 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.4
3163 + '@rollup/rollup-linux-arm-musleabihf': 4.62.4
3164 + '@rollup/rollup-linux-arm64-gnu': 4.62.4
3165 + '@rollup/rollup-linux-arm64-musl': 4.62.4
3166 + '@rollup/rollup-linux-loong64-gnu': 4.62.4
3167 + '@rollup/rollup-linux-loong64-musl': 4.62.4
3168 + '@rollup/rollup-linux-ppc64-gnu': 4.62.4
3169 + '@rollup/rollup-linux-ppc64-musl': 4.62.4
3170 + '@rollup/rollup-linux-riscv64-gnu': 4.62.4
3171 + '@rollup/rollup-linux-riscv64-musl': 4.62.4
3172 + '@rollup/rollup-linux-s390x-gnu': 4.62.4
3173 + '@rollup/rollup-linux-x64-gnu': 4.62.4
3174 + '@rollup/rollup-linux-x64-musl': 4.62.4
3175 + '@rollup/rollup-openbsd-x64': 4.62.4
3176 + '@rollup/rollup-openharmony-arm64': 4.62.4
3177 + '@rollup/rollup-win32-arm64-msvc': 4.62.4
3178 + '@rollup/rollup-win32-ia32-msvc': 4.62.4
3179 + '@rollup/rollup-win32-x64-gnu': 4.62.4
3180 + '@rollup/rollup-win32-x64-msvc': 4.62.4
3181 + fsevents: 2.3.3
3182 +
3183 + run-parallel@1.2.0:
3184 + dependencies:
3185 + queue-microtask: 1.2.3
3186 +
3187 + scheduler@0.23.2:
3188 + dependencies:
3189 + loose-envify: 1.4.0
3190 +
3191 + semver@7.8.5: {}
3192 +
3193 + shebang-command@2.0.0:
3194 + dependencies:
3195 + shebang-regex: 3.0.0
3196 +
3197 + shebang-regex@3.0.0: {}
3198 +
3199 + siginfo@2.0.0: {}
3200 +
3201 + source-map-js@1.2.1: {}
3202 +
3203 + stackback@0.0.2: {}
3204 +
3205 + standard-as-callback@2.1.0: {}
3206 +
3207 + standardwebhooks@1.0.0:
3208 + dependencies:
3209 + '@stablelib/base64': 1.0.1
3210 + fast-sha256: 1.3.0
3211 +
3212 + std-env@3.10.0: {}
3213 +
3214 + streamsearch@1.1.0: {}
3215 +
3216 + strip-json-comments@3.1.1: {}
3217 +
3218 + styled-jsx@5.1.1(react@18.3.1):
3219 + dependencies:
3220 + client-only: 0.0.1
3221 + react: 18.3.1
3222 +
3223 + sucrase@3.35.1:
3224 + dependencies:
3225 + '@jridgewell/gen-mapping': 0.3.13
3226 + commander: 4.1.1
3227 + lines-and-columns: 1.2.4
3228 + mz: 2.7.0
3229 + pirates: 4.0.7
3230 + tinyglobby: 0.2.17
3231 + ts-interface-checker: 0.1.13
3232 +
3233 + supports-color@7.2.0:
3234 + dependencies:
3235 + has-flag: 4.0.0
3236 +
3237 + supports-preserve-symlinks-flag@1.0.0: {}
3238 +
3239 + tailwindcss@3.4.19(tsx@4.23.6):
3240 + dependencies:
3241 + '@alloc/quick-lru': 5.2.0
3242 + arg: 5.0.2
3243 + chokidar: 3.6.0
3244 + didyoumean: 1.2.2
3245 + dlv: 1.1.3
3246 + fast-glob: 3.3.3
3247 + glob-parent: 6.0.2
3248 + is-glob: 4.0.3
3249 + jiti: 1.21.7
3250 + lilconfig: 3.1.3
3251 + micromatch: 4.0.8
3252 + normalize-path: 3.0.0
3253 + object-hash: 3.0.0
3254 + picocolors: 1.1.1
3255 + postcss: 8.5.25
3256 + postcss-import: 15.1.0(postcss@8.5.25)
3257 + postcss-js: 4.1.0(postcss@8.5.25)
3258 + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.25)(tsx@4.23.6)
3259 + postcss-nested: 6.2.0(postcss@8.5.25)
3260 + postcss-selector-parser: 6.1.4
3261 + resolve: 1.22.12
3262 + sucrase: 3.35.1
3263 + transitivePeerDependencies:
3264 + - tsx
3265 + - yaml
3266 +
3267 + thenify-all@1.6.0:
3268 + dependencies:
3269 + thenify: 3.3.1
3270 +
3271 + thenify@3.3.1:
3272 + dependencies:
3273 + any-promise: 1.3.0
3274 +
3275 + tinybench@2.9.0: {}
3276 +
3277 + tinyexec@0.3.2: {}
3278 +
3279 + tinyglobby@0.2.17:
3280 + dependencies:
3281 + fdir: 6.5.0(picomatch@4.0.5)
3282 + picomatch: 4.0.5
3283 +
3284 + tinypool@1.1.1: {}
3285 +
3286 + tinyrainbow@1.2.0: {}
3287 +
3288 + tinyspy@3.0.2: {}
3289 +
3290 + to-regex-range@5.0.1:
3291 + dependencies:
3292 + is-number: 7.0.0
3293 +
3294 + ts-algebra@2.0.0: {}
3295 +
3296 + ts-api-utils@2.5.0(typescript@5.9.3):
3297 + dependencies:
3298 + typescript: 5.9.3
3299 +
3300 + ts-interface-checker@0.1.13: {}
3301 +
3302 + tslib@2.8.1: {}
3303 +
3304 + tsx@4.23.6:
3305 + dependencies:
3306 + esbuild: 0.28.1
3307 + optionalDependencies:
3308 + fsevents: 2.3.3
3309 +
3310 + turbo@2.10.8:
3311 + optionalDependencies:
3312 + '@turbo/darwin-64': 2.10.8
3313 + '@turbo/darwin-arm64': 2.10.8
3314 + '@turbo/linux-64': 2.10.8
3315 + '@turbo/linux-arm64': 2.10.8
3316 + '@turbo/windows-64': 2.10.8
3317 + '@turbo/windows-arm64': 2.10.8
3318 +
3319 + type-check@0.4.0:
3320 + dependencies:
3321 + prelude-ls: 1.2.1
3322 +
3323 + typescript-eslint@8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3):
3324 + dependencies:
3325 + '@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)
3326 + '@typescript-eslint/parser': 8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)
3327 + '@typescript-eslint/typescript-estree': 8.66.0(typescript@5.9.3)
3328 + '@typescript-eslint/utils': 8.66.0(eslint@9.39.5(jiti@1.21.7))(typescript@5.9.3)
3329 + eslint: 9.39.5(jiti@1.21.7)
3330 + typescript: 5.9.3
3331 + transitivePeerDependencies:
3332 + - supports-color
3333 +
3334 + typescript@5.9.3: {}
3335 +
3336 + undici-types@6.21.0: {}
3337 +
3338 + update-browserslist-db@1.2.3(browserslist@4.28.7):
3339 + dependencies:
3340 + browserslist: 4.28.7
3341 + escalade: 3.2.0
3342 + picocolors: 1.1.1
3343 +
3344 + uri-js@4.4.1:
3345 + dependencies:
3346 + punycode: 2.3.1
3347 +
3348 + util-deprecate@1.0.2: {}
3349 +
3350 + vite-node@2.1.9(@types/node@20.19.43):
3351 + dependencies:
3352 + cac: 6.7.14
3353 + debug: 4.4.3
3354 + es-module-lexer: 1.7.0
3355 + pathe: 1.1.2
3356 + vite: 5.4.21(@types/node@20.19.43)
3357 + transitivePeerDependencies:
3358 + - '@types/node'
3359 + - less
3360 + - lightningcss
3361 + - sass
3362 + - sass-embedded
3363 + - stylus
3364 + - sugarss
3365 + - supports-color
3366 + - terser
3367 +
3368 + vite@5.4.21(@types/node@20.19.43):
3369 + dependencies:
3370 + esbuild: 0.21.5
3371 + postcss: 8.5.25
3372 + rollup: 4.62.4
3373 + optionalDependencies:
3374 + '@types/node': 20.19.43
3375 + fsevents: 2.3.3
3376 +
3377 + vitest@2.1.9(@types/node@20.19.43):
3378 + dependencies:
3379 + '@vitest/expect': 2.1.9
3380 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.43))
3381 + '@vitest/pretty-format': 2.1.9
3382 + '@vitest/runner': 2.1.9
3383 + '@vitest/snapshot': 2.1.9
3384 + '@vitest/spy': 2.1.9
3385 + '@vitest/utils': 2.1.9
3386 + chai: 5.3.3
3387 + debug: 4.4.3
3388 + expect-type: 1.4.0
3389 + magic-string: 0.30.21
3390 + pathe: 1.1.2
3391 + std-env: 3.10.0
3392 + tinybench: 2.9.0
3393 + tinyexec: 0.3.2
3394 + tinypool: 1.1.1
3395 + tinyrainbow: 1.2.0
3396 + vite: 5.4.21(@types/node@20.19.43)
3397 + vite-node: 2.1.9(@types/node@20.19.43)
3398 + why-is-node-running: 2.3.0
3399 + optionalDependencies:
3400 + '@types/node': 20.19.43
3401 + transitivePeerDependencies:
3402 + - less
3403 + - lightningcss
3404 + - msw
3405 + - sass
3406 + - sass-embedded
3407 + - stylus
3408 + - sugarss
3409 + - supports-color
3410 + - terser
3411 +
3412 + which@2.0.2:
3413 + dependencies:
3414 + isexe: 2.0.0
3415 +
3416 + why-is-node-running@2.3.0:
3417 + dependencies:
3418 + siginfo: 2.0.0
3419 + stackback: 0.0.2
3420 +
3421 + word-wrap@1.2.5: {}
3422 +
3423 + yocto-queue@0.1.0: {}
3424 +
3425 + zod@3.25.76: {}
added pnpm-workspace.yaml +3 −0
@@ -0,0 +1,3 @@
1 +packages:
2 + - "apps/*"
3 + - "packages/*"
added turbo.json +19 −0
@@ -0,0 +1,19 @@
1 +{
2 + "$schema": "https://turbo.build/schema.json",
3 + "tasks": {
4 + "build": {
5 + "dependsOn": ["^build"],
6 + "outputs": [".next/**", "!.next/cache/**", "dist/**"]
7 + },
8 + "dev": {
9 + "cache": false,
10 + "persistent": true
11 + },
12 + "test": {
13 + "outputs": []
14 + },
15 + "typecheck": {
16 + "outputs": []
17 + }
18 + }
19 +}
20