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%
12.8 KB

# CLAUDE.md — AI Risk Index Platform (www.airiskindex.io)

This file provides guidance to Claude Code when working in this repository.


# 1. Project Overview

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.

Core product promise: "The most methodologically rigorous, fully transparent AI job-exposure index."

# The three concepts (never confuse them)

  • Exposure — AI is technically capable of performing the task
  • Substitution — AI actually replaces the human performing it
  • Augmentation — AI assists the human, increasing productivity

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.

# Scoring model (v1)

Composite score 0–100 per occupation, computed from task-level scores weighted by task importance/frequency (O*NET weights). Dimensions:

Dimension Key Weight (v1)
Task automatability automatability 0.35
Current technical feasibility feasibility 0.20
Cost of substitution vs. wage cost_ratio 0.15
Adoption barriers (regulation, liability, human-contact requirement) barriers 0.20
Sector adoption velocity adoption_velocity 0.10
  • Weights live in packages/scoring/src/weights.tsnever hardcode weights anywhere else.
  • Every scoring release gets a semver version (INDEX_VERSION in packages/scoring/src/version.ts) and a changelog entry in docs/methodology/CHANGELOG.md.
  • Scores are stored with confidence intervals (score_low, score, score_high). UI must always be able to display uncertainty.

# Data sources

  • 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).
  • ESCO crosswalk for EU/France occupations (ROME codes) → data/raw/esco/
  • LLM-as-evaluator task ratings (see §6) → data/derived/ratings/
  • Expert panel (Delphi) validation overrides → data/derived/expert_overrides/

Raw data is never edited in place. All transformations go through the ETL pipeline (apps/etl).


# 2. Tech Stack

  • Monorepo: pnpm workspaces + Turborepo
  • Language: TypeScript everywhere (strict mode). Python 3.12 only inside apps/etl for data science steps.
  • Frontend: Next.js 14 (App Router), React 18, Tailwind CSS, shadcn/ui, Recharts for visualizations
  • API: Next.js route handlers for public API (/api/v1/*) + tRPC for internal app calls
  • Database: PostgreSQL 16 (Prisma ORM). Read-heavy → materialized views for score lookups.
  • Cache: Redis (score lookups, rate limiting)
  • Jobs: BullMQ workers in apps/worker (score recomputation, LLM rating batches)
  • Auth: Auth.js (email magic link + OAuth). Public browsing requires no auth.
  • Testing: Vitest (unit), Playwright (e2e), pytest (ETL)
  • Lint/format: ESLint + Prettier, ruff for Python. CI fails on warnings.

# 3. Repository Layout

text
airiskindex/
├── apps/
│   ├── web/          # Next.js app (site + public API routes)
│   ├── worker/       # BullMQ background workers
│   └── etl/          # Python data pipeline (raw → derived → DB)
├── packages/
│   ├── scoring/      # Pure TS scoring engine — NO I/O, fully deterministic
│   ├── db/           # Prisma schema + client + seeds
│   ├── ui/           # Shared React components
│   └── config/       # Shared eslint/ts/tailwind configs
├── data/
│   ├── raw/          # Immutable source dumps (gitignored, fetched by script)
│   └── derived/      # Pipeline outputs (versioned manifests committed)
├── docs/
│   └── methodology/  # Public methodology doc, changelog, sensitivity analyses
├── infra/            # Deployment scripts, ngrok config, systemd units
└── CLAUDE.md

# 4. Commands

bash
pnpm install                  # install all workspaces
pnpm dev                      # run web app on :3000 (+ worker with --filter)
pnpm build                    # turbo build all
pnpm test                     # vitest across packages
pnpm test:e2e                 # playwright (requires pnpm dev running)
pnpm lint && pnpm typecheck   # must pass before any commit
pnpm db:migrate               # prisma migrate dev
pnpm db:seed                  # seed occupations + demo scores
pnpm score:recompute          # full index recomputation (writes new INDEX_VERSION run)
cd apps/etl && make pipeline  # full ETL: raw → derived → DB load

Single test file: pnpm vitest run packages/scoring/src/composite.test.ts

# 5. Coding Conventions

  • 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.
  • Every scoring function has property-based tests (fast-check) + snapshot tests against the published methodology examples in docs/methodology/examples/.
  • 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".
  • API responses are versioned (/api/v1/...) and include index_version in every payload.
  • Money/wages: store as integer cents + ISO currency. Percentages: store as 0–1 floats, format only at the UI layer.
  • Never commit anything under data/raw/. Derived data commits only the manifest JSON (hashes + row counts), not the payloads.
  • Migrations: additive-first. Destructive migrations require a -- DESTRUCTIVE comment and a manual approval in PR review.
  • Accessibility: all charts need a data-table fallback (<VisuallyHidden> table) — this is a public-interest tool.

# 6. LLM-as-Evaluator (task rating pipeline)

Task automatability ratings are produced by an LLM rater in apps/worker/src/raters/, then validated by human experts.

  • 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.
  • Newer models (Sonnet 5 / Opus 5) reject the temperature parameter — rating variance comes from the multi-model panel, not sampling temperature.
  • Prompts live in apps/worker/src/raters/prompts/*.md and are versioned; a prompt change bumps RATER_PROMPT_VERSION and invalidates cached ratings.
  • Every rating stores: model, prompt version, raw response, parsed score, timestamp. Full audit trail, always.
  • Ratings are sampled (5%) for human review; disagreement > 1 point on the 5-point scale flags the task for the expert panel queue.
  • Batch jobs must be idempotent and resumable (BullMQ job IDs = deterministic hash of task_id + prompt version).
  • 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.

# 7. Environments & Deployment

# Environments

  • local — developer machine, SQLite optional shortcut is not allowed; always Postgres via Docker (infra/docker-compose.dev.yml).
  • staging — node m3u96b, exposed via ngrok (see below).
  • productionairiskindex.io (target: VPS/managed later; staging setup is the current deployment).

# Deploying to node m3u96b (staging/current prod)

The app currently runs on the self-hosted node m3u96b and is exposed publicly through an ngrok tunnel mapped to www.airiskindex.io.

Process layout on m3u96b (macOS node — PM2, not systemd; deployed 2026-08-05):

  • App dir: ~/apps/airiskindex (deployed by rsync from the laptop — no git remote yet)
  • airiskindex-web (PM2) → ./run-web.shnext start -p 3000 (cwd apps/web)
  • airiskindex-ngrok (PM2) → ./run-ngrok.shngrok http 3000 --url=https://www.airiskindex.io
  • postgresql@16 + redis via brew services (NOT Docker on this node)
  • Worker (airiskindex-worker) not started yet — starting it submits a live Anthropic rating batch
  • .env at repo root (chmod 600), symlinked into apps/web/.env, packages/db/.env, apps/worker/.env (Next.js and Prisma each load env from their own directory in a monorepo)
  • Registered in ~/Desktop/cluster-skill/cluster-deployments.json

Deploy procedure:

bash
# from the laptop
rsync -az --delete --exclude node_modules --exclude .next --exclude .turbo \
  --exclude .env --exclude "data/raw/*" ~/Desktop/airiskindex/ M3U96b:apps/airiskindex/
ssh M3U96b
cd ~/apps/airiskindex
pnpm install --no-frozen-lockfile
pnpm build
set -a && . ./.env && set +a && pnpm db:migrate:deploy
pm2 restart airiskindex-web && pm2 save
curl -fsS http://127.0.0.1:3000/api/v1/health      # must return {"ok":true,...}
curl -fsS https://www.airiskindex.io/api/v1/health # ALWAYS check the public URL too

ngrok configuration (infra/ngrok.yml, copied to /etc/ngrok/ngrok.yml on the node):

yaml
version: 3
agent:
  authtoken: ${NGROK_AUTHTOKEN}     # from env, never committed
endpoints:
  - name: airiskindex
    url: https://www.airiskindex.io  # requires custom domain configured in ngrok dashboard + CNAME
    upstream:
      url: 3000
  • 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.
  • ngrok runs as ngrok.service (systemd, Restart=always). Logs: journalctl -u ngrok -f.
  • Health rule: after every deploy, hit the public URL, not just localhost — tunnel failures are the most common outage cause.
  • 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.

Environment variables (/srv/airiskindex/.env, template in infra/.env.example):

text
DATABASE_URL=postgresql://...
REDIS_URL=redis://...
ANTHROPIC_API_KEY=...        # rater pipeline
RATER_MODELS=...             # comma-separated model IDs (multi-model rating panel)
NGROK_AUTHTOKEN=...
NEXTAUTH_URL=https://www.airiskindex.io
NEXTAUTH_SECRET=...
PUBLIC_BASE_URL=https://www.airiskindex.io

Never print, log, or commit secrets. infra/.env.example lists keys with empty values only.

# Rollback

bash
ssh m3u96b
cd /srv/airiskindex
git checkout <previous-tag>
pnpm install --frozen-lockfile && pnpm build
sudo systemctl restart airiskindex-web airiskindex-worker

DB rollbacks: only via forward-fix migrations. Nightly pg_dump to /srv/backups (retained 14 days) — verify the cron is alive when touching infra.

# 8. Public API rules

  • /api/v1/occupations — list/search (paginated, cached 1h)
  • /api/v1/occupations/:code — full score breakdown incl. sub-scores, CI bounds, task list, index_version
  • /api/v1/methodology — machine-readable weights + version metadata
  • Rate limit: 60 req/min unauthenticated, 600 with API key. Return 429 with Retry-After.
  • Breaking changes require a new /api/v2 — never mutate v1 response shapes.

# 9. Methodology Integrity Rules (non-negotiable)

  1. Any change to weights, formulas, or rater prompts ⇒ bump INDEX_VERSION, add changelog entry, regenerate docs/methodology/sensitivity/ outputs.
  2. Scores shown anywhere must be traceable to a stored computation run (score_runs table) — no ad-hoc numbers.
  3. Historical scores are immutable; recomputations create new runs, old runs remain queryable.
  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.

# 10. When Working in This Repo, Claude Should

  • Run pnpm typecheck && pnpm lint && pnpm test before declaring any task done.
  • Touch packages/scoring only with accompanying tests and a methodology changelog note.
  • Ask before: destructive migrations, changing index weights, editing ngrok/systemd config, or anything that alters public API shapes.
  • Prefer small, reviewable commits with conventional-commit messages (feat(scoring): ..., fix(api): ..., infra(deploy): ...).
  • Keep UI copy aligned with the "adaptation, not doom" tone guide.
  • When uncertain about Anthropic API specifics (models, batch API, limits), consult the official docs instead of guessing.