2025Cancelled
Drive Simulation Dashboard
Telemetry dashboard that parses OptimumLap race simulation data and aggregates it through MongoDB. Cancelled due to simulation licensing/data constraints.
PythonMongoDBReact
Note: the backend is hosted on Render's free tier and spins down when idle — it may take up to a minute to load.
The Dashboard
A now-deprecated dashboard that parses OptimumLap race simulation data (.csv) using Python and aggregates it through MongoDB. Each tab on the dashboard corresponds to its own aggregation pipeline on the backend.
Due to a lack of simulation licensing and real data, the source CSVs were transformed to "simulate" a simulation. It didn't produce anything useful. The front-end was built with functionality in mind. The project has since been cancelled, so some tabs still have bugs and errors.
The client is deployed on Vercel, the Express + MongoDB backend runs on Render. Data processing lives server-side so new lap simulations could, in theory, be dropped in without touching the client.
| Tab | Purpose |
|---|---|
| Session Comparison | Aggregate min/avg/max across numeric fields per session |
| Event Detection | Group threshold crossings into contiguous events (e.g. braking zones) |
| Correlation | Scatter two numeric fields against each other across sessions |
| Time Buckets | Window-bucketed min/avg/max for a single field over time |
| Stack | Role |
|---|---|
| React + Vite | Client UI, deployed on Vercel |
| Express.js | API server, deployed on Render |
| MongoDB | Document store and aggregation engine |
| Python | Parse OptimumLap CSV exports into MongoDB documents |
| Environment Variables | Wire the Vercel client to the Render backend |
The Code
Each tab maps to its own router file under
server/routes/. The original repo packs them into a single aggregations.js; they've been split out here so each file shows only the aggregation pipeline behind one tab.▫sessionComparison.js
1// Tab: Session Comparison2// Aggregates avg/max/min for every numeric field across the selected sessions,3// then joins in the session metadata so the client can label each row.45import { Router } from "express";6import { ObjectId } from "mongodb";7import { getDb } from "../db.js";89const router = Router();1011const NUMERIC_FIELDS = [12 "speed_kmh",13 "lateral_accel_ms2",14 "longitudinal_accel_ms2",15 "throttle_position_pct",16 "brake_position_pct",17 "engine_speed_rpm",18 "torque_nm",19 "power_hp",20 "downforce_n",21 "drag_force_n"22];2324// GET /stats/session-comparison?sessions=id1,id225router.get("/session-comparison", async (req, res) => {26 const db = await getDb();27 const ids = String(req.query.sessions || "").split(",").filter(Boolean).map(id => new ObjectId(id));2829 const group = { _id: "$session_id", count: { $sum: 1 } };30 for (const f of NUMERIC_FIELDS) {31 group[`${f}_avg`] = { $avg: `$${f}` };32 group[`${f}_max`] = { $max: `$${f}` };33 group[`${f}_min`] = { $min: `$${f}` };34 }3536 const rows = await db.collection("readings").aggregate([37 { $match: { session_id: { $in: ids } } },38 { $group: group },39 { $lookup: { from: "sessions", localField: "_id", foreignField: "_id", as: "session" } },40 { $unwind: "$session" }41 ]).toArray();4243 res.json(rows);44});4546export default router;47