diff --git a/backend/main.py b/backend/main.py index cbea79c..6bba16e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -108,6 +108,11 @@ def on_message_handler(station_id, topic, payload): 'data': decoded_data }, room=station_id) + # If the message is an EVENT or PERIODIC data, it could affect analytics. + if message_type in ['EVENTS', 'PERIODIC']: + print(f"Analytics-related data received ({message_type}). Notifying clients to refresh.") + socketio.emit('analytics_updated', room=station_id) + # --- (WebSocket and API routes remain the same) --- @socketio.on('connect') def handle_connect(): @@ -367,7 +372,7 @@ def get_analytics_data(): MqttLog.station_id == station_id, MqttLog.topic_type == 'EVENTS', MqttLog.timestamp.between(start_datetime, end_datetime) - ).all() + ).order_by(MqttLog.timestamp.asc()).all() # <-- ADD THIS SORTING except Exception as e: return jsonify({"message": f"Could not query event logs: {e}"}), 500 @@ -381,35 +386,83 @@ def get_analytics_data(): except Exception as e: return jsonify({"message": f"Could not query periodic logs: {e}"}), 500 - # 3. Process EVENT logs for swap KPIs and charts - total_swaps, completed_swaps, aborted_swaps = 0, 0, 0 - completed_swap_times, daily_completed, daily_aborted, hourly_swaps, abort_reason_counts = [], {}, {}, [0] * 24, {} - slot_utilization_counts = {i: 0 for i in range(1, 10)} # For the heatmap + # # 3. Process EVENT logs for swap KPIs and charts + # total_swaps, completed_swaps, aborted_swaps = 0, 0, 0 + # completed_swap_times, daily_completed, daily_aborted, hourly_swaps, abort_reason_counts = [], {}, {}, [0] * 24, {} + # slot_utilization_counts = {i: 0 for i in range(1, 10)} # For the heatmap + # for log in event_logs: + # # (This processing logic is unchanged) + # event_type = log.payload.get('eventType') + # log_date = log.timestamp.date() + # log_hour = log.timestamp.hour + # if event_type == 'EVENT_SWAP_START': + # total_swaps += 1 + # hourly_swaps[log_hour] += 1 + # elif event_type == 'EVENT_SWAP_ENDED': + # completed_swaps += 1 + # daily_completed[log_date] = daily_completed.get(log_date, 0) + 1 + # swap_time = log.payload.get('eventData', {}).get('swapTime') + # if swap_time is not None: + # completed_swap_times.append(swap_time) + # elif event_type == 'EVENT_SWAP_ABORTED': + # aborted_swaps += 1 + # daily_aborted[log_date] = daily_aborted.get(log_date, 0) + 1 + # reason = log.payload.get('eventData', {}).get('swapAbortReason', 'ABORT_UNKNOWN') + # abort_reason_counts[reason] = abort_reason_counts.get(reason, 0) + 1 + # elif event_type == 'EVENT_BATTERY_EXIT': + # slot_id = log.payload.get('eventData', {}).get('slotId') + # if slot_id and slot_id in slot_utilization_counts: + # slot_utilization_counts[slot_id] += 1 + + # --- 3. REVISED: Process logs to calculate KPIs and chart data --- + swap_starts = {} # Dictionary to store start times by sessionId + completed_swap_times = [] + + total_swaps, completed_swaps, aborted_swaps = 0, 0, 0 + daily_completed, daily_aborted, hourly_swaps, abort_reason_counts = {}, {}, [0] * 24, {} + slot_utilization_counts = {i: 0 for i in range(1, 10)} + + print("\n--- STARTING SWAP ANALYSIS ---") # Add this line for log in event_logs: - # (This processing logic is unchanged) event_type = log.payload.get('eventType') + session_id = log.payload.get('sessionId') log_date = log.timestamp.date() log_hour = log.timestamp.hour + if event_type == 'EVENT_SWAP_START': total_swaps += 1 hourly_swaps[log_hour] += 1 + if session_id: + swap_starts[session_id] = log.timestamp # Store start time + print(f"Found START for session '{session_id}' at {log.timestamp}") # Add this line + elif event_type == 'EVENT_SWAP_ENDED': completed_swaps += 1 daily_completed[log_date] = daily_completed.get(log_date, 0) + 1 - swap_time = log.payload.get('eventData', {}).get('swapTime') - if swap_time is not None: - completed_swap_times.append(swap_time) + if session_id and session_id in swap_starts: + # Calculate duration if we have a matching start event + duration = (log.timestamp - swap_starts[session_id]).total_seconds() + completed_swap_times.append(duration) + print(f"Found MATCHING END for session '{session_id}'. Duration: {duration}s") # Add this line + del swap_starts[session_id] # Remove to prevent reuse + else: + print(f"Found END event but could not find matching START for session '{session_id}'") # Add this line + elif event_type == 'EVENT_SWAP_ABORTED': aborted_swaps += 1 daily_aborted[log_date] = daily_aborted.get(log_date, 0) + 1 reason = log.payload.get('eventData', {}).get('swapAbortReason', 'ABORT_UNKNOWN') abort_reason_counts[reason] = abort_reason_counts.get(reason, 0) + 1 + elif event_type == 'EVENT_BATTERY_EXIT': slot_id = log.payload.get('eventData', {}).get('slotId') if slot_id and slot_id in slot_utilization_counts: slot_utilization_counts[slot_id] += 1 + print(f"--- ANALYSIS COMPLETE ---") # Add this line + print(f"Calculated Durations: {completed_swap_times}") # Add this line + # --- NEW: 4. Calculate Station Uptime --- total_period_seconds = (end_datetime - start_datetime).total_seconds() total_downtime_seconds = 0 @@ -437,9 +490,28 @@ def get_analytics_data(): station_uptime = 100 * (1 - (total_downtime_seconds / total_period_seconds)) station_uptime = max(0, min(100, station_uptime)) # Ensure value is between 0 and 100 + # if not periodic_logs: + # total_downtime_seconds = total_period_seconds + # else: + # first_gap = (periodic_logs[0].timestamp - start_datetime).total_seconds() + # if first_gap > MAX_ONLINE_GAP_SECONDS: + # total_downtime_seconds += first_gap + # for i in range(1, len(periodic_logs)): + # gap = (periodic_logs[i].timestamp - periodic_logs[i-1].timestamp).total_seconds() + # if gap > MAX_ONLINE_GAP_SECONDS: + # total_downtime_seconds += gap + # last_gap = (end_datetime - periodic_logs[-1].timestamp).total_seconds() + # if last_gap > MAX_ONLINE_GAP_SECONDS: + # total_downtime_seconds += last_gap + # station_uptime = 100 * (1 - (total_downtime_seconds / total_period_seconds)) + # station_uptime = max(0, min(100, station_uptime)) + # 5. Prepare final data structures (KPI section is now updated) avg_swap_time_seconds = sum(completed_swap_times) / len(completed_swap_times) if completed_swap_times else 0 + + # avg_swap_time_seconds = sum(completed_swap_times) / len(completed_swap_times) if completed_swap_times else None + kpi_data = { "total_swaps": total_swaps, "completed_swaps": completed_swaps, "aborted_swaps": aborted_swaps, "avg_swap_time_seconds": avg_swap_time_seconds, diff --git a/frontend/analytics.html b/frontend/analytics.html index 9f4768d..287e336 100644 --- a/frontend/analytics.html +++ b/frontend/analytics.html @@ -205,7 +205,10 @@
Avg. Swap Time
-... min
++ ... + min +
Station Uptime
@@ -224,11 +227,6 @@