// File: page.tsx // Path: apps/web/app/occupations/page.tsx // Project: AI Risk Index — airiskindex.io // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Copyright © 2026 Simon-Pierre Boucher. All rights reserved. // // Description: Browse page: all occupations grouped by SOC major group with score chips. import Link from "next/link"; import { prisma } from "@airiskindex/db"; import { SOC_MAJOR_GROUPS } from "@/lib/soc-groups"; export const dynamic = "force-dynamic"; export const metadata = { title: "All occupations — AI Risk Index", }; export default async function OccupationsPage(): Promise { const run = await prisma.scoreRun.findFirst({ orderBy: { createdAt: "desc" } }); const [occupations, scores] = await Promise.all([ prisma.occupation.findMany({ orderBy: { code: "asc" }, select: { code: true, title: true }, }), run ? prisma.occupationScore.findMany({ where: { runId: run.id }, select: { occupationCode: true, substitution: true }, }) : Promise.resolve([]), ]); const scoreByCode = new Map(scores.map((s) => [s.occupationCode, s.substitution])); const groups = new Map(); for (const occupation of occupations) { const prefix = occupation.code.slice(0, 2); const list = groups.get(prefix) ?? []; list.push(occupation); groups.set(prefix, list); } return (

All occupations

{occupations.length.toLocaleString("en-US")} O*NET occupations across{" "} {groups.size} major groups. Occupations without a chip have not been scored in the latest run yet.

{[...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([prefix, list]) => (

{SOC_MAJOR_GROUPS[prefix] ?? prefix}{" "} ({list.length})

    {list.map((occupation) => { const score = scoreByCode.get(occupation.code); return (
  • {occupation.title} {score !== undefined && ( {score.toFixed(0)} )}
  • ); })}
))}
); }