Developer Guide#
This guide explains how to contribute to PolicyEngine Australia and extend the model with new parameters, variables, and reforms.
Development Approach#
PolicyEngine Australia follows these key principles:
1. Test-Driven Development (TDD)#
Write tests first, then implementation
Comprehensive test coverage for all calculations
YAML-based policy tests for easy scenario creation
2. Government Source References#
All parameters linked to official sources
Regular updates when rates change
Clear documentation trail
3. Continuous Integration#
Automated testing on all pull requests
Code formatting with Black (79 chars)
Python 3.10-3.13 compatibility
4. Modern Python Tooling#
uv
for fast package managementpytest
for testingmyst
(Jupyter Book 2.0) for documentation
Setting Up Development Environment#
Prerequisites#
Python 3.10 or higher (3.13 recommended)
Git
uv (recommended) or pip
Installation#
# Clone the repository
git clone https://github.com/PolicyEngine/policyengine-au.git
cd policyengine-au
# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install the package in development mode
uv pip install --system -e .[dev]
# Or with pip
pip install -e .[dev]
Running Tests#
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=policyengine_au
# Run specific test file
uv run pytest policyengine_au/tests/policy/baseline/test_income_tax.yaml
# Run only YAML policy tests
uv run pytest policyengine_au/tests/policy -v
Code Formatting#
# Format code
make format
# Or manually
black . -l 79
linecheck . --fix
# Check formatting (CI will fail if not formatted)
black . -l 79 --check
Adding New Parameters#
Parameters define values that change over time (rates, thresholds, amounts).
1. Create YAML Parameter File#
# policyengine_au/parameters/gov/dss/new_benefit/payment_rate.yaml
description: New Benefit payment rate
reference:
- title: Official source title
href: https://government-source-url.gov.au
metadata:
unit: currency-AUD
period: fortnight
label: New Benefit rate
uprating: gov.treasury.cpi # Optional: automatic uprating
values:
2023-07-01: 500.00
2024-01-01: 515.00
2024-07-01: 530.00
2. Parameter Organization#
Place parameters in appropriate directory:
gov/ato/
- Tax parametersgov/dss/
- Social services parametersgov/treasury/
- Economic parametersgov/states/
- State-specific parameters
Adding New Variables#
Variables calculate values based on parameters and other variables.
1. Create Variable File#
# policyengine_au/variables/gov/dss/new_benefit/new_benefit_payment.py
"""New Benefit payment calculation."""
from policyengine_au.model_api import *
class new_benefit_payment(Variable):
value_type = float
entity = Person # or TaxUnit, Family, Household
definition_period = YEAR # or MONTH, FORTNIGHT
label = "New Benefit payment"
documentation = "Total New Benefit payment amount"
reference = "https://official-source.gov.au"
unit = AUD
def formula(person, period, parameters):
# Get eligibility
eligible = person("new_benefit_eligible", period)
# Get payment rate from parameters
p = parameters(period).gov.dss.new_benefit
payment_rate = p.payment_rate
# Convert fortnight to year if needed
annual_payment = payment_rate * 26 # 26 fortnights per year
return eligible * annual_payment
2. Variable Best Practices#
One variable per file
Clear, descriptive names
Include documentation and references
Handle edge cases
Use vectorized operations (numpy)
Writing Tests#
1. YAML Policy Tests#
# policyengine_au/tests/policy/baseline/test_new_benefit.yaml
- name: Single person eligible for new benefit
period: 2024
input:
people:
person_1:
age: 25
employment_income: 0
new_benefit_eligible: true
households:
household:
members: [person_1]
output:
new_benefit_payment: 13_780 # $530 * 26 fortnights
- name: Person not eligible receives no benefit
period: 2024
input:
people:
person_1:
age: 25
employment_income: 50_000
new_benefit_eligible: false
output:
new_benefit_payment: 0
2. Python Unit Tests#
# policyengine_au/tests/variables/test_new_benefit.py
import pytest
from policyengine_au import AustralianTaxBenefitSystem
def test_new_benefit_calculation():
system = AustralianTaxBenefitSystem()
simulation = system.new_simulation()
# Set inputs
simulation.set_input("age", 2024, [25])
simulation.set_input("new_benefit_eligible", 2024, [True])
# Calculate result
result = simulation.calculate("new_benefit_payment", 2024)
# Assert expected value
assert result[0] == 13_780
Creating Reforms#
Reforms modify the baseline system for policy analysis.
1. Define Reform#
# policyengine_au/reforms/increase_jobseeker.py
from policyengine_au.model_api import *
def increase_jobseeker_by_50(parameters):
"""Increase JobSeeker payment by $50 per fortnight."""
# Modify parameter
p = parameters.gov.dss.jobseeker.payment_rates
# Increase all rates by $50
p.single.no_children.update(
value=lambda v: v + 50,
period="2024-01-01"
)
return parameters
# Create reform object
increase_jobseeker_reform = Reform(
name="Increase JobSeeker by $50",
parameter_reforms=[increase_jobseeker_by_50],
country_id="au"
)
2. Test Reform#
def test_jobseeker_reform():
# Apply reform to system
baseline = AustralianTaxBenefitSystem()
reformed = AustralianTaxBenefitSystem(
reform=increase_jobseeker_reform
)
# Compare results
# ... test implementation
Documentation#
1. Update Program Documentation#
When adding new programs, update:
docs/tax-programs.md
for tax changesdocs/benefit-programs.md
for benefit changesInclude rates, thresholds, eligibility
2. Build Documentation#
# Build with MyST (Jupyter Book 2.0)
myst build docs
# View locally
open docs/_build/index.html
CI/CD Pipeline#
Pull Request Checks#
Formatting: Black and linecheck
Tests: All Python versions (3.10-3.13)
Coverage: Maintained above threshold
Documentation: Builds successfully
Main Branch#
Runs full test suite
Deploys documentation to GitHub Pages
Prepares for PyPI release (when enabled)
Contributing Checklist#
Before submitting a PR:
Code formatted with
make format
Tests written and passing
Documentation updated
Government sources referenced
Changelog entry added
PR description explains changes
Common Patterns#
Income Testing#
def formula(person, period, parameters):
income = person("assessable_income", period)
p = parameters(period).gov.dss.payment
free_area = p.income_test.free_area
taper_rate = p.income_test.taper_rate
max_payment = p.payment_rate
reduction = max_(0, income - free_area) * taper_rate
payment = max_(0, max_payment - reduction)
return payment
Age-Based Eligibility#
def formula(person, period, parameters):
age = person("age", period)
min_age = parameters(period).gov.program.min_age
max_age = parameters(period).gov.program.max_age
return (age >= min_age) & (age <= max_age)
Period Conversion#
# Convert annual to fortnightly
fortnightly_amount = annual_amount / 26
# Convert monthly to annual
annual_amount = monthly_amount * 12
Getting Help#
GitHub Issues: Bug reports and feature requests
Discussions: Questions and ideas
Documentation: This guide and API reference
Examples: Test files show usage patterns