Spaces:
Sleeping
Sleeping
File size: 16,193 Bytes
7f53bc2 4c8b4d5 1f2416b fc8894f 346db32 2b5f454 f303655 2b5f454 ae1873a 32308ef ae1873a 2b5f454 09b12dc 6e4de3c ae1873a 1f2416b ddfb219 f91d245 0cffa94 7aa3d69 d0c9a6e 7aa3d69 0cffa94 7aa3d69 dd2c1ff 7aa3d69 3f8e536 7aa3d69 f008966 7aa3d69 b5edb7c 7aa3d69 b5edb7c 7aa3d69 1f70408 7aa3d69 1f70408 7aa3d69 d98e4ef 7aa3d69 b5edb7c 7aa3d69 b5edb7c 607a134 24b9639 607a134 e330d29 607a134 b20cba3 ad4fac2 bfbcf7a 39a2183 bfbcf7a 5fedc81 f008966 39a2183 b5edb7c 3f8e536 f838ca7 b5edb7c 9c22ed4 32308ef b5edb7c cc873f2 9c22ed4 b5edb7c 24b9639 b5edb7c 24b9639 b5edb7c 24b9639 6c6ae46 24b9639 6c6ae46 24b9639 1f63ae9 24b9639 2004733 24b9639 2004733 13e3202 ddfb219 24b9639 62d34d5 2004733 24b9639 2004733 24b9639 2b5f454 e426419 e9694f1 e426419 7145a3d e426419 24b9639 e913fba 24b9639 00ce247 005dfdc 9162194 2d36ab1 24b9639 17b4f07 edbe9e1 24b9639 698c8f7 edbe9e1 e6ce543 4cdc7b6 dcdae15 e89993d 4cdc7b6 dcdae15 b2a70cb 2516614 005be17 c0ae0b4 8f11160 375f962 005be17 4cdc7b6 e19c0bb 9652bfc df10d63 9652bfc df10d63 9652bfc a66d277 df10d63 9652bfc df10d63 9652bfc a120c2a ae1873a 09b12dc ccf9308 dbbca92 ccf9308 dbbca92 5dd67f0 ae1873a 3ff61a8 46ca8b1 1d62100 edbe9e1 39b56f5 67a935e edbe9e1 b5010df edbe9e1 c888109 edbe9e1 b5010df 39b56f5 24b9639 b5010df |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 |
import streamlit as st
import json
import pandas as pd
import requests
import os
import math
from openai import OpenAI
import folium
from streamlit_folium import folium_static
from twilio.rest import Client
from datetime import datetime
from datetime import time
from zoneinfo import ZoneInfo
timezone = ZoneInfo('America/Los_Angeles')
def get_time_score(current_datetime, shelter):
current_day = current_datetime.strftime("%A")
if current_day not in shelter['Days']:
return 1
weekday = current_datetime.weekday()
current_hour = current_datetime.strftime("%H")
current_minute = current_datetime.strftime("%M")
current_time = time(int(current_hour), int(current_minute))
hour_start = shelter['Hour Start'].split(',')
minute_start = shelter['Minute Start'].split(',')
shelter_start = time(int(hour_start[weekday]), int(minute_start[weekday]))
hour_end = shelter['Hour End'].split(',')
minute_end = shelter['Minute End'].split(',')
shelter_end = time(int(hour_end[weekday]), int(minute_end[weekday]))
if shelter_start < shelter_end:
if shelter_start <= current_time <= shelter_end: return 0
else: return 1
else:
if current_time >= shelter_start or current_time <= shelter_end: return 0
else: return 1
def geocode_address(address, api_key):
# URL encode the address
encoded_address = requests.utils.quote(address)
# Send a request to the Google Maps Geocoding API
geocode_url = f"https://maps.googleapis.com/maps/api/geocode/json?address={encoded_address}&key={api_key}"
response = requests.get(geocode_url)
data = response.json()
lat = data['results'][0]['geometry']['location']['lat']
lon = data['results'][0]['geometry']['location']['lng']
return round(lat, 6), round(lon, 6)
# Reference: https://github.com/sfc38/Google-Maps-API-Streamlit-App/blob/master/google_maps_app.py#L126-L135
def create_map():
# Create the map with Google Maps
map_obj = folium.Map(tiles=None)
folium.TileLayer("https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}",
attr="google",
name="Google Maps",
overlay=True,
control=True,
subdomains=["mt0", "mt1", "mt2", "mt3"]).add_to(map_obj)
return map_obj
def call_gpt(user_needs, shelter_services, api_key):
client = OpenAI(api_key = api_key)
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Given two variables 'user needs' (the ideal qualities/services of a shelter) and 'shelter services' (the services offered by a shelter), return an integer 0-10 that scores how well the 'shelter services' match the 'user needs' where 0 is the best fit and 10 is the worst fit. IMPORTANT: NO MATTER WHAT, ONLY RETURN THE INTEGER (NO EXTRA WORDS, PUNCTUATION, ETC.)"},
{"role": "user", "content": f"user_needs: {user_needs}, shelter_services: {shelter_services}"}
]
)
score = completion.choices[0].message.content.strip()
return int(score)
def get_urgency_score(user, shelter):
if user == "Today":
if shelter == "Immidiate": return 0
if shelter == "High": return 0.75
if shelter == "Moderate": return 1
elif user == "In the next few days":
if shelter == "Immidiate": return 0.25
if shelter == "High": return 0
if shelter == "Moderate": return 0.75
elif user == "In a week or more":
if shelter == "Immidiate": return 0.75
if shelter == "High": return 0.25
if shelter == "Moderate": return 0
def get_duration_score(user, shelter):
if user == "Overnight":
if shelter == "Overnight": return 0
if shelter == "Temporary": return 0.5
if shelter == "Transitional": return 0.75
if shelter == "Long-Term": return 1
elif user == "A month or less":
if shelter == "Overnight": return 0.5
if shelter == "Temporary": return 0
if shelter == "Transitional": return 0.25
if shelter == "Long-Term": return 0.75
elif user == "A couple of months":
if shelter == "Overnight": return 0.75
if shelter == "Temporary": return 0.25
if shelter == "Transitional": return 0
if shelter == "Long-Term": return 0.5
elif user == "A year or more":
if shelter == "Overnight": return 1
if shelter == "Temporary": return 0.75
if shelter == "Transitional": return 0.5
if shelter == "Long-Term": return 0
def get_coordinates(zipcode: str, api_key: str) -> list:
"""
Get the coordinates (latitude and longitude) of an address using the OpenWeather Geocoding API.
Parameters:
zipcode (str): The zipcode to geocode.
api_key (str): Your OpenWeather API key.
Returns:
list: A list containing the latitude and longitude of the address.
"""
base_url = "http://api.openweathermap.org/geo/1.0/zip"
params = {
'zip': str(zipcode) + ",US",
'appid': api_key
}
response = requests.get(base_url, params=params)
data = response.json()
print(data)
return [data.get('lat'), data.get('lon')]
def haversine(lat1, lon1, lat2, lon2):
R = 6371 # Earth radius in kilometers. Use 3956 for miles.
dlat = math.radians(lat2 - lat1)
dlon = math.radians(lon2 - lon1)
a = math.sin(dlat / 2) ** 2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon / 2) ** 2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
distance = R * c
return distance
# Initialize session state
if 'form_submitted' not in st.session_state:
st.session_state.form_submitted = False
if 'shelter_index' not in st.session_state:
st.session_state.shelter_index = 0
if 'shelters_filtered' not in st.session_state:
st.session_state.shelters_filtered = False
# Page config
st.set_page_config(
page_title="ShelterSearch",
layout="wide",
)
st.title("ShelterSearch")
if not st.session_state.form_submitted:
st.write("Hello there! Fill out this quick form to receive recommendation for where you can go to receive help.")
st.markdown("Please give us feedback at this [link](https://forms.gle/oLMJ2qVc6HYgwfCw9)")
# should be updated manually annually - use zipcodebase API
zipcodes = {
'San Francisco': ['94101', '94102', '94103', '94104', '94105', '94107', '94108', '94109', '94110', '94111', '94112', '94114', '94115', '94116', '94117', '94118', '94119', '94120', '94121', '94122', '94123', '94124', '94125', '94126', '94127', '94128', '94129', '94130', '94131', '94132', '94133', '94134', '94140', '94141', '94142', '94146', '94147', '94157', '94159', '94164', '94165', '94166', '94167', '94168', '94169', '94170', '94172', '94188'],
'Oakland': ['94601', '94602', '94603', '94604', '94605', '94606', '94607', '94608', '94609', '94610', '94611', '94612', '94613', '94614', '94615', '94617', '94618', '94619', '94620', '94621', '94623', '94624', '94661', '94662'],
'Berkeley': ['94701', '94702', '94703', '94704', '94705', '94706', '94707', '94708', '94709', '94710', '94712']
}
city = st.selectbox("City", ['San Francisco', 'Oakland', 'Berkeley'])
zipcode = st.selectbox("Zipcode", ['Unsure'] + zipcodes[city])
sex = st.radio("Sex", ["Male", "Female", "Other"])
lgbtq = st.radio("Do you identify as LGBTQ+ (some shelters serve this community specifically)", ["No", "Yes"])
domestic_violence = st.radio("Have you experienced domestic violence (some shelters serve these individuals specifically", ["No", "Yes"])
urgency = st.radio("How quickly do you need help?", ("Today", "In the next few days", "In a week or more"))
duration = st.radio("How long do you need a place to stay?", ("Overnight", "A month or less", "A couple of months", "A year or more"))
needs = st.text_area("Optional - Needs (tell us what you need and how we can help)")
phone_number = st.text_input('Optional - Enter your phone number (to text shelter info to you)', '+1')
consent = st.checkbox('I consent to receiving a one-time message')
if st.button("Submit"):
data = {
"City": city,
"Zip Code": zipcode,
"Sex": sex,
"LGBTQ": lgbtq,
"Domestic Violence": domestic_violence,
"Urgency": urgency,
"Duration": duration,
"Needs": needs,
"Phone Number": phone_number,
"Consent": consent
}
with open('data.json', 'w') as f:
json.dump(data, f)
st.session_state.form_submitted = True
st.rerun()
else:
if not st.session_state.shelters_filtered:
with open('data.json', 'r') as f:
data = json.load(f)
shelters = pd.read_csv("database.csv")
# filter city
shelters = shelters[(shelters['City'] == data['City'])]
# filter sex
shelters = shelters[(shelters['Sex'] == data['Sex']) | (shelters['Sex'] == 'All')]
# filter lgbtq
if data['LGBTQ'] == 'No':
shelters = shelters[(shelters['LGBTQ'] == "No")]
# filter domestic violence
if data['Domestic Violence'] == "No":
shelters = shelters[(shelters['Domestic Violence'] == "No")]
# keep track of which scores are calculated
scores = []
# calculate distances between zipcodes
if data['Zip Code'] != "Unsure":
geocoding_api_key = os.environ['GoogleAPI']
shelters_coordinates = shelters.apply(lambda row: geocode_address(f"{row['Address']}, {row['City']}, CA {row['Zip Code']}", geocoding_api_key), axis=1).tolist()
user_coordinates = geocode_address(f"{data['City']}, CA {data['Zip Code']}", geocoding_api_key)
distances = []
for coordinates in shelters_coordinates:
distances.append(haversine(coordinates[0], coordinates[1], user_coordinates[0], user_coordinates[1]))
max_d = max(distances) if (max(distances) != 0) else 1
shelters['zipcode_score'] = [d / max_d for d in distances]
scores.append('zipcode_score')
print('zipcode_score')
# get urgency scores
urgency_scores = shelters.apply(lambda row: get_urgency_score(data['Urgency'], row['Urgency']), axis=1).tolist()
shelters['urgency_score'] = urgency_scores
scores.append('urgency_score')
# get duration scores
duration_scores = shelters.apply(lambda row: get_duration_score(data['Duration'], row['Duration']), axis=1).tolist()
shelters['duration_score'] = duration_scores
scores.append('duration_score')
# get services scores
if data['Needs'] != "":
OpenAI_API_KEY = os.environ["OPENAI_API_KEY"]
services_scores = shelters.apply(lambda row: call_gpt(data['Needs'], row['Services'], OpenAI_API_KEY), axis=1).tolist()
services_scores = [s / 10 for s in services_scores]
shelters['services_score'] = services_scores
scores.append('services_score')
# get time-based scores
time_scores = shelters.apply(lambda row: get_time_score(datetime.now(timezone), row), axis=1).tolist()
if data['Urgency'] == "Today":
for i in range(len(scores)):
shelters[f'time_score_{i}'] = time_scores
scores.append(f'time_score_{i}')
elif data['Urgency'] == "In the next few days":
shelters['time_score'] = time_scores
scores.append('time_score')
elif data['Urgency'] == "In a week or more":
pass
print(scores)
# calcualte cumulative score
shelters['total_score'] = shelters[scores].sum(axis=1)
shelters['total_score'] = shelters['total_score'] / len(scores)
shelters = shelters.sort_values(by='total_score', ascending=True)
shelters = shelters.head(3)
# convert pandas df into list of dicts
shelters = shelters.to_dict(orient='records')
# text messaging
if len(data['Phone Number']) == 12 and data['Consent']:
try:
account_sid = os.environ["SID"]
auth_token = os.environ["auth_token"]
client = Client(account_sid, auth_token)
message_body = "Here's some key shelter information from using ShelterSearch today:\n\n"
for i in range(len(shelters)):
phone = str(shelters[i]['Phone'])
message_body += f"{shelters[i]['Organization Name']}: {shelters[i]['Program Name']}\n"
message_body += f"🕒 Open Hours: {shelters[i]['Open Hours']}\n"
message_body += f"📍 Address: {shelters[i]['Address']}\n"
message_body += f"📞 Phone Number: ({phone[1:4]}) {phone[4:7]}-{phone[7:]}\n\n"
message = client.messages.create(
body = message_body,
from_= "+15107212356",
to = data['Phone Number']
)
except: pass
st.session_state.shelters_filtered = True
st.session_state.shelters = shelters
# Display the current shelter information
shelter = st.session_state.shelters[st.session_state.shelter_index]
st.header(f"{shelter['Organization Name']}: {shelter['Program Name']}")
st.subheader(f"{shelter['Type']}")
st.divider()
st.subheader("Shelter Summary")
st.write(shelter['Summary'])
st.divider()
st.subheader("How to Receive Help")
st.write(shelter['Application Details'])
st.markdown(f"- **🕒\tOpen Hours**: {shelter['Open Hours']}")
st.markdown(f"- **📍\tAddress**: {shelter['Address']}")
phone_number = str(shelter['Phone'])
formatted_phone_number = f"({phone_number[1:4]}) {phone_number[4:7]}-{phone_number[7:]}"
phone_link = f"<a href='tel:{phone_number}'>{formatted_phone_number}</a>"
st.markdown(f"- **📞\tPhone Number**: {phone_link}", unsafe_allow_html=True)
st.divider()
with st.expander("More Information"):
tabs = st.tabs(["Full List of Services", "More About the Program", "More About the Organization", "Webpage Link"])
with tabs[0]:
st.write(shelter['Services'])
with tabs[1]:
st.write(shelter['Program About'])
with tabs[2]:
st.write(shelter['Organization About'])
with tabs[3]:
st.write(shelter['Webpage'])
st.divider()
# Create map for address
map = create_map()
key = os.environ['GoogleAPI']
address = f"{shelter['Address']}, {shelter['City']}, CA"
lat, long = geocode_address(address, key)
# Fit the map bounds to include all markers
south_west = [lat - 0.02, long - 0.02]
north_east = [lat + 0.02, long + 0.02]
map_bounds = [south_west, north_east]
map.fit_bounds(map_bounds)
folium.Marker([lat, long], popup=shelter['Address']).add_to(map)
folium_static(map)
st.markdown(f" ## [Get Directions](https://www.google.com/maps/dir/?api=1&origin=current+location&destination={lat},{long})")
st.divider()
# Create two columns
col1, col2, col3 = st.columns([1,1,1])
# Add buttons to each column
with col1:
if st.button("Previous"):
if st.session_state.shelter_index > 0:
st.session_state.shelter_index -= 1
st.rerun()
with col2:
if st.button("Next"):
if st.session_state.shelter_index < 2:
st.session_state.shelter_index += 1
st.rerun()
with col3:
if st.button("Reset"):
st.session_state.shelter_index = 0
st.session_state.form_submitted = False
st.session_state.shelters_filtered = False
st.rerun() |