Skip to content

r0jer/carbon-test

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Carbon Emissions Calculator — Technical Test

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.

Stack

  • Client: Vite + React + TypeScript (plain CSS)
  • Server: Node + Express + TypeScript
  • State: in-memory on the server (no database)

Getting started

npm run install:all
npm run dev

This 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.

Project layout

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

API

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;
};

Tasks

1. UI — per-activity contribution breakdown

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.

2. Calculation — add a new emission activity

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.

3. Endpoint — aggregation summary

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.

Stretch task — pick one

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.

A. Annualised footprint normaliser

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.

B. Reduction-target solver

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.

What we're looking for

  • 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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors