File size: 12,930 Bytes
29f24d0
686d487
 
 
 
 
27da82c
 
 
 
 
de94fe0
5b665a7
 
27da82c
0b62f8d
 
 
686d487
 
 
27da82c
 
 
 
 
 
 
 
 
 
 
 
 
 
9e1b162
a56447e
27da82c
 
9e1b162
27da82c
 
 
a56447e
27da82c
 
 
686d487
27da82c
 
 
 
 
 
686d487
27da82c
 
 
 
 
 
 
 
 
8e637c6
a1d1526
 
8e637c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a1d1526
0b62f8d
 
 
 
 
 
 
9e1b162
0b62f8d
 
 
 
 
9e1b162
0b62f8d
 
 
 
 
 
9e1b162
0b62f8d
 
 
 
 
 
 
 
 
 
 
27da82c
 
 
 
9e1b162
5b665a7
 
 
 
 
 
 
 
9e1b162
27da82c
 
9e1b162
27da82c
a1d1526
9e1b162
27da82c
 
 
 
 
5b665a7
27da82c
a1d1526
27da82c
9e1b162
27da82c
5b665a7
de94fe0
 
0b62f8d
9e1b162
5b665a7
0b62f8d
 
 
9e1b162
0b62f8d
 
 
 
 
9e1b162
27da82c
 
9e1b162
27da82c
 
 
9e1b162
27da82c
 
 
 
 
 
9e1b162
0b62f8d
27da82c
 
0b62f8d
686d487
 
27da82c
 
 
 
 
 
 
686d487
27da82c
 
 
 
686d487
27da82c
 
 
 
 
 
 
 
 
a1d1526
 
27da82c
0b62f8d
27da82c
0b62f8d
27da82c
 
0b62f8d
686d487
 
 
 
 
 
 
 
 
27da82c
 
686d487
0b62f8d
27da82c
 
 
686d487
 
 
 
 
27da82c
 
0b62f8d
686d487
 
 
 
 
27da82c
 
8e637c6
27da82c
 
 
 
 
 
0a98b28
27da82c
 
 
0b62f8d
27da82c
0a98b28
cbf8115
0a98b28
 
a1d1526
0a98b28
 
 
9e1b162
27da82c
 
 
0a98b28
27da82c
 
 
 
686d487
27da82c
 
 
 
 
 
686d487
a56447e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
import gradio as gr
import pandas as pd
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import uvicorn
from sklearn.linear_model import LinearRegression
import base64
import os
from datetime import datetime
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
from reportlab.lib.utils import simpleSplit
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from io import BytesIO
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

weather_map = {"Cloudy": 0, "Rainy": 1, "Sunny": 2}

print("Loading and preprocessing data...")
try:
    if not os.path.exists("new_delay_data.csv"):
        print("Warning: new_delay_data.csv not found. Using default dataset.")
        default_data = {
            "Phase": ["Framing", "Foundation", "Finishing"],
            "Weather": ["Sunny", "Rainy", "Cloudy"],
            "Absentee": [10, 20, 5],
            "DelayLog": [5, 10, 2],
            "Delay%": [30, 60, 15]
        }
        df = pd.DataFrame(default_data)
    else:
        df = pd.read_csv("new_delay_data.csv")

    df = pd.get_dummies(df, columns=["Phase"], drop_first=True)
    df["Weather"] = df["Weather"].map(weather_map)
    df.dropna(subset=["Weather", "Absentee", "DelayLog", "Delay%"], inplace=True)

    for col in ["Phase_Framing", "Phase_Foundation"]:
        if col not in df.columns:
            df[col] = 0
    print("Data loaded and preprocessed. Columns:", df.columns.tolist())
except Exception as e:
    print(f"Error loading data: {e}")
    raise

try:
    X = df[["Phase_Framing", "Phase_Foundation", "Weather", "Absentee", "DelayLog"]]
    y = df["Delay%"]
except Exception as e:
    print(f"Error preparing features: {e}")
    raise

print("Training model...")
try:
    model = LinearRegression()
    model.fit(X, y)
    print("Model trained successfully.")
except Exception as e:
    print(f"Error training model: {e}")
    raise

# New fast AI insight generator without ML model, just logic + template

def generate_ai_insight(phase, weather, absentee_pct, delay_log, prediction):
    # Build a dynamic insight string
    insight = f"Predicted delay of {prediction}% indicates a "
    if prediction >= 75:
        insight += "high risk of project delay. Immediate action is recommended.\n"
    elif prediction >= 50:
        insight += "moderate risk of project delay. Monitor and manage resources carefully.\n"
    else:
        insight += "low risk of project delay. Continue with current plans but remain vigilant.\n"

    insight += f"Phase: {phase}. Weather conditions are {weather.lower()}.\n"

    if absentee_pct > 30:
        insight += f"High absenteeism ({absentee_pct}%) may severely impact progress. Consider temporary staffing or overtime.\n"
    elif absentee_pct > 10:
        insight += f"Moderate absenteeism ({absentee_pct}%) requires attention to maintain productivity.\n"
    else:
        insight += f"Low absenteeism ({absentee_pct}%) supports steady work progress.\n"

    if delay_log > 5:
        insight += f"Previous delays logged ({delay_log}) suggest bottlenecks; analyze root causes and improve workflows.\n"
    else:
        insight += f"Past delays ({delay_log}) are minimal; maintain efficient task coordination.\n"

    if weather == "Rainy":
        insight += "Rainy weather may cause disruptions; plan indoor or protected activities.\n"
    elif weather == "Cloudy":
        insight += "Cloudy weather is less disruptive but monitor conditions.\n"
    else:
        insight += "Sunny weather is ideal for outdoor tasks; maximize on-site work.\n"

    insight += "\n**Suggested Migration Plan:**\n"
    if prediction >= 75:
        insight += "- Deploy additional workforce immediately.\n"
        insight += "- Reschedule non-critical tasks.\n"
        insight += "- Increase monitoring frequency and daily progress reporting.\n"
    elif prediction >= 50:
        insight += "- Cross-train staff to cover absenteeism.\n"
        insight += "- Review supply chains for potential delays.\n"
    else:
        insight += "- Maintain current schedule.\n"
        insight += "- Conduct routine check-ins to prevent issues.\n"

    return insight.strip()

def generate_heatmap(phase, weather, model):
    try:
        absentee_range = np.linspace(0, 100, 20)
        delay_log_range = np.linspace(0, 20, 20)
        framing = 1 if phase == "Framing" else 0
        foundation = 1 if phase == "Foundation" else 0
        weather_encoded = weather_map.get(weather, 0)

        Z = np.zeros((len(delay_log_range), len(absentee_range)))
        for i, delay_log in enumerate(delay_log_range):
            for j, absentee in enumerate(absentee_range):
                input_data = [[framing, foundation, weather_encoded, absentee, delay_log]]
                Z[i, j] = model.predict(input_data)[0]

        plt.figure(figsize=(8, 6))
        sns.heatmap(Z, xticklabels=np.round(absentee_range, 1), yticklabels=np.round(delay_log_range, 1),
                    cmap="YlOrRd", annot=True, fmt=".1f", cbar_kws={'label': 'Predicted Delay %'})
        plt.xlabel("Absentee %")
        plt.ylabel("Previous Delay Log")
        plt.title(f"Delay Prediction Heatmap (Phase: {phase}, Weather: {weather})")

        output_dir = "pdf_reports"
        os.makedirs(output_dir, exist_ok=True)
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        heatmap_path = os.path.join(output_dir, f"heatmap_{timestamp}.png")
        plt.savefig(heatmap_path, bbox_inches='tight')
        plt.close()
        return heatmap_path
    except Exception as e:
        print(f"Heatmap generation failed: {e}")
        return None

def generate_pdf_report(phase, weather, absentee_pct, delay_log, prediction, risk, insight):
    try:
        buffer = BytesIO()
        c = canvas.Canvas(buffer, pagesize=letter)

        try:
            pdfmetrics.registerFont(TTFont('DejaVuSans', 'DejaVuSans.ttf'))
            c.setFont("DejaVuSans", 12)
            flag_indicator = " 🚩" if prediction >= 75 else ""
        except Exception as e:
            print(f"Failed to load DejaVuSans font: {e}. Falling back to Helvetica with text flag.")
            c.setFont("Helvetica", 12)
            flag_indicator = " [FLAG]" if prediction >= 75 else ""

        c.drawString(100, 750, "Project Delay Prediction Report")
        c.drawString(100, 730, f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

        y_position = 700
        max_width = 400

        details = [
            f"Phase: {phase}",
            f"Weather: {weather}",
            f"Absentee Percentage: {absentee_pct}%",
            f"Previous Delay Log: {delay_log}",
            f"Predicted Delay: {prediction}%{flag_indicator}",
            f"Risk Level: {risk}",
            "AI Insight & Migration Plan:"
        ]

        for line in details:
            lines = simpleSplit(line, 'DejaVuSans' if 'DejaVuSans' in pdfmetrics.getRegisteredFontNames() else 'Helvetica', 12, max_width)
            for wrapped_line in lines:
                c.drawString(100, y_position, wrapped_line)
                y_position -= 16

        insight_lines = simpleSplit(insight, 'DejaVuSans' if 'DejaVuSans' in pdfmetrics.getRegisteredFontNames() else 'Helvetica', 12, max_width)
        for wrapped_line in insight_lines:
            c.drawString(100, y_position, wrapped_line)
            y_position -= 16

        heatmap_path = generate_heatmap(phase, weather, model)
        if heatmap_path and os.path.exists(heatmap_path):
            c.drawString(100, y_position - 20, "Delay Prediction Heatmap:")
            c.drawImage(heatmap_path, 100, y_position - 250, width=400, height=200)
            y_position -= 270

        c.showPage()
        c.save()

        pdf_data = buffer.getvalue()
        buffer.close()
        pdf_base64 = base64.b64encode(pdf_data).decode("utf-8")

        output_dir = "pdf_reports"
        os.makedirs(output_dir, exist_ok=True)
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        output_path = os.path.join(output_dir, f"delay_report_{timestamp}.pdf")
        with open(output_path, "wb") as f:
            f.write(pdf_data)

        return pdf_base64, output_path, heatmap_path
    except Exception as e:
        print(f"PDF generation failed: {e}")
        return None, None, None

def predict_delay(phase, weather, absentee_pct, delay_log):
    try:
        valid_phases = ["Framing", "Foundation", "Finishing"]
        valid_weather = ["Sunny", "Rainy", "Cloudy"]
        phase = phase if isinstance(phase, str) and phase in valid_phases else "Framing"
        weather = weather if isinstance(weather, str) and weather in valid_weather else "Sunny"
        absentee_pct = float(absentee_pct) if isinstance(absentee_pct, (int, float, str)) and float(absentee_pct) >= 0 else 0
        delay_log = float(delay_log) if isinstance(delay_log, (int, float, str)) and float(delay_log) >= 0 else 0

        framing = 1 if phase == "Framing" else 0
        foundation = 1 if phase == "Foundation" else 0
        weather_encoded = weather_map.get(weather, 0)
        input_data = [[framing, foundation, weather_encoded, absentee_pct, delay_log]]

        prediction = model.predict(input_data)[0]
        prediction = round(prediction, 2)

        if prediction >= 75:
            risk = "High Risk"
        elif prediction >= 50:
            risk = "Moderate Risk"
        else:
            risk = "Low Risk"

        insight = generate_ai_insight(phase, weather, absentee_pct, delay_log, prediction)

        pdf_base64, pdf_path, heatmap_path = generate_pdf_report(phase, weather, absentee_pct, delay_log, prediction, risk, insight)

        return prediction, risk, insight, pdf_base64, pdf_path, heatmap_path
    except Exception as e:
        print(f"Prediction error: {e}")
        return None, None, f"Error: {e}", None, None, None

api_app = FastAPI()

@api_app.post("/predict")
async def predict_from_salesforce(request: Request):
    try:
        data = await request.json()
        phase = data.get("phase", "Framing")
        weather = data.get("weather", "Sunny")
        absentee_pct = data.get("absentee_pct", 0)
        delay_log = data.get("delay_log", 0)

        prediction, risk, insight, pdf_base64, pdf_path, heatmap_path = predict_delay(phase, weather, absentee_pct, delay_log)

        if prediction is None:
            return JSONResponse(status_code=500, content={"status": "error", "message": insight})

        return JSONResponse(content={
            "delay_probability": prediction,
            "risk_alert": risk,
            "ai_insight": insight,
            "pdf_report_base64": pdf_base64 if pdf_base64 else "",
            "pdf_local_path": pdf_path if pdf_path else "PDF generation failed",
            "heatmap_path": heatmap_path if heatmap_path else "Heatmap generation failed",
            "status": "success"
        })
    except Exception as e:
        return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})

try:
    with gr.Blocks() as demo:
        gr.Markdown("## πŸ—οΈ Delay Predictor with AI Insights (Fast, No ML Text Generation)")
        with gr.Row():
            phase_input = gr.Textbox(label="Phase (Framing/Foundation/Finishing)", value="Framing")
            weather_input = gr.Textbox(label="Weather (Sunny/Rainy/Cloudy)", value="Sunny")
        with gr.Row():
            absentee_input = gr.Number(label="Absentee %", value=0)
            delay_input = gr.Number(label="Previous Delay Log", value=0)
        output = gr.Textbox(label="Prediction Summary")
        submit = gr.Button("Predict")

        def predict_and_format(phase, weather, absentee, delay_log):
            prediction, risk, insight, pdf_base64, pdf_path, heatmap_path = predict_delay(phase, weather, absentee, delay_log)
            if prediction is None:
                return f"Error: {insight}"
            flag = " 🚩" if prediction >= 75 else ""
            return (f"Predicted Delay: {prediction}%{flag}\n"
                    f"Risk Level: {risk}\n"
                    f"Insight & Migration Plan:\n{insight}\n\n"
                    f"PDF Report: {'Saved locally at ' + pdf_path if pdf_path else 'Failed to generate'}\n"
                    f"Heatmap: {'Saved locally at ' + heatmap_path if heatmap_path else 'Failed to generate'}\n"
                    f"PDF Base64: {'Generated' if pdf_base64 else 'Not generated'}")

        submit.click(
            predict_and_format,
            inputs=[phase_input, weather_input, absentee_input, delay_input],
            outputs=output
        )
except Exception as e:
    print(f"Error setting up Gradio UI: {e}")
    raise

try:
    app = gr.mount_gradio_app(api_app, demo, path="/")
except Exception as e:
    print(f"Error mounting Gradio app: {e}")
    raise

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=7860)