App for customers and microgrid managers to monitor electricity usage.
Install dependencies and start the Vite dev server:
npm install
npm run devFor auth-enabled local development, add a .env file with:
VITE_SUPABASE_URL=...
VITE_SUPABASE_ANON_KEY=...
SUPABASE_URL=...
SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_ROLE_KEY=...SUPABASE_URL and SUPABASE_ANON_KEY are used by the Vercel Function backend for server-side token verification. For now, they should match the same Supabase project as the frontend values.
SUPABASE_SERVICE_ROLE_KEY is required for server-side account/profile/service access and for the onboarding and import scripts.
Step 2 adds a minimal protected identity endpoint at /api/me. The frontend now waits for backend verification before showing the signed-in dashboard.
Step 3 adds server-side customer/account/service lookup on top of that verified identity flow. A signed-in user must be present in the Supabase customer data tables before the dashboard is shown.
Useful commands:
npm test
npm run build
npm run release:check
npm run db:migrate:customers
npm run db:onboard:customer -- --email person@example.com --profile-name "Person Example" --account-number acct-1001 --account-name "Person Example Account" --services '[{"serviceType":"electric","serviceName":"Main Electric Service","serviceAddress":"123 Main St"}]'
npm run db:import:usagenpm run release:check is the required local gate before merging work to main. It runs the full test suite and a production build.
For custom Supabase email delivery setup, use docs/supabase-smtp-runbook.md.
Apply the schema in supabase/schema.sql in Supabase before running the onboarding or migration scripts. Apply incremental updates from supabase/migrations/20260415_add_customer_rls_policies.sql when updating an existing project.
The app now expects:
customer_profilesutility_accountsutility_servicesmeter_sourcesusage_import_files
The /api/me response now returns:
emailprofileaccountservices
- Row Level Security is enabled on all app tables in
public. - Authenticated customer reads are limited to the signed-in customer's own profile, account, services, linked microgrid topology, and usage snapshots.
- Operational tables such as
meter_sourcesandusage_import_filesremain server-only and are intended to be accessed through the service role. - The server-side Supabase helper requires
SUPABASE_SERVICE_ROLE_KEY; it no longer falls back to the anon key for privileged data access.
- Apply supabase/schema.sql in Supabase SQL Editor.
- Ensure
SUPABASE_URLandSUPABASE_SERVICE_ROLE_KEYare set locally. - Run:
npm run db:migrate:customersThis imports the current records from legacy-customer-map.js and creates:
- one customer profile per email
- one account per profile
- one default electric service per account
The migration is idempotent and safe to rerun.
- Invite the user or let them sign in through Supabase Auth.
- Run the onboarding command with their email and account/service data.
- After the profile/account/services exist, the user can access the dashboard.
Example:
npm run db:onboard:customer -- \
--email jane@example.com \
--profile-name "Jane Example" \
--account-number acct-2001 \
--account-name "Jane Example Main Account" \
--services '[{"serviceType":"electric","serviceName":"Main Electric Service","serviceAddress":"123 Main St","meterSource":{"meterId":"meter-001","sourceType":"nextcloud_csv","meterName":"Main Three Phase","timezone":"America/Chicago"}},{"serviceType":"water","serviceName":"Main Water Service","serviceAddress":"123 Main St"}]'Add --append-services if you want to keep existing services for that account and add new ones instead of replacing them.
Real usage data is imported from a Nextcloud-synced directory on the laptop running the importer. The importer reads CSV files from a bind-mounted read-only directory, computes daily kWh deltas from cumulative energy_total, and upserts those totals into usage_daily_snapshots.
Required environment variables for import runs:
SUPABASE_URL=...
SUPABASE_SERVICE_ROLE_KEY=...
USAGE_IMPORT_DIR=/data/import
USAGE_IMPORT_REPROCESS_DAYS=3
USAGE_IMPORT_FORCE_FULL_SYNC=falseFor local non-Docker runs, point USAGE_IMPORT_DIR at the synced host directory directly. For Docker runs, mount the host directory read-only into /data/import.
Manual Docker run:
export USAGE_IMPORT_HOST_DIR="/absolute/path/to/Nextcloud/meters"
export SUPABASE_URL=...
export SUPABASE_SERVICE_ROLE_KEY=...
./scripts/run-usage-import-docker.shThe importer image can also be run directly:
docker build -f Dockerfile.importer -t nfe-usage-importer .
docker run --rm \
-e SUPABASE_URL \
-e SUPABASE_SERVICE_ROLE_KEY \
-e USAGE_IMPORT_DIR=/data/import \
-e USAGE_IMPORT_REPROCESS_DAYS=3 \
-v "/absolute/path/to/Nextcloud/meters:/data/import:ro" \
nfe-usage-importerOperational notes:
- original CSV files remain in Nextcloud as the recovery and recomputation source
- Supabase stores only daily totals in v1
- daily usage is computed from row timestamps, not the filename date
- the seeded demo fallback is now opt-in with
ENABLE_USAGE_DEMO_FALLBACK=true
Meters with meter_sources.source_type = 'openems' are synchronized through server-side Vercel Functions. OpenEMS remains the reading source of truth; Supabase stores portal-ready daily kWh snapshots.
The integration provides:
POST /api/internal/openems-syncfor protected scheduled synchronizationPOST /api/sync-usagefor customer-authorized, per-service synchronizationnpm run openems:discoverfor Edge/channel discovery and mappingnpm run openems:backfillfor bounded historical imports
Configuration, rollout, and Supabase Cron instructions are in docs/openems-integration-runbook.md.
This repository is set up for a trunk-based Vercel workflow:
mainis the only long-lived branch and the only deploy source.- Vercel should treat this as a static Vite frontend.
- A merge to
mainshould create a staged production deployment. - That staged deployment is validated at its Vercel deployment URL first.
- Production is updated only by manually promoting the already-tested staged deployment.
- This workflow is designed to work on the Vercel Hobby plan.
The repository includes vercel.json to pin the expected Vercel behavior:
- framework:
vite - build command:
npm run build - output directory:
dist
Also note:
.vercel/is gitignored because it is local machine/project linkage metadata.- The app now uses a minimal Vercel Function at
/api/mefor server-side identity verification. - Customer identity is now mapped server-side from Supabase Postgres customer/account/service tables.
Configure one Vercel project for this repo with:
- Production branch:
main - Production domain: your live customer-facing domain
- No dedicated preprod domain on Hobby; use the staged
.vercel.appdeployment URL as the temporary verification environment
On the Hobby plan, keep the workflow simple:
- merge to
main - let Vercel build the deployment
- validate the staged deployment at its generated
.vercel.appURL - manually promote the exact same deployment to production
Reference: Vercel promoting deployments
- Create a single Vercel project from this GitHub repository.
- Confirm the framework is
Vite. - Confirm the build command is
npm run build. - Confirm the output directory is
dist. - Set the Production Branch to
main. - Turn off
Auto-assign Custom Production DomainsinSettings -> Environments -> Production. - Add only the live production custom domain.
- Use the deployment URL generated by Vercel as the temporary verification environment for each release candidate.
For auth + backend verification, configure these environment variables in both Preview and Production:
VITE_SUPABASE_URL=...
VITE_SUPABASE_ANON_KEY=...
SUPABASE_URL=...
SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_ROLE_KEY=...- Pull the latest
main. - Do the work locally.
- Run
npm run release:check. - Merge to
mainonly if the local checks pass.
- Keep merges to
mainsmall and self-contained. - Avoid batching unrelated changes into a single merge.
- Treat every merge to
mainas a release candidate.
After the merge reaches main:
- Wait for Vercel to finish the staged production deployment.
- Open the staged
.vercel.appdeployment URL in Vercel and verify:- the site loads without console-breaking issues
- the main navigation or primary user path works
- the deployed build matches the intended change
- there are no obvious visual regressions on desktop and mobile-width layouts
- Confirm the live production domain still shows the previous production release.
- If the staged deployment fails verification, do not promote. Fix locally, rerun
npm run release:check, merge again, and validate the new staged deployment.
- Production promotion is always manual.
- Promote only the exact staged deployment that passed verification.
- Do not trigger a fresh rebuild for production.
If a bad release reaches production, roll back by promoting the previous known-good Vercel deployment rather than patching production directly.