Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions .github/workflows/release-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Publish a test release to TestPyPI.
#
# Trigger: manual dispatch from the Actions tab (workflow_dispatch). You provide
# the version string (e.g. "0.1.0rc1") via the input field. The workflow patches
# pyproject.toml with that version, builds, and publishes to TestPyPI.
#
# Use this BEFORE the real release to verify that:
# - The package builds cleanly in CI
# - The wheel metadata (description, classifiers, URLs) renders correctly
# - The dependency list is correct (including the cairo caveat)
# - OIDC trusted publishing works end-to-end
#
# One-time setup required on TestPyPI before the first run:
#
# 1. Create an account at https://test.pypi.org if you don't have one
# 2. Go to https://test.pypi.org/manage/account/publishing/
# 3. Add a new Trusted Publisher:
# PyPI project name : rate-design-platform
# GitHub owner : switchbox-data
# Repository name : rate-design-platform
# Workflow filename : release-test.yml
# Environment name : testpypi
# 4. Create the "testpypi" GitHub Environment under Settings → Environments
#
# After publishing, verify at:
# https://test.pypi.org/project/rate-design-platform/
#
# Test-install with:
# pip install -i https://test.pypi.org/simple/ rate-design-platform==<version>
#
# NOTE: TestPyPI does not have all dependencies (e.g. polars, buildstock-fetch),
# so pip install will fail on dependency resolution. That's expected — you're
# only verifying that the package itself uploads and renders correctly.
name: Test release to TestPyPI
on:
workflow_dispatch:
inputs:
version:
description: "Version to publish (e.g. 0.1.0rc1, 0.1.0.dev1). Use PEP 440 pre-release suffixes so it doesn't collide with real releases."
required: true
type: string
jobs:
build:
name: Build distribution packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Configure Git for private CAIRO dependency
run: |
git config --global url."https://${{ secrets.GH_PAT }}@github.com/".insteadOf "https://github.com/"
- name: Set version in pyproject.toml
run: |
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
echo "Building version: $VERSION"
grep '^version' pyproject.toml
env:
VERSION: ${{ inputs.version }}
- name: Build sdist and wheel
run: uv build --no-sources
- name: Verify distribution contents
run: uvx twine check dist/*
- name: Upload distribution packages
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
if-no-files-found: error
publish:
name: Publish to TestPyPI
needs: build
runs-on: ubuntu-latest
environment:
name: testpypi
url: https://test.pypi.org/p/rate-design-platform
permissions:
id-token: write
steps:
- name: Download distribution packages
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
100 changes: 100 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Publish a release to PyPI.
#
# Trigger: create a GitHub Release whose tag is the version with a v prefix
# (e.g. `v0.2.0`). The workflow strips the `v`, writes the version into
# pyproject.toml, builds the package, and publishes to PyPI — so you never
# need to bump the version in source before tagging.
#
# Publishing uses PyPI Trusted Publishing (OIDC) — no long-lived token is stored
# as a secret. One-time setup required on PyPI before the first run:
#
# 1. Go to https://pypi.org/manage/account/publishing/
# 2. Add a new Trusted Publisher:
# PyPI project name : rate-design-platform
# GitHub owner : switchbox-data
# Repository name : rate-design-platform
# Workflow filename : release.yml
# Environment name : pypi
# 3. Create the "pypi" GitHub Environment under Settings → Environments
# (optional: add a required reviewer for a manual approval gate).
#
# The CAIRO private-git dependency is handled the same way as in CI: by
# configuring Git to use GH_PAT when fetching from GitHub. uv uses this when
# resolving/building so that the private repo is reachable.
#
# NOTE on the cairo PyPI conflict: the built wheel declares `cairo` as a
# Requires-Dist entry. pip users who install from PyPI will receive the unrelated
# Cairo graphics package instead of the simulation engine. See CHANGELOG.md
# "Known limitations" for the resolution plan.
name: Release to PyPI
on:
release:
types: [published]
jobs:
set-version:
name: Set version from release tag
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Export tag
id: vars
run: |
TAG=${GITHUB_REF#refs/*/}
# Strip leading 'v' if present (v0.2.0 → 0.2.0) for PEP 440 compliance
echo "tag=${TAG#v}" >> $GITHUB_OUTPUT
- name: Update project version
run: |
sed -i "s/^version = \".*\"/version = \"$RELEASE_VERSION\"/" pyproject.toml
env:
RELEASE_VERSION: ${{ steps.vars.outputs.tag }}
- name: Upload updated pyproject.toml
uses: actions/upload-artifact@v4
with:
name: pyproject-toml
path: pyproject.toml
build:
name: Build distribution packages
needs: set-version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Configure Git for private CAIRO dependency
run: |
git config --global url."https://${{ secrets.GH_PAT }}@github.com/".insteadOf "https://github.com/"
- name: Download updated pyproject.toml
uses: actions/download-artifact@v4
with:
name: pyproject-toml
- name: Build sdist and wheel
# --no-sources ignores [tool.uv.sources] so the private cairo git URL is not
# accessed during the build and is not baked into the wheel's dependency metadata.
# The wheel will declare `cairo` as a plain PyPI dependency (see CHANGELOG.md
# "Known limitations" for the resolution plan).
run: uv build --no-sources
- name: Verify distribution contents
run: uvx twine check dist/*
- name: Upload distribution packages
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
if-no-files-found: error
publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/rate-design-platform
permissions:
id-token: write # Required for OIDC trusted publishing
steps:
- name: Download distribution packages
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
52 changes: 52 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Changelog

All notable changes to this project will be documented in this file.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.1.0] - 2026-05-21

Initial release of the rate-design-platform package.

### Added

- ResStock data pipeline (`data/resstock/`) — fetch, modify, and upload NREL ResStock
metadata and load curves to S3; supports NY and RI; includes HP customer identification,
heating type classification, natural gas connection detection, vulnerability columns,
non-HP load approximation, MF electricity adjustment, utility assignment, and monthly
load aggregation.
- Cambium marginal-cost pipeline (`data/cambium/`) — fetch and process NREL Cambium
marginal energy, generation capacity, and bulk transmission cost data.
- EIA data pipelines (`data/eia/`) — hourly zone loads and EIA-861 utility stats.
- HUD AMI and SMI pipelines (`data/hud/`) — area median income and state median income
for LMI rate design.
- Census ACS PUMS pipeline (`data/census/pums/`) — person and housing microdata for
vulnerability analysis.
- FRED CPI pipeline (`data/fred/cpi/`) — CPI series for real-dollar adjustments.
- ASPE Federal Poverty Guidelines pipeline (`data/aspe/fpl/`).
- HP rates scenario runner (`rate_design/hp_rates/`) — shared entrypoint and Justfile
for NY and RI heat-pump-friendly rate design simulations.
- Pre-processing utilities (`utils/pre/`) — tariff mapping, scenario YAML generation,
marginal-cost allocation, config validation.
- Mid-run utilities (`utils/mid/`) — calibrated tariff promotion, subclass revenue
requirements, seasonal discount derivation, output resolution.
- Post-processing utilities (`utils/post/`) — LMI discount application, master bill and
BAT table construction across utilities.
- Manifest and provenance tracking for the ResStock pipeline
(`data/resstock/manifest.py`).

### Known limitations

- **CAIRO dependency**: CAIRO (the bill-alignment simulation engine) is a private Git
dependency resolved via `[tool.uv.sources]`. This override is transparent to pip, so
`pip install rate-design-platform` will attempt to install the unrelated `cairo` PyPI
package (Cairo graphics bindings) rather than the correct engine. Team members should
install via `uv` in a clone of this repository, which resolves the Git source
automatically. A future release will address this by publishing CAIRO to a package
index under a distinct name.

[Unreleased]: https://github.com/switchbox-data/rate-design-platform/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/switchbox-data/rate-design-platform/releases/tag/v0.1.0
27 changes: 27 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,33 @@ install:
clean:
rm -rf .pytest_cache .ruff_cache tmp

# =============================================================================
# 📦 BUILD & RELEASE
# =============================================================================
# These commands build the package locally (useful for verification).
# Actual releases are published by the release.yml GitHub Action when a
# GitHub Release is created.

# Clean build artifacts
clean-build:
echo "🚀 Removing build artifacts"
rm -rf dist

# Build sdist and wheel (mirrors what release.yml does)
build: clean-build
echo "🚀 Building distribution packages"
uv build --no-sources

# Build and verify with twine
build-and-verify: build
echo "🚀 Verifying distribution packages"
uvx twine check dist/*

# Upload to TestPyPI (run build-and-verify first)
publish-test: build-and-verify
echo "🚀 Uploading to TestPyPI"
uvx twine upload --repository testpypi dist/*

# =============================================================================
# 🔍 AWS
# =============================================================================
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# rate-design-platform

[![Build status](https://img.shields.io/github/actions/workflow/status/switchbox-data/rate-design-platform/ci-runner-native.yml?branch=main)](https://github.com/switchbox-data/rate-design-platform/actions/workflows/ci-runner-native.yml?query=branch%3Amain)
[![PyPI version](https://img.shields.io/pypi/v/rate-design-platform)](https://pypi.org/project/rate-design-platform/)
[![Commit activity](https://img.shields.io/github/commit-activity/m/switchbox-data/rate-design-platform)](https://github.com/switchbox-data/rate-design-platform)
[![License](https://img.shields.io/github/license/switchbox-data/rate-design-platform)](https://github.com/switchbox-data/rate-design-platform)

Expand All @@ -21,13 +22,25 @@ Large inputs/outputs (buildstock, CAIRO cases) are gitignored; sync via S3 or ke

## Install

From the repo root:
**From the repo (development / running simulations):**

```bash
just install
```

Uses **uv** for Python (see `pyproject.toml`). CAIRO is a private Git dependency; set `GH_PAT` for clone. Optional env vars (e.g. `ARCADIA_APP_ID`, `HUD_API_KEY`, `EIA_API_KEY`) are in `.env.example`—copy to `.env` and fill as needed. For AWS (S3), run `just aws` to refresh SSO when needed.
Uses **uv** for Python (see `pyproject.toml`). CAIRO is a private Git dependency; set `GH_PAT` for clone.

**As a library dependency (from PyPI):**

```bash
pip install rate-design-platform
# or
uv add rate-design-platform
```

> **Note:** The CAIRO simulation engine is a private Git dependency that cannot be
> distributed through PyPI. Install from the repo using `just install` if you need
> to run CAIRO simulations directly. See `CHANGELOG.md` for details. Optional env vars (e.g. `ARCADIA_APP_ID`, `HUD_API_KEY`, `EIA_API_KEY`) are in `.env.example`—copy to `.env` and fill as needed. For AWS (S3), run `just aws` to refresh SSO when needed.

## Run sims

Expand Down
14 changes: 13 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
[project]
name = "rate-design-platform"
version = "0.0.0"
description = "Simulation platform for electrical rate design."
description = "Simulation platform for electric rate design."
authors = [{ name = "Switchbox", email = "hello@switch.box" }]
readme = "README.md"
requires-python = ">=3.12,<3.14"
license = { text = "MIT" }
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering",
"Topic :: Office/Business :: Financial",
]
dependencies = [
"buildstock-fetch>=1.6.1",
"cairo",
Expand Down Expand Up @@ -41,6 +52,7 @@ dependencies = [

[project.urls]
Repository = "https://github.com/switchbox-data/rate-design-platform"
Changelog = "https://github.com/switchbox-data/rate-design-platform/blob/main/CHANGELOG.md"

[dependency-groups]
dev = [
Expand Down