BESS-SCADA-Data / CLAUDE.md
pourmousavi's picture
Add CLAUDE.md with project guide and 12-rule template
25b1e06
|
Raw
History Blame Contribute Delete
7.75 kB
# 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
1. Push to `master` β†’ `sync_to_hf.yml` runs.
2. The workflow rewrites local history (in the runner only) to strip
`app/data/aemo_gen_info.xlsx` from 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.
3. Force-pushes the filtered history to the HF Space's `main` branch.
4. 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.py` docstring.
- 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_DATE`
and `DISPATCH_START_DATE` must 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-refreshed
`bess_list.json` snapshot. 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.py`
filters 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/bess` returns `{states, source, fetched_at, warnings}` β€”
callers must read `.states` for the unit list. **Breaking this shape
breaks the frontend dropdown** (and any external scripts). If you must
change it, update `app.js:init()` in lockstep.
- `/api/data` and `/api/energy-data` return `{duid, date, total_rows, summary, data, warnings?}`.
- All endpoints raise `HTTPException(404)` for "no data for this DUID/date"
and `HTTPException(503)` for upstream AEMO failures. Don't change these
status codes β€” the frontend distinguishes them.
## Running locally
```bash
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.json` once 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
against `scripts/refresh_bess_list.py` output.
- Browser cache served old `app.js` with new `/api/bess` shape β†’
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.