Inspiration

Picture a small factory in Shah Alam. Forty-something staff, supplies parts to a big multinational. One morning the customer sends over an ESG questionnaire and gives them two weeks to fill it in. There's no sustainability team. There's no budget to hire one. The guy who got handed the form also does payroll and orders the stationery.

The information isn't missing exactly. It's just everywhere. Electricity bills in a folder, an HR spreadsheet nobody has checked since August, an anti-bribery policy someone wrote in 2019 and never opened again.

We kept coming back to the same thing. There are already loads of tools that calculate your carbon footprint. That's the easy bit. What that guy actually needs to know is which answers he can back up, and what to do about the ones he can't.

This is happening all over Malaysia right now. Listed companies are pushing ESG requirements down to their suppliers, and the suppliers are small businesses with no idea where to start. That's who we built for.

What it does

You upload the documents you already have. EcoMetrics reads them and tells you where you actually stand.

It tells you what you can prove. Every number we pull out gets tagged as verified, estimated, or inferred, and it keeps a link back to where it came from. In our demo data there are nine months of electricity bills that are real meter readings, one month that's only an estimate, and three months that just aren't there. We show it exactly like that. Something only goes green if everything behind it is verified. One estimated reading and it drops to amber. We didn't want to build another tool that quietly rounds a guess into a fact.

It actually reads your documents. Our demo company has an anti-bribery policy. Most assessment tools would see the file, tick the box, done. Ours opens it and finds it's still marked draft, nobody signed it, it's six years past the review date it set for itself, the owner field literally still says "[Insert name of Compliance Officer]", and it points to a gift register that was never created. Having a policy and following one aren't the same thing, so that stays amber.

It also picked up two workers being paid below the RM1,700 minimum wage. That was sitting in a spreadsheet column that nobody had looked at.

It shows the maths. We rank issues with severity × customer asked × quick win, and we print that on the screen. The minimum wage one comes out at 3 × 2 × 1.5 = 9.0. If someone asks why it's top of the list, they can check it themselves in about four seconds.

It closes things properly. Every issue can become an action with an owner and a due date, worked out from how serious it is, and always landing before the customer's deadline. Then here's the part we like: fix the spreadsheet, upload it again, and EcoMetrics re-runs everything and closes that action by itself. Marked as verified by re-scan. Nobody ticked a box. Everything else that's still broken stays open.

It stops the report being edited later. When the report is done we hash it and write the hash to Solana. Change a number after that and verification fails.

How we built it

One rule the whole thing is built around: the AI reads, the code decides.

Gemini only ever turns a messy document into JSON. Every rule, every score, every red/amber/green is plain TypeScript. We did it that way on purpose. If your ranking comes out different each time you run it, it's not really a ranking, and telling an auditor "the model decided" isn't going to go down well.

Bills are a PDF, so those go to Gemini vision. The policy is a Word doc, so mammoth pulls the text out and Gemini reads it. The payroll file we don't send to the AI at all. It's a spreadsheet, it's already structured, so SheetJS just parses it. Faster, free, and it can't hallucinate a salary.

Everything lands in one evidence table. One row per fact, each with the file it came from, where in the file (page 6, Payroll Dec 2025!I14) and how much we trust it. If we can't point at where a fact came from, we don't keep it.

On top of that sit twelve rules that look for gaps, contradictions and compliance problems across E, S and G. We don't save the results anywhere. They get recalculated from the evidence every time you load a page, which sounds wasteful but meant the re-upload feature was basically just comparing two lists.

For Solana we take the finished report, sort the keys so it's always the same JSON, SHA-256 it, and write ESGPROOF|v1|<hash> to devnet using the Memo program. There's a verify endpoint that recalculates the hash and compares. We left the mismatch case demoable because it's more convincing than the happy path.

Stack is Next.js on Vercel, API routes as the backend, Tailwind and shadcn/ui, Upstash for storage.

Challenges we ran into

Blank isn't zero. Twelve people in the payroll file have nothing in the training hours column. Easiest thing in the world is to read that as 0 and move on. But zero training and no record of training are completely different answers to give your customer. So our extractors skip empty cells entirely and the rules report it as unknown. Sounds like a small thing. It ended up shaping the whole data model.

Vercel's filesystem is read-only. We were storing everything in a JSON file, which worked great locally and would have thrown away every action the moment we deployed. Caught it early, thankfully. Now it's a file on your laptop and one Upstash key in production, behind the same interface so the app doesn't know the difference.

Cutting our own idea. The first version of this ended with a button that let you pay for carbon offsets on-chain. Easy demo, obvious blockchain angle. We dropped it. Offsets don't fix anything operationally and the whole point of our app is being honest about what's real, so it would have been working against us. Anchoring the report is a harder thing to explain but we can actually defend it.

We had to build the mess first. You can't test whether a tool is honest using clean data. So before we built anything we wrote a fake company and gave it genuinely broken records: three months of bills missing, one estimated meter reading, a duplicate staff ID, a headcount that comes out as 45, 43 or 42 depending which sheet you trust. Working out what should be broken taught us more about the problem than building the features did.

Accomplishments that we're proud of

The re-upload thing. Fix the two salaries, upload the file again, and that action closes on its own while everything else stays open. We got a completion status that can't be faked, in one day, and that's the bit we'd show first.

Reading the policy properly. Checking a file exists is nothing. Opening it and telling someone it's an unsigned draft from 2019 with a missing appendix is a different product.

What we left out. No login, no ORM, no database server, no state library, no charts. Five pages, twelve rules. Every extra thing we didn't add is one less thing that could have broken at 3pm, and it still ships as one Next.js app.

And a blockchain feature we can explain in a sentence without feeling awkward about it.

What we learned

Our instinct going in was to hand the AI as much as possible. We got better results doing the opposite. Once Gemini was only doing extraction and all the judgement was normal code, everything got faster, cheaper, repeatable, and we could explain any result to someone non-technical. For anything compliance-related that explainability isn't a bonus feature, it's most of the value.

The other thing that surprised us is that being honest demos better than being clever. The moment people react to isn't a calculation. It's the app saying we can't prove this yet.

What's next for EcoMetrics

  • Line the indicators up with Malaysia's Simplified ESG Disclosure Guide so the answers drop straight into the format customers are already asking for
  • Read the customer's actual questionnaire, so it answers their form instead of a generic checklist
  • More document types: water bills, waste manifests, DOSH safety records, training registers
  • Email digests for whoever owns the open actions
  • Track it across quarters, so a supplier can show a customer they're improving and back up every step

Built With

Share this project:

Updates