AWS Builder Center

One click to report, seven days to prove: building SunProof, a rooftop solar check that trusts only data

SunProof finds the energy a rooftop solar system quietly loses, gets the installer to fix it, and refuses to call it fixed until seven days of real output agree. Built on AWS with Lambda, Step Functions, DynamoDB, Cedar, Polly, EventBridge and Amplify.

The first version of SunProof had a button that lied.
It said After the fix: back to normal. You tapped it, the chart turned green, and the job closed as fixed. It demoed beautifully. It was also wrong about how repairs work. A loose connector gets tightened and holds for four days. A panel gets wiped and the dust is back in a week. An installer says "fixed remotely" and nobody checks.
So on day two we deleted the button. Now SunProof watches the next seven days of real output, one day at a time, and only then says Fixed. We also added a second demo house, Mehta ji's, where the repair doesn't hold, because a demo that only shows success isn't showing the product.
That change is what this post is about: learning, more than once, that "done" has to be proven.

The short version: we set out to build a solar health check. We shipped a small accountability loop between a home owner and an installer, with a home owner app, an installer app, and a check that only trusts data.

The problem: solar breaks quietly

India wants rooftop solar on one crore homes through the  PM Surya Ghar scheme , and more than 51 lakh rooftop systems are already installed under it (official portal, October 2026). Getting panels onto roofs is only half the job. Once they're up, nobody checks that they keep working.
When a system starts making 15% less, nothing alarms. The lights stay on, the inverter shows green, and the bill is too complicated to tell. Meet Sharma ji, our demo home owner, with a 3 kW system in Delhi. Over the last 30 days his roof made 48 units less than the sun allowed. That's about ₹381 and 34 kg of extra CO₂ from the grid, and he has no idea.
Even when someone notices, they still have to find the installer, explain the problem, chase a reply, and judge whether the "fix" worked. And every unit the roof fails to make comes from the grid instead, which in India means mostly coal.
In simple words: think of SunProof as a fitness tracker for a solar roof. It knows what a healthy day should look like for this roof under today's sun. When the roof falls short, it tells you how much it's costing you, gets the right person to look at it, and keeps watching until it's really better.


Four questions, four answers Every home owner we imagined had the same four questions, in this order. So the app is built around them.
  1. Is my solar working? SunProof uses pvlib, an open-source solar model, with the weather to work out what the roof should have made, as a range (P10 to P90), not a single number. If actual output sits below that range, something is wrong. If it sits inside it, a cloudy week is just a cloudy week.
  1. What is it costing me? Every shortfall is shown three ways: units lost, rupees lost, and kg of CO₂ added. The CO₂ figure uses India's grid emission factor, 0.705 kg per unit, from the CEA CO₂ Baseline Database v22.0.
The verdict can also be heard in Hindi, read aloud by Amazon Polly, because a chart doesn't help someone who won't read a report. The wording always says "worth checking", never "your installer is at fault", because a weather model gives a range, not a certainty.
  1. Who fixes it? One tap sends the evidence to the installer, and only when the home owner taps. The installer gets it in their own app, with no signup. A paper plane lands and the job appears. They reply in one tap: Book a visit, Fixed it remotely, or Not a system issue.
When they book a visit, the home owner follows it live, the way you'd track a delivery: booked, on the way, on site, done.
If the installer goes quiet, a reminder goes out at the halfway point. At the deadline, the home owner gets a ready-to-file complaint draft. SunProof never files anything itself.





  1. Did the fix really work? This is the part we care about most, and the part we rebuilt the most times. An installer saying "fixed" is a claim. SunProof turns it into a test with three rules.
Rule 1: a day passes if it's inside the expected range. For every day, the model gives a range for what this roof should make under that day's sun: P10 at the bottom, P90 at the top. A day counts as good if real output is at or above P10. That's deliberately forgiving, so a hazy day doesn't fail a real fix.
Rule 2: two low days in a row means the fix didn't hold. One low day is usually weather. Two in a row is a pattern. If the first low day is day 1, the fix never worked and the job reopens. If output came back and then fell, as with Mehta ji on day 5, it's a relapse, and the home owner is told why and what to do next.
Python
streak = 0
for day, d in enumerate(after_fix, 1):
streak = streak + 1 if d["actual"] < d["p10"] else 0
if streak == 2: # two low days in a row
first = day - 1
return "relapse" if first > 1 else "reopen", first
Rule 3: after seven days, the week has to add up. If no relapse happened, the seven days of real output together must reach the seven days of P10 together. Only then does the job close as Fixed.
Then comes the part that's easy to get wrong: what counts as recovered. We don't credit the whole week's output, because most of it would have happened anyway. We compare how close the roof was to expected before the fix with how close it is after, and credit only that improvement, in units, rupees and CO₂. That's the number both apps show and the ledger stores, counted once.
On AWS, the check is a paused Step Functions execution. When the installer marks the job done, the workflow waits on a task token. Every day an EventBridge schedule runs the check, and when it reaches a verdict the API resumes the workflow with "fixed" or "reopen". Nothing runs, and nothing costs money, while it waits.

If cleaning brings a failed fix back, the job closes as Fixed by cleaning for both sides, counted once.





Why each AWS service is there

Job
Service
Why this one
Solar model, verdicts, API
Lambda (container image) behind API Gateway
pvlib, pandas and numpy are too big for a zip, so the function ships as a container
Readings, issues, timeline, recovery ledger
DynamoDB
One table, one item per issue, with every step appended to its timeline
Waiting days for the installer, reminders, deadline
Step Functions
It can pause for a human for a week, at no cost while it waits
Daily fix checks
EventBridge
A schedule, not a server
Who may see what
Cedar
Permission rules written as policy, so they can be tested
Hindi voice
Polly (Kajal, neural)
Natural Hindi without a recording studio
Voice clips, evidence charts
S3
Short-lived, presigned links
Home owner and installer apps
Amplify Hosting
One static React build for both apps

Step Functions waits for a person. The workflow hands the issue a task token and pauses, for up to a week, at no cost. When the installer replies, the API calls  SendTaskSuccess  and it carries on. If nobody replies by half time, the wait times out and it sends a reminder; if it times out again, it drafts the complaint.
JSON
"Notify": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
"TimeoutSecondsPath": "$.timing.half",
"Catch": [{ "ErrorEquals": ["States.Timeout"], "Next": "Remind" }]
}

Cedar keeps each side in its lane. The installer sees the generation data and the evidence, never the home owner's bill or contact details. Writing that as one policy, instead of  if  statements scattered through the code, made it a single line we could test:
plaintext
forbid (principal is Pro, action, resource)
when { ["bill",
"household_contact"].contains(resource.kind) };
he trade-off of a container Lambda: a cold start of about 14 seconds after a quiet period, then about 0.15 seconds per request.
The AWS open-source stack we used:
  • Cedar (open-sourced by AWS, used through cedarpy): every request is checked against written rules.
  • AWS SAM: one template defines and deploys the whole backend.
  • The AWS Lambda Python base image (3.12, ARM64 Graviton): the container the solar engine runs in.
  • boto3: how Lambda talks to Step Functions, DynamoDB, Polly and S3.
  • AWS CLI v2: the deploy scripts.

What fought back

Days arrived in pairs. During testing, the 7-day check sometimes jumped two days at once. The cause was two open home owner tabs, each ticking the clock forward. The fix was to let the server own time: it now refuses to advance a day more than once every eight seconds, whoever asks. The lesson we took: anything that counts toward "fixed" can't live in the browser.
The cleaning that only one side saw. After a failed fix, the home owner cleaned the panels and their app said fixed, while the installer's still said "fix did not hold": cleaning lived only in the browser. Now it's a backend event that closes the job for both sides and writes the recovered energy to the ledger once.
Three screens, three different numbers. While taking screenshots for this post, we noticed the app disagreed with itself: the voice said 20% low, the evidence pack 14%, and the installer's card "4 weeks" while the chart showed 3. Each was right for what it measured (the low days only, or the whole month), but to a home owner it looks like the app can't count. Now the backend measures the drop once and every screen quotes it. An app whose promise is proof can't give three answers to one question.
We deleted the email, and the product got better. The first version emailed the installer through Amazon SES. It worked, and it was the wrong shape. An email is a message: it says something once, then sits in an inbox, and nobody knows whether it was read. What we needed was one shared truth that both people look at.
So we removed email and made the job itself the conversation. Sharma ji taps send, and seconds later a paper plane lands in the installer's app with the evidence attached. The installer replies in one tap, and Sharma ji sees "on the way", then "on site". When they mark it done, the 7-day check starts on both phones at once.
No inbox, no "did you get my mail?", no forwarding screenshots. Two people, one job, the same state, live. Removing a feature turned out to be the biggest upgrade we made.

Is the check actually accurate?

A check that cries wolf is worse than none, so we measured it before trusting it.
We took real data from two Indian solar plants: a public Kaggle dataset with 34 days and 22 inverters each, at 15-minute readings with on-site sunlight. We hid known faults in it and ran the same detector the product uses, without telling it where the faults were. Thresholds were tuned on 11 inverters and scored on the other 11, across 20 random fault layouts.
Hidden fault
Caught
System stopped
60 of 60
Sudden 20% drop
40 of 40
Slow dust build-up
30 of 38
Total
130 of 138
False alarms
31 of 5,561 healthy inverter-days (0.56%, about 1 in 180)
On the untouched data it also flagged two real inverters that make 7–8% less than their neighbours under the same sun.
What this shows, and what it doesn't:
  • We injected the faults, and the data comes from large plants, not homes. It shows detection ability, not guaranteed field accuracy.
  • A stopped system is easy to catch because output is zero. The drop and dust numbers are the meaningful ones, and dust is where it misses most.


Why there's no AI in the check

We looked at adding a Bedrock agent that would chase installers and suggest clean-or-revisit by itself. We kept it out of this build. Part of the reason is practical: model access on a new account and cost. The main reason is the product's promise. "Fixed" has to mean the data agrees. Every number in SunProof comes from physics and measurement, so nothing is generated and nothing is made up.
An agent could still help around the check, such as writing the follow-up, under the same Cedar rules as a person. That's a next step.

What it can't do yet

  • Demo homes, not real ones. Sharma ji and Mehta ji are built from public plant data, not real homes. A real pilot is the first next step.
  • No sign-in. There's no home owner sign-in, and the installer app acts as one demo installer. A real rollout needs Cognito, per-home ownership and a way to add a home.
  • No bill check yet. It checks the inverter's output against the weather, so mis-billing by the DISCOM is still a next step.
  • Replayed data in the demo. Readings after the fix are replayed, labelled REPLAY, and sped up to about 10 seconds per day for the demo.

Three things we'd tell ourselves on day one

  1. Build the failure case first. The house where the fix fails taught us more about the product than the house where it works.
  1. Let the server own anything that counts. Time, cleaning and recovered energy each broke the moment the browser owned them.
  1. "Done" is a claim. Make it prove itself. That applies to the installer's fix, and it applied to our own demo button too.

Try it

  • 3-minute video: [add the video link]
Built by Sarayu Vakkalagadda and Deepak Pydipamula for Environmental Hacks, the second stop of the WeMakeDevs × AWS Bharat Builds Tour (Oct 8–11, 2026), Waste and Energy track.
AI tools helped us work faster and more efficiently on the code and the writing. The design decisions, and every claim in this post, are ours.
If you've built something that reports "done", how do you check it really is? We'd like to hear how others prove a fix.
SunProof. One click away from a greener future.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article