nniehaus commited on
Commit
fd1c160
·
verified ·
1 Parent(s): 86065fb

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -674
app.py CHANGED
@@ -1,6 +1,13 @@
1
  import streamlit as st
 
 
 
 
 
 
 
2
 
3
- # Set page config to minimize default margins and padding - MUST BE FIRST STREAMLIT COMMAND
4
  st.set_page_config(
5
  page_title="Home Value Maximizer",
6
  page_icon="🏡",
@@ -8,703 +15,131 @@ st.set_page_config(
8
  initial_sidebar_state="collapsed"
9
  )
10
 
11
- import requests
12
- from PIL import Image
13
- import base64
14
- from io import BytesIO
15
- import time
16
- from datetime import datetime, timedelta
17
- import re
18
-
19
- # Configure OpenAI API settings
20
- API_URL = "https://api.openai.com/v1/chat/completions"
21
- MODEL_NAME = "gpt-4o" # Updated to use GPT-4o which includes vision capabilities
22
-
23
- # Get current date
24
- current_date = datetime.now().strftime("%B %d, %Y")
25
-
26
- # Set API key from Hugging Face secrets
27
- try:
28
- OPENAI_API_KEY = st.secrets["OPENAI_API_KEY"]
29
- except:
30
- OPENAI_API_KEY = "" # Will be prompted to enter API key if not in secrets
31
-
32
- # Session state initialization
33
- if "uploaded_images" not in st.session_state:
34
- st.session_state["uploaded_images"] = []
35
- if "api_key" not in st.session_state:
36
- st.session_state["api_key"] = OPENAI_API_KEY
37
- if "token_usage" not in st.session_state:
38
- st.session_state["token_usage"] = {}
39
-
40
- # Function to encode image to base64
41
- def encode_image(image):
42
- try:
43
- # Convert RGBA to RGB if needed
44
- if image.mode == 'RGBA':
45
- background = Image.new('RGB', image.size, (255, 255, 255))
46
- background.paste(image, (0, 0), image)
47
- image = background
48
- elif image.mode not in ['RGB', 'L']:
49
- # Convert any other mode to RGB
50
- image = image.convert('RGB')
51
-
52
- # Verify the image has valid dimensions
53
- if image.width <= 0 or image.height <= 0:
54
- raise ValueError("Image has invalid dimensions: width or height ≤ 0")
55
-
56
- # Resize large images to reduce token usage
57
- max_size = (1024, 1024)
58
- if image.width > max_size[0] or image.height > max_size[1]:
59
- image.thumbnail(max_size, Image.LANCZOS)
60
-
61
- # Save to buffer with error handling
62
- buffered = BytesIO()
63
- image.save(buffered, format="JPEG", quality=85)
64
- if buffered.getvalue() == b'':
65
- raise ValueError("Generated empty image data")
66
-
67
- # Encode to base64
68
- encoded = base64.b64encode(buffered.getvalue()).decode('utf-8')
69
- if not encoded:
70
- raise ValueError("Base64 encoding produced empty result")
71
-
72
- return encoded
73
- except Exception as e:
74
- raise Exception(f"Image encoding failed: {str(e)}")
75
 
76
- # Function to fix formatting issues in text
77
- def fix_formatting(text):
78
- # Fix issues with numbers and text run together
79
- text = re.sub(r'(\d+)([a-zA-Z])', r'\1 \2', text)
80
-
81
- # Fix issues with missing spaces after commas and periods
82
- text = re.sub(r'([.,])([a-zA-Z0-9])', r'\1 \2', text)
83
-
84
- # Fix issues with missing spaces after colons
85
- text = re.sub(r':([a-zA-Z0-9])', r': \1', text)
86
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  return text
88
 
89
- # Function to analyze home photos with OpenAI's API
90
- def analyze_home_photos(images, timeframe, additional_details, api_key):
91
- if not api_key:
92
- return "Error: API Key is required for analysis."
93
-
94
- if not images or len(images) == 0:
95
- return "Error: No valid images provided for analysis. Please upload at least one image of your home."
96
-
97
- headers = {
98
- "Content-Type": "application/json",
99
- "Authorization": f"Bearer {api_key}"
100
- }
101
-
102
- # Construct the message content
103
- content = [
104
- {"type": "text", "text": f"""Analyze these home photos and provide EXTREMELY SPECIFIC strategies to maximize the selling price.
105
- The owner plans to sell within {timeframe}. Today's date is {current_date}. {additional_details}
106
-
107
- Be incredibly detailed and specific about what you see in each image. Reference exact features, colors, materials, and conditions.
108
- Provide precise recommendations with specific products, materials, colors, and techniques.
109
-
110
- IMPORTANT: Ensure all your text is properly formatted with spaces between numbers and words, and proper spacing after punctuation.
111
- """}
112
- ]
113
-
114
- # Add images to the message with proper format for vision API
115
- try:
116
- for i, img in enumerate(images):
117
- try:
118
- base64_img = encode_image(img)
119
- content.append({
120
- "type": "image_url",
121
- "image_url": {
122
- "url": f"data:image/jpeg;base64,{base64_img}",
123
- "detail": "low" # Use low detail to reduce token usage
124
- }
125
- })
126
- except Exception as img_error:
127
- # Log error but continue with other images
128
- print(f"Error processing image {i}: {str(img_error)}")
129
- except Exception as e:
130
- return f"Error preparing images for analysis: {str(e)}"
131
-
132
- # Enhanced system prompt for more specific and actionable recommendations
133
- system_prompt = """You are an expert real estate advisor with deep knowledge of home selling strategies. You analyze home photos to provide EXTREMELY SPECIFIC, actionable recommendations that will maximize the property's selling price.
134
-
135
- ## IMPORTANT: ONLY make recommendations based on what you can actually see in the photos. DO NOT make assumptions about areas not shown (like exterior if only interior is shown or vice versa).
136
-
137
- ## FORMAT YOUR RESPONSE LIKE THIS:
138
-
139
- ### 📋 KEY RECOMMENDATIONS SUMMARY
140
- Begin with this exact text: "**HOME VALUE MAXIMIZER SUMMARY**"
141
 
142
- Then add this line: "Below are the top improvements to maximize your home's value. See detailed sections for specific costs, instructions, and timeline."
143
 
144
- Then, list 5 bullet points of the MOST IMPACTFUL improvements that will increase the property's value. ONLY include improvements for areas actually visible in the photos. For each bullet:
145
- - Make the first sentence **bold**
146
- - Include the specific issue identified
147
- - Include the recommended solution
148
- - Include the estimated value increase or ROI
149
 
150
  ### 👁️ AREAS ANALYZED
151
- Clearly list which areas of the home you can see in the photos (e.g., "Kitchen, Living Room, Master Bathroom") and note that recommendations are based only on visible areas.
152
 
153
  ### 🌟 TOP PRICE-MAXIMIZING PRIORITIES
154
- Present the 3-4 MOST IMPACTFUL improvements first, formatted as a clear list. Each item MUST include:
155
- - **Exact issue you see**: Be incredibly specific about what you observe in the photo (e.g., "The beige laminate countertops in the kitchen are visibly worn with scratches near the sink area")
156
- - **Precise recommendation**: Specify EXACT materials, colors, products, or contractors (e.g., "Replace with Calacatta Quartz countertops in white with subtle gray veining")
157
- - **Specific cost estimate**: Give narrow ranges (e.g., "$2,800-$3,200")
158
- - **Precise value impact**: Quantify the increase (e.g., "Estimated to add $8,000-$10,000 to home value")
159
- - **Timeline**: Exact number of days needed and mention if permits are required
160
 
161
- ### 🔨 QUICK WINS (1-3 days)
162
- List 5-6 fast, high-ROI improvements with EXACT details, BUT ONLY for areas visible in the photos:
163
- - **Kitchen**: Name specific issues, specific solutions, specific products/colors/materials
164
- - **Living Spaces**: Name specific issues, specific solutions, specific products/colors/materials
165
- - **Bathroom**: Name specific issues, specific solutions, specific products/colors/materials
166
- Only include categories that are actually visible in the photos.
167
- Include PRECISE cost estimates, DIY or contractor recommendations, and EXACT product suggestions.
168
 
169
  ### 📊 SPECIFIC PRICING STRATEGY
170
- Provide EXACT details on:
171
- - **Precise price point**: Suggest a specific price point or narrow range based on visible features (e.g., "$399,900" not "$399,900toappealpsychologically")
172
- - **Specific pricing psychology**: e.g., "$399,900 rather than $400,000" - MAKE SURE TO INCLUDE SPACES BETWEEN NUMBERS AND TEXT
173
- - **Exact timing**: Specific days of week and dates based on current date and selling timeframe
174
- - **Market-specific advice**: Relate to current market conditions with specific details
175
-
176
- IMPORTANT: ALWAYS use proper spacing in your text. For example:
177
- - CORRECT: "List at $399,900 to appeal psychologically to buyers under $400k."
178
- - INCORRECT: "List at $399,900toappealpsychologicallytobuyers."
179
 
180
  ### 📸 DETAILED MARKETING PLAN
181
- Provide SPECIFIC advice on:
182
- - **Exactly which room features**: Name specific architectural elements or features to highlight
183
- - **Specific photography angles**: Exact camera positions and times of day for optimal lighting
184
- - **Specific staging items**: Name exact furniture pieces, decor items, or color accents to add/remove
185
- - **Exact virtual tools**: Name specific apps or services for virtual tours/staging
186
 
187
  ### 📝 SPECIFIC NEGOTIATION TACTICS
188
- Recommend PRECISE approaches:
189
- - **Exact language**: Provide specific scripts for countering common objections
190
- - **Specific inspection strategy**: Name exact items to fix pre-inspection
191
- - **Precise contingency planning**: Specific strategies for common issues based on the home's visible condition
192
 
193
  ### ⚠️ CRITICAL ISSUES TO ADDRESS
194
- List 3 SPECIFIC problems visible in the photos that must be fixed, with EXACT solutions.
195
 
196
  ### 📅 DETAILED TIMELINE WITH DATES
197
- Create a PRECISE calendar using EXACT DATES starting from TODAY (${current_date}). Factor in the user's stated selling timeframe of ${timeframe}.
198
-
199
- For example, if selling within 1-3 months:
200
- - By [EXACT DATE]: Complete specific kitchen updates (list precisely what)
201
- - By [EXACT DATE]: Finish specific bathroom improvements (list precisely what)
202
- - By [EXACT DATE]: Address specific interior issues (list precisely what)
203
- - [EXACT DATE]: Schedule professional photography
204
- - [EXACT DATE]: List the home on the market
205
 
206
  ### 🚫 AREAS NOT VISIBLE
207
- If important areas for home selling evaluation (like exterior, bathrooms, bedrooms) are not shown in the photos, briefly mention: "Note: The following areas were not visible in the provided photos and could not be evaluated: [list areas]. Consider uploading photos of these areas for more comprehensive recommendations."
208
-
209
- ## IMPORTANT GUIDELINES:
210
- 1. ONLY make recommendations based on what you can ACTUALLY SEE in the photos. Never make recommendations for areas not shown.
211
- 2. Be EXTREMELY SPECIFIC about what you see in each photo - reference exact details, colors, materials
212
- 3. Give PRECISE product recommendations when possible - name brands, models, colors
213
- 4. Provide EXACT cost estimates in narrow ranges
214
- 5. Specify CLEAR timelines with CALENDAR DATES based on today's date (${current_date})
215
- 6. Make all advice HYPER-SPECIFIC to the actual property in the photos
216
- 7. NEVER give generic advice - every recommendation should directly reference visible elements
217
- 8. ALWAYS use proper spacing in text - add spaces between numbers and words, after punctuation, etc.
218
  """
219
-
220
- # Replace placeholders in the system prompt
221
- formatted_system_prompt = system_prompt.replace("${current_date}", current_date).replace("${timeframe}", timeframe)
222
-
223
- # Create the API payload with enhanced system prompt
224
- payload = {
225
- "model": MODEL_NAME,
226
- "messages": [
227
- {
228
- "role": "system",
229
- "content": formatted_system_prompt
230
- },
231
- {
232
- "role": "user",
233
- "content": content
234
- }
235
- ],
236
- "max_tokens": 2500
237
- }
238
-
239
- try:
240
- response = requests.post(API_URL, headers=headers, json=payload, timeout=90)
241
-
242
- if response.status_code == 200:
243
- response_data = response.json()
244
-
245
- # Store token usage data
246
- if "usage" in response_data:
247
- st.session_state["token_usage"] = response_data["usage"]
248
-
249
- # Get the content and fix any formatting issues
250
- content = response_data["choices"][0]["message"]["content"]
251
- content = fix_formatting(content)
252
-
253
- return content
254
- else:
255
- error_text = f"API Error: {response.status_code}"
256
- if response.text:
257
- try:
258
- error_json = response.json()
259
- if "error" in error_json:
260
- error_text += f" - {error_json['error']['message']}"
261
- except:
262
- error_text += f" - {response.text[:200]}"
263
- return f"Error: {error_text}. Please check your API key and try again."
264
- except Exception as e:
265
- return f"Error communicating with OpenAI API: {str(e)}. Please check your internet connection and try again."
266
 
267
- # Calculate estimated cost
268
- def calculate_cost(token_usage):
269
- if not token_usage:
270
- return {"total": "Unknown"}
271
-
272
- # Latest pricing as of April 2024 for GPT-4o
273
- input_cost_per_1M = 3 # $3 per 1M input tokens for GPT-4o
274
- output_cost_per_1M = 10 # $10 per 1M output tokens for GPT-4o
275
-
276
- prompt_tokens = token_usage.get("prompt_tokens", 0)
277
- completion_tokens = token_usage.get("completion_tokens", 0)
278
-
279
- input_cost = (prompt_tokens / 1000000) * input_cost_per_1M
280
- output_cost = (completion_tokens / 1000000) * output_cost_per_1M
281
- total_cost = input_cost + output_cost
282
-
283
- return {
284
- "input_tokens": prompt_tokens,
285
- "output_tokens": completion_tokens,
286
- "input_cost": input_cost,
287
- "output_cost": output_cost,
288
- "total_cost": total_cost
289
- }
290
 
291
- # Custom CSS to fix all styling issues including text visibility and header spacing
292
- st.markdown("""
293
- <style>
294
- /* Override streamlit's default padding to prevent header cutoff */
295
- .main .block-container {
296
- padding-top: 2rem !important; /* Increased from 1rem to give more space */
297
- padding-bottom: 1rem !important;
298
- max-width: 100% !important;
299
- }
300
-
301
- /* Fix for the header area to ensure it's fully visible in Hugging Face Spaces */
302
- .stApp {
303
- margin-top: 0.5rem !important;
304
- }
305
-
306
- /* Fix text visibility issues - ensure good contrast */
307
- .upload-text {
308
- color: #262730 !important;
309
- background-color: transparent !important;
310
- font-weight: 500 !important;
311
- }
312
-
313
- /* Make the title and header area more compact but visible */
314
- .title-area {
315
- margin: 0 !important;
316
- padding: 0.5rem 0 !important; /* Added top/bottom padding */
317
- margin-bottom: 0.5rem !important;
318
- }
319
-
320
- /* Make header more compact but ensure visibility */
321
- .stMarkdown h1 {
322
- margin-top: 0.5rem !important; /* Added margin to prevent cutoff */
323
- margin-bottom: 0.5rem !important;
324
- font-size: 1.8rem !important;
325
- padding: 0 !important;
326
- line-height: 1.3 !important; /* Improved line height */
327
- }
328
-
329
- /* Ensure the logo and title are visible */
330
- .title-section {
331
- display: block !important;
332
- padding-top: 0.5rem !important;
333
- margin-bottom: 0.75rem !important;
334
- }
335
-
336
- /* Steps styling - improved visibility and spacing */
337
- .steps-container {
338
- display: flex;
339
- justify-content: space-between;
340
- margin: 0.75rem 0 1rem 0 !important; /* Added top margin */
341
- padding: 0 !important;
342
- }
343
-
344
- .step-item {
345
- flex: 1;
346
- padding: 0.5rem !important; /* Increased padding */
347
- font-size: 0.85rem;
348
- color: #262730;
349
- background-color: #f0f2f6;
350
- border-radius: 4px;
351
- margin-right: 0.5rem;
352
- text-align: center;
353
- }
354
-
355
- /* Enhanced section styling */
356
- .stMarkdown h3 {
357
- margin-top: 1.5rem;
358
- padding: 0.5rem;
359
- border-radius: 0.5rem;
360
- font-weight: 600;
361
- color: white;
362
- }
363
-
364
- /* Style for summary section */
365
- .stMarkdown h3:contains("KEY RECOMMENDATIONS SUMMARY") {
366
- background-color: #2196f3;
367
- }
368
-
369
- /* Summary content styling */
370
- .summary-title {
371
- font-size: 1.2rem;
372
- font-weight: bold;
373
- margin-bottom: 0.5rem;
374
- color: #2196f3;
375
- }
376
-
377
- .summary-subtitle {
378
- font-size: 0.9rem;
379
- font-weight: 500;
380
- margin-bottom: 1rem;
381
- color: #424242;
382
- }
383
-
384
- /* Priority recommendations */
385
- .stMarkdown h3:contains("TOP PRICE") {
386
- background-color: #1e88e5;
387
- }
388
-
389
- /* Quick improvements section */
390
- .stMarkdown h3:contains("QUICK WINS") {
391
- background-color: #43a047;
392
- }
393
-
394
- /* Strategic pricing section */
395
- .stMarkdown h3:contains("SPECIFIC PRICING") {
396
- background-color: #5e35b1;
397
- }
398
-
399
- /* Marketing section */
400
- .stMarkdown h3:contains("DETAILED MARKETING") {
401
- background-color: #fb8c00;
402
- }
403
-
404
- /* Negotiation section */
405
- .stMarkdown h3:contains("SPECIFIC NEGOTIATION") {
406
- background-color: #00897b;
407
- }
408
-
409
- /* Critical issues section */
410
- .stMarkdown h3:contains("CRITICAL ISSUES") {
411
- background-color: #e53935;
412
- }
413
-
414
- /* Timeline section */
415
- .stMarkdown h3:contains("DETAILED TIMELINE") {
416
- background-color: #8e24aa;
417
- }
418
-
419
- /* Style for bullet points in summary */
420
- .stMarkdown ul li {
421
- margin-bottom: 0.75rem;
422
- line-height: 1.6;
423
- }
424
-
425
- /* Bold text */
426
- .stMarkdown strong {
427
- color: #262730;
428
- font-weight: 700;
429
- }
430
-
431
- /* Results container */
432
- .results-container {
433
- padding: 1.5rem;
434
- background-color: white;
435
- border-radius: 0.5rem;
436
- box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
437
- margin-bottom: 1.5rem;
438
- }
439
-
440
- /* Cost information */
441
- .cost-info {
442
- background-color: #f8f9fa;
443
- padding: 0.75rem;
444
- border-radius: 0.5rem;
445
- font-size: 0.85rem;
446
- margin-top: 1rem;
447
- }
448
-
449
- /* Blue background for summary section */
450
- .summary-section {
451
- background-color: #e3f2fd;
452
- border-radius: 8px;
453
- padding: 1rem;
454
- margin-bottom: 1.5rem;
455
- border-left: 4px solid #2196f3;
456
- }
457
-
458
- /* Custom header style for subheaders */
459
- .custom-subheader {
460
- background-color: #f5f5f5;
461
- padding: 10px;
462
- border-radius: 5px;
463
- margin-bottom: 10px;
464
- color: #262730;
465
- font-weight: 600;
466
- }
467
-
468
- /* Fix for code blocks and pre-formatted text */
469
- pre, code {
470
- white-space: pre-wrap !important;
471
- word-break: break-word !important;
472
- }
473
-
474
- /* Fix for Hugging Face Spaces iframe header issues */
475
- @media screen and (max-width: 1200px) {
476
- .main .block-container {
477
- padding-top: 3rem !important; /* Even more padding for smaller screens */
478
- }
479
- }
480
- </style>
481
- """, unsafe_allow_html=True)
482
 
483
- # Page config was moved to the top of the file as the first Streamlit command
484
-
485
- # Custom compact header - modified for better visibility
486
- st.markdown("""
487
- <div class="title-section">
488
- <h1>Home Value Maximizer 🏡</h1>
489
- <div class="upload-text">📸 Upload photos → 💰 Get specific improvements → 🏡 Maximize selling price</div>
490
- </div>
491
-
492
- <div class="steps-container">
493
- <div class="step-item">1. Upload multiple photos of your home</div>
494
- <div class="step-item">2. Enter your selling timeline & preferences</div>
495
- <div class="step-item">3. Click 'Analyze My Home' for detailed strategies</div>
496
- </div>
497
- """, unsafe_allow_html=True)
498
-
499
- # API Key input if not in secrets
500
- if not st.session_state["api_key"]:
501
- st.session_state["api_key"] = st.text_input("Enter your OpenAI API Key", type="password")
502
- st.info("Your API key is required to analyze the images. It will not be stored permanently.")
503
-
504
- # Main content columns
505
- col1, col2 = st.columns(2)
506
-
507
- with col1:
508
- # Custom subheader with background
509
- st.markdown('<div class="custom-subheader">📸 Upload Home Photos</div>', unsafe_allow_html=True)
510
- uploaded_files = st.file_uploader("Upload photos of different areas of your home",
511
- accept_multiple_files=True,
512
- type=["jpg", "jpeg", "png"])
513
-
514
- # Process uploaded images
515
- if uploaded_files:
516
- images = []
517
- valid_images = []
518
- error_messages = []
519
-
520
- for file in uploaded_files:
521
- try:
522
- # Read file content first to check if it's valid
523
- file_bytes = file.getvalue()
524
- if len(file_bytes) == 0:
525
- error_messages.append(f"Error: {file.name} appears to be empty.")
526
- continue
527
-
528
- # Try to open the image with PIL
529
- image = Image.open(BytesIO(file_bytes))
530
-
531
- # Validate image by trying to get its format
532
- image_format = image.format
533
- if not image_format:
534
- error_messages.append(f"Error: {file.name} doesn't appear to be a valid image format.")
535
- continue
536
-
537
- # Resize large images to reduce token usage
538
- max_size = (1024, 1024)
539
- if image.width > max_size[0] or image.height > max_size[1]:
540
- image.thumbnail(max_size, Image.LANCZOS)
541
-
542
- images.append(image)
543
- valid_images.append(file.name)
544
- except Exception as e:
545
- error_messages.append(f"Error processing {file.name}: {str(e)}")
546
-
547
- # Update session state with valid images
548
- st.session_state["uploaded_images"] = images
549
-
550
- # Display uploaded images and errors
551
- if images:
552
- st.write(f"**{len(images)} valid images uploaded**")
553
- image_cols = st.columns(min(3, len(images)))
554
- for i, img in enumerate(images):
555
- with image_cols[i % min(3, len(images))]:
556
- st.image(img, width=150, caption=f"Image {i+1}: {valid_images[i]}")
557
-
558
- # Display error messages if any
559
- if error_messages:
560
- with st.expander(f"⚠️ {len(error_messages)} image upload issues detected. Click to view details."):
561
- for error in error_messages:
562
- st.error(error)
563
-
564
- # Custom subheader with background
565
- st.markdown('<div class="custom-subheader">🏠 Selling Timeline & Details</div>', unsafe_allow_html=True)
566
-
567
- # Expanded selling timeline options
568
- timeframe = st.radio("When do you plan to sell your home?",
569
- ["Within 1 month", "1-3 months", "3-6 months", "6-12 months", "More than 12 months"])
570
-
571
- additional_details = st.text_area("Additional Details",
572
- placeholder="Describe your home, local market conditions, target buyers, or any specific concerns you have about the selling process.",
573
- height=100)
574
-
575
- # Additional preferences with custom subheader
576
- st.markdown('<div class="custom-subheader">💰 Budget & Preferences</div>', unsafe_allow_html=True)
577
-
578
- max_budget = st.slider("Maximum budget for improvements ($)",
579
- min_value=1000,
580
- max_value=50000,
581
- value=10000,
582
- step=1000)
583
-
584
- improvement_focus = st.multiselect(
585
- "Areas to focus on (optional)",
586
- ["Curb appeal", "Kitchen", "Bathroom", "Living spaces", "Outdoor areas", "Storage solutions", "Energy efficiency", "Marketing", "Pricing strategy", "Negotiation", "Staging"],
587
- default=["Curb appeal", "Kitchen", "Bathroom", "Pricing strategy"]
588
- )
589
-
590
- diy_preference = st.select_slider(
591
- "DIY Preference",
592
- options=["Professional work only", "Mix of DIY and professional", "Mostly DIY if possible"],
593
- value="Mix of DIY and professional"
594
- )
595
-
596
- # Analysis button with improved error handling
597
- analyze_button = st.button('🔍 Analyze My Home',
598
- use_container_width=True,
599
- disabled=len(st.session_state["uploaded_images"]) == 0 or not st.session_state["api_key"])
600
-
601
- # Clear image error message placement
602
- image_error_placeholder = st.empty()
603
-
604
- if len(st.session_state["uploaded_images"]) == 0:
605
- image_error_placeholder.warning("⚠️ Please upload at least one valid photo of your home to receive recommendations.")
606
-
607
- if not st.session_state["api_key"]:
608
- st.warning("⚠️ Please enter your OpenAI API key to enable analysis.")
609
-
610
- with col2:
611
- # Custom subheader with background
612
- st.markdown('<div class="custom-subheader">💡 Comprehensive Selling Strategies</div>', unsafe_allow_html=True)
613
-
614
- # Process and display analysis results
615
- if analyze_button or "analysis_result" in st.session_state:
616
- # Process analysis if button pressed
617
- if analyze_button:
618
- # Check if there are valid images
619
- if len(st.session_state["uploaded_images"]) == 0:
620
- st.error("No valid images to analyze. Please upload at least one home photo.")
621
- else:
622
- # Simple loading indicator using pure Streamlit - no HTML, JS or CSS
623
- with st.spinner("🏡 Analyzing your home photos..."):
624
- # Show a progress bar
625
- progress_bar = st.progress(0)
626
-
627
- # Show the analysis steps with native Streamlit components
628
- steps_placeholder = st.empty()
629
- steps_placeholder.info("Step 1: Identifying property features and conditions...")
630
- progress_bar.progress(25)
631
- time.sleep(0.5) # Reduced delay for faster response
632
-
633
- steps_placeholder.info("Step 2: Evaluating improvement opportunities and researching specific recommendations...")
634
- progress_bar.progress(50)
635
- time.sleep(0.5) # Reduced delay for faster response
636
-
637
- steps_placeholder.info("Step 3: Calculating ROI potential and precise cost estimates...")
638
- progress_bar.progress(75)
639
- time.sleep(0.5) # Reduced delay for faster response
640
-
641
- steps_placeholder.info("Step 4: Creating detailed timeline and finalizing recommendations...")
642
-
643
- # Make the actual API call with enhanced error handling
644
- try:
645
- analysis_text = analyze_home_photos(
646
- st.session_state["uploaded_images"],
647
- timeframe,
648
- f"Budget: ${max_budget}. Focus areas: {', '.join(improvement_focus)}. DIY preference: {diy_preference}. {additional_details}",
649
- st.session_state["api_key"]
650
- )
651
-
652
- # Store the result
653
- st.session_state["analysis_result"] = analysis_text
654
- except Exception as e:
655
- st.error(f"Error during analysis: {str(e)}")
656
- st.session_state["analysis_result"] = f"An error occurred during analysis: {str(e)}. Please try again with different images or check your API key."
657
-
658
- # Complete the progress bar and clear the loading indicators
659
- progress_bar.progress(100)
660
- steps_placeholder.empty()
661
- progress_bar.empty()
662
-
663
- # Display results
664
- if "analysis_result" in st.session_state:
665
- # Process the output to enhance summary formatting
666
- result = st.session_state["analysis_result"]
667
-
668
- # Check if we need to style the summary section
669
- if "**HOME VALUE MAXIMIZER SUMMARY**" in result:
670
- # Split the result to find the summary section
671
- parts = result.split("### 📋 KEY RECOMMENDATIONS SUMMARY", 1)
672
- if len(parts) > 1:
673
- before_summary = parts[0]
674
- rest_parts = parts[1].split("### 🌟", 1)
675
- if len(rest_parts) > 1:
676
- summary_content = rest_parts[0]
677
- after_summary = "### 🌟" + rest_parts[1]
678
-
679
- # Apply custom formatting to the summary
680
- result = before_summary + '<div class="summary-section">' + summary_content + '</div>' + after_summary
681
-
682
- # Display the enhanced result
683
- st.markdown(f'<div class="results-container">{result}</div>', unsafe_allow_html=True)
684
-
685
- # Add a download button for the report
686
- st.download_button(
687
- label="Download Selling Strategies as Text",
688
- data=st.session_state["analysis_result"],
689
- file_name="home_selling_strategies.txt",
690
- mime="text/plain",
691
- use_container_width=True
692
- )
693
  else:
694
- st.markdown('<div class="results-container">**Personalized selling strategies will appear here after you submit your home photos and details**</div>', unsafe_allow_html=True)
695
-
696
- # Disclaimer section
697
- st.markdown("---")
698
- with st.expander("📜 **Disclaimer**"):
699
- st.markdown("""
700
- **Disclaimer:**
701
- This Home Value Maximizer tool provides recommendations based on AI analysis of your home photos.
702
- Results are for **informational purposes only** and may not be accurate for your specific market or situation.
703
-
704
- - **Price impact estimates** are approximate and depend on local market conditions
705
- - **Pricing strategies** should be verified with a local real estate professional familiar with your specific market
706
- - **Improvement recommendations** and costs may vary based on your location and local labor costs
707
- - **Product recommendations** are suggestions only; please research compatibility with your home
708
-
709
- For the best results, consult with experienced real estate professionals in your area before making significant decisions.
710
- """)
 
1
  import streamlit as st
2
+ from PIL import Image
3
+ from io import BytesIO
4
+ import openai
5
+ import base64
6
+ import time
7
+ from datetime import datetime
8
+ import re
9
 
10
+ # --- Page config (MUST be first Streamlit command) ---
11
  st.set_page_config(
12
  page_title="Home Value Maximizer",
13
  page_icon="🏡",
 
15
  initial_sidebar_state="collapsed"
16
  )
17
 
18
+ # --- Global CSS for consistent fonts & spacing ---
19
+ st.markdown(
20
+ """
21
+ <style>
22
+ body, .stApp { font-family: 'Helvetica Neue', Arial, sans-serif !important; }
23
+ h1, h2, h3, h4, h5, h6 { font-family: inherit !important; font-weight: 600 !important; }
24
+ pre, code { font-family: 'Courier New', monospace !important; white-space: pre-wrap !important; word-break: break-word !important; }
25
+ .stMarkdown ul > li { margin-bottom: 0.5rem; line-height: 1.4; }
26
+ </style>
27
+ """,
28
+ unsafe_allow_html=True
29
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ # --- OpenAI SDK client caching ---
32
+ @st.cache_resource
33
+ def get_openai_client(api_key: str):
34
+ openai.api_key = api_key
35
+ return openai
36
+
37
+ # --- Image encoding helper ---
38
+ @st.cache_data
39
+ def encode_image_to_b64(image: Image.Image) -> str:
40
+ # Convert to RGB if needed
41
+ if image.mode == 'RGBA':
42
+ bg = Image.new('RGB', image.size, (255,255,255))
43
+ bg.paste(image, mask=image.split()[3])
44
+ image = bg
45
+ elif image.mode not in ['RGB', 'L']:
46
+ image = image.convert('RGB')
47
+ # Resize to limit token usage
48
+ max_size = (1024, 1024)
49
+ if image.width > max_size[0] or image.height > max_size[1]:
50
+ image.thumbnail(max_size, Image.LANCZOS)
51
+ buf = BytesIO()
52
+ image.save(buf, format='JPEG', quality=85)
53
+ return base64.b64encode(buf.getvalue()).decode('utf-8')
54
+
55
+ # --- Simple formatting fixes ---
56
+ def fix_formatting(text: str) -> str:
57
+ text = re.sub(r'(\d+)([A-Za-z])', r'\1 \2', text)
58
+ text = re.sub(r'([.,])([A-Za-z0-9])', r'\1 \2', text)
59
+ text = re.sub(r':([A-Za-z0-9])', r': \1', text)
60
  return text
61
 
62
+ # --- System prompt ---
63
+ SYSTEM_PROMPT = f"""
64
+ You are an expert real estate advisor with deep knowledge of home selling strategies. You analyze home photos to provide EXTREMELY SPECIFIC, actionable recommendations that will maximize the property's selling price.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
+ ONLY make recommendations based on visible areas in the photos. Reference exact features, colors, materials, and conditions you can see.
67
 
68
+ FORMAT:
69
+ ### 📋 HOME VALUE MAXIMIZER SUMMARY
70
+ - Start with exactly this header.
71
+ - Then: "Below are the top improvements to maximize your home’s value..."
72
+ - List 5 bullet points: **bold issue**, specific solution, estimated ROI.
73
 
74
  ### 👁️ AREAS ANALYZED
75
+ List which areas you see (e.g., Kitchen, Living Room).
76
 
77
  ### 🌟 TOP PRICE-MAXIMIZING PRIORITIES
78
+ For the 34 biggest improvements: exact issue, precise recommendation, cost estimate, value impact, timeline.
 
 
 
 
 
79
 
80
+ ### 🔨 QUICK WINS (13 days)
81
+ List 56 fast, high-ROI fixes for visible areas; include product names/colors/costs.
 
 
 
 
 
82
 
83
  ### 📊 SPECIFIC PRICING STRATEGY
84
+ - Precise price point and pricing psychology (spaces between numbers and text).
85
+ - Exact timing: days/dates based on current date ({datetime.now().strftime('%B %d, %Y')}).
 
 
 
 
 
 
 
86
 
87
  ### 📸 DETAILED MARKETING PLAN
88
+ List specific features to highlight, angles, staging items, virtual tools.
 
 
 
 
89
 
90
  ### 📝 SPECIFIC NEGOTIATION TACTICS
91
+ Exact scripts, inspection strategies, contingency planning.
 
 
 
92
 
93
  ### ⚠️ CRITICAL ISSUES TO ADDRESS
94
+ List 3 visible problems with exact fixes.
95
 
96
  ### 📅 DETAILED TIMELINE WITH DATES
97
+ Provide calendar dates for each step, from today.
 
 
 
 
 
 
 
98
 
99
  ### 🚫 AREAS NOT VISIBLE
100
+ Note any important areas you couldn’t see.
 
 
 
 
 
 
 
 
 
 
101
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
+ # --- Streamlit layout ---
104
+ st.title("Home Value Maximizer 🏡")
105
+ st.write("Upload photos, enter your timeline & budget, then click Analyze for detailed, hyper-specific advice.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
+ # API key
108
+ api_key = st.sidebar.text_input("OpenAI API Key", type="password")
109
+ client = get_openai_client(api_key) if api_key else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
+ # Upload images
112
+ st.sidebar.header("📸 Upload Home Photos")
113
+ uploaded = st.sidebar.file_uploader("Images", type=["jpg","png","jpeg"], accept_multiple_files=True)
114
+ images = []
115
+ for file in uploaded:
116
+ try:
117
+ img = Image.open(file)
118
+ images.append(img)
119
+ except:
120
+ st.sidebar.error(f"Failed to load {file.name}")
121
+
122
+ # Inputs
123
+ timeframe = st.sidebar.selectbox("Selling timeframe", ["Within 1 month","1–3 months","3–6 months","6–12 months",">12 months"])
124
+ budget = st.sidebar.slider("Max improvement budget ($)", 1000, 50000, 10000, 1000)
125
+ improve_focus = st.sidebar.multiselect("Focus areas (opt)", ["Curb appeal","Kitchen","Bathroom","Living Spaces","Outdoor","Storage","Energy","Marketing","Negotiation","Staging"], default=["Kitchen","Bathroom"])
126
+
127
+ # Analyze button
128
+ if st.button("🔍 Analyze My Home"):
129
+ if not client:
130
+ st.error("Enter API key to analyze.")
131
+ elif not images:
132
+ st.error("Upload at least one photo.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  else:
134
+ with st.spinner("Analyzing..."):
135
+ user_details = f"Budget: ${budget}. Focus: {', '.join(improve_focus)}"
136
+ analysis = analyze_home_photos(images, timeframe, user_details, client)
137
+ st.success("Analysis complete!")
138
+ st.markdown(f"<div style='background:#e3f2fd;padding:1rem;border-left:4px solid #1f77b4;'>{analysis}</div>", unsafe_allow_html=True)
139
+ # Cost
140
+ usage = client.api_usage if hasattr(client, 'api_usage') else {}
141
+ cost = calculate_cost(usage)
142
+ st.metric("💲 Estimated Cost", f"${cost:.2f}")
143
+
144
+ else:
145
+ st.info("Submit images & API key to generate recommendations.")