Inspiration
A developer building a 5 MW rooftop solar project in Gujarat has to track regulations from MNRE (national policy), CERC (central tariffs), SECI (federal tenders), GERC (state orders), and GUVNL (utility tenders) — each publishing in different formats, on different schedules, with deadlines that can invalidate a bid if missed. Large utilities have compliance teams for this. A five-person EPC contractor doesn't — and a missed deadline can cost them the entire contract, not just paperwork. We built Greenlight to close that gap, inside the tool teams already live in.
What it does
Greenlight monitors 10 live Indian energy regulators (MNRE, SECI, CERC, RERC, GERC, GUVNL, APERC, BEE, NITI Aayog, MERC — 805 real scraped documents, not seed data) and:
- Answers questions with citations — ask anything in Slack or on the web dashboard; every claim links back to its actual source document, and every cited tender carries a live 🟢 Open / 🔴 Closed status so it never tells you to bid on something that's already closed.
- Tracks deadlines —
/deadlinesrenders a severity-ranked list of upcoming compliance dates pulled directly from the corpus. - Reminds you before it's too late — click "Remind me" on any open tender and Greenlight DMs you before it closes, instead of hoping someone remembers to check back.
- Digests what matters to you —
/mydigest(Slack or web) shows what's new in the last 3 days and what's due in the next 7, filtered to your saved interests. - Doesn't require Slack access at all — a public onboarding portal lets anyone (an independent developer, a solo consultant) register directly and link their Slack, so alerts aren't gated behind already being in a company's workspace.
How we built it
One PostgreSQL database serves two independent processes: a scheduled
ingestion pipeline (scraper.py + scheduler.py, every 6 hours) that
classifies documents with Claude Haiku, chunks them into passages, and
extracts deadlines — and a live serving stack where a FastMCP server exposes
five tools (search_regulations, search_chunks, get_document,
get_document_diff, upcoming_deadlines) over the Model Context Protocol. A
direct Anthropic API tool-use loop (not the SDK subprocess) calls those same
five tools for both the LLM agent and deterministic UI calls, with prompt
caching on the system prompt and tool definitions. We deliberately skipped a
vector DB — at 805 documents, Postgres full-text search plus the agent's own
query reformulation covers real usage, and pgvector is a one-line opt-in
already wired into the schema if the corpus outgrows it.
Challenges we ran into
A few real bugs surfaced along the way that we think are worth naming rather than glossing over:
- Relevance vs. recency — "latest solar tender" queries were ranking by
keyword match strength, so a generic undated placeholder document could
outrank the actual newest tender. Added
sort_by="date"as an explicit mode. - Duplicate Slack action_ids — multiple "Remind me" buttons in one message silently crashed message delivery — Slack rejects the entire message if any two interactive elements share an action_id anywhere in it. Every button now gets a unique per-document id, and we added local validation to catch this class of bug before it ever reaches the Slack API again.
- Two independent truncation bugs — the Anthropic call's
max_tokenscap was cutting comprehensive answers short, and — even after fixing that — Slack's own 3,000-character section block limit was silently truncating complete answers at the rendering step. Fixed both, verified with byte-for-byte reconstruction tests.
Accomplishments that we're proud of
Every feature in this submission is real and independently verified
end-to-end against live data and the real Slack API — not mocked for the
demo. The reminder system genuinely DMs a real Slack account before a real
tender deadline. The "closed" badge on a tender is computed live against
CURRENT_DATE, not hardcoded.
What we learned
Two lessons stuck. First, precision beats cleverness for anything
compliance-adjacent — we deliberately skipped a vector DB and semantic
fuzziness in favor of full-text search plus explicit filters like
sort_by="date", because a confidently wrong answer about a filing deadline
is worse than a slightly-less-clever one. Second, the two truncation bugs
taught us that "the answer looks cut off" can have stacked causes — an LLM
max_tokens cap and Slack's independent 3,000-character block limit — and
fixing the first one doesn't mean you're done; you have to verify the whole
path end to end, not just the layer you suspect.
What's next for GreenLight
The architecture is source-agnostic — adding a new state is one scraper
function and a row in the sources table, which is literally how this grew
from two states to four during the build. The same pattern extends to any
other regulated sector — pharmaceuticals, banking, telecommunications —
anywhere compliance teams currently monitor government portals by hand.
Built With
- anthropic-api
- beautiful-soup
- claude
- full-text-search
- mcp
- pdfplumber
- python
- slack-api
- slack-bolt
- slack-socket-mode
- tesseract-ocr
Log in or sign up for Devpost to join the conversation.