apingali Claude Sonnet 4.6 commited on
Commit
bf0e212
·
1 Parent(s): a903d24

fix(drive-and-save): no negative savings when home metro is suppressed

Browse files

When the home metro has insufficient hospital data (n_hospitals < min_hospitals),
its median price is unreliable. Guard the savings branch with `not home_suppressed`
so a suppressed home falls through to the else branch (savings=0.0) rather than
producing a nonsensical negative savings figure. Also moves `import h3` up to
sit with the other third-party imports at the top of core.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. core.py +3 -3
  2. test_core.py +10 -0
core.py CHANGED
@@ -9,6 +9,7 @@ import math
9
  from dataclasses import dataclass
10
  from pathlib import Path
11
 
 
12
  import yaml
13
 
14
  CONFIG_PATH = Path(__file__).parent / "config.yaml"
@@ -24,8 +25,6 @@ def load_config(path: "Path | str" = CONFIG_PATH) -> dict:
24
  # H3 distance helpers
25
  # ---------------------------------------------------------------------------
26
 
27
- import h3
28
-
29
 
30
  def _haversine_km(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
31
  R = 6371.0
@@ -141,6 +140,7 @@ def rank_metros(medians_df, metros_df, procedure_code: str, home_metro: str, cfg
141
  visible = [r for r in rows if not r.suppressed]
142
  home_row = next((r for r in rows if r.is_home), None)
143
  home_median = home_row.median_price if home_row else None
 
144
 
145
  candidates = [r for r in visible if not r.is_home]
146
  cheapest = candidates[0] if candidates else None # rows already price-asc
@@ -148,7 +148,7 @@ def rank_metros(medians_df, metros_df, procedure_code: str, home_metro: str, cfg
148
  home_row and not home_row.suppressed and visible and visible[0].is_home
149
  )
150
 
151
- if cheapest and home_median is not None and not home_is_cheapest:
152
  savings = home_median - cheapest.median_price
153
  savings_pct = (savings / home_median * 100.0) if home_median else 0.0
154
  drive_to = cheapest.drive_min
 
9
  from dataclasses import dataclass
10
  from pathlib import Path
11
 
12
+ import h3
13
  import yaml
14
 
15
  CONFIG_PATH = Path(__file__).parent / "config.yaml"
 
25
  # H3 distance helpers
26
  # ---------------------------------------------------------------------------
27
 
 
 
28
 
29
  def _haversine_km(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
30
  R = 6371.0
 
140
  visible = [r for r in rows if not r.suppressed]
141
  home_row = next((r for r in rows if r.is_home), None)
142
  home_median = home_row.median_price if home_row else None
143
+ home_suppressed = home_row.suppressed if home_row else True
144
 
145
  candidates = [r for r in visible if not r.is_home]
146
  cheapest = candidates[0] if candidates else None # rows already price-asc
 
148
  home_row and not home_row.suppressed and visible and visible[0].is_home
149
  )
150
 
151
+ if cheapest and home_median is not None and not home_suppressed and not home_is_cheapest:
152
  savings = home_median - cheapest.median_price
153
  savings_pct = (savings / home_median * 100.0) if home_median else 0.0
154
  drive_to = cheapest.drive_min
test_core.py CHANGED
@@ -133,6 +133,16 @@ def test_build_cta_url_homepage_fallback_when_no_pattern():
133
  assert "utm_source=mile-hi-labs" in url
134
 
135
 
 
 
 
 
 
 
 
 
 
 
136
  def test_build_cta_url_uses_deep_link_pattern():
137
  cfg = core.load_config()
138
  cfg["funnel"]["deep_link_pattern"] = "/procedure/{code}/?metro={metro_slug}"
 
133
  assert "utm_source=mile-hi-labs" in url
134
 
135
 
136
+ def test_rank_metros_home_suppressed_no_negative_savings():
137
+ medians, metros = _fixtures()
138
+ # "Tiny Town" has n_hospitals=1 (suppressed) and the lowest price ($50)
139
+ r = core.rank_metros(medians, metros, "45378", "Tiny Town", CFG)
140
+ assert r.savings >= 0 # never negative
141
+ assert r.savings == 0 # home price unreliable -> no claimed savings
142
+ assert r.home_is_cheapest is False
143
+ assert r.cheapest_metro == "Boulder" # cheapest non-suppressed shown informationally
144
+
145
+
146
  def test_build_cta_url_uses_deep_link_pattern():
147
  cfg = core.load_config()
148
  cfg["funnel"]["deep_link_pattern"] = "/procedure/{code}/?metro={metro_slug}"