mtoft20 commited on
Commit
d8e9148
Β·
verified Β·
1 Parent(s): 57776dc

Upload 2 files

Browse files
Files changed (2) hide show
  1. app_deploy.py +448 -0
  2. requirements.txt +5 -3
app_deploy.py ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ import pandas as pd
4
+ from together import Together
5
+ import os
6
+
7
+ # =============================================================================
8
+ # CONFIGURATION - Using Secrets Management
9
+ # =============================================================================
10
+ NOCODB_URL = "https://app.nocodb.com" # Base URL
11
+
12
+ # Get sensitive data from Streamlit secrets or environment variables
13
+ def get_api_credentials():
14
+ """Get API credentials from secrets or environment"""
15
+ try:
16
+ # Try Streamlit secrets first (for Hugging Face Spaces)
17
+ api_token = st.secrets.get("NOCODB_API_TOKEN", os.environ.get("NOCODB_API_TOKEN", ""))
18
+ together_key = st.secrets.get("TOGETHER_API_KEY", os.environ.get("TOGETHER_API_KEY", ""))
19
+ endpoint_path = st.secrets.get("NOCODB_ENDPOINT_PATH", os.environ.get("NOCODB_ENDPOINT_PATH", ""))
20
+
21
+ return api_token, together_key, endpoint_path
22
+ except:
23
+ # Fallback to environment variables
24
+ api_token = os.environ.get("NOCODB_API_TOKEN", "")
25
+ together_key = os.environ.get("TOGETHER_API_KEY", "")
26
+ endpoint_path = os.environ.get("NOCODB_ENDPOINT_PATH", "")
27
+
28
+ return api_token, together_key, endpoint_path
29
+
30
+ # Initialize Together AI client
31
+ @st.cache_resource
32
+ def get_ai_client():
33
+ """Initialize Together AI client"""
34
+ _, together_key, _ = get_api_credentials()
35
+ if not together_key:
36
+ st.error("Together AI API key not found. Please configure it in the secrets.")
37
+ return None
38
+ return Together(api_key=together_key)
39
+
40
+ # =============================================================================
41
+ # HELPER FUNCTIONS
42
+ # =============================================================================
43
+ def safe_int(value, default=0):
44
+ """Safely convert value to integer"""
45
+ try:
46
+ return int(float(value)) if value else default
47
+ except (ValueError, TypeError):
48
+ return default
49
+
50
+ def safe_float(value, default=0.0):
51
+ """Safely convert value to float"""
52
+ try:
53
+ return float(value) if value else default
54
+ except (ValueError, TypeError):
55
+ return default
56
+
57
+ @st.cache_data(ttl=300) # Cache for 5 minutes
58
+ def get_properties():
59
+ """Fetch properties from NocoDB"""
60
+ api_token, _, endpoint_path = get_api_credentials()
61
+
62
+ if not api_token or not endpoint_path:
63
+ st.error("NocoDB credentials not configured. Please set up your secrets.")
64
+ return []
65
+
66
+ headers = {"xc-token": api_token}
67
+
68
+ try:
69
+ response = requests.get(
70
+ f"{NOCODB_URL}{endpoint_path}?limit=1000", # Get more records
71
+ headers=headers
72
+ )
73
+
74
+ if response.status_code == 200:
75
+ data = response.json()
76
+ return data.get('list', [])
77
+ else:
78
+ st.error(f"Failed to fetch data: {response.status_code}")
79
+ return []
80
+
81
+ except Exception as e:
82
+ st.error(f"Error connecting to database: {e}")
83
+ return []
84
+
85
+ def filter_properties(properties, filters):
86
+ """Apply filters to properties list"""
87
+ filtered = []
88
+
89
+ for prop in properties:
90
+ # Price filter
91
+ price = safe_int(prop.get('cash_price'))
92
+ if price > filters['max_price']:
93
+ continue
94
+
95
+ # Rooms filter
96
+ rooms = safe_int(prop.get('rooms'))
97
+ if rooms < filters['min_rooms']:
98
+ continue
99
+
100
+ # Energy rating filter
101
+ if filters['energy_ratings'] and prop.get('energy_rating') not in filters['energy_ratings']:
102
+ continue
103
+
104
+ # City filter
105
+ if filters['cities'] and prop.get('city') not in filters['cities']:
106
+ continue
107
+
108
+ filtered.append(prop)
109
+
110
+ return filtered
111
+
112
+ def create_property_context(properties):
113
+ """Create context string about current properties for AI"""
114
+ if not properties:
115
+ return "No properties match the current filters."
116
+
117
+ total = len(properties)
118
+ prices = [safe_int(p.get('cash_price')) for p in properties if safe_int(p.get('cash_price')) > 0]
119
+
120
+ if prices:
121
+ avg_price = sum(prices) / len(prices)
122
+ min_price = min(prices)
123
+ max_price = max(prices)
124
+
125
+ context = f"""Currently showing {total} Danish villas.
126
+ Price range: {min_price:,} - {max_price:,} DKK.
127
+ Average price: {avg_price:,.0f} DKK. """
128
+ else:
129
+ context = f"Currently showing {total} Danish villas. "
130
+
131
+ # Add some location info
132
+ cities = list(set([p.get('city', 'Unknown') for p in properties[:10]]))
133
+ if cities:
134
+ context += f"Cities include: {', '.join(cities[:5])}. "
135
+
136
+ return context
137
+
138
+ def get_ai_response(client, question, context, model_name):
139
+ """Get response from Together AI"""
140
+ try:
141
+ # Create a comprehensive prompt
142
+ prompt = f"""You are a helpful Danish real estate assistant. Based on the current property data, please answer the user's question accurately and helpfully.
143
+
144
+ Current Property Data Context:
145
+ {context}
146
+
147
+ User Question: {question}
148
+
149
+ Please provide a helpful, accurate response based on the data provided. Keep your answer concise but informative."""
150
+
151
+ response = client.chat.completions.create(
152
+ model=model_name,
153
+ messages=[
154
+ {"role": "system", "content": "You are a helpful Danish real estate assistant with expertise in property analysis and market insights."},
155
+ {"role": "user", "content": prompt}
156
+ ],
157
+ max_tokens=300,
158
+ temperature=0.7,
159
+ )
160
+
161
+ return response.choices[0].message.content
162
+
163
+ except Exception as e:
164
+ raise Exception(f"Together AI Error: {str(e)}")
165
+
166
+ def test_together_models():
167
+ """Test different Together AI models"""
168
+ # Include both Gemma and other reliable serverless models
169
+ models_to_test = [
170
+ # Gemma models (Google's lightweight models)
171
+ "google/gemma-2b-it",
172
+ "google/gemma-2-27b-it",
173
+ # Other reliable models
174
+ "mistralai/Mistral-7B-Instruct-v0.1",
175
+ "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO",
176
+ "mistralai/Mixtral-8x7B-Instruct-v0.1"
177
+ ]
178
+
179
+ results = {}
180
+ client = get_ai_client()
181
+
182
+ if not client:
183
+ return {"error": "Could not initialize AI client"}
184
+
185
+ for model_name in models_to_test:
186
+ try:
187
+ test_response = client.chat.completions.create(
188
+ model=model_name,
189
+ messages=[
190
+ {"role": "system", "content": "You are a helpful assistant."},
191
+ {"role": "user", "content": "Hello, can you help me analyze real estate data?"}
192
+ ],
193
+ max_tokens=50,
194
+ temperature=0.7,
195
+ )
196
+
197
+ results[model_name] = {
198
+ "status": "βœ… Success",
199
+ "response": test_response.choices[0].message.content[:100]
200
+ }
201
+
202
+ except Exception as e:
203
+ results[model_name] = {"status": "❌ Error", "response": str(e)[:100]}
204
+
205
+ return results
206
+
207
+ # =============================================================================
208
+ # MAIN APP
209
+ # =============================================================================
210
+ def main():
211
+ # Page config
212
+ st.set_page_config(
213
+ page_title="Danish Villa Assistant",
214
+ page_icon="🏑",
215
+ layout="wide"
216
+ )
217
+
218
+ # Header
219
+ st.title("🏑 Danish Villa Assistant")
220
+ st.write("Explore Danish villas with AI-powered insights using Together AI!")
221
+
222
+ # Check API credentials
223
+ api_token, together_key, endpoint_path = get_api_credentials()
224
+
225
+ if not together_key:
226
+ st.error("⚠️ Together AI API key not configured!")
227
+ st.info("Please set your TOGETHER_API_KEY in the Hugging Face Spaces secrets.")
228
+ st.stop()
229
+
230
+ if not api_token or not endpoint_path:
231
+ st.error("⚠️ NocoDB credentials not configured!")
232
+ st.info("Please set NOCODB_API_TOKEN and NOCODB_ENDPOINT_PATH in the Hugging Face Spaces secrets.")
233
+ st.stop()
234
+
235
+ # Add model testing section
236
+ with st.expander("πŸ§ͺ Test Together AI Models (for debugging)"):
237
+ if st.button("Test Different Models"):
238
+ with st.spinner("Testing models..."):
239
+ test_results = test_together_models()
240
+ for model, result in test_results.items():
241
+ st.write(f"**{model}:** {result['status']}")
242
+ if result['status'] == "βœ… Success":
243
+ st.success(f"Response preview: {result['response']}")
244
+ else:
245
+ st.error(f"Error: {result['response']}")
246
+
247
+ # Initialize AI client
248
+ try:
249
+ client = get_ai_client()
250
+ if not client:
251
+ st.stop()
252
+ except Exception as e:
253
+ st.error(f"Failed to initialize Together AI client: {e}")
254
+ st.stop()
255
+
256
+ # Sidebar filters
257
+ st.sidebar.header("πŸ” Filter Properties")
258
+
259
+ # Get all properties first to populate filter options
260
+ with st.spinner("Loading properties..."):
261
+ all_properties = get_properties()
262
+
263
+ if not all_properties:
264
+ st.error("Could not load properties. Please check your NocoDB connection.")
265
+ st.stop()
266
+
267
+ # Extract unique values for filters
268
+ all_cities = sorted(list(set([p.get('city', 'Unknown') for p in all_properties if p.get('city')])))
269
+ all_energy_ratings = sorted(list(set([p.get('energy_rating') for p in all_properties if p.get('energy_rating')])))
270
+
271
+ # Sidebar filter controls
272
+ max_price = st.sidebar.slider(
273
+ "Maximum Price (DKK)",
274
+ min_value=0,
275
+ max_value=20000000,
276
+ value=10000000,
277
+ step=500000,
278
+ format="%d"
279
+ )
280
+
281
+ min_rooms = st.sidebar.slider(
282
+ "Minimum Rooms",
283
+ min_value=1,
284
+ max_value=15,
285
+ value=3
286
+ )
287
+
288
+ selected_cities = st.sidebar.multiselect(
289
+ "Cities",
290
+ options=all_cities,
291
+ default=[]
292
+ )
293
+
294
+ selected_energy_ratings = st.sidebar.multiselect(
295
+ "Energy Ratings",
296
+ options=all_energy_ratings,
297
+ default=[]
298
+ )
299
+
300
+ # Create filter dictionary
301
+ filters = {
302
+ 'max_price': max_price,
303
+ 'min_rooms': min_rooms,
304
+ 'cities': selected_cities,
305
+ 'energy_ratings': selected_energy_ratings
306
+ }
307
+
308
+ # Apply filters
309
+ filtered_properties = filter_properties(all_properties, filters)
310
+
311
+ # Main content area
312
+ col1, col2 = st.columns([2, 1])
313
+
314
+ with col1:
315
+ # Property listings
316
+ st.subheader(f"πŸ“‹ Found {len(filtered_properties)} Properties")
317
+
318
+ if filtered_properties:
319
+ # Show first 10 properties
320
+ for i, prop in enumerate(filtered_properties[:10]):
321
+ with st.expander(
322
+ f"{prop.get('address', 'N/A')} - {safe_int(prop.get('cash_price')):,} DKK"
323
+ ):
324
+ # Property details in columns
325
+ detail_col1, detail_col2, detail_col3 = st.columns(3)
326
+
327
+ with detail_col1:
328
+ st.write(f"**πŸ™οΈ City:** {prop.get('city', 'N/A')}")
329
+ st.write(f"**πŸšͺ Rooms:** {prop.get('rooms', 'N/A')}")
330
+ st.write(f"**πŸ“ Living Area:** {prop.get('living_area', 'N/A')} mΒ²")
331
+
332
+ with detail_col2:
333
+ st.write(f"**⚑ Energy Rating:** {prop.get('energy_rating', 'N/A')}")
334
+ st.write(f"**πŸ“… Year Built:** {prop.get('year_built', 'N/A')}")
335
+ st.write(f"**πŸ›οΈ Municipality:** {prop.get('municipal', 'N/A')}")
336
+
337
+ with detail_col3:
338
+ price_per_sqm = safe_int(prop.get('square_meter_price'))
339
+ st.write(f"**πŸ’° Price/mΒ²:** {price_per_sqm:,} DKK" if price_per_sqm else "**πŸ’° Price/mΒ²:** N/A")
340
+
341
+ plot_area = safe_int(prop.get('area'))
342
+ st.write(f"**🌿 Plot Area:** {plot_area:,} m²" if plot_area else "**🌿 Plot Area:** N/A")
343
+
344
+ st.write(f"**🏠 Type:** {prop.get('legal_type', 'N/A')}")
345
+
346
+ if len(filtered_properties) > 10:
347
+ st.info(f"Showing first 10 of {len(filtered_properties)} properties. Adjust filters to narrow results.")
348
+ else:
349
+ st.info("No properties match your current filters. Try adjusting the criteria.")
350
+
351
+ with col2:
352
+ # AI Chat Section
353
+ st.subheader("πŸ€– Ask AI Assistant")
354
+ st.write("Ask questions about the Danish villa market!")
355
+
356
+ # Model selection for Together AI
357
+ model_choice = st.selectbox(
358
+ "Select AI Model:",
359
+ [
360
+ # Gemma models (Google's efficient models)
361
+ "google/gemma-2b-it",
362
+ "google/gemma-2-27b-it",
363
+ # Other reliable models
364
+ "mistralai/Mistral-7B-Instruct-v0.1",
365
+ "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO",
366
+ "mistralai/Mixtral-8x7B-Instruct-v0.1"
367
+ ],
368
+ help="Gemma models are Google's efficient, lightweight models."
369
+ )
370
+
371
+ # Example questions
372
+ with st.expander("πŸ’‘ Example Questions"):
373
+ st.write("β€’ What's the average price range?")
374
+ st.write("β€’ Tell me about energy ratings in the data")
375
+ st.write("β€’ Which areas have the most expensive properties?")
376
+ st.write("β€’ How many properties are available in each city?")
377
+ st.write("β€’ What's the price per square meter trend?")
378
+
379
+ user_question = st.text_area(
380
+ "Your Question:",
381
+ placeholder="Ask about prices, locations, energy ratings, market trends...",
382
+ height=100
383
+ )
384
+
385
+ if st.button("πŸ” Ask AI", type="primary"):
386
+ if user_question:
387
+ with st.spinner("AI is analyzing the data..."):
388
+ # Create context from current filtered data
389
+ context = create_property_context(filtered_properties)
390
+
391
+ try:
392
+ # Get AI response
393
+ ai_response = get_ai_response(client, user_question, context, model_choice)
394
+
395
+ st.success("**AI Assistant Response:**")
396
+ st.write(ai_response)
397
+
398
+ # Show debug info
399
+ with st.expander("Debug Info"):
400
+ st.write(f"Model used: {model_choice}")
401
+ st.write(f"Properties analyzed: {len(filtered_properties)}")
402
+ st.write(f"Context: {context[:150]}...")
403
+
404
+ except Exception as e:
405
+ st.error(f"AI Error: {str(e)}")
406
+
407
+ # Fallback response with data analysis
408
+ st.info("**Fallback Analysis:**")
409
+ if filtered_properties:
410
+ avg_price = sum(safe_int(p.get('cash_price')) for p in filtered_properties) / len(filtered_properties)
411
+ st.write(f"β€’ Found {len(filtered_properties)} properties")
412
+ st.write(f"β€’ Average price: {avg_price:,.0f} DKK")
413
+
414
+ cities = list(set(p.get('city') for p in filtered_properties if p.get('city')))
415
+ if cities:
416
+ st.write(f"β€’ Cities: {', '.join(cities[:3])}")
417
+
418
+ energy_ratings = list(set(p.get('energy_rating') for p in filtered_properties if p.get('energy_rating')))
419
+ if energy_ratings:
420
+ st.write(f"β€’ Energy ratings: {', '.join(energy_ratings[:3])}")
421
+ else:
422
+ st.warning("Please enter a question first!")
423
+
424
+ # Footer stats
425
+ st.markdown("---")
426
+ if all_properties:
427
+ total_props = len(all_properties)
428
+ filtered_props = len(filtered_properties)
429
+
430
+ stat_col1, stat_col2, stat_col3, stat_col4 = st.columns(4)
431
+
432
+ with stat_col1:
433
+ st.metric("Total Properties", total_props)
434
+
435
+ with stat_col2:
436
+ st.metric("Filtered Results", filtered_props)
437
+
438
+ with stat_col3:
439
+ if filtered_properties:
440
+ avg_price = sum(safe_int(p.get('cash_price')) for p in filtered_properties) / len(filtered_properties)
441
+ st.metric("Avg Price", f"{avg_price:,.0f} DKK")
442
+
443
+ with stat_col4:
444
+ unique_cities = len(set(p.get('city') for p in filtered_properties if p.get('city')))
445
+ st.metric("Cities", unique_cities)
446
+
447
+ if __name__ == "__main__":
448
+ main()
requirements.txt CHANGED
@@ -1,3 +1,5 @@
1
- altair
2
- pandas
3
- streamlit
 
 
 
1
+ streamlit==1.45.1
2
+ requests==2.31.0
3
+ pandas>=2.1.0
4
+ together==1.5.8
5
+ numpy>=1.24.0