diff --git a/backend/__pycache__/models.cpython-313.pyc b/backend/__pycache__/models.cpython-313.pyc index fc4be5d..38b37e5 100644 Binary files a/backend/__pycache__/models.cpython-313.pyc and b/backend/__pycache__/models.cpython-313.pyc differ diff --git a/backend/main.py b/backend/main.py index 5bbef7a..ad3f77b 100644 --- a/backend/main.py +++ b/backend/main.py @@ -5,7 +5,8 @@ import json import csv import io import time -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone, time as dt_time +from zoneinfo import ZoneInfo from flask import Flask, jsonify, request, Response from flask_socketio import SocketIO, join_room from flask_cors import CORS @@ -55,6 +56,8 @@ app.config['SECRET_KEY'] = os.getenv("SECRET_KEY", "a_very_secret_key") db.init_app(app) socketio = SocketIO(app, cors_allowed_origins="*") +IST = ZoneInfo("Asia/Kolkata") + # --- User Loader for Flask-Login --- @login_manager.user_loader def load_user(user_id): @@ -283,25 +286,29 @@ def get_stations(): @app.route('/api/stations/daily-stats', methods=['GET']) def get_all_station_stats(): """ - Calculates the swap statistics for today for all stations. + Calculates the swap statistics for the current calendar day in the IST timezone. """ try: - # --- CHANGE THESE TWO LINES --- - today_start = datetime.combine(datetime.utcnow().date(), time.min) - today_end = datetime.combine(datetime.utcnow().date(), time.max) + # Get the current time and date in your timezone (IST is defined globally) + now_ist = datetime.now(IST) + today_ist = now_ist.date() - # This is an efficient query that groups by station_id and counts events in one go + # Calculate the precise start and end of that day + start_of_day_ist = datetime.combine(today_ist, dt_time.min, tzinfo=IST) + end_of_day_ist = datetime.combine(today_ist, dt_time.max, tzinfo=IST) + + # --- The rest of the query uses this new date range --- stats = db.session.query( MqttLog.station_id, - func.count(case((MqttLog.payload['eventType'] == 'EVENT_SWAP_START', 1))).label('total_starts'), - func.count(case((MqttLog.payload['eventType'] == 'EVENT_SWAP_ENDED', 1))).label('completed'), - func.count(case((MqttLog.payload['eventType'] == 'EVENT_SWAP_ABORTED', 1))).label('aborted') + func.count(case((MqttLog.payload['eventType'].astext == 'EVENT_SWAP_START', 1))).label('total_starts'), + func.count(case((MqttLog.payload['eventType'].astext == 'EVENT_SWAP_ENDED', 1))).label('completed'), + func.count(case((MqttLog.payload['eventType'].astext == 'EVENT_SWAP_ABORTED', 1))).label('aborted') ).filter( MqttLog.topic_type == 'EVENTS', - MqttLog.timestamp.between(today_start, today_end) + # Use the new timezone-aware date range + MqttLog.timestamp.between(start_of_day_ist, end_of_day_ist) ).group_by(MqttLog.station_id).all() - # Convert the list of tuples into a dictionary for easy lookup stats_dict = { station_id: { "total_starts": total_starts, diff --git a/frontend/js/station_selection.js b/frontend/js/station_selection.js index 641ee7f..f6b5c97 100644 --- a/frontend/js/station_selection.js +++ b/frontend/js/station_selection.js @@ -117,7 +117,10 @@ document.addEventListener('DOMContentLoaded', () => { //-- NEW: Fetch and apply daily stats to each card --- const fetchAndApplyStats = async () => { try { - const response = await fetch(`${API_BASE}/stations/daily-stats`); + const response = await fetch(`${API_BASE}/stations/daily-stats`, { + method: 'GET', + credentials: 'include' // <-- ADD THIS LINE + }); if (!response.ok) return; // Fail silently if stats aren't available const stats = await response.json(); @@ -158,7 +161,10 @@ document.addEventListener('DOMContentLoaded', () => { } try { - const response = await fetch(`${API_BASE}/stations/${stationId}`, { method: 'DELETE' }); + const response = await fetch(`${API_BASE}/stations/${stationId}`, { + method: 'DELETE', + credentials: 'include' // <-- ADD THIS LINE + }); if (response.ok) { alert(`Station "${stationName}" removed successfully.`); allStations = []; // Force a full refresh on next poll @@ -175,26 +181,61 @@ document.addEventListener('DOMContentLoaded', () => { }); // --- DATA FETCHING & POLLING --- + // const loadAndPollStations = async () => { + // try { + // const response = await fetch(`${API_BASE}/stations`, { + // method: 'GET', + // credentials: 'include' // <-- ADD THIS LINE + // }); + // if (!response.ok) throw new Error('Failed to fetch stations'); + + // const newStationList = await response.json(); + + // // If the number of stations has changed, we must do a full re-render. + // if (newStationList.length !== allStations.length) { + // allStations = newStationList; + // renderStations(allStations); + // } else { + // // Otherwise, we can do a more efficient status-only update. + // allStations = newStationList; + // updateStationStatuses(allStations); + // fetchAndApplyStats(); // Fetch and update daily stats + // } + // } catch (error) { + // console.error(error); + // stationCountEl.textContent = 'Could not load stations. Is the backend running?'; + // if (pollingInterval) clearInterval(pollingInterval); + // } + // }; + const loadAndPollStations = async () => { try { - const response = await fetch(`${API_BASE}/stations`); + const response = await fetch(`${API_BASE}/stations`, { credentials: 'include' }); + if (response.status === 401) { + localStorage.clear(); + window.location.href = 'index.html'; + return; + } if (!response.ok) throw new Error('Failed to fetch stations'); const newStationList = await response.json(); - // If the number of stations has changed, we must do a full re-render. if (newStationList.length !== allStations.length) { allStations = newStationList; renderStations(allStations); } else { - // Otherwise, we can do a more efficient status-only update. allStations = newStationList; - updateStationStatuses(allStations); - fetchAndApplyStats(); // Fetch and update daily stats + // A more efficient status-only update could go here later + renderStations(allStations); // Re-render to update statuses } + + // --- THIS IS THE FIX --- + // Call this AFTER the if/else, so it always runs on a successful fetch. + fetchAndApplyStats(); + } catch (error) { console.error(error); - stationCountEl.textContent = 'Could not load stations. Is the backend running?'; + if (stationCountEl) stationCountEl.textContent = 'Could not load stations. Is the backend running?'; if (pollingInterval) clearInterval(pollingInterval); } }; diff --git a/frontend/station_selection.html b/frontend/station_selection.html index a4f3036..9ba2c69 100644 --- a/frontend/station_selection.html +++ b/frontend/station_selection.html @@ -224,221 +224,204 @@ - + // --- MODIFIED: Start Everything on Page Load --- + document.addEventListener('DOMContentLoaded', () => { + loadAndPollStations(); // Load once immediately + pollingInterval = setInterval(loadAndPollStations, 10000); // Then poll every 10 seconds + }); + +