extended broadband, fema table, updated map
This commit is contained in:
@@ -96,6 +96,7 @@
|
||||
"SHOW_ELECTION_LAYER = True\n",
|
||||
"SHOW_ELECTION_2020_LAYER = True\n",
|
||||
"SHOW_ELECTION_2024_LAYER = False\n",
|
||||
"SHOW_NRI_LAYER = True\n",
|
||||
"\n",
|
||||
"OUTPUT_DIR.mkdir(exist_ok=True)\n",
|
||||
"print('points:', POINTS_CSV)\n",
|
||||
@@ -103,7 +104,7 @@
|
||||
"print('point context:', POINT_CONTEXT_CSV)\n",
|
||||
"print('HUC8 GeoJSON:', HUC8_GEOJSON)\n",
|
||||
"print('state energy context:', STATE_ENERGY_CSV)\n",
|
||||
"print('html output:', MAP_HTML)\n"
|
||||
"print('html output:', MAP_HTML)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -191,6 +192,7 @@
|
||||
"climate_context = pd.DataFrame()\n",
|
||||
"broadband_context = pd.DataFrame()\n",
|
||||
"election_context = pd.DataFrame()\n",
|
||||
"nri_context = pd.DataFrame()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def load_zsh_secrets() -> None:\n",
|
||||
@@ -227,7 +229,7 @@
|
||||
"\n",
|
||||
"def load_optional_db_layers() -> None:\n",
|
||||
" global internet_cables_geojson, opposition_cases, drought_context, smoke_context\n",
|
||||
" global climate_context, broadband_context, election_context, points\n",
|
||||
" global climate_context, broadband_context, election_context, nri_context, points\n",
|
||||
"\n",
|
||||
" if not ENABLE_DB_LAYER_LOAD:\n",
|
||||
" print('DB layer load disabled')\n",
|
||||
@@ -392,8 +394,37 @@
|
||||
" cols = [c for c in election_context.columns if c != 'master_id']\n",
|
||||
" points = points.merge(election_context[['master_id'] + cols], on='master_id', how='left')\n",
|
||||
"\n",
|
||||
" if SHOW_NRI_LAYER:\n",
|
||||
" # FEMA National Risk Index (December 2025). Per-DC values come from the\n",
|
||||
" # census tract that contains the DC point. We pull composite scores plus\n",
|
||||
" # the per-hazard risk score for the 18 NRI hazards.\n",
|
||||
" nri_sql = \"\"\"\n",
|
||||
" select\n",
|
||||
" master_id, nri_status, \"TRACTFIPS\" as nri_tractfips,\n",
|
||||
" \"RISK_SCORE\" as nri_risk_score, \"RISK_RATNG\" as nri_risk_rating,\n",
|
||||
" \"EAL_SCORE\" as nri_eal_score, \"EAL_RATNG\" as nri_eal_rating,\n",
|
||||
" \"EAL_VALT\" as nri_eal_total_usd,\n",
|
||||
" \"SOVI_SCORE\" as nri_sovi_score, \"SOVI_RATNG\" as nri_sovi_rating,\n",
|
||||
" \"RESL_SCORE\" as nri_resl_score, \"RESL_RATNG\" as nri_resl_rating,\n",
|
||||
" \"AVLN_RISKS\" as nri_avln_risk, \"CFLD_RISKS\" as nri_cfld_risk,\n",
|
||||
" \"CWAV_RISKS\" as nri_cwav_risk, \"DRGT_RISKS\" as nri_drgt_risk,\n",
|
||||
" \"ERQK_RISKS\" as nri_erqk_risk, \"HAIL_RISKS\" as nri_hail_risk,\n",
|
||||
" \"HWAV_RISKS\" as nri_hwav_risk, \"HRCN_RISKS\" as nri_hrcn_risk,\n",
|
||||
" \"ISTM_RISKS\" as nri_istm_risk, \"LNDS_RISKS\" as nri_lnds_risk,\n",
|
||||
" \"LTNG_RISKS\" as nri_ltng_risk, \"IFLD_RISKS\" as nri_ifld_risk,\n",
|
||||
" \"SWND_RISKS\" as nri_swnd_risk, \"TRND_RISKS\" as nri_trnd_risk,\n",
|
||||
" \"TSUN_RISKS\" as nri_tsun_risk, \"VLCN_RISKS\" as nri_vlcn_risk,\n",
|
||||
" \"WFIR_RISKS\" as nri_wfir_risk, \"WNTW_RISKS\" as nri_wntw_risk\n",
|
||||
" from public.data_center_nri_exposure\n",
|
||||
" \"\"\"\n",
|
||||
" nri_context = pd.read_sql(nri_sql, conn)\n",
|
||||
" print(f'nri_context rows: {len(nri_context):,}')\n",
|
||||
" if not nri_context.empty:\n",
|
||||
" cols = [c for c in nri_context.columns if c != 'master_id']\n",
|
||||
" points = points.merge(nri_context[['master_id'] + cols], on='master_id', how='left')\n",
|
||||
"\n",
|
||||
"load_optional_db_layers()\n"
|
||||
"\n",
|
||||
"load_optional_db_layers()"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -408,6 +439,10 @@
|
||||
"- `public.opposition_cases_geocoded` (point layer)\n",
|
||||
"- `public.data_center_usdm_drought_exposure` (point popup enrichment)\n",
|
||||
"- `public.data_center_hms_smoke_exposure` (point popup enrichment)\n",
|
||||
"- `public.data_center_historical_climate` (climate stress layer + popup)\n",
|
||||
"- `public.data_center_broadband_connection` (broadband capacity layer + popup)\n",
|
||||
"- `public.data_center_rdh_precinct_vote_matches` (election context layer + popup)\n",
|
||||
"- `public.data_center_nri_exposure` (FEMA NRI multi-hazard risk layer + popup)\n",
|
||||
"\n",
|
||||
"If DB credentials are unavailable, map generation still works with CSV/GeoJSON sources."
|
||||
]
|
||||
@@ -437,6 +472,19 @@
|
||||
"INTERNET_CABLE_COLOR = '#7c3aed'\n",
|
||||
"OPPOSITION_CASE_COLOR = '#b91c1c'\n",
|
||||
"\n",
|
||||
"# NRI hazard prefix -> human-readable label, used in the per-DC popup.\n",
|
||||
"NRI_HAZARDS = [\n",
|
||||
" ('avln', 'Avalanche'), ('cfld', 'Coastal flood'),\n",
|
||||
" ('cwav', 'Cold wave'), ('drgt', 'Drought'),\n",
|
||||
" ('erqk', 'Earthquake'), ('hail', 'Hail'),\n",
|
||||
" ('hwav', 'Heat wave'), ('hrcn', 'Hurricane'),\n",
|
||||
" ('istm', 'Ice storm'), ('lnds', 'Landslide'),\n",
|
||||
" ('ltng', 'Lightning'), ('ifld', 'Inland flood'),\n",
|
||||
" ('swnd', 'Strong wind'), ('trnd', 'Tornado'),\n",
|
||||
" ('tsun', 'Tsunami'), ('vlcn', 'Volcanic activity'),\n",
|
||||
" ('wfir', 'Wildfire'), ('wntw', 'Winter weather'),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"cluster_info = clusters.set_index('cluster_id').to_dict('index')\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -517,6 +565,34 @@
|
||||
" return '#6b7280'\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def nri_color(risk_score):\n",
|
||||
" \"\"\"FEMA NRI composite RISK_SCORE color ramp (0-100, higher = more risk).\"\"\"\n",
|
||||
" if pd.isna(risk_score):\n",
|
||||
" return '#94a3b8'\n",
|
||||
" r = float(risk_score)\n",
|
||||
" if r >= 80:\n",
|
||||
" return '#7f1d1d'\n",
|
||||
" if r >= 60:\n",
|
||||
" return '#dc2626'\n",
|
||||
" if r >= 40:\n",
|
||||
" return '#ea580c'\n",
|
||||
" if r >= 20:\n",
|
||||
" return '#ca8a04'\n",
|
||||
" return '#0284c7'\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def top_nri_hazards(row, n=3):\n",
|
||||
" \"\"\"Return the top-N hazards by risk score for this DC, as 'Label: score' strings.\"\"\"\n",
|
||||
" pairs = []\n",
|
||||
" for prefix, label in NRI_HAZARDS:\n",
|
||||
" attr = f'nri_{prefix}_risk'\n",
|
||||
" val = getattr(row, attr, None)\n",
|
||||
" if val is not None and pd.notna(val):\n",
|
||||
" pairs.append((label, float(val)))\n",
|
||||
" pairs.sort(key=lambda p: p[1], reverse=True)\n",
|
||||
" return [f'{label}: {score:.1f}' for label, score in pairs[:n]]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def point_popup(row):\n",
|
||||
" cluster_label, cluster_size, cluster_rank = cluster_label_and_size(row.cluster_id)\n",
|
||||
" nearest = row.nearest_neighbor_km\n",
|
||||
@@ -643,6 +719,24 @@
|
||||
" {election_2020_lines}\n",
|
||||
" '''\n",
|
||||
"\n",
|
||||
" nri_lines = ''\n",
|
||||
" if hasattr(row, 'nri_status') and pd.notna(row.nri_status):\n",
|
||||
" top3 = top_nri_hazards(row, n=3)\n",
|
||||
" top3_html = ('<br>'.join(top3)) if top3 else 'n/a'\n",
|
||||
" nri_lines = f'''\n",
|
||||
" <hr style=\"margin: 6px 0;\">\n",
|
||||
" <strong>FEMA NRI context (Dec 2025)</strong><br>\n",
|
||||
" Status: {clean_value(row.nri_status)}<br>\n",
|
||||
" Census tract: {clean_value(row.nri_tractfips)}<br>\n",
|
||||
" Composite RISK: {fmt_number(row.nri_risk_score, 1)} ({clean_value(row.nri_risk_rating)})<br>\n",
|
||||
" EAL score: {fmt_number(row.nri_eal_score, 1)} ({clean_value(row.nri_eal_rating)})<br>\n",
|
||||
" EAL total $/yr: {fmt_number(row.nri_eal_total_usd, 0, prefix='$')}<br>\n",
|
||||
" SOVI (social vuln): {fmt_number(row.nri_sovi_score, 1)} ({clean_value(row.nri_sovi_rating)})<br>\n",
|
||||
" RESL (resilience): {fmt_number(row.nri_resl_score, 1)} ({clean_value(row.nri_resl_rating)})<br>\n",
|
||||
" Top hazards (risk score):<br>\n",
|
||||
" {top3_html}<br>\n",
|
||||
" '''\n",
|
||||
"\n",
|
||||
" return folium.Popup(f'''\n",
|
||||
" <div style=\"font-family: system-ui, sans-serif; min-width: 310px; max-width: 420px;\">\n",
|
||||
" <strong>{title}</strong><br>\n",
|
||||
@@ -663,6 +757,7 @@
|
||||
" {climate_lines}\n",
|
||||
" {broadband_lines}\n",
|
||||
" {election_lines}\n",
|
||||
" {nri_lines}\n",
|
||||
" </div>\n",
|
||||
" ''', max_width=460)\n",
|
||||
"\n",
|
||||
@@ -770,7 +865,7 @@
|
||||
" Outcome: {clean_value(row.outcome)}<br>\n",
|
||||
" Source: {clean_value(row.data_source)}\n",
|
||||
" </div>\n",
|
||||
" ''', max_width=400)\n"
|
||||
" ''', max_width=400)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -819,9 +914,16 @@
|
||||
" <div><span style=\"display:inline-block;width:10px;height:10px;background:#6b7280;margin-right:6px;\"></span>-4.9 to 4.9</div>\n",
|
||||
" <div><span style=\"display:inline-block;width:10px;height:10px;background:#dc2626;margin-right:6px;\"></span>5 to 19.9</div>\n",
|
||||
" <div><span style=\"display:inline-block;width:10px;height:10px;background:#7f1d1d;margin-right:6px;\"></span>>= 20</div>\n",
|
||||
"\n",
|
||||
" <div style=\"font-weight: 600; margin-top: 6px;\">FEMA NRI (composite RISK_SCORE)</div>\n",
|
||||
" <div><span style=\"display:inline-block;width:10px;height:10px;background:#0284c7;margin-right:6px;\"></span>< 20 (very low)</div>\n",
|
||||
" <div><span style=\"display:inline-block;width:10px;height:10px;background:#ca8a04;margin-right:6px;\"></span>20-39 (rel. low)</div>\n",
|
||||
" <div><span style=\"display:inline-block;width:10px;height:10px;background:#ea580c;margin-right:6px;\"></span>40-59 (rel. moderate)</div>\n",
|
||||
" <div><span style=\"display:inline-block;width:10px;height:10px;background:#dc2626;margin-right:6px;\"></span>60-79 (rel. high)</div>\n",
|
||||
" <div><span style=\"display:inline-block;width:10px;height:10px;background:#7f1d1d;margin-right:6px;\"></span>>= 80 (very high)</div>\n",
|
||||
" </div>\n",
|
||||
" \"\"\"\n",
|
||||
" map_obj.get_root().html.add_child(folium.Element(legend_html))\n"
|
||||
" map_obj.get_root().html.add_child(folium.Element(legend_html))"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -852,6 +954,7 @@
|
||||
" climate_layer = folium.FeatureGroup(name='Climate stress context', show=False)\n",
|
||||
" broadband_layer = folium.FeatureGroup(name='Broadband capacity context', show=False)\n",
|
||||
" election_2020_layer = folium.FeatureGroup(name='Election context (2020 precinct match)', show=False)\n",
|
||||
" nri_layer = folium.FeatureGroup(name='FEMA NRI multi-hazard risk', show=False)\n",
|
||||
" clustered_layer = folium.FeatureGroup(name='Data centers: clustered', show=True)\n",
|
||||
" noise_layer = folium.FeatureGroup(name='Data centers: noise / isolated', show=True)\n",
|
||||
" centroid_layer = folium.FeatureGroup(name='Cluster centroids and p90 radius', show=True)\n",
|
||||
@@ -972,6 +1075,31 @@
|
||||
" tooltip=tip,\n",
|
||||
" ).add_to(election_2020_layer)\n",
|
||||
"\n",
|
||||
" if SHOW_NRI_LAYER:\n",
|
||||
" nri_rows = points_df.dropna(subset=['nri_risk_score']) if 'nri_risk_score' in points_df.columns else pd.DataFrame()\n",
|
||||
" for row in nri_rows.itertuples(index=False):\n",
|
||||
" color = nri_color(row.nri_risk_score)\n",
|
||||
" # Scale marker by composite RISK_SCORE so higher-risk DCs visually stand out.\n",
|
||||
" score = float(row.nri_risk_score)\n",
|
||||
" radius = max(4, min(14, 3 + score / 8.0))\n",
|
||||
" top_label = ''\n",
|
||||
" top3 = top_nri_hazards(row, n=1)\n",
|
||||
" if top3:\n",
|
||||
" top_label = f\"; top hazard {top3[0]}\"\n",
|
||||
" tip = (\n",
|
||||
" f\"NRI risk {fmt_number(row.nri_risk_score, 1)} \"\n",
|
||||
" f\"({clean_value(row.nri_risk_rating)}){top_label}\"\n",
|
||||
" )\n",
|
||||
" folium.CircleMarker(\n",
|
||||
" location=[row.latitude, row.longitude],\n",
|
||||
" radius=radius,\n",
|
||||
" color=color,\n",
|
||||
" fill=True,\n",
|
||||
" fill_color=color,\n",
|
||||
" fill_opacity=0.4,\n",
|
||||
" weight=1,\n",
|
||||
" tooltip=tip,\n",
|
||||
" ).add_to(nri_layer)\n",
|
||||
"\n",
|
||||
" bounds = []\n",
|
||||
" for row in points_df.itertuples(index=False):\n",
|
||||
@@ -1023,6 +1151,7 @@
|
||||
" climate_layer.add_to(m)\n",
|
||||
" broadband_layer.add_to(m)\n",
|
||||
" election_2020_layer.add_to(m)\n",
|
||||
" nri_layer.add_to(m)\n",
|
||||
" clustered_layer.add_to(m)\n",
|
||||
" noise_layer.add_to(m)\n",
|
||||
" centroid_layer.add_to(m)\n",
|
||||
@@ -1033,7 +1162,7 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"cluster_map = build_cluster_map(points, clusters)\n",
|
||||
"cluster_map\n"
|
||||
"cluster_map"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user