Spaces:
Sleeping
Sleeping
Update model.py
Browse files
model.py
CHANGED
|
@@ -1,10 +1,47 @@
|
|
| 1 |
import logging
|
| 2 |
from typing import Dict, List
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
# Configure logging
|
| 5 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 6 |
logger = logging.getLogger(__name__)
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
def get_weather_condition(score: int) -> str:
|
| 9 |
"""Map weather impact score (0-100) to descriptive weather condition."""
|
| 10 |
if score <= 10:
|
|
@@ -36,7 +73,7 @@ def call_ai_model_for_insights(input_data: Dict, delay_risk: float) -> List[str]
|
|
| 36 |
shift_hours = input_data.get("workforce_shift_hours", 0)
|
| 37 |
weather_score = input_data.get("weather_impact_score", 0)
|
| 38 |
weather_condition = input_data.get("weather_condition", get_weather_condition(weather_score))
|
| 39 |
-
|
| 40 |
|
| 41 |
# Initialize insights with scores for prioritization
|
| 42 |
insights = []
|
|
@@ -47,42 +84,42 @@ def call_ai_model_for_insights(input_data: Dict, delay_risk: float) -> List[str]
|
|
| 47 |
|
| 48 |
# Delay risk-based insights
|
| 49 |
if delay_risk > 75:
|
| 50 |
-
add_insight(f"Urgent: High delay risk detected for {phase}: {task} in {
|
| 51 |
elif delay_risk > 50:
|
| 52 |
-
add_insight(f"Monitor {phase}: {task} closely in {
|
| 53 |
elif delay_risk > 25:
|
| 54 |
-
add_insight(f"Maintain steady progress for {phase}: {task} in {
|
| 55 |
else:
|
| 56 |
-
add_insight(f"Optimize resources for {phase}: {task} in {
|
| 57 |
|
| 58 |
-
# Weather-specific insights
|
| 59 |
if weather_score > 85:
|
| 60 |
-
add_insight(f"Critical: Severe storm forecast in {
|
| 61 |
elif weather_score > 70:
|
| 62 |
-
add_insight(f"Reschedule outdoor tasks for {phase}: {task} in {
|
| 63 |
elif weather_score > 50:
|
| 64 |
-
add_insight(f"Shift to indoor or weather-resistant tasks for {phase}: {task} in {
|
| 65 |
elif weather_score > 30:
|
| 66 |
-
add_insight(f"Monitor cloudy conditions in {
|
| 67 |
else:
|
| 68 |
-
add_insight(f"Proceed with {phase}: {task} in {
|
| 69 |
|
| 70 |
# Phase/task-specific insights
|
| 71 |
task_specific = {
|
| 72 |
"Planning": {
|
| 73 |
-
"Define Scope": f"Ensure stakeholder alignment for Planning: Define Scope in {
|
| 74 |
-
"Resource Allocation": f"Secure budget and resources early for Planning: Resource Allocation in {
|
| 75 |
-
"Permit Acquisition": f"Expedite permits for Planning: Permit Acquisition in {
|
| 76 |
},
|
| 77 |
"Design": {
|
| 78 |
-
"Architectural Drafting": f"Engage architects early for Design: Architectural Drafting in {
|
| 79 |
-
"Engineering Analysis": f"Hire additional engineers for Design: Engineering Analysis in {
|
| 80 |
-
"Design Review": f"Schedule thorough reviews for Design: Design Review in {
|
| 81 |
},
|
| 82 |
"Construction": {
|
| 83 |
-
"Foundation Work": f"Optimize material delivery for Construction: Foundation Work in {
|
| 84 |
-
"Structural Build": f"Ensure equipment availability for Construction: Structural Build in {
|
| 85 |
-
"Utility Installation": f"Coordinate subcontractors for Construction: Utility Installation in {
|
| 86 |
}
|
| 87 |
}
|
| 88 |
if phase in task_specific and task in task_specific[phase]:
|
|
@@ -90,139 +127,146 @@ def call_ai_model_for_insights(input_data: Dict, delay_risk: float) -> List[str]
|
|
| 90 |
|
| 91 |
# Workforce-based insights
|
| 92 |
if workforce_gap > 30:
|
| 93 |
-
add_insight(f"Urgently hire subcontractors in {
|
| 94 |
elif workforce_gap > 15:
|
| 95 |
-
add_insight(f"Recruit additional workers in {
|
| 96 |
elif workforce_gap > 5:
|
| 97 |
-
add_insight(f"Consider temporary staff in {
|
| 98 |
|
| 99 |
if skill_level == "low":
|
| 100 |
-
add_insight(f"Provide training in {
|
| 101 |
elif skill_level == "medium" and delay_risk > 50:
|
| 102 |
-
add_insight(f"Upskill workforce in {
|
| 103 |
elif skill_level == "high" and delay_risk < 25:
|
| 104 |
-
add_insight(f"Leverage high skill levels in {
|
| 105 |
|
| 106 |
if shift_hours < 6:
|
| 107 |
-
add_insight(f"Extend shift hours beyond {shift_hours} in {
|
| 108 |
elif shift_hours < 8 and delay_risk > 50:
|
| 109 |
-
add_insight(f"Increase shift hours to 8 in {
|
| 110 |
elif shift_hours > 10:
|
| 111 |
-
add_insight(f"Balance shifts in {
|
| 112 |
|
| 113 |
# Progress and duration-based insights
|
| 114 |
if expected_duration > 0 and actual_duration > expected_duration:
|
| 115 |
overrun_pct = ((actual_duration - expected_duration) / expected_duration) * 100
|
| 116 |
if overrun_pct > 20:
|
| 117 |
-
add_insight(f"Address significant duration overrun ({overrun_pct:.1f}%) for {phase}: {task} in {
|
| 118 |
elif overrun_pct > 10:
|
| 119 |
-
add_insight(f"Review scheduling to address {overrun_pct:.1f}% overrun in {phase}: {task} in {
|
| 120 |
|
| 121 |
if expected_duration > 0:
|
| 122 |
expected_progress = min((actual_duration / expected_duration) * 100, 100)
|
| 123 |
if current_progress < expected_progress * 0.8:
|
| 124 |
-
add_insight(f"Accelerate task progress for {phase}: {task} in {
|
| 125 |
elif current_progress < 50 and delay_risk > 50:
|
| 126 |
-
add_insight(f"Increase resources to boost {current_progress}% progress in {phase}: {task} in {
|
| 127 |
|
| 128 |
# Edge cases
|
| 129 |
if workforce_gap >= 90:
|
| 130 |
-
add_insight(f"Critical: Halt non-essential tasks in {
|
| 131 |
if current_progress == 0 and delay_risk > 50:
|
| 132 |
-
add_insight(f"Initiate {phase}: {task} in {
|
| 133 |
if expected_duration == 0 or actual_duration == 0:
|
| 134 |
-
add_insight(f"Provide accurate duration estimates for {phase}: {task} in {
|
| 135 |
if weather_score > 50 and phase == "Construction":
|
| 136 |
-
add_insight(f"Prepare contingency plans for {phase}: {task} in {
|
| 137 |
|
| 138 |
# Sort insights by priority and select top 3-5
|
| 139 |
insights.sort(key=lambda x: x[1], reverse=True)
|
| 140 |
selected_insights = [insight[0] for insight in insights[:5]]
|
| 141 |
|
| 142 |
logger.info(f"Generated insights: {selected_insights}")
|
| 143 |
-
return selected_insights or [f"No significant delay factors detected for {phase}: {task} in {
|
| 144 |
|
| 145 |
def predict_delay(input_data: Dict) -> Dict:
|
| 146 |
"""
|
| 147 |
-
Predict delay probability
|
| 148 |
-
|
| 149 |
-
|
| 150 |
"""
|
| 151 |
-
logger.info("Starting delay prediction")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
phase = input_data.get("phase", "")
|
| 153 |
task = input_data.get("task", "")
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
-
#
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
|
| 168 |
-
#
|
| 169 |
task_options = {
|
| 170 |
"Planning": ["Define Scope", "Resource Allocation", "Permit Acquisition"],
|
| 171 |
"Design": ["Architectural Drafting", "Engineering Analysis", "Design Review"],
|
| 172 |
"Construction": ["Foundation Work", "Structural Build", "Utility Installation"]
|
| 173 |
}
|
| 174 |
-
|
| 175 |
-
delay_risk = 0
|
| 176 |
-
|
| 177 |
-
# 1. Duration overrun risk
|
| 178 |
-
if expected_duration > 0 and actual_duration > expected_duration:
|
| 179 |
-
overrun_pct = ((actual_duration - expected_duration) / expected_duration) * 100
|
| 180 |
-
delay_risk += min(overrun_pct, 30)
|
| 181 |
-
|
| 182 |
-
# 2. Progress lag risk
|
| 183 |
-
if expected_duration > 0 and current_progress >= 0:
|
| 184 |
-
expected_progress = (actual_duration / expected_duration) * 100
|
| 185 |
-
if current_progress < expected_progress:
|
| 186 |
-
progress_gap = expected_progress - current_progress
|
| 187 |
-
delay_risk += min(progress_gap, 25)
|
| 188 |
-
|
| 189 |
-
# 3. Workforce gap impact
|
| 190 |
-
if workforce_gap_pct > 0:
|
| 191 |
-
delay_risk += min(workforce_gap_pct * 0.5, 20)
|
| 192 |
-
|
| 193 |
-
# 4. Skill level effect
|
| 194 |
-
if skill_level == "low":
|
| 195 |
-
delay_risk += 15
|
| 196 |
-
elif skill_level == "medium":
|
| 197 |
-
delay_risk += 7
|
| 198 |
-
|
| 199 |
-
# 5. Shift hours effect
|
| 200 |
-
if shift_hours < 8:
|
| 201 |
-
delay_risk += (8 - shift_hours) * 3
|
| 202 |
-
elif shift_hours > 8:
|
| 203 |
-
delay_risk -= min((shift_hours - 8) * 2, 10)
|
| 204 |
-
|
| 205 |
-
# 6. Weather impact effect
|
| 206 |
-
if weather_score > 50:
|
| 207 |
-
delay_risk += min(weather_score / 2, 20)
|
| 208 |
-
|
| 209 |
-
# Ensure delay_risk is between 0 and 100
|
| 210 |
-
delay_risk = max(0, min(delay_risk, 100))
|
| 211 |
-
|
| 212 |
-
# Generate high_risk_phases for all tasks in the phase
|
| 213 |
high_risk_phases = []
|
| 214 |
if phase in task_options:
|
| 215 |
for t in task_options[phase]:
|
| 216 |
task_risk = delay_risk
|
| 217 |
if t != task:
|
| 218 |
-
task_risk = min(max(task_risk + (hash(t) % 10 - 5), 0), 100)
|
| 219 |
high_risk_phases.append({
|
| 220 |
"phase": phase,
|
| 221 |
"task": t,
|
| 222 |
"risk": round(task_risk, 1)
|
| 223 |
})
|
| 224 |
|
| 225 |
-
# Generate
|
| 226 |
insights = call_ai_model_for_insights(input_data, delay_risk)
|
| 227 |
|
| 228 |
logger.info(f"Prediction completed: Delay risk = {delay_risk:.1f}%")
|
|
@@ -230,7 +274,7 @@ def predict_delay(input_data: Dict) -> Dict:
|
|
| 230 |
"project": input_data.get("project_name", "Unnamed Project"),
|
| 231 |
"phase": phase,
|
| 232 |
"task": task,
|
| 233 |
-
"delay_probability":
|
| 234 |
"ai_insights": "; ".join(insights) if insights else "No significant delay factors detected.",
|
| 235 |
"high_risk_phases": high_risk_phases,
|
| 236 |
"weather_condition": weather_condition
|
|
|
|
| 1 |
import logging
|
| 2 |
from typing import Dict, List
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pickle
|
| 7 |
+
from sklearn.preprocessing import StandardScaler
|
| 8 |
|
| 9 |
# Configure logging
|
| 10 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 11 |
logger = logging.getLogger(__name__)
|
| 12 |
|
| 13 |
+
# LSTM Model Definition (must match training script)
|
| 14 |
+
class DelayPredictor(nn.Module):
|
| 15 |
+
def __init__(self, input_size, hidden_size, num_layers):
|
| 16 |
+
super(DelayPredictor, self).__init__()
|
| 17 |
+
self.hidden_size = hidden_size
|
| 18 |
+
self.num_layers = num_layers
|
| 19 |
+
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
|
| 20 |
+
self.attention = nn.Linear(hidden_size, 1)
|
| 21 |
+
self.fc = nn.Linear(hidden_size, 1)
|
| 22 |
+
self.sigmoid = nn.Sigmoid()
|
| 23 |
+
|
| 24 |
+
def forward(self, x):
|
| 25 |
+
lstm_out, _ = self.lstm(x)
|
| 26 |
+
attn_weights = torch.softmax(self.attention(lstm_out).squeeze(-1), dim=1)
|
| 27 |
+
context = torch.bmm(attn_weights.unsqueeze(1), lstm_out).squeeze(1)
|
| 28 |
+
out = self.fc(context)
|
| 29 |
+
return self.sigmoid(out) * 100
|
| 30 |
+
|
| 31 |
+
# Load model and scaler
|
| 32 |
+
try:
|
| 33 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 34 |
+
model = DelayPredictor(input_size=7, hidden_size=64, num_layers=2).to(device)
|
| 35 |
+
model.load_state_dict(torch.load("models/delay_model.pth", map_location=device))
|
| 36 |
+
model.eval()
|
| 37 |
+
with open("models/scaler.pkl", "rb") as f:
|
| 38 |
+
scaler = pickle.load(f)
|
| 39 |
+
logger.info("LSTM model and scaler loaded successfully")
|
| 40 |
+
except Exception as e:
|
| 41 |
+
logger.error(f"Failed to load model or scaler: {str(e)}")
|
| 42 |
+
model = None
|
| 43 |
+
scaler = None
|
| 44 |
+
|
| 45 |
def get_weather_condition(score: int) -> str:
|
| 46 |
"""Map weather impact score (0-100) to descriptive weather condition."""
|
| 47 |
if score <= 10:
|
|
|
|
| 73 |
shift_hours = input_data.get("workforce_shift_hours", 0)
|
| 74 |
weather_score = input_data.get("weather_impact_score", 0)
|
| 75 |
weather_condition = input_data.get("weather_condition", get_weather_condition(weather_score))
|
| 76 |
+
project_location = input_data.get("project_location", "Unknown")
|
| 77 |
|
| 78 |
# Initialize insights with scores for prioritization
|
| 79 |
insights = []
|
|
|
|
| 84 |
|
| 85 |
# Delay risk-based insights
|
| 86 |
if delay_risk > 75:
|
| 87 |
+
add_insight(f"Urgent: High delay risk detected for {phase}: {task} in {project_location}. Take immediate action.", 1.0)
|
| 88 |
elif delay_risk > 50:
|
| 89 |
+
add_insight(f"Monitor {phase}: {task} closely in {project_location} to prevent delays.", 0.9)
|
| 90 |
elif delay_risk > 25:
|
| 91 |
+
add_insight(f"Maintain steady progress for {phase}: {task} in {project_location}.", 0.7)
|
| 92 |
else:
|
| 93 |
+
add_insight(f"Optimize resources for {phase}: {task} in {project_location} to maintain schedule.", 0.6)
|
| 94 |
|
| 95 |
+
# Weather-specific insights
|
| 96 |
if weather_score > 85:
|
| 97 |
+
add_insight(f"Critical: Severe storm forecast in {project_location} for {phase}: {task}. Consider halting outdoor activities.", 1.1)
|
| 98 |
elif weather_score > 70:
|
| 99 |
+
add_insight(f"Reschedule outdoor tasks for {phase}: {task} in {project_location} due to heavy rain forecast.", 1.0)
|
| 100 |
elif weather_score > 50:
|
| 101 |
+
add_insight(f"Shift to indoor or weather-resistant tasks for {phase}: {task} in {project_location} due to light rain.", 0.9)
|
| 102 |
elif weather_score > 30:
|
| 103 |
+
add_insight(f"Monitor cloudy conditions in {project_location} for {phase}: {task} to avoid unexpected delays.", 0.7)
|
| 104 |
else:
|
| 105 |
+
add_insight(f"Proceed with {phase}: {task} in {project_location} under favorable weather conditions.", 0.6)
|
| 106 |
|
| 107 |
# Phase/task-specific insights
|
| 108 |
task_specific = {
|
| 109 |
"Planning": {
|
| 110 |
+
"Define Scope": f"Ensure stakeholder alignment for Planning: Define Scope in {project_location}, considering weather impacts.",
|
| 111 |
+
"Resource Allocation": f"Secure budget and resources early for Planning: Resource Allocation in {project_location}.",
|
| 112 |
+
"Permit Acquisition": f"Expedite permits for Planning: Permit Acquisition in {project_location} to avoid weather-related delays."
|
| 113 |
},
|
| 114 |
"Design": {
|
| 115 |
+
"Architectural Drafting": f"Engage architects early for Design: Architectural Drafting in {project_location}, accounting for weather.",
|
| 116 |
+
"Engineering Analysis": f"Hire additional engineers for Design: Engineering Analysis in {project_location} to meet deadlines.",
|
| 117 |
+
"Design Review": f"Schedule thorough reviews for Design: Design Review in {project_location}, considering forecast."
|
| 118 |
},
|
| 119 |
"Construction": {
|
| 120 |
+
"Foundation Work": f"Optimize material delivery for Construction: Foundation Work in {project_location}, avoiding {weather_condition.lower()}.",
|
| 121 |
+
"Structural Build": f"Ensure equipment availability for Construction: Structural Build in {project_location} under {weather_condition.lower()}.",
|
| 122 |
+
"Utility Installation": f"Coordinate subcontractors for Construction: Utility Installation in {project_location}, monitoring weather."
|
| 123 |
}
|
| 124 |
}
|
| 125 |
if phase in task_specific and task in task_specific[phase]:
|
|
|
|
| 127 |
|
| 128 |
# Workforce-based insights
|
| 129 |
if workforce_gap > 30:
|
| 130 |
+
add_insight(f"Urgently hire subcontractors in {project_location} to address {workforce_gap}% workforce shortage.", 1.0)
|
| 131 |
elif workforce_gap > 15:
|
| 132 |
+
add_insight(f"Recruit additional workers in {project_location} to reduce {workforce_gap}% workforce gap.", 0.9)
|
| 133 |
elif workforce_gap > 5:
|
| 134 |
+
add_insight(f"Consider temporary staff in {project_location} to address minor workforce gap.", 0.7)
|
| 135 |
|
| 136 |
if skill_level == "low":
|
| 137 |
+
add_insight(f"Provide training in {project_location} to improve low skill levels for {phase}: {task}.", 0.9)
|
| 138 |
elif skill_level == "medium" and delay_risk > 50:
|
| 139 |
+
add_insight(f"Upskill workforce in {project_location} for efficiency in {phase}: {task}.", 0.8)
|
| 140 |
elif skill_level == "high" and delay_risk < 25:
|
| 141 |
+
add_insight(f"Leverage high skill levels in {project_location} to maintain {phase}: {task} progress.", 0.6)
|
| 142 |
|
| 143 |
if shift_hours < 6:
|
| 144 |
+
add_insight(f"Extend shift hours beyond {shift_hours} in {project_location} to meet {phase}: {task} deadlines.", 0.9)
|
| 145 |
elif shift_hours < 8 and delay_risk > 50:
|
| 146 |
+
add_insight(f"Increase shift hours to 8 in {project_location} for {phase}: {task}.", 0.8)
|
| 147 |
elif shift_hours > 10:
|
| 148 |
+
add_insight(f"Balance shifts in {project_location} to prevent burnout during {phase}: {task}.", 0.7)
|
| 149 |
|
| 150 |
# Progress and duration-based insights
|
| 151 |
if expected_duration > 0 and actual_duration > expected_duration:
|
| 152 |
overrun_pct = ((actual_duration - expected_duration) / expected_duration) * 100
|
| 153 |
if overrun_pct > 20:
|
| 154 |
+
add_insight(f"Address significant duration overrun ({overrun_pct:.1f}%) for {phase}: {task} in {project_location}.", 1.0)
|
| 155 |
elif overrun_pct > 10:
|
| 156 |
+
add_insight(f"Review scheduling to address {overrun_pct:.1f}% overrun in {phase}: {task} in {project_location}.", 0.8)
|
| 157 |
|
| 158 |
if expected_duration > 0:
|
| 159 |
expected_progress = min((actual_duration / expected_duration) * 100, 100)
|
| 160 |
if current_progress < expected_progress * 0.8:
|
| 161 |
+
add_insight(f"Accelerate task progress for {phase}: {task} in {project_location} to align with schedule.", 0.9)
|
| 162 |
elif current_progress < 50 and delay_risk > 50:
|
| 163 |
+
add_insight(f"Increase resources to boost {current_progress}% progress in {phase}: {task} in {project_location}.", 0.8)
|
| 164 |
|
| 165 |
# Edge cases
|
| 166 |
if workforce_gap >= 90:
|
| 167 |
+
add_insight(f"Critical: Halt non-essential tasks in {project_location} until workforce gap for {phase}: {task} is resolved.", 1.1)
|
| 168 |
if current_progress == 0 and delay_risk > 50:
|
| 169 |
+
add_insight(f"Initiate {phase}: {task} in {project_location} immediately to avoid further delays.", 1.0)
|
| 170 |
if expected_duration == 0 or actual_duration == 0:
|
| 171 |
+
add_insight(f"Provide accurate duration estimates for {phase}: {task} in {project_location} for reliable predictions.", 0.7)
|
| 172 |
if weather_score > 50 and phase == "Construction":
|
| 173 |
+
add_insight(f"Prepare contingency plans for {phase}: {task} in {project_location} due to adverse weather forecast.", 0.95)
|
| 174 |
|
| 175 |
# Sort insights by priority and select top 3-5
|
| 176 |
insights.sort(key=lambda x: x[1], reverse=True)
|
| 177 |
selected_insights = [insight[0] for insight in insights[:5]]
|
| 178 |
|
| 179 |
logger.info(f"Generated insights: {selected_insights}")
|
| 180 |
+
return selected_insights or [f"No significant delay factors detected for {phase}: {task} in {project_location}."]
|
| 181 |
|
| 182 |
def predict_delay(input_data: Dict) -> Dict:
|
| 183 |
"""
|
| 184 |
+
Predict delay probability using LSTM model.
|
| 185 |
+
Inputs: Project task data (phase, progress, duration, workforce, weather).
|
| 186 |
+
Outputs: Delay probability, AI insights, high-risk phases, weather condition.
|
| 187 |
"""
|
| 188 |
+
logger.info("Starting LSTM delay prediction")
|
| 189 |
+
if model is None or scaler is None:
|
| 190 |
+
logger.error("Model or scaler not loaded; falling back to default")
|
| 191 |
+
return {
|
| 192 |
+
"project": input_data.get("project_name", "Unnamed Project"),
|
| 193 |
+
"phase": input_data.get("phase", ""),
|
| 194 |
+
"task": input_data.get("task", ""),
|
| 195 |
+
"delay_probability": 50.0,
|
| 196 |
+
"ai_insights": "Model unavailable; please check deployment.",
|
| 197 |
+
"high_risk_phases": [],
|
| 198 |
+
"weather_condition": "Unknown"
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
phase = input_data.get("phase", "")
|
| 202 |
task = input_data.get("task", "")
|
| 203 |
+
weather_condition = input_data.get("weather_condition", get_weather_condition(input_data.get("weather_impact_score", 0)))
|
| 204 |
+
|
| 205 |
+
# Prepare input features
|
| 206 |
+
phase_mapping = {"Planning": 0, "Design": 1, "Construction": 2}
|
| 207 |
+
skill_mapping = {"Low": 0, "Medium": 1, "High": 2}
|
| 208 |
+
try:
|
| 209 |
+
features = np.array([[
|
| 210 |
+
input_data.get("current_progress", 0),
|
| 211 |
+
input_data.get("task_expected_duration", 0),
|
| 212 |
+
input_data.get("task_actual_duration", 0),
|
| 213 |
+
input_data.get("workforce_gap", 0),
|
| 214 |
+
input_data.get("weather_impact_score", 0),
|
| 215 |
+
skill_mapping.get(input_data.get("workforce_skill_level", "Medium"), 1),
|
| 216 |
+
phase_mapping.get(phase, 0)
|
| 217 |
+
]])
|
| 218 |
+
except KeyError as e:
|
| 219 |
+
logger.error(f"Invalid input data: {str(e)}")
|
| 220 |
+
return {
|
| 221 |
+
"project": input_data.get("project_name", "Unnamed Project"),
|
| 222 |
+
"phase": phase,
|
| 223 |
+
"task": task,
|
| 224 |
+
"delay_probability": 50.0,
|
| 225 |
+
"ai_insights": f"Invalid input: {str(e)}",
|
| 226 |
+
"high_risk_phases": [],
|
| 227 |
+
"weather_condition": weather_condition
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
# Standardize and reshape
|
| 231 |
+
try:
|
| 232 |
+
features_scaled = scaler.transform(features)
|
| 233 |
+
features_tensor = torch.tensor(features_scaled.reshape(1, 1, -1), dtype=torch.float32).to(device)
|
| 234 |
+
except Exception as e:
|
| 235 |
+
logger.error(f"Feature preprocessing failed: {str(e)}")
|
| 236 |
+
return {
|
| 237 |
+
"project": input_data.get("project_name", "Unnamed Project"),
|
| 238 |
+
"phase": phase,
|
| 239 |
+
"task": task,
|
| 240 |
+
"delay_probability": 50.0,
|
| 241 |
+
"ai_insights": f"Preprocessing error: {str(e)}",
|
| 242 |
+
"high_risk_phases": [],
|
| 243 |
+
"weather_condition": weather_condition
|
| 244 |
+
}
|
| 245 |
|
| 246 |
+
# Predict
|
| 247 |
+
with torch.no_grad():
|
| 248 |
+
delay_risk = model(features_tensor).cpu().numpy().item()
|
| 249 |
+
delay_risk = round(max(0, min(delay_risk, 100)), 1)
|
| 250 |
|
| 251 |
+
# Generate high_risk_phases
|
| 252 |
task_options = {
|
| 253 |
"Planning": ["Define Scope", "Resource Allocation", "Permit Acquisition"],
|
| 254 |
"Design": ["Architectural Drafting", "Engineering Analysis", "Design Review"],
|
| 255 |
"Construction": ["Foundation Work", "Structural Build", "Utility Installation"]
|
| 256 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
high_risk_phases = []
|
| 258 |
if phase in task_options:
|
| 259 |
for t in task_options[phase]:
|
| 260 |
task_risk = delay_risk
|
| 261 |
if t != task:
|
| 262 |
+
task_risk = min(max(task_risk + (hash(t) % 10 - 5), 0), 100)
|
| 263 |
high_risk_phases.append({
|
| 264 |
"phase": phase,
|
| 265 |
"task": t,
|
| 266 |
"risk": round(task_risk, 1)
|
| 267 |
})
|
| 268 |
|
| 269 |
+
# Generate insights
|
| 270 |
insights = call_ai_model_for_insights(input_data, delay_risk)
|
| 271 |
|
| 272 |
logger.info(f"Prediction completed: Delay risk = {delay_risk:.1f}%")
|
|
|
|
| 274 |
"project": input_data.get("project_name", "Unnamed Project"),
|
| 275 |
"phase": phase,
|
| 276 |
"task": task,
|
| 277 |
+
"delay_probability": delay_risk,
|
| 278 |
"ai_insights": "; ".join(insights) if insights else "No significant delay factors detected.",
|
| 279 |
"high_risk_phases": high_risk_phases,
|
| 280 |
"weather_condition": weather_condition
|