Skip to content

feat: Initial backend optimization engine and React dashboard#1

Merged
pisethTang merged 7 commits into
mainfrom
feature/backend-init
Mar 24, 2026
Merged

feat: Initial backend optimization engine and React dashboard#1
pisethTang merged 7 commits into
mainfrom
feature/backend-init

Conversation

@pisethTang
Copy link
Copy Markdown
Owner

Overview

This PR introduces the core Battery Dispatch Optimization engine and its accompanying interactive frontend dashboard. It establishes the full-stack architecture, connecting a mathematically optimized FastAPI backend to a React visualization layer using live Australian grid data.

Key Changes

Backend (FastAPI & Math Engine)

  • Linear Programming: Implemented constraints using PuLP to calculate the maximum-profit battery charge/discharge schedule based on capacity, max power, and efficiency.
  • Live Data Pipeline: Integrated the OpenElectricity Python SDK and pandas to fetch live 5-minute spot prices for the SA1 (South Australia) region and resample them into 30-minute intervals.
  • API Routing: Added the /api/v1/simulate endpoint with Pydantic validation and CORS configured for the frontend.

Frontend (React & Vite)

  • Dashboard UI: Scaffolded a React SPA using Vite, TypeScript, and Tailwind CSS v4.
  • Data Visualization: Built a responsive, dual-axis Recharts graph to simultaneously visualize the volatile electricity spot price (duck curve) and the battery's State of Charge (SoC) decisions.
  • Type Safety: Defined end-to-end TypeScript interfaces matching the backend Pydantic models.

Infrastructure & Quality

  • CI/CD Pipeline: Created a GitHub Actions workflow (backend-tests.yml) utilizing uv to automatically run the test suite on PRs and pushes to main.
  • Testing: Implemented pytest coverage for the core math engine, API routing, and edge-case validation.

Validation

  • Pytest suite passes locally.
  • Frontend successfully parses the FastAPI response and renders the chart without mounting warnings.
  • GitHub Actions workflow triggers correctly on PR creation.

@pisethTang pisethTang self-assigned this Mar 24, 2026
Copilot AI review requested due to automatic review settings March 24, 2026 03:45
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR bootstraps a full-stack “battery dispatch optimization” prototype: a FastAPI backend that optimizes battery charge/discharge schedules (via LP/PuLP) and a React (Vite + TS + Tailwind) dashboard that calls the backend and visualizes the resulting schedule.

Changes:

  • Added FastAPI backend with optimization engine and live OpenElectricity-based simulation endpoint.
  • Added React dashboard scaffolding and chart/KPI UI for schedule visualization.
  • Added CI workflow and initial pytest-based backend tests.

Reviewed changes

Copilot reviewed 27 out of 36 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
main.py FastAPI app, Pydantic models, PuLP optimization, /optimize + /simulate endpoints
pipeline.py Local script to fetch live SA1 prices and call the API
optimizer.py Standalone CSV-based optimization script (PuLP)
generate_data.py Synthetic price CSV generator
market_data.csv Example 30-minute price series CSV
tests/test_main.py Unit/integration/validation tests for the backend
pyproject.toml Project metadata, dependencies, pytest pythonpath config
requirements.txt uv-compiled pinned dependencies (used by Docker build)
Dockerfile Container build for running the FastAPI app
.github/workflows/backend-tests.yml GitHub Actions workflow running pytest via uv
.python-version Pins local Python version (3.12)
.gitignore Ignores venv, caches, .env
.vscode/launch.json VS Code debug configuration for uvicorn
README.md Root documentation links/references
frontend/package.json Frontend dependencies and scripts
frontend/vite.config.ts Vite config with React + Tailwind plugin
frontend/tsconfig.json TS project references
frontend/tsconfig.app.json TS config for app build
frontend/tsconfig.node.json TS config for tooling (vite config)
frontend/eslint.config.js ESLint flat config for TS/React
frontend/index.html App HTML entrypoint
frontend/src/main.tsx React root render
frontend/src/App.tsx Dashboard UI, fetches /api/v1/simulate, renders Recharts chart + KPIs
frontend/src/types.ts Shared request/response TypeScript interfaces
frontend/src/mock_data.ts Mock schedule data for UI experimentation
frontend/src/index.css Global styles + Tailwind import
frontend/src/App.css Template CSS from starter
frontend/public/favicon.svg Favicon asset
frontend/public/icons.svg Icon sprite asset
frontend/src/assets/vite.svg Vite logo asset
frontend/src/assets/react.svg React logo asset
frontend/src/assets/hero.png UI hero image asset
frontend/README.md Default Vite template README
frontend/.gitignore Frontend-specific ignore patterns

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread main.py
prob += soc[t] == (charge[t] * battery.efficiency - discharge[t]) * dt
else:
# Update SoC based on the previous timestep
prob += soc[t] == soc[t-1] + (charge[t] * battery.efficiency - discharge[t]) * dt
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no terminal SoC constraint (e.g., SoC must return to the initial level). If spot prices go negative near the end of the horizon, the optimizer can finish with a high SoC and still report profit (getting paid to charge without ever discharging), which overstates arbitrage value. Consider enforcing a terminal SoC (soc[T-1] == 0) or adding an explicit terminal energy value/penalty.

Suggested change
prob += soc[t] == soc[t-1] + (charge[t] * battery.efficiency - discharge[t]) * dt
prob += soc[t] == soc[t-1] + (charge[t] * battery.efficiency - discharge[t]) * dt
# Enforce terminal SoC to avoid overstating arbitrage value from ending with stored energy
if T > 0:
prob += soc[T - 1] == 0

Copilot uses AI. Check for mistakes.
Comment thread main.py
return calculate_optimal_dispatch(battery, market_data)

except Exception as e:
return {"status": "error", "message": str(e)} No newline at end of file
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /api/v1/simulate endpoint swallows exceptions and returns a JSON error payload with HTTP 200. This makes failures hard to detect on the client and breaks standard API semantics. Prefer raising an HTTPException with an appropriate status code (and keep error response shape consistent with other endpoints).

Suggested change
return {"status": "error", "message": str(e)}
# Propagate errors with an appropriate HTTP status code
raise HTTPException(status_code=500, detail=str(e))

Copilot uses AI. Check for mistakes.
Comment thread tests/test_main.py
Comment on lines +43 to +44
assert result["total_profit_aud"] == 90.0
assert result["schedule"][0]["charge_mw"] == 1.0 # Should charge in the first block
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test assumes the solver will charge specifically in the first interval (schedule[0]). Since the first two intervals have identical prices, there are multiple optimal solutions and the solver may choose to charge in the second block instead, causing flaky failures. Assert on aggregate behavior (e.g., total charged energy across cheap blocks) or allow either of the first two blocks to be charged.

Copilot uses AI. Check for mistakes.
Comment thread generate_data.py
Comment on lines +27 to +32
create_synthetic_prices()
print("Generated market data.csv!")




Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script executes create_synthetic_prices() at import time, which will overwrite market_data.csv whenever the module is imported (including by tooling). Guard the execution with if __name__ == "__main__": so it only runs when invoked as a script.

Suggested change
create_synthetic_prices()
print("Generated market data.csv!")
if __name__ == "__main__":
create_synthetic_prices()
print("Generated market data.csv!")

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +6 to +8
[reference](https://gemini.google.com/app/d42f0dfeac9b254a?is_sa=1&is_sa=1&android-min-version=301356232&ios-min-version=322.0&campaign_id=bkws&utm_source=sem&utm_medium=paid-media&utm_campaign=bkws&pt=9008&mt=8&ct=p-growth-sem-bkws&gclsrc=aw.ds&gad_source=1&gad_campaignid=22437964261&gbraid=0AAAAApk5BhlTX3MkpF7nIacpTUb7etnbN&gclid=Cj0KCQiAnJHMBhDAARIsABr7b84shuWqfx0UvOhc3MwNnDRCJJO5jpV4lSTTbBx42jMRPPgo7ersQ84aAgB9EALw_wcB)


Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README contains a long Gemini reference link with tracking parameters that doesn’t describe how to run/build/test the project. Consider removing it or replacing with a short, relevant project reference section (and keep URLs minimal).

Suggested change
[reference](https://gemini.google.com/app/d42f0dfeac9b254a?is_sa=1&is_sa=1&android-min-version=301356232&ios-min-version=322.0&campaign_id=bkws&utm_source=sem&utm_medium=paid-media&utm_campaign=bkws&pt=9008&mt=8&ct=p-growth-sem-bkws&gclsrc=aw.ds&gad_source=1&gad_campaignid=22437964261&gbraid=0AAAAApk5BhlTX3MkpF7nIacpTUb7etnbN&gclid=Cj0KCQiAnJHMBhDAARIsABr7b84shuWqfx0UvOhc3MwNnDRCJJO5jpV4lSTTbBx42jMRPPgo7ersQ84aAgB9EALw_wcB)
## Project reference
To run the FastAPI application locally:
1. Create and activate a Python virtual environment.
2. Install dependencies: `pip install -r requirements.txt`.
3. Start the server: `uvicorn main:app --reload`.

Copilot uses AI. Check for mistakes.
Comment thread pipeline.py
Comment on lines +143 to +146
if __name__ == "__main__":
run_pipeline()

# https://gemini.google.com/app/d42f0dfeac9b254a?is_sa=1&is_sa=1&android-min-version=301356232&ios-min-version=322.0&campaign_id=bkws&utm_source=sem&utm_medium=paid-media&utm_campaign=bkws&pt=9008&mt=8&ct=p-growth-sem-bkws&gclsrc=aw.ds&gad_source=1&gad_campaignid=22437964261&gbraid=0AAAAApk5BhlTX3MkpF7nIacpTUb7etnbN&gclid=Cj0KCQiAnJHMBhDAARIsABr7b84shuWqfx0UvOhc3MwNnDRCJJO5jpV4lSTTbBx42jMRPPgo7ersQ84aAgB9EALw_wcB No newline at end of file
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file includes a very long Gemini URL comment at the end. It’s unrelated to runtime behavior, adds noise to the repo, and contains lots of tracking/query params. Consider removing it (or moving references into documentation with a clean URL).

Copilot uses AI. Check for mistakes.
Comment thread tests/test_main.py
Comment on lines +1 to +4
import pytest
from fastapi.testclient import TestClient
from datetime import datetime, timezone
from main import app, calculate_optimal_dispatch, BatterySpecs, MarketInterval
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pytest is imported but never used in this test module.

Copilot uses AI. Check for mistakes.
Comment thread .vscode/launch.json
"request": "launch",
"module": "uvicorn",
"args": [
"main:app", // Replace 'main' with your file name and 'app' with your FastAPI instance name
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This launch configuration file contains // comments, which makes it invalid strict JSON (it’s JSONC). Some tools that parse .vscode/launch.json as JSON may fail. Consider removing comments or renaming/formatting according to JSONC expectations if the repo relies on strict JSON tooling.

Suggested change
"main:app", // Replace 'main' with your file name and 'app' with your FastAPI instance name
"main:app",

Copilot uses AI. Check for mistakes.
Comment thread Dockerfile
@@ -0,0 +1,18 @@
# 1. Use an official, lightweight Python image
FROM python:3.11-slim
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Docker image uses Python 3.11, but the project declares requires-python = ">=3.12" and CI installs 3.12. This mismatch can cause runtime incompatibilities and makes the Docker build diverge from tested environments. Update the base image to Python 3.12 (or relax the project requirement if 3.11 is intended).

Suggested change
FROM python:3.11-slim
FROM python:3.12-slim

Copilot uses AI. Check for mistakes.
Comment thread frontend/src/App.tsx
Comment on lines +45 to +56
const base_url = "http://localhost:8000/api/v1";
try {
const payload = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"capacity_mwh": capacity,
"max_mw": maxMw,
efficiency: 0.9 // hardcorded for efficiency for now
})
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The frontend hard-codes the backend base URL and efficiency. This makes deployments and local dev (non-default ports/hosts) harder and couples UI behavior to a single environment. Consider moving these to environment variables (e.g., Vite import.meta.env) and making efficiency user-configurable or clearly fixed in UI.

Copilot uses AI. Check for mistakes.
@pisethTang pisethTang merged commit 84f7d8d into main Mar 24, 2026
2 checks passed
@pisethTang
Copy link
Copy Markdown
Owner Author

Good job (:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants