
Energy Truth: we tried to check India's corporate carbon numbers in one weekend
The problem: nobody checks the homework Every year, India's top 1,000 listed companies publish a Business Responsibility and Sustainability Report (BRSR). Inside it are the numbers that matter for climate: Scope 1 (emissions from fuel the company burns itself) and Scope 2 (emissions from the electricity it buys).
Lenders, investors and rating teams treat those numbers as fact. Yet a lot of independent public data describes the very same plants: Climate TRACE's satellite-based plant estimates, Global Energy Monitor's ownership records, India's grid emission factor from the CEA, and daily grid fuel mix from Grid-India. Nobody joins them up.
Plenty of good software helps companies write their carbon numbers from the inside, using meters and internal data. We wanted the opposite: an outside-in tool that checks them using only public information.
That became Energy Truth. Pick a company, and it:
- Reads the company's BRSR PDF and pulls out the reported Scope 1 and 2, with the exact page.
- Finds every plant the company owns and how much of each.
- Rebuilds Scope 1 by adding up each plant's independent emissions estimate, weighted by ownership.
- Recalculates Scope 2 from the company's own electricity use times the grid factor (0.727 tCO₂/MWh).
- Compares reported against rebuilt, with an uncertainty range, a z-score and a plain verdict.
- Links every number back to its source.
What the numbers showed (FY2024-25)
Company | Reported Scope 1 | Rebuilt Scope 1 | z-score | Verdict |
|---|---|---|---|---|
NTPC (standalone) | 327.0 Mt (filing p.17) | 361.1 ± 21.8 Mt | +1.43 | Watch |
Tata Steel (consolidated) | 78.0 Mt (filing p.47) | 30.8 ± 9.1 Mt | −4.42 | Inconclusive: low coverage |
The Tata Steel row is the most important result in the project. A naive tool would put a −4.4 z-score in big red letters. But Global Energy Monitor lists 109 units in Tata's group, while Climate TRACE has estimates for only 20 sources. The gap comes from what public data can't see, not from what Tata reported. So the verdict says Inconclusive, and the app explains why. Our rule: where data is missing, the app says so instead of implying misreporting.
One cross-check gave us real confidence. NTPC's printed Scope 2 (64,979.66 t) equals its non-renewable electricity (321.77 TJ = 89,380.6 MWh) × 0.727 exactly. That confirmed both our parser and the grid factor.
The stack
AWS services
- Amazon Bedrock: Nova writes the audit narrative; Titan Embeddings match plant names across databases.
- Strands Agents SDK (AWS open source): a "judge" agent that answers free-form questions using the same tools and evidence ids.
- Chronos-2 (Amazon's open-source forecasting model): monthly emissions and grid-intensity forecasts, runnable on SageMaker.
- Lambda + API Gateway (HTTP and WebSocket): a FastAPI app via Mangum, plus a WebSocket that streams the audit live.
- S3 + DynamoDB: raw evidence, curated tables and generated PDF reports.
- Cognito, Amplify, EventBridge + SNS, CloudWatch: sign-in, hosting, daily alert runs and a $50 budget guard.
- AWS CDK: all infrastructure as code in 8 stacks, every resource tagged
Project=energy-truth.
Everything else: LangGraph runs the audit as a 9-node agent graph. The frontend is React + MapLibre + deck.gl, with 3D terrain from AWS Terrain Tiles. Data comes from Climate TRACE, GEM, CEA, Robbie Andrew's India energy datasets, NASA GIBS and Sentinel-2.
An audit fans out into nine small agents: find the company, read the BRSR, add up owned plants, check the grid, check satellites, forecast, compare, write a cited note, prepare the PDF. Each step streams to the screen and lights up plants on the map. If a plant match scores below 75%, the run pauses and asks a human whether to count it.
What fought back
1. Our first prototype was a beautiful lie. The first version had a company dropdown and nicely styled emission cards, and almost everything behind it was hardcoded. It looked done but did nothing. A teammate's blunt review ("it gives someone a company selector and empty emissions cards without explaining why they should care") forced a restart around one question: what is an analyst actually trying to finish? The answer was a review brief with sources, not a dashboard.
2. Wiring up the endpoints. Getting the frontend, API and agent stream to talk reliably was the hardest engineering part. Locally the web app calls
/api/... through the Vite proxy. On AWS it has to call API Gateway, and the live audit needs a separate WebSocket API that posts events back with postToConnection . Every route had to read the same curated tables in both places. We eventually wrote an API test that hits every route and checks that every evidence id resolves, which caught a lot of silent breakage.3. Lambda can't hold an agent's memory. LangGraph's human-in-the-loop pause normally keeps a checkpoint in memory. Lambda functions don't stay alive between requests. Our fix: make the graph deterministic, so "resume" simply re-runs the same graph with the human's decision supplied and gets the same result.
4. Climate TRACE is annual; our forecaster wants months. We spread each plant's annual total across months using real activity profiles (regional coal and gas generation, steel and cement indices), and each year still sums exactly to the original. Every forecast carries a tag,
calibrated_capacity_mix , so nobody mistakes it for measured monthly data.5. Indian number formatting. BRSR tables write numbers like
32,69,59,946.44 . Parsers expecting 3,269,599,946 break quietly. We built a rules-based extractor that handles Indian digit grouping, normalises units and records the page and quoted line. Bedrock is used only as a cross-check: any value it proposes must appear on the cited page.6. Apples vs oranges. NTPC files standalone, Tata files consolidated, and the CT data is calendar-year while BRSR is April–March. Ownership weights had to follow each filing's boundary, and every comparison had to be fiscal-year aligned before it meant anything.
7. The map fought too. deck.gl's overlay threw an error on every frame under MapLibre's 3D terrain. We detach it in 3D plant focus and switch to HTML markers there. And the first dark "command centre" design read as military, so we redesigned it warm and editorial.
Takeaway
The interesting part of a "truth" tool isn't the big number. It's being honest about what the data can't see, and about what your own stack can't do yet. Inconclusive on 20 of 109 plants. An AI layer that falls back gracefully and refuses uncited numbers beats one that breaks when a free quota runs out.


Data attribution: Climate TRACE, Global Energy Monitor (CC BY 4.0), Central Electricity Authority, Robbie Andrew (CC BY 4.0), NASA EOSDIS/GIBS, Copernicus Sentinel-2, AWS Open Data Terrain Tiles, company BRSR filin

gs.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article