66from backend .models .session_laps import SessionLaps
77from backend .models .session_result import SessionResult
88from backend .models .sessions import F1Session
9- from sqlalchemy import select
9+ from backend .models .teams import Teams
10+
11+ from sqlalchemy import select , exists
1012from sqlmodel import Session , distinct , select , desc
13+ from sqlalchemy .dialects .postgresql import insert
1114from collections import defaultdict
12-
13- from backend .models .teams import Teams
15+ from sqlalchemy import bindparam , text
1416
1517"""
1618This is the script that updates the database.
1719"""
1820
19- def fetch_latest_session (session : Session ) -> str :
21+ def fetch_latest_session (session : Session ) -> F1Session | None :
2022 """
2123 Queries the database to find the latest session we have a record of.
2224 :param session: Database session
2325 :return: Latest 'F1Session' session
2426 """
2527 result = session .exec (
26- select (F1Session . date ).order_by (desc (F1Session .date ))
28+ select (F1Session ).order_by (desc (F1Session .date ))
2729 ).first ()
2830 return result
2931
@@ -58,6 +60,32 @@ def add_meetings_to_db(session: Session):
5860 session .add (new_meeting )
5961 logger .info (f"Staged meeting { str (meeting ['meeting_key' ])} for addition." )
6062
63+ def add_current_meeting (session , meeting_key ):
64+ existing = session .get (Event , meeting_key )
65+
66+ if existing :
67+ return
68+
69+ meeting = get_data (f'{ URL_BASE } meetings?meeting_key={ meeting_key } ' )[0 ]
70+
71+ if not meeting :
72+ logger .warning (f"No meeting data for meeting_key={ meeting_key } " )
73+ return
74+
75+ new_meeting = Event (
76+ meeting_key = meeting .get ('meeting_key' ),
77+ circuit_key = meeting .get ('circuit_key' ),
78+ location = meeting .get ('location' ),
79+ country_name = meeting .get ('country_name' ),
80+ circuit_name = meeting .get ('circuit_short_name' ),
81+ meeting_official_name = meeting .get ('meeting_official_name' ),
82+ year = meeting .get ('year' )
83+ )
84+
85+ session .add (new_meeting )
86+ session .flush ()
87+
88+
6189def add_session_to_db (session :Session , f1session : dict ):
6290 """
6391 Adds an F1Session to the database.
@@ -162,71 +190,118 @@ def add_drivers_and_session_links(session: Session, session_key: int, all_driver
162190
163191def add_all_laps_for_session (session : Session , session_key : int ):
164192 """
165- Efficiently fetches and adds all laps for all drivers in a session.
193+ Fetch lap/stint/driver data, ensure drivers are linked, then batch upsert laps.
194+ Assumes the caller manages transactions (e.g. with session.begin()).
166195 """
167196 logger .info (f"Starting bulk lap/stint processing for session { session_key } ." )
168197
169- # bulk fetch all lap and stint data for a session
170- laps_url = URL_BASE + f'laps?session_key={ str ( session_key ) } '
171- all_laps_data = get_data (laps_url )
198+ # fetch remote data
199+ laps_url = URL_BASE + f'laps?session_key={ session_key } '
200+ all_laps_data = get_data (laps_url ) or []
172201
173- stints_url = URL_BASE + f"stints?session_key={ str ( session_key ) } "
174- all_stints_data = get_data (stints_url )
202+ stints_url = URL_BASE + f"stints?session_key={ session_key } "
203+ all_stints_data = get_data (stints_url ) or []
175204
176- # group data for ease of access
205+ drivers_url = URL_BASE + f"drivers?session_key={ session_key } "
206+ all_drivers_data = get_data (drivers_url ) or []
207+
208+ # group by driver_number for quick lookup
177209 laps_by_driver = defaultdict (list )
178210 for lap in all_laps_data :
179- laps_by_driver [lap ['driver_number' ]].append (lap )
211+ dn = lap .get ('driver_number' )
212+ if dn is None :
213+ continue
214+ laps_by_driver [dn ].append (lap )
180215
181216 stints_by_driver = defaultdict (list )
182217 for stint in all_stints_data :
183- stints_by_driver [ stint [ 'driver_number' ]]. append ( stint )
184-
185- drivers_url = URL_BASE + f"drivers?session_key= { str ( session_key ) } "
186- all_drivers_data = get_data ( drivers_url )
218+ dn = stint . get ( 'driver_number' )
219+ if dn is None :
220+ continue
221+ stints_by_driver [ dn ]. append ( stint )
187222
188- # ensure all drivers and their session links are in the DB
223+ # ensure drivers/ session links exist
189224 add_drivers_and_session_links (session , session_key , all_drivers_data )
190225
191- # make one query to fetch all laps for that session to check existence
192- existing_driver_laps = session .exec (
226+ # fetch existing laps for this session that already have a compound
227+ rows = session .exec (
193228 select (SessionLaps .driver_id , SessionLaps .lap_number ).where (
194- SessionLaps .session_key == session_key
195- ))
229+ (SessionLaps .session_key == session_key ) & (SessionLaps .compound != None )
230+ )
231+ ).all ()
232+ existing_laps = {(r [0 ], r [1 ]) for r in rows } if rows else set ()
196233
197- existing_laps = {(lap .driver_id , lap .lap_number ) for lap in existing_driver_laps }
234+ # collect the parameter dicts we want to upsert
235+ values_to_upsert : list [dict ] = []
198236
199237 for driver_data in all_drivers_data :
200- driver_number = driver_data [ 'driver_number' ]
201- driver_id = driver_data [ 'driver_id' ]
238+ driver_number = driver_data . get ( 'driver_number' )
239+ driver_id = driver_data . get ( 'driver_id' )
202240
203- # get laps and stints for this driver from our in-memory groups
204- driver_laps = laps_by_driver .get (driver_number , [])
205- driver_stints = stints_by_driver .get (driver_number , [])
241+ if driver_number is None or driver_id is None :
242+ continue
206243
244+ driver_laps = laps_by_driver .get (driver_number , [])
207245 if not driver_laps :
208246 continue
209247
210- stints_hashmap = map_stints_laps (driver_stints )
248+ stints_hashmap = map_stints_laps (stints_by_driver . get ( driver_number , []) )
211249
212250 for lap in driver_laps :
251+ lap_num = lap .get ('lap_number' )
252+ if lap_num is None :
253+ continue
213254
214- if (driver_id , lap . get ( 'lap_number' ) ) in existing_laps :
255+ if (driver_id , lap_num ) in existing_laps :
215256 continue
216257
217- compound = stints_hashmap .get (lap [ 'lap_number' ], FALLBACK_COMPOUND )
258+ compound = stints_hashmap .get (lap_num , None )
218259
219- new_lap = SessionLaps (
220- driver_id = driver_id ,
221- session_key = lap .get ('session_key' ),
222- lap_number = lap .get ('lap_number' ),
223- is_pit_out_lap = lap .get ('is_pit_out_lap' ),
224- lap_time = lap .get ('lap_duration' , 0.0 ),
225- st_speed = lap .get ('st_speed' , 0 ),
226- compound = compound
227- )
228- session .add (new_lap )
229- logger .info (f"Staged all laps for session { session_key } ." )
260+ values_to_upsert .append ({
261+ 'driver_id' : driver_id ,
262+ 'session_key' : lap .get ('session_key' ),
263+ 'lap_number' : lap_num ,
264+ 'is_pit_out_lap' : lap .get ('is_pit_out_lap' ),
265+ 'lap_time' : lap .get ('lap_duration' , 0.0 ),
266+ 'st_speed' : lap .get ('st_speed' , 0 ),
267+ 'compound' : compound ,
268+ })
269+
270+ if not values_to_upsert :
271+ logger .info (f"No new laps to upsert for session { session_key } ." )
272+ return
273+
274+ stmt = insert (SessionLaps ).values (
275+ driver_id = bindparam ('driver_id' ),
276+ session_key = bindparam ('session_key' ),
277+ lap_number = bindparam ('lap_number' ),
278+ is_pit_out_lap = bindparam ('is_pit_out_lap' ),
279+ lap_time = bindparam ('lap_time' ),
280+ st_speed = bindparam ('st_speed' ),
281+ compound = bindparam ('compound' ),
282+ )
283+
284+ where_clause_expr = (
285+ SessionLaps .compound .is_distinct_from (stmt .excluded .compound )
286+ | SessionLaps .lap_time .is_distinct_from (stmt .excluded .lap_time )
287+ | SessionLaps .st_speed .is_distinct_from (stmt .excluded .st_speed )
288+ | SessionLaps .is_pit_out_lap .is_distinct_from (stmt .excluded .is_pit_out_lap )
289+ )
290+
291+ do_update = stmt .on_conflict_do_update (
292+ index_elements = ['driver_id' , 'session_key' , 'lap_number' ],
293+ set_ = {
294+ 'is_pit_out_lap' : stmt .excluded .is_pit_out_lap ,
295+ 'lap_time' : stmt .excluded .lap_time ,
296+ 'st_speed' : stmt .excluded .st_speed ,
297+ 'compound' : stmt .excluded .compound ,
298+ },
299+ where = where_clause_expr
300+ )
301+
302+ session .execute (do_update , values_to_upsert )
303+
304+ logger .info (f"Upserted { len (values_to_upsert )} laps for session { session_key } ." )
230305
231306def add_session_result_to_db (session :Session , session_key :int ):
232307 """
@@ -278,7 +353,7 @@ def add_session_result_to_db(session:Session, session_key:int):
278353 session .add (sr_entry )
279354 logger .info (f"Staged session results of session { str (session_key )} for addition." )
280355
281- def add_teams_colors (session :Session ):
356+ def add_teams_colors (session :Session , year = None ):
282357 try :
283358 teams = session .exec (select (distinct (SessionDriver .team )))
284359 existing_teams_query = session .exec (select (Teams .name ))
@@ -310,27 +385,29 @@ def update_db():
310385 Controls the flow to update the database, calling all necessary methods.
311386 """
312387 with Session (engine ) as session :
313- latest_session_date = fetch_latest_session (session )
314-
315- if latest_session_date :
316- url = URL_BASE + f'sessions?date_start>={ latest_session_date } '
317- # if this is the first time populating the script (no latest session in db), get all sessions
318- else :
319- url = URL_BASE + f'sessions'
320- data = get_data (url )
321-
388+ data_url = ""
322389 try :
323- add_meetings_to_db (session )
390+ latest_session = fetch_latest_session (session )
391+
392+ if latest_session :
393+ data_url = URL_BASE + f'sessions?date_start>={ latest_session .date } '
394+ # if this is the first time populating the script (no latest session in db), get all sessions and meetings
395+ else :
396+ data_url = URL_BASE + 'sessions'
397+ add_meetings_to_db (session )
398+ add_teams_colors (session )
324399 session .commit ()
325400 except Exception as e :
326- logger .error (f"Failed to add meetings to db. Error : { e } " )
401+ logger .error (f"Failed to fetch data : { e } " )
327402
403+ data = get_data (data_url )
328404 data_size = len (data )
329- c = 0
405+ c = 0
330406 for f1session in data :
331407 try :
332408 with session .begin ():
333409 session_key = f1session ['session_key' ]
410+ add_current_meeting (session , f1session ['meeting_key' ])
334411 add_session_to_db (session , f1session )
335412 add_all_laps_for_session (session , session_key )
336413 add_session_result_to_db (session , session_key )
@@ -342,7 +419,6 @@ def update_db():
342419 exc_info = True ,
343420 )
344421 break
345- add_teams_colors (session )
346422
347423if __name__ == "__main__" :
348424 from .database import create_db_and_tables
0 commit comments