SunShift Flex: the local solar match a bill hides
Nebius Global AI Hackathon — Best Apps and Agents
Inspiration
Australia has one of the world's highest rates of rooftop solar. On a sunny afternoon, one roof can make more electricity than the people under it can use. The surplus is exported for a few cents. At the same hour, a neighbour can be importing at the retail rate. Each household bill shows only its own side, so the local match between them is invisible.
That gap is the project. A bill says "exported at 8c, imported at 34c"; it does not say "your neighbour needed that electricity at the same time." I wanted to put three homes on one table and make the trade-off inspectable — without pretending a retailer, network or market had approved it.
The codebase began as a two-home NEM planning prototype. For this hackathon I rebuilt the core as a three-home, 24-interval Flex simulator and a new interface around it.
What it does
- Loads a synthetic three-home South Australian neighbourhood: Home A has the larger roof and battery, Home B has a family evening peak, Home C has a smaller roof and its own evening demand.
- Lets you choose which home is yours and state a preference in plain language, or pick a labelled sample.
- Sends the preference to NVIDIA Nemotron 3.5 Lightning through Nebius Token Factory, which returns a strict set of constraints: battery reserve, local-first, evening sharing and an optional no-discharge window.
- Validates every field on the server, then runs the deterministic simulator for 24 one-hour intervals.
- Animates solar, demand, battery state, local allocation, grid import and export; compares independent homes with the coordinated neighbourhood on identical inputs and tariffs.
- Reports local solar used, grid imports, exports, modelled energy cost and self-sufficiency, plus the exact tariffs and a 30-day illustration.
- Checks energy balance, battery balance, reserve, charge/discharge limits and transfer limits at every home and interval, and reports
constraints.okand zero violations. - Optionally records a local hash-chain receipt for the exact plan. It is local only, not a blockchain transaction.
In the committed sample day: 11.0 kWh is allocated locally, grid imports fall from 57.1 to 50.7 kWh, modelled energy cost falls from $16.88 to $15.22 (−$1.66/day), and self-sufficiency rises from 41.2% to 47.8%. These are simulated sample-day results, not a bill or a measured saving.
The AI's role
The model is deliberately narrow. It interprets intent; it does not calculate energy, cost, savings or feasibility. Every kWh, dollar and balance comes from flex_model.py, a deterministic simulator.
The request uses response_format with a JSON schema against nvidia/Nemotron-3_5-Lightning. The server validates every field again in Python: types, ranges and a consistent no-discharge window. Invalid, missing or contradictory output is rejected; it is never turned into a preference object.
A second, optional model call writes a qualitative explanation of an already-calculated result. It is rejected if it introduces a number that is not in the supplied summary, or makes a guarantee or measured-impact claim. If NEBIUS_API_KEY is absent or the API fails, the UI says the AI is unavailable and uses the labelled manual sample. It never fabricates a model result.
That separation is the main design decision: the model reads the household's words; the simulator owns the numbers.
How I built it
flex_model.py— three homes, 24 one-hour intervals. Solar serves own demand first; surplus charges the home battery; remaining demand is served above reserve unless a no-discharge window is active; eligible surplus enters a local pool; the pool allocates at most available supply, eligible demand and the transfer limit; remaining demand imports and remaining surplus exports. The independent case uses the same inputs, batteries, reserve and tariffs with no pool.flex_ai.py— server-only Nebius client, schema-constrained JSON, Python validation, explanation guard, explicit unavailable/error states and secret-safe logging (model, outcome, HTTP status, latency; no key, prompt or personal data).server.py— Python standard-library HTTP server bound to loopback; serves the API and the static UI. The key never reaches the browser.web/— vanilla HTML, CSS and JavaScript. The three-home stage, hour slider, AI panel, comparison, chart, validation table, CSV download and local receipt are all in the existing SunShift visual identity.api/flex/— the same routes as Vercel Python functions, with rewrites invercel.json.tests/— 49 tests covering energy and battery balance at every home and interval, reserve/no-discharge/charge/discharge/transfer limits, baseline versus coordinated calculations, tariff-based costs and the 30-day extrapolation, malformed and contradictory model output, missing key and API failure, key leakage, and the hosted routes.data/flex_scenario.jsonandflex_homes_sample.csv— the synthetic three-home scenario, labelled in the UI and README as simulated, not measured billing data.
Challenges
Keeping the AI honest. Language models are good at intent and unreliable at arithmetic. The strict schema and server-side re-validation were necessary, but the harder part was refusing to let the explanation add numbers or guarantees. The guard rejects those outputs instead of trying to repair them.
Making the comparison fair. The independent and coordinated cases must use identical solar, demand, batteries, reserve and tariffs. Only the sharing rule changes. I check energy and battery balance at every home and interval, not just the daily totals, so a plausible-looking total cannot hide an impossible flow.
Making an allocation legible. A table of kWh values is not a demo. The stage had to show, at a glance, who is sharing, who is receiving, what the battery is doing and what still comes from the grid. The 18:00 moment — Home A sharing 1.9 kW while B and C receive — is the shot that makes the idea land.
Staying honest about the limits. This is a virtual allocation on synthetic profiles, with energy-only tariffs. It is not a retailer agreement, a market bid, a settlement or a measured saving. The UI says so next to the numbers, and the README lists what a real pilot would still need.
The demo. I built a 2:21 explainer from real app captures and a code rebuild of the three-home stage, with narration, captions and a piano score. It gives judges the full workflow — problem, preference, Nemotron call, simulation, comparison and limits — without asking them to install anything.
What I learned
- The most useful agent was a constrained one. A narrow extraction task with a strict schema, a deterministic downstream model and a visible validation step was more trustworthy than asking a model to "optimise" anything.
- Legibility is the product. The interesting question is not a single daily total; it is the hourly mismatch and what changes when the sharing rule changes.
- Validation can be a feature. Showing the checks, the exact tariffs and the 30-day assumption made the demo more credible, not less.
- Honest labels shape the design. "Simulated scenario", "virtual allocation" and "modelled estimate" are not a disclaimer bolted on at the end; they determine what the UI can say.
Limits and what's next
The homes and profiles are synthetic, not measured billing or meter data. Local sharing is a virtual accounting comparison; the grid stays connected and no electricity is routed directly between houses. Tariffs are energy-only and exclude daily supply, network charges, taxes, retailer margins, losses and settlement. The model does not include battery degradation, temperature, inverter limits or standby losses. The monthly figure is the sample day repeated exactly 30 times, not a seasonal forecast.
A real deployment would need consented interval-meter data, a retailer or community-energy agreement, network approval, consumer protections and measured follow-up. The next version would add more homes and tariff structures, and a pilot with real interval data.
Built With
- api
- climate-tech
- css
- energy-modelling
- html
- javascript
- json-schema
- nebius-token-factory
- nvidia-nemotron
- openai-compatible
- python
- serverless-functions
- simulation
- solar
- svg
- vercel
Log in or sign up for Devpost to join the conversation.