Spaces:
Sleeping
CLAUDE.md β BESS-SCADA-Data project guide
A FastAPI app that streams BESS SCADA (4-second power) and dispatch (5-minute energy) data live from AEMO NEMWEB. Deployed as a Docker Space on HuggingFace. Nothing is stored server-side; every request fetches fresh from AEMO.
Where things live
app/
main.py FastAPI app, static mounting, cache-busted index
config.py AEMO URLs, start dates, timeouts, column lists
routers/api.py All HTTP endpoints
services/
aemo_fetcher.py FPPDAILY ZIP download (Current + Archive)
dispatch_fetcher.py Next_Day_Dispatch ZIP download
data_processor.py Polars: parse + filter SCADA CSV
dispatch_processor.py Polars: parse + filter dispatch CSV
gen_info_fetcher.py AEMO XLSX β BESS list (live β bundled snapshot)
analytics.py SQLite request logging (/tmp/analytics.db)
data/
bess_list.json Snapshot, refreshed daily by CI workflow
bess_list_meta.json Snapshot timestamp + provenance
quality_flags.json MW_QUALITY_FLAG descriptions
static/ index.html, app.js, style.css (UI)
scripts/refresh_bess_list.py Standalone refresher invoked by CI
.github/workflows/
sync_to_hf.yml Master push β HF Space deploy (filters binaries)
refresh_bess_list.yml Daily cron β regenerates bess_list.json on master
Deploy flow
- Push to
masterβsync_to_hf.ymlruns. - The workflow rewrites local history (in the runner only) to strip
app/data/aemo_gen_info.xlsxfrom every commit. HuggingFace rejects any pushed commit that contains a non-Xet binary file, including historical commits β this filter step exists because of that policy. - Force-pushes the filtered history to the HF Space's
mainbranch. - HF rebuilds the Docker image and swaps the container (~30β60 s).
Origin master is never rewritten by the deploy. Only HF's main is.
Cache-busting
app/main.py reads app/static/index.html once at startup, substitutes
__VERSION__ with int(time.time()), and serves the result from memory.
Every container restart = new version stamp = browsers refetch JS/CSS.
Keep the __VERSION__ placeholders on the app.js and style.css
<script> / <link> tags in index.html β removing them silently
reintroduces the cache-staleness bug.
AEMO data quirks
- Three FPPMW format eras β see
aemo_fetcher.pydocstring.- Era 1 (Dec 2024 β 28 Apr 2025): single daily file, settlement date in filename.
- Era 2 (29 Apr β 10 Sep 2025): single 12-hour file (04:00β16:00 only), publication date in filename.
- Era 3 (11 Sep 2025+): two files per day (FPPMW first half, FPPMW_2 second half).
Era logic lives in
_era()and_inner_zip_date_str().
- Rolling archive β AEMO removes old monthly bundles.
DATA_START_DATEandDISPATCH_START_DATEmust be updated when bundles roll off. As of 2026-05: SCADA from 29 Apr 2025, dispatch from 1 Apr 2025. Verify against the actual NEMWEB Archive directory listings, not memory. - AEMO WAF blocks datacentre IPs on
www.aemo.com.au(returns 403 to the HF egress range). The Generation Information XLSX fetch is wrapped in browser-style headers AND backed by the daily-refreshedbess_list.jsonsnapshot. The snapshot's CI runner is on GitHub-hosted infrastructure, which AEMO doesn't block. - NEM market day boundary is 04:00 AEST, not midnight.
data_processor.pyfilters strictly to[04:00 AEST D, 04:00 AEST D+1). Era 1 callers must request both D and D+1 calendar files to cover one market day.
Critical API contracts
/api/bessreturns{states, source, fetched_at, warnings}β callers must read.statesfor the unit list. Breaking this shape breaks the frontend dropdown (and any external scripts). If you must change it, updateapp.js:init()in lockstep./api/dataand/api/energy-datareturn{duid, date, total_rows, summary, data, warnings?}.- All endpoints raise
HTTPException(404)for "no data for this DUID/date" andHTTPException(503)for upstream AEMO failures. Don't change these status codes β the frontend distinguishes them.
Running locally
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
# then open http://localhost:8000
Things that have bitten us before
bess_list.jsononce contained fictional DUIDs (e.g.NESBESS1,HORNSDALE_PWR1) that don't exist in real SCADA data. The daily workflow guarantees this can't happen now, but verify any manual edit againstscripts/refresh_bess_list.pyoutput.- Browser cache served old
app.jswith new/api/bessshape β nonsense state-dropdown options. The__VERSION__cache-bust prevents recurrence; do not remove it. - HuggingFace rejects pushed commits with binary files anywhere in history, not just current tree. Don't reintroduce binary commits even if you plan to delete them in the next commit.
CLAUDE.md skill to append to any coding project
CLAUDE.md β 12-rule template
These rules apply to every task in this project unless explicitly overridden. Bias: caution over speed on non-trivial work. Use judgment on trivial tasks.
Rule 1 β Think Before Coding
State assumptions explicitly. If uncertain, ask rather than guess. Present multiple interpretations when ambiguity exists. Push back when a simpler approach exists. Stop when confused. Name what's unclear.
Rule 2 β Simplicity First
Minimum code that solves the problem. Nothing speculative. No features beyond what was asked. No abstractions for single-use code. Test: would a senior engineer say this is overcomplicated? If yes, simplify.
Rule 3 β Surgical Changes
Touch only what you must. Clean up only your own mess. Don't "improve" adjacent code, comments, or formatting. Don't refactor what isn't broken. Match existing style.
Rule 4 β Goal-Driven Execution
Define success criteria. Loop until verified. Don't follow steps. Define success and iterate. Strong success criteria let you loop independently.
Rule 5 β Use the model only for judgment calls
Use me for: classification, drafting, summarization, extraction. Do NOT use me for: routing, retries, deterministic transforms. If code can answer, code answers.
Rule 6 β Token budgets are not advisory
Per-task: 4,000 tokens. Per-session: 30,000 tokens. If approaching budget, summarize and start fresh. Surface the breach. Do not silently overrun.
Rule 7 β Surface conflicts, don't average them
If two patterns contradict, pick one (more recent / more tested). Explain why. Flag the other for cleanup. Don't blend conflicting patterns.
Rule 8 β Read before you write
Before adding code, read exports, immediate callers, shared utilities. "Looks orthogonal" is dangerous. If unsure why code is structured a way, ask.
Rule 9 β Tests verify intent, not just behavior
Tests must encode WHY behavior matters, not just WHAT it does. A test that can't fail when business logic changes is wrong.
Rule 10 β Checkpoint after every significant step
Summarize what was done, what's verified, what's left. Don't continue from a state you can't describe back. If you lose track, stop and restate.
Rule 11 β Match the codebase's conventions, even if you disagree
Conformance > taste inside the codebase. If you genuinely think a convention is harmful, surface it. Don't fork silently.
Rule 12 β Fail loud
"Completed" is wrong if anything was skipped silently. "Tests pass" is wrong if any were skipped. Default to surfacing uncertainty, not hiding it.