4.8 KiB
CMU Meal Estimator
A small single-page web app that estimates how many meal blocks and FLEX dollars a Carnegie Mellon student should have consumed by a given date — and how many should remain — based on their dining plan and time spent on campus.
The tool spreads a plan's totals evenly across the days you're expected to be on campus, then reports three views of that budget: a per-week rate, an amount consumed so far, and an estimated remaining balance, for both meal blocks and FLEX.
Estimates are informational and based on even distribution across on-campus days. They are not official dining balances.
Features
- Pick from all eight dining plans (Green, Blue, Red, Yellow, Tartan Flex, Scotty's Choice, Whitfield's Favor, Piper Select).
- "As of" date picker that defaults to today and is clamped to the meal period.
- Checkboxes for configured breaks ("missing periods") you spent off campus.
- A numeric input for any additional days you don't want counted.
- Live-updating results — every input recomputes the estimate instantly.
- Everything is config-driven: plans, dates, and breaks live in one JSON file. No code changes needed to adjust them.
- Dark theme with restrained CMU Tartan-red accents.
Getting started
Requires Node.js 18+.
npm install # install dependencies
npm run dev # start the dev server at http://localhost:5173
Other scripts
npm run build # type-check and produce a production build in dist/
npm run preview # serve the production build locally
npm test # run the estimation-engine tests
Configuration
All tunable values live in src/config.json. Edit that file, then reload the
page — the UI updates reactively.
{
"mealStartDate": "2026-08-24", // first day of the meal period (YYYY-MM-DD)
"mealEndDate": "2026-12-13", // last day of the meal period (YYYY-MM-DD)
"plans": [
{
"id": "green", // stable unique key
"displayName": "Green", // shown in the dropdown
"totalMealBlocks": 210, // total blocks for the whole meal period
"totalFlexDollars": 500 // total FLEX dollars for the whole meal period
}
// ...more plans
],
"missingPeriods": [
{
"id": "fall-break", // stable unique key
"displayName": "Fall Break", // shown next to the checkbox
"startDate": "2026-10-19", // inclusive
"endDate": "2026-10-20" // inclusive
}
// ...add as many as you like; each becomes a checkbox
]
}
Notes:
- The shipped plan numbers are placeholders. Replace
totalMealBlocksandtotalFlexDollarswith the real figures from your CMU dining agreement, and set the correctmealStartDate/mealEndDatefor your semester. - Add or remove plans and missing periods freely. Every missing period automatically becomes a checkbox in the UI.
- If the config is malformed (missing fields, bad dates, duplicate ids, negative totals), the app shows a readable list of problems instead of rendering broken estimates.
How the estimate works
- Total active days = every calendar day in
[mealStartDate, mealEndDate], minus the days covered by any break you check. - Daily rate =
plan total / total active days, computed separately for blocks and FLEX. - Active days consumed = active days from the start up to your "as of" date, minus checked breaks in that window, minus any additional excluded days.
- Consumed =
daily rate × active days consumed. - Remaining =
plan total − consumed. - Per week =
daily rate × 7.
Checking a break or adding excluded days shifts your expected consumption down, which is what you want when you were away and not eating on campus.
Project structure
index.html # app shell
src/
config.json # all tunable values (plans, dates, breaks)
types.ts # shared types
dates.ts # whole-day date helpers
configLoader.ts # validates + normalizes config
engine.ts # pure estimation math (no DOM)
store.ts # tiny reactive state store
main.ts # builds the UI, re-renders on change
style.css # dark CMU-accented theme
engine.test.ts # engine unit tests
sanity.test.ts # checks against the shipped config
Tech stack
- Vite for dev server and bundling
- TypeScript (strict mode)
- Vitest for tests
- No UI framework — plain DOM with a small reactive store
Tests
The estimation engine is a set of pure functions, kept separate from the DOM so it can be tested directly:
npm test
Covers day counting across boundaries, budget conservation, monotonicity when excluding days, the zero-active-days (division-by-zero) edge case, and sanity checks against the shipped config.