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%
1// File: route.ts2// Path: apps/web/app/api/v1/occupations/route.ts3// Project: AI Risk Index — airiskindex.io4// Author: Simon-Pierre Boucher5// Contact: contact@spboucher.ai6// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.7//8// Description: Public API: occupation list/search (paginated).910import { prisma } from "@airiskindex/db";11import { INDEX_VERSION } from "@airiskindex/scoring";12import type { NextRequest } from "next/server";1314export const dynamic = "force-dynamic";1516export async function GET(request: NextRequest): Promise<Response> {17 const { searchParams } = new URL(request.url);18 const q = searchParams.get("q") ?? undefined;19 const page = Math.max(1, Number(searchParams.get("page") ?? "1") || 1);20 const perPage = Math.min(100, Math.max(1, Number(searchParams.get("per_page") ?? "25") || 25));2122 const where = q23 ? {24 OR: [25 { title: { contains: q, mode: "insensitive" as const } },26 { code: { startsWith: q } },27 ],28 }29 : {};3031 try {32 const [total, items] = await Promise.all([33 prisma.occupation.count({ where }),34 prisma.occupation.findMany({35 where,36 orderBy: { code: "asc" },37 skip: (page - 1) * perPage,38 take: perPage,39 select: { code: true, title: true },40 }),41 ]);42 return Response.json(43 { index_version: INDEX_VERSION, page, per_page: perPage, total, items },44 { headers: { "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=600" } },45 );46 } catch {47 return Response.json({ error: "database_unavailable" }, { status: 503 });48 }49}50