← back to Norma

app/api/census/route.ts

64 lines

import { NextRequest, NextResponse } from 'next/server';
import { requireRole } from '@/lib/require-role';

const CENSUS_BASE = 'https://api.census.gov/data/2022/acs/acs5';

/**
 * GET /api/census?action=zip&zip=90210
 * Fetch American Community Survey data by ZIP code.
 * Returns population, median income, education, debt proxies.
 */
export async function GET(request: NextRequest) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  const apiKey = process.env.CENSUS_API_KEY;
  if (!apiKey) {
    return NextResponse.json({ error: 'CENSUS_API_KEY not configured (free at api.census.gov)' }, { status: 503 });
  }

  const { searchParams } = new URL(request.url);
  const action = searchParams.get('action') || 'zip';

  try {
    if (action === 'zip') {
      const zip = searchParams.get('zip');
      if (!zip || !/^\d{5}$/.test(zip)) {
        return NextResponse.json({ error: 'Valid 5-digit ZIP required' }, { status: 400 });
      }

      // Key ACS variables for advocacy:
      // B01003_001E = total population
      // B19013_001E = median household income
      // B15003_022E = bachelor's degree
      // B15003_023E = master's degree
      // B25077_001E = median home value
      const vars = ['NAME', 'B01003_001E', 'B19013_001E', 'B15003_022E', 'B15003_023E', 'B25077_001E'].join(',');
      const url = `${CENSUS_BASE}?get=${vars}&for=zip%20code%20tabulation%20area:${zip}&key=${apiKey}`;

      const res = await fetch(url);
      if (!res.ok) throw new Error(`Census returned ${res.status}`);
      const data = await res.json();

      if (!Array.isArray(data) || data.length < 2) {
        return NextResponse.json({ error: 'No data returned for ZIP' }, { status: 404 });
      }

      const row = data[1];
      return NextResponse.json({
        zip,
        name: row[0],
        population: parseInt(row[1], 10) || 0,
        median_income: parseInt(row[2], 10) || 0,
        bachelors_degree_count: parseInt(row[3], 10) || 0,
        masters_degree_count: parseInt(row[4], 10) || 0,
        median_home_value: parseInt(row[5], 10) || 0,
      });
    }

    return NextResponse.json({ error: `Unknown action: ${action}` }, { status: 400 });
  } catch (err) {
    return NextResponse.json({ error: (err as Error).message }, { status: 500 });
  }
}