A small full-stack carbon emissions calculator. The app already works end-to-end: enter activity quantities, see a live total in kg CO₂e, save calculations to the server, and view the saved list.
Your job is to extend it with three small features.
- Client: Vite + React + TypeScript (plain CSS)
- Server: Node + Express + TypeScript
- State: in-memory on the server (no database)
npm run install:all
npm run devThis starts the server on http://localhost:3001 and the client on http://localhost:5173. Open the client URL in your browser.
You may install any additional npm dependencies you'd like.
client/src/
App.tsx main UI: inputs, total, save, saved list
ActivityInput.tsx reusable labelled number input
api.ts fetch wrappers for the backend
calculate.ts client-side total computation
types.ts shared types
server/src/
index.ts express bootstrap
routes.ts GET /api/factors, GET/POST /api/calculations
factors.ts seed emission factors
store.ts in-memory calculation list
types.ts shared types
| Method | Path | Body / response |
|---|---|---|
| GET | /api/factors |
Factor[] |
| GET | /api/calculations |
Calculation[] (newest first) |
| POST | /api/calculations |
{ entries: { factorId, quantity }[] } → Calculation |
type Factor = { id: string; label: string; unit: string; kgCO2ePerUnit: number };
type Calculation = {
id: string;
entries: { factorId: string; quantity: number }[];
totalKgCO2e: number;
createdAt: string;
};Next to each activity input, show what percentage of the running total that activity contributes. For example, if electricity contributes 23.3 kg and a flight contributes 246 kg, electricity should show 9% and the flight 91%.
- Don't display anything when the total is zero.
- Update live as the user types.
Add natural gas as a fourth activity, with a factor of 5.3 kg CO₂e per therm.
It should work end-to-end: visible as a new input in the UI, included in the running total, and persisted to saved calculations alongside the others.
Add a new endpoint GET /api/calculations/summary returning aggregate totals across all saved calculations:
{
totalKgCO2e: number;
byActivity: Record<string, number>; // keyed by factorId
}Surface it in the UI as a footer below the saved list, e.g. "All-time emissions: 482.30 kg CO₂e", with a per-activity breakdown.
If you finish the three tasks above with time to spare, or you'd rather skip them and tackle a single larger piece of work, pick one of the following. Either is intended to be the substantial part of the session (~2 hours), and the conversation around the trade-offs matters as much as the code itself.
Right now an entry is just { factorId, quantity } and the total is Σ quantity × kgCO2ePerUnit — there is no notion of over what time the activity happens, so "100 kWh" and "1 short-haul flight" sit on the same axis even though one is implicitly per-month and the other one-off.
Extend the model so each entry has a frequency (e.g. daily | weekly | monthly | annual | one-off). Compute and display:
- Annualised total — kg CO₂e per year, normalising every entry by its frequency.
- Multi-year projection — e.g. 1 / 5 / 10 year totals.
Persist frequency on save so saved calculations still display their annualised total in the list.
Things to think about:
- Multipliers — 365 vs 365.25? 52 vs 52.1775? Pick something defensible.
- "One-off" is genuinely ambiguous over a multi-year horizon — does a single flight contribute its full kg in year 1 only, or amortise across the projection? Either choice is fine; just be deliberate.
- Existing saved calculations don't have a frequency. What's your migration / default story?
- The UI now has more than one number to display (current-period, annualised, projected). Which is the headline, and how do you keep the screen uncluttered?
Verify by entering e.g. 100 kWh / month + 50 miles / week + 1 flight (one-off), saving, refreshing, and confirming the saved record's annualised total matches what you expect.
Given an existing saved calculation as a baseline and a target percentage reduction (e.g. "I want to cut 20%"), produce 2–3 ranked strategies that hit (or get as close as possible to) the target. Each strategy is a concrete set of modifications to the entries plus the projected new total.
Suggested strategy archetypes — pick at least two:
- Uniform — scale every activity down by the same factor.
- Biggest-impact-first — reduce the largest contributor until the target is met.
- Substitute — swap part of one activity for a lower-emission alternative (e.g. 30% of car miles → public transit at ~0.04 kg/mile; short-haul flight → train). You'll need a small substitution table — design it.
Suggested API (deviate if you prefer):
POST /api/calculations/:id/reduction-plan
body: { targetPercent: number }
returns: {
baselineKg: number,
targetKg: number,
strategies: Array<{
name, description, modifiedEntries,
projectedKg, actualReductionPercent, achievable
}>
}
UI: a target-percent input on a saved calculation, then a side-by-side comparison of the strategies.
Things to think about:
- What's the loop invariant for your greedy reduction? When do you stop — at the target, just past it, or whichever is closer?
- What if the target is unachievable given your substitutes (e.g. user wants 95% reduction but 90% of their footprint is flights)? How does the response convey that?
- How are the strategies ranked? By total reduction? Minimal lifestyle disruption (number of entries touched)? Be ready to defend the choice.
- Edge cases: target 0%, negative target, a baseline with a single entry, floating-point overshoot.
Verify by saving a calculation dominated by a single high-emitter (e.g. 2 short-haul flights ≈ 492 kg), then asking for 30% and 99% reductions — strategies should differ visibly, and the 99% case should clearly indicate it isn't achievable.
- Working features over polish
- Clean, readable TypeScript
- Sensible handling of edge cases (empty inputs, zero totals, no saved calculations yet)
- Talk through your thinking — this is a pair session, so we want to hear how you're approaching it