import gradio as gr from PIL import Image import os from dotenv import load_dotenv from simple_salesforce import Salesforce from datetime import datetime from fastapi import FastAPI, HTTPException, Security, Depends from fastapi.security import APIKeyHeader import base64 import io import random # For mock predictions # Load environment variables load_dotenv() SF_USERNAME = os.getenv("SF_USERNAME") SF_PASSWORD = os.getenv("SF_PASSWORD") SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN") SF_CONSUMER_KEY = os.getenv("SF_CONSUMER_KEY") SF_CONSUMER_SECRET = os.getenv("SF_CONSUMER_SECRET") API_KEY = os.getenv("API_KEY", "your-api-key-here") # Validate Salesforce credentials if not all([SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN, SF_CONSUMER_KEY, SF_CONSUMER_SECRET]): raise ValueError("Missing Salesforce credentials. Set SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN, SF_CONSUMER_KEY, and SF_CONSUMER_SECRET in environment variables.") # Initialize Salesforce connection try: sf = Salesforce( username=SF_USERNAME, password=SF_PASSWORD, security_token=SF_SECURITY_TOKEN, consumer_key=SF_CONSUMER_KEY, consumer_secret=SF_CONSUMER_SECRET, domain='login' # Use 'test' for sandbox ) except Exception as e: print(f"Salesforce connection failed: {str(e)}") raise # FastAPI app for API endpoint app = FastAPI() # API Key authentication api_key_header = APIKeyHeader(name="X-API-Key") async def verify_api_key(api_key: str = Security(api_key_header)): if api_key != API_KEY: raise HTTPException(status_code=401, detail="Invalid API Key") return api_key # Mock AI model for milestone detection (since we can't train a real model here) def mock_ai_model(image): # Preprocessing: Resize, normalize (simulated) img = image.convert("RGB") max_size = 1024 img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS) # Feature Extraction and Milestone Detection (simulated) # In a real scenario, this would use a CNN model trained on construction images milestones = [ "Foundation Completed", "Structural Framework Started", "Walls In Progress", "Roofing Started", "Interior Work Started", "Project Completed" ] # For this image, based on the concrete pillars and rebar, we assume "Structural Framework Started" milestone = "Structural Framework Started" completion_percent = 30 # Estimated based on the image confidence_score = round(random.uniform(0.85, 0.95), 2) # Random confidence between 85-95% return milestone, completion_percent, confidence_score @app.post("/predict-milestone") async def predict_milestone(payload: dict, api_key: str = Depends(verify_api_key)): try: # Validate payload if "image" not in payload: raise HTTPException(status_code=400, detail="Image field is required") # Decode base64 image image_data = payload["image"] if image_data.startswith("data:image"): image_data = image_data.split(",")[1] # Remove data URI prefix img_bytes = base64.b64decode(image_data) img = Image.open(io.BytesIO(img_bytes)) # Validate image size (max 20MB) img_bytes_size = len(img_bytes) / (1024 * 1024) if img_bytes_size > 20: raise HTTPException(status_code=400, detail="Image size exceeds 20MB") # Validate image type if not img.format.lower() in ["jpeg", "png"]: raise HTTPException(status_code=400, detail="Only JPG/PNG images are supported") # Run mock AI model milestone, percent_complete, confidence_score = mock_ai_model(img) return { "milestone": milestone, "percent_complete": percent_complete, "confidence_score": confidence_score } except Exception as e: raise HTTPException(status_code=500, detail=f"Error processing image: {str(e)}") # Function for Gradio UI to process the image def process_image(image, project_name): try: # Validate inputs if image is None: return "Error: Please upload an image to proceed.", "Pending", "", "", 0 if not project_name: return "Error: Please enter a project name to proceed.", "Pending", "", "", 0 if not project_name.isalnum(): return "Error: Project name must be alphanumeric (letters and numbers only).", "Pending", "", "", 0 # Open and validate image img = Image.open(image) # Validate image size and type image_size_mb = os.path.getsize(image) / (1024 * 1024) if image_size_mb > 20: return "Error: Image size exceeds 20MB.", "Failure", "", "", 0 if not image.lower().endswith(('.jpg', '.jpeg', '.png')): return "Error: Only JPG/PNG images are supported.", "Failure", "", "", 0 # Run mock AI model milestone, percent_complete, confidence_score = mock_ai_model(img) # Update Salesforce record record = { "Name": project_name, "Current_Milestone__c": milestone, "Completion_Percentage__c": percent_complete, "Last_Updated_On__c": datetime.now().isoformat(), "Upload_Status__c": "Success", "Comments__c": f"AI Prediction: {milestone} with {confidence_score*100}% confidence" } try: query = f"SELECT Id FROM Construction_Project__c WHERE Name = '{project_name}'" result = sf.query(query) if result["totalSize"] > 0: project_id = result["records"][0]["Id"] sf.Construction_Project__c.update(project_id, record) else: sf.Construction_Project__c.create(record) except Exception as e: return f"Error: Failed to update Salesforce - {str(e)}", "Failure", "", "", 0 return ( f"Success: Milestone: {milestone}, Completion: {percent_complete}%", "Success", milestone, f"Confidence Score: {confidence_score}", percent_complete ) except Exception as e: return f"Error: {str(e)}", "Failure", "", "", 0 # Gradio interface for testing with gr.Blocks(css=".gradio-container {background-color: #f0f4f8; font-family: Arial;} .title {color: #2c3e50; font-size: 24px; text-align: center;}") as demo: gr.Markdown("