nniehaus commited on
Commit
bc2dd4c
·
verified ·
1 Parent(s): 750ea5f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -67
app.py CHANGED
@@ -2,49 +2,95 @@ import streamlit as st
2
  import requests
3
  from bs4 import BeautifulSoup
4
  from geopy.geocoders import Nominatim
 
 
5
  import os
 
6
  import folium
7
  from streamlit_folium import folium_static
 
8
 
9
  # Configure environment
10
  DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_KEY")
11
  API_ENDPOINT = "https://api.deepseek.com/v1/chat/completions"
12
 
13
- # Docker-compatible settings
14
- st.set_page_config(layout="wide", page_icon="🏡")
15
- geolocator = Nominatim(user_agent="docker_neighborhood_finder")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
- @st.cache_data(show_spinner=False)
18
  def scrape_location_data(query):
19
- """Scrape location data with Docker-friendly timeout"""
20
- try:
21
- response = requests.get(
22
- f"https://www.niche.com/places-to-live/search/{query}",
23
- timeout=10
24
- )
25
- soup = BeautifulSoup(response.text, 'html.parser')
26
- return [
27
- item.find('h2').text.strip()
28
- for item in soup.find_all('div', class_='search-results__list__item')[:3]
29
- ]
30
- except Exception:
31
- return []
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  def generate_recommendations(preferences):
34
- """Docker-optimized API call"""
 
 
 
 
35
  headers = {
36
  "Authorization": f"Bearer {DEEPSEEK_API_KEY}",
37
  "Content-Type": "application/json"
38
  }
39
 
40
  prompt = f"""
41
- Analyze these preferences: {preferences}
42
- Recommend neighborhoods with:
43
- - Top 3 matches
44
- - 1 hidden gem
45
- - Key amenities
46
- - Safety insights
47
- Format with markdown bullet points
 
 
 
 
48
  """
49
 
50
  try:
@@ -54,54 +100,73 @@ def generate_recommendations(preferences):
54
  "model": "deepseek-chat",
55
  "messages": [{"role": "user", "content": prompt}],
56
  "temperature": 0.7,
57
- "max_tokens": 1000
58
  },
59
  headers=headers,
60
- timeout=20 # Docker-friendly timeout
61
  )
62
- return response.json()["choices"][0]["message"]["content"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  except Exception as e:
64
- st.error(f"API Error: {str(e)}")
65
  return None
66
 
67
  # Streamlit UI
68
- st.title("🏡 Neighborhood Finder (Docker)")
 
69
 
70
- with st.form("preferences"):
71
- col1, col2 = st.columns(2)
72
- with col1:
73
- city = st.text_input("City/Region", "New York, NY")
74
- budget = st.slider("Monthly Budget ($)", 1000, 10000, 3000)
75
- with col2:
76
- commute = st.selectbox("Max Commute", ["15m", "30m", "45m", "1h"])
77
- lifestyle = st.selectbox("Lifestyle", ["Family", "Urban", "Remote"])
78
-
79
- if st.form_submit_button("Find Neighborhoods"):
80
- with st.spinner("Searching..."):
81
- # Get data
82
- locations = scrape_location_data(city)
83
- preferences = {
84
- "city": city,
85
- "budget": budget,
86
- "commute": commute,
87
- "lifestyle": lifestyle
88
- }
89
-
90
- # Generate report
91
- report = generate_recommendations(preferences)
 
 
 
 
 
 
 
 
92
 
93
- # Show results
94
- if report:
95
- st.markdown(report)
96
-
97
- # Docker-compatible map
98
- try:
99
- location = geolocator.geocode(city)
100
- m = folium.Map(
101
- location=[location.latitude, location.longitude],
102
- zoom_start=12,
103
- tiles="CartoDB positron"
104
- )
105
- folium_static(m, width=1000, height=400)
106
- except Exception:
107
- st.warning("Map unavailable - location not found")
 
2
  import requests
3
  from bs4 import BeautifulSoup
4
  from geopy.geocoders import Nominatim
5
+ from urllib.parse import urljoin, urlparse
6
+ import re
7
  import os
8
+ import pandas as pd
9
  import folium
10
  from streamlit_folium import folium_static
11
+ from jsonschema import validate, ValidationError
12
 
13
  # Configure environment
14
  DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_KEY")
15
  API_ENDPOINT = "https://api.deepseek.com/v1/chat/completions"
16
 
17
+ # JSON Schema for API response validation
18
+ RESPONSE_SCHEMA = {
19
+ "type": "object",
20
+ "required": ["choices"],
21
+ "properties": {
22
+ "choices": {
23
+ "type": "array",
24
+ "minItems": 1,
25
+ "items": {
26
+ "type": "object",
27
+ "required": ["message"],
28
+ "properties": {
29
+ "message": {
30
+ "type": "object",
31
+ "required": ["content"],
32
+ "properties": {
33
+ "content": {"type": "string"}
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
 
42
+ @st.cache_data
43
  def scrape_location_data(query):
44
+ """Scrape location data with enhanced error handling"""
45
+ sources = {
46
+ "Niche": f"https://www.niche.com/places-to-live/search/{query}",
47
+ }
48
+
49
+ results = []
50
+ for source, url in sources.items():
51
+ try:
52
+ response = requests.get(url, timeout=15)
53
+ response.raise_for_status()
54
+ soup = BeautifulSoup(response.text, 'html.parser')
55
+
56
+ if source == "Niche":
57
+ listings = soup.find_all('div', class_='search-results__list__item')
58
+ for item in listings[:3]:
59
+ results.append({
60
+ 'name': item.find('h2').text.strip(),
61
+ 'details': item.find('div', class_='search-result-tagline').text.strip(),
62
+ 'score': item.find('div', class_='search-result-grade').text.strip()
63
+ })
64
+
65
+ except Exception as e:
66
+ st.error(f"Scraping error for {source}: {str(e)}")
67
+ continue
68
+
69
+ return results
70
 
71
  def generate_recommendations(preferences):
72
+ """Generate recommendations with robust error handling"""
73
+ if not DEEPSEEK_API_KEY:
74
+ st.error("Missing API key - check environment configuration")
75
+ return None
76
+
77
  headers = {
78
  "Authorization": f"Bearer {DEEPSEEK_API_KEY}",
79
  "Content-Type": "application/json"
80
  }
81
 
82
  prompt = f"""
83
+ Create a neighborhood recommendation report based on these preferences:
84
+ {preferences}
85
+
86
+ Include these sections:
87
+ 1. Top 5 Neighborhood Matches
88
+ 2. Hidden Gem Recommendation
89
+ 3. Key Amenities Analysis
90
+ 4. Commute Times Overview
91
+ 5. Safety & Community Insights
92
+
93
+ Format with markdown headers and bullet points.
94
  """
95
 
96
  try:
 
100
  "model": "deepseek-chat",
101
  "messages": [{"role": "user", "content": prompt}],
102
  "temperature": 0.7,
103
+ "max_tokens": 1500
104
  },
105
  headers=headers,
106
+ timeout=30
107
  )
108
+
109
+ # Validate response status
110
+ if response.status_code != 200:
111
+ error_msg = response.json().get('error', {}).get('message', 'Unknown API error')
112
+ st.error(f"API Error {response.status_code}: {error_msg}")
113
+ return None
114
+
115
+ # Validate response structure
116
+ response_data = response.json()
117
+ validate(instance=response_data, schema=RESPONSE_SCHEMA)
118
+
119
+ return response_data["choices"][0]["message"]["content"]
120
+
121
+ except ValidationError as ve:
122
+ st.error(f"Invalid API response format: {str(ve)}")
123
+ return None
124
  except Exception as e:
125
+ st.error(f"Connection Error: {str(e)}")
126
  return None
127
 
128
  # Streamlit UI
129
+ st.set_page_config(layout="wide", page_icon="🏡")
130
+ st.title("Neighborhood Matchmaker")
131
 
132
+ with st.sidebar:
133
+ st.header("Search Preferences")
134
+ city = st.text_input("City/Region", "New York, NY")
135
+ budget = st.slider("Monthly Housing Budget ($)", 1000, 10000, 3000)
136
+ commute = st.selectbox("Max Commute Time", ["15 mins", "30 mins", "45 mins", "1 hour"])
137
+ amenities = st.multiselect("Must-Have Amenities", [
138
+ "Good Schools", "Parks", "Public Transport",
139
+ "Nightlife", "Shopping", "Healthcare"
140
+ ])
141
+ lifestyle = st.selectbox("Lifestyle Preference", [
142
+ "Family-Friendly", "Urban Professional", "Retirement",
143
+ "Student", "Remote Worker", "Outdoor Enthusiast"
144
+ ])
145
+
146
+ if st.button("Find My Neighborhood"):
147
+ with st.spinner("Analyzing locations..."):
148
+ preferences = {
149
+ "city": city,
150
+ "budget": f"${budget}/mo",
151
+ "max_commute": commute,
152
+ "amenities": amenities,
153
+ "lifestyle": lifestyle
154
+ }
155
+
156
+ location_data = scrape_location_data(city)
157
+ report = generate_recommendations(preferences)
158
+
159
+ if report:
160
+ st.subheader("Your Personalized Neighborhood Report")
161
+ st.markdown(report)
162
 
163
+ try:
164
+ geolocator = Nominatim(user_agent="neighborhood_finder")
165
+ location = geolocator.geocode(city)
166
+ m = folium.Map(location=[location.latitude, location.longitude], zoom_start=12)
167
+ folium_static(m, width=1200, height=500)
168
+ except Exception as e:
169
+ st.warning(f"Map visualization error: {str(e)}")
170
+
171
+ st.markdown("---")
172
+ st.caption("Note: Recommendations generated by AI. Verify with local experts.")