Update app.py
Browse files
app.py
CHANGED
|
@@ -108,21 +108,41 @@ def validate_usage_series(usage_str):
|
|
| 108 |
|
| 109 |
def generate_forecast_pdf(forecast_data: dict, daily_forecasts: pd.DataFrame, alert_status: list, current_stock: int, forecast_7: int, forecast_14: int, forecast_30: int, fig_daily: go.Figure, fig_alerts: go.Figure, usage_series: str) -> BytesIO:
|
| 110 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
pdf_file = BytesIO()
|
| 112 |
c = canvas.Canvas(pdf_file, pagesize=letter)
|
| 113 |
c.setFont("Helvetica", 12)
|
| 114 |
c.drawString(1 * inch, 10 * inch, "Consumables Forecast Report")
|
| 115 |
c.setFont("Helvetica", 10)
|
| 116 |
y_position = 9.5 * inch
|
|
|
|
| 117 |
|
| 118 |
-
# Basic Forecast Data
|
|
|
|
| 119 |
for key, value in forecast_data.items():
|
| 120 |
display_key = key.replace('_', ' ').title()
|
| 121 |
value_str = str(value)
|
| 122 |
c.drawString(1 * inch, y_position, f"{display_key}: {value_str}")
|
| 123 |
y_position -= 0.3 * inch
|
| 124 |
|
| 125 |
-
# Add Last 60 Days Usage
|
| 126 |
y_position -= 0.3 * inch
|
| 127 |
c.drawString(1 * inch, y_position, "Last 60 Days Usage (comma-separated):")
|
| 128 |
y_position -= 0.3 * inch
|
|
@@ -133,8 +153,9 @@ def generate_forecast_pdf(forecast_data: dict, daily_forecasts: pd.DataFrame, al
|
|
| 133 |
text_object.textLine(line)
|
| 134 |
y_position -= 0.3 * inch
|
| 135 |
c.drawText(text_object)
|
|
|
|
| 136 |
|
| 137 |
-
# Add Daily Forecast Values
|
| 138 |
y_position -= 0.3 * inch
|
| 139 |
c.drawString(1 * inch, y_position, "Daily Forecast Values (Next 30 Days):")
|
| 140 |
y_position -= 0.3 * inch
|
|
@@ -146,8 +167,9 @@ def generate_forecast_pdf(forecast_data: dict, daily_forecasts: pd.DataFrame, al
|
|
| 146 |
text_object.textLine(line)
|
| 147 |
y_position -= 0.3 * inch
|
| 148 |
c.drawText(text_object)
|
|
|
|
| 149 |
|
| 150 |
-
# Add Threshold Alerts
|
| 151 |
y_position -= 0.3 * inch
|
| 152 |
c.drawString(1 * inch, y_position, "Threshold Alerts:")
|
| 153 |
y_position -= 0.3 * inch
|
|
@@ -159,8 +181,9 @@ def generate_forecast_pdf(forecast_data: dict, daily_forecasts: pd.DataFrame, al
|
|
| 159 |
alert_text = f"No alert for {period} forecast."
|
| 160 |
c.drawString(1 * inch, y_position, alert_text)
|
| 161 |
y_position -= 0.3 * inch
|
|
|
|
| 162 |
|
| 163 |
-
# Add Daily Forecast Visualization Data
|
| 164 |
y_position -= 0.3 * inch
|
| 165 |
c.drawString(1 * inch, y_position, "Daily Forecast Visualization Data (Next 30 Days):")
|
| 166 |
y_position -= 0.3 * inch
|
|
@@ -173,19 +196,25 @@ def generate_forecast_pdf(forecast_data: dict, daily_forecasts: pd.DataFrame, al
|
|
| 173 |
c.showPage()
|
| 174 |
c.setFont("Helvetica", 10)
|
| 175 |
y_position = 10 * inch
|
|
|
|
| 176 |
|
| 177 |
# Add Daily Forecast Visualization Image
|
| 178 |
y_position -= 0.3 * inch
|
| 179 |
-
if y_position < 4 * inch:
|
| 180 |
c.showPage()
|
| 181 |
y_position = 10 * inch
|
| 182 |
c.drawString(1 * inch, y_position, "Daily Forecast Visualization (Next 30 Days):")
|
| 183 |
y_position -= 0.3 * inch
|
| 184 |
daily_chart_img = BytesIO()
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
y_position -= 4.5 * inch
|
| 190 |
|
| 191 |
# Add Threshold Alerts Visualization Data
|
|
@@ -208,6 +237,7 @@ def generate_forecast_pdf(forecast_data: dict, daily_forecasts: pd.DataFrame, al
|
|
| 208 |
c.showPage()
|
| 209 |
c.setFont("Helvetica", 10)
|
| 210 |
y_position = 10 * inch
|
|
|
|
| 211 |
|
| 212 |
# Add Threshold Alerts Visualization Image
|
| 213 |
y_position -= 0.3 * inch
|
|
@@ -217,14 +247,20 @@ def generate_forecast_pdf(forecast_data: dict, daily_forecasts: pd.DataFrame, al
|
|
| 217 |
c.drawString(1 * inch, y_position, "Threshold Alerts Visualization:")
|
| 218 |
y_position -= 0.3 * inch
|
| 219 |
alerts_chart_img = BytesIO()
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
|
| 225 |
c.showPage()
|
| 226 |
c.save()
|
| 227 |
pdf_file.seek(0)
|
|
|
|
| 228 |
return pdf_file
|
| 229 |
except Exception as e:
|
| 230 |
logger.error(f"Error generating PDF: {str(e)}")
|
|
@@ -403,7 +439,7 @@ def main():
|
|
| 403 |
{"Pdf_report__c": pdf_url}
|
| 404 |
)
|
| 405 |
logger.info(f"PDF uploaded to Salesforce: {pdf_url}")
|
| 406 |
-
|
| 407 |
else:
|
| 408 |
logger.error("Failed to upload PDF to Salesforce")
|
| 409 |
st.error("Failed to upload PDF to Salesforce")
|
|
|
|
| 108 |
|
| 109 |
def generate_forecast_pdf(forecast_data: dict, daily_forecasts: pd.DataFrame, alert_status: list, current_stock: int, forecast_7: int, forecast_14: int, forecast_30: int, fig_daily: go.Figure, fig_alerts: go.Figure, usage_series: str) -> BytesIO:
|
| 110 |
try:
|
| 111 |
+
logger.info("Starting PDF generation")
|
| 112 |
+
# Validate inputs
|
| 113 |
+
if not isinstance(forecast_data, dict) or not forecast_data:
|
| 114 |
+
logger.error("Invalid forecast_data: Must be a non-empty dictionary")
|
| 115 |
+
return None
|
| 116 |
+
if not isinstance(daily_forecasts, pd.DataFrame) or daily_forecasts.empty:
|
| 117 |
+
logger.error("Invalid daily_forecasts: Must be a non-empty DataFrame")
|
| 118 |
+
return None
|
| 119 |
+
if not isinstance(alert_status, list) or len(alert_status) != 3:
|
| 120 |
+
logger.error("Invalid alert_status: Must be a list of 3 booleans")
|
| 121 |
+
return None
|
| 122 |
+
if not isinstance(usage_series, str) or not usage_series:
|
| 123 |
+
logger.error("Invalid usage_series: Must be a non-empty string")
|
| 124 |
+
return None
|
| 125 |
+
if not isinstance(fig_daily, go.Figure) or not isinstance(fig_alerts, go.Figure):
|
| 126 |
+
logger.error("Invalid Plotly figures: fig_daily and fig_alerts must be valid go.Figure objects")
|
| 127 |
+
return None
|
| 128 |
+
|
| 129 |
pdf_file = BytesIO()
|
| 130 |
c = canvas.Canvas(pdf_file, pagesize=letter)
|
| 131 |
c.setFont("Helvetica", 12)
|
| 132 |
c.drawString(1 * inch, 10 * inch, "Consumables Forecast Report")
|
| 133 |
c.setFont("Helvetica", 10)
|
| 134 |
y_position = 9.5 * inch
|
| 135 |
+
logger.info("Initialized PDF canvas")
|
| 136 |
|
| 137 |
+
# Basic Forecast Data
|
| 138 |
+
logger.info("Writing forecast data")
|
| 139 |
for key, value in forecast_data.items():
|
| 140 |
display_key = key.replace('_', ' ').title()
|
| 141 |
value_str = str(value)
|
| 142 |
c.drawString(1 * inch, y_position, f"{display_key}: {value_str}")
|
| 143 |
y_position -= 0.3 * inch
|
| 144 |
|
| 145 |
+
# Add Last 60 Days Usage
|
| 146 |
y_position -= 0.3 * inch
|
| 147 |
c.drawString(1 * inch, y_position, "Last 60 Days Usage (comma-separated):")
|
| 148 |
y_position -= 0.3 * inch
|
|
|
|
| 153 |
text_object.textLine(line)
|
| 154 |
y_position -= 0.3 * inch
|
| 155 |
c.drawText(text_object)
|
| 156 |
+
logger.info("Added usage series")
|
| 157 |
|
| 158 |
+
# Add Daily Forecast Values
|
| 159 |
y_position -= 0.3 * inch
|
| 160 |
c.drawString(1 * inch, y_position, "Daily Forecast Values (Next 30 Days):")
|
| 161 |
y_position -= 0.3 * inch
|
|
|
|
| 167 |
text_object.textLine(line)
|
| 168 |
y_position -= 0.3 * inch
|
| 169 |
c.drawText(text_object)
|
| 170 |
+
logger.info("Added daily forecast values")
|
| 171 |
|
| 172 |
+
# Add Threshold Alerts
|
| 173 |
y_position -= 0.3 * inch
|
| 174 |
c.drawString(1 * inch, y_position, "Threshold Alerts:")
|
| 175 |
y_position -= 0.3 * inch
|
|
|
|
| 181 |
alert_text = f"No alert for {period} forecast."
|
| 182 |
c.drawString(1 * inch, y_position, alert_text)
|
| 183 |
y_position -= 0.3 * inch
|
| 184 |
+
logger.info("Added threshold alerts")
|
| 185 |
|
| 186 |
+
# Add Daily Forecast Visualization Data
|
| 187 |
y_position -= 0.3 * inch
|
| 188 |
c.drawString(1 * inch, y_position, "Daily Forecast Visualization Data (Next 30 Days):")
|
| 189 |
y_position -= 0.3 * inch
|
|
|
|
| 196 |
c.showPage()
|
| 197 |
c.setFont("Helvetica", 10)
|
| 198 |
y_position = 10 * inch
|
| 199 |
+
logger.info("Added daily forecast visualization data")
|
| 200 |
|
| 201 |
# Add Daily Forecast Visualization Image
|
| 202 |
y_position -= 0.3 * inch
|
| 203 |
+
if y_position < 4 * inch:
|
| 204 |
c.showPage()
|
| 205 |
y_position = 10 * inch
|
| 206 |
c.drawString(1 * inch, y_position, "Daily Forecast Visualization (Next 30 Days):")
|
| 207 |
y_position -= 0.3 * inch
|
| 208 |
daily_chart_img = BytesIO()
|
| 209 |
+
try:
|
| 210 |
+
pio.write_image(fig_daily, daily_chart_img, format='png', width=600, height=400)
|
| 211 |
+
daily_chart_img.seek(0)
|
| 212 |
+
img = Image(daily_chart_img, width=6 * inch, height=4 * inch)
|
| 213 |
+
img.drawOn(c, 1 * inch, y_position - 4 * inch)
|
| 214 |
+
logger.info("Added daily forecast visualization image")
|
| 215 |
+
except Exception as e:
|
| 216 |
+
logger.error(f"Failed to export daily forecast image: {str(e)}")
|
| 217 |
+
c.drawString(1 * inch, y_position - 0.3 * inch, "Error: Could not include daily forecast visualization.")
|
| 218 |
y_position -= 4.5 * inch
|
| 219 |
|
| 220 |
# Add Threshold Alerts Visualization Data
|
|
|
|
| 237 |
c.showPage()
|
| 238 |
c.setFont("Helvetica", 10)
|
| 239 |
y_position = 10 * inch
|
| 240 |
+
logger.info("Added threshold alerts visualization data")
|
| 241 |
|
| 242 |
# Add Threshold Alerts Visualization Image
|
| 243 |
y_position -= 0.3 * inch
|
|
|
|
| 247 |
c.drawString(1 * inch, y_position, "Threshold Alerts Visualization:")
|
| 248 |
y_position -= 0.3 * inch
|
| 249 |
alerts_chart_img = BytesIO()
|
| 250 |
+
try:
|
| 251 |
+
pio.write_image(fig_alerts, alerts_chart_img, format='png', width=600, height=400)
|
| 252 |
+
alerts_chart_img.seek(0)
|
| 253 |
+
img = Image(alerts_chart_img, width=6 * inch, height=4 * inch)
|
| 254 |
+
img.drawOn(c, 1 * inch, y_position - 4 * inch)
|
| 255 |
+
logger.info("Added threshold alerts visualization image")
|
| 256 |
+
except Exception as e:
|
| 257 |
+
logger.error(f"Failed to export alerts visualization image: {str(e)}")
|
| 258 |
+
c.drawString(1 * inch, y_position - 0.3 * inch, "Error: Could not include threshold alerts visualization.")
|
| 259 |
|
| 260 |
c.showPage()
|
| 261 |
c.save()
|
| 262 |
pdf_file.seek(0)
|
| 263 |
+
logger.info("PDF generation completed successfully")
|
| 264 |
return pdf_file
|
| 265 |
except Exception as e:
|
| 266 |
logger.error(f"Error generating PDF: {str(e)}")
|
|
|
|
| 439 |
{"Pdf_report__c": pdf_url}
|
| 440 |
)
|
| 441 |
logger.info(f"PDF uploaded to Salesforce: {pdf_url}")
|
| 442 |
+
logger.info(f"PDF Report generated and uploaded to Salesforce: {pdf_url}")
|
| 443 |
else:
|
| 444 |
logger.error("Failed to upload PDF to Salesforce")
|
| 445 |
st.error("Failed to upload PDF to Salesforce")
|