diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000..a52f9cd
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,30 @@
+FROM mcr.microsoft.com/devcontainers/python:1-3.12-bullseye
+
+# Raise file descriptor limits
+RUN echo "* soft nofile 65536\n* hard nofile 65536" >> /etc/security/limits.conf
+
+# Install just
+RUN curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
+
+# Install Quarto CLI
+RUN QUARTO_VERSION=1.6.42 && \
+ curl -LO "https://github.com/quarto-dev/quarto-cli/releases/download/v${QUARTO_VERSION}/quarto-${QUARTO_VERSION}-linux-arm64.deb" && \
+ dpkg -i "quarto-${QUARTO_VERSION}-linux-arm64.deb" && \
+ rm "quarto-${QUARTO_VERSION}-linux-arm64.deb"
+
+# Install AWS CLI v2
+RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip" && \
+ unzip awscliv2.zip && \
+ ./aws/install && \
+ rm -rf aws awscliv2.zip
+
+# Install Chromium (remove stale yarn repo first to unblock apt-get update)
+RUN rm -f /etc/apt/sources.list.d/yarn.list && \
+ apt-get update && \
+ apt-get install -y chromium && \
+ apt-get clean
+
+# Create workspaces directory expected by devcontainer
+RUN mkdir -p /workspaces/smart-meter-analysis
+RUN mkdir -p /opt/venv && chown vscode:vscode /opt/venv
+WORKDIR /workspaces/smart-meter-analysis
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 9c3b51d..756fd53 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,25 +1,11 @@
-// .devcontainer/devcontainer.json
-
{
"name": "smart_meter_analysis",
- "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye",
-
- // Install Quarto CLI and Chromium
- "features": {
- "ghcr.io/rocker-org/devcontainer-features/quarto-cli:1": {
- "installChromium": true
- },
- "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
- "packages": "chromium"
- },
- "ghcr.io/guiyomh/features/just:0.1.0": {
- "version": "1.40.0"
- },
- "ghcr.io/devcontainers/features/aws-cli:1": {
- "version": "2.27.4"
- }
+ "workspaceFolder": "/workspaces/smart-meter-analysis",
+ "runArgs": ["--ulimit", "nofile=65536:65536"],
+ "build": {
+ "dockerfile": "Dockerfile",
+ "context": ".."
},
-
"initializeCommand": "mkdir -p ${localEnv:HOME}/.aws",
"mounts": [
"source=${localEnv:HOME}/.aws,target=/home/vscode/.aws,type=bind,consistency=cached"
@@ -27,15 +13,11 @@
"remoteEnv": {
"AWS_REGION": "us-west-2"
},
-
- // Run after the container is created to install uv, create .venv, and install deps
"postCreateCommand": "./.devcontainer/postCreateCommand.sh",
-
- // Tell Quarto to use the Python interpreter in the uv-managed virtual environment.
"containerEnv": {
- "QUARTO_PYTHON": "/workspaces/smart-meter-analysis/.venv/bin/python"
+ "UV_PROJECT_ENVIRONMENT": "/opt/venv",
+ "QUARTO_PYTHON": "/opt/venv/bin/python"
},
-
"customizations": {
"vscode": {
"extensions": [
@@ -48,12 +30,8 @@
"python.testing.pytestArgs": ["tests"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
-
- // Use the uv-created virtual environment
- "python.defaultInterpreterPath": "/workspaces/smart-meter-analysis/.venv/bin/python",
- "python.testing.pytestPath": "/workspaces/smart-meter-analysis/.venv/bin/pytest",
-
- // Let the Python extension auto-activate the environment in its own terminals
+ "python.defaultInterpreterPath": "/opt/venv/bin/python",
+ "python.testing.pytestPath": "/opt/venv/bin/pytest",
"python.terminal.activateEnvironment": true
}
}
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..96f3fda
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,10 @@
+data/
+archive/
+tmp_polars_*/
+.mypy_cache/
+.ruff_cache/
+.venv/
+__pycache__/
+*.parquet
+*.csv
+.git/
diff --git a/.gitignore b/.gitignore
index 15eb7d1..042079b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,7 +18,6 @@ dist/
downloads/
eggs/
.eggs/
-lib/
lib64/
parts/
sdist/
@@ -143,6 +142,7 @@ cython_debug/
*.xlsx
*.csv
*.parquet
+*.geojson
*.tsv
*.kml
*.zip
@@ -156,7 +156,10 @@ data/
# Temporary files
scratch/
*_sample_*.csv
+# Ignore ad-hoc test scripts in the project root (test_foo.py scratch files)
+# but keep real test files in the tests/ directory via negation pattern.
test_*.py
+!tests/test_*.py
debug_*.py
# Debug files
@@ -166,9 +169,28 @@ archive/
# generated artifacts
out/
+output/
results/
# benchmark artificats
profiles/
docs/*.html
docs/index_files/
+
+# Local run artifacts (shard lists, input lists, temp dirs)
+*.txt
+.tmp/
+archive_quarantine/
+tmp_polars_run_*/
+subagent_packages/
+
+# Operator-only env (do not commit)
+.env.comed
+
+# Pricing pilot data
+data/pilot_interval_parquet/
+CLAUDE.md
+.cursor/
+
+/.quarto/
+_manuscript/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 23d8811..6b277b4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -13,10 +13,19 @@ repos:
args: [--autofix, --no-sort-keys]
- id: end-of-file-fixer
- id: trailing-whitespace
+ - id: detect-private-key
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: "v0.11.5"
+ rev: "v0.14.4"
hooks:
- id: ruff
args: [--exit-non-zero-on-fix]
- id: ruff-format
+
+ - repo: local
+ hooks:
+ - id: forbid-secrets
+ name: Block secrets and credential files
+ entry: "bash -c 'echo BLOCKED: secrets/credential file staged for commit >&2; exit 1'"
+ language: system
+ files: '(\.env$|\.env\.|\.secrets|\.secret|credentials\.json|\.pem$|\.key$|\.p12$|\.pfx$|\.jks$)'
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000..e4fba21
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.12
diff --git a/.ruffignore b/.ruffignore
index 6ce45d2..a012ec2 100644
--- a/.ruffignore
+++ b/.ruffignore
@@ -1,2 +1 @@
archive/
-archive/
diff --git a/.style/fonts/ft-bold.otf b/.style/fonts/ft-bold.otf
new file mode 100644
index 0000000..28a7a2b
Binary files /dev/null and b/.style/fonts/ft-bold.otf differ
diff --git a/.style/fonts/ft-regular.otf b/.style/fonts/ft-regular.otf
new file mode 100644
index 0000000..c418da5
Binary files /dev/null and b/.style/fonts/ft-regular.otf differ
diff --git a/.style/fonts/gtp-black.otf b/.style/fonts/gtp-black.otf
new file mode 100644
index 0000000..0f2988b
Binary files /dev/null and b/.style/fonts/gtp-black.otf differ
diff --git a/.style/fonts/gtp-bold.otf b/.style/fonts/gtp-bold.otf
new file mode 100644
index 0000000..33653a9
Binary files /dev/null and b/.style/fonts/gtp-bold.otf differ
diff --git a/.style/fonts/gtp-regular.otf b/.style/fonts/gtp-regular.otf
new file mode 100644
index 0000000..73c1dc7
Binary files /dev/null and b/.style/fonts/gtp-regular.otf differ
diff --git a/.style/fonts/ips-bold.otf b/.style/fonts/ips-bold.otf
new file mode 100644
index 0000000..5ae5057
Binary files /dev/null and b/.style/fonts/ips-bold.otf differ
diff --git a/.style/fonts/ips-regular.otf b/.style/fonts/ips-regular.otf
new file mode 100644
index 0000000..51b38a2
Binary files /dev/null and b/.style/fonts/ips-regular.otf differ
diff --git a/.style/inline_svgs.py b/.style/inline_svgs.py
new file mode 100644
index 0000000..286316e
--- /dev/null
+++ b/.style/inline_svgs.py
@@ -0,0 +1,244 @@
+"""Inline SVG figures into Quarto-rendered HTML files.
+
+Replaces
tags with the raw