import%20marimo%0A%0A__generated_with%20%3D%20%220.23.9%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Pluvial%20drainage-vulnerability%20index%20(SP1)%0A%0A%20%20%20%20A%20**relative**%20parcel-level%20read%20on%20rain-driven%20(pluvial)%20drainage%20risk%20%E2%80%94%0A%20%20%20%20distinct%20from%20the%20surge%20cat-model.%20**Limits%2C%20stated%20up%20front%3A**%20this%20is%20a%0A%20%20%20%20*relative%20percentile%20screening%20index*%2C%20not%20an%20absolute%20flood%20depth%3B%20it%20uses%20a%0A%20%20%20%2010%20m%20DEM%20and%20no%20rainfall%20input%3B%20and%20there%20is%20**no%20pluvial%20ground%20truth**%2C%20so%0A%20%20%20%20nothing%20here%20is%20%22validated.%22%20The%20index%20is%20moderately%20correlated%20with%20surge%20depth%0A%20%20%20%20(%CF%81%20%E2%89%88%200.68%20%E2%80%94%20both%20respond%20to%20low%20elevation)%2C%20so%20this%20is%20*partial*%20orthogonality%3A%0A%20%20%20%20the%20drainage-service%20component%20is%20the%20distinct%20signal.%20Credibility%20rests%20on%20the%0A%20%20%20%20index%20still%20flagging%20a%20sizeable%20set%20of%20parcels%20FEMA%20zones%20and%20the%20surge%20model%0A%20%20%20%20both%20miss.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20sys%0A%20%20%20%20import%20pathlib%0A%0A%20%20%20%20_root%20%3D%20pathlib.Path(__file__).resolve().parents%5B1%5D%0A%20%20%20%20if%20str(_root)%20not%20in%20sys.path%3A%0A%20%20%20%20%20%20%20%20sys.path.insert(0%2C%20str(_root))%0A%0A%20%20%20%20import%20duckdb%0A%20%20%20%20import%20geopandas%20as%20gpd%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%0A%20%20%20%20from%20analysis.catmodel%20import%20exposure%2C%20hazard%0A%0A%20%20%20%20con%20%3D%20duckdb.connect(str(_root%20%2F%20%22analytics.db%22)%2C%20read_only%3DTrue)%0A%20%20%20%20bg_geojson%20%3D%20_root%20%2F%20%22data%22%20%2F%20%22block_group_boundaries.geojson%22%0A%20%20%20%20return%20bg_geojson%2C%20con%2C%20exposure%2C%20gpd%2C%20hazard%2C%20mo%2C%20np%2C%20pd%2C%20plt%0A%0A%0A%40app.cell%0Adef%20_(con)%3A%0A%20%20%20%20%23%20The%20index%20%2B%20parcel%20attributes%20(FEMA%20zone%2C%20location).%0A%20%20%20%20risk%20%3D%20con.execute(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20SELECT%20r.parcel_id%2C%20r.pluvial_risk_score%2C%20r.low_lying_score%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20r.drainage_service_score%2C%20r.ground_elev_ft%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20p.flood_zone%2C%20p.latitude%2C%20p.longitude%0A%20%20%20%20%20%20%20%20FROM%20parcel_pluvial_risk%20r%0A%20%20%20%20%20%20%20%20JOIN%20parcels%20p%20ON%20r.parcel_id%20%3D%20p.parcel_id%0A%20%20%20%20%20%20%20%20WHERE%20r.pluvial_risk_score%20IS%20NOT%20NULL%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20).df()%0A%20%20%20%20risk.describe()%0A%20%20%20%20return%20(risk%2C)%0A%0A%0A%40app.cell%0Adef%20_(plt%2C%20risk)%3A%0A%20%20%20%20%23%20Index%20map%3A%20parcels%20coloured%20by%20pluvial_risk_score.%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(8%2C%208))%0A%20%20%20%20_sc%20%3D%20_ax.scatter(risk%5B%22longitude%22%5D%2C%20risk%5B%22latitude%22%5D%2C%20c%3Drisk%5B%22pluvial_risk_score%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20s%3D1%2C%20cmap%3D%22viridis%22)%0A%20%20%20%20_ax.set_title(%22Pluvial%20drainage-vulnerability%20index%20(relative%2C%200-100)%22)%0A%20%20%20%20_fig.colorbar(_sc%2C%20ax%3D_ax%2C%20label%3D%22pluvial_risk_score%22)%0A%20%20%20%20_fig%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(bg_geojson%2C%20con%2C%20gpd%2C%20plt)%3A%0A%20%20%20%20%23%20Block-group%20choropleth%3A%20average%20pluvial%20risk%20per%20census%20block%20group%20%E2%80%94%20a%0A%20%20%20%20%23%20readable%20neighbourhood%20view%20of%20the%20same%20signal%20as%20the%20parcel%20map%20above.%0A%20%20%20%20%23%20Boundaries%20from%20scripts%2Ffetch_block_group_boundaries.py%20(Census%20TIGER).%0A%20%20%20%20_bg%20%3D%20gpd.read_file(bg_geojson)%0A%20%20%20%20_roll%20%3D%20con.execute(%0A%20%20%20%20%20%20%20%20%22SELECT%20block_group%2C%20avg_pluvial_risk_score%2C%20scored_parcel_count%20%22%0A%20%20%20%20%20%20%20%20%22FROM%20block_group_pluvial_risk%22%0A%20%20%20%20).df()%0A%20%20%20%20%23%20St-Pete%20extent%3B%20inner%20join%20drops%20BGs%20with%20no%20scored%20parcels.%0A%20%20%20%20_bg%20%3D%20_bg.merge(_roll%2C%20on%3D%22block_group%22%2C%20how%3D%22inner%22).cx%5B-82.77%3A-82.55%2C%2027.66%3A27.86%5D%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(8%2C%208))%0A%20%20%20%20_bg.plot(column%3D%22avg_pluvial_risk_score%22%2C%20cmap%3D%22YlOrRd%22%2C%20legend%3DTrue%2C%20ax%3D_ax%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20edgecolor%3D%22white%22%2C%20linewidth%3D0.3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20legend_kwds%3D%7B%22label%22%3A%20%22avg%20pluvial_risk_score%22%7D)%0A%20%20%20%20_ax.set_title(%22Average%20pluvial%20risk%20by%20census%20block%20group%20(St.%20Pete)%22)%0A%20%20%20%20_fig%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pd%2C%20risk)%3A%0A%20%20%20%20%23%20Orthogonality%20check%201%20%E2%80%94%20vs%20FEMA%3A%20high-risk%20parcels%20by%20flood%20zone.%0A%20%20%20%20risk_%20%3D%20risk.copy()%0A%20%20%20%20risk_%5B%22tier%22%5D%20%3D%20pd.qcut(risk_%5B%22pluvial_risk_score%22%5D%2C%204%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20labels%3D%5B%22Q1%22%2C%20%22Q2%22%2C%20%22Q3%22%2C%20%22Q4%20(highest)%22%5D)%0A%20%20%20%20fema_xtab%20%3D%20(risk_.assign(zone_x%3D(risk_%5B%22flood_zone%22%5D%20%3D%3D%20%22X%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.groupby(%22tier%22%2C%20observed%3DTrue)%5B%22zone_x%22%5D.mean())%0A%20%20%20%20fema_xtab%20%20%23%20fraction%20of%20each%20pluvial%20tier%20that%20is%20FEMA%20Zone%20X%0A%20%20%20%20return%20(risk_%2C)%0A%0A%0A%40app.cell%0Adef%20_(con%2C%20exposure%2C%20hazard)%3A%0A%20%20%20%20%23%20Per-parcel%20surge%20depth%20from%20the%20cat-model%20(deterministic).%20MAX_DIST_KM%3D60%20is%20the%0A%20%20%20%20%23%20notebook-level%20regional-HWM%20choice%20used%20across%20the%20cat-model%20deck.%0A%20%20%20%20exp%20%3D%20exposure.load_exposure(con)%0A%20%20%20%20hwm%20%3D%20hazard.load_hwm(con)%0A%20%20%20%20exp%20%3D%20hazard.assign_flood_depth(exp%2C%20hwm%2C%20max_dist_km%3D60)%0A%20%20%20%20surge%20%3D%20exp%5B%5B%22parcel_id%22%2C%20%22depth_ft%22%5D%5D.copy()%0A%20%20%20%20surge.describe()%0A%20%20%20%20return%20(surge%2C)%0A%0A%0A%40app.cell%0Adef%20_(risk_%2C%20surge)%3A%0A%20%20%20%20%23%20Orthogonality%20check%202%20%E2%80%94%20the%20HIDDEN%20set%3A%20top-quartile%20pluvial%20risk%2C%20Zone%20X%2C%20and%0A%20%20%20%20%23%20DRY%20in%20the%20cat-model%20(depth%20%3C%3D%200).%20Restricted%20to%20cat-model-covered%20parcels%20(SFR%20St-Pete).%0A%20%20%20%20m%20%3D%20risk_.merge(surge%2C%20on%3D%22parcel_id%22%2C%20how%3D%22inner%22)%0A%20%20%20%20print(f%22no-coverage%20(-9999)%20excluded%3A%20%7B(m%5B'depth_ft'%5D%20%3C%3D%20-9999).sum()%3A%2C%7D%22)%0A%20%20%20%20hidden%20%3D%20m%5B(m%5B%22tier%22%5D%20%3D%3D%20%22Q4%20(highest)%22)%20%26%20(m%5B%22flood_zone%22%5D%20%3D%3D%20%22X%22)%20%26%20(m%5B%22depth_ft%22%5D%20%3C%3D%200)%20%26%20(m%5B%22depth_ft%22%5D%20%3E%20-9999)%5D%0A%20%20%20%20print(f%22cat-model-covered%20parcels%3A%20%7Blen(m)%3A%2C%7D%22)%0A%20%20%20%20print(f%22hidden%20set%20(Q4%20pluvial%20%2B%20Zone%20X%20%2B%20cat-model-dry%2C%20coverage-only)%3A%20%7Blen(hidden)%3A%2C%7D%20%22%0A%20%20%20%20%20%20%20%20%20%20f%22(%7B100*len(hidden)%2Flen(m)%3A.1f%7D%25%20of%20covered)%22)%0A%20%20%20%20_rho%20%3D%20m%5B'pluvial_risk_score'%5D.corr(m%5B'depth_ft'%5D)%0A%20%20%20%20print(f%22pluvial%20vs%20surge-depth%20correlation%3A%20%7B_rho%3A.3f%7D%22)%0A%20%20%20%20print(%22%20%20(moderate%20correlation%20%E2%80%94%20not%20independence%20%E2%80%94%20but%20the%20hidden%20set%20shows%20complementary%20coverage%20beyond%20FEMA%20%2B%20surge)%22)%0A%20%20%20%20return%20hidden%2C%20m%0A%0A%0A%40app.cell%0Adef%20_(hidden%2C%20m%2C%20plt)%3A%0A%20%20%20%20%23%20Map%20the%20hidden%20set%20against%20all%20cat-model-covered%20parcels.%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(8%2C%208))%0A%20%20%20%20_ax.scatter(m%5B%22longitude%22%5D%2C%20m%5B%22latitude%22%5D%2C%20s%3D1%2C%20color%3D%22%23dddddd%22%2C%20label%3D%22covered%22)%0A%20%20%20%20_ax.scatter(hidden%5B%22longitude%22%5D%2C%20hidden%5B%22latitude%22%5D%2C%20s%3D4%2C%20color%3D%22%23cb181d%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22hidden%20(Q4%20pluvial%20%2B%20Zone%20X%20%2B%20dry)%22)%0A%20%20%20%20_ax.set_title(%22Pluvial%20risk%20the%20FEMA%20map%20%2B%20surge%20model%20miss%22)%0A%20%20%20%20_ax.legend()%0A%20%20%20%20_fig%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20pd%2C%20risk_)%3A%0A%20%20%20%20%23%20Weight-sensitivity%3A%20is%20the%20hidden%20set%20stable%20under%20reweighting%3F%20Recompute%20the%0A%20%20%20%20%23%20index%20under%20alternative%20(w_low%2C%20w_drain)%20blends%20and%20measure%20top-quartile%20overlap.%0A%20%20%20%20%23%20Re-derive%20inputs%20needed%20by%20pluvial_index%20from%20the%20persisted%20columns.%0A%20%20%20%20inputs%20%3D%20risk_%5B%5B%22parcel_id%22%2C%20%22ground_elev_ft%22%2C%20%22low_lying_score%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22drainage_service_score%22%5D%5D.copy()%0A%0A%20%20%20%20def%20_topq_parcels(score)%3A%0A%20%20%20%20%20%20%20%20thr%20%3D%20np.nanpercentile(score%2C%2075)%0A%20%20%20%20%20%20%20%20return%20set(inputs.loc%5Bscore%20%3E%3D%20thr%2C%20%22parcel_id%22%5D)%0A%0A%20%20%20%20base%20%3D%20_topq_parcels(risk_%5B%22pluvial_risk_score%22%5D.to_numpy())%0A%20%20%20%20rows%20%3D%20%5B%5D%0A%20%20%20%20for%20w_low%20in%20(0.3%2C%200.5%2C%200.7)%3A%0A%20%20%20%20%20%20%20%20%23%20pluvial_risk_score%20%3D%20w_low*low%20%2B%20w_drain*drain%20(sub-scores%20already%200-100)%0A%20%20%20%20%20%20%20%20alt%20%3D%20w_low%20*%20risk_%5B%22low_lying_score%22%5D%20%2B%20(1%20-%20w_low)%20*%20risk_%5B%22drainage_service_score%22%5D%0A%20%20%20%20%20%20%20%20alt_set%20%3D%20_topq_parcels(alt.to_numpy())%0A%20%20%20%20%20%20%20%20jac%20%3D%20len(base%20%26%20alt_set)%20%2F%20len(base%20%7C%20alt_set)%0A%20%20%20%20%20%20%20%20rows.append(%7B%22w_low%22%3A%20w_low%2C%20%22w_drain%22%3A%20round(1%20-%20w_low%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22top_quartile_jaccard_vs_default%22%3A%20round(jac%2C%203)%7D)%0A%20%20%20%20pd.DataFrame(rows)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
77ad53b65f25bc3d0205752a53b30644