diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 51f8769..78833a5 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -21,7 +21,7 @@ jobs: pip install furo - name: Generate documentation - run: make docs + run: make documentation - name: Check documentation build run: | diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 45f696c..a110b40 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -21,8 +21,6 @@ jobs: pip install furo pip install sphinx-argparse - - - name: Generate documentation run: make documentation diff --git a/docs/_toc.yml b/docs/_toc.yml index b2699f2..51cd2ae 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -3,4 +3,7 @@ root: index parts: - caption: Maintenance chapters: - - file: maintaining \ No newline at end of file + - file: maintaining + - caption: Schema + chapters: + - file: schema \ No newline at end of file diff --git a/docs/schema.ipynb b/docs/schema.ipynb new file mode 100644 index 0000000..89ed21b --- /dev/null +++ b/docs/schema.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Schema\n", + "\n", + "This page contains examples of the schema of the returned simulation outputs for given types of simulation. Each subsection specifies the country, scope and whether a reform has been passed. The schema is given in YAML format.\n", + "\n", + "## UK, macro, reform-comparison" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [ + "hide-input" + ] + }, + "outputs": [ + { + "data": { + "text/markdown": [ + "```yaml\n", + "macro:\n", + " baseline:\n", + " gov:\n", + " balance:\n", + " total_gov_spending: \n", + " total_state_tax: \n", + " total_tax_revenue: \n", + " programs:\n", + " child_benefit: \n", + " council_tax: \n", + " fuel_duty: \n", + " income_tax: \n", + " national_insurance: \n", + " ni_employer: \n", + " pension_credit: \n", + " state_pension: \n", + " tax_credits: \n", + " universal_credit: \n", + " vat: \n", + " household:\n", + " demographic_values:\n", + " household_count_people: \n", + " household_weight: \n", + " person_weight: \n", + " comparison:\n", + " revenue_impact:\n", + " tax_revenues: \n", + " reform:\n", + " gov:\n", + " balance:\n", + " total_gov_spending: \n", + " total_state_tax: \n", + " total_tax_revenue: \n", + " programs:\n", + " child_benefit: \n", + " council_tax: \n", + " fuel_duty: \n", + " income_tax: \n", + " national_insurance: \n", + " ni_employer: \n", + " pension_credit: \n", + " state_pension: \n", + " tax_credits: \n", + " universal_credit: \n", + " vat: \n", + " household:\n", + " demographic_values:\n", + " household_count_people: \n", + " household_weight: \n", + " person_weight: \n", + "\n", + "```" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from policyengine import Simulation\n", + "from IPython.display import Markdown\n", + "import yaml\n", + "\n", + "def replace_value_with_dtype(tree):\n", + " for key in tree:\n", + " if isinstance(tree[key], dict):\n", + " replace_value_with_dtype(tree[key])\n", + " else:\n", + " tree[key] = str(type(tree[key]))\n", + " return tree\n", + "\n", + "sim = Simulation(\n", + " country=\"uk\",\n", + " scope=\"macro\",\n", + " time_period=2025,\n", + " reform={\n", + " \"gov.hmrc.income_tax.allowances.personal_allowance.amount\": {\n", + " \"2025\": 0,\n", + " }\n", + " }\n", + ")\n", + "sim.calculate(\"macro\")\n", + "tree = replace_value_with_dtype(sim.outputs)\n", + "\n", + "Markdown('```yaml\\n' + yaml.dump(tree, indent=2) + '\\n```')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/policyengine/outputs/macro/comparison/revenue_impact.py b/policyengine/outputs/macro/comparison/revenue_impact.py index e7276ca..9cce7be 100644 --- a/policyengine/outputs/macro/comparison/revenue_impact.py +++ b/policyengine/outputs/macro/comparison/revenue_impact.py @@ -10,6 +10,13 @@ def revenue_impact(simulation: Simulation): Returns: float: The revenue impact of the simulation. """ - tax_revenue_baseline = simulation.calculate("macro/baseline/tax_revenue") - tax_revenue_reform = simulation.calculate("macro/reform/tax_revenue") - return tax_revenue_reform - tax_revenue_baseline + tax_revenue_baseline = simulation.calculate( + "macro/baseline/gov/balance" + )["total_tax_revenue"] + tax_revenue_reform = simulation.calculate( + "macro/reform/gov/balance" + )["total_tax_revenue"] + tax_revenue_impact = tax_revenue_reform - tax_revenue_baseline + return { + "tax_revenues": tax_revenue_impact, + } diff --git a/policyengine/outputs/macro/single/gov/balance.py b/policyengine/outputs/macro/single/gov/balance.py new file mode 100644 index 0000000..ceb66b6 --- /dev/null +++ b/policyengine/outputs/macro/single/gov/balance.py @@ -0,0 +1,20 @@ +from policyengine import Simulation + + +def balance(simulation: Simulation) -> dict: + sim = simulation.baseline + if simulation.country == "uk": + total_tax = sim.calculate("gov_tax").sum() + total_spending = sim.calculate("gov_spending").sum() + total_state_tax = 0 + elif simulation.country == "us": + total_tax = sim.calculate("household_tax").sum() + total_spending = sim.calculate("household_benefits").sum() + total_state_tax = simulation.calculate( + "household_state_income_tax" + ).sum() + return { + "total_tax_revenue": total_tax, + "total_gov_spending": total_spending, + "total_state_tax": total_state_tax, + } diff --git a/policyengine/outputs/macro/single/gov/programs.py b/policyengine/outputs/macro/single/gov/programs.py new file mode 100644 index 0000000..c261440 --- /dev/null +++ b/policyengine/outputs/macro/single/gov/programs.py @@ -0,0 +1,35 @@ +from policyengine import Simulation +from dataclasses import dataclass + + +@dataclass +class UKProgram: + name: str + is_positive: bool + + +class UKPrograms: + PROGRAMS = [ + UKProgram("income_tax", True), + UKProgram("national_insurance", True), + UKProgram("vat", True), + UKProgram("council_tax", True), + UKProgram("fuel_duty", True), + UKProgram("tax_credits", False), + UKProgram("universal_credit", False), + UKProgram("child_benefit", False), + UKProgram("state_pension", False), + UKProgram("pension_credit", False), + UKProgram("ni_employer", True), + ] + + +def programs(simulation: Simulation) -> dict: + if simulation.country == "uk": + return { + program.name: simulation.baseline.calculate( + program.name, map_to="household" + ).sum() + * (1 if program.is_positive else -1) + for program in UKPrograms.PROGRAMS + } diff --git a/policyengine/outputs/macro/single/household/demographic_values.py b/policyengine/outputs/macro/single/household/demographic_values.py new file mode 100644 index 0000000..e0ea72d --- /dev/null +++ b/policyengine/outputs/macro/single/household/demographic_values.py @@ -0,0 +1,15 @@ +from policyengine import Simulation + + +def demographic_values(simulation: Simulation) -> dict: + sim = simulation.baseline + household_count_people = ( + sim.calculate("household_count_people").astype(int).tolist() + ) + person_weight = sim.calculate("person_weight").astype(float).tolist() + household_weight = sim.calculate("household_weight").astype(float).tolist() + return { + "household_count_people": household_count_people, + "person_weight": person_weight, + "household_weight": household_weight, + } diff --git a/policyengine/outputs/macro/single/tax_revenue.py b/policyengine/outputs/macro/single/tax_revenue.py deleted file mode 100644 index 3a2f70d..0000000 --- a/policyengine/outputs/macro/single/tax_revenue.py +++ /dev/null @@ -1,2 +0,0 @@ -def tax_revenue(simulation): - return simulation.baseline.calculate("gov_tax").sum() / 1e9 diff --git a/policyengine/simulation.py b/policyengine/simulation.py index 0832b90..18a34a9 100644 --- a/policyengine/simulation.py +++ b/policyengine/simulation.py @@ -73,9 +73,15 @@ def calculate(self, output: str): """ if output.endswith("/"): output = output[:-1] + + if output == "": + output = list(self.outputs.keys())[0] node = self.outputs + for child_key in output.split("/")[:-1]: + node = node[child_key] + parent = node child_key = output.split("/")[-1] node = parent[child_key] diff --git a/test.ipynb b/test.ipynb deleted file mode 100644 index 798dba0..0000000 --- a/test.ipynb +++ /dev/null @@ -1,89 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from policyengine import Simulation" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "sim = Simulation(\n", - " country=\"uk\",\n", - " scope=\"household\",\n", - " data={\n", - " \"employment_income\": 30_000\n", - " },\n", - " time_period=2025,\n", - " reform={\n", - " \"gov.hmrc.income_tax.allowances.personal_allowance.amount\": {\n", - " \"2025\": 0,\n", - " }\n", - " }\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Calculating net_income_change...\n", - "Calculating net_income...\n", - "Stored result in household/baseline/net_income\n", - "Calculating net_income...\n", - "Stored result in household/reform/net_income\n", - "Stored result in household/comparison/net_income_change\n" - ] - }, - { - "data": { - "text/plain": [ - "{'comparison': {'net_income_change': -2514.0},\n", - " 'baseline': {'net_income': 28298.0},\n", - " 'reform': {'net_income': 25784.0}}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sim.calculate(\"household\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "base", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.14" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -}