from __future__ import annotations from datetime import datetime, timedelta, timezone from typing import Any from sqlalchemy import func, select from sqlalchemy.orm import Session from .models import CarData, Session as SessionModel from .openf1 import fetch_car_data, fetch_sessions, fetch_sessions_for_date def _parse_dt(value: str | None) -> datetime | None: if not value: return None try: if value.endswith("Z"): value = value.replace("Z", "+00:00") return datetime.fromisoformat(value) except ValueError: return None def _session_type(value: str | None, fallback: str | None) -> str: return (value or fallback or "").strip() def is_practice_session(session_type: str) -> bool: return "practice" in session_type.lower() def is_competitive_session(session_type: str) -> bool: lowered = session_type.lower() return any(keyword in lowered for keyword in ("qualifying", "sprint", "race")) def ingest_sessions_for_date(db: Session, date_iso: str) -> int: sessions = fetch_sessions_for_date(date_iso) inserted = 0 for item in sessions: session_key = item.get("session_key") if session_key is None: continue exists = db.get(SessionModel, session_key) if exists: continue session_type = _session_type(item.get("session_type"), item.get("session_name")) db.add( SessionModel( session_key=session_key, meeting_key=item.get("meeting_key"), session_name=item.get("session_name"), session_type=session_type, country_name=item.get("country_name"), date_start=_parse_dt(item.get("date_start")), date_end=_parse_dt(item.get("date_end")), ) ) inserted += 1 if inserted: db.commit() return inserted def ingest_sessions_with_params(db: Session, params: dict[str, Any]) -> int: sessions = fetch_sessions(params=params) inserted = 0 for item in sessions: session_key = item.get("session_key") if session_key is None: continue exists = db.get(SessionModel, session_key) if exists: continue session_type = _session_type(item.get("session_type"), item.get("session_name")) db.add( SessionModel( session_key=session_key, meeting_key=item.get("meeting_key"), session_name=item.get("session_name"), session_type=session_type, country_name=item.get("country_name"), date_start=_parse_dt(item.get("date_start")), date_end=_parse_dt(item.get("date_end")), ) ) inserted += 1 if inserted: db.commit() return inserted def has_car_data(db: Session, session_key: int) -> bool: stmt = select(func.count(CarData.id)).where(CarData.session_key == session_key) return (db.execute(stmt).scalar() or 0) > 0 def ingest_car_data_for_session( db: Session, *, session_key: int, driver_number: int | None = None, speed_min: float | None = None, ) -> int: records = fetch_car_data( session_key=session_key, driver_number=driver_number, speed_min=speed_min, ) inserted = 0 for item in records: db.add( CarData( session_key=item.get("session_key"), driver_number=item.get("driver_number"), date=_parse_dt(item.get("date")), speed=item.get("speed"), throttle=item.get("throttle"), brake=item.get("brake"), n_gear=item.get("n_gear"), ) ) inserted += 1 if inserted: db.commit() return inserted def ingest_practice_for_date(db: Session, date_iso: str) -> dict[str, int]: inserted_sessions = ingest_sessions_for_date(db, date_iso) try: date_start = datetime.fromisoformat(date_iso).replace(tzinfo=timezone.utc) except ValueError: date_start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) date_end = date_start + timedelta(days=1) stmt = select(SessionModel).where( SessionModel.date_start.isnot(None), SessionModel.date_start >= date_start, SessionModel.date_start < date_end, ) sessions = db.execute(stmt).scalars().all() ingested_sessions = 0 ingested_samples = 0 for session in sessions: session_type = session.session_type or session.session_name or "" if not is_practice_session(session_type): continue if has_car_data(db, session.session_key): continue ingested_sessions += 1 ingested_samples += ingest_car_data_for_session(db, session_key=session.session_key) return { "sessions_added": inserted_sessions, "practice_sessions_ingested": ingested_sessions, "samples_ingested": ingested_samples, } def telemetry_summary(db: Session, session_key: int) -> list[dict[str, Any]]: stmt = ( select( CarData.driver_number, func.avg(CarData.speed).label("avg_speed"), func.max(CarData.speed).label("max_speed"), func.count(CarData.id).label("samples"), ) .where(CarData.session_key == session_key) .group_by(CarData.driver_number) .order_by(func.avg(CarData.speed).desc()) ) rows = db.execute(stmt).all() return [ { "driver_number": row.driver_number, "avg_speed": float(row.avg_speed or 0), "max_speed": float(row.max_speed or 0), "samples": int(row.samples or 0), } for row in rows ] def telemetry_series( db: Session, *, session_key: int, driver_number: int, limit: int = 300, ) -> list[dict[str, Any]]: stmt = ( select( CarData.date, CarData.speed, CarData.throttle, CarData.brake, CarData.n_gear, ) .where(CarData.session_key == session_key, CarData.driver_number == driver_number) .order_by(CarData.date.desc()) .limit(limit) ) rows = db.execute(stmt).all() return [ { "date": row.date.isoformat() if row.date else None, "speed": float(row.speed or 0), "throttle": float(row.throttle or 0), "brake": float(row.brake or 0), "n_gear": int(row.n_gear or 0), } for row in reversed(rows) ]