jaredandClaude Sonnet 5 cf8e65bb8e
Deploy to git-pages / deploy (push) Successful in 12s
Fix chart bugs found on deployed site, redesign Chart 2 as box-and-whisker
Chart 4 (model predictions vs. observed) was picking the first row for a
given wave/model instead of summing across selected groups, so any district
whose first-returned group had zero counts (e.g. a suppressed race group)
showed a collapsed, flat modeled marker instead of the real aggregate
interval. Also fixes the rightmost wave label ("2021-22") clipping off the
edge of the small-multiple panels by anchoring edge ticks away from the
viewBox boundary instead of centering on it.

Chart 6's y-axis group labels (e.g. "American Indian / Alaska Native
Female") were wider than their margin and clipped past the left edge of the
SVG. Adds a shared shortGroupLabel() to colors.js and uses it here, matching
the convention already used in charts 2 and 5.

Chart 2 previously drew a plain bar to the modeled median next to an
observed diamond that often sat well past the bar's end, reading as if the
bar itself were an uncertainty range when it wasn't. Replaces it with an
actual horizontal box-and-whisker: whisker = the API's reported 90%
interval, box = the fitted approximation's 25th/75th percentiles (via a new
fit.quantile() inverse-CDF, exact round-trip of fitSkewedInterval's own
construction), median tick, observed diamond overlaid.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 18:58:35 -04:00

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.

S
Description
Demonstration web app for the CRDC School Arrest Rate API — explore school-based arrest rates by U.S. district with Bayesian model comparisons.
Readme
16 MiB
Languages
JavaScript 95.5%
CSS 3.2%
PHP 0.7%
Dockerfile 0.4%
HTML 0.2%