Files
cmu-meal-estimator/README.md
T
2026-09-17 01:29:20 -04:00

185 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 that is fetched at
runtime. Edit it on the server and changes appear on the next page load — no rebuild needed.
- Your plan, checked breaks, and extra-days value are remembered between visits (via
`localStorage`). The date always resets to today.
- Dark theme with restrained CMU Tartan-red accents.
- Ships as plain static files — no server-side code or backend.
## Getting started
Requires [Node.js](https://nodejs.org/) 18+.
```bash
npm install # install dependencies
npm run dev # start the dev server at http://localhost:5173
```
### Other scripts
```bash
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 [`public/config.json`](public/config.json). This file is **fetched at
runtime**, not compiled into the bundle — so after deployment you can edit the deployed
`config.json` directly and the changes take effect on the next page load, with no rebuild.
During development, edit `public/config.json` and reload the page.
```jsonc
{
"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 `totalMealBlocks` and `totalFlexDollars`
with the real figures from your CMU dining agreement, and set the correct `mealStartDate` /
`mealEndDate` for 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
1. **Total active days** = every calendar day in `[mealStartDate, mealEndDate]`, minus the days
covered by any break you check.
2. **Daily rate** = `plan total / total active days`, computed separately for blocks and FLEX.
3. **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.
4. **Consumed** = `daily rate × active days consumed`.
5. **Remaining** = `plan total − consumed`.
6. **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.
## Deployment (static hosting)
The app is a purely client-side static site — no backend, no server-side rendering. To deploy:
```bash
npm run build # outputs everything to dist/
```
Then copy the **contents of `dist/`** to wherever you're serving from. The build produces:
```
dist/
index.html
config.json # the live, editable config
assets/ # hashed JS + CSS
```
### Subpath hosting
Asset paths in the build are **relative**, and `config.json` is loaded relative to the page's
own location. That means the app works unchanged whether it's served from a domain root or a
subpath — e.g. `https://www.cmu.edu/~you/meal-estimator/`. No base-path configuration or rebuild
is needed to move it between locations.
The one requirement is that the app is served as a directory with a trailing slash (e.g.
`.../meal-estimator/`). Virtually all static servers (Apache, nginx, and the like) redirect
`.../meal-estimator` to `.../meal-estimator/` automatically, so this is handled for you.
### Editing config after deployment
Because `config.json` sits alongside `index.html` in the deployed files and is fetched at
runtime, you can update plan numbers, dates, or breaks by editing the deployed
`config.json` directly. No rebuild or redeploy of the JS is required — just reload the page.
## Data persistence
The selected plan, checked breaks, and additional-days value are saved to the browser's
`localStorage` and restored on the next visit. The "as of" date is intentionally **not**
persisted — it always defaults to today. Persistence fails silently if `localStorage` is
unavailable (e.g. private browsing), and stale entries (a plan or break no longer in the config)
are ignored on load.
## Project structure
```
index.html # app shell
public/
config.json # all tunable values (plans, dates, breaks); served + fetched at runtime
src/
types.ts # shared types
dates.ts # whole-day date helpers
configLoader.ts # validates + normalizes config
configFetch.ts # fetches config.json at runtime (subpath-aware)
engine.ts # pure estimation math (no DOM)
store.ts # tiny reactive state store
persistence.ts # localStorage load/save of selections
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](https://vitejs.dev/) for dev server and bundling
- TypeScript (strict mode)
- [Vitest](https://vitest.dev/) 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:
```bash
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.