Spaces:
Sleeping
Sleeping
Alex Amari commited on
Commit ·
389f234
1
Parent(s): ea2e95c
Restyle app to match OHA branding
Browse files- __pycache__/app.cpython-312.pyc +0 -0
- app.py +82 -19
- requirements.txt +1 -0
__pycache__/app.cpython-312.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-312.pyc and b/__pycache__/app.cpython-312.pyc differ
|
|
|
app.py
CHANGED
|
@@ -16,6 +16,7 @@ import gradio as gr
|
|
| 16 |
from openai import OpenAI
|
| 17 |
from geopy.geocoders import Nominatim
|
| 18 |
from geopy.distance import geodesic
|
|
|
|
| 19 |
import time
|
| 20 |
|
| 21 |
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
|
|
@@ -150,6 +151,7 @@ TRANSLATIONS = {
|
|
| 150 |
"continue_button": "Continue",
|
| 151 |
"back_button": "Back",
|
| 152 |
"miles_away": "miles away",
|
|
|
|
| 153 |
"free_care": "Free care up to",
|
| 154 |
"sliding_scale": "Sliding scale up to",
|
| 155 |
"status": "Status",
|
|
@@ -179,6 +181,7 @@ TRANSLATIONS = {
|
|
| 179 |
"continue_button": "Continuar",
|
| 180 |
"back_button": "Atrás",
|
| 181 |
"miles_away": "millas de distancia",
|
|
|
|
| 182 |
"free_care": "Atención gratuita hasta",
|
| 183 |
"sliding_scale": "Escala móvil hasta",
|
| 184 |
"status": "Estado",
|
|
@@ -216,8 +219,8 @@ def _geocode_zip(zip_code):
|
|
| 216 |
_zip_geocode_cache[zip_code] = location
|
| 217 |
return location
|
| 218 |
|
| 219 |
-
def find_nearby_hospitals(zip_code, lang):
|
| 220 |
-
"""Find the
|
| 221 |
|
| 222 |
Validates the ZIP is a CT format (060xx-069xx), geocodes it,
|
| 223 |
then returns markdown cards and choice labels sorted by distance.
|
|
@@ -239,7 +242,7 @@ def find_nearby_hospitals(zip_code, lang):
|
|
| 239 |
distances.append((name, dist, data))
|
| 240 |
|
| 241 |
distances.sort(key=lambda x: x[1])
|
| 242 |
-
nearest = distances[:
|
| 243 |
|
| 244 |
choices = []
|
| 245 |
cards = ""
|
|
@@ -1032,8 +1035,8 @@ def create_interface():
|
|
| 1032 |
gr.HTML(
|
| 1033 |
'<div class="oha-header">'
|
| 1034 |
'<a href="https://portal.ct.gov/oha" target="_blank" rel="noopener">'
|
| 1035 |
-
'<h1>Office of the Healthcare Advocate<
|
| 1036 |
-
'<div class="oha-subtitle">Hospital Financial Assistance Eligibility Screener</div>'
|
| 1037 |
'</a>'
|
| 1038 |
'</div>'
|
| 1039 |
)
|
|
@@ -1049,6 +1052,15 @@ def create_interface():
|
|
| 1049 |
container=False
|
| 1050 |
)
|
| 1051 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1052 |
# Step 1: Choose path
|
| 1053 |
step1 = gr.Group(visible=True, elem_classes="step-group")
|
| 1054 |
with step1:
|
|
@@ -1057,19 +1069,16 @@ def create_interface():
|
|
| 1057 |
help_btn = gr.Button("Find a Hospital by ZIP Code", variant="primary", size="lg")
|
| 1058 |
know_btn = gr.Button("I Know My Hospital", variant="secondary", size="lg")
|
| 1059 |
|
| 1060 |
-
gr.HTML(
|
| 1061 |
-
'<div class="map-embed" style="margin-top:1rem;">'
|
| 1062 |
-
'<iframe src="https://www.google.com/maps/d/u/0/embed?mid=1UVO9r7ZS26kZr8Q62dY0147v3Milu_s&ll=41.508444988744984%2C-72.7719992&z=9" '
|
| 1063 |
-
'width="100%" height="480" style="border:0; border-radius: 6px;" '
|
| 1064 |
-
'allowfullscreen="" loading="lazy" title="Map: Connecticut Hospital Locations"></iframe>'
|
| 1065 |
-
'</div>'
|
| 1066 |
-
)
|
| 1067 |
|
| 1068 |
# Step 2a: Find hospital by ZIP
|
| 1069 |
step2a = gr.Group(visible=False, elem_classes="step-group")
|
| 1070 |
with step2a:
|
| 1071 |
gr.Markdown("### Enter your ZIP code")
|
| 1072 |
zip_input = gr.Textbox(label="ZIP Code", placeholder="e.g., 06511", max_lines=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1073 |
search_btn = gr.Button("Search", variant="primary")
|
| 1074 |
|
| 1075 |
nearby_cards = gr.Markdown()
|
|
@@ -1118,6 +1127,8 @@ def create_interface():
|
|
| 1118 |
chatbot = gr.Chatbot(
|
| 1119 |
label="Conversation",
|
| 1120 |
height=350,
|
|
|
|
|
|
|
| 1121 |
)
|
| 1122 |
|
| 1123 |
# Suggested prompt buttons (pill-shaped via CSS)
|
|
@@ -1170,9 +1181,9 @@ def create_interface():
|
|
| 1170 |
gr.update(visible=False)
|
| 1171 |
]
|
| 1172 |
|
| 1173 |
-
def search_hospitals_wrapper(zip_code, lang):
|
| 1174 |
"""Geocode a ZIP and return nearby hospital cards and radio choices."""
|
| 1175 |
-
cards, choices = find_nearby_hospitals(zip_code, lang)
|
| 1176 |
if choices:
|
| 1177 |
return cards, gr.update(choices=choices, visible=True, value=choices[0]), gr.update(visible=True)
|
| 1178 |
return cards, gr.update(visible=False), gr.update(visible=False)
|
|
@@ -1257,13 +1268,65 @@ def create_interface():
|
|
| 1257 |
]
|
| 1258 |
|
| 1259 |
def download_results(last_result, lang):
|
| 1260 |
-
"""Generate a
|
| 1261 |
if not last_result:
|
| 1262 |
gr.Warning("No results to download." if lang == "en" else "No hay resultados para descargar.")
|
| 1263 |
return gr.update(visible=False)
|
| 1264 |
-
|
| 1265 |
-
|
| 1266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1267 |
tmp.close()
|
| 1268 |
return gr.update(value=tmp.name, visible=True)
|
| 1269 |
|
|
@@ -1330,7 +1393,7 @@ def create_interface():
|
|
| 1330 |
|
| 1331 |
search_btn.click(
|
| 1332 |
fn=search_hospitals_wrapper,
|
| 1333 |
-
inputs=[zip_input, lang_state],
|
| 1334 |
outputs=[nearby_cards, hospital_radio, continue_btn_a]
|
| 1335 |
)
|
| 1336 |
|
|
|
|
| 16 |
from openai import OpenAI
|
| 17 |
from geopy.geocoders import Nominatim
|
| 18 |
from geopy.distance import geodesic
|
| 19 |
+
from fpdf import FPDF
|
| 20 |
import time
|
| 21 |
|
| 22 |
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
|
|
|
|
| 151 |
"continue_button": "Continue",
|
| 152 |
"back_button": "Back",
|
| 153 |
"miles_away": "miles away",
|
| 154 |
+
"num_hospitals": "Number of hospitals to show",
|
| 155 |
"free_care": "Free care up to",
|
| 156 |
"sliding_scale": "Sliding scale up to",
|
| 157 |
"status": "Status",
|
|
|
|
| 181 |
"continue_button": "Continuar",
|
| 182 |
"back_button": "Atrás",
|
| 183 |
"miles_away": "millas de distancia",
|
| 184 |
+
"num_hospitals": "Número de hospitales a mostrar",
|
| 185 |
"free_care": "Atención gratuita hasta",
|
| 186 |
"sliding_scale": "Escala móvil hasta",
|
| 187 |
"status": "Estado",
|
|
|
|
| 219 |
_zip_geocode_cache[zip_code] = location
|
| 220 |
return location
|
| 221 |
|
| 222 |
+
def find_nearby_hospitals(zip_code, lang, max_results=5):
|
| 223 |
+
"""Find the nearest CT hospitals to a given ZIP code.
|
| 224 |
|
| 225 |
Validates the ZIP is a CT format (060xx-069xx), geocodes it,
|
| 226 |
then returns markdown cards and choice labels sorted by distance.
|
|
|
|
| 242 |
distances.append((name, dist, data))
|
| 243 |
|
| 244 |
distances.sort(key=lambda x: x[1])
|
| 245 |
+
nearest = distances[:int(max_results)]
|
| 246 |
|
| 247 |
choices = []
|
| 248 |
cards = ""
|
|
|
|
| 1035 |
gr.HTML(
|
| 1036 |
'<div class="oha-header">'
|
| 1037 |
'<a href="https://portal.ct.gov/oha" target="_blank" rel="noopener">'
|
| 1038 |
+
'<h1>Office of the Healthcare Advocate</h1>'
|
| 1039 |
+
'<div class="oha-subtitle">Hospital Financial Assistance Eligibility Screener <span class="oha-beta">BETA</span></div>'
|
| 1040 |
'</a>'
|
| 1041 |
'</div>'
|
| 1042 |
)
|
|
|
|
| 1052 |
container=False
|
| 1053 |
)
|
| 1054 |
|
| 1055 |
+
# Google Maps embed — placed outside step groups so it persists across all steps
|
| 1056 |
+
gr.HTML(
|
| 1057 |
+
'<div class="map-embed" style="margin-top:1rem;">'
|
| 1058 |
+
'<iframe src="https://www.google.com/maps/d/u/0/embed?mid=1UVO9r7ZS26kZr8Q62dY0147v3Milu_s&ll=41.508444988744984%2C-72.7719992&z=9" '
|
| 1059 |
+
'width="100%" height="480" style="border:0; border-radius: 6px;" '
|
| 1060 |
+
'allowfullscreen="" loading="lazy" title="Map: Connecticut Hospital Locations"></iframe>'
|
| 1061 |
+
'</div>'
|
| 1062 |
+
)
|
| 1063 |
+
|
| 1064 |
# Step 1: Choose path
|
| 1065 |
step1 = gr.Group(visible=True, elem_classes="step-group")
|
| 1066 |
with step1:
|
|
|
|
| 1069 |
help_btn = gr.Button("Find a Hospital by ZIP Code", variant="primary", size="lg")
|
| 1070 |
know_btn = gr.Button("I Know My Hospital", variant="secondary", size="lg")
|
| 1071 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1072 |
|
| 1073 |
# Step 2a: Find hospital by ZIP
|
| 1074 |
step2a = gr.Group(visible=False, elem_classes="step-group")
|
| 1075 |
with step2a:
|
| 1076 |
gr.Markdown("### Enter your ZIP code")
|
| 1077 |
zip_input = gr.Textbox(label="ZIP Code", placeholder="e.g., 06511", max_lines=1)
|
| 1078 |
+
num_hospitals_slider = gr.Slider(
|
| 1079 |
+
minimum=3, maximum=9, value=5, step=1,
|
| 1080 |
+
label="Number of hospitals to show"
|
| 1081 |
+
)
|
| 1082 |
search_btn = gr.Button("Search", variant="primary")
|
| 1083 |
|
| 1084 |
nearby_cards = gr.Markdown()
|
|
|
|
| 1127 |
chatbot = gr.Chatbot(
|
| 1128 |
label="Conversation",
|
| 1129 |
height=350,
|
| 1130 |
+
show_share_button=False,
|
| 1131 |
+
show_copy_button=True,
|
| 1132 |
)
|
| 1133 |
|
| 1134 |
# Suggested prompt buttons (pill-shaped via CSS)
|
|
|
|
| 1181 |
gr.update(visible=False)
|
| 1182 |
]
|
| 1183 |
|
| 1184 |
+
def search_hospitals_wrapper(zip_code, lang, max_results):
|
| 1185 |
"""Geocode a ZIP and return nearby hospital cards and radio choices."""
|
| 1186 |
+
cards, choices = find_nearby_hospitals(zip_code, lang, max_results=max_results)
|
| 1187 |
if choices:
|
| 1188 |
return cards, gr.update(choices=choices, visible=True, value=choices[0]), gr.update(visible=True)
|
| 1189 |
return cards, gr.update(visible=False), gr.update(visible=False)
|
|
|
|
| 1268 |
]
|
| 1269 |
|
| 1270 |
def download_results(last_result, lang):
|
| 1271 |
+
"""Generate a PDF with the eligibility summary for download."""
|
| 1272 |
if not last_result:
|
| 1273 |
gr.Warning("No results to download." if lang == "en" else "No hay resultados para descargar.")
|
| 1274 |
return gr.update(visible=False)
|
| 1275 |
+
t = TRANSLATIONS[lang]
|
| 1276 |
+
data = last_result
|
| 1277 |
+
|
| 1278 |
+
pdf = FPDF()
|
| 1279 |
+
pdf.add_page()
|
| 1280 |
+
pdf.set_auto_page_break(auto=True, margin=20)
|
| 1281 |
+
|
| 1282 |
+
# Header
|
| 1283 |
+
pdf.set_font("Helvetica", "B", 18)
|
| 1284 |
+
pdf.set_text_color(0, 52, 120) # #003478
|
| 1285 |
+
pdf.cell(0, 12, "CT Hospital Financial Assistance", new_x="LMARGIN", new_y="NEXT")
|
| 1286 |
+
pdf.set_font("Helvetica", "", 12)
|
| 1287 |
+
pdf.set_text_color(33, 37, 41)
|
| 1288 |
+
pdf.cell(0, 8, "Eligibility Summary", new_x="LMARGIN", new_y="NEXT")
|
| 1289 |
+
pdf.ln(4)
|
| 1290 |
+
|
| 1291 |
+
# Hospital & Status
|
| 1292 |
+
pdf.set_font("Helvetica", "B", 14)
|
| 1293 |
+
pdf.cell(0, 10, data['hospital'], new_x="LMARGIN", new_y="NEXT")
|
| 1294 |
+
pdf.set_font("Helvetica", "B", 12)
|
| 1295 |
+
pdf.cell(0, 8, f"{t['status']}: {data['eligibility_status']}", new_x="LMARGIN", new_y="NEXT")
|
| 1296 |
+
pdf.ln(2)
|
| 1297 |
+
|
| 1298 |
+
# Details table
|
| 1299 |
+
pdf.set_font("Helvetica", "", 11)
|
| 1300 |
+
fields = [
|
| 1301 |
+
("Annual Household Income" if lang == "en" else "Ingreso anual del hogar", f"${data['income']:,.0f}"),
|
| 1302 |
+
("Household Size" if lang == "en" else "Tamaño del hogar", str(data['household_size'])),
|
| 1303 |
+
("FPL Percentage" if lang == "en" else "Porcentaje FPL", f"{data['fpl_percentage']}%"),
|
| 1304 |
+
("Discount Level" if lang == "en" else "Nivel de descuento", data['discount_level']),
|
| 1305 |
+
("SNAP/WIC Enrolled" if lang == "en" else "Inscrito en SNAP/WIC", "Yes" if data['has_snap_wic'] else "No"),
|
| 1306 |
+
("Has Insurance" if lang == "en" else "Tiene seguro", "Yes" if data.get('has_insurance') else "No"),
|
| 1307 |
+
("PA 24-81 Eligible" if lang == "en" else "Elegible PA 24-81", "Yes" if data['pa_24_81_eligible'] else "No"),
|
| 1308 |
+
]
|
| 1309 |
+
for label, value in fields:
|
| 1310 |
+
pdf.set_font("Helvetica", "B", 10)
|
| 1311 |
+
pdf.cell(80, 7, label)
|
| 1312 |
+
pdf.set_font("Helvetica", "", 10)
|
| 1313 |
+
pdf.cell(0, 7, value, new_x="LMARGIN", new_y="NEXT")
|
| 1314 |
+
pdf.ln(4)
|
| 1315 |
+
|
| 1316 |
+
# Contact
|
| 1317 |
+
pdf.set_font("Helvetica", "B", 11)
|
| 1318 |
+
pdf.cell(0, 8, f"Contact: {data['contact']}", new_x="LMARGIN", new_y="NEXT")
|
| 1319 |
+
if data['asset_limit']:
|
| 1320 |
+
pdf.cell(0, 8, f"Asset Limit: {data['asset_limit']}", new_x="LMARGIN", new_y="NEXT")
|
| 1321 |
+
pdf.ln(4)
|
| 1322 |
+
|
| 1323 |
+
# Disclaimer
|
| 1324 |
+
pdf.set_font("Helvetica", "I", 9)
|
| 1325 |
+
pdf.set_text_color(100, 100, 100)
|
| 1326 |
+
pdf.multi_cell(0, 5, t['disclaimer'])
|
| 1327 |
+
|
| 1328 |
+
tmp = tempfile.NamedTemporaryFile(suffix='.pdf', prefix='eligibility_summary_', delete=False)
|
| 1329 |
+
pdf.output(tmp.name)
|
| 1330 |
tmp.close()
|
| 1331 |
return gr.update(value=tmp.name, visible=True)
|
| 1332 |
|
|
|
|
| 1393 |
|
| 1394 |
search_btn.click(
|
| 1395 |
fn=search_hospitals_wrapper,
|
| 1396 |
+
inputs=[zip_input, lang_state, num_hospitals_slider],
|
| 1397 |
outputs=[nearby_cards, hospital_radio, continue_btn_a]
|
| 1398 |
)
|
| 1399 |
|
requirements.txt
CHANGED
|
@@ -1,3 +1,4 @@
|
|
| 1 |
gradio>=4.0
|
| 2 |
openai>=1.0
|
| 3 |
geopy>=2.4
|
|
|
|
|
|
| 1 |
gradio>=4.0
|
| 2 |
openai>=1.0
|
| 3 |
geopy>=2.4
|
| 4 |
+
fpdf2>=2.7
|