尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Carbon-aware EV charging optimizer — linear-programming scheduling against real ENTSO-E grid carbon & price data; lets one street transformer host 5.3× more EVs. FastAPI · React · TypeScript
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Carbon-aware EV charging optimizer: schedules home charging against real grid carbon and price data, and lets one street transformer host about 4× more EVs (median of 20 fleet draws).
In one line: GridSense schedules home EV charging against real grid data and
measures what that is worth — little for carbon, and a lot for how many EVs one
street transformer can carry (about 4× on the median draw; an estimate that rests on
assumed inputs, stated as such below). Nine of its own published claims were wrong
at some point; LESSONS.md records how each was caught.查看完整 README ↗Carbon-aware EV charging optimizer: schedules home charging against real grid carbon and price data, and lets one street transformer host about 4× more EVs (median of 20 fleet draws).
In one line: GridSense schedules home EV charging against real grid data and measures what that is worth — little for carbon, and a lot for how many EVs one street transformer can carry (about 4× on the median draw; an estimate that rests on assumed inputs, stated as such below). Nine of its own published claims were wrong at some point; LESSONS.md records how each was caught.
Live: https://gridsense-app-adityaasahoo-gmailcoms-projects.vercel.app · Simulator · Existing vs GridSense · Grid headroom · API health · days
Both tiers run from one Vercel project, so the dashboard and the API share an
origin. The split gridsense-es / gridsense-api-es pair this used to name has
been superseded.
Carbon-aware EV charging optimization — a full-stack system that schedules residential EV charging against measured grid carbon-intensity and price data instead of naive arrival-time charging, and quantifies what that actually saves.
Problem: most EVs charge the moment their owner gets home (5–9pm), overlapping the grid's existing demand peak — the window when utilities lean on carbon-intensive peaker plants. GridSense reschedules that load using a linear-programming optimizer, and shows the before/after impact on a live dashboard.
Hosting capacity. The finding with money attached: on a 250 kW street transformer serving 200 homes, charging on arrival supports about 75 EVs before the median night exceeds the rating. Coordinating the street against the transformer supports about 308 — a median 4.1× gain over twenty draws of the fleet (range 3.3–5.3×) — because the binding constraint moves from instantaneous power to energy: charging on arrival uses one or two hours of a thirteen-hour window and leaves the rest empty. That is a deferred capital upgrade, not a rounding error. See the Grid headroom pane.
The gain depends on how tight the transformer is — 5.8× at 160 kW, 3.7× at 630 kW — so the rating belongs with the figure. It also rests on assumed inputs (baseline shape, fleet mix, homes per transformer). A one-at-a-time sweep moved it no lower than the noise band and raised it for a peakier street, but that is sensitivity to assumptions. It has since been checked against real streets built from public smart-meter data: Spain's real streets give about the same gain (4.9-5.0×), the UK's about twice as much, and India has only a published-figure check because no open household data exists. See the Validation pane and FINDINGS.md findings 1 and 21.
(This has been corrected three times. An earlier version claimed 14×, on a fleet where every car arrived at exactly 18:00; staggered arrivals triple what dumb charging can host. It then read 6.3×, measured against a residential baseline that overstated household demand sevenfold, then 5.3×, which turned out to be the most favourable of twenty fleet draws. All are documented in FINDINGS.md and LESSONS.md.)
What the data says. Backtested over every complete overnight window in
ENTSO-E's Spanish record (2015-2018, corrected: the widely used Kaggle copy is wrong on
the first twelve days of every month, see finding 17), the median night saves 2.0% of
charging emissions and the fleet-wide total is 6.6% — not the flat 30%+ a smooth
synthetic curve suggests. The saving is long-tailed: 41% of nights save under
1%, while 9% save over 20%. The objectives genuinely conflict, too — the
cheapest hour and the cleanest hour coincide on only 18% of nights, so
minimizing cost can raise emissions. The dashboard shows those as increases
rather than hiding them. See ml/README.md for the analysis.
backend/ FastAPI + PuLP optimization engine (Python)
frontend/ React + TypeScript dashboard (Vite)
Optimizer (backend/app/optimizer.py) — LP model minimizing carbon,
cost, or peak load, subject to each vehicle's energy requirement, charger
power limit, arrival/deadline window, and an optional shared feeder limit.
What the LP is actually for. Without a constraint linking vehicles the problem is separable: each vehicle independently fills its own cleanest hours, and a greedy loop reproduces the LP's objective to within 1e-6. Vehicle heterogeneity does not change this — differing arrivals, energies and charger ratings still separate. The optimizer earns its keep only under a coupling constraint, of which this model has two: the
peakobjective, and the feeder-capacity limit. Given a 180 kW feeder, greedy peaks at 485 kW and simply breaks it; the LP lands on 180 kW exactly.test_without_a_coupling_constraint_the_lp_only_matches_greedypins this down so the claim cannot silently rot.
API (backend/app/main.py) — POST /api/scenario runs a full
naive-vs-optimized comparison for one measured grid night and returns hourly
series + summary stats; GET /api/days lists the nights available to
simulate. Interactive docs at /docs once running.
Data (backend/app/data_sources.py) — serves measured Spanish grid days
from backend/app/data/grid_days.json: carbon intensity derived from the
ENTSO-E generation mix weighted by IPCC AR5 lifecycle emission factors, and
day-ahead market price plus Spain's flat PVPC access component. Each stored
day is an 18:00→07:00 charging night, so indices 0-7 are the following
morning. Only the residential baseline load remains synthetic — ENTSO-E
reports system-wide load, which cannot be scaled to one feeder without an
assumption.
Spain rather than California because it is one of the few countries where ordinary households are billed at genuinely hourly-varying prices (PVPC), which is the premise the cost objective depends on.
The derived series is average carbon intensity, not marginal. Load shifting responds to the marginal rate; closing that gap needs MOER data this dataset does not carry.
ml/src/carbon.pyestimates a system marginal rate but the served series is the average.
cd backend
python -m venv venv && source venv/bin/activate
pip install -r requirements-dev.txt # requirements.txt is runtime-only
cp .env.example .env
uvicorn app.main:app --reload
API now running at http://localhost:8000, docs at http://localhost:8000/docs
cd backend
pytest tests/ -v
cd frontend
npm install
npm run dev
Dashboard now running at http://localhost:5173
Both tiers ship from one Vercel project, built from this repo on every push
to main. The root vercel.json routes /api/* to the Python function and
everything else to the static Vite output, so the dashboard and the API share an
origin.
That removes two moving parts: requests are same-origin so CORS is not load-bearing in production, and the frontend no longer needs the API hostname baked in at build time.
.vercel.apphostnames are globally unique, not per-account.gridsense,gridsense-appandgridsense-apiare all taken by unrelated projects, so confirm the alias a deploy actually returns rather than assuming it from the project name.
backend/vercel.json uses the legacy builds/routes form rather than
rewrites. This is deliberate: rewrites rewrites the request path, so the
ASGI app receives /api/index and every route 404s. routes with a dest
preserves the original path.
backend/api/index.py is the entrypoint — it prepends the deployment root to
sys.path so app.main resolves, then re-exports the FastAPI app.
PuLP's bundled CBC solver (7.2 MB, Linux x86-64) runs fine in the serverless sandbox; a full 80-vehicle scenario solves in well under a second.
Point a Vercel project at backend/ as its root directory and deploy.
Root directory frontend/. Set VITE_API_BASE to the API URL — either as a
Vercel env var (Production + Preview + Development) or via .env.production.
app/main.py accepts an exact allowlist via ALLOWED_ORIGINS and a pattern
via ALLOWED_ORIGIN_REGEX, which defaults to:
^https://gridsense[a-z0-9-]*\.vercel\.app$
The pattern exists because Vercel mints a fresh hostname for every preview
deployment, which an exact list can't cover. Both anchors matter — without $,
https://gridsense.evil.com would be admitted.
The API also runs as a Docker container, which is the better fit if you want a warm process rather than a serverless function — the LP solver benefits from not re-initialising per request.
Deploy: Render dashboard -> New -> Blueprint -> pick this repo. render.yaml
at the repo root describes the whole service, so there is nothing to fill in.
It comes up at https://gridsense-api-es.onrender.com:
backend/, which is required — the Dockerfile COPYs
requirements.txt and app/ as top-level paths.app/data/grid_days.json lives inside app/, so the measured grid data is
baked into the image. No volume, no fetch-on-boot, no external dependency at
runtime.$PORT. The CMD is in shell form
deliberately: the exec form does no variable expansion, so ${PORT} would be
passed to uvicorn as a literal string and the service would fail its health
check./api/health.onrender.com hostnames are globally unique,
not per-account: gridsense-api is already an unrelated FastAPI service, and
claiming a taken name silently gets you a suffixed hostname instead. Check
first with curl -sI https://<name>.onrender.com | grep x-render-routing —
no-server means it is free.ALLOWED_ORIGIN_REGEX default
already admits this project's *.vercel.app frontends, including the fresh
hostname Vercel mints for every preview deploy. Set ALLOWED_ORIGINS only for
an origin outside that pattern, such as a custom domain.Then point the frontend at it by setting VITE_API_BASE to the Render URL and
rebuilding (see the gotcha below — a restart is not enough).
Note the free instance sleeps after ~15 minutes idle and takes ~50s to wake. The
dashboard's warmUp() ping covers some of that, but not a fully cold start.
PuLP shells out to CBC, which writes a problem file and a solution file per solve. If the system temp drive is full those writes fail and every scheduling request returns a 500 — a disk fault that surfaces as an application error.
backend/app/tmpdir.py runs at optimizer import and points Python and the
CBC subprocess at a drive with room, preferring a configured location over the
platform default. Override with GRIDSENSE_TMPDIR. On a host with none of the
candidates — Vercel, CI — it keeps the platform default, so this is inert there.
frontend/.npmrc moves npm's cache off the system drive for the same reason:
with C: full, npm run build fails with ENOSPC before Vite is reached, because
npm cannot write its own cache.
VITE_API_BASE requires a Vercel rebuild, not just a restart —
Vite bakes the value into the bundle at build time.<project>.vercel.app is globally unique, not per-account. gridsense,
gridsense-app and gridsense-api are each taken by unrelated projects,
which is why the live URL carries the account-scoped suffix rather than a
short name. Always confirm the alias a deploy actually returns rather than
assuming it from the project name./api/health ping on mount
(warmUp() in frontend/src/api.ts) so the function is warm by the time the
visitor hits Run. On Vercel that saves ~1-2s; on a Render free-tier container,
which spins down after ~15 minutes idle, it saves ~50s.requirements.txt is runtime-only — it is what both the container image
and the Vercel function install, so anything added there ships to production.
Tests install requirements-dev.txt, which layers pytest on top.
python-dotenv, sqlalchemy and psycopg2-binary were dropped: nothing
imports them, and they cost build time on every deploy.ml/); the demo currently serves measured curves rather than forecastsIn use (India, merged by ml/build_india_dataset.py into one 74,784-hour table with 184 columns):
ml/fetch_india_psp.py)In use (Spain):
Not yet wired in: