|
import gradio as gr |
|
from PIL import Image |
|
import os |
|
from dotenv import load_dotenv |
|
from simple_salesforce import Salesforce |
|
from datetime import datetime |
|
import hashlib |
|
import shutil |
|
import base64 |
|
import pytz |
|
|
|
|
|
load_dotenv() |
|
SF_USERNAME = os.getenv("SF_USERNAME") |
|
SF_PASSWORD = os.getenv("SF_PASSWORD") |
|
SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN") |
|
|
|
|
|
if not all([SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN]): |
|
raise ValueError("Missing Salesforce credentials. Set SF_USERNAME, SF_PASSWORD, and SF_SECURITY_TOKEN in environment variables.") |
|
|
|
|
|
try: |
|
sf = Salesforce( |
|
username=SF_USERNAME, |
|
password=SF_PASSWORD, |
|
security_token=SF_SECURITY_TOKEN, |
|
domain='login' |
|
) |
|
except Exception as e: |
|
print(f"Salesforce connection failed: {str(e)}") |
|
raise |
|
|
|
|
|
VALID_MILESTONES = ["Planning", "Foundation", "Walls Erected", "Completed"] |
|
MILESTONE_WEIGHTS = { |
|
"Planning": 1, |
|
"Foundation": 2, |
|
"Walls Erected": 3, |
|
"Completed": 4 |
|
} |
|
|
|
|
|
local_timezone = pytz.timezone("Asia/Kolkata") |
|
|
|
|
|
def process_image(images, project_name): |
|
try: |
|
if not images or len(images) == 0: |
|
return "Error: Please upload at least one image to proceed.", "Pending", "", "", 0 |
|
if len(images) < 2: |
|
return "Error: Please upload at least one indoor and one outdoor image for accurate milestone detection.", "Pending", "", "", 0 |
|
|
|
|
|
image_milestones = [] |
|
image_types = [] |
|
|
|
for image in images: |
|
img = Image.open(image) |
|
image_size_mb = os.path.getsize(image) / (1024 * 1024) |
|
if image_size_mb > 20: |
|
return "Error: One or more images exceed 20MB.", "Failure", "", "", 0 |
|
if not str(image).lower().endswith(('.jpg', '.jpeg', '.png')): |
|
return "Error: Only JPG/PNG images are supported.", "Failure", "", "", 0 |
|
|
|
|
|
upload_dir = "public_uploads" |
|
os.makedirs(upload_dir, exist_ok=True) |
|
unique_id = datetime.now().strftime("%Y%m%d%H%M%S") |
|
image_filename = f"{unique_id}_{os.path.basename(image)}" |
|
saved_image_path = os.path.join(upload_dir, image_filename) |
|
shutil.copy(image, saved_image_path) |
|
|
|
|
|
with open(saved_image_path, 'rb') as image_file: |
|
image_data = base64.b64encode(image_file.read()).decode('utf-8') |
|
|
|
|
|
content_version = { |
|
'Title': image_filename, |
|
'PathOnClient': saved_image_path, |
|
'VersionData': image_data |
|
} |
|
|
|
|
|
try: |
|
content_version_result = sf.ContentVersion.create(content_version) |
|
content_version_id = content_version_result['id'] |
|
file_url = f"https://sathkruthatechsolutionspri8-dev-ed.develop.lightning.force.com/{content_version_id}" |
|
except Exception as e: |
|
return f"Error: Failed to upload image to Salesforce - {str(e)}", "Failure", "", "", 0 |
|
|
|
|
|
filename_lower = os.path.basename(image).lower() |
|
is_indoor = any(keyword in filename_lower for keyword in ["indoor", "interior", "inside"]) |
|
image_type = "Indoor" if is_indoor else "Outdoor" |
|
image_types.append(image_type) |
|
|
|
|
|
|
|
milestone = "Planning" |
|
if any(keyword in filename_lower for keyword in ["site", "clearing", "planning", "design"]): |
|
milestone = "Planning" |
|
elif any(keyword in filename_lower for keyword in ["foundation", "footing", "slab", "excavation"]): |
|
milestone = "Foundation" |
|
elif any(keyword in filename_lower for keyword in ["wall", "structure", "beam", "column", "facade"]): |
|
milestone = "Walls Erected" |
|
elif any(keyword in filename_lower for keyword in ["electrical", "plumbing", "hvac", "finish", "completed"]): |
|
milestone = "Completed" |
|
|
|
|
|
if image_type == "Indoor" and milestone in ["Planning", "Foundation"]: |
|
milestone = "Walls Erected" |
|
elif image_type == "Outdoor" and milestone == "Completed": |
|
milestone = "Walls Erected" |
|
|
|
image_milestones.append(milestone) |
|
|
|
|
|
if not any(t == "Indoor" for t in image_types) or not any(t == "Outdoor" for t in image_types): |
|
return "Error: Both indoor and outdoor images are required for accurate milestone detection.", "Pending", "", "", 0 |
|
|
|
|
|
max_milestone_index = max(MILESTONE_WEIGHTS[m] for m in image_milestones) |
|
final_milestone = [m for m, w in MILESTONE_WEIGHTS.items() if w == max_milestone_index][0] |
|
|
|
|
|
if final_milestone == "Completed" and not any(m == "Completed" and t == "Indoor" for m, t in zip(image_milestones, image_types)): |
|
final_milestone = "Walls Erected" |
|
|
|
milestone_completion_map = { |
|
"Planning": 10, |
|
"Foundation": 30, |
|
"Walls Erected": 50, |
|
"Completed": 100, |
|
} |
|
percent_complete = milestone_completion_map.get(final_milestone, 0) |
|
|
|
completion_details = { |
|
"Planning": { |
|
"completed": [ |
|
"Initial project outline and objectives have been established.", |
|
"Preliminary designs and architectural plans are drafted.", |
|
"Stakeholder meetings and initial approvals are completed." |
|
], |
|
"not_completed": [ |
|
"Detailed construction plans and blueprints are pending finalization.", |
|
"Permits and regulatory approvals are yet to be obtained.", |
|
"Contractor selection and procurement processes are not yet complete." |
|
] |
|
}, |
|
"Foundation": { |
|
"completed": [ |
|
"Site preparation, including clearing and leveling, is finished.", |
|
"Excavation for the foundation has been completed.", |
|
"Concrete pouring for the foundation, including footings and slabs, is done.", |
|
"Initial structural inspections for the foundation have been passed." |
|
], |
|
"not_completed": [ |
|
"Plumbing and electrical groundwork installations are pending.", |
|
"Backfilling and site grading around the foundation are not yet done.", |
|
"Above-ground structural work, such as columns and walls, has not started." |
|
] |
|
}, |
|
"Walls Erected": { |
|
"completed": [ |
|
"The concrete framework, including columns and beams, is in place.", |
|
"All structural walls have been erected and stabilized.", |
|
"Temporary scaffolding and safety measures are installed for ongoing work.", |
|
"Initial inspections for structural integrity have been completed." |
|
], |
|
"not_completed": [ |
|
"Roofing installation and weatherproofing are pending.", |
|
"Windows, doors, and exterior cladding are not yet installed.", |
|
"Interior walls, electrical, and plumbing systems are still to be implemented." |
|
] |
|
}, |
|
"Completed": { |
|
"completed": [ |
|
"The concrete framework, including columns, beams, and floor slabs, is fully constructed.", |
|
"Exterior walls, windows, and cladding are installed, completing the building's facade.", |
|
"Interior work, including electrical, plumbing, and HVAC systems, is fully implemented.", |
|
"Finishing touches, such as flooring, painting, and fixtures, are completed.", |
|
"All phases of the project are finished, including final inspections and approvals." |
|
], |
|
"not_completed": [ |
|
"There should be no more pending work as the project is fully completed." |
|
] |
|
} |
|
} |
|
|
|
completed_tasks = completion_details[final_milestone]["completed"] |
|
not_completed_tasks = completion_details[final_milestone]["not_completed"] |
|
|
|
completed_html = "".join([f'<li style="color: green;">✔ {task}</li>' for task in completed_tasks]) |
|
not_completed_html = "".join([f'<li style="color: red;">✘ {task}</li>' for task in not_completed_tasks]) |
|
|
|
|
|
image_summary = "".join([f'<li>{os.path.basename(img)} ({img_type}): {milestone}</li>' for img, img_type, milestone in zip(images, image_types, image_milestones)]) |
|
|
|
result_html = f""" |
|
<div style="font-family: Arial, sans-serif; padding: 20px; background-color: #f9f9f9; border-radius: 10px;"> |
|
<h2 style="color: #2c3e50; text-align: center;">Project Summary</h2> |
|
<div style="display: flex; justify-content: space-around; margin-bottom: 20px;"> |
|
<div style="text-align: center;"> |
|
<h3 style="color: #34495e;">Detected Milestone</h3> |
|
<p style="font-size: 18px; font-weight: bold;">{final_milestone}</p> |
|
</div> |
|
<div style="text-align: center;"> |
|
<h3 style="color: #34495e;">Completion</h3> |
|
<progress value="{percent_complete}" max="100" style="width: 200px; height: 20px;"></progress> |
|
<p>{percent_complete}%</p> |
|
</div> |
|
</div> |
|
|
|
<h3 style="color: #2c3e50;">Image Analysis</h3> |
|
<ul style="padding-left: 20px; margin-bottom: 20px;"> |
|
{image_summary} |
|
</ul> |
|
|
|
<h3 style="color: #2c3e50;">Milestone Timeline</h3> |
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> |
|
<span style="color: {'#2ecc71' if final_milestone == 'Planning' else '#bdc3c7'};">Planning</span> |
|
<span style="color: {'#2ecc71' if final_milestone == 'Foundation' else '#bdc3c7'};">Foundation</span> |
|
<span style="color: {'#2ecc71' if final_milestone == 'Walls Erected' else '#bdc3c7'};">Walls Erected</span> |
|
<span style="color: {'#2ecc71' if final_milestone == 'Completed' else '#bdc3c7'};">Completed</span> |
|
</div> |
|
|
|
<details style="margin-bottom: 20px;"> |
|
<summary style="color: #2c3e50; font-weight: bold;">Completed Tasks</summary> |
|
<ul style="padding-left: 20px;"> |
|
{completed_html} |
|
</ul> |
|
</details> |
|
|
|
<details style="margin-bottom: 20px;"> |
|
<summary style="color: #2c3e50; font-weight: bold;">Not Completed Tasks</summary> |
|
<ul style="padding-left: 20px;"> |
|
{not_completed_html} |
|
</ul> |
|
</details> |
|
</div> |
|
""" |
|
|
|
now = datetime.now(local_timezone) |
|
local_time = now.strftime("%Y-%m-%dT%H:%M:%S") + now.strftime("%z")[:-2] + ":" + now.strftime("%z")[-2:] |
|
|
|
record = { |
|
"Name__c": project_name, |
|
"Current_Milestone__c": final_milestone, |
|
"Completion_Percentage__c": percent_complete, |
|
"Last_Updated_On__c": local_time, |
|
"Upload_Status__c": "Success", |
|
"Comments__c": f"{final_milestone}", |
|
"Last_Updated_Image__c": file_url |
|
} |
|
|
|
try: |
|
sf.Construction__c.create(record) |
|
except Exception as e: |
|
return f"Error: Failed to update Salesforce - {str(e)}", "Failure", "", "", 0 |
|
|
|
return result_html, "Success", final_milestone, f"{percent_complete}%" |
|
|
|
except Exception as e: |
|
return f"Error: {str(e)}", "Failure", "", "", "0%" |
|
|
|
|
|
with gr.Blocks(css=""" |
|
.gradio-container { |
|
background-color: #f0f4f8; |
|
font-family: Arial, sans-serif; |
|
} |
|
.title { |
|
color: #2c3e50; |
|
font-size: 24px; |
|
text-align: center; |
|
font-weight: bold; |
|
} |
|
.gradio-row { |
|
text-align: center; |
|
} |
|
.gradio-container .output { |
|
text-align: center; |
|
} |
|
.gradio-container .input { |
|
text-align: center; |
|
} |
|
.gradio-container .button { |
|
display: block; |
|
margin: 0 auto; |
|
background-color: #3498db; |
|
color: white; |
|
border: none; |
|
padding: 10px 20px; |
|
border-radius: 5px; |
|
cursor: pointer; |
|
} |
|
.gradio-container .button:hover { |
|
background-color: #2980b9; |
|
} |
|
progress::-webkit-progress-value { |
|
background-color: #2ecc71; |
|
border-radius: 5px; |
|
} |
|
.gradio-container progress::-webkit-progress-bar { |
|
background-color: #ecf0f1; |
|
border-radius: 5px; |
|
} |
|
details summary { |
|
cursor: pointer; |
|
padding: 10px; |
|
background-color: #ecf0f1; |
|
border-radius: 5px; |
|
} |
|
details ul { |
|
margin-top: 10px; |
|
} |
|
""") as demo: |
|
gr.Markdown("<h1 class='title'>Construction Progress Analyzer</h1>") |
|
gr.Markdown(""" |
|
<p style='text-align: center; color: #34495e;'> |
|
Upload at least one indoor and one outdoor image of the construction site (JPG/PNG, ≤ 20MB each).<br> |
|
Use descriptive filenames (e.g., 'outdoor_foundation.jpg', 'indoor_electrical.jpg') for best results. |
|
</p> |
|
""") |
|
with gr.Row(): |
|
image_input = gr.Files(type="filepath", label="Upload Construction Site Photos (JPG/PNG, ≤ 20MB)") |
|
project_name_input = gr.Textbox(label="Project Name (Required)", placeholder="e.g. Project_12345") |
|
|
|
submit_button = gr.Button("Process Image") |
|
output_html = gr.HTML(label="Result") |
|
upload_status = gr.Textbox(label="Upload Status") |
|
milestone = gr.Textbox(label="Detected Milestone") |
|
progress = gr.Textbox(label="Completion Percentage", interactive=False) |
|
|
|
submit_button.click( |
|
fn=process_image, |
|
inputs=[image_input, project_name_input], |
|
outputs=[output_html, upload_status, milestone, progress] |
|
) |
|
|
|
demo.launch(share=True) |