rajkhanke commited on
Commit
05fec25
·
verified ·
1 Parent(s): f2a0c2b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +306 -306
app.py CHANGED
@@ -1,307 +1,307 @@
1
- from flask import Flask, request, jsonify, render_template
2
- import json
3
- import requests
4
- import random
5
- import math
6
- import os
7
- from dotenv import load_dotenv
8
-
9
- load_dotenv() # Load environment variables from .env file
10
-
11
- app = Flask(__name__)
12
-
13
- GEMINI_API_KEY = "AIzaSyDkiYr-eSkqIXpZ1fHlik_YFsFtfQoFi0w"
14
- NOMINATIM_USER_AGENT = "GeoSafeConstruct/1.0 ([email protected])" # IMPORTANT: Change to your app name and contact email
15
-
16
-
17
- # --- Nominatim API Functions ---
18
- def get_location_name_from_coords(lat, lon):
19
- """Fetches a location name/address from coordinates using Nominatim."""
20
- headers = {"User-Agent": NOMINATIM_USER_AGENT}
21
- url = f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}&zoom=16&addressdetails=1"
22
- try:
23
- response = requests.get(url, headers=headers, timeout=10)
24
- response.raise_for_status()
25
- data = response.json()
26
- # Construct a readable address, prioritizing certain fields
27
- address = data.get('address', {})
28
- parts = [
29
- address.get('road'), address.get('neighbourhood'), address.get('suburb'),
30
- address.get('city_district'), address.get('city'), address.get('town'),
31
- address.get('village'), address.get('county'), address.get('state'),
32
- address.get('postcode'), address.get('country')
33
- ]
34
- # Filter out None values and join
35
- display_name = data.get('display_name', "Unknown Location")
36
- filtered_parts = [part for part in parts if part]
37
- if len(filtered_parts) > 3: # If many parts, use display_name for brevity
38
- return display_name
39
- elif filtered_parts:
40
- return ", ".join(filtered_parts[:3]) # Take first few relevant parts
41
- return display_name # Fallback to full display_name
42
- except requests.exceptions.RequestException as e:
43
- print(f"Nominatim reverse geocoding error: {e}")
44
- return f"Area around {lat:.3f}, {lon:.3f}"
45
- except (json.JSONDecodeError, KeyError) as e:
46
- print(f"Nominatim response parsing error: {e}")
47
- return f"Area around {lat:.3f}, {lon:.3f}"
48
-
49
-
50
- def get_coords_from_location_name(query):
51
- """Fetches coordinates from a location name/address using Nominatim."""
52
- headers = {"User-Agent": NOMINATIM_USER_AGENT}
53
- url = f"https://nominatim.openstreetmap.org/search?q={requests.utils.quote(query)}&format=json&limit=1"
54
- try:
55
- response = requests.get(url, headers=headers, timeout=10)
56
- response.raise_for_status()
57
- data = response.json()
58
- if data and isinstance(data, list) and len(data) > 0:
59
- return {
60
- "latitude": float(data[0].get("lat")),
61
- "longitude": float(data[0].get("lon")),
62
- "display_name": data[0].get("display_name")
63
- }
64
- return None
65
- except requests.exceptions.RequestException as e:
66
- print(f"Nominatim geocoding error: {e}")
67
- return None
68
- except (json.JSONDecodeError, KeyError, IndexError) as e:
69
- print(f"Nominatim geocoding response parsing error: {e}")
70
- return None
71
-
72
-
73
- # --- Gemini API Functions ---
74
- def call_gemini_api(prompt_text):
75
- """Generic function to call Gemini API."""
76
- if not GEMINI_API_KEY:
77
- print("GEMINI_API_KEY not found in environment variables.")
78
- return None, "Gemini API key not configured."
79
-
80
- url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent"
81
- headers = {
82
- "Content-Type": "application/json",
83
- "x-goog-api-key": GEMINI_API_KEY
84
- }
85
- payload = {
86
- "contents": [{"parts": [{"text": prompt_text}]}],
87
- "generationConfig": {
88
- "response_mime_type": "application/json",
89
- "temperature": 0.6, # Adjust for creativity vs. factuality
90
- "topP": 0.9,
91
- "topK": 40
92
- },
93
- "safetySettings": [ # Add safety settings if needed
94
- {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
95
- {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
96
- {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
97
- {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}
98
- ]
99
- }
100
-
101
- try:
102
- response = requests.post(url, json=payload, headers=headers, timeout=60) # Increased timeout
103
- response.raise_for_status()
104
- gemini_response_data = response.json()
105
-
106
- if 'candidates' not in gemini_response_data or not gemini_response_data['candidates']:
107
- print("Gemini API Error: No candidates in response.")
108
- print("Full Gemini Response:", gemini_response_data)
109
- if 'promptFeedback' in gemini_response_data and 'blockReason' in gemini_response_data['promptFeedback']:
110
- return None, f"Content blocked by API: {gemini_response_data['promptFeedback']['blockReason']}"
111
- return None, "Gemini API returned no content."
112
-
113
- json_text_content = gemini_response_data['candidates'][0]['content']['parts'][0]['text']
114
-
115
- # Attempt to clean JSON string if it's wrapped in markdown
116
- if json_text_content.strip().startswith("```json"):
117
- json_text_content = json_text_content.strip()[7:]
118
- if json_text_content.strip().endswith("```"):
119
- json_text_content = json_text_content.strip()[:-3]
120
-
121
- return json.loads(json_text_content.strip()), None
122
- except requests.exceptions.Timeout:
123
- print("Gemini API call timed out.")
124
- return None, "Analysis request timed out. Please try again."
125
- except requests.exceptions.RequestException as e:
126
- print(f"Error calling Gemini API (RequestException): {e}")
127
- return None, f"Could not connect to analysis service: {e}"
128
- except json.JSONDecodeError as e:
129
- print(f"Error decoding JSON from Gemini API: {e}")
130
- print(f"Received text for JSON parsing: {json_text_content if 'json_text_content' in locals() else 'N/A'}")
131
- return None, "AI returned an invalid response format. Please try again."
132
- except (KeyError, IndexError) as e:
133
- print(f"Error parsing Gemini API response structure: {e}")
134
- print(f"Gemini response: {gemini_response_data if 'gemini_response_data' in locals() else 'N/A'}")
135
- return None, "AI returned an unexpected response structure."
136
- except Exception as e:
137
- print(f"An unexpected error occurred during Gemini call: {e}")
138
- return None, f"An unexpected error occurred: {e}"
139
-
140
-
141
- def get_safety_analysis_with_gemini(latitude, longitude, location_address):
142
- """
143
- Calls Gemini API to analyze construction safety.
144
- """
145
- prompt = f"""
146
- **Objective:** Provide a detailed and realistic construction site safety analysis for the location: {location_address} (Latitude: {latitude}, Longitude: {longitude}).
147
-
148
- **Output Format:** STRICTLY JSON. Do NOT include any text outside the JSON structure (e.g., no '```json' or '```' wrappers).
149
-
150
- **JSON Structure:**
151
- {{
152
- "location_name": "{location_address}", // Use the provided address
153
- "safety_score": "integer (0-100, overall safety score, be critical and realistic)",
154
- "suitability_statement": "string (e.g., 'Generally Suitable with Mitigations', 'High Caution Advised', 'Not Recommended without Major Intervention')",
155
- "summary_assessment": "string (3-4 sentences summarizing key findings, suitability, and major concerns. Be specific.)",
156
- "geological_risks": {{
157
- "earthquake_risk": {{
158
- "level": "string ('Low', 'Medium', 'High', 'Very High', 'Not Assessed')",
159
- "details": "string (Specifics like proximity to faults, historical activity, soil liquefaction potential if known. If not assessed, state why.)"
160
- }},
161
- "landslide_risk": {{
162
- "level": "string ('Low', 'Medium', 'High', 'Very High', 'Not Assessed')",
163
- "details": "string (Slope stability, soil type, vegetation, historical incidents. If not assessed, state why.)"
164
- }},
165
- "soil_stability": {{
166
- "type": "string ('Stable', 'Moderately Stable', 'Unstable', 'Variable', 'Requires Investigation')",
167
- "concerns": ["string array (e.g., 'Expansive clays present', 'High water table', 'Poor drainage', 'Subsidence risk')"]
168
- }}
169
- }},
170
- "hydrological_risks": {{
171
- "flood_risk": {{
172
- "level": "string ('Low', 'Medium', 'High', 'Very High', 'Not Assessed')",
173
- "details": "string (Proximity to water bodies, floodplain maps, historical flooding, drainage issues. If not assessed, state why.)"
174
- }},
175
- "tsunami_risk": {{ // Only include if geographically relevant (coastal)
176
- "level": "string ('Negligible', 'Low', 'Medium', 'High')",
177
- "details": "string (Distance from coast, elevation, historical data.)"
178
- }}
179
- }},
180
- "other_environmental_risks": [ // Array of objects, include if relevant
181
- {{ "type": "Wildfire", "level": "string ('Low', 'Medium', 'High')", "details": "string (Vegetation, climate, fire history)" }},
182
- {{ "type": "Extreme Weather (Hurricanes/Tornadoes/High Winds)", "level": "string ('Low', 'Medium', 'High')", "details": "string (Regional patterns, building code requirements)" }},
183
- {{ "type": "Industrial Hazards", "level": "string ('Low', 'Medium', 'High')", "details": "string (Proximity to industrial plants, pipelines, hazardous material routes)" }}
184
- ],
185
- "key_risk_factors_summary": ["string array (Bulleted list of 3-5 most critical risk factors for this specific site)"],
186
- "mitigation_recommendations": ["string array (Actionable, specific recommendations, e.g., 'Conduct Level 2 Geotechnical Survey focusing on shear strength', 'Implement earthquake-resistant design to Zone IV standards', 'Elevate foundation by 1.5m above base flood elevation')"],
187
- "further_investigations_needed": ["string array (e.g., 'Detailed hydrological study', 'Environmental Impact Assessment', 'Traffic impact study')"],
188
- "alternative_locations_analysis": [ // Up to 2-3 alternatives. If none are significantly better, state that.
189
- {{
190
- "name_suggestion": "string (Suggest a conceptual name like 'North Ridge Site' or 'Valley View Plot')",
191
- "latitude": "float (as string, e.g., '18.5234')",
192
- "longitude": "float (as string, e.g., '73.8567')",
193
- "estimated_distance_km": "float (as string, e.g., '8.5')",
194
- "brief_justification": "string (Why is this a potential alternative? E.g., 'Appears to be on more stable ground', 'Further from flood plain')",
195
- "potential_pros": ["string array"],
196
- "potential_cons": ["string array"],
197
- "comparative_safety_score_estimate": "integer (0-100, relative to primary site)"
198
- }}
199
- ],
200
- "data_confidence_level": "string ('Low', 'Medium', 'High' - based on assumed availability of public data for this general region, not specific site data which is unknown to you)",
201
- "disclaimer": "This AI-generated analysis is for preliminary informational purposes only and not a substitute for professional engineering, geological, and environmental assessments. On-site investigations are crucial."
202
- }}
203
-
204
- **Guidelines for Content:**
205
- - make sumre rnaodmness in score generation not same view ppijn t everytime
206
- - Be realistic. If data for a specific risk is unlikely to be publicly available for a random coordinate, mark it 'Not Assessed' and explain.
207
- - Focus on actionable insights.
208
- - For alternative locations, provide *different* lat/lon coordinates that are plausibly nearby (within 5-20km).
209
- - Ensure all string values are properly quoted. Ensure latitudes and longitudes for alternatives are strings representing floats.
210
- - The safety_score should reflect a comprehensive evaluation of all risks.
211
- - proper spacing and upo down margins shoudl be there
212
- """
213
- return call_gemini_api(prompt)
214
-
215
-
216
- # --- Flask Routes ---
217
- @app.route('/')
218
- def index():
219
- return render_template('index.html')
220
-
221
-
222
- @app.route('/api/geocode', methods=['POST'])
223
- def geocode_location():
224
- data = request.json
225
- query = data.get('query')
226
- if not query:
227
- return jsonify({"error": "Query parameter is required."}), 400
228
-
229
- coords_data = get_coords_from_location_name(query)
230
- if coords_data:
231
- return jsonify(coords_data), 200
232
- else:
233
- return jsonify({"error": "Location not found or geocoding failed."}), 404
234
-
235
-
236
- @app.route('/api/analyze_location', methods=['POST'])
237
- def analyze_location_route():
238
- data = request.json
239
- try:
240
- latitude = float(data.get('latitude'))
241
- longitude = float(data.get('longitude'))
242
- except (TypeError, ValueError):
243
- return jsonify({"error": "Invalid latitude or longitude provided."}), 400
244
-
245
- # Get a human-readable name for the primary location
246
- primary_location_address = get_location_name_from_coords(latitude, longitude)
247
-
248
- # Call Gemini for the main analysis
249
- analysis_data, error = get_safety_analysis_with_gemini(latitude, longitude, primary_location_address)
250
-
251
- if error:
252
- # Fallback to a simpler mock-like structure if Gemini fails critically
253
- return jsonify({
254
- "error_message": error,
255
- "location_name": primary_location_address,
256
- "safety_score": random.randint(20, 50), # Low score to indicate issue
257
- "summary_assessment": f"Could not perform detailed analysis due to: {error}. Basic location: {primary_location_address}.",
258
- "suitability_statement": "Analysis Incomplete",
259
- "geological_risks": {"earthquake_risk": {"level": "Not Assessed", "details": error}},
260
- "hydrological_risks": {"flood_risk": {"level": "Not Assessed", "details": error}},
261
- "disclaimer": "A technical issue prevented full analysis."
262
- }), 500
263
-
264
- # Post-process alternative locations to get their names
265
- if analysis_data.get("alternative_locations_analysis"):
266
- for alt_loc in analysis_data["alternative_locations_analysis"]:
267
- try:
268
- alt_lat = float(alt_loc.get("latitude"))
269
- alt_lon = float(alt_loc.get("longitude"))
270
- alt_loc["actual_name"] = get_location_name_from_coords(alt_lat, alt_lon)
271
- # Ensure distance is calculated if not provided by Gemini or if it's nonsensical
272
- if not alt_loc.get("estimated_distance_km") or float(alt_loc.get("estimated_distance_km", 0)) == 0:
273
- alt_loc["estimated_distance_km"] = str(
274
- calculate_haversine_distance(latitude, longitude, alt_lat, alt_lon))
275
-
276
- except (TypeError, ValueError, KeyError) as e:
277
- print(f"Error processing alternative location coords: {e}, data: {alt_loc}")
278
- alt_loc["actual_name"] = "Nearby Area (details unavailable)"
279
- # Fallback if lat/lon are missing or invalid for an alternative
280
- if "latitude" not in alt_loc or "longitude" not in alt_loc:
281
- alt_loc["latitude"] = str(latitude + (random.random() - 0.5) * 0.05) # Small offset
282
- alt_loc["longitude"] = str(longitude + (random.random() - 0.5) * 0.05)
283
-
284
- return jsonify(analysis_data), 200
285
-
286
-
287
- def calculate_haversine_distance(lat1, lon1, lat2, lon2):
288
- R = 6371 # Radius of Earth in kilometers
289
- try:
290
- lat1_rad, lon1_rad = math.radians(float(lat1)), math.radians(float(lon1))
291
- lat2_rad, lon2_rad = math.radians(float(lat2)), math.radians(float(lon2))
292
- except (ValueError, TypeError):
293
- print(f"Error converting lat/lon to float for Haversine: {lat1}, {lon1}, {lat2}, {lon2}")
294
- return 0.0 # Fallback
295
- dlon = lon2_rad - lon1_rad
296
- dlat = lat2_rad - lat1_rad
297
- a = math.sin(dlat / 2) ** 2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon / 2) ** 2
298
- c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
299
- distance = R * c
300
- return round(distance, 1)
301
-
302
-
303
- if __name__ == '__main__':
304
- if not GEMINI_API_KEY:
305
- print("CRITICAL ERROR: GEMINI_API_KEY is not set in the environment.")
306
- print("Please create a .env file with GEMINI_API_KEY=YOUR_API_KEY or set it as an environment variable.")
307
  app.run(debug=True, port=5000)
 
1
+ from flask import Flask, request, jsonify, render_template
2
+ import json
3
+ import requests
4
+ import random
5
+ import math
6
+ import os
7
+ from dotenv import load_dotenv
8
+
9
+ load_dotenv() # Load environment variables from .env file
10
+
11
+ app = Flask(__name__)
12
+
13
+ GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
14
+ NOMINATIM_USER_AGENT = "GeoSafeConstruct/1.0 ([email protected])" # IMPORTANT: Change to your app name and contact email
15
+
16
+
17
+ # --- Nominatim API Functions ---
18
+ def get_location_name_from_coords(lat, lon):
19
+ """Fetches a location name/address from coordinates using Nominatim."""
20
+ headers = {"User-Agent": NOMINATIM_USER_AGENT}
21
+ url = f"https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}&zoom=16&addressdetails=1"
22
+ try:
23
+ response = requests.get(url, headers=headers, timeout=10)
24
+ response.raise_for_status()
25
+ data = response.json()
26
+ # Construct a readable address, prioritizing certain fields
27
+ address = data.get('address', {})
28
+ parts = [
29
+ address.get('road'), address.get('neighbourhood'), address.get('suburb'),
30
+ address.get('city_district'), address.get('city'), address.get('town'),
31
+ address.get('village'), address.get('county'), address.get('state'),
32
+ address.get('postcode'), address.get('country')
33
+ ]
34
+ # Filter out None values and join
35
+ display_name = data.get('display_name', "Unknown Location")
36
+ filtered_parts = [part for part in parts if part]
37
+ if len(filtered_parts) > 3: # If many parts, use display_name for brevity
38
+ return display_name
39
+ elif filtered_parts:
40
+ return ", ".join(filtered_parts[:3]) # Take first few relevant parts
41
+ return display_name # Fallback to full display_name
42
+ except requests.exceptions.RequestException as e:
43
+ print(f"Nominatim reverse geocoding error: {e}")
44
+ return f"Area around {lat:.3f}, {lon:.3f}"
45
+ except (json.JSONDecodeError, KeyError) as e:
46
+ print(f"Nominatim response parsing error: {e}")
47
+ return f"Area around {lat:.3f}, {lon:.3f}"
48
+
49
+
50
+ def get_coords_from_location_name(query):
51
+ """Fetches coordinates from a location name/address using Nominatim."""
52
+ headers = {"User-Agent": NOMINATIM_USER_AGENT}
53
+ url = f"https://nominatim.openstreetmap.org/search?q={requests.utils.quote(query)}&format=json&limit=1"
54
+ try:
55
+ response = requests.get(url, headers=headers, timeout=10)
56
+ response.raise_for_status()
57
+ data = response.json()
58
+ if data and isinstance(data, list) and len(data) > 0:
59
+ return {
60
+ "latitude": float(data[0].get("lat")),
61
+ "longitude": float(data[0].get("lon")),
62
+ "display_name": data[0].get("display_name")
63
+ }
64
+ return None
65
+ except requests.exceptions.RequestException as e:
66
+ print(f"Nominatim geocoding error: {e}")
67
+ return None
68
+ except (json.JSONDecodeError, KeyError, IndexError) as e:
69
+ print(f"Nominatim geocoding response parsing error: {e}")
70
+ return None
71
+
72
+
73
+ # --- Gemini API Functions ---
74
+ def call_gemini_api(prompt_text):
75
+ """Generic function to call Gemini API."""
76
+ if not GEMINI_API_KEY:
77
+ print("GEMINI_API_KEY not found in environment variables.")
78
+ return None, "Gemini API key not configured."
79
+
80
+ url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent"
81
+ headers = {
82
+ "Content-Type": "application/json",
83
+ "x-goog-api-key": GEMINI_API_KEY
84
+ }
85
+ payload = {
86
+ "contents": [{"parts": [{"text": prompt_text}]}],
87
+ "generationConfig": {
88
+ "response_mime_type": "application/json",
89
+ "temperature": 0.6, # Adjust for creativity vs. factuality
90
+ "topP": 0.9,
91
+ "topK": 40
92
+ },
93
+ "safetySettings": [ # Add safety settings if needed
94
+ {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
95
+ {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
96
+ {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
97
+ {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}
98
+ ]
99
+ }
100
+
101
+ try:
102
+ response = requests.post(url, json=payload, headers=headers, timeout=60) # Increased timeout
103
+ response.raise_for_status()
104
+ gemini_response_data = response.json()
105
+
106
+ if 'candidates' not in gemini_response_data or not gemini_response_data['candidates']:
107
+ print("Gemini API Error: No candidates in response.")
108
+ print("Full Gemini Response:", gemini_response_data)
109
+ if 'promptFeedback' in gemini_response_data and 'blockReason' in gemini_response_data['promptFeedback']:
110
+ return None, f"Content blocked by API: {gemini_response_data['promptFeedback']['blockReason']}"
111
+ return None, "Gemini API returned no content."
112
+
113
+ json_text_content = gemini_response_data['candidates'][0]['content']['parts'][0]['text']
114
+
115
+ # Attempt to clean JSON string if it's wrapped in markdown
116
+ if json_text_content.strip().startswith("```json"):
117
+ json_text_content = json_text_content.strip()[7:]
118
+ if json_text_content.strip().endswith("```"):
119
+ json_text_content = json_text_content.strip()[:-3]
120
+
121
+ return json.loads(json_text_content.strip()), None
122
+ except requests.exceptions.Timeout:
123
+ print("Gemini API call timed out.")
124
+ return None, "Analysis request timed out. Please try again."
125
+ except requests.exceptions.RequestException as e:
126
+ print(f"Error calling Gemini API (RequestException): {e}")
127
+ return None, f"Could not connect to analysis service: {e}"
128
+ except json.JSONDecodeError as e:
129
+ print(f"Error decoding JSON from Gemini API: {e}")
130
+ print(f"Received text for JSON parsing: {json_text_content if 'json_text_content' in locals() else 'N/A'}")
131
+ return None, "AI returned an invalid response format. Please try again."
132
+ except (KeyError, IndexError) as e:
133
+ print(f"Error parsing Gemini API response structure: {e}")
134
+ print(f"Gemini response: {gemini_response_data if 'gemini_response_data' in locals() else 'N/A'}")
135
+ return None, "AI returned an unexpected response structure."
136
+ except Exception as e:
137
+ print(f"An unexpected error occurred during Gemini call: {e}")
138
+ return None, f"An unexpected error occurred: {e}"
139
+
140
+
141
+ def get_safety_analysis_with_gemini(latitude, longitude, location_address):
142
+ """
143
+ Calls Gemini API to analyze construction safety.
144
+ """
145
+ prompt = f"""
146
+ **Objective:** Provide a detailed and realistic construction site safety analysis for the location: {location_address} (Latitude: {latitude}, Longitude: {longitude}).
147
+
148
+ **Output Format:** STRICTLY JSON. Do NOT include any text outside the JSON structure (e.g., no '```json' or '```' wrappers).
149
+
150
+ **JSON Structure:**
151
+ {{
152
+ "location_name": "{location_address}", // Use the provided address
153
+ "safety_score": "integer (0-100, overall safety score, be critical and realistic)",
154
+ "suitability_statement": "string (e.g., 'Generally Suitable with Mitigations', 'High Caution Advised', 'Not Recommended without Major Intervention')",
155
+ "summary_assessment": "string (3-4 sentences summarizing key findings, suitability, and major concerns. Be specific.)",
156
+ "geological_risks": {{
157
+ "earthquake_risk": {{
158
+ "level": "string ('Low', 'Medium', 'High', 'Very High', 'Not Assessed')",
159
+ "details": "string (Specifics like proximity to faults, historical activity, soil liquefaction potential if known. If not assessed, state why.)"
160
+ }},
161
+ "landslide_risk": {{
162
+ "level": "string ('Low', 'Medium', 'High', 'Very High', 'Not Assessed')",
163
+ "details": "string (Slope stability, soil type, vegetation, historical incidents. If not assessed, state why.)"
164
+ }},
165
+ "soil_stability": {{
166
+ "type": "string ('Stable', 'Moderately Stable', 'Unstable', 'Variable', 'Requires Investigation')",
167
+ "concerns": ["string array (e.g., 'Expansive clays present', 'High water table', 'Poor drainage', 'Subsidence risk')"]
168
+ }}
169
+ }},
170
+ "hydrological_risks": {{
171
+ "flood_risk": {{
172
+ "level": "string ('Low', 'Medium', 'High', 'Very High', 'Not Assessed')",
173
+ "details": "string (Proximity to water bodies, floodplain maps, historical flooding, drainage issues. If not assessed, state why.)"
174
+ }},
175
+ "tsunami_risk": {{ // Only include if geographically relevant (coastal)
176
+ "level": "string ('Negligible', 'Low', 'Medium', 'High')",
177
+ "details": "string (Distance from coast, elevation, historical data.)"
178
+ }}
179
+ }},
180
+ "other_environmental_risks": [ // Array of objects, include if relevant
181
+ {{ "type": "Wildfire", "level": "string ('Low', 'Medium', 'High')", "details": "string (Vegetation, climate, fire history)" }},
182
+ {{ "type": "Extreme Weather (Hurricanes/Tornadoes/High Winds)", "level": "string ('Low', 'Medium', 'High')", "details": "string (Regional patterns, building code requirements)" }},
183
+ {{ "type": "Industrial Hazards", "level": "string ('Low', 'Medium', 'High')", "details": "string (Proximity to industrial plants, pipelines, hazardous material routes)" }}
184
+ ],
185
+ "key_risk_factors_summary": ["string array (Bulleted list of 3-5 most critical risk factors for this specific site)"],
186
+ "mitigation_recommendations": ["string array (Actionable, specific recommendations, e.g., 'Conduct Level 2 Geotechnical Survey focusing on shear strength', 'Implement earthquake-resistant design to Zone IV standards', 'Elevate foundation by 1.5m above base flood elevation')"],
187
+ "further_investigations_needed": ["string array (e.g., 'Detailed hydrological study', 'Environmental Impact Assessment', 'Traffic impact study')"],
188
+ "alternative_locations_analysis": [ // Up to 2-3 alternatives. If none are significantly better, state that.
189
+ {{
190
+ "name_suggestion": "string (Suggest a conceptual name like 'North Ridge Site' or 'Valley View Plot')",
191
+ "latitude": "float (as string, e.g., '18.5234')",
192
+ "longitude": "float (as string, e.g., '73.8567')",
193
+ "estimated_distance_km": "float (as string, e.g., '8.5')",
194
+ "brief_justification": "string (Why is this a potential alternative? E.g., 'Appears to be on more stable ground', 'Further from flood plain')",
195
+ "potential_pros": ["string array"],
196
+ "potential_cons": ["string array"],
197
+ "comparative_safety_score_estimate": "integer (0-100, relative to primary site)"
198
+ }}
199
+ ],
200
+ "data_confidence_level": "string ('Low', 'Medium', 'High' - based on assumed availability of public data for this general region, not specific site data which is unknown to you)",
201
+ "disclaimer": "This AI-generated analysis is for preliminary informational purposes only and not a substitute for professional engineering, geological, and environmental assessments. On-site investigations are crucial."
202
+ }}
203
+
204
+ **Guidelines for Content:**
205
+ - make sumre rnaodmness in score generation not same view ppijn t everytime
206
+ - Be realistic. If data for a specific risk is unlikely to be publicly available for a random coordinate, mark it 'Not Assessed' and explain.
207
+ - Focus on actionable insights.
208
+ - For alternative locations, provide *different* lat/lon coordinates that are plausibly nearby (within 5-20km).
209
+ - Ensure all string values are properly quoted. Ensure latitudes and longitudes for alternatives are strings representing floats.
210
+ - The safety_score should reflect a comprehensive evaluation of all risks.
211
+ - proper spacing and upo down margins shoudl be there
212
+ """
213
+ return call_gemini_api(prompt)
214
+
215
+
216
+ # --- Flask Routes ---
217
+ @app.route('/')
218
+ def index():
219
+ return render_template('index.html')
220
+
221
+
222
+ @app.route('/api/geocode', methods=['POST'])
223
+ def geocode_location():
224
+ data = request.json
225
+ query = data.get('query')
226
+ if not query:
227
+ return jsonify({"error": "Query parameter is required."}), 400
228
+
229
+ coords_data = get_coords_from_location_name(query)
230
+ if coords_data:
231
+ return jsonify(coords_data), 200
232
+ else:
233
+ return jsonify({"error": "Location not found or geocoding failed."}), 404
234
+
235
+
236
+ @app.route('/api/analyze_location', methods=['POST'])
237
+ def analyze_location_route():
238
+ data = request.json
239
+ try:
240
+ latitude = float(data.get('latitude'))
241
+ longitude = float(data.get('longitude'))
242
+ except (TypeError, ValueError):
243
+ return jsonify({"error": "Invalid latitude or longitude provided."}), 400
244
+
245
+ # Get a human-readable name for the primary location
246
+ primary_location_address = get_location_name_from_coords(latitude, longitude)
247
+
248
+ # Call Gemini for the main analysis
249
+ analysis_data, error = get_safety_analysis_with_gemini(latitude, longitude, primary_location_address)
250
+
251
+ if error:
252
+ # Fallback to a simpler mock-like structure if Gemini fails critically
253
+ return jsonify({
254
+ "error_message": error,
255
+ "location_name": primary_location_address,
256
+ "safety_score": random.randint(20, 50), # Low score to indicate issue
257
+ "summary_assessment": f"Could not perform detailed analysis due to: {error}. Basic location: {primary_location_address}.",
258
+ "suitability_statement": "Analysis Incomplete",
259
+ "geological_risks": {"earthquake_risk": {"level": "Not Assessed", "details": error}},
260
+ "hydrological_risks": {"flood_risk": {"level": "Not Assessed", "details": error}},
261
+ "disclaimer": "A technical issue prevented full analysis."
262
+ }), 500
263
+
264
+ # Post-process alternative locations to get their names
265
+ if analysis_data.get("alternative_locations_analysis"):
266
+ for alt_loc in analysis_data["alternative_locations_analysis"]:
267
+ try:
268
+ alt_lat = float(alt_loc.get("latitude"))
269
+ alt_lon = float(alt_loc.get("longitude"))
270
+ alt_loc["actual_name"] = get_location_name_from_coords(alt_lat, alt_lon)
271
+ # Ensure distance is calculated if not provided by Gemini or if it's nonsensical
272
+ if not alt_loc.get("estimated_distance_km") or float(alt_loc.get("estimated_distance_km", 0)) == 0:
273
+ alt_loc["estimated_distance_km"] = str(
274
+ calculate_haversine_distance(latitude, longitude, alt_lat, alt_lon))
275
+
276
+ except (TypeError, ValueError, KeyError) as e:
277
+ print(f"Error processing alternative location coords: {e}, data: {alt_loc}")
278
+ alt_loc["actual_name"] = "Nearby Area (details unavailable)"
279
+ # Fallback if lat/lon are missing or invalid for an alternative
280
+ if "latitude" not in alt_loc or "longitude" not in alt_loc:
281
+ alt_loc["latitude"] = str(latitude + (random.random() - 0.5) * 0.05) # Small offset
282
+ alt_loc["longitude"] = str(longitude + (random.random() - 0.5) * 0.05)
283
+
284
+ return jsonify(analysis_data), 200
285
+
286
+
287
+ def calculate_haversine_distance(lat1, lon1, lat2, lon2):
288
+ R = 6371 # Radius of Earth in kilometers
289
+ try:
290
+ lat1_rad, lon1_rad = math.radians(float(lat1)), math.radians(float(lon1))
291
+ lat2_rad, lon2_rad = math.radians(float(lat2)), math.radians(float(lon2))
292
+ except (ValueError, TypeError):
293
+ print(f"Error converting lat/lon to float for Haversine: {lat1}, {lon1}, {lat2}, {lon2}")
294
+ return 0.0 # Fallback
295
+ dlon = lon2_rad - lon1_rad
296
+ dlat = lat2_rad - lat1_rad
297
+ a = math.sin(dlat / 2) ** 2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon / 2) ** 2
298
+ c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
299
+ distance = R * c
300
+ return round(distance, 1)
301
+
302
+
303
+ if __name__ == '__main__':
304
+ if not GEMINI_API_KEY:
305
+ print("CRITICAL ERROR: GEMINI_API_KEY is not set in the environment.")
306
+ print("Please create a .env file with GEMINI_API_KEY=YOUR_API_KEY or set it as an environment variable.")
307
  app.run(debug=True, port=5000)