尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
See how Ontario schools use energy. WattMap turns Ontario's public school-board energy reports into searchable, peer-benchmarked, fully explained school energy profiles.
Screenshots: add after the first production deployment (docs/screenshots/).
WattMap is an independent project using publicly available data. It is not affiliated with or endorsed by the Government of Ontario or any school board.檢視完整 README ↗See how Ontario schools use energy. WattMap turns Ontario's public school-board energy reports into searchable, peer-benchmarked, fully explained school energy profiles.
Screenshots: add after the first production deployment (
docs/screenshots/).
WattMap is an independent project using publicly available data. It is not affiliated with or endorsed by the Government of Ontario or any school board.
Every Ontario school board reports each facility's annual energy use and greenhouse-gas emissions, and the province publishes those reports as open data. They are hard to use: 13 years of large spreadsheets in three different layouts, units that change between years (and sometimes appear in French), facility names that don't match the Ministry's school directory, and no way to tell whether a school's number is high or low for a building like it.
WattMap ingests every English BPS report from 2011 onward, maps each layout onto one schema, matches facilities to Ministry school records with explicit confidence, and benchmarks each school only against comparable schools. Every figure links back to its source row and a documented formula.
/toolkit): how students can read a profile, questions for facility staff, safe investigationsflowchart TD
A[Ontario Data Catalogue<br/>BPS energy reports 2011–2024<br/>School directory · School information] -->|fetch_sources.py| B[data/raw + manifest.json]
B -->|run.py: header detection, column aliases,<br/>exact unit conversions, quality flags| C[Normalized facility-years]
C -->|matching.py: override → exact name → address<br/>→ near-identical name; fuzzy = review only| D[Facilities ↔ Ministry schools]
D -->|Supabase upsert (idempotent)| E[(PostgreSQL<br/>boards · schools · facilities<br/>energy_records · ingestion_runs)]
E -->|analytics:rebuild — lib/analytics pure functions| F[(peer_metrics)]
E --> G[Next.js server components<br/>+ typed, validated route handlers]
F --> G
G --> H[WattMap UI]
D -->|match-review.csv| R[Human review → match_overrides.csv]
R --> D
More detail: docs/architecture.md.
| Layer | Choice |
|---|---|
| App | Next.js 14 (App Router), React 18, TypeScript (strict), Tailwind CSS, Lucide icons |
| Charts / map | Recharts, plain SVG for print, MapLibre GL JS with OpenFreeMap (OpenStreetMap) tiles |
| Database | PostgreSQL on Supabase, Drizzle ORM + drizzle-kit migrations; PGlite for local mode and tests |
| Data processing | Python 3 · pandas · numpy · openpyxl · supabase-py |
| Validation | Zod on every query parameter |
| Tests | Vitest (unit + PGlite integration), Python unittest (pipeline), Playwright (end-to-end) |
All from the Ontario Data Catalogue, used under the Open Government Licence – Ontario. Field-level documentation: docs/data-dictionary.md.
EUI (GJ/m²) = total site energy (GJ) ÷ floor area (m²)
percentile = 100 × (peers below + ½ peers equal) ÷ peers (higher = more energy per m²)
score = round(100 × (0.8 × percentile/100 + 0.2 × clamp((trend%/yr + 10) / 20, 0, 1)))
energy gap (GJ) = max(0, EUI − peer median EUI) × floor area
robust z = 0.6745 × (EUI − peer median) ÷ MAD
Peers share the reporting year, operation type, and school level, with floor area 0.67–1.5× (relaxed step by step until ≥ 20 peers; ≥ 10 required). Full details: docs/methodology.md and the in-app /methodology page.
Requirements: Node 20+ (tested on 24), Python 3.12+ (tested on 3.14).
npm install
python -m venv .venv
.venv/Scripts/python -m pip install -r requirements.txt # macOS/Linux: .venv/bin/python
cp .env.example .env.local # then fill in the values
| Variable | Used by | Notes |
|---|---|---|
DATABASE_URL | Next.js server, Drizzle, admin scripts | Supabase transaction pooler URI (port 6543). pglite:./data/local-pglite selects local mode. Server-only. |
NEXT_PUBLIC_SUPABASE_URL | Ingestion pipeline | Project URL (not secret). |
SUPABASE_SERVICE_ROLE_KEY | Ingestion pipeline | Bypasses Row Level Security. Never expose to the browser. |
NEXT_PUBLIC_SITE_URL | Metadata, sitemap | Production URL, e.g. https://wattmap.ca. |
NEXT_PUBLIC_MAP_STYLE_URL | Map | Optional MapLibre style for the light theme; defaults to OpenFreeMap Positron. |
NEXT_PUBLIC_MAP_STYLE_URL_DARK | Map | Optional MapLibre style for the dark theme; defaults to OpenFreeMap Dark. |
npm run db:migrate # applies drizzle/migrations to DATABASE_URL
All tables have Row Level Security enabled with no policies: the public Supabase API exposes nothing, while the server connection and the service-role pipeline work normally.
npm run data:fetch # download/refresh official files into data/raw (skips unchanged)
npm run data:ingest # normalize, match, quality-check, upsert into Supabase
npm run analytics:rebuild # recompute peer_metrics
npm run admin:validate # check database invariants
python scripts/ingest/run.py --dry-run runs every stage without writing. Each run writes data/processed/ingestion-summary.json, match-review.csv, and rejected-rows.csv. Re-running is idempotent. See docs/ingestion.md and docs/matching.md.
scripts/admin/)| Command | What it does |
|---|---|
npm run analytics:rebuild | Recompute peer groups, percentiles, trends, and scores (rebuild-analytics.ts) |
npm run admin:unresolved | List facilities awaiting a matching decision, current ones first |
npm run admin:approve -- <key> --school <number> --note "…" | Record a verified match (or --no-match) that survives re-ingestion |
npm run admin:validate | Check database invariants; exits non-zero on failure |
npm run admin:stats | Export the provincial statistics shown on the site as JSON |
.venv/Scripts/python scripts/ingest/run.py --export-dir data/processed/load
npm run db:seed-local
DATABASE_URL=pglite:./data/local-pglite npm run dev
npm test # Vitest: analytics, formatting, search, and PGlite integration tests
npm run test:ingest # Python pipeline tests
npm run build && npm run test:e2e # Playwright (desktop + mobile) against the local database
Vercel for the app, Supabase for Postgres. Set the environment variables above in Vercel, run migrations and ingestion from a trusted machine or CI job, then deploy. See docs/deployment.md.
Advanced map filtering, saved comparison links, shareable chart images, automated annual refresh in CI, richer reports, a public API, French-language UI, open-data exports, and a school Eco Club toolkit.
Source data © King's Printer for Ontario, licensed under the Open Government Licence – Ontario. A licence for the WattMap source code has not been chosen yet; add a LICENSE file before publishing the repository.