Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
스페인 전력망 데이터를 기반으로 전기차 충전을 일정 관리, 탄소 절감 효과와 절감이 없는 밤 수를 보여줍니다
Scheduling EV charging against measured Spanish grid data. What carbon-aware charging actually saves, and on how many nights it saves nothing.
라이프스타일Python · TypeScriptGitHub @adityaayushman온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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: