Skip to content
This repository was archived by the owner on Oct 19, 2026. It is now read-only.
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
2 changes: 1 addition & 1 deletion .github/workflows/changelog_entry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
name: Changelog entry check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6

- name: Check for changelog entry
run: |
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ jobs:

steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v8.1.0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install Python dependencies
Expand All @@ -26,9 +26,9 @@ jobs:
- name: Run tests with coverage
run: make test
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v6
with:
file: ./coverage.xml
files: ./coverage.xml
fail_ci_if_error: false
verbose: true
- name: Test documentation builds
Expand All @@ -43,7 +43,7 @@ jobs:
fi
done
- name: Deploy documentation
uses: JamesIves/github-pages-deploy-action@releases/v3
uses: JamesIves/github-pages-deploy-action@v4
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages # The branch the action should deploy to.
Expand Down
22 changes: 11 additions & 11 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ jobs:
Lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v8.1.0
- name: Install relevant dependencies
run: |
uv pip install black isort linecheck --system
Expand All @@ -29,11 +29,11 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v8.1.0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install Python dependencies
Expand All @@ -42,21 +42,21 @@ jobs:
- name: Run tests with coverage
run: make test
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v6
with:
file: ./coverage.xml
files: ./coverage.xml
fail_ci_if_error: false
verbose: true

Build:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v8.1.0
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install dependencies
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/versioning.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,26 @@ jobs:
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
token: ${{ steps.app-token.outputs.token }}
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: 3.13
- name: Build changelog
run: pip install yaml-changelog && make changelog
- name: Preview changelog update
run: ".github/get-changelog-diff.sh"
- name: Update changelog
uses: EndBug/add-and-commit@v9
uses: EndBug/add-and-commit@v10
with:
add: "."
message: Update package version
Expand All @@ -49,11 +49,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0 # Fetch all history for all tags and branches
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: 3.13
- name: Install package
Expand Down
4 changes: 4 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- bump: patch
changes:
changed:
- Updated GitHub Actions workflows for Node 24-compatible action runtimes.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ dev = [
"build",
"linecheck",
"yaml-changelog>=0.1.7",
"policyengine-us==1.370.1",
"policyengine-us==1.667.1",
]

docs = [
Expand Down
112 changes: 63 additions & 49 deletions src/policyengine_data/calibration/calibrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def calibrate_single_geography_level(
use_dataset_weights: Optional[bool] = True,
regularize_with_l0: Optional[bool] = False,
calibration_log_path: Optional[str] = None,
raise_error: Optional[bool] = True,
raise_error: Optional[bool] = False,
) -> "SingleYearDataset":
"""
This function will calibrate the dataset for a specific geography level, defaulting to stacking the base dataset per area within it.
Expand All @@ -77,7 +77,7 @@ def calibrate_single_geography_level(
use_dataset_weights (Optional[bool]): Whether to use original dataset weights as the starting weights for calibration. Default: True.
regularize_with_l0 (Optional[bool]): Whether to use L0 regularization during calibration. Default: False.
calibration_log_path (Optional[str]): The path to the calibration log file. If None, calibration log CSVs will not be saved.
raise_error (Optional[bool]): Whether to raise an error if matrix creation fails. Default: True.
raise_error (Optional[bool]): Whether to raise an error if matrix creation fails. Default: False.

Returns:
geography_level_calibrated_dataset (SingleYearDataset): The calibrated dataset for the specified geography level.
Expand All @@ -100,9 +100,7 @@ def calibrate_single_geography_level(
if stack_datasets:
# Load dataset configured for the specific geography first
# TODO: move away from hardcoding UCGID for geographic identification once -us is updated
from policyengine_us.variables.household.demographic.geographic.ucgid.ucgid_enum import (
UCGID,
)
from policyengine_data.calibration.ucgid import UCGID

sim_data_to_calibrate = load_dataset_for_geography_legacy(
microsimulation_class=microsimulation_class,
Expand All @@ -129,49 +127,67 @@ def calibrate_single_geography_level(
stratum_filter_value=geo_identifier,
stratum_filter_operation="in",
)
metrics_evaluation = validate_metrics_matrix(
metrics_matrix,
targets,
target_info=target_info,
raise_error=raise_error,
)

target_names = []
excluded_targets = []
for target_id, info in target_info.items():
target_names.append(info["name"])
if not info["active"]:
excluded_targets.append(target_id)
target_names = np.array(target_names)

if use_dataset_weights:
weights = sim_data_to_calibrate.calculate(
"household_weight"
).values
if len(targets) == 0:
logger.warning(
"No calibration targets found for %s in %s; preserving "
"the sampled dataset without reweighting.",
area,
year,
)
if use_dataset_weights:
optimized_weights = sim_data_to_calibrate.calculate(
"household_weight"
).values
else:
optimized_weights = np.ones(
len(sim_data_to_calibrate.calculate("household_id").values)
)
optimized_sparse_weights = pd.Series(optimized_weights)
else:
weights = np.ones(len(metrics_matrix))

# Calibrate with L0 regularization
from microcalibrate import Calibration

calibrator = Calibration(
weights=weights,
targets=targets,
target_names=target_names,
estimate_matrix=metrics_matrix,
epochs=epochs,
learning_rate=0.2,
noise_level=noise_level,
excluded_targets=(
excluded_targets if len(excluded_targets) > 0 else None
),
sparse_learning_rate=0.1,
regularize_with_l0=regularize_with_l0,
csv_path=calibration_log_path,
)
performance_log = calibrator.calibrate()
optimized_sparse_weights = calibrator.sparse_weights
optimized_weights = calibrator.weights
metrics_evaluation = validate_metrics_matrix(
metrics_matrix,
targets,
target_info=target_info,
raise_error=raise_error,
)

target_names = []
excluded_targets = []
for target_id, info in target_info.items():
target_names.append(info["name"])
if not info["active"]:
excluded_targets.append(target_id)
target_names = np.array(target_names)

if use_dataset_weights:
weights = sim_data_to_calibrate.calculate(
"household_weight"
).values
else:
weights = np.ones(len(metrics_matrix))

# Calibrate with L0 regularization
from microcalibrate import Calibration

calibrator = Calibration(
weights=weights,
targets=targets,
target_names=target_names,
estimate_matrix=metrics_matrix,
epochs=epochs,
learning_rate=0.2,
noise_level=noise_level,
excluded_targets=(
excluded_targets if len(excluded_targets) > 0 else None
),
sparse_learning_rate=0.1,
regularize_with_l0=regularize_with_l0,
csv_path=calibration_log_path,
)
performance_log = calibrator.calibrate()
optimized_sparse_weights = calibrator.sparse_weights
optimized_weights = calibrator.weights

# Minimize the calibrated dataset storing only records with non-zero weights
single_year_calibrated_dataset = minimize_calibrated_dataset_legacy(
Expand Down Expand Up @@ -309,9 +325,7 @@ def calibrate_all_levels(
logger.info(f"Stacking dataset for {area}...")

# Load dataset configured for the specific geographic area
from policyengine_us.variables.household.demographic.geographic.ucgid.ucgid_enum import (
UCGID,
)
from policyengine_data.calibration.ucgid import UCGID

sim_data_to_stack = load_dataset_for_geography_legacy(
microsimulation_class=microsimulation_class,
Expand Down
62 changes: 62 additions & 0 deletions src/policyengine_data/calibration/database_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Compatibility helpers for calibration database schema drift."""


def ensure_calibration_schema(engine) -> None:
"""Add nullable columns expected by current calibration queries if absent."""
with engine.begin() as conn:
table_names = {
row[0]
for row in conn.exec_driver_sql(
"SELECT name FROM sqlite_master WHERE type = 'table'"
)
}

if "strata" in table_names:
strata_columns = {
row[1]
for row in conn.exec_driver_sql("PRAGMA table_info(strata)")
}
if "stratum_group_id" not in strata_columns:
conn.exec_driver_sql(
"ALTER TABLE strata ADD COLUMN stratum_group_id INTEGER"
)
conn.exec_driver_sql(
"UPDATE strata SET stratum_group_id = stratum_id "
"WHERE stratum_group_id IS NULL"
)
if "parent_stratum_id" not in strata_columns:
conn.exec_driver_sql(
"ALTER TABLE strata ADD COLUMN parent_stratum_id INTEGER"
)
if "definition_hash" not in strata_columns:
conn.exec_driver_sql(
"ALTER TABLE strata ADD COLUMN definition_hash TEXT"
)

if "targets" in table_names:
target_columns = {
row[1]
for row in conn.exec_driver_sql("PRAGMA table_info(targets)")
}
if "active" not in target_columns:
conn.exec_driver_sql(
"ALTER TABLE targets ADD COLUMN active BOOLEAN"
)
conn.exec_driver_sql(
"UPDATE targets SET active = 1 WHERE active IS NULL"
)
if "tolerance" not in target_columns:
conn.exec_driver_sql(
"ALTER TABLE targets ADD COLUMN tolerance REAL"
)
if "notes" not in target_columns:
conn.exec_driver_sql(
"ALTER TABLE targets ADD COLUMN notes TEXT"
)
if "reform_id" not in target_columns:
conn.exec_driver_sql(
"ALTER TABLE targets ADD COLUMN reform_id INTEGER"
)
conn.exec_driver_sql(
"UPDATE targets SET reform_id = 0 WHERE reform_id IS NULL"
)
Loading