尚未检测到更新。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.