harshit08042004 commited on
Commit
917701a
·
verified ·
1 Parent(s): e1c601e

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +162 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,164 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ from PIL import Image
3
+ import os
4
+ from dotenv import load_dotenv
5
+ import requests
6
+ import base64
7
+ from io import BytesIO
8
+ import json
9
+ import re
10
 
11
+ # Load environment variables
12
+ load_dotenv()
13
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
14
+ # Placeholder for AI image analysis (fallback)
15
+ def analyze_image_placeholder():
16
+ return {
17
+ "area_m2": 100,
18
+ "orientation_deg": 180,
19
+ "shading_percent": 10,
20
+ "obstructions": ["chimney"]
21
+ }
22
+
23
+ # OpenRouter API image analysis
24
+ def analyze_image(image=None):
25
+ if image is None:
26
+ return analyze_image_placeholder()
27
+
28
+ try:
29
+ # Convert PIL image to base64
30
+ buffered = BytesIO()
31
+ image.save(buffered, format="PNG")
32
+ img_str = base64.b64encode(buffered.getvalue()).decode()
33
+
34
+ # Call OpenRouter API
35
+ url = "https://openrouter.ai/api/v1/chat/completions"
36
+ headers = {
37
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
38
+ "Content-Type": "application/json"
39
+ }
40
+ payload = {
41
+ "model": "opengvlab/internvl3-14b:free",
42
+ "messages": [
43
+ {
44
+ "role": "user",
45
+ "content": [
46
+ {
47
+ "type": "text",
48
+ "text": (
49
+ "Analyze this rooftop image for solar potential. "
50
+ "Respond ONLY with valid JSON, no explanation, with these keys: "
51
+ "area_m2, orientation_deg, shading_percent, obstructions."
52
+ )
53
+ },
54
+ {
55
+ "type": "image_url",
56
+ "image_url": {"url": f"data:image/png;base64,{img_str}"}
57
+ }
58
+ ]
59
+ }
60
+ ]
61
+ }
62
+ response = requests.post(url, headers=headers, json=payload)
63
+
64
+ if response.status_code == 200:
65
+ result = response.json()
66
+ content = result["choices"][0]["message"]["content"]
67
+ st.write("API raw response:", content) # For debugging
68
+ try:
69
+ # Extract JSON block (from first { to last })
70
+ match = re.search(r"\{.*\}", content, re.DOTALL)
71
+ if not match:
72
+ raise json.JSONDecodeError("No JSON object found", content, 0)
73
+ json_str = match.group(0)
74
+ # Remove comments (// ...)
75
+ json_str = re.sub(r"//.*", "", json_str)
76
+ # Parse JSON
77
+ parsed = json.loads(json_str)
78
+ # Map fields to expected format
79
+ if "solar_analysis" in parsed:
80
+ sa = parsed["solar_analysis"]
81
+ result_dict = {
82
+ "area_m2": sa.get("total_area", {}).get("suitable_rooftops_area", 100),
83
+ "orientation_deg": sa.get("orientation", 180),
84
+ "shading_percent": sa.get("shading_percentage", 10),
85
+ "obstructions": sa.get("obstructions", []),
86
+ }
87
+ else:
88
+ result_dict = parsed # fallback, in case structure matches directly
89
+ # Validate required fields
90
+ required = ["area_m2", "orientation_deg", "shading_percent", "obstructions"]
91
+ if all(key in result_dict for key in required):
92
+ return result_dict
93
+ else:
94
+ st.warning("API response missing required fields. Using placeholder data.")
95
+ return analyze_image_placeholder()
96
+ except Exception as e:
97
+ st.warning(f"Invalid JSON from API. Using placeholder data. ({e})")
98
+ return analyze_image_placeholder()
99
+ else:
100
+ st.error(f"API error: {response.text}")
101
+ return analyze_image_placeholder()
102
+ except Exception as e:
103
+ st.error(f"Error processing image: {str(e)}")
104
+ return analyze_image_placeholder()
105
+
106
+ def orientation_factor(orientation):
107
+ if 165 <= orientation <= 195: # South-facing
108
+ return 1.0
109
+ elif 75 <= orientation <= 105 or 255 <= orientation <= 285: # East/West
110
+ return 0.8
111
+ else: # Other orientations
112
+ return 0.6
113
+
114
+ # Calculate solar potential (kWh/year)
115
+ def calculate_solar_potential(area, orientation, shading, insolation=5):
116
+ efficiency = 0.2 # Panel efficiency
117
+ usable_area = area * (1 - shading / 100)
118
+ orientation_adj = usable_area * orientation_factor(orientation)
119
+ annual_kwh = orientation_adj * insolation * 365 * efficiency
120
+ return round(annual_kwh, 2)
121
+ # Calculate ROI
122
+ def calculate_roi(kwh, cost_per_watt=3, incentive=0.3, electricity_rate=0.12):
123
+ system_size_w = kwh / (5 * 365) * 1000 # Convert kWh to system size
124
+ total_cost = system_size_w * cost_per_watt
125
+ cost_after_incentive = total_cost * (1 - incentive)
126
+ payback_years = cost_after_incentive / (kwh * electricity_rate)
127
+ return round(cost_after_incentive, 2), round(payback_years, 1)
128
+
129
+ # Streamlit app
130
+ st.title("Solar Rooftop Analysis Tool")
131
+
132
+ # Image upload
133
+ uploaded_file = st.file_uploader("Upload satellite image of rooftop", type=["png", "jpg", "jpeg"])
134
+
135
+ if uploaded_file is not None:
136
+ image = Image.open(uploaded_file)
137
+ st.image(image, caption="Uploaded Rooftop Image", use_column_width=True)
138
+ result = analyze_image(image)
139
+ else:
140
+ st.write("No image uploaded. Using placeholder data: 100m², south-facing, 10% shading.")
141
+ result = analyze_image()
142
+
143
+ # Display analysis results
144
+ st.write("### Rooftop Analysis")
145
+ st.json(result)
146
+
147
+ # Calculate and display solar potential
148
+ kwh = calculate_solar_potential(
149
+ result["area_m2"], result["orientation_deg"], result["shading_percent"]
150
+ )
151
+ st.write(f"**Estimated Annual Energy Production**: {kwh} kWh")
152
+
153
+ # Calculate and display ROI
154
+ cost, payback = calculate_roi(kwh)
155
+ st.write(f"**Estimated Cost (after 30% incentive)**: ${cost}")
156
+ st.write(f"**Payback Period**: {payback} years")
157
+
158
+ # Installation recommendations
159
+ st.write("### Installation Recommendations")
160
+ st.write("- **Panel Type**: Monocrystalline (20% efficiency)")
161
+ st.write(f"- **Number of Panels**: ~{int(result['area_m2'] / 2)} (2m² per panel)")
162
+ st.write("- **Mounting**: Flush mount, south-facing")
163
+ st.write("- **Maintenance**: Annual cleaning, monitor via app")
164
+ st.write("- **Compliance**: Follow NEC 2020 standards, check local net metering policies")