DEV Community

Cover image for From Kilowatt-Hours to Carbon Counts: Shaping Accurate Energy Reporting with OED
Johnny Santamaria
Johnny Santamaria

Posted on • Originally published at linkedin.com

From Kilowatt-Hours to Carbon Counts: Shaping Accurate Energy Reporting with OED

"garbage in, garbage out" - Some quote I heard before

Introduction

Every building tells a story through its energy usage.

A classroom left lit overnight, an HVAC system running longer than expected, or a sudden spike in electricity consumption across campus, these patterns often go unnoticed without the right tools to visualize them. That’s where the Open Energy Dashboard (OED) comes in.

OED is an open-source platform that helps organizations transform raw utility data into meaningful insights through charts, analytics, and real-time monitoring.

Schools, universities, sustainability teams, and facility managers use the dashboard to better understand how energy and resources are consumed across their buildings. Instead of sorting through spreadsheets or complicated meter data, users can quickly identify inefficiencies, compare trends, and make informed decisions that reduce both operational costs and environmental impact.

The development build is similar to the production build. In order to run development, you must install Docker which is a container for images, like screenshots of a build you can run on your particular operating system. Here is the app running using ‘docker compose up’, and to quit the browser I used ‘docker compose down’.

OED dev build

The Issue

issue link

In this contribution, I worked on LR18, an integration test focused on validating energy conversion behavior in the readings API. The test ensures that 15-minute interval energy readings can be correctly converted into kg CO₂ values while maintaining proper daily aggregation over large time ranges. While the change may seem small from the outside, it introduced interesting challenges involving fixture setup, unit conversions, expected CSV outputs, and backend testing consistency across the codebase.

The following files and resources were the most important locations in the codebase for understanding and implementing the LR18 integration test:

  1. src/server/test/web/readingsLineMeterRangeQuantity.js This was the primary file where the LR18 test case was implemented. The file contains integration tests for line chart range (min/max) quantity meter readings across different aggregation ranges and unit behaviors. I used the surrounding LR1–LR7 tests to understand the expected structure, helper utilities, API request format, and CSV validation workflow. LR18 was added here because it specifically validates range aggregation behavior combined with unit conversion into kg CO₂.

  2. testing.md Documentation link

This design document was important for understanding the intended testing requirements and naming conventions used throughout the project. The document outlines the required test scenarios for readings APIs, including line range (min/max) tests, aggregation expectations, and expected behavior across different unit conversions and time ranges. It helped confirm that LR18 belonged in the line range quantity test suite and clarified how expected CSV outputs should be structured.

  1. readings_ri_15_days_75.csv This readings dataset file contains the raw 15-minute interval test readings used for validating aggregation and conversion behavior. The LR18 test loads this dataset into the database during setup to simulate realistic meter readings over a long time range. These readings are later aggregated into daily points by the API.
  2. expected_line_range_ri_15_mu_kWh_gu_kgCO2_st_-inf_et_inf.csv This expected CSV file represents the correct API response output for LR18 after daily aggregation and unit conversion into kg CO₂. The test compares the API response against this file using helper assertions to verify timestamps, minimum/maximum values, and aggregation correctness.
  3. src/server/util/readingsUtils.js This utility file contains important helper functions and shared testing utilities used throughout the readings integration tests. Functions such as:
  • prepareTest()
  • parseExpectedCsv()
  • expectRangeToEqualExpected()
  • getUnitId() were essential for seeding test fixtures, loading expected CSV data, resolving unit IDs dynamically, and validating API responses against expected outputs.
  1. src/server/test/web/readingsLineMeterQuantity.js This file was used as a reference implementation because it already contained an existing L18 conversion-based test. I compared its fixture setup, conversion seeding, and naming conventions against LR18 to ensure consistency between quantity tests and range quantity tests.

  2. package.json

The package.json file was useful for identifying available npm scripts and test commands used to run the OED test suite. This helped with running targeted test cases and validating the LR18 implementation locally during debugging.

  1. npm run testData This command was referenced in the testing documentation and used to load sample data into the system for validating the readings API behavior during local testing and debugging.

The Product/Codebase

Tech Stack

Redux Toolkit (RTK): Manages the application's state, ensuring that data remains consistent and synchronized across all parts of the dashboard. Handles things like user selections, fetched meter data, UI state, etc.

Plotly.js: Generates the interactive graphs and charts that visualize energy and resource consumption.

React: The core frontend library used to build the user interface. It renders components for dashboards, selectors, and controls.

Node.js: Runs the backend server, handling data API requests and managing business logic and authentication.

PostgreSQL: Stores all historical energy/resource data, meter definitions, user configurations, etc. Acts as the main database.

Express.js: Web server running on Node.js, exposes RESTful API endpoints for the frontend to request and update data.

TypeScript: Provides type safety and improved developer experience for both frontend and backend codebases.

Docker: Used to containerize and deploy the application and its dependencies for consistent environments (optional in some deployments).

Syatem diagram

System diagram of OED

Use case

“A user wants to see the CO₂ emissions for electricity usage over a selected date range.”

Step-by-step walkthrough:

  • User selects a building and a date range in the dashboard's UI (React) and clicks "View CO₂ Emissions."
  • The React UI dispatches an action to Redux Toolkit to update the state with the new selection.
  • Redux triggers an asynchronous operation (a thunk or saga) to fetch the relevant readings and emission data from the backend.
  • The browser makes a REST API request to the Node.js/Express API, specifying the building and date range.
  • The backend API receives the request. It queries the PostgreSQL database for all energy readings within the range and uses internal logic (data services) to convert those readings into kg CO₂, applying the project’s conversion formulas (the logic tested by the LR18 integration test).
  • The backend returns a JSON object with time-stamped CO₂ emissions data to the frontend.
  • The Redux store receives the data and the UI updates accordingly.
  • Plotly.js renders an interactive chart with the emissions data, letting the user explore and analyze the results visually.

Challenges

Challenge 1: Inconsistent Unit Conversion Setup in LR18

One of the main technical challenges I faced was diagnosing why the LR18 integration test did not match the expected API behavior for converting readings into kg CO₂. Originally, the test was still requesting the kWh graphic unit, even though project requirements and the expected CSV output specified kg CO₂.

Self-Directed Problem-Solving Attempts

Compared with Existing Tests: I reviewed LR1–LR7 to look for patterns in how units were handled and where API requests might be constructed differently.
Codebase Search: I searched for kgCO2 and related conversion logic in both backend tests and the conversion fixture seeding code to find established best practices.
Implementation Cross-Reference: I closely examined the relevant section in readingsLineMeterQuantity.js, focusing on how conversion fixtures were prepared and used, to spot differences.

Additional Help

I leveraged GitHub Copilot to reason through discrepancies, generating code suggestions and checking my test’s construction against unit-handling best practices.
I carefully read the project’s testing.md guidelines to ensure my approach to data seeding and test assertions matched the intended design.

Status

Resolved. The LR18 test setup was corrected: the test now requests the API with the correct kg CO₂ graphic unit and seeds the conversion fixtures to align with expectations. The test passes and provides accurate verification for future code changes.

Challenge 2: CSV Naming and Expected Output Consistency

I also struggled with inconsistent naming conventions for expected CSV files (kg_of_CO2 vs. kgCO2) when comparing actual output to reference data files. This led to confusion in test assertions and in locating previous examples for regression testing.

Self-Directed Problem-Solving Attempts

Repository Search: I searched the repo for both naming styles to see which was most commonly used.
Pattern Review: I surveyed surrounding tests (both quantity and range-based) to verify which format appeared in assertion logic and file loading calls.
Assertion Validation: I checked the test utilities (such as readingsUtils.js) to see if there were helper functions that auto-resolved or validated expected outputs.

Additional Help

I referenced conversion-based tests in readingsLineMeterQuantity.js to ensure consistency in both naming and output structure.

Status

Resolved. I updated the LR18 test’s CSV file expectations and assertions to use the kgCO2 convention, eliminating confusion across the codebase and achieving a passing assertion.

Challenge 3: Maintaining Consistent Branch Workflow (Using git cherry-pick)

A key workflow challenge our team encountered was maintaining a consistent branch structure during development. There were instances when new commits were accidentally made to the wrong branch, which could cause confusion, code conflicts, or integration issues if left unaddressed.

Self-Directed Problem-Solving Attempts

Manual Merging: Attempted to merge the changes or re-apply the edits manually onto the correct branch, but this was time-consuming and could introduce mistakes.
Branch Reset & Rebase: Explored using git reset and git rebase to move commits, but these approaches risked rewriting history or affecting unrelated changes.
Git Documentation Review: Consulted the official Git docs and several online tutorials to look for a safer, more targeted solution for moving just the desired commit(s).

Additional Help

Once I identified git cherry-pick as a possible approach, I confirmed the workflow with Copilot and checked with project teammates (if applicable) to ensure this was in line with team conventions.

Status

Resolved. I successfully used git cherry-pick to copy the intended commit(s) to the correct branch without affecting unrelated work. This helped maintain branch hygiene and enabled a clean pull request history.

Solution

To address the issue of ensuring accurate conversion of energy usage data to kilograms of CO₂ (LR18), I developed an integration test that simulates a user requesting CO₂ emissions data over a specific date range. I modeled my implementation outline using the structure and methodology established in existing test cases LR1–LR7, which demonstrated good practices for preparing test data, invoking the API endpoint, and checking results.

In my solution, I set up sample meter readings in the database, triggered the backend logic to request the calculated CO₂ values, and verified that the backend (Node.js/Express) returned the correct results according to the expected conversion rules. This test helps ensure the data displayed in the dashboard (via Redux Toolkit and Plotly.js in the frontend) will always be calculated correctly, catching any regressions as the codebase evolves.

I relied heavily on Redux Toolkit to manage the application state in the frontend, this ensures that, once the backend API returns the correct CO₂ data, the entire dashboard updates consistently and all visualizations generated by Plotly.js reflect accurate numbers.

On the backend, I focused on the API logic and data services component (see System Diagram: Node.js/Express and Data Services) that handles the data transformation and communicates directly with the PostgreSQL database. My test verifies that conversions at this backend layer, before the data even reaches Redux or Plotly, are correct.

I verified the solution using automated test runners via Mocha. After implementing the LR18 test, I ran the full test suite and confirmed that all tests, including my new one, passed. This proves that both existing and new requirements for data conversion are met.

Testing solution with Mocha

See the full solution here

Top comments (0)