11from datetime import date , timedelta
22from decimal import Decimal
33import json
4+ from boto3 .dynamodb .conditions import Key
45import re
56from typing import List
67from urllib .parse import urlencode
910import requests
1011
1112from chalicelib import constants , dynamo
12- from chalicelib .delays .aggregate import group_weekly_data
13+ from chalicelib .delays .aggregate import group_weekly_data , group_daily_data
1314from chalicelib .delays .types import Alert , AlertsRequest
1415
15- TABLE_NAME = "AlertDelaysWeekly"
16+ WEEKLY_TABLE_NAME = "AlertDelaysWeekly"
17+ DAILY_TABLE_NAME = "AlertDelaysDaily"
1618
1719
1820def generate_requests (start_date : date , end_date : date , lines = constants .ALL_LINES ) -> List [AlertsRequest ]:
@@ -193,23 +195,8 @@ def process_delay_time(alerts: List[Alert]):
193195 }
194196 )
195197 total_delay = 0
196- delay_by_type = {
197- "disabled_vehicle" : 0 ,
198- "signal_problem" : 0 ,
199- "power_problem" : 0 ,
200- "door_problem" : 0 ,
201- "brake_problem" : 0 ,
202- "switch_problem" : 0 ,
203- "track_issue" : 0 ,
204- "mechanical_problem" : 0 ,
205- "track_work" : 0 ,
206- "car_traffic" : 0 ,
207- "police_activity" : 0 ,
208- "medical_emergency" : 0 ,
209- "fire" : 0 ,
210- "flooding" : 0 ,
211- "other" : 0 ,
212- }
198+ delay_by_type = constants .DELAY_BY_TYPE .copy ()
199+
213200 for delay in delays :
214201 if (delay is None ) or (len (delay ) == 0 ):
215202 continue
@@ -227,16 +214,21 @@ def process_requests(requests: List[AlertsRequest], lines=constants.ALL_LINES):
227214
228215 for request in requests :
229216 data = process_single_day (request )
217+ # Initializing at 0 regardless of condition
218+ total_delay = 0
219+ delay_by_type = constants .DELAY_BY_TYPE .copy ()
220+
230221 if data is not None and len (data ) != 0 :
231222 total_delay , delay_by_type = process_delay_time (data )
232- all_data [request .route ].append (
233- {
234- "date" : request .date .isoformat (),
235- "line" : request .route ,
236- "total_delay_time" : total_delay ,
237- "delay_by_type" : delay_by_type ,
238- }
239- )
223+ # We should always append zero records just in case
224+ all_data [request .route ].append (
225+ {
226+ "date" : request .date .isoformat (),
227+ "line" : request .route ,
228+ "total_delay_time" : total_delay ,
229+ "delay_by_type" : delay_by_type ,
230+ }
231+ )
240232
241233 df_data = {}
242234 for line in lines :
@@ -249,6 +241,55 @@ def process_requests(requests: List[AlertsRequest], lines=constants.ALL_LINES):
249241 return df_data
250242
251243
244+ def get_daily_data_for_week (start_date : date , end_date : date , lines = constants .ALL_LINES ):
245+ """
246+ Query daily data from DynamoDB for weekly aggregation.
247+ Done as opposed to performing another API call for weekly aggregation.
248+ Also guarantees complete info for every day.
249+ """
250+ daily_records = []
251+
252+ for line in lines :
253+ # Convert dates to ISO format strings to match DynamoDB storage format
254+ start_date_str = start_date .isoformat ()
255+ end_date_str = end_date .isoformat ()
256+
257+ # Query DynamoDB for this line and date range
258+ params = {
259+ "KeyConditionExpression" : Key ("line" ).eq (line )
260+ & Key ("date" ).between (start_date_str , end_date_str ) # Use string format
261+ }
262+ try :
263+ line_data = dynamo .query_dynamo (params , DAILY_TABLE_NAME )
264+ daily_records .extend (line_data )
265+ except Exception as e :
266+ print (f"Error querying { line } : { e } " )
267+
268+ return daily_records
269+
270+
271+ def update_weekly_from_daily (start_date : date , end_date : date , lines = constants .ALL_LINES ):
272+ """
273+ Update weekly table by aggregating daily data from DynamoDB.
274+ Avoids another api call.
275+ """
276+
277+ # Get daily data from DynamoDB instead of API
278+ daily_records = get_daily_data_for_week (start_date , end_date , lines )
279+
280+ # Convert to DataFrame and process
281+ df = pd .DataFrame (daily_records )
282+ df ["date" ] = pd .to_datetime (df ["date" ])
283+ df .set_index ("date" , inplace = True )
284+
285+ # Aggregate weekly for each line
286+ weekly_data = []
287+ for line , line_df in df .groupby ("line" ):
288+ weekly_data .extend (group_weekly_data (line_df , start_date .isoformat ()))
289+
290+ dynamo .dynamo_batch_write (json .loads (json .dumps (weekly_data ), parse_float = Decimal ), WEEKLY_TABLE_NAME )
291+
292+
252293def update_table (start_date : date , end_date : date , lines = constants .ALL_LINES ):
253294 """
254295 Update the table with rapid transit data
@@ -258,12 +299,13 @@ def update_table(start_date: date, end_date: date, lines=constants.ALL_LINES):
258299
259300 grouped_data = []
260301 for line , df in all_data .items ():
261- grouped_data .extend (group_weekly_data (df , start_date .isoformat ()))
262-
263- dynamo .dynamo_batch_write (json .loads (json .dumps (grouped_data ), parse_float = Decimal ), TABLE_NAME )
302+ grouped_data .extend (group_daily_data (df , start_date .isoformat ()))
303+ dynamo .dynamo_batch_write (json .loads (json .dumps (grouped_data ), parse_float = Decimal ), DAILY_TABLE_NAME )
264304
265305
306+ # Testing daily updates. Using random dates. Feel free to change and uncomment as needed.
266307if __name__ == "__main__" :
267- start_date = date (2025 , 6 , 1 )
268- end_date = date (2025 , 8 , 10 )
269- update_table (start_date , end_date , constants .ALL_LINES )
308+ start_date = date (2025 , 9 , 15 )
309+ end_date = date (2025 , 9 , 21 )
310+ # update_table(start_date, end_date, constants.ALL_LINES)
311+ # update_weekly_from_daily(start_date, end_date, constants.ALL_LINES)
0 commit comments