pranit144 commited on
Commit
878ab80
·
verified ·
1 Parent(s): e0ea777

Update apps/flask_server.py

Browse files
Files changed (1) hide show
  1. apps/flask_server.py +431 -436
apps/flask_server.py CHANGED
@@ -1,436 +1,431 @@
1
- """
2
- DCRM Analysis Flask API - Three Phase Support
3
- ==============================================
4
- Flask API wrapper for the DCRM analysis pipeline.
5
- Accepts 3 CSV uploads (R, Y, B phases) via POST and returns comprehensive JSON analysis.
6
-
7
- Endpoint: POST /api/circuit-breakers/{breaker_id}/tests/upload-three-phase
8
- """
9
-
10
- import os
11
- import json
12
- import traceback
13
- import uuid
14
- from datetime import datetime, timezone
15
- import sys
16
- import concurrent.futures
17
-
18
- # Add project root to sys.path to allow importing from core
19
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
20
-
21
- # Previous Name: flask_app.py
22
- from flask import Flask, request, jsonify
23
- from flask_cors import CORS
24
- from werkzeug.utils import secure_filename
25
- import pandas as pd
26
- from io import StringIO
27
-
28
- # Load environment variables
29
- from dotenv import load_dotenv
30
- load_dotenv()
31
-
32
- # Ensure API key is set
33
- if not os.getenv("GOOGLE_API_KEY"):
34
- print("WARNING: GOOGLE_API_KEY not found in environment variables. Please check your .env file.")
35
-
36
- from langchain_google_genai import ChatGoogleGenerativeAI
37
- from core.calculators.kpi import calculate_kpis
38
- from core.calculators.cbhi import compute_cbhi
39
- from core.signal.phases import analyze_dcrm_data
40
- from core.engines.rules import analyze_dcrm_advanced
41
- from core.agents.diagnosis import detect_fault, standardize_input
42
- from core.utils.report_generator import generate_dcrm_json
43
- from core.agents.recommendation import generate_recommendations
44
-
45
- # Optional ViT Model
46
- try:
47
- from core.models.vit_classifier import predict_dcrm_image, plot_resistance_for_vit
48
- VIT_AVAILABLE = True
49
- except Exception as e:
50
- print(f"ViT Model not available: {e}")
51
- VIT_AVAILABLE = False
52
- predict_dcrm_image = None
53
- plot_resistance_for_vit = None
54
-
55
- # =============================================================================
56
- # CONFIGURATION - CHANGE THIS URL AFTER DEPLOYMENT
57
- # =============================================================================
58
- DEPLOYMENT_URL = "http://localhost:5000" # Change this to your deployed URL
59
- # Example: DEPLOYMENT_URL = "https://your-domain.com"
60
- # =============================================================================
61
-
62
- # Initialize Flask app
63
- app = Flask(__name__)
64
- CORS(app) # Enable CORS for frontend access
65
-
66
- def get_llm(api_key=None):
67
- """
68
- Factory function to create an LLM instance with a specific API key.
69
- If no key is provided, falls back to the default GOOGLE_API_KEY.
70
- """
71
- if not api_key:
72
- api_key = os.getenv("GOOGLE_API_KEY")
73
-
74
- if not api_key:
75
- raise ValueError("No Google API Key provided and GOOGLE_API_KEY not found in env.")
76
-
77
- return ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0, google_api_key=api_key)
78
-
79
-
80
- def process_single_phase_csv(args):
81
- """
82
- Process a single phase CSV through the complete DCRM pipeline.
83
- Designed to be run in a separate thread.
84
-
85
- Args:
86
- args: Tuple containing (df, breaker_id, api_key, phase_name)
87
-
88
- Returns:
89
- dict: Complete analysis results for one phase
90
- """
91
- df, breaker_id, api_key, phase_name = args
92
-
93
- try:
94
- print(f"[{phase_name.upper()}] Starting processing with key ending in ...{api_key[-4:] if api_key else 'None'}")
95
-
96
- # Initialize local LLM for this thread
97
- llm = get_llm(api_key)
98
-
99
- # 1. Calculate KPIs
100
- kpi_results = calculate_kpis(df)
101
- kpis = kpi_results['kpis']
102
-
103
- # 2. Phase Segmentation (AI-based)
104
- phase_analysis_result = analyze_dcrm_data(df, llm)
105
-
106
- # 3. Prepare KPIs for Rule Engine and AI Agent
107
- raj_kpis = {
108
- "Closing Time (ms)": kpis.get('closing_time'),
109
- "Opening Time (ms)": kpis.get('opening_time'),
110
- "Contact Speed (m/s)": kpis.get('contact_speed'),
111
- "DLRO Value (µΩ)": kpis.get('dlro'),
112
- "Peak Resistance (µΩ)": kpis.get('peak_resistance'),
113
- "Peak Close Coil Current (A)": kpis.get('peak_close_coil'),
114
- "Peak Trip Coil 1 Current (A)": kpis.get('peak_trip_coil_1'),
115
- "Peak Trip Coil 2 Current (A)": kpis.get('peak_trip_coil_2'),
116
- "SF6 Pressure (bar)": kpis.get('sf6_pressure'),
117
- "Ambient Temperature (°C)": kpis.get('ambient_temp'),
118
- "Main Wipe (mm)": kpis.get('main_wipe'),
119
- "Arc Wipe (mm)": kpis.get('arc_wipe'),
120
- "Contact Travel Distance (mm)": kpis.get('contact_travel')
121
- }
122
-
123
- raj_ai_kpis = {
124
- "kpis": [
125
- {"name": "Closing Time", "unit": "ms", "value": kpis.get('closing_time')},
126
- {"name": "Opening Time", "unit": "ms", "value": kpis.get('opening_time')},
127
- {"name": "DLRO Value", "unit": "µΩ", "value": kpis.get('dlro')},
128
- {"name": "Peak Resistance", "unit": "µΩ", "value": kpis.get('peak_resistance')},
129
- {"name": "Contact Speed", "unit": "m/s", "value": kpis.get('contact_speed')},
130
- {"name": "Peak Close Coil Current", "unit": "A", "value": kpis.get('peak_close_coil')},
131
- {"name": "Peak Trip Coil 1 Current", "unit": "A", "value": kpis.get('peak_trip_coil_1')},
132
- {"name": "Peak Trip Coil 2 Current", "unit": "A", "value": kpis.get('peak_trip_coil_2')},
133
- {"name": "SF6 Pressure", "unit": "bar", "value": kpis.get('sf6_pressure')},
134
- {"name": "Ambient Temperature", "unit": "°C", "value": kpis.get('ambient_temp')}
135
- ]
136
- }
137
-
138
- # 4. Standardize resistance data for Rule Engine
139
- temp_df = df[['Resistance']].copy()
140
- if len(temp_df) < 401:
141
- last_val = temp_df.iloc[-1, 0]
142
- padding = pd.DataFrame({'Resistance': [last_val] * (401 - len(temp_df))})
143
- temp_df = pd.concat([temp_df, padding], ignore_index=True)
144
-
145
- std_df = standardize_input(temp_df)
146
- row_values = std_df.iloc[0].values.tolist()
147
-
148
- # 5. Run Rule Engine Analysis
149
- rule_engine_result = analyze_dcrm_advanced(row_values, raj_kpis)
150
-
151
- # 6. Run AI Agent Analysis with error handling
152
- try:
153
- ai_agent_result = detect_fault(df, raj_ai_kpis)
154
- print(f"[{phase_name.upper()}] AI Agent analysis completed successfully")
155
- except Exception as e:
156
- print(f"[{phase_name.upper()}] AI Agent failed: {e}. Using fallback.")
157
- # Fallback: Use rule engine result as AI result
158
- ai_agent_result = {
159
- "Fault_Detection": rule_engine_result.get("Fault_Detection", []),
160
- "overall_health_assessment": rule_engine_result.get("overall_health_assessment", {}),
161
- "classifications": rule_engine_result.get("classifications", [])
162
- }
163
-
164
- # 7. Run ViT Model (if available)
165
- vit_result = None
166
- vit_plot_path = f"temp_vit_plot_{phase_name}_{uuid.uuid4().hex[:8]}.png" # Unique path for parallel safety
167
-
168
- plot_generated = False
169
- try:
170
- if plot_resistance_for_vit and plot_resistance_for_vit(df, vit_plot_path):
171
- plot_generated = True
172
- except Exception as e:
173
- print(f"[{phase_name.upper()}] ViT Plot generation failed: {e}")
174
-
175
- if plot_generated and VIT_AVAILABLE and predict_dcrm_image:
176
- try:
177
- # Pass API key to ViT as well if needed, though currently it might use env var
178
- # The updated vit_classifier uses requests to a deployed model, so API key is for Gemini part
179
- vit_class, vit_conf, vit_details = predict_dcrm_image(vit_plot_path, api_key=api_key)
180
- if vit_class:
181
- vit_result = {
182
- "class": vit_class,
183
- "confidence": vit_conf,
184
- "details": vit_details
185
- }
186
- except Exception as e:
187
- print(f"[{phase_name.upper()}] ViT Prediction failed: {e}")
188
- finally:
189
- # Cleanup temp file
190
- if os.path.exists(vit_plot_path):
191
- try:
192
- os.remove(vit_plot_path)
193
- except:
194
- pass
195
-
196
- # 8. Calculate CBHI Score
197
- cbhi_phase_data = {}
198
- if 'phaseWiseAnalysis' in phase_analysis_result:
199
- for phase in phase_analysis_result['phaseWiseAnalysis']:
200
- p_name = f"Phase {phase.get('phaseNumber')}"
201
- cbhi_phase_data[p_name] = {
202
- "status": phase.get('status', 'Unknown'),
203
- "confidence": phase.get('confidence', 0)
204
- }
205
-
206
- cbhi_score = compute_cbhi(raj_ai_kpis['kpis'], ai_agent_result, cbhi_phase_data)
207
-
208
- # 9. Generate Recommendations with error handling
209
- try:
210
- recommendations = generate_recommendations(
211
- kpis=kpis,
212
- cbhi_score=cbhi_score,
213
- rule_faults=rule_engine_result.get("Fault_Detection", []),
214
- ai_faults=ai_agent_result.get("Fault_Detection", []),
215
- llm=llm
216
- )
217
- print(f"[{phase_name.upper()}] Recommendations generated successfully")
218
- except Exception as e:
219
- print(f"[{phase_name.upper()}] Recommendations failed: {e}. Using fallback.")
220
- # Fallback: Create basic recommendations from rule engine
221
- recommendations = {
222
- "maintenanceActions": [],
223
- "futureFaultsPdf": []
224
- }
225
- # Extract from rule faults
226
- for fault in rule_engine_result.get("Fault_Detection", []):
227
- if fault.get("Severity") in ["High", "Critical"]:
228
- recommendations["maintenanceActions"].append({
229
- "action": f"Address {fault.get('defect_name')}",
230
- "priority": "High",
231
- "timeframe": "Immediate"
232
- })
233
-
234
- # 10. Generate Final JSON Report with error handling
235
- try:
236
- full_report = generate_dcrm_json(
237
- df=df,
238
- kpis=kpis,
239
- cbhi_score=cbhi_score,
240
- rule_result=rule_engine_result,
241
- ai_result=ai_agent_result,
242
- llm=llm,
243
- vit_result=vit_result,
244
- phase_analysis_result=phase_analysis_result,
245
- recommendations=recommendations
246
- )
247
- print(f"[{phase_name.upper()}] Final report generated successfully")
248
- except Exception as e:
249
- print(f"[{phase_name.upper()}] Report generation failed: {e}. Using fallback.")
250
- # Fallback: Create minimal valid report
251
- full_report = {
252
- "_id": f"fallback_{phase_name}_{uuid.uuid4().hex[:8]}",
253
- "phase": phase_name,
254
- "status": "partial_success",
255
- "error": str(e),
256
- "ruleBased_result": rule_engine_result,
257
- "vitResult": vit_result,
258
- "kpis": kpis,
259
- "cbhi": {"score": cbhi_score},
260
- "phaseWiseAnalysis": phase_analysis_result.get('phaseWiseAnalysis', [])
261
- }
262
-
263
- print(f"[{phase_name.upper()}] Processing complete.")
264
- return full_report
265
-
266
- except Exception as e:
267
- print(f"[{phase_name.upper()}] Error: {e}")
268
- traceback.print_exc()
269
- # Return a partial error result so the whole request doesn't fail
270
- return {
271
- "error": str(e),
272
- "phase": phase_name
273
- }
274
-
275
-
276
- @app.route('/')
277
- def root():
278
- """Health check endpoint"""
279
- return jsonify({
280
- "status": "healthy",
281
- "service": "DCRM Analysis Flask API",
282
- "version": "2.1.0",
283
- "deployment_url": DEPLOYMENT_URL
284
- })
285
-
286
-
287
- @app.route('/api/health')
288
- def health_check():
289
- """Detailed health check with component status"""
290
- return jsonify({
291
- "status": "healthy",
292
- "components": {
293
- "llm": "operational",
294
- "vit_model": "available" if VIT_AVAILABLE else "unavailable",
295
- "kpi_calculator": "operational",
296
- "rule_engine": "operational",
297
- "ai_agent": "operational",
298
- "phase_analysis": "operational"
299
- },
300
- "deployment_url": DEPLOYMENT_URL
301
- })
302
-
303
-
304
- @app.route('/api/circuit-breakers/<breaker_id>/tests/upload-three-phase', methods=['POST'])
305
- def analyze_three_phase_dcrm(breaker_id):
306
- """
307
- Analyze DCRM test data from 3 uploaded CSV files (R, Y, B phases).
308
- Uses parallel processing with multiple API keys to speed up execution.
309
-
310
- Expected files in request:
311
- - fileR: Red phase CSV
312
- - fileY: Yellow phase CSV
313
- - fileB: Blue phase CSV
314
-
315
- Returns:
316
- - Comprehensive JSON analysis report with combined three-phase results
317
- """
318
-
319
- try:
320
- # Validate files are present
321
- if 'fileR' not in request.files or 'fileY' not in request.files or 'fileB' not in request.files:
322
- return jsonify({
323
- "error": "Missing required files",
324
- "message": "All three phase files are required: fileR, fileY, fileB",
325
- "received": list(request.files.keys())
326
- }), 400
327
-
328
- fileR = request.files['fileR']
329
- fileY = request.files['fileY']
330
- fileB = request.files['fileB']
331
-
332
- # Validate file types
333
- for file in [fileR, fileY, fileB]:
334
- if not file.filename.endswith('.csv'):
335
- return jsonify({
336
- "error": "Invalid file type",
337
- "message": "Only CSV files are accepted",
338
- "received": file.filename
339
- }), 400
340
-
341
- # Prepare DataFrames
342
- dfs = {}
343
- for phase_name, file in [('r', fileR), ('y', fileY), ('b', fileB)]:
344
- file.seek(0)
345
- csv_string = file.read().decode('utf-8')
346
- try:
347
- df = pd.read_csv(StringIO(csv_string))
348
-
349
- # Basic validation
350
- if len(df) < 100:
351
- raise ValueError(f"Insufficient data in {phase_name.upper()} phase")
352
-
353
- dfs[phase_name] = df
354
- except Exception as e:
355
- return jsonify({
356
- "error": f"Error reading {phase_name.upper()} CSV",
357
- "details": str(e)
358
- }), 400
359
-
360
- # Get API Keys
361
- # Fallback to main key if specific ones aren't set
362
- main_key = os.getenv("GOOGLE_API_KEY")
363
- keys = {
364
- 'r': os.getenv("GOOGLE_API_KEY_1", main_key),
365
- 'y': os.getenv("GOOGLE_API_KEY_2", main_key),
366
- 'b': os.getenv("GOOGLE_API_KEY_3", main_key)
367
- }
368
-
369
- # Prepare tasks
370
- tasks = []
371
- for phase in ['r', 'y', 'b']:
372
- tasks.append((dfs[phase], breaker_id, keys[phase], phase))
373
-
374
- # Execute in parallel
375
- results = {}
376
- health_scores = []
377
-
378
- print("Starting parallel processing of 3 phases...")
379
- with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
380
- # Map tasks to futures
381
- future_to_phase = {
382
- executor.submit(process_single_phase_csv, task): task[3]
383
- for task in tasks
384
- }
385
-
386
- for future in concurrent.futures.as_completed(future_to_phase):
387
- phase = future_to_phase[future]
388
- try:
389
- result = future.result()
390
- results[phase] = result
391
- if 'healthScore' in result:
392
- health_scores.append(result['healthScore'])
393
- except Exception as exc:
394
- print(f'{phase} generated an exception: {exc}')
395
- results[phase] = {"error": str(exc)}
396
-
397
- # Combine results into three-phase structure (removed breakerId and operator)
398
- combined_result = {
399
- "_id": str(uuid.uuid4()).replace('-', '')[:24],
400
- "createdAt": datetime.now(timezone.utc).strftime("%a, %d %b %Y %H:%M:%S GMT"),
401
- "healthScore": round(sum(health_scores) / len(health_scores), 1) if health_scores else 0,
402
- "r": results.get('r', {}),
403
- "y": results.get('y', {}),
404
- "b": results.get('b', {})
405
- }
406
-
407
- return jsonify(combined_result), 200
408
-
409
- except Exception as e:
410
- # Log the full error for debugging
411
- error_trace = traceback.format_exc()
412
- print(f"ERROR in three-phase DCRM analysis: {error_trace}")
413
-
414
- # Return clean error to client
415
- return jsonify({
416
- "error": "Analysis failed",
417
- "message": "An error occurred during DCRM analysis",
418
- "error_type": type(e).__name__,
419
- "error_details": str(e)
420
- }), 500
421
-
422
-
423
- if __name__ == "__main__":
424
- # Print all registered routes for debugging
425
- print("Registered Routes:")
426
- print(app.url_map)
427
-
428
- # Run the Flask app
429
- # Run the Flask app
430
- port = int(os.environ.get("PORT", 7860))
431
- app.run(
432
- host="0.0.0.0",
433
- port=port,
434
- debug=False, # Set debug to False for production
435
- use_reloader=False
436
- )
 
1
+ """
2
+ DCRM Analysis Flask API - Three Phase Support
3
+ ==============================================
4
+ Flask API wrapper for the DCRM analysis pipeline.
5
+ Accepts 3 CSV uploads (R, Y, B phases) via POST and returns comprehensive JSON analysis.
6
+
7
+ Endpoint: POST /api/circuit-breakers/{breaker_id}/tests/upload-three-phase
8
+ """
9
+
10
+ import os
11
+ import json
12
+ import traceback
13
+ import uuid
14
+ from datetime import datetime, timezone
15
+ import sys
16
+ import concurrent.futures
17
+
18
+ # Add project root to sys.path to allow importing from core
19
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
20
+
21
+ # Previous Name: flask_app.py
22
+ from flask import Flask, request, jsonify
23
+ from flask_cors import CORS
24
+ from werkzeug.utils import secure_filename
25
+ import pandas as pd
26
+ from io import StringIO
27
+
28
+ # Load environment variables
29
+ from dotenv import load_dotenv
30
+ load_dotenv()
31
+
32
+ # Ensure API key is set
33
+ if not os.getenv("GOOGLE_API_KEY"):
34
+ print("WARNING: GOOGLE_API_KEY not found in environment variables. Please check your .env file.")
35
+
36
+ from langchain_google_genai import ChatGoogleGenerativeAI
37
+ from core.calculators.kpi import calculate_kpis
38
+ from core.calculators.cbhi import compute_cbhi
39
+ from core.signal.phases import analyze_dcrm_data
40
+ from core.engines.rules import analyze_dcrm_advanced
41
+ from core.agents.diagnosis import detect_fault, standardize_input
42
+ from core.utils.report_generator import generate_dcrm_json
43
+ from core.agents.recommendation import generate_recommendations
44
+
45
+ # Optional ViT Model
46
+ try:
47
+ from core.models.vit_classifier import predict_dcrm_image, plot_resistance_for_vit
48
+ VIT_AVAILABLE = True
49
+ except Exception as e:
50
+ print(f"ViT Model not available: {e}")
51
+ VIT_AVAILABLE = False
52
+ predict_dcrm_image = None
53
+ plot_resistance_for_vit = None
54
+
55
+ # =============================================================================
56
+ # CONFIGURATION - CHANGE THIS URL AFTER DEPLOYMENT
57
+ # =============================================================================
58
+ DEPLOYMENT_URL = "http://localhost:5000" # Change this to your deployed URL
59
+ # Example: DEPLOYMENT_URL = "https://your-domain.com"
60
+ # =============================================================================
61
+
62
+ # Initialize Flask app
63
+ app = Flask(__name__)
64
+ CORS(app) # Enable CORS for frontend access
65
+
66
+ def get_llm(api_key=None):
67
+ """
68
+ Factory function to create an LLM instance with a specific API key.
69
+ If no key is provided, falls back to the default GOOGLE_API_KEY.
70
+ """
71
+ if not api_key:
72
+ api_key = os.getenv("GOOGLE_API_KEY")
73
+
74
+ if not api_key:
75
+ raise ValueError("No Google API Key provided and GOOGLE_API_KEY not found in env.")
76
+
77
+ return ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0, google_api_key=api_key)
78
+
79
+
80
+ def process_single_phase_csv(args):
81
+ """
82
+ Process a single phase CSV through the complete DCRM pipeline.
83
+ Designed to be run in a separate thread.
84
+
85
+ Args:
86
+ args: Tuple containing (df, breaker_id, api_key, phase_name)
87
+
88
+ Returns:
89
+ dict: Complete analysis results for one phase
90
+ """
91
+ df, breaker_id, api_key, phase_name = args
92
+
93
+ try:
94
+ print(f"[{phase_name.upper()}] Starting processing with key ending in ...{api_key[-4:] if api_key else 'None'}")
95
+
96
+ # Initialize local LLM for this thread
97
+ llm = get_llm(api_key)
98
+
99
+ # 1. Calculate KPIs
100
+ kpi_results = calculate_kpis(df)
101
+ kpis = kpi_results['kpis']
102
+
103
+ # 2. Phase Segmentation (AI-based)
104
+ phase_analysis_result = analyze_dcrm_data(df, llm)
105
+
106
+ # 3. Prepare KPIs for Rule Engine and AI Agent
107
+ raj_kpis = {
108
+ "Closing Time (ms)": kpis.get('closing_time'),
109
+ "Opening Time (ms)": kpis.get('opening_time'),
110
+ "Contact Speed (m/s)": kpis.get('contact_speed'),
111
+ "DLRO Value (µΩ)": kpis.get('dlro'),
112
+ "Peak Resistance (µΩ)": kpis.get('peak_resistance'),
113
+ "Peak Close Coil Current (A)": kpis.get('peak_close_coil'),
114
+ "Peak Trip Coil 1 Current (A)": kpis.get('peak_trip_coil_1'),
115
+ "Peak Trip Coil 2 Current (A)": kpis.get('peak_trip_coil_2'),
116
+ "SF6 Pressure (bar)": kpis.get('sf6_pressure'),
117
+ "Ambient Temperature (°C)": kpis.get('ambient_temp'),
118
+ "Main Wipe (mm)": kpis.get('main_wipe'),
119
+ "Arc Wipe (mm)": kpis.get('arc_wipe'),
120
+ "Contact Travel Distance (mm)": kpis.get('contact_travel')
121
+ }
122
+
123
+ raj_ai_kpis = {
124
+ "kpis": [
125
+ {"name": "Closing Time", "unit": "ms", "value": kpis.get('closing_time')},
126
+ {"name": "Opening Time", "unit": "ms", "value": kpis.get('opening_time')},
127
+ {"name": "DLRO Value", "unit": "µΩ", "value": kpis.get('dlro')},
128
+ {"name": "Peak Resistance", "unit": "µΩ", "value": kpis.get('peak_resistance')},
129
+ {"name": "Contact Speed", "unit": "m/s", "value": kpis.get('contact_speed')},
130
+ {"name": "Peak Close Coil Current", "unit": "A", "value": kpis.get('peak_close_coil')},
131
+ {"name": "Peak Trip Coil 1 Current", "unit": "A", "value": kpis.get('peak_trip_coil_1')},
132
+ {"name": "Peak Trip Coil 2 Current", "unit": "A", "value": kpis.get('peak_trip_coil_2')},
133
+ {"name": "SF6 Pressure", "unit": "bar", "value": kpis.get('sf6_pressure')},
134
+ {"name": "Ambient Temperature", "unit": "°C", "value": kpis.get('ambient_temp')}
135
+ ]
136
+ }
137
+
138
+ # 4. Standardize resistance data for Rule Engine
139
+ temp_df = df[['Resistance']].copy()
140
+ if len(temp_df) < 401:
141
+ last_val = temp_df.iloc[-1, 0]
142
+ padding = pd.DataFrame({'Resistance': [last_val] * (401 - len(temp_df))})
143
+ temp_df = pd.concat([temp_df, padding], ignore_index=True)
144
+
145
+ std_df = standardize_input(temp_df)
146
+ row_values = std_df.iloc[0].values.tolist()
147
+
148
+ # 5. Run Rule Engine Analysis
149
+ rule_engine_result = analyze_dcrm_advanced(row_values, raj_kpis)
150
+
151
+ # 6. Run AI Agent Analysis with error handling
152
+ try:
153
+ ai_agent_result = detect_fault(df, raj_ai_kpis)
154
+ print(f"[{phase_name.upper()}] AI Agent analysis completed successfully")
155
+ except Exception as e:
156
+ print(f"[{phase_name.upper()}] AI Agent failed: {e}. Using fallback.")
157
+ # Fallback: Use rule engine result as AI result
158
+ ai_agent_result = {
159
+ "Fault_Detection": rule_engine_result.get("Fault_Detection", []),
160
+ "overall_health_assessment": rule_engine_result.get("overall_health_assessment", {}),
161
+ "classifications": rule_engine_result.get("classifications", [])
162
+ }
163
+
164
+ # 7. Run ViT Model (if available)
165
+ vit_result = None
166
+ vit_plot_path = f"temp_vit_plot_{phase_name}_{uuid.uuid4().hex[:8]}.png" # Unique path for parallel safety
167
+
168
+ plot_generated = False
169
+ try:
170
+ if plot_resistance_for_vit and plot_resistance_for_vit(df, vit_plot_path):
171
+ plot_generated = True
172
+ except Exception as e:
173
+ print(f"[{phase_name.upper()}] ViT Plot generation failed: {e}")
174
+
175
+ if plot_generated and VIT_AVAILABLE and predict_dcrm_image:
176
+ try:
177
+ # Pass API key to ViT as well if needed, though currently it might use env var
178
+ # The updated vit_classifier uses requests to a deployed model, so API key is for Gemini part
179
+ vit_class, vit_conf, vit_details = predict_dcrm_image(vit_plot_path, api_key=api_key)
180
+ if vit_class:
181
+ vit_result = {
182
+ "class": vit_class,
183
+ "confidence": vit_conf,
184
+ "details": vit_details
185
+ }
186
+ except Exception as e:
187
+ print(f"[{phase_name.upper()}] ViT Prediction failed: {e}")
188
+ finally:
189
+ # Cleanup temp file
190
+ if os.path.exists(vit_plot_path):
191
+ try:
192
+ os.remove(vit_plot_path)
193
+ except:
194
+ pass
195
+
196
+ # 8. Calculate CBHI Score
197
+ cbhi_phase_data = {}
198
+ if 'phaseWiseAnalysis' in phase_analysis_result:
199
+ for phase in phase_analysis_result['phaseWiseAnalysis']:
200
+ p_name = f"Phase {phase.get('phaseNumber')}"
201
+ cbhi_phase_data[p_name] = {
202
+ "status": phase.get('status', 'Unknown'),
203
+ "confidence": phase.get('confidence', 0)
204
+ }
205
+
206
+ cbhi_score = compute_cbhi(raj_ai_kpis['kpis'], ai_agent_result, cbhi_phase_data)
207
+
208
+ # 9. Generate Recommendations with error handling
209
+ try:
210
+ recommendations = generate_recommendations(
211
+ kpis=kpis,
212
+ cbhi_score=cbhi_score,
213
+ rule_faults=rule_engine_result.get("Fault_Detection", []),
214
+ ai_faults=ai_agent_result.get("Fault_Detection", []),
215
+ llm=llm
216
+ )
217
+ print(f"[{phase_name.upper()}] Recommendations generated successfully")
218
+ except Exception as e:
219
+ print(f"[{phase_name.upper()}] Recommendations failed: {e}. Using fallback.")
220
+ # Fallback: Create basic recommendations from rule engine
221
+ recommendations = {
222
+ "maintenanceActions": [],
223
+ "futureFaultsPdf": []
224
+ }
225
+ # Extract from rule faults
226
+ for fault in rule_engine_result.get("Fault_Detection", []):
227
+ if fault.get("Severity") in ["High", "Critical"]:
228
+ recommendations["maintenanceActions"].append({
229
+ "action": f"Address {fault.get('defect_name')}",
230
+ "priority": "High",
231
+ "timeframe": "Immediate"
232
+ })
233
+
234
+ # 10. Generate Final JSON Report with error handling
235
+ try:
236
+ full_report = generate_dcrm_json(
237
+ df=df,
238
+ kpis=kpis,
239
+ cbhi_score=cbhi_score,
240
+ rule_result=rule_engine_result,
241
+ ai_result=ai_agent_result,
242
+ llm=llm,
243
+ vit_result=vit_result,
244
+ phase_analysis_result=phase_analysis_result,
245
+ recommendations=recommendations
246
+ )
247
+ print(f"[{phase_name.upper()}] Final report generated successfully")
248
+ except Exception as e:
249
+ print(f"[{phase_name.upper()}] Report generation failed: {e}. Using fallback.")
250
+ # Fallback: Create minimal valid report
251
+ full_report = {
252
+ "_id": f"fallback_{phase_name}_{uuid.uuid4().hex[:8]}",
253
+ "phase": phase_name,
254
+ "status": "partial_success",
255
+ "error": str(e),
256
+ "ruleBased_result": rule_engine_result,
257
+ "vitResult": vit_result,
258
+ "kpis": kpis,
259
+ "cbhi": {"score": cbhi_score},
260
+ "phaseWiseAnalysis": phase_analysis_result.get('phaseWiseAnalysis', [])
261
+ }
262
+
263
+ print(f"[{phase_name.upper()}] Processing complete.")
264
+ return full_report
265
+
266
+ except Exception as e:
267
+ print(f"[{phase_name.upper()}] Error: {e}")
268
+ traceback.print_exc()
269
+ # Return a partial error result so the whole request doesn't fail
270
+ return {
271
+ "error": str(e),
272
+ "phase": phase_name
273
+ }
274
+
275
+
276
+ @app.route("/")
277
+ def home():
278
+ return {
279
+ "service": "DCRM Analysis Flask API",
280
+ "status": "healthy",
281
+ "message": "Flask API running on Hugging Face!"
282
+ }
283
+
284
+
285
+
286
+ @app.route('/api/health')
287
+ def health_check():
288
+ """Detailed health check with component status"""
289
+ return jsonify({
290
+ "status": "healthy",
291
+ "components": {
292
+ "llm": "operational",
293
+ "vit_model": "available" if VIT_AVAILABLE else "unavailable",
294
+ "kpi_calculator": "operational",
295
+ "rule_engine": "operational",
296
+ "ai_agent": "operational",
297
+ "phase_analysis": "operational"
298
+ },
299
+ "deployment_url": DEPLOYMENT_URL
300
+ })
301
+
302
+
303
+ @app.route('/api/circuit-breakers/<breaker_id>/tests/upload-three-phase', methods=['POST'])
304
+ def analyze_three_phase_dcrm(breaker_id):
305
+ """
306
+ Analyze DCRM test data from 3 uploaded CSV files (R, Y, B phases).
307
+ Uses parallel processing with multiple API keys to speed up execution.
308
+
309
+ Expected files in request:
310
+ - fileR: Red phase CSV
311
+ - fileY: Yellow phase CSV
312
+ - fileB: Blue phase CSV
313
+
314
+ Returns:
315
+ - Comprehensive JSON analysis report with combined three-phase results
316
+ """
317
+
318
+ try:
319
+ # Validate files are present
320
+ if 'fileR' not in request.files or 'fileY' not in request.files or 'fileB' not in request.files:
321
+ return jsonify({
322
+ "error": "Missing required files",
323
+ "message": "All three phase files are required: fileR, fileY, fileB",
324
+ "received": list(request.files.keys())
325
+ }), 400
326
+
327
+ fileR = request.files['fileR']
328
+ fileY = request.files['fileY']
329
+ fileB = request.files['fileB']
330
+
331
+ # Validate file types
332
+ for file in [fileR, fileY, fileB]:
333
+ if not file.filename.endswith('.csv'):
334
+ return jsonify({
335
+ "error": "Invalid file type",
336
+ "message": "Only CSV files are accepted",
337
+ "received": file.filename
338
+ }), 400
339
+
340
+ # Prepare DataFrames
341
+ dfs = {}
342
+ for phase_name, file in [('r', fileR), ('y', fileY), ('b', fileB)]:
343
+ file.seek(0)
344
+ csv_string = file.read().decode('utf-8')
345
+ try:
346
+ df = pd.read_csv(StringIO(csv_string))
347
+
348
+ # Basic validation
349
+ if len(df) < 100:
350
+ raise ValueError(f"Insufficient data in {phase_name.upper()} phase")
351
+
352
+ dfs[phase_name] = df
353
+ except Exception as e:
354
+ return jsonify({
355
+ "error": f"Error reading {phase_name.upper()} CSV",
356
+ "details": str(e)
357
+ }), 400
358
+
359
+ # Get API Keys
360
+ # Fallback to main key if specific ones aren't set
361
+ main_key = os.getenv("GOOGLE_API_KEY")
362
+ keys = {
363
+ 'r': os.getenv("GOOGLE_API_KEY_1", main_key),
364
+ 'y': os.getenv("GOOGLE_API_KEY_2", main_key),
365
+ 'b': os.getenv("GOOGLE_API_KEY_3", main_key)
366
+ }
367
+
368
+ # Prepare tasks
369
+ tasks = []
370
+ for phase in ['r', 'y', 'b']:
371
+ tasks.append((dfs[phase], breaker_id, keys[phase], phase))
372
+
373
+ # Execute in parallel
374
+ results = {}
375
+ health_scores = []
376
+
377
+ print("Starting parallel processing of 3 phases...")
378
+ with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
379
+ # Map tasks to futures
380
+ future_to_phase = {
381
+ executor.submit(process_single_phase_csv, task): task[3]
382
+ for task in tasks
383
+ }
384
+
385
+ for future in concurrent.futures.as_completed(future_to_phase):
386
+ phase = future_to_phase[future]
387
+ try:
388
+ result = future.result()
389
+ results[phase] = result
390
+ if 'healthScore' in result:
391
+ health_scores.append(result['healthScore'])
392
+ except Exception as exc:
393
+ print(f'{phase} generated an exception: {exc}')
394
+ results[phase] = {"error": str(exc)}
395
+
396
+ # Combine results into three-phase structure (removed breakerId and operator)
397
+ combined_result = {
398
+ "_id": str(uuid.uuid4()).replace('-', '')[:24],
399
+ "createdAt": datetime.now(timezone.utc).strftime("%a, %d %b %Y %H:%M:%S GMT"),
400
+ "healthScore": round(sum(health_scores) / len(health_scores), 1) if health_scores else 0,
401
+ "r": results.get('r', {}),
402
+ "y": results.get('y', {}),
403
+ "b": results.get('b', {})
404
+ }
405
+
406
+ return jsonify(combined_result), 200
407
+
408
+ except Exception as e:
409
+ # Log the full error for debugging
410
+ error_trace = traceback.format_exc()
411
+ print(f"ERROR in three-phase DCRM analysis: {error_trace}")
412
+
413
+ # Return clean error to client
414
+ return jsonify({
415
+ "error": "Analysis failed",
416
+ "message": "An error occurred during DCRM analysis",
417
+ "error_type": type(e).__name__,
418
+ "error_details": str(e)
419
+ }), 500
420
+
421
+ if __name__ == "__main__":
422
+ print("Registered Routes:")
423
+ print(app.url_map)
424
+
425
+ port = int(os.environ.get("PORT", 7860))
426
+ app.run(
427
+ host="0.0.0.0",
428
+ port=port,
429
+ debug=False,
430
+ use_reloader=False
431
+ )