1414from datetime import datetime
1515import tempfile
1616import atexit
17+ from reportlab .lib .pagesizes import letter
18+ from reportlab .pdfgen import canvas
19+ from reportlab .lib .units import inch
1720
1821# === CONFIG ===
1922BACKEND_BASE_URL = "https://ai-dslab-backend-cpf2feachnetbbck.westus-01.azurewebsites.net"
2528UPLOAD_FOLDER = TEMP_DIR .name
2629PLOT_PATH = os .path .join (UPLOAD_FOLDER , "plot.png" )
2730FORECAST_PLOT_PATH = os .path .join (UPLOAD_FOLDER , "forecast_plot.png" )
28- REPORT_PATH = os .path .join (UPLOAD_FOLDER , "summary_report.txt " )
31+ REPORT_PATH = os .path .join (UPLOAD_FOLDER , "summary_report.pdf " )
2932
3033@atexit .register
3134def cleanup_temp_dir ():
3235 TEMP_DIR .cleanup ()
3336
3437log_stream = io .StringIO ()
38+ cached_summary = "" # Global cache for OpenAI summary
3539
3640def log_print (* args ):
3741 print (* args , file = log_stream )
3842 sys .stdout .flush ()
3943
44+ def generate_pdf_report (summary , r2 , mse , forecast_dict ):
45+ c = canvas .Canvas (REPORT_PATH , pagesize = letter )
46+ width , height = letter
47+ text = c .beginText (1 * inch , height - 1 * inch )
48+
49+ text .setFont ("Helvetica-Bold" , 14 )
50+ text .textLine ("AI Forecast Report" )
51+ text .setFont ("Helvetica" , 10 )
52+ text .textLine (f"Model: Linear Regression" )
53+ text .textLine (f"R² Score: { r2 :.4f} " )
54+ text .textLine (f"MSE: { mse :.4f} " )
55+ text .textLine ("" )
56+
57+ text .setFont ("Helvetica-Bold" , 12 )
58+ text .textLine ("OpenAI Data Summary:" )
59+ text .setFont ("Helvetica" , 9 )
60+ for line in summary .splitlines ():
61+ text .textLine (line )
62+
63+ text .textLine ("" )
64+ text .setFont ("Helvetica-Bold" , 12 )
65+ text .textLine ("Forecast Results:" )
66+ text .setFont ("Helvetica" , 10 )
67+ for k , v in forecast_dict .items ():
68+ text .textLine (f"{ k } : { v } " )
69+
70+ c .drawText (text )
71+
72+ if os .path .exists (FORECAST_PLOT_PATH ):
73+ c .drawImage (FORECAST_PLOT_PATH , 1 * inch , 1 * inch , width = 5.5 * inch , preserveAspectRatio = True )
74+
75+ c .save ()
76+
4077@app .route ("/" )
4178def index ():
4279 return jsonify ({"message" : "AI DataScience Backend is running on Azure." })
4380
4481@app .route ("/upload" , methods = ["POST" ])
4582def upload_file ():
83+ global cached_summary
84+
4685 log_stream .truncate (0 )
4786 log_stream .seek (0 )
4887
@@ -63,9 +102,8 @@ def upload_file():
63102
64103 df = df [[x_col , y_col ]].dropna ()
65104 df .columns = ['X' , 'Y' ]
66- log_print ("Cleaned Data:\n " , df .head ())
105+ log_print ("Cleaned Data:\n \n " , df .head ())
67106
68- # Scatter plot
69107 plt .figure ()
70108 plt .scatter (df ['X' ], df ['Y' ])
71109 plt .xlabel ('X' )
@@ -74,7 +112,6 @@ def upload_file():
74112 plt .savefig (PLOT_PATH )
75113 plt .close ()
76114
77- # Try parsing X as datetime
78115 df ['X_date' ] = pd .to_datetime (df ['X' ], errors = 'coerce' )
79116 use_dates = df ['X_date' ].notna ().sum () >= len (df ) // 2
80117 try :
@@ -87,18 +124,13 @@ def upload_file():
87124 except :
88125 return jsonify ({"error" : "Failed to parse X or Y as numeric or date." }), 400
89126
90- if model_choice == "linear" :
91- model = LinearRegression ()
92- else :
93- return jsonify ({"error" : "Only linear regression supported for now." }), 400
94-
127+ model = LinearRegression ()
95128 model .fit (X , y )
96129 y_pred = model .predict (X )
97130 r2 = r2_score (y , y_pred )
98131 mse = mean_squared_error (y , y_pred )
99132 log_print (f"Model Trained. R² = { r2 :.4f} , MSE = { mse :.4f} " )
100133
101- # OpenAI Summary
102134 try :
103135 openai .api_key = os .getenv ("OPENAI_API_KEY" )
104136 client = openai .OpenAI (api_key = openai .api_key )
@@ -111,9 +143,11 @@ def upload_file():
111143 )
112144 summary = response .choices [0 ].message .content
113145 log_print ("Summary generated by OpenAI." )
146+ cached_summary = summary # cache for report
114147 except Exception as e :
115148 summary = "OpenAI summarization failed."
116149 log_print ("OpenAI error:" , str (e ))
150+ cached_summary = summary
117151
118152 return jsonify ({
119153 "summary" : summary ,
@@ -167,11 +201,10 @@ def predict():
167201
168202 y_future = model .predict (values_parsed )
169203 result = {
170- ( datetime .fromordinal (int (x )) if use_dates else float (x ) ): round (p , 2 )
204+ datetime .fromordinal (int (x )). strftime ( "%Y-%m-%d" ) if use_dates else float (x ): round (p , 2 )
171205 for x , p in zip (values_parsed .flatten (), y_future )
172206 }
173207
174- # Generate second plot
175208 X_all = np .concatenate ((X , values_parsed ))
176209 x_min , x_max = X_all .min (), X_all .max ()
177210 x_plot = np .linspace (x_min , x_max , 200 ).reshape (- 1 , 1 )
@@ -188,19 +221,11 @@ def predict():
188221 plt .savefig (FORECAST_PLOT_PATH )
189222 plt .close ()
190223
191- # Write report
192- with open (REPORT_PATH , 'w' ) as f :
193- f .write ("AI Forecast Report\\ n" )
194- f .write ("=================\\ n\\ n" )
195- f .write ("Model: Linear Regression\\ n" )
196- f .write (f"R² Score: { r2_score (y , model .predict (X )):.4f} \\ n" )
197- f .write (f"MSE: { mean_squared_error (y , model .predict (X )):.4f} \\ n" )
198- f .write ("\\ nForecast Results:\\ n" )
199- for k , v in result .items ():
200- f .write (f"{ k } : { v } \\ n" )
224+ # PDF report generation
225+ generate_pdf_report (cached_summary , r2_score (y , model .predict (X )), mean_squared_error (y , model .predict (X )), result )
201226
202227 return jsonify ({
203- "forecast" : { str ( k ): v for k , v in result . items ()} ,
228+ "forecast" : result ,
204229 "log" : log_stream .getvalue (),
205230 "plot_url" : f"{ BACKEND_BASE_URL } /plot.png" ,
206231 "forecast_plot_url" : f"{ BACKEND_BASE_URL } /forecast_plot.png"
@@ -224,4 +249,4 @@ def serve_forecast_plot():
224249
225250@app .route ("/download-report" , methods = ["GET" ])
226251def download_report ():
227- return send_file (REPORT_PATH , as_attachment = True , download_name = "report.txt " )
252+ return send_file (REPORT_PATH , as_attachment = True , download_name = "report.pdf " )
0 commit comments