Projects/Drive Simulation Dashboard
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
View Live Demo →

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.
TabPurpose
Session ComparisonAggregate min/avg/max across numeric fields per session
Event DetectionGroup threshold crossings into contiguous events (e.g. braking zones)
CorrelationScatter two numeric fields against each other across sessions
Time BucketsWindow-bucketed min/avg/max for a single field over time
StackRole
React + ViteClient UI, deployed on Vercel
Express.jsAPI server, deployed on Render
MongoDBDocument store and aggregation engine
PythonParse OptimumLap CSV exports into MongoDB documents
Environment VariablesWire 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.
FILES
sessionComparison.js
1// Tab: Session Comparison
2// 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.
4
5import { Router } from "express";
6import { ObjectId } from "mongodb";
7import { getDb } from "../db.js";
8
9const router = Router();
10
11const 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];
23
24// GET /stats/session-comparison?sessions=id1,id2
25router.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));
28
29 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 }
35
36 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();
42
43 res.json(rows);
44});
45
46export default router;
47