I wanted to build something that mattered. Climate change is one of those topics where awareness is the first barrier — most people don't know their actual footprint. So I built Carbon.Ledger: a full-stack web app that lets you log activities, see your CO₂ impact, and get AI-powered tips to cut back.
Here's how I built it, what went wrong, and the one engineering decision I'm most proud of.
The Stack
- Backend: Django 5
- Frontend: Tailwind CSS + htmx + Chart.js
-
LLM: NVIDIA NIM —
mistralai/mistral-large-3-675b-instruct-2512 - Database: SQLite (dev) / PostgreSQL (prod)
- Cache: Redis (prod)
- Deployed on: Render.com
The Three Pillars
The app is built around one simple loop:
Understand → Track → Reduce
Understand
Users can read lesson articles, browse a glossary (CO₂e, Scope 1/2/3, Net Zero), and ask free-text climate questions via an LLM-powered Q&A — rate limited to 10 questions/day.
Track
An activity logging form lets users log transport, energy, food, and goods. CO₂ is auto-computed on save using emission factors stored in the database — no LLM involved here. The dashboard shows a 30-day breakdown with a Chart.js donut chart and progress against a monthly goal.
Reduce
Rule-based insights compare the user's totals against national average benchmarks. The top emission category drives filtered recommendations. And once per day, the LLM generates a personalized tip — cached 24h per user.
The Hardest Part: LLM Hallucination Guard
This is the part I'm most proud of.
LLMs make up numbers. That's a real problem when you're talking about emissions data — if the model says "beef produces 5 kg CO₂ per kg" when the real figure is 27, that's actively harmful misinformation.
My solution in services/llm.py:
def _contains_hallucinated_numbers(response: str, context: str) -> bool:
"""
Extract all numbers from the LLM response.
Check every one of them exists in the context we provided.
If not → hallucination detected.
"""
import re
response_numbers = set(re.findall(r'\d+\.?\d*', response))
context_numbers = set(re.findall(r'\d+\.?\d*', context))
return not response_numbers.issubset(context_numbers)
The flow:
- Call LLM with context (user's actual data)
- Extract all numbers from the response
- Check every number exists in the context
- If not → retry with explicit "do not use numbers" instruction
- If still fails → return a safe static fallback message
On top of that:
timeout=10.0andmax_retries=1are explicitly set to prevent hung Gunicorn worker threads on slow LLM responses.
Key principle: the LLM is never used for calculations. All math is Python/DB.
htmx for the Category Dropdown
The activity log form has a dependent dropdown — selecting a category dynamically loads the relevant emission factors. I used htmx for this instead of writing custom JavaScript:
<select name="category"
hx-get="/factors/"
hx-target="#factor-select"
hx-trigger="change">
One attribute. No JS file. The server returns a partial HTML snippet with the filtered factors. This is exactly what htmx is built for.
Deployment on Render
Render makes Django deployment straightforward. A few Django-specific things worth noting:
# settings.py
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = not DEBUG
SESSION_COOKIE_SECURE = not DEBUG
CSRF_COOKIE_SECURE = not DEBUG
Render terminates TLS at the proxy level, so SECURE_PROXY_SSL_HEADER is essential — without it Django won't recognize HTTPS requests and will redirect loop forever.
Also: randomize your admin URL via env var:
ADMIN_URL = env('ADMIN_URL', default='admin/')
Stops bots from hammering the default /admin/ endpoint.
What I'd Do Differently
Async LLM calls. Right now the LLM calls are synchronous — a slow NVIDIA NIM response blocks a Gunicorn worker. The fix is Celery + Redis for background tasks. I skipped it for the MVP but it's the first thing I'd add.
Audited emission factors. The 22 factors I seeded are approximations from public sources, not peer-reviewed figures. For a production app, you'd want to integrate a verified dataset like the UK Government GHG conversion factors.
Email verification. Currently anyone can sign up with any email. Not a problem for an MVP, but a real gap.
Results
- 146 tests, 99% coverage
- Deployed and live
- Built in under a week
🔗 Live demo: carbon-footprint-awareness-4qpf.onrender.com
🐙 GitHub: github.com/Divyansh0208/Carbon-Footprint-Awareness
If you're building something with Django + LLMs, the hallucination guard pattern is worth stealing. And if you have thoughts on better emission factor datasets, I'd love to hear them in the comments.










