- JavaScript 99.9%
- Add degradationPct (2%/yr) to all batteries and quote battery defaults - Add calculateAdjustedPaybackYears to payback.js: year-by-year loop compounds escalation (savings grow with rates) and degradation (battery capacity fades) to find the real break-even year - Restructure projectionScenarioList to carry batterySavings and solarSavings per scenario so each can be degraded at its own rate - buildProjectionData degrades battery/solar savings independently while leaving rate-plan-switch savings untouched - calculationEngine uses adjusted payback for all paybackYears, fullSystemPaybackYears, solarPaybackYears, paybackVsAnytimeYears, and quote comparison payback; pulls rateIncreasePct and battery.degradationPct from state/modeledSystem - calculationRuntime passes degradation rates to buildProjectionData for server-side projection snapshots - ProjectionsTab passes degradation rates to chart, fixes annual cost table to show rising costs as battery effectiveness decays, and updates the disclaimer footnotes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| backend | ||
| docs | ||
| Specs | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| docker-compose.full.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| index.html | ||
| nginx.conf | ||
| package.json | ||
| postcss.config.js | ||
| README.md | ||
| tailwind.config.js | ||
| vite.config.js | ||
Ameren MO Rate Plan & Battery Storage Calculator
Interactive web app for comparing Ameren Missouri residential rate plans, modeling battery/solar ROI, evaluating backup runtime, and reconciling modeled bills against actual Ameren bills.
Run Frontend Only
docker compose up --build
Then open http://localhost:3000.
Run Full Stack
cp .env.example .env
docker compose -f docker-compose.full.yml up --build
The full stack starts:
- frontend on port
3000 - backend API on port
4000 - PostgreSQL with a persistent Docker volume
Homelab / Caddy
The app works behind a normal subdomain reverse proxy. Point Caddy at the frontend container if you only run the frontend, or at the backend/frontend ports separately if you split them.
One simple pattern is:
battery.example.com {
reverse_proxy 10.1.1.14:3000
}
If you expose backend APIs through the same hostname, proxy /api/* and /auth/* to the backend and everything else to the frontend:
battery.example.com {
handle /api/* {
reverse_proxy 10.1.1.14:4000
}
handle /auth/* {
reverse_proxy 10.1.1.14:4000
}
handle {
reverse_proxy 10.1.1.14:3000
}
}
Authentik / OIDC
Authentication is configured with environment variables. No Authentik groups are required; home roles are managed inside the app.
Required for OIDC mode:
AUTH_MODE=oidcAUTH_REQUIRED=truePUBLIC_APP_URL=https://battery.example.comSESSION_SECRET=<long random value>OIDC_ISSUER_URL=<Authentik issuer URL>OIDC_CLIENT_ID=<client id>OIDC_CLIENT_SECRET=<client secret>OIDC_REDIRECT_URI=https://battery.example.com/auth/callback
When using docker-compose.full.yml, the frontend container proxies /api/* and /auth/* to the backend container. Use FRONTEND_PORT for the public frontend port and BACKEND_HOST_PORT if you need to expose the backend directly. The backend always listens on port 4000 inside the Compose network.
The backend validates OIDC issuer, audience, expiration, signature, and login nonce. API routes for home-scoped data require home membership once auth is enabled.
Data Sources
- Emporia usage: CSV import is the active path. Backend sync jobs and integration settings exist for a future official or user-provided adapter.
- Weather: CSV import or Open-Meteo historical daily temperature sync using the home latitude/longitude.
- Ameren billing: manual entry or CSV import. Ameren Missouri customer billing API support is not assumed.
Configuration And Backups
- Browser state persists in localStorage.
- JSON export/import remains available in the My Data tab.
- Backend snapshot migration is available in the My Data tab when the API is reachable.
- PostgreSQL backup example:
docker compose -f docker-compose.full.yml exec postgres pg_dump -U battery battery_storage > battery_storage.sql
Restore example:
docker compose -f docker-compose.full.yml exec -T postgres psql -U battery battery_storage < battery_storage.sql
Features
- Overview: rate comparison, dispatch impact, and rate reference.
- My Data: Emporia CSV import, circuit backup status, outage priority, whole-home exclusions, persistence tools.
- Batteries: configurable battery library and cost metrics.
- Backup: reserve settings, outage scenarios, load shedding, SOC/load charts.
- Weather: home location, geocoding, weather import/sync, temperature correlation.
- Solar: panel library, installer costs, export/no-export settings, lifetime production.
- Billing: manual/CSV bill import and modeled-vs-actual reconciliation.
- Quotes: installer quote scenarios with included hardware and revision history.
- Projections: 15-year savings and payback projections.
- Admin: auth status, homes, memberships, integration status, and backend diagnostics.
Database Migrations
Migrations run automatically on backend startup. To apply a new migration manually:
docker compose -f docker-compose.full.yml exec backend node src/migrate.js
Current migrations:
| File | Description |
|---|---|
001_initial.sql |
Initial schema: users, homes, memberships, battery/inverter/solar configurations, circuit assignments, usage, weather, billing, jobs, calculation results |
002_billing_auth_ops.sql |
Billing periods, auth operations |
003_circuit_whole_home_exclusion.sql |
Adds exclude_from_whole_home column to circuit_assignments |
Development
Frontend tests:
npm test
Backend tests:
npm --prefix backend test
Production build:
npm run build