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
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
# Agent Guide — CRDC Demo App
|
||||
|
||||
This document captures context, decisions, and guidance for agents working on this codebase. It is the primary source of truth for how to make changes safely.
|
||||
|
||||
## Project Overview
|
||||
|
||||
A React + Vite static web app demonstrating the [CRDC School Arrest Rate API](https://crdc-api.civilytics.org/api/v1/). Visitors select a state, search for a school district, and see 6 charts comparing observed arrest data against Bayesian model estimates. Deployed via Gitea Actions to `pages.civilytics.org/crdc-demo/`.
|
||||
|
||||
## Architecture Summary
|
||||
|
||||
- **Frontend**: React 19 + Vite (static site generation)
|
||||
- **Styling**: Plain CSS custom properties matching Civilytics design tokens (`src/styles/tokens.css`)
|
||||
- **Charts**: Mixed approach:
|
||||
- Charts 1–3 use inline SVG with manual scales (no D3 dependency for these)
|
||||
- Chart 4 (ModelDrawsComparison) uses D3.js v7 for data-driven rendering of quadrant comparisons
|
||||
- Chart 5 (RateDensityRidgeline) uses D3.js v7 for density ridge visualizations
|
||||
- **API**: Calls public read-only API directly from browser; no backend required
|
||||
- **Deployment**: Static site deployed via Gitea Actions (`.gitea/workflows/pages.yml`)
|
||||
|
||||
## Key Components and Data Flow
|
||||
|
||||
```
|
||||
App.jsx (router)
|
||||
→ StateSelector (landing screen: state dropdown/grid)
|
||||
→ DistrictSearch (search + "interesting" suggestions from /estimates?state=&year=)
|
||||
→ LoadingAnimation (fetches all data in parallel, shows animated histogram grid)
|
||||
→ ChartPanel (receives district object, fetches structured estimates for 6 charts)
|
||||
├── ArrestsOverTime — SVG line chart by wave (3 years)
|
||||
├── RateByGroupBar — SVG bar chart: observed vs modeled per group
|
||||
├── DistrictVsNational — SVG comparison to national average
|
||||
├── ModelDrawsComparison — D3 quadrant charts (4 model types × 1 year)
|
||||
├── RateDensityRidgeline — D3 density ridges per race×sex group
|
||||
└── ExceedanceProbability — P(district > national) per student group
|
||||
```
|
||||
|
||||
### Data Fetching Strategy (`ChartPanel.jsx`)
|
||||
|
||||
`ChartPanel` fetches all chart data on mount (after `LoadingAnimation` pre-fetched via batch calls):
|
||||
|
||||
1. **Wave data** for Charts 1–3: Fetches `unified_m3_mod` model estimates for years `['21-22', '17-18', '15-16']`.
|
||||
- Uses three-year models because they return observed arrest counts across all waves (one-year models only have data for the most recent wave).
|
||||
|
||||
2. **Quad data** for Charts 4–6: Fetches estimates from all four quadrant models (`unified_m1_mod`, `unified_m2_mod`, `unified_m3_mod`, `unified_m4_mod`) for year `21-22` only (one year, as the most recent wave).
|
||||
|
||||
3. **National rates**: Loaded once from a static JSON fixture or cached by `LoadingAnimation`.
|
||||
|
||||
### Error Bar Convention: 90% Intervals
|
||||
|
||||
The API returns 95% HPD intervals (`count_lower`, `count_upper`). However, all chart labels and calculations in this app use **90% intervals**. When converting HPD bounds to standard deviations for synthetic draw generation (used in Chart 5's ridgelines), the divisor used is **3.29** (corresponding to z = 1.645 for a two-tailed 90% interval).
|
||||
|
||||
If you change this convention, update:
|
||||
- `RateDensityRidgeline.jsx` — SD calculation (`intervalWidth / 3.29`) and label text
|
||||
- `ModelDrawsComparison.jsx` — Legend labels mentioning "90%"
|
||||
- Any documentation referencing confidence/credible intervals
|
||||
|
||||
## Common Pitfalls & Gotchas
|
||||
|
||||
### 1. Null Safety in Chart Components
|
||||
|
||||
Several API responses may return empty arrays or missing fields for districts with no arrests:
|
||||
```javascript
|
||||
// Always use optional chaining and defaults:
|
||||
const yearRow = (quadModels[q.key] || []).find(r => r.year === year)
|
||||
const predMedian = yearRow?.count_median || 0
|
||||
const enroll = row.stu_enroll || 1 // Prevent division by zero
|
||||
```
|
||||
|
||||
### 2. D3 useEffect Dependency Arrays
|
||||
|
||||
When using `useEffect` for D3 rendering, always include all data dependencies to prevent stale renders:
|
||||
```javascript
|
||||
// Correct — includes all props used inside the effect
|
||||
}, [quadData, selectedModel, rateByGroup])
|
||||
```
|
||||
|
||||
### 3. SVG Dimensions and Responsiveness
|
||||
|
||||
Charts use fixed dimensions with responsive containers (`overflowX: 'auto'` for wide content):
|
||||
- Chart cards have `max-width: 70rem` in `ChartPanel.jsx` (vs the default text width of ~60rem)
|
||||
- SVG elements should set both `width="100%"` and a fixed `viewBox` for proper scaling
|
||||
|
||||
### 4. API Endpoint Availability
|
||||
|
||||
Not all endpoints are available to browser-based clients:
|
||||
- `/api/v1/estimates/{leaid}` — ✅ Returns estimates summary (median, lower, upper bounds)
|
||||
- `/api/v1/draws?...` — ❌ Returns Parquet shard URL only; meant for bulk processing via DuckDB, **not** usable from the browser
|
||||
|
||||
If you need raw posterior draws in the browser app, a new API endpoint would be required. Currently, Chart 5 generates synthetic draws using normal approximation (`d3.randomNormal`) based on the interval bounds.
|
||||
|
||||
### 5. CORS Configuration
|
||||
|
||||
The CRDC API does not send CORS headers. When deployed to git-pages (static hosting), requests are blocked by same-origin policy unless a proxy is configured:
|
||||
- The app auto-detects proxy availability via `VITE_PROXY_URL` environment variable
|
||||
- If unset, the app attempts direct fetch — works when served from Docker/nginx or same-origin
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
Before pushing to production:
|
||||
|
||||
1. **Build succeeds**: `npm run build` (check for new errors)2. **No console errors in browser** after hard refresh3. **All 6 charts render** with sample districts (test "Denver", "Mobile County")
|
||||
4. **Loading animation** appears briefly, then transitions to ChartPanel
|
||||
|
||||
### Commit Message Convention
|
||||
|
||||
Use descriptive commit messages that explain the *why*, not just the what:
|
||||
```bash
|
||||
# Good
|
||||
git commit -m "Fix: use three-year model for wave data (returns observed counts across all years)"
|
||||
git commit -m "Add D3 density ridges to Chart 5 with diamond markers for observed rates"
|
||||
|
||||
# Avoid vague messages
|
||||
git commit -m "Fix charts" # Too generic
|
||||
git commit -m "Update code" # No context
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
There are no automated tests in this project. Manual verification is required:1. **Visual check**: Load a district and verify all 6 charts render correctly2. **Error console**: Check browser DevTools for JavaScript errors3. **Data accuracy**: Compare observed values against API response (check Network tab)
|
||||
4. **Responsiveness**: Resize window to ensure layout adapts
|
||||
|
||||
## Style Guide References
|
||||
|
||||
- R code style: Follows tidyverse principles (`r-style-guide` skill in agent knowledge base)- Chart aesthetic decisions should match patterns from `social_media_posts.md` and `white_paper.qmd`
|
||||
- Colors, typography, spacing are defined as CSS custom properties in `src/styles/tokens.css`
|
||||
|
||||
## Related Repositories
|
||||
|
||||
- **crdc-arrests** — The API server (Plumber/R) at `/home/jared/Nextcloud/Civilytics/Code/Civilytics/crdc-arrests/`
|
||||
- **civilyticsR** — R package with wordmark and visualization functions at `/home/jared/Nextcloud/Civilytics/Code/Civilytics/civilyticsR/`
|
||||
|
||||
## Git Conventions
|
||||
|
||||
- Remote: `https://gitea.civilytics.org/Civilytics/crdc-demo.git`
|
||||
- Default branch: `main` (not `master`)
|
||||
- Gitea Actions workflow auto-deploys on push to `main` via `.gitea/workflows/pages.yml`
|
||||
- Always pull before making changes: `git pull origin main`
|
||||
Reference in New Issue
Block a user