{"profile":{"name":"Zain Rizvi","title":"ML engineer and builder.","tagline":"Forecasting systems, AI tools and robotics that ship.","roles":["ML/AI engineering","Data science","Software engineering","Robotics"],"siteUrl":"https://zainrizvi.ca","email":"zain4rizvi@gmail.com","links":{"linkedin":"https://linkedin.com/in/zain4rizvi","github":"https://github.com/Zain4Rizvi","resume":"/resume.pdf"},"metrics":[{"value":"75%","label":"forecast error cut","detail":"4% to 1% MAPE"},{"value":"10M+","label":"shipments","detail":"5 years of data, cross-validated"},{"value":"25→3","label":"min pipeline runtime","detail":"end-to-end freight forecasting"},{"value":"95%","label":"processing time cut","detail":"1M+ in HCV payments reconciled"}],"about":["I am a recent Honours BSc graduate from the University of Toronto, with a double major in Computer Science and Mathematics. My work sits between statistics and software: forecasting systems, machine learning models and the tooling that makes them usable.","I have shipped production forecasting at a national logistics company, built a robotics policy with language conditioning, and written everything from a ray tracer in C to a platformer in assembly. I work best on small teams where one person owns a problem end to end."]},"experience":[{"id":"purolator","org":"Purolator Inc.","role":"Data Scientist (Co-op)","location":"Mississauga, ON","start":"May 2025","end":"Dec 2025","featured":true,"bullets":["Cut national operations forecast error 75% (4% to 1% MAPE) vs. the previous biweekly forecast, cross-validated on 5 years of data across 10M+ shipments.","Compressed 50+ routes into 5 PCA components, forecast each with STL+ARIMA, enabling route-level staffing and capacity decisions (~50x more forecasts).","Automated end-to-end freight forecasting pipelines in Python and R (25 to 3 min runtime), extended to new terminals via historical demand reconstruction.","Automated executive KPI reporting (Python, QlikView, Excel) giving 100+ stakeholders real-time capacity visibility."],"tags":["Python","R","STL+ARIMA","PCA","QlikView"]},{"id":"mto","org":"Ontario Ministry of Transportation","role":"Data Analyst (Co-op)","location":"Toronto, ON","start":"May 2024","end":"Aug 2024","featured":false,"bullets":["Reconciled $1M+ across 130,000+ HCV payment records with algorithmic outlier detection in Python/Excel (processing time -95%).","Built and maintained Spring Boot REST APIs for the Towing and Storage Safety and Enforcement Act application, with AODA accessibility compliance."],"tags":["Python","Excel","Spring Boot","REST","AODA"]}],"education":{"degrees":[{"school":"University of Toronto","degree":"Honours BSc","majors":["Computer Science","Mathematics"],"start":"Sep 2022","end":"Aug 2026","coursework":["Machine Learning","Artificial Intelligence","Probability","Data Structures","Algorithms"]}],"awards":[{"title":"1st Place, AMACSS AI Olympics Hackathon"},{"title":"Frank Faubert Scholarship"},{"title":"Admission Scholarship"}]},"skills":{"Languages":["Python","C++","SQL","Java","R","C"],"Libraries":["PyTorch","Scikit-learn","XGBoost","LangChain","Spring Boot","Pandas","NumPy","Polars"],"Tools":["Docker","AWS (Cloud Practitioner)","Git/GitHub","REST APIs","Postman","MySQL","Cloudflare"]},"projects":[{"title":"Robotics VLA","slug":"robotics-vla","summary":"Fine-tuned the SmolVLA (450M) action expert to follow language commands, with attention visualized across 16 layers and a 7-joint MuJoCo simulator.","tags":["PyTorch","SmolVLA","MuJoCo","Gymnasium","React","Three.js"],"metrics":[{"value":"9/10","label":"grasp success"},{"value":"5/10","label":"stack success"},{"value":"87%","label":"bin throws (scripted expert)"},{"value":"90%","label":"peg insertion (scripted expert)"}],"links":[{"label":"Visit website","href":"https://robotics.zainrizvi.ca"}],"media":[{"type":"image","src":"/media/robotics-vla/demo.gif","alt":"Looping clip of the robot arm simulation following a language command","caption":"Simulation: the arm following a command"},{"type":"image","src":"/media/robotics-vla/model.webp","alt":"SmolVLA architecture view with 16-layer transformer blocks for the vision-language model and the action expert","caption":"Model: 16-layer blocks, VLM and action expert"}],"featured":true,"order":1,"url":"/projects/robotics-vla","body":"\n## Problem\n\nA robot policy that only repeats one trained motion is not useful. The goal was a policy that takes a plain-language command and acts on it, plus a way to see what the model attends to while it does.\n\n## Approach\n\nI fine-tuned the action expert of SmolVLA (450M parameters) on demonstrations to follow language commands. To generate demonstrations and evaluate, I wrote a 7-joint arm simulator in MuJoCo with a Gymnasium interface and PyTorch, driven by scripted experts. A React and Three.js app visualizes the model's attention across all 16 layers.\n\n{/* TODO: dataset size, number of demonstrations, training hardware and time */}\n\n## Key technical decision\n\nFine-tune only the action expert rather than the full model. It keeps the pretrained vision-language backbone intact and makes training practical at this scale.\n\n{/* TODO: confirm which components were frozen and the exact training recipe */}\n\n## Result\n\nThe fine-tuned policy grasps 9 of 10 times and stacks 5 of 10 times when given a language command. The scripted experts that generate the data reach 87% on bin throws and 90% on peg insertion.\n\n{/* TODO: evaluation protocol (number of seeds, object set, command set) */}\n"},{"title":"Rocket League Analyzer","slug":"rocket-league-analyzer","summary":"A LangChain tool-calling LLM over match telemetry, plus a Dockerized 3D dashboard synced to replay playback.","tags":["LangChain","LLM tool calling","Docker","3D dashboard"],"metrics":[],"links":[{"label":"Visit website","href":"https://rocket.zainrizvi.ca"}],"media":[{"type":"image","src":"/media/rocket-league-analyzer/sim.gif","alt":"Looping 3D replay: cars and the ball on the pitch, with coverage, centroid and pressure overlays plus the coach console","caption":"Replay simulation"},{"type":"image","src":"/media/rocket-league-analyzer/site.png","alt":"Replay Lab site: 3D match view beside the Ask the coach panel","caption":"The analyzer site"}],"featured":true,"order":2,"url":"/projects/rocket-league-analyzer","body":"\n## Problem\n\nMatch telemetry is dense and hard to read by eye. Players want answers to questions about a match, not a spreadsheet.\n\n{/* TODO: telemetry source (replay parser), match volume, who it is for */}\n\n## Approach\n\nDrop in a Rocket League .replay file and the app prepares its telemetry and opens an interactive 3D simulation. A LangChain agent with tool calling answers natural-language questions (\"Why did we concede the third goal?\") by calling functions over that telemetry. The Dockerized dashboard stays synced to playback, with controls for follow-ball, coverage, centroid and pressure overlays.\n\n## Key technical decision\n\nLet the model call typed tools over the telemetry instead of reading it as text, so answers come from computed values.\n\n{/* TODO: list the tools, and how answers are checked for accuracy */}\n\n## Result\n\nA working analyzer: ask a question, get an answer grounded in the match data, and watch the same moment in 3D.\n\n{/* TODO: quantitative result, if any */}\n"},{"title":"Canadian Political Donations Platform","slug":"political-donations","summary":"Explore donation data by donor, party and region, with filtering and export for researchers. I built the backend and API.","tags":["React","MapLibre GL","Express","PostgreSQL","Supabase","Jest"],"metrics":[{"value":"52","label":"backend tests (Jest)"},{"value":"3","label":"person team"}],"links":[{"label":"Source","href":"https://github.com/UTSC-CSCC01-Software-Engineering-I/course-project-weavers"}],"embed":{"kind":"iframe","src":"/demos/political-donations/index.html","title":"Canadian political donations explorer","badge":"Runs in your browser","hint":"Filter by year and party, click a province, then open Analytics from the bar inside the demo."},"media":[{"type":"image","src":"/media/political-donations/map.webp","alt":"Map of donation totals by province, shaded by amount","caption":"Donation totals by province"}],"featured":true,"order":3,"url":"/projects/political-donations","body":"\n## Problem\n\nElections Canada publishes contribution records, but they are hard for a researcher to explore. Totals by region, party and year need aggregation, and individual records need controlled access.\n\n## Approach\n\nA React and MapLibre front end shows totals by federal riding for 2019 and 2021, with party filters, per-party breakdowns, per-year trends and a comparison view. An Express API aggregates in SQL on Supabase Postgres. A pandas pipeline cleans the raw data. I owned the backend and API layer on a team of three (Weavers).\n\n## Key technical decision\n\nEnforce access on the server, not in the UI. The individual-record endpoint validates the caller's token on the backend and limits it to university-affiliated accounts, public map endpoints serve aggregates only, and every access, including denied ones, is written to an audit log. Sort columns are whitelisted and search text is escaped, so user input never becomes SQL.\n\n## Result\n\nResearchers sign in, then filter, sort, paginate and export individual records. The public map stays open. The backend has 52 passing Jest tests, and the data pipeline has pytest coverage.\n\n## Run it\n\nThe demo on this page is the project front end. While the database answers, the map, analytics, researcher records, and export use live aggregates. If it does not, the same UI shows a preview of public province, year, and party totals.\n\n```bash\ncd backend && npm install && npm start    # http://localhost:3001/api/health\ncd frontend && npm install && npm start   # http://localhost:3000\n```\n\nThe backend needs `SUPABASE_DB_URL`, `SUPABASE_URL` and `SUPABASE_ANON_KEY` (see `backend/.env.example`). Load the data with the scripts in `data/Database Creation`.\n"},{"title":"Assembly Platformer","slug":"assembly-platformer","summary":"An 8-bit platformer written entirely in MIPS assembly, playable in the browser.","tags":["MIPS assembly","sms32v50","JavaScript"],"metrics":[{"value":"2","label":"jumps (double jump)"},{"value":"3","label":"item pickups"}],"links":[{"label":"Play now","href":"/demos/assembly/player.html"},{"label":"Video demo","href":"https://drive.google.com/file/d/12QJi8dmDWqEKQ2wYqBIx1lfLX601aeZ7/view?usp=sharing"}],"embed":{"kind":"iframe","src":"/demos/assembly/player.html","title":"Assembly Platformer, playable","badge":"Playable in your browser","hint":"Click the game, then use A/D to move, W to jump, S to drop."},"media":[{"type":"image","src":"/media/assembly-platformer/screen.png","alt":"The platformer on its 64 by 64 bitmap display","caption":"64 x 64 bitmap display, 4 px per unit"}],"featured":true,"order":4,"url":"/projects/assembly-platformer","body":"\n## Problem\n\nWrite a complete real-time game with no language runtime: no sprites, no engine, just registers, memory and a bitmap display.\n\n## Approach\n\nThe game targets a 64 x 64 memory-mapped display and the keyboard register. It has movement, double jumping, and three item pickups (slow time, higher jump, super speed). Everything, including collision and drawing, is hand-written assembly.\n\n## Key technical decision\n\nTo make it playable on the web, I ran the original assembly unchanged: `mips.js` is a small MIPS interpreter that executes the `game_final` source, and `player.html` draws the display to a canvas and feeds key presses into the keyboard registers. There is no port to JavaScript, so what you play is the assembly.\n\n## Result\n\nPlayable at the link above. Controls: A/D move, W jump (twice for double jump), S drop, R restart, Q quit. Collect the three items, then jump off the top.\n\n{/* TODO: lines of assembly, frame rate, and anything notable about collision handling */}\n"},{"title":"Ray Tracer in C","slug":"ray-tracer","summary":"A ray tracer in C: move a light source and watch colours refract.","tags":["C","Computer graphics","Phong shading"],"metrics":[],"links":[],"media":[{"type":"image","src":"/media/ray-tracer/signature.webp","alt":"Flat object colours only","caption":"Albedo only"},{"type":"image","src":"/media/ray-tracer/diffuse.webp","alt":"Diffuse term","caption":"Diffuse term"},{"type":"image","src":"/media/ray-tracer/specular.webp","alt":"Specular term","caption":"Specular term"},{"type":"image","src":"/media/ray-tracer/full.webp","alt":"Full render with reflections and shadows","caption":"Full render: Phong, reflection, shadows"}],"featured":true,"order":5,"url":"/projects/ray-tracer","body":"\n## Problem\n\nBuild the rendering pipeline from scratch: no graphics library, only rays, intersections and lighting maths.\n\n## Approach\n\nRays are cast per pixel against transformed ellipsoids and a plane. Each hit is lit with the Phong model (ambient, diffuse, specular) and extended with shadows and recursive reflection. The four figures above split the final image into its parts.\n\n{/* TODO: confirm refraction support (the one-line description says light refracts) and which scene features are implemented */}\n\n## Key technical decision\n\nObjects are unit primitives with a transform matrix and its stored inverse, so intersection runs in object space and any scaling, rotation or translation costs one matrix multiply per ray.\n\n## Result\n\nA 512 x 512 render of two ellipsoids and a floor with shading, reflections and shadows. It is a native program, so the site shows its output rather than running it.\n\n{/* TODO: render time, recursion depth used, anti-aliasing settings for the figures */}\n\n## Build it\n\n```bash\ncd \"Light Raytracer\"\ngcc -O2 -o RayTracer RayTracer.c utils.c svdDynamic.c -lm\n./RayTracer 512 3 0 out.ppm    # size, recursion depth, antialias 0/1, output file\n```\n"},{"title":"Light2D: Dispersion in 2D","slug":"light2d","summary":"A 2D light transport simulator in C: mirrors, scatterers and glass lenses, with colour dispersion that splits white light into rainbows.","tags":["C","Optics","Snell's law","Fresnel","Dispersion"],"metrics":[{"value":"12","label":"refracting lenses in the scene"},{"value":"n + 0.5h","label":"dispersion: index shifts with hue h"}],"links":[],"embed":{"kind":"light2d","title":"Light2D interactive simulator","badge":"Interactive","hint":"Drag lights and shapes, add or remove them, and resize with the sliders. Rays re-trace live."},"media":[{"type":"image","src":"/media/light2d/scene.webp","alt":"Symmetric scene of twelve glass circles lit by a point source at the centre, with rainbow fringes along the refracted beams","caption":"Full scene: 12 glass circles, point light at the centre"},{"type":"image","src":"/media/light2d/dispersion.webp","alt":"Close-up of a beam leaving a lens, split into red, green and blue bands","caption":"Detail: dispersion fringes"}],"featured":true,"order":6,"url":"/projects/light2d","body":"\n## Problem\n\nSimulate how light moves through a box of objects: it bounces off mirrors, scatters off rough walls, and bends through glass. The starter code (CSC D18, F.J. Estrada) supplied the scene, image and ray-drawing machinery. I wrote the propagation core and then went past the brief.\n\n## Approach\n\nRays leave a point source in random directions and are traced recursively. Each step finds the nearest hit among the four walls and the circles (a quadratic solve per circle), draws the segment, then branches by material. Mirrors reflect, scattering surfaces send the ray out in a random direction in the hemisphere around the normal, and glass splits it into a reflected and a transmitted ray whose brightness is shared by the Fresnel term. Each ray is drawn additively, so bright regions come from many rays overlapping.\n\n## Key technical decision\n\nDispersion. Every ray is born with a random hue in [0, 1], converted to RGB, and that hue also shifts the glass's refractive index (n + 0.5 &times; hue). Because Snell's law then bends each colour by a slightly different angle, the white light fans out into a spectrum with no extra rendering step. The strength of the effect is a single constant.\n\n## Result\n\nA symmetric scene of twelve glass circles produces caustics and rainbow fringes along the refracted beams. The image above is the submitted render.\n\n{/* TODO: rays, depth and resolution used for the submitted render (it is 1080 x 1080) */}\n\n## Build it\n\n```bash\ncd light2D_1009319881\ngcc -O2 -o light2D light2D.c -lm              # add -fopenmp for multithreading, if available\n./light2D 1080 1080 300000 12                 # width height rays max_depth, writes light2D_output.ppm\n```\n\nOutput is a PPM, so convert it to PNG to view it. Each glass hit spawns two child rays, so cost grows quickly with depth: a small run (512 x 512, 3,000 rays, depth 8) takes about 2 seconds single-threaded.\n"},{"title":"Songify","slug":"songify","summary":"OpenAI-powered lyrics and album art. 1st place at the AMACSS AI Olympics Hackathon, demoed to 200+ people.","tags":["OpenAI","Hackathon"],"metrics":[{"value":"1st","label":"place, AMACSS AI Olympics"},{"value":"200+","label":"people demoed to"}],"links":[],"media":[],"featured":false,"order":7,"url":"/projects/songify","body":"\n{/* TODO: repo/demo link, tech stack, team size. Compact list entry only, no case-study page. */}\n"}]}