mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 19:56:18 -04:00
Add local-first refactor foundation
This commit is contained in:
270
internal/ingest/openf1.go
Normal file
270
internal/ingest/openf1.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/AmanTahiliani/box-box/internal/api"
|
||||
"github.com/AmanTahiliani/box-box/internal/models"
|
||||
"github.com/AmanTahiliani/box-box/internal/store"
|
||||
)
|
||||
|
||||
const sourceOpenF1 = "openf1"
|
||||
|
||||
// FetchResult holds a fetched OpenF1 endpoint response with provenance metadata.
|
||||
type FetchResult struct {
|
||||
Endpoint string
|
||||
RequestKey string
|
||||
URL string
|
||||
Body []byte
|
||||
FetchedAt time.Time
|
||||
}
|
||||
|
||||
// Source fetches OpenF1 REST data for ingestion workflows.
|
||||
type Source interface {
|
||||
FetchMeetingsForYear(year int) (FetchResult, []models.Meeting, error)
|
||||
FetchMeetingsForMeetingKey(meetingKey int) (FetchResult, []models.Meeting, error)
|
||||
FetchSessionsForMeeting(meetingKey int) (FetchResult, []models.Session, error)
|
||||
FetchSessionsForSessionKey(sessionKey int) (FetchResult, []models.Session, error)
|
||||
FetchDriversForSession(sessionKey int) (FetchResult, []models.Driver, error)
|
||||
FetchSessionResult(sessionKey int) (FetchResult, []models.SessionResult, error)
|
||||
FetchStartingGrid(sessionKey int) (FetchResult, []models.StartingGrid, error)
|
||||
}
|
||||
|
||||
// OpenF1Source adapts OpenF1Client for ingestion using strict fetches.
|
||||
type OpenF1Source struct {
|
||||
client *api.OpenF1Client
|
||||
}
|
||||
|
||||
// NewOpenF1Source returns a Source backed by the OpenF1 API client.
|
||||
func NewOpenF1Source(client *api.OpenF1Client) *OpenF1Source {
|
||||
return &OpenF1Source{client: client}
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) FetchMeetingsForYear(year int) (FetchResult, []models.Meeting, error) {
|
||||
url := fmt.Sprintf("%s/v1/meetings?year=%d", s.client.BaseURL(), year)
|
||||
return s.fetchMeetings(url, "meetings", fmt.Sprintf("year=%d", year))
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) FetchMeetingsForMeetingKey(meetingKey int) (FetchResult, []models.Meeting, error) {
|
||||
url := fmt.Sprintf("%s/v1/meetings?meeting_key=%d", s.client.BaseURL(), meetingKey)
|
||||
return s.fetchMeetings(url, "meetings", fmt.Sprintf("meeting_key=%d", meetingKey))
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) FetchSessionsForMeeting(meetingKey int) (FetchResult, []models.Session, error) {
|
||||
url := fmt.Sprintf("%s/v1/sessions?meeting_key=%d", s.client.BaseURL(), meetingKey)
|
||||
return s.fetchSessions(url, "sessions", fmt.Sprintf("meeting_key=%d", meetingKey))
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) FetchSessionsForSessionKey(sessionKey int) (FetchResult, []models.Session, error) {
|
||||
url := fmt.Sprintf("%s/v1/sessions?session_key=%d", s.client.BaseURL(), sessionKey)
|
||||
return s.fetchSessions(url, "sessions", fmt.Sprintf("session_key=%d", sessionKey))
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) FetchDriversForSession(sessionKey int) (FetchResult, []models.Driver, error) {
|
||||
url := fmt.Sprintf("%s/v1/drivers?session_key=%d", s.client.BaseURL(), sessionKey)
|
||||
return s.fetchDrivers(url, "drivers", fmt.Sprintf("session_key=%d", sessionKey))
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) FetchSessionResult(sessionKey int) (FetchResult, []models.SessionResult, error) {
|
||||
url := fmt.Sprintf("%s/v1/session_result?session_key=%d", s.client.BaseURL(), sessionKey)
|
||||
return s.fetchSessionResults(url, "session_result", fmt.Sprintf("session_key=%d", sessionKey))
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) FetchStartingGrid(sessionKey int) (FetchResult, []models.StartingGrid, error) {
|
||||
url := fmt.Sprintf("%s/v1/starting_grid?session_key=%d", s.client.BaseURL(), sessionKey)
|
||||
return s.fetchStartingGrid(url, "starting_grid", fmt.Sprintf("session_key=%d", sessionKey))
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) fetchMeetings(url, endpoint, requestKey string) (FetchResult, []models.Meeting, error) {
|
||||
body, err := s.client.FetchStrict(url)
|
||||
if err != nil {
|
||||
return FetchResult{}, nil, err
|
||||
}
|
||||
var result []models.Meeting
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return FetchResult{}, nil, err
|
||||
}
|
||||
return FetchResult{
|
||||
Endpoint: endpoint,
|
||||
RequestKey: requestKey,
|
||||
URL: url,
|
||||
Body: body,
|
||||
FetchedAt: time.Now(),
|
||||
}, result, nil
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) fetchSessions(url, endpoint, requestKey string) (FetchResult, []models.Session, error) {
|
||||
body, err := s.client.FetchStrict(url)
|
||||
if err != nil {
|
||||
return FetchResult{}, nil, err
|
||||
}
|
||||
var result []models.Session
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return FetchResult{}, nil, err
|
||||
}
|
||||
return FetchResult{
|
||||
Endpoint: endpoint,
|
||||
RequestKey: requestKey,
|
||||
URL: url,
|
||||
Body: body,
|
||||
FetchedAt: time.Now(),
|
||||
}, result, nil
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) fetchDrivers(url, endpoint, requestKey string) (FetchResult, []models.Driver, error) {
|
||||
body, err := s.client.FetchStrict(url)
|
||||
if err != nil {
|
||||
return FetchResult{}, nil, err
|
||||
}
|
||||
var result []models.Driver
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return FetchResult{}, nil, err
|
||||
}
|
||||
return FetchResult{
|
||||
Endpoint: endpoint,
|
||||
RequestKey: requestKey,
|
||||
URL: url,
|
||||
Body: body,
|
||||
FetchedAt: time.Now(),
|
||||
}, result, nil
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) fetchSessionResults(url, endpoint, requestKey string) (FetchResult, []models.SessionResult, error) {
|
||||
body, err := s.client.FetchStrict(url)
|
||||
if err != nil {
|
||||
return FetchResult{}, nil, err
|
||||
}
|
||||
var result []models.SessionResult
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return FetchResult{}, nil, err
|
||||
}
|
||||
return FetchResult{
|
||||
Endpoint: endpoint,
|
||||
RequestKey: requestKey,
|
||||
URL: url,
|
||||
Body: body,
|
||||
FetchedAt: time.Now(),
|
||||
}, result, nil
|
||||
}
|
||||
|
||||
func (s *OpenF1Source) fetchStartingGrid(url, endpoint, requestKey string) (FetchResult, []models.StartingGrid, error) {
|
||||
body, err := s.client.FetchStrict(url)
|
||||
if err != nil {
|
||||
return FetchResult{}, nil, err
|
||||
}
|
||||
var result []models.StartingGrid
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return FetchResult{}, nil, err
|
||||
}
|
||||
return FetchResult{
|
||||
Endpoint: endpoint,
|
||||
RequestKey: requestKey,
|
||||
URL: url,
|
||||
Body: body,
|
||||
FetchedAt: time.Now(),
|
||||
}, result, nil
|
||||
}
|
||||
|
||||
func meetingToStore(m models.Meeting) store.Meeting {
|
||||
return store.Meeting{
|
||||
MeetingKey: int(m.MeetingKey),
|
||||
MeetingName: m.MeetingName,
|
||||
MeetingOfficialName: m.MeetingOfficialName,
|
||||
Location: m.Location,
|
||||
CountryCode: m.CountryCode,
|
||||
CountryName: m.CountryName,
|
||||
CircuitKey: m.CircuitKey,
|
||||
CircuitShortName: m.CircuitShortName,
|
||||
GMTOffset: m.GMTOffset,
|
||||
DateStart: m.DateStart,
|
||||
DateEnd: m.DateEnd,
|
||||
Year: m.Year,
|
||||
}
|
||||
}
|
||||
|
||||
func sessionToStore(s models.Session) store.Session {
|
||||
return store.Session{
|
||||
SessionKey: s.SessionKey,
|
||||
MeetingKey: s.MeetingKey,
|
||||
SessionName: s.SessionName,
|
||||
SessionType: s.SessionType,
|
||||
CircuitKey: s.CircuitKey,
|
||||
DateStart: s.DateStart,
|
||||
DateEnd: s.DateEnd,
|
||||
GMTOffset: s.GMTOffset,
|
||||
}
|
||||
}
|
||||
|
||||
func driverToStore(d models.Driver) store.Driver {
|
||||
return store.Driver{
|
||||
DriverNumber: d.DriverNumber,
|
||||
BroadcastName: d.BroadcastName,
|
||||
FirstName: d.FirstName,
|
||||
FullName: d.FullName,
|
||||
LastName: d.LastName,
|
||||
NameAcronym: d.NameAcronym,
|
||||
HeadshotURL: d.HeadshotURL,
|
||||
TeamName: d.TeamName,
|
||||
TeamColour: d.TeamColour,
|
||||
}
|
||||
}
|
||||
|
||||
func sessionDriverToStore(d models.Driver) store.SessionDriver {
|
||||
return store.SessionDriver{
|
||||
SessionKey: d.SessionKey,
|
||||
DriverNumber: d.DriverNumber,
|
||||
MeetingKey: d.MeetingKey,
|
||||
TeamName: d.TeamName,
|
||||
TeamColour: d.TeamColour,
|
||||
}
|
||||
}
|
||||
|
||||
func sessionResultToStore(r models.SessionResult) store.SessionResult {
|
||||
return store.SessionResult{
|
||||
SessionKey: r.SessionKey,
|
||||
DriverNumber: r.DriverNumber,
|
||||
MeetingKey: r.MeetingKey,
|
||||
Position: r.Position,
|
||||
Points: r.Points,
|
||||
NumberOfLaps: r.NumberOfLaps,
|
||||
DurationJSON: jsonField(r.Duration),
|
||||
GapToLeaderJSON: jsonField(r.GapToLeader),
|
||||
DNF: r.DNF,
|
||||
DNS: r.DNS,
|
||||
DSQ: r.DSQ,
|
||||
}
|
||||
}
|
||||
|
||||
func startingGridToStore(g models.StartingGrid) store.StartingGridEntry {
|
||||
return store.StartingGridEntry{
|
||||
SessionKey: g.SessionKey,
|
||||
DriverNumber: g.DriverNumber,
|
||||
MeetingKey: g.MeetingKey,
|
||||
Position: g.Position,
|
||||
LapDuration: g.LapDuration,
|
||||
}
|
||||
}
|
||||
|
||||
func jsonField(v any) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func provenanceJSON(fetch FetchResult) string {
|
||||
meta := map[string]string{
|
||||
"url": fetch.URL,
|
||||
}
|
||||
b, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
Reference in New Issue
Block a user