plan529lab
A Python package for evaluating the after-tax tradeoff between investing through a 529/Qualified Tuition Program (QTP) and a taxable brokerage account for education savings.
This is not a simple "529 penalty calculator." It is a scenario engine that compares after-tax outcomes across multiple future states — including qualified education use, nonqualified withdrawal, beneficiary change, and Roth IRA rollover paths.
Disclaimer: This package is for educational and analytical purposes only. It is not tax, legal, or investment advice. Tax outcomes depend on facts, jurisdiction, and future law changes. Consult a qualified tax professional for your situation.
Why This Exists
Public discussion of 529 plans often oversimplifies the tradeoff. Common claims that 529s are "risky" because of "penalties" miss key nuances:
- Only the earnings portion of a nonqualified withdrawal is taxable — contributions/basis come back tax-free
- The 10% additional tax applies to the amount included in income, not the entire withdrawal
- Qualified expenses must be reduced by tax-free educational assistance and expenses used for AOTC/LLC
- State tax benefits, recapture rules, and the Roth rollover path materially affect the comparison
This package makes these interactions explicit and quantifiable.
Installation
pip install plan529lab
For development:
pip install -e ".[dev]"
Requires Python 3.11+.
Quick Start
Python API
from plan529lab.api import analyze_tradeoff
from plan529lab.io.yaml_loader import load_config
from plan529lab.state_rules.no_income_tax import NoIncomeTaxStateRule
config = load_config("examples/washington_no_income_tax.yaml")
result = analyze_tradeoff(config, state_rule=NoIncomeTaxStateRule("WA"))
print(result.explain())
print(f"Delta: ${result.delta:,.2f}")
print(f"Break-even probability: {result.break_even_qualified_use_probability:.1%}")
Monte Carlo Simulation
from plan529lab.api import run_monte_carlo
from plan529lab.models.monte_carlo import MonteCarloConfig, StochasticAssumptions
mc_config = MonteCarloConfig(
n_paths=10_000,
seed=42,
stochastic=StochasticAssumptions(return_std=0.15),
)
mc_result = run_monte_carlo(config, mc_config)
print(f"P(529 wins): {mc_result.prob_qtp_wins:.1%}")
print(f"Mean delta: ${mc_result.mean_delta:,.2f}")
Sensitivity Analysis
from plan529lab.api import run_sensitivity
result = run_sensitivity(config, "qualified_use_probability", [0.0, 0.25, 0.5, 0.75, 1.0])
for v, d in zip(result.param_values, result.deltas):
print(f" p={v:.0%}: delta=${d:,.0f}")
CLI
# Deterministic analysis
python -m plan529lab analyze --config examples/washington_no_income_tax.yaml
# Monte Carlo simulation
python -m plan529lab monte-carlo --config examples/washington_no_income_tax.yaml --n-sims 10000 --seed 42
# Sensitivity analysis
python -m plan529lab sensitivity --config examples/washington_no_income_tax.yaml \
--param qualified_use_probability --min 0 --max 1 --steps 11
# Break-even probability
python -m plan529lab breakeven --config examples/washington_no_income_tax.yaml
# State rule info
python -m plan529lab state-info WA
Configuration
Scenarios are defined in YAML files. See examples/ for templates.
tax_profile:
ordinary_income_rate: 0.35
ltcg_rate: 0.15
qualified_dividend_rate: 0.15
portfolio_assumptions:
annual_return: 0.07
dividend_yield: 0.015
qualified_dividend_share: 0.95
turnover_realization_rate: 0.05
scenario_policy:
qualified_use_probability: 0.75
horizon_years: 18
State Rules
The package uses a plugin architecture for state-specific tax treatment:
- NoIncomeTaxStateRule — for WA, TX, FL, NV, SD, WY, AK, TN, NH
- GenericDeductionStateRule — models a state income tax deduction on 529 contributions
- GenericCreditStateRule — models a state tax credit on 529 contributions
Generic rules do not reflect any specific state's exact rules, caps, or conditions.
Assumptions and Limitations
- Annual timesteps with start-of-year contributions
- Simplified taxable account model (no lot-level accounting, wash-sale rules, or AMT)
- Tax rates are assumed constant over the horizon
- Roth rollover uses one-shot eligibility estimate (not year-by-year staged rollover)
- Monte Carlo treats dividend yield and turnover as per-path constants (not per-year)
- State rules are generic unless specifically implemented
Development
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
pytest -v
# Lint
ruff check .
# Type check
mypy .
License
MIT
Metadata
Release files for plan529lab 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| plan529lab-0.1.0.tar.gz | 58.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| plan529lab-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 98.6 kB
Release files / plan529lab-0.1.0.tar.gz
| Download URL | plan529lab-0.1.0.tar.gz |
|---|---|
| Size | 58.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eb86a6023e9022e99e53c27a14c70c73dee56e36da57bfef32c371badc319dec
|
|
BLAKE2b-256 checksum How to use checksums |
b1df9cd6f3a209b9240634886f85f440e900a03bedd52db78ae8eb2f15495b40
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 2, 2026.
Transparency logRelease files / plan529lab-0.1.0-py3-none-any.whl
| Download URL | plan529lab-0.1.0-py3-none-any.whl |
|---|---|
| Size | 40.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
49e4949755b978029d716d968237e9d81be8a6982e844bae7c39a93cc5185d18
|
|
BLAKE2b-256 checksum How to use checksums |
954ece40f2843189d5b471ebb5d9dfc8b1057afe60a54cf6f5fb8975b7f33b5a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 2, 2026.
Transparency log