# Oregon Socioeconomic Risk — Interactive Policy Screen

A self-contained static single-page HTML visualization that overlays Business Oregon's
economic distress index against a derived demographic-fragility risk index for Oregon
counties. The current version also adds a draft public-goods viability subindex using
county-level property-tax base, property-tax yield, fiscal effort, and population scale.

## Files

- `index.html` — entry point. Open directly in a browser or serve statically.
- `styles.css` — design tokens, layout, chart styling.
- `app.js` — D3 v7 chart, tooltip, details panel, search, filter chips, reset, and
  the embedded dataset (`RAW = [...]`).
- `data.csv` — source CSV copied from
  `oregon_policy_population/outputs/oregon_county_socioeconomic_fragility_with_public_goods_viability.csv`.
- `data.json` — same dataset normalized to JSON (kept alongside as a reference; the
  page itself does not fetch it because the data is embedded directly in `app.js`).
- `assets/oregon-public-goods-viability-concept-paper.pdf` — companion concept paper
  and recommendations document.
- `assets/public-goods-viability-vs-demographic-fragility.png` — static chart used in
  the public-goods viability findings section.
- `qa_desktop_final.png`, `qa_mobile_final.png` — QA screenshots.

## Stack

- D3 v7 from the official CDN — only runtime dependency.
- Fonts: **Fraunces** (display) + **Inter** (body) + **JetBrains Mono** (numerics),
  loaded from Google Fonts with system-font fallbacks.
- No backend, no `localStorage`/`sessionStorage`/cookies, no build step.

## Updating the data

The page is self-contained: the dataset is embedded inside `app.js` as the `RAW`
constant near the top. To refresh from a new CSV:

```bash
cp /path/to/new.csv data.csv
python3 -c "
import csv, json
rows = []
with open('data.csv') as f:
    for row in csv.DictReader(f):
        for k, v in list(row.items()):
            if v == '': row[k] = None
            else:
                try: row[k] = float(v)
                except: pass
        for k in ('biz_is_distressed_2026','biz_misses_demographic_watchlist','biz_distressed_but_low_demographic_risk'):
            if isinstance(row.get(k), str): row[k] = (row[k].lower() == 'true')
        rows.append(row)
print(json.dumps(rows, separators=(',',':')))
" > _new_data.json
# Then paste the contents of _new_data.json after `const RAW = ` in app.js
```

The field-to-display mapping lives in `app.js` in the `data = RAW.map(...)` block.
Add a new field there and a new row in the relevant `renderDetails` `kv-*` section
to surface it in the details panel.

## Field/label changes

- Axis titles, threshold labels, and quadrant labels: search for `axis-title`,
  `threshold-label`, and the `quads` array in `app.js`.
- Details panel section headings: in `index.html` under `aside.details`.
- Filter chip labels: in `index.html` inside `.chips`.
- Source/footer copy: in `index.html` inside `footer.foot`.
- Color tokens (warm neutrals, teal, rust, watchlist brown): the `:root` block at
  the top of `styles.css`.

## Local preview

```bash
cd business_oregon_interactive_chart
python3 -m http.server 8000
# open http://localhost:8000/
```

## Notes

- Multnomah is in the dataset but is not plotted when its PSU-forecast-based
  demographic risk index is unavailable at this county geography. This is called
  out on the page itself.
- The labeling heuristic shows direct labels only for notable counties, including
  high demographic-risk counties, high public-goods viability counties, and the
  currently selected county, with a greedy de-overlap routine.
- The public-goods viability layer is exploratory. It is intended as a supplemental
  policy screen, not an official state classification.
