homelab / services /api /ingest.py
nj9997
v1.1.2
b10d399
Raw
History Blame Contribute Delete
6.7 kB
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)
]