Files
economic-brain-bhi/docs/scoring.md
2026-04-05 20:15:36 +00:00

126 lines
4.8 KiB
Markdown

# BHI Composite Scoring Function
## Formula
```
composite_score =
(demand_severity * 0.25) +
(supply_shortage * 0.25) +
(pain_signal_volume * 0.20) +
(capacity_trend * 0.10) +
(workforce_shortage * 0.10) +
(regulatory_tailwind * 0.05) +
(govt_demand * 0.05)
```
All components are normalized to 0-100 before weighting. Final `composite_score` is 0-100.
Each component is computed at the **geo x niche x age-bracket** level (state, county, or MSA depending on data).
Thesis this reflects (all-of-the-above): demand is outpacing supply, delivery model is shifting, and regulation is restructuring the market — we weight demand + supply heaviest (50% combined), then real-time pain signals, then the three tailwinds.
---
## Component definitions
### 1. demand_severity (25%)
Feeder: `bhi_demand_indicators` (CDC WONDER, BRFSS, YRBSS, NSCH).
For a given geo + age bracket, combine:
- Suicide rate per 100k (CDC WONDER, ICD-10 X60-X84)
- Drug overdose death rate per 100k (CDC WONDER, X40-X44 + Y10-Y14)
- YRBSS "seriously considered suicide" % (adolescent)
- BRFSS "mental health not good 14+ days" % (young adult via 18-24 bracket)
- NSCH unmet mental health treatment need %
Normalize each to 0-100 against the national distribution (percentile rank), then average.
Trend multiplier: +10 if 5-yr CAGR > 5%.
### 2. supply_shortage (25%)
Feeders: `bhi_shortages` (HRSA HPSA) + `bhi_facilities` (SAMHSA + CMS).
For a geo:
- HPSA mental health score (0-25, already normalized; rescale x4 -> 0-100)
- Inverse of facility density: beds per 100k population (percentile-invert)
- Inverse of adolescent/young-adult-specific bed density (if scoring those brackets)
Weighted average: 50% HPSA score, 30% total bed density, 20% age-targeted bed density.
### 3. pain_signal_volume (20%)
Feeders: base Brain's `reddit_posts`, `app_reviews`, and `risk_factors` tables (already being built).
For a niche (e.g., "adolescent inpatient"):
- Count of posts/reviews/risk-factor hits matching niche keywords in last 90 days
- Z-score against the full base Brain niche distribution
- Clamp to 0-100
Depends on base Brain being live — until then, this component defaults to 50 (neutral).
### 4. capacity_trend (10%)
Feeder: `bhi_facilities` (opened_date, closed_date) + CMS POS termination records.
For the geo x niche:
- Facilities opened in last 24 months minus closed in last 24 months, normalized by baseline facility count
- Negative net = high score (more opportunity), positive net = low score (saturated)
- Formula: `100 * (1 - (net_change + baseline) / (2 * baseline))` clamped 0-100
### 5. workforce_shortage (10%)
Feeder: `bhi_workforce` (BLS OES).
For the MSA:
- Wage growth YoY for SOC codes 29-1223, 21-1014, 21-1018, 103T (percentile rank)
- Employment per 100k (inverse percentile)
- Average them
High wage growth + low employment density = high shortage score = high opportunity for new supply.
### 6. regulatory_tailwind (5%)
Feeder: `bhi_policy_events`.
Count of favorable policy events in the last 18 months for the geo:
- Medicaid rate increases for BH services
- New state mandates for adolescent crisis services
- Expanded provider types (peer support, mobile crisis)
- Federal rules (e.g., Mental Health Parity enforcement)
`count * 20`, clamped to 0-100.
### 7. govt_demand (5%)
Feeder: base Brain's `sam_gov_opportunities` table (if present) + `bhi_policy_events`.
Active + awarded SAM.gov opportunities in NAICS 621112 (Physician offices - mental), 621420 (Outpatient mental health/SUD), 623220 (Residential mental health), 623210 (Residential intellectual/developmental), 624190 (Other individual/family services). Dollar-value-weighted and geo-filtered.
Log-scale: `min(100, 10 * log10(total_dollar_value + 1))`.
---
## Age bracket handling
Every row in `bhi_demand_indicators` carries an `age_bracket`. When scoring a niche tagged for adolescents (13-17), the demand_severity and pain_signal components filter to that bracket. Young-adult scores pull 18-25. "All" niches average both brackets 50/50.
Young-adult gap note: for young-adult scoring, supply_shortage should apply an extra +15 penalty on facility density since very few IPFs have dedicated young-adult units — this is captured via the `young_adult_unit` boolean in `bhi_facilities`.
---
## Output table (to be added)
Scores write to `bhi_scores` (created at runtime, not in bhi_tables.sql v1 — add once inputs are flowing):
```sql
CREATE TABLE bhi_scores (
id SERIAL PRIMARY KEY,
niche TEXT,
geo_type TEXT,
geo_code TEXT,
age_bracket TEXT,
composite NUMERIC,
demand_severity NUMERIC,
supply_shortage NUMERIC,
pain_signal NUMERIC,
capacity_trend NUMERIC,
workforce_short NUMERIC,
reg_tailwind NUMERIC,
govt_demand NUMERIC,
computed_at TIMESTAMPTZ DEFAULT NOW()
);
```