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

4.8 KiB

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):

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()
);