Files
crdc-demo/README.md
T
jared 6c8e1e8409 docs: refresh AGENTS.md, HANDOFF.md, and README with recent work
- Create AGENTS.md: comprehensive agent guide covering architecture,
  data flow, error bar convention (90% intervals), D3 usage patterns,
  null safety pitfalls, deployment checklist, and related repos
- Update HANDOFF.md: mark CORS as resolved, document chart improvements
  (Chart 5 ridgeline rewrite with synthetic draws + smooth rendering)
- Refresh README.md: accurate tech stack (D3 v7), updated file structure
  with new chart files, corrected API endpoint table
2026-08-10 17:10:35 -04:00

7.8 KiB
Raw Blame History

CRDC Arrests API Demo

A demonstration web application for the CRDC School Arrest Rate API, showing Bayesian model comparisons of school-based arrest rates across U.S. districts. Built with React + Vite, deployable as a static site or Docker container.

Quick Start (Development)

# Install dependencies
npm ci

# Start dev server on localhost:5173
npm run dev

# Build for production
npm run build      # outputs to dist/
npm run preview    # serve built files locally

What It Does

Visitors select a U.S. state, search for a school district (with suggestions of districts that have the most arrests), and see 6 charts comparing observed data against Bayesian model estimates:

  1. Arrests over time (Chart 1) — raw counts by CRDC wave with per-1k rate labels, built with inline SVG
  2. Rate by student group (Chart 2) — bar chart, most recent year (observed vs. modeled), SVG
  3. District vs. national (Chart 3) — highest-rate group compared to the U.S. average, SVG
  4. Model predictions vs. observed (Chart 4) — four quadrants (one-year/three-year × baseline/covariate)
  5. Predicted rates by student group (Chart 5) — D3 density ridges showing posterior distributions with diamond markers for observed rates
  6. Exceedance probability (Chart 6) — P(district > national) per student group

Architecture

Tech Stack

  • React 19 + Vite (static site generation, no backend required)
  • Plain CSS custom properties for styling (matches Civilytics design tokens exactly)
  • D3.js v7 for data-driven visualizations (density ridges, scales, axes)
  • Inline SVG rendering — charts are built with vanilla DOM/D3, not charting libraries
  • Calls the public read-only API directly from the browser

File Structure

crdc-demo/
├── index.html                    # Entry point
├── vite.config.mjs               # Vite build config
├── public/                       # Static assets (wordmark, favicon)
│   └── civilytics-wordmark.svg  # Civilytics wordmark from civilyticsR package
├── src/
│   ├── main.jsx                  # React entry
│   ├── App.jsx                   # Main router (state → search → loading → charts)
│   ├── hooks/useApi.js           # API client with retry/backoff + endpoint wrappers
│   ├── components/
│   │   ├── StateSelector.jsx     # Landing screen — state dropdown/grid
│   │   ├── DistrictSearch.jsx    # Search + "interesting" district suggestions
│   │   ├── LoadingAnimation.jsx  # Animated histogram grid during data fetch
│   │   └── ChartPanel.jsx        # Orchestrates all 6 charts + data fetching
│   ├── charts/
│   │   ├── ArrestsOverTime.jsx          # Chart 1 — line chart by wave (SVG)
│   │   ├── RateByGroupBar.jsx            # Chart 2 — bar chart by group (SVG)
│   │   ├── DistrictVsNational.jsx        # Chart 3 — comparison vs. national avg
│   │   ├── ModelDrawsComparison.jsx      # Chart 4 — quadrant model comparison (D3)
│   │   ├── RateDensityRidgeline.jsx      # Chart 5 — density ridges per student group (D3)
│   │   └── ExceedanceProbability.jsx     # Chart 6 — P(district > nat) per group
│   ├── styles/tokens.css          # Civilytics design tokens (colors, fonts, spacing)
│   └── data/national_rates.json    # Static national rates fixture for comparisons
├── Dockerfile                    # Multi-stage build → nginx static server
├── docker-compose.yml            # Local dev / self-hosted deployment
└── .gitea/workflows/pages.yml   # CI/CD — builds and deploys to pages branch

API Endpoints Used

Endpoint Purpose Frequency
/api/v1/models List available Bayesian model specs Once (cached)
/api/v1/districts?q=&state= District name/geo lookup → LEAID On keystroke
/api/v1/estimates/{leaid}?model=X&year=Y Estimates for one district/model/year/group ~40 calls per district
/api/v1/draws?... Locate raw-posterior Parquet shard (bulk only, not used in browser) Not called from app
/data/national_rates.json Static national rates fixture (committed) Once per session

Deployment

The app is a static site deployed automatically via Gitea Actions. On every push to main, the workflow in .gitea/workflows/pages.yml builds the Vite app and copies dist/ to a pages branch served by the git-pages webhook receiver.

URL: https://pages.civilytics.org/crdc-demo/

CORS note: The CRDC API (crdc-api.civilytics.org) does not send CORS headers. When deployed as a static site, browser-based fetch requests will be blocked by the same-origin policy. To fix this, deploy proxy.php to your git-pages host and set the VITE_PROXY_URL environment variable (e.g., /crdc-demo/proxy.php). The app automatically detects proxy availability — when unset, it attempts direct API calls (works via Docker/nginx or same-origin setups).

The workflow follows the same pattern as Civilytics/sln-school-comparison:

  1. Triggers on push to main (only when source files change)
  2. Builds with Node 22 + Vite (npm ci && npm run build)
  3. Copies dist/ contents to an orphan pages branch
  4. Forces push — the git-pages webhook picks it up automatically

Note

: Ensure ${{ secrets.GITHUB_TOKEN }} is configured in repo settings on gitea.civilytics.org.

Option A.5: CORS Proxy for Git Pages

If you can't deploy PHP, create a simple reverse proxy in nginx or use an edge function that:

  1. Accepts ?target=/api/v1/... as a query parameter
  2. Forwards the request to `https://crdc-api.civilytics.org/api/v1/...
  3. Returns the response with Access-Control-Allow-Origin: *
  4. Set VITE_PROXY_URL at build time to point at this endpoint.

Option B: Docker (self-hosted fleet) — includes CORS proxy nginx config:

docker compose up -d    # builds + serves on localhost:8080

# Or build manually for your registry:
docker buildx build --platform linux/amd64 -t registry.civilytics.org/crdc-demo:latest .
docker push registry.civilytics.org/crdc-demo:latest

The container is ~5MB (nginx Alpine) and serves static files with immutable cache headers. A /healthz endpoint supports container orchestration health checks.

Environment Variables

Variable Default Description
VITE_API_BASE https://crdc-api.civilytics.org/api/v1 Override to point at a staging API (set in Dockerfile build or docker-compose)

Visual Design

This app follows the Civilytics visual identity as defined in:

  • theme/_tokens.scss — colors, typography, spacing tokens
  • social_media_posts.md — chart patterns from published social media figures
  • white_paper.qmd / R/paper_figures.R — white paper figure builders

Key design decisions:

  • Color palette: Warm paper background (#FAF7F2), civic navy text (#0E1A2B), ember accent (#C25311)
  • Data viz colors: Teal, plum, moss, brass from the supporting palette
  • Fonts: Libre Franklin/Inter for display and body, JetBrains Mono for code
  • Chart patterns: Pointrange comparisons (model vs. observed), ridgeline-style density bars, faceted small multiples

Citation & Attribution

The CRDC School Arrest Rate API: Knowles, J.E., & Miller, H. (2025). CRDC School Arrest Rate API v1. Civilytics Consulting. https://crdc-api.civilytics.org/api/v1/

Data source: US Department of Education, Office for Civil Rights. Civil Rights Data Collection (2021-22).

This research was supported by a grant from the American Educational Research Association which receives funds for its "AERA Grants Program" from the National Science Foundation under NSF award NSF-DRL #1749275. Opinions reflect those of the author and do not necessarily reflect those AERA or NSF.