Rekham1110 commited on
Commit
609a40a
·
verified ·
1 Parent(s): 1101cd0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +280 -149
app.py CHANGED
@@ -1,165 +1,296 @@
1
- import streamlit as st
2
- import requests
3
- import base64
4
- import json
5
- from datetime import datetime
6
- import boto3
7
  from PIL import Image
8
- import io
9
  import os
10
  from dotenv import load_dotenv
11
  from simple_salesforce import Salesforce
 
 
 
 
 
12
 
13
  # Load environment variables
14
  load_dotenv()
 
 
 
15
 
16
- # Configuration
17
- AI_MODEL_ENDPOINT = os.getenv("AI_MODEL_ENDPOINT", "https://your-huggingface-or-lambda-endpoint/predict-milestone")
18
- S3_BUCKET = os.getenv("S3_BUCKET", "your-s3-bucket")
19
- AWS_REGION = os.getenv("AWS_REGION", "us-east-1")
20
- MAX_FILE_SIZE = 20 * 1024 * 1024 # 20MB in bytes
21
- MIN_IMAGE_DIMENSIONS = (800, 600) # Minimum width and height for clarity
22
 
23
- # Initialize AWS S3 client
24
- try:
25
- s3_client = boto3.client(
26
- "s3",
27
- region_name=AWS_REGION,
28
- aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"),
29
- aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY")
30
- )
31
- except Exception as e:
32
- st.error(f"Failed to initialize S3 client: {str(e)}")
33
- st.stop()
34
-
35
- # Initialize Salesforce client
36
  try:
37
  sf = Salesforce(
38
- username=os.getenv("SF_USERNAME"),
39
- password=os.getenv("SF_PASSWORD"),
40
- security_token=os.getenv("SF_SECURITY_TOKEN"),
41
- domain="login" # Use "test" for sandbox
42
  )
43
  except Exception as e:
44
- st.error(f"Failed to connect to Salesforce: {str(e)}")
45
- st.stop()
46
-
47
- # Streamlit app
48
- st.title("Construction Progress Verifier")
49
- st.write("Upload one or more construction site photos to track project milestones.")
50
-
51
- # Input for Salesforce Construction__c Record ID
52
- project_id = st.text_input("Enter Salesforce Construction ID (Construction__c Record ID)", "")
53
-
54
- # File uploader for multiple images
55
- uploaded_files = st.file_uploader("Choose images (JPG/PNG, <20MB each)", type=["jpg", "png"], accept_multiple_files=True)
56
-
57
- if uploaded_files and project_id:
58
- # Initialize results list for output
59
- results = []
60
- progress_bar = st.progress(0)
61
- status_text = st.empty()
62
- total_images = len(uploaded_files)
63
- progress_increment = 100 // (total_images * 3) # Divide progress into upload, AI, and Salesforce steps
64
-
65
- for idx, uploaded_file in enumerate(uploaded_files, 1):
66
- # Validate file size
67
- if uploaded_file.size > MAX_FILE_SIZE:
68
- results.append({
69
- "filename": uploaded_file.name,
70
- "status": "Failed",
71
- "message": "File size exceeds 20MB. Please upload a smaller image."
72
- })
73
- continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
  try:
76
- # Read and validate image
77
- status_text.text(f"Processing image {idx}/{total_images}: Validating {uploaded_file.name}...")
78
- progress_bar.progress(min((idx - 1) * progress_increment * 3 + progress_increment, 100))
79
- image = Image.open(uploaded_file)
80
- if image.size < MIN_IMAGE_DIMENSIONS:
81
- results.append({
82
- "filename": uploaded_file.name,
83
- "status": "Failed",
84
- "message": "Image resolution too low. Please upload a higher resolution image (min 800x600)."
85
- })
86
- continue
87
-
88
- # Convert image to base64
89
- buffered = io.BytesIO()
90
- image.save(buffered, format="JPEG")
91
- img_str = base64.b64encode(buffered.getvalue()).decode()
92
-
93
- # Upload image to S3
94
- status_text.text(f"Processing image {idx}/{total_images}: Uploading {uploaded_file.name} to storage...")
95
- s3_key = f"construction_images/{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uploaded_file.name}"
96
- s3_client.put_object(Bucket=S3_BUCKET, Key=s3_key, Body=buffered.getvalue())
97
- s3_url = f"https://{S3_BUCKET}.s3.{AWS_REGION}.amazonaws.com/{s3_key}"
98
- progress_bar.progress(min((idx - 1) * progress_increment * 3 + 2 * progress_increment, 100))
99
-
100
- # Call AI model endpoint
101
- status_text.text(f"Processing image {idx}/{total_images}: Analyzing {uploaded_file.name} with AI model...")
102
- payload = {"image": img_str}
103
- ai_response = requests.post(
104
- AI_MODEL_ENDPOINT,
105
- json=payload,
106
- headers={"Authorization": f"Bearer {os.getenv('AI_API_KEY')}"},
107
- timeout=5
108
- )
109
-
110
- if ai_response.status_code == 200:
111
- ai_result = ai_response.json()
112
- milestone = ai_result.get("milestone", "Unknown")
113
-
114
- # Update Salesforce record using simple-salesforce
115
- status_text.text(f"Processing image {idx}/{total_images}: Updating Salesforce for {uploaded_file.name}...")
116
- sf.Construction__c.update(project_id, {
117
- "Current_Milestone__c": milestone,
118
- "Last_Updated_Image__c": s3_url,
119
- "Upload_Status__c": "Success"
120
- })
121
-
122
- results.append({
123
- "filename": uploaded_file.name,
124
- "status": "Success",
125
- "milestone": milestone,
126
- "s3_url": s3_url,
127
- "message": "Salesforce record updated successfully."
128
- })
129
- progress_bar.progress(min(idx * progress_increment * 3, 100))
130
- else:
131
- results.append({
132
- "filename": uploaded_file.name,
133
- "status": "Failed",
134
- "message": f"AI model error: {ai_response.text}. Please retake the photo or try again."
135
- })
136
- progress_bar.progress(min(idx * progress_increment * 3, 100))
137
  except Exception as e:
138
- results.append({
139
- "filename": uploaded_file.name,
140
- "status": "Failed",
141
- "message": f"Error processing image: {str(e)}. Please retake the photo or contact support."
142
- })
143
- progress_bar.progress(min(idx * progress_increment * 3, 100))
144
-
145
- # Display results
146
- st.markdown("### Processing Results")
147
- for result in results:
148
- if result["status"] == "Success":
149
- st.success(
150
- f"**{result['filename']}**: "
151
- f"Milestone: {result['milestone']}, "
152
- f"S3 URL: {result['s3_url']}, "
153
- f"Message: {result['message']}"
154
- )
155
- else:
156
- st.error(f"**{result['filename']}**: {result['message']}")
157
-
158
- elif uploaded_files and not project_id:
159
- st.warning("Please enter a valid Salesforce Construction ID.")
160
- elif project_id and not uploaded_files:
161
- st.warning("Please upload at least one image.")
162
-
163
- # Footer
164
- st.markdown("---")
165
- st.write("Developed by Sathkrutha Tech Solutions | © 2025")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
 
 
 
 
 
2
  from PIL import Image
 
3
  import os
4
  from dotenv import load_dotenv
5
  from simple_salesforce import Salesforce
6
+ from datetime import datetime
7
+ import hashlib
8
+ import shutil
9
+ import base64
10
+ import pytz
11
 
12
  # Load environment variables
13
  load_dotenv()
14
+ SF_USERNAME = os.getenv("SF_USERNAME")
15
+ SF_PASSWORD = os.getenv("SF_PASSWORD")
16
+ SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN")
17
 
18
+ # Validate Salesforce credentials
19
+ if not all([SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN]):
20
+ raise ValueError("Missing Salesforce credentials. Set SF_USERNAME, SF_PASSWORD, and SF_SECURITY_TOKEN in environment variables.")
 
 
 
21
 
22
+ # Initialize Salesforce connection
 
 
 
 
 
 
 
 
 
 
 
 
23
  try:
24
  sf = Salesforce(
25
+ username=SF_USERNAME,
26
+ password=SF_PASSWORD,
27
+ security_token=SF_SECURITY_TOKEN,
28
+ domain='login'
29
  )
30
  except Exception as e:
31
+ print(f"Salesforce connection failed: {str(e)}")
32
+ raise
33
+
34
+ # Valid milestones
35
+ VALID_MILESTONES = ["Planning", "Foundation", "Walls Erected", "Completed"]
36
+
37
+ # Adjust the timezone to your local timezone
38
+ local_timezone = pytz.timezone("Asia/Kolkata")
39
+
40
+ # Image processing and Salesforce upload
41
+ def process_image(images, project_name):
42
+ try:
43
+ if not images or len(images) == 0:
44
+ return "Error: Please upload at least one image to proceed.", "Pending", "", "", 0
45
+
46
+ # Process each image
47
+ milestones = []
48
+ for image in images:
49
+ img = Image.open(image)
50
+ image_size_mb = os.path.getsize(image) / (1024 * 1024)
51
+ if image_size_mb > 20:
52
+ return "Error: One or more images exceed 20MB.", "Failure", "", "", 0
53
+ if not str(image).lower().endswith(('.jpg', '.jpeg', '.png')):
54
+ return "Error: Only JPG/PNG images are supported.", "Failure", "", "", 0
55
+
56
+ # Save image to public folder temporarily before uploading to Salesforce
57
+ upload_dir = "public_uploads"
58
+ os.makedirs(upload_dir, exist_ok=True)
59
+ unique_id = datetime.now().strftime("%Y%m%d%H%M%S")
60
+ image_filename = f"{unique_id}_{os.path.basename(image)}"
61
+ saved_image_path = os.path.join(upload_dir, image_filename)
62
+ shutil.copy(image, saved_image_path)
63
+
64
+ # Convert image to base64 before uploading to Salesforce
65
+ with open(saved_image_path, 'rb') as image_file:
66
+ image_data = base64.b64encode(image_file.read()).decode('utf-8')
67
+
68
+ # Create the ContentVersion record in Salesforce
69
+ content_version = {
70
+ 'Title': image_filename,
71
+ 'PathOnClient': saved_image_path,
72
+ 'VersionData': image_data
73
+ }
74
+
75
+ # Upload the file to Salesforce
76
+ try:
77
+ content_version_result = sf.ContentVersion.create(content_version)
78
+ content_version_id = content_version_result['id']
79
+ file_url = f"https://sathkruthatechsolutionspri8-dev-ed.develop.lightning.force.com/{content_version_id}"
80
+ except Exception as e:
81
+ return f"Error: Failed to upload image to Salesforce - {str(e)}", "Failure", "", "", 0
82
+
83
+ # Use file name to detect milestones as a fallback (simple keyword-based detection)
84
+ milestone = "Planning" # Default if no match is found
85
+ image_filename_lower = image_filename.lower()
86
+
87
+ if any(keyword in image_filename_lower for keyword in ["foundation", "excavation", "digging"]):
88
+ milestone = "Foundation"
89
+ elif any(keyword in image_filename_lower for keyword in ["walls", "framing", "structure"]):
90
+ milestone = "Walls Erected"
91
+ elif any(keyword in image_filename_lower for keyword in ["completed", "final", "finished"]):
92
+ milestone = "Completed"
93
+
94
+ milestones.append(milestone)
95
+
96
+ # Determine overall milestone (most advanced detected)
97
+ final_milestone = max(set(milestones), key=milestones.count) if milestones else "Planning"
98
+ milestone_completion_map = {
99
+ "Planning": 10,
100
+ "Foundation": 30,
101
+ "Walls Erected": 50,
102
+ "Completed": 100,
103
+ }
104
+ percent_complete = milestone_completion_map.get(final_milestone, 0)
105
+
106
+ completion_details = {
107
+ "Planning": {
108
+ "completed": [
109
+ "Initial project outline and objectives have been established.",
110
+ "Preliminary designs and architectural plans are drafted.",
111
+ "Stakeholder meetings and initial approvals are completed."
112
+ ],
113
+ "not_completed": [
114
+ "Detailed construction plans and blueprints are pending finalization.",
115
+ "Permits and regulatory approvals are yet to be obtained.",
116
+ "Contractor selection and procurement processes are not yet complete."
117
+ ]
118
+ },
119
+ "Foundation": {
120
+ "completed": [
121
+ "Site preparation, including clearing and leveling, is finished.",
122
+ "Excavation for the foundation has been completed.",
123
+ "Concrete pouring for the foundation, including footings and slabs, is done.",
124
+ "Initial structural inspections for the foundation have been passed."
125
+ ],
126
+ "not_completed": [
127
+ "Plumbing and electrical groundwork installations are pending.",
128
+ "Backfilling and site grading around the foundation are not yet done.",
129
+ "Above-ground structural work, such as columns and walls, has not started."
130
+ ]
131
+ },
132
+ "Walls Erected": {
133
+ "completed": [
134
+ "The concrete framework, including columns and beams, is in place.",
135
+ "All structural walls have been erected and stabilized.",
136
+ "Temporary scaffolding and safety measures are installed for ongoing work.",
137
+ "Initial inspections for structural integrity have been completed."
138
+ ],
139
+ "not_completed": [
140
+ "Roofing installation and weatherproofing are pending.",
141
+ "Windows, doors, and exterior cladding are not yet installed.",
142
+ "Interior walls, electrical, and plumbing systems are still to be implemented."
143
+ ]
144
+ },
145
+ "Completed": {
146
+ "completed": [
147
+ "The concrete framework, including columns, beams, and floor slabs, is fully constructed.",
148
+ "Exterior walls, windows, and cladding are installed, completing the building's facade.",
149
+ "Interior work, including electrical, plumbing, and HVAC systems, is fully implemented.",
150
+ "Finishing touches, such as flooring, painting, and fixtures, are completed.",
151
+ "All phases of the project are finished, including final inspections and approvals."
152
+ ],
153
+ "not_completed": [
154
+ "There should be no more pending work as the project is fully completed."
155
+ ]
156
+ }
157
+ }
158
+
159
+ completed_tasks = completion_details[final_milestone]["completed"]
160
+ not_completed_tasks = completion_details[final_milestone]["not_completed"]
161
+
162
+ completed_html = "".join([f'<li style="color: green;">✔ {task}</li>' for task in completed_tasks])
163
+ not_completed_html = "".join([f'<li style="color: red;">✘ {task}</li>' for task in not_completed_tasks])
164
+
165
+ result_html = f"""
166
+ <div style="font-family: Arial, sans-serif; padding: 20px; background-color: #f9f9f9; border-radius: 10px;">
167
+ <h2 style="color: #2c3e50; text-align: center;">Project Summary</h2>
168
+ <div style="display: flex; justify-content: space-around; margin-bottom: 20px;">
169
+ <div style="text-align: center;">
170
+ <h3 style="color: #34495e;">Detected Milestone</h3>
171
+ <p style="font-size: 18px; font-weight: bold;">{final_milestone}</p>
172
+ </div>
173
+ <div style="text-align: center;">
174
+ <h3 style="color: #34495e;">Completion</h3>
175
+ <progress value="{percent_complete}" max="100" style="width: 200px; height: 20px;"></progress>
176
+ <p>{percent_complete}%</p>
177
+ </div>
178
+ </div>
179
+
180
+ <h3 style="color: #2c3e50;">Milestone Timeline</h3>
181
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
182
+ <span style="color: {'#2ecc71' if final_milestone == 'Planning' else '#bdc3c7'};">Planning</span>
183
+ <span style="color: {'#2ecc71' if final_milestone == 'Foundation' else '#bdc3c7'};">Foundation</span>
184
+ <span style="color: {'#2ecc71' if final_milestone == 'Walls Erected' else '#bdc3c7'};">Walls Erected</span>
185
+ <span style="color: {'#2ecc71' if final_milestone == 'Completed' else '#bdc3c7'};">Completed</span>
186
+ </div>
187
+
188
+ <details style="margin-bottom: 20px;">
189
+ <summary style="color: #2c3e50; font-weight: bold;">Completed Tasks</summary>
190
+ <ul style="padding-left: 20px;">
191
+ {completed_html}
192
+ </ul>
193
+ </details>
194
+
195
+ <details style="margin-bottom: 20px;">
196
+ <summary style="color: #2c3e50; font-weight: bold;">Not Completed Tasks</summary>
197
+ <ul style="padding-left: 20px;">
198
+ {not_completed_html}
199
+ </ul>
200
+ </details>
201
+ </div>
202
+ """
203
+
204
+ now = datetime.now(local_timezone)
205
+ local_time = now.strftime("%Y-%m-%dT%H:%M:%S") + now.strftime("%z")[:-2] + ":" + now.strftime("%z")[-2:]
206
+
207
+ record = {
208
+ "Name__c": project_name,
209
+ "Current_Milestone__c": final_milestone,
210
+ "Completion_Percentage__c": percent_complete,
211
+ "Last_Updated_On__c": local_time,
212
+ "Upload_Status__c": "Success",
213
+ "Comments__c": f"{final_milestone}",
214
+ "Last_Updated_Image__c": file_url
215
+ }
216
 
217
  try:
218
+ sf.Construction__c.create(record)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  except Exception as e:
220
+ return f"Error: Failed to update Salesforce - {str(e)}", "Failure", "", "", 0
221
+
222
+ return result_html, "Success", final_milestone, f"{percent_complete}%"
223
+
224
+ except Exception as e:
225
+ return f"Error: {str(e)}", "Failure", "", "", "0%"
226
+
227
+ # Gradio UI
228
+ with gr.Blocks(css="""
229
+ .gradio-container {
230
+ background-color: #f0f4f8;
231
+ font-family: Arial, sans-serif;
232
+ }
233
+ .title {
234
+ color: #2c3e50;
235
+ font-size: 24px;
236
+ text-align: center;
237
+ font-weight: bold;
238
+ }
239
+ .gradio-row {
240
+ text-align: center;
241
+ }
242
+ .gradio-container .output {
243
+ text-align: center;
244
+ }
245
+ .gradio-container .input {
246
+ text-align: center;
247
+ }
248
+ .gradio-container .button {
249
+ display: block;
250
+ margin: 0 auto;
251
+ background-color: #3498db;
252
+ color: white;
253
+ border: none;
254
+ padding: 10px 20px;
255
+ border-radius: 5px;
256
+ cursor: pointer;
257
+ }
258
+ .gradio-container .button:hover {
259
+ background-color: #2980b9;
260
+ }
261
+ progress::-webkit-progress-value {
262
+ background-color: #2ecc71;
263
+ border-radius: 5px;
264
+ }
265
+ progress::-webkit-progress-bar {
266
+ background-color: #ecf0f1;
267
+ border-radius: 5px;
268
+ }
269
+ details summary {
270
+ cursor: pointer;
271
+ padding: 10px;
272
+ background-color: #ecf0f1;
273
+ border-radius: 5px;
274
+ }
275
+ details ul {
276
+ margin-top: 10px;
277
+ }
278
+ """) as demo:
279
+ gr.Markdown("<h1 class='title'>Construction Progress Analyzer</h1>")
280
+ with gr.Row():
281
+ image_input = gr.Files(type="filepath", label="Upload Construction Site Photos (JPG/PNG, ≤ 20MB)")
282
+ project_name_input = gr.Textbox(label="Project Name (Required)", placeholder="e.g. Project_12345")
283
+
284
+ submit_button = gr.Button("Process Image")
285
+ output_html = gr.HTML(label="Result")
286
+ upload_status = gr.Textbox(label="Upload Status")
287
+ milestone = gr.Textbox(label="Detected Milestone")
288
+ progress = gr.Textbox(label="Completion Percentage", interactive=False)
289
+
290
+ submit_button.click(
291
+ fn=process_image,
292
+ inputs=[image_input, project_name_input],
293
+ outputs=[output_html, upload_status, milestone, progress]
294
+ )
295
+
296
+ demo.launch(share=True)