AWS Builder Center
Five panels of 500 W don't make 3 kW

Five panels of 500 W don't make 3 kW

A rooftop-solar quote checker on AWS, and the numbers that made us redesign it.

Asks models hard questions, writes down when they lie
Five panels of 500 watts make 2.5 kilowatts. The quote says three.
It also promises ₹85,800 in central subsidy where this example's calculation gives ₹69,000. We made up the quote and planted both problems. They let us demonstrate the checks without accusing a real vendor.
Surya Lekka checks rooftop-solar quotes before a household signs. It runs on AWS in Mumbai, with results and vendor questions available in Telugu. Our frozen reader recovered fewer than a third of printed values on unseen quotes. We show the source evidence, ask for corrections, and require confirmation before definitive results.
Surya Lekka results show the ₹1,65,850 total price matches, while five 500 W panels give 2.5 kWp rather than the quoted 3 kWp

The synthetic quote contains two planted discrepancies. Its price arithmetic still adds up.
Checking a solar quote is a little like checking a kirana bill. The addition can be right even when the quantity on a line is wrong. You need to inspect what went into the total.
That is where Arya and I started with Surya Lekka, our project for Environmental Hacks, Track 03: Waste and Energy, part of the WeMakeDevs x AWS Bharat Builds Tour.

Part one: a quote can add up and still be wrong

PM Surya Ghar targets rooftop solar for  one crore households . Coal supplied  71% of India's electricity in 2025 . Our hypothesis is that clearer quotes could help families proceed with solar. We have not measured installations, savings or emissions.
We built something narrower: a way to inspect the figures a household is being asked to accept.
Our synthetic S2 quote states its panel capacity in kWp, or kilowatt-peak, the panels' rated direct-current capacity under standard test conditions. That is distinct from the inverter's output rating.
It also includes a net-meter charge. Net metering accounts for electricity imported from and exported to the grid.
Here is the example worked through:
Check
Working
Result after confirmation
Panel capacity
5 × 500 W ÷ 1,000 = 2.5 kWp; quote states 3 kWp DC
Doesn't match
Total price
₹1,50,000 base + ₹13,350 GST + ₹2,500 net-meter charge = ₹1,65,850
Matches
Central subsidy
2 kWp × ₹30,000 + 0.5 kWp × ₹18,000 = ₹69,000; quote states ₹85,800
Doesn't match
Net cost
₹1,65,850 − the quoted ₹85,800 = ₹80,050
Matches
Even using the quote's stated 3 kWp, the general-category central calculation would cap at ₹78,000, below its ₹85,800 claim.
The net-cost subtraction still matches because it correctly uses the subsidy printed on the quote. A correct subtraction cannot establish that the subsidy itself is right.
The central calculation assumes the example's Telangana household and applicable inputs. The  rule we cover  applies to National Portal applications received on or after 13 February 2024. Eligibility also includes domestic-content requirements, or DCR, for modules manufactured in India using Indian solar cells. Surya Lekka does not verify eligibility.

Part two: every extracted value needs a source

The browser converts the PDF or photos into page images on the device. Presigned POSTs then upload those images directly to a private S3 bucket. An S3 event starts the worker Lambda.
For each page, the worker asks Amazon Textract for tables and 15 fixed queries. The request builder looks like this:
Plain text
def build_request(page):
return {
"Document": {"Bytes": page.jpeg},
"FeatureTypes": ["TABLES", "QUERIES"],
"QueriesConfig": queries_config(),
}
The queries ask for fields such as panel count, wattage and total price; the review screen gives the household a way to check the answers.
Every extracted value we keep comes with its source text. The screen highlights the relevant area on the user's page. A correction is labelled as the household's input, and calculated results show their working.
Review screen highlights “No. of modules: 5” on the quote beside the extracted panel count.

The source stays beside the reading, so the household can compare them before confirming.
In our recorded S2 run, the reader recovered 13 values correctly and none incorrectly, taking about five seconds. It missed the inverter rating, a net-meter charge and four yes-or-no details. We supplied those from the page.
Only after confirmation could the checks return "Matches" or "Doesn't match". Unresolved details became questions to copy or send to the vendor through WhatsApp.

Part three: blocked services changed the deployment

We intended to read quotes with Amazon Nova on Bedrock. My account remained under verification, with zero Nova quota, throughout the build. Textract became the reader. The Nova path remains in the code, switched off.
CloudFront presented another account restriction: we could not create distributions until verification finished. We published the frontend through Amplify Hosting, with GitHub Pages as a mirror. Both call the same API.
The backend remained in Mumbai. The SAM template still supports CloudFront hosting, but that is not the frontend arrangement running for this demo.

Part four: losing "non" changes the meaning

We also tried Textract FORMS and  AnalyzeExpense .
In our adversarial cases, FORMS could drop negative wording, making a "non-DCR" line look like a DCR line.  AnalyzeExpense  could label a subsidy as Grand Total or produce a panel quantity of 60 without support on the corresponding line.
We restricted the readings we kept to values we could connect to printed evidence. That removed some failure paths, but the evaluation showed how much remained unresolved.

Part five: the reader recovered fewer than a third

I hand-labelled 17 real quotes. Two rounds of cold calls to Hyderabad vendors got nowhere. Most examples came from quotes vendors had posted publicly, plus one provided with permission by a Hyderabad vendor.
I redacted each quote on my laptop with a small tool we built before sending anything to AWS.
We used eight quotes for development and held out nine. We froze the labels and reader at  reader-safe-2 , then read the held-out set once: 61 pages, costing about $1.22 in Textract charges.
Reading result
Development: 8 quotes
Held out: 9 quotes
Printed values recovered correctly
36 of 82
27 of 92
Filled values that were correct
36 of 46
27 of 34
Wrong values
9
4
Fills unsupported by the quote
1
3
On unseen quotes, the frozen reader recovered fewer than a third of the printed values. When it filled a value, it was correct about four times in five.
Across the raw readings of all 17 quotes in our tested scenarios, zero checks reached "Matches" or "Doesn't match". Too many required inputs were missing. With no final verdicts, we could not measure how often those verdicts would be wrong.
Our "Check this" rule, built from development mistakes, flagged four of the seven wrong or unsupported held-out fills. Three were unflagged.
That is why the household confirms every value a definitive result uses, including values without a warning. Missing values may need to be supplied first. Confirmation gives the user a chance to catch a mistake; it does not establish that they will.

Part six: our best statistic measured the wrong thing

Our draft once said that 16 of 17 quotes omitted a DCR declaration, vendor registration number or net-meter charges.
Tracing the count back to the labels exposed the problem. DCR wording was recorded in a field the evaluation script did not read. The labels had no field for registration numbers or net-meter charges at all.
We had converted gaps in our evaluation data into claims about vendors' quotes.
A text search found DCR wording in six quotes, registration or portal wording in seven, and net-meter wording in fourteen. Those hits did not establish compliance either: a portal mention is not necessarily a registration number.
We withdrew the missing-field claim from the README and video. We wanted extracted numbers to point back to their source. Our own headline statistic had to meet the same standard.

Part seven: "charges" became "accusations"

Amazon Translate drafted the Telugu at build time. It is never called when a user switches languages.
Its translation of "charges" was ఆరోపణలు, meaning accusations, including in the message addressed to the vendor. "Cells" became biological cells. Its word for "quote" could be read as "in crores".
The review changed 115 of 147 strings, followed by back-translation to inspect meaning. No professional translator has reviewed it.

The message asks about unresolved details. Missing information must not become an accusation.
Only results and vendor messages currently have Telugu support. Upload and review screens remain in English.

Part eight: an old confirmation could survive a new answer

We ran eight rounds of adversarial review. One bug let an older confirmation request overwrite newer corrections and still produce a definitive result. Another let an edit followed by a revert revive a typed-value confirmation.
The server now signs confirmations with HMAC and binds them to a job and review revision. Changing the inputs invalidates the earlier confirmation, even when the user later restores the old value.
Other findings included PDFs exceeding the 20-page limit losing pages without incomplete processing being recorded, exception messages reaching X-Ray, and saved samples bypassing daily caps.
Those findings became failing tests and fixes. At commit  0320cff , our local test collection found 1,569 tests. The regression cases matter because they preserve the exact sequences that previously broke the application.

Part nine: a deletion request needs an outcome

The worker attempts to delete uploaded page images after reading and retries deletion failures. S3 lifecycle expiration provides a backup.

The trace shows AnalyzeDocument calls lasting 2.22 and 1.99 seconds, followed by a deletion request. The request alone does not prove that every object was removed.
Job access expires after 24 hours. The API refuses expired jobs, while DynamoDB removes expired items asynchronously.  TTL does not guarantee physical deletion at the expiry instant. 
There was another privacy setting outside our application. AWS AI services may retain customer content for service improvement unless the account opts out. We configured an  AWS Organizations opt-out policy , and the deploy script checks the effective policy before choosing the site's privacy notice.
Logs and traces are designed to carry operational information without quote text. The exception-message bug showed why we had to inspect more than our own logging statements.
We also tested the notification path. The failed-job alarm email arrived in my inbox with the reason "Synthetic test for the demo video". We had manually triggered the alarm state; the email confirmed that the notification reached us.

CloudWatch's manually triggered alarm reached the inbox through SNS. This was a notification test.

Each service has a specific job


The backend runs in Mumbai. Hosting and the one-time translation step are separate from document processing.
Service
Its job here
Why this one
API Gateway
HTTP endpoints
Connects the browser to the backend functions
Lambda
Seven Python 3.12 functions
Runs job handling, reading and checks
S3
Private page-image storage
Accepts direct uploads through presigned POSTs
Textract
Tables and fixed queries
Became the reader while Nova access was unavailable
DynamoDB
Jobs, results and review state
Supports conditional updates and expiry records
Secrets Manager
Confirmation-signing secret
Holds the per-stack key
SQS
Failed worker events
Keeps failed events available for investigation
CloudWatch and SNS
Monitoring and alarm emails
Makes operational failures visible to us
X-Ray
AWS-call traces
Shows the work performed by an upload
Translate
Initial Telugu draft
Used at build time, not during a user session
Amplify Hosting
Public frontend
Hosted the site while CloudFront creation was blocked
GitHub Pages serves as a mirror. The backend resources are defined in one AWS SAM template in  ap-south-1 .
Our Textract configuration costs about US$0.02 per page. Daily check/page limits, per-address limits and a kill switch restrict new work; a $15 monthly budget alerts at $3, $8 and 100%, alongside a zero-spend budget. These alerts notify us rather than cap the bill, and  notifications can lag spending .

The evidence supports a narrow claim

A separate script, sharing no calculation code with the app, recomputed 11 findings from labelled inputs. I rechecked them by hand. All 11 agreed.
What we can claim
What we cannot claim yet
The synthetic example demonstrates the specified calculations after confirmation
Comparable completion on arbitrary uploaded quotes
All 11 checked labelled findings agreed with separate calculations
A general accuracy guarantee
The frozen held-out reader produced the reported results
That confirmation reliably prevents every user error
Telugu results and vendor messages have received review
Professional translation validation or a fully Telugu interface
The app helps inspect quote inputs and arithmetic
Verified eligibility, vendor quality, savings or emissions reductions
One person tried Surya Lekka with our made-up S3 quote during the demo. The results showed five items needing checking and four missing details, followed by questions for the vendor. This illustrates the review flow; it is not a measure of usability or accuracy. We haven’t yet tested with enough first-time users to report usability results, and our simulated tester logs aren’t included as user evidence.
A simulated walkthrough before real testing found usability issues, but involved no real users. It led to "Fix a number" focusing the field, clearer "The app read 550 W; you changed it to 500 W" wording, and a retry for shared sample links.
Our next priorities are better reading coverage on more quotes and a measured evaluation of Nova when access arrives. Telugu on earlier screens, reviewed Hindi support and state-subsidy checks are also unfinished. Whether quote checking changes installation decisions remains an open question.

Three things I would tell myself on day one

  1. Inspect what a statistic actually counts. Our 16-of-17 claim measured gaps in the labels, not omissions in the documents. Finding the relevant text forced us to withdraw it.
  1. Put evidence beside the number. Being correct about four times in five filled values still leaves wrong inputs available for calculation. The user needs an accessible source and a way to correct it.
  1. Review translations inside the task. "Charges" becoming "accusations" would change the tone of a message sent to a vendor. Back-translation helped us inspect the meaning of the words we were asking people to send.
If a number is wrong, can the person relying on it find its source before acting?

How we built it

Arya and I are final-year CSM students. I prepared the data, ran the evaluation and managed AWS. Arya is our team lead and handles testing and the demo. We made the product decisions and reviewed the implementation and results.
We used Claude Code for implementation from our specifications, Claude for planning and language review, and ChatGPT for research and adversarial reviews. AI also assisted the simulated walkthrough. These tools were part of the build, not independent human reviewers. The repository documents the tools and third-party licences.

Try it with the made-up quote

  1. Open the  live site  and choose Sample 2. It uses a saved synthetic reading, not a live Textract call.
  1. Review the values and complete the household questions for the Telangana example. Confirm the numbers each result uses.
  1. Inspect the system-size and subsidy "Doesn't match" results, then compare them with the matching price arithmetic.
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