Created
July 2, 2026 00:25
-
-
Save Alphajeez96/8a5183c057f586b0f52b29ca8bd4abd5 to your computer and use it in GitHub Desktop.
RACEMAKE PRODUCT ENGINEER CHALLENGE SUBMISSION - Prince Chukwudire
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import {Hono} from "hono"; | |
| import {cors} from "hono/cors"; | |
| import {readFileSync} from "fs"; | |
| import {fileURLToPath} from "url"; | |
| import {resolve, dirname} from "path"; | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = dirname(__filename); | |
| interface RawFrame { | |
| ts: number; | |
| lap: number; | |
| pos: number; | |
| spd: number; | |
| thr: number; | |
| brk: number; | |
| str: number; | |
| gear: number; | |
| rpm: number; | |
| tyres: {fl: number; fr: number; rl: number; rr: number}; | |
| } | |
| interface CleanFrame { | |
| ts: number; | |
| lap: number; | |
| pos: number; | |
| spd: number; | |
| thr: number; | |
| brk: number; | |
| str: number; | |
| gear: number; | |
| rpm: number; | |
| tyres: {fl: number; fr: number; rl: number; rr: number}; | |
| } | |
| interface TelemetryData { | |
| schema: string; | |
| frame_count: number; | |
| frames: RawFrame[]; | |
| } | |
| interface TyreStats { | |
| avg_c: number; | |
| min_c: number; | |
| max_c: number; | |
| } | |
| interface LapResult { | |
| number: number; | |
| lap_time_s: number; | |
| frame_count: number; | |
| steering: {max: number; avg_abs: number}; | |
| classification: "out_lap" | "flying" | "in_lap"; | |
| brake: {avg: number; max: number; braking_pct: number}; | |
| speeds: {avg_kmh: number; top_kmh: number; min_kmh: number}; | |
| gear: {change_count: number; highest: number; lowest: number}; | |
| tyres: {fl: TyreStats; fr: TyreStats; rl: TyreStats; rr: TyreStats}; | |
| throttle: { | |
| avg: number; | |
| coast_pct: number; | |
| above_90_pct: number; | |
| below_10_pct: number; | |
| full_throttle_pct: number; | |
| }; | |
| } | |
| interface LapBoundary { | |
| endIdx: number; | |
| startIdx: number; | |
| lapFromData: number; | |
| } | |
| interface StageResult { | |
| notes: string[]; | |
| frames: CleanFrame[]; | |
| } | |
| interface StintResponse { | |
| valid_laps: LapResult[]; | |
| cleaning_notes: string[]; | |
| discarded_laps: LapResult[]; | |
| summary: { | |
| cleaned_frames: number; | |
| valid_lap_count: number; | |
| total_raw_frames: number; | |
| anomalies_discarded: number; | |
| discarded_lap_count: number; | |
| }; | |
| } | |
| const RPM_MAX = 15000; | |
| const TYRE_SCALE = 10.0; | |
| const SPEED_MAX_KMH = 400; | |
| const TYRE_TEMP_MAX_C = 200; | |
| const TYRE_TEMP_MIN_C = -50; | |
| const COAST_THRESHOLD = 0.05; | |
| const BRAKING_THRESHOLD = 0.05; | |
| const LAP_POS_WRAP_THRESHOLD = 0.5; | |
| const IN_LAP_END_POS_THRESHOLD = 0.9; | |
| const FULL_THROTTLE_THRESHOLD = 0.99; | |
| const OUT_LAP_START_POS_THRESHOLD = 0.1; | |
| const loadTelemetry = (filePath: string): TelemetryData => { | |
| const raw = readFileSync(filePath, "utf-8"); | |
| const data = JSON.parse(raw) as TelemetryData; | |
| if (!data.schema || !Array.isArray(data.frames)) { | |
| throw new Error("Invalid telemetry data: missing schema or frames array"); | |
| } | |
| return data; | |
| }; | |
| // Pipeline helpers | |
| const stageNote = (label: string, count: number, examples: string[]): string => | |
| `${label}: removed ${count} frame(s)${examples.length ? ` (${examples.join("; ")})` : ""}`; | |
| const rawToClean = (raw: RawFrame[]): CleanFrame[] => | |
| raw.map((f) => ({ | |
| ts: f.ts, | |
| lap: f.lap, | |
| pos: f.pos, | |
| spd: f.spd, | |
| thr: f.thr, | |
| brk: f.brk, | |
| str: f.str, | |
| gear: f.gear, | |
| rpm: f.rpm, | |
| tyres: {...f.tyres}, | |
| })); | |
| const sortFrames = (frames: CleanFrame[]): StageResult => { | |
| const sorted = [...frames].sort((a, b) => a.ts - b.ts); | |
| const moved = frames.length > 0 && sorted[0].ts !== frames[0].ts; | |
| return { | |
| frames: sorted, | |
| notes: moved | |
| ? ["Sorted frames by timestamp (input order not guaranteed)"] | |
| : [], | |
| }; | |
| }; | |
| const removeDuplicates = (frames: CleanFrame[]): StageResult => { | |
| const seen = new Set<number>(); | |
| const deduped: CleanFrame[] = []; | |
| for (const f of frames) { | |
| if (!seen.has(f.ts)) { | |
| seen.add(f.ts); | |
| deduped.push(f); | |
| } | |
| } | |
| const removed = frames.length - deduped.length; | |
| return { | |
| frames: deduped, | |
| notes: | |
| removed > 0 | |
| ? [ | |
| stageNote("Duplicate timestamps", removed, [ | |
| "kept first occurrence — assumes duplicates are redundant reads", | |
| ]), | |
| ] | |
| : [], | |
| }; | |
| }; | |
| const removeTimestampRegressions = (frames: CleanFrame[]): StageResult => { | |
| let maxTs = -Infinity; | |
| const valid: CleanFrame[] = []; | |
| const examples: string[] = []; | |
| for (const f of frames) { | |
| if (f.ts >= maxTs) { | |
| valid.push(f); | |
| maxTs = f.ts; | |
| } else if (examples.length < 3) { | |
| examples.push(`ts=${f.ts} < previous ${maxTs}`); | |
| } | |
| } | |
| const removed = frames.length - valid.length; | |
| return { | |
| frames: valid, | |
| notes: | |
| removed > 0 | |
| ? [stageNote("Timestamp regressions", removed, examples)] | |
| : [], | |
| }; | |
| }; | |
| const removeInvalidPositions = (frames: CleanFrame[]): StageResult => { | |
| const valid = frames.filter( | |
| (f) => Number.isFinite(f.pos) && f.pos >= 0 && f.pos <= 1, | |
| ); | |
| const removed = frames.length - valid.length; | |
| const badExamples = frames | |
| .filter((f) => !(Number.isFinite(f.pos) && f.pos >= 0 && f.pos <= 1)) | |
| .slice(0, 3); | |
| return { | |
| frames: valid, | |
| notes: | |
| removed > 0 | |
| ? [ | |
| stageNote( | |
| "Invalid positions (pos < 0 or > 1)", | |
| removed, | |
| badExamples.map((f) => `ts=${f.ts}, pos=${f.pos}`), | |
| ), | |
| ] | |
| : [], | |
| }; | |
| }; | |
| const removeInvalidThrottleBrake = (frames: CleanFrame[]): StageResult => { | |
| const valid = frames.filter((f) => { | |
| const thrOk = Number.isFinite(f.thr) && f.thr >= 0 && f.thr <= 1; | |
| const brkOk = Number.isFinite(f.brk) && f.brk >= 0 && f.brk <= 1; | |
| return thrOk && brkOk; | |
| }); | |
| const removed = frames.length - valid.length; | |
| return { | |
| frames: valid, | |
| notes: | |
| removed > 0 | |
| ? [stageNote("Invalid throttle/brake (outside [0, 1])", removed, [])] | |
| : [], | |
| }; | |
| }; | |
| const removeImpossibleSpeeds = (frames: CleanFrame[]): StageResult => { | |
| const good: CleanFrame[] = []; | |
| const bad: CleanFrame[] = []; | |
| for (const f of frames) { | |
| if (Number.isFinite(f.spd) && f.spd >= 0 && f.spd <= SPEED_MAX_KMH) { | |
| good.push(f); | |
| } else { | |
| bad.push(f); | |
| } | |
| } | |
| const removed = bad.length; | |
| const examples = bad.slice(0, 3).map((f) => `ts=${f.ts}, spd=${f.spd} km/h`); | |
| return { | |
| frames: good, | |
| notes: | |
| removed > 0 | |
| ? [ | |
| stageNote( | |
| `Impossible speeds (not finite, < 0, or > ${SPEED_MAX_KMH} km/h)`, | |
| removed, | |
| examples, | |
| ), | |
| ] | |
| : [], | |
| }; | |
| }; | |
| const removeImpossibleRPM = (frames: CleanFrame[]): StageResult => { | |
| const good: CleanFrame[] = []; | |
| const bad: CleanFrame[] = []; | |
| for (const f of frames) { | |
| if (Number.isFinite(f.rpm) && f.rpm >= 0 && f.rpm <= RPM_MAX) { | |
| good.push(f); | |
| } else { | |
| bad.push(f); | |
| } | |
| } | |
| const removed = bad.length; | |
| const examples = bad.slice(0, 3).map((f) => `ts=${f.ts}, rpm=${f.rpm}`); | |
| return { | |
| frames: good, | |
| notes: | |
| removed > 0 | |
| ? [ | |
| stageNote( | |
| `Impossible RPM (not finite, < 0, or > ${RPM_MAX})`, | |
| removed, | |
| examples, | |
| ), | |
| ] | |
| : [], | |
| }; | |
| }; | |
| const decodeTyres = (frames: CleanFrame[]): StageResult => { | |
| const decoded = frames.map((f) => ({ | |
| ...f, | |
| tyres: { | |
| fl: f.tyres.fl / TYRE_SCALE, | |
| fr: f.tyres.fr / TYRE_SCALE, | |
| rl: f.tyres.rl / TYRE_SCALE, | |
| rr: f.tyres.rr / TYRE_SCALE, | |
| }, | |
| })); | |
| return { | |
| frames: decoded, | |
| notes: [ | |
| `Decoded tyre temperatures: divided raw values by ${TYRE_SCALE} (Rust recorder stores °C × ${TYRE_SCALE} as i16 — see recorder.rs scales::TEMPERATURE)`, | |
| ], | |
| }; | |
| }; | |
| const removeImpossibleTyreTemps = (frames: CleanFrame[]): StageResult => { | |
| const good: CleanFrame[] = []; | |
| const bad: CleanFrame[] = []; | |
| for (const f of frames) { | |
| const {fl, fr, rl, rr} = f.tyres; | |
| if ( | |
| [fl, fr, rl, rr].every( | |
| (t) => | |
| Number.isFinite(t) && t >= TYRE_TEMP_MIN_C && t <= TYRE_TEMP_MAX_C, | |
| ) | |
| ) { | |
| good.push(f); | |
| } else { | |
| bad.push(f); | |
| } | |
| } | |
| const removed = bad.length; | |
| return { | |
| frames: good, | |
| notes: | |
| removed > 0 | |
| ? [ | |
| stageNote( | |
| `Impossible tyre temperatures (outside ${TYRE_TEMP_MIN_C}–${TYRE_TEMP_MAX_C} °C after decode)`, | |
| removed, | |
| [], | |
| ), | |
| ] | |
| : [], | |
| }; | |
| }; | |
| // Pipeline | |
| const runPipeline = (raw: RawFrame[]): StageResult => { | |
| let frames = rawToClean(raw); | |
| const notes: string[] = []; | |
| const run = (fn: (f: CleanFrame[]) => StageResult): void => { | |
| const result = fn(frames); | |
| frames = result.frames; | |
| notes.push(...result.notes); | |
| }; | |
| run(sortFrames); | |
| run(removeDuplicates); | |
| run(removeTimestampRegressions); | |
| run(removeInvalidPositions); | |
| run(removeInvalidThrottleBrake); | |
| run(removeImpossibleSpeeds); | |
| run(removeImpossibleRPM); | |
| run(decodeTyres); | |
| run(removeImpossibleTyreTemps); | |
| return {frames, notes}; | |
| }; | |
| // Lap Detection (dual-signal: pos wrap AND lap increment) | |
| const detectLapBoundaries = ( | |
| frames: CleanFrame[], | |
| ): { | |
| boundaries: LapBoundary[]; | |
| notes: string[]; | |
| } => { | |
| const notes: string[] = []; | |
| const boundaries: LapBoundary[] = []; | |
| let lapStart = 0; | |
| let inconsistencies = 0; | |
| for (let i = 1; i < frames.length; i++) { | |
| const prev = frames[i - 1]; | |
| const curr = frames[i]; | |
| const posDrop = prev.pos - curr.pos; | |
| const posWraps = posDrop > LAP_POS_WRAP_THRESHOLD && curr.pos < 0.15; | |
| const lapIncrements = curr.lap === prev.lap + 1; | |
| if (posWraps && lapIncrements) { | |
| boundaries.push({ | |
| startIdx: lapStart, | |
| endIdx: i - 1, | |
| lapFromData: prev.lap, | |
| }); | |
| lapStart = i; | |
| } else if (posWraps || lapIncrements) { | |
| inconsistencies++; | |
| } | |
| } | |
| if (lapStart < frames.length) { | |
| boundaries.push({ | |
| startIdx: lapStart, | |
| endIdx: frames.length - 1, | |
| lapFromData: frames[lapStart].lap, | |
| }); | |
| } | |
| notes.push( | |
| `Detected ${boundaries.length} lap(s) via dual-signal detection (position wrapping AND lap counter increment)`, | |
| ); | |
| if (inconsistencies > 0) { | |
| notes.push( | |
| `Found ${inconsistencies} telemetry inconsistency(ies) where position and lap counter disagreed — boundaries only created when both signals agree`, | |
| ); | |
| } | |
| return {boundaries, notes}; | |
| }; | |
| // Lap Classification | |
| const classifyLap = ( | |
| boundary: LapBoundary, | |
| index: number, | |
| totalLaps: number, | |
| frames: CleanFrame[], | |
| ): LapResult["classification"] => { | |
| const startFrame = frames[boundary.startIdx]; | |
| const endFrame = frames[boundary.endIdx]; | |
| if (index === 0 && startFrame.pos > OUT_LAP_START_POS_THRESHOLD) { | |
| return "out_lap"; | |
| } | |
| if (index === totalLaps - 1 && endFrame.pos < IN_LAP_END_POS_THRESHOLD) { | |
| return "in_lap"; | |
| } | |
| return "flying"; | |
| }; | |
| // Analysis | |
| const computeDeltas = (frames: CleanFrame[]): number[] => { | |
| const deltas: number[] = []; | |
| for (let i = 0; i < frames.length; i++) { | |
| deltas.push(i < frames.length - 1 ? frames[i + 1].ts - frames[i].ts : 0); | |
| } | |
| return deltas; | |
| }; | |
| const weightedMean = (values: number[], weights: number[]): number => { | |
| let sumW = 0; | |
| let sumV = 0; | |
| for (let i = 0; i < values.length; i++) { | |
| if (weights[i] > 0) { | |
| sumW += weights[i]; | |
| sumV += values[i] * weights[i]; | |
| } | |
| } | |
| return sumW > 0 ? sumV / sumW : 0; | |
| }; | |
| const pct = (weight: number, totalWeight: number): number => | |
| totalWeight > 0 ? Math.round((weight / totalWeight) * 1000) / 10 : 0; | |
| const analyzeLap = ( | |
| boundary: LapBoundary, | |
| frames: CleanFrame[], | |
| classification: LapResult["classification"], | |
| ): LapResult => { | |
| const lapFrames = frames.slice(boundary.startIdx, boundary.endIdx + 1); | |
| const n = lapFrames.length; | |
| const deltas = computeDeltas(lapFrames); | |
| const totalWeight = deltas.reduce((s, d) => s + d, 0); | |
| const totalTimeMs = lapFrames[n - 1].ts - lapFrames[0].ts; | |
| const spds = lapFrames.map((f) => f.spd); | |
| const tyreStats = (extract: (f: CleanFrame) => number): TyreStats => { | |
| const vals = lapFrames.map(extract); | |
| return { | |
| avg_c: Math.round(weightedMean(vals, deltas) * 10) / 10, | |
| min_c: Math.round(Math.min(...vals) * 10) / 10, | |
| max_c: Math.round(Math.max(...vals) * 10) / 10, | |
| }; | |
| }; | |
| // Throttle weighted aggregates | |
| const thrWeightedSum = deltas.reduce( | |
| (s, d, i) => s + lapFrames[i].thr * d, | |
| 0, | |
| ); | |
| const thrWeightedAvg = totalWeight > 0 ? thrWeightedSum / totalWeight : 0; | |
| const fullThrottleWeight = deltas.reduce( | |
| (s, d, i) => (lapFrames[i].thr >= FULL_THROTTLE_THRESHOLD ? s + d : s), | |
| 0, | |
| ); | |
| const coastWeight = deltas.reduce( | |
| (s, d, i) => (lapFrames[i].thr <= COAST_THRESHOLD ? s + d : s), | |
| 0, | |
| ); | |
| const above90Weight = deltas.reduce( | |
| (s, d, i) => (lapFrames[i].thr >= 0.9 ? s + d : s), | |
| 0, | |
| ); | |
| const below10Weight = deltas.reduce( | |
| (s, d, i) => (lapFrames[i].thr <= 0.1 ? s + d : s), | |
| 0, | |
| ); | |
| // Brake | |
| const brkWSum = deltas.reduce((s, d, i) => s + lapFrames[i].brk * d, 0); | |
| const brkWeightedAvg = totalWeight > 0 ? brkWSum / totalWeight : 0; | |
| const maxBrk = Math.max(...lapFrames.map((f) => f.brk)); | |
| const brakingWeight = deltas.reduce( | |
| (s, d, i) => (lapFrames[i].brk >= BRAKING_THRESHOLD ? s + d : s), | |
| 0, | |
| ); | |
| // Steering | |
| const strAbsVals = lapFrames.map((f) => Math.abs(f.str)); | |
| // Gear | |
| let gearChanges = 0; | |
| let maxGear = 0; | |
| let minGear = 8; | |
| for (let i = 0; i < lapFrames.length; i++) { | |
| const g = lapFrames[i].gear; | |
| if (i > 0 && g !== lapFrames[i - 1].gear) gearChanges++; | |
| if (g > maxGear) maxGear = g; | |
| if (g > 0 && g < minGear) minGear = g; | |
| } | |
| if (minGear === 8) minGear = 0; | |
| return { | |
| number: boundary.lapFromData, | |
| classification, | |
| lap_time_s: Math.round((totalTimeMs / 1000) * 100) / 100, | |
| frame_count: n, | |
| speeds: { | |
| avg_kmh: Math.round(weightedMean(spds, deltas) * 10) / 10, | |
| top_kmh: Math.round(Math.max(...spds) * 10) / 10, | |
| min_kmh: Math.round(Math.min(...spds) * 10) / 10, | |
| }, | |
| tyres: { | |
| fl: tyreStats((f) => f.tyres.fl), | |
| fr: tyreStats((f) => f.tyres.fr), | |
| rl: tyreStats((f) => f.tyres.rl), | |
| rr: tyreStats((f) => f.tyres.rr), | |
| }, | |
| throttle: { | |
| avg: Math.round(thrWeightedAvg * 1000) / 1000, | |
| full_throttle_pct: pct(fullThrottleWeight, totalWeight), | |
| coast_pct: pct(coastWeight, totalWeight), | |
| above_90_pct: pct(above90Weight, totalWeight), | |
| below_10_pct: pct(below10Weight, totalWeight), | |
| }, | |
| brake: { | |
| avg: Math.round(brkWeightedAvg * 1000) / 1000, | |
| max: Math.round(maxBrk * 1000) / 1000, | |
| braking_pct: pct(brakingWeight, totalWeight), | |
| }, | |
| steering: { | |
| max: Math.round(Math.max(...strAbsVals) * 1000) / 1000, | |
| avg_abs: Math.round(weightedMean(strAbsVals, deltas) * 1000) / 1000, | |
| }, | |
| gear: { | |
| change_count: gearChanges, | |
| highest: maxGear, | |
| lowest: minGear, | |
| }, | |
| }; | |
| }; | |
| // Orchestration | |
| const analyzeStint = (filePath: string): StintResponse => { | |
| const data = loadTelemetry(filePath); | |
| const rawCount = data.frames.length; | |
| const {frames: cleaned, notes: cleanNotes} = runPipeline(data.frames); | |
| const anomaliesDiscarded = rawCount - cleaned.length; | |
| const {boundaries, notes: lapNotes} = detectLapBoundaries(cleaned); | |
| const allLaps: LapResult[] = boundaries.map((b, i) => { | |
| const classification = classifyLap(b, i, boundaries.length, cleaned); | |
| return analyzeLap(b, cleaned, classification); | |
| }); | |
| const validLaps = allLaps.filter((l) => l.classification === "flying"); | |
| const discardedLaps = allLaps.filter((l) => l.classification !== "flying"); | |
| // Engineering-justification notes | |
| const justificationNotes: string[] = [ | |
| `Speed threshold: ≤ ${SPEED_MAX_KMH} km/h (F1 top speed ~370 km/h, margin for slipstream/tow; also rejects NaN/Infinity and negative values)`, | |
| `RPM threshold: ≥ 0 and ≤ ${RPM_MAX} (typical F1 rev limit ~15000; also rejects NaN/Infinity)`, | |
| `Lap detection: both position wrapping (pos drop > ${LAP_POS_WRAP_THRESHOLD} from >0.85 to <0.15) AND lap counter increment required — prevents false positives from position glitches`, | |
| `Out-lap classification: first lap with start_pos > ${OUT_LAP_START_POS_THRESHOLD} (recording began mid-track, lap is incomplete) — excluded from valid_laps`, | |
| `In-lap classification: last lap with end_pos < ${IN_LAP_END_POS_THRESHOLD} (recording ended before lap completion) — excluded from valid_laps`, | |
| `Tyre temperature range: sanity-checked to ${TYRE_TEMP_MIN_C}–${TYRE_TEMP_MAX_C} °C after ÷${TYRE_SCALE} decode`, | |
| `All averaged metrics are Δt-weighted (weighted by time to next frame) to account for the recorder's non-uniform sample rate — simple arithmetic mean would bias toward denser sampling periods`, | |
| `Lap time derived from first and last valid frame in each detected lap: (last.ts - first.ts) / 1000`, | |
| ]; | |
| return { | |
| summary: { | |
| total_raw_frames: rawCount, | |
| cleaned_frames: cleaned.length, | |
| anomalies_discarded: anomaliesDiscarded, | |
| valid_lap_count: validLaps.length, | |
| discarded_lap_count: discardedLaps.length, | |
| }, | |
| valid_laps: validLaps, | |
| discarded_laps: discardedLaps, | |
| cleaning_notes: [...cleanNotes, ...lapNotes, ...justificationNotes], | |
| }; | |
| }; | |
| const app = new Hono(); | |
| app.use("/*", cors()); | |
| // const DATA_PATH = resolve(__dirname, "data", "stint.telemetry.json"); | |
| const DATA_PATH = ""; | |
| app.get("/stint/analysis", (c) => { | |
| try { | |
| const result = analyzeStint(DATA_PATH); | |
| return c.json(result); | |
| } catch (err) { | |
| const message = err instanceof Error ? err.message : String(err); | |
| return c.json({error: message}, 500); | |
| } | |
| }); | |
| app.get("/", (c) => | |
| c.json({ | |
| service: "RaceMake Telemetry Analyzer", | |
| endpoints: {"/stint/analysis": "Analyze stint telemetry data"}, | |
| }), | |
| ); | |
| const PORT = parseInt(process.env.PORT || "3001", 10); | |
| console.log(`Server running on http://localhost:${PORT}`); | |
| export default {port: PORT, fetch: app.fetch}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment