Housing assistance#

Housing assistance is a payment that varies with income, household size, rental expenses, local economic conditions, and other factors.

How earnings affect a one-person household’s housing assistance#

Consider a single person household in San Francisco, California with $2,000 monthly earned income and $1,000 monthly rent. They would be eligible for $400 per month in housing assistance.

from policyengine_us import IndividualSim
import pandas as pd
import plotly.express as px

sim_emp = IndividualSim(year=2022)
sim_emp.add_person(name="person", employment_income=2000 * 12)
sim_emp.add_spm_unit(
    name="spm_unit",
    members=["person"],
    hud_gross_rent=1_000 * 12,
    # 2022 AMI.
    # https://sfmohcd.org/sites/default/files/Documents/MOH/BMR%20Ownership/2022%20AMI-IncomeLimits.pdf
    ami=138550,
    # 2019 1BR payment standard.
    # https://sfmohcd.org/sites/default/files/Documents/MOH/Asset%20Management/2019%20AMI_RentLimits-HMFA.pdf
    pha_payment_standard=2748 * 12,
)

print(
    "Housing assistance: ",
    round(sim_emp.calc("housing_assistance")[0] / 12),
)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/policyengine_core/simulations/simulation_builder.py:549, in SimulationBuilder.init_variable_values(self, entity, instance_object, instance_id)
    548 try:
--> 549     entity.check_variable_defined_for_entity(variable_name)
    550 except (
    551     ValueError
    552 ) as e:  # The variable is defined for another entity

File /opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/policyengine_core/entities/entity.py:52, in Entity.check_variable_defined_for_entity(self, variable_name)
     40 message = os.linesep.join(
     41     [
     42         "You tried to compute the variable '{0}' for the entity '{1}';".format(
   (...)
     50     ]
     51 )
---> 52 raise ValueError(message)

ValueError: You tried to compute the variable 'ami' for the entity 'spm_units';
however the variable 'ami' is defined for 'households'.
Learn more about entities in our documentation:
<https://openfisca.org/doc/coding-the-legislation/50_entities.html>.

During handling of the above exception, another exception occurred:

SituationParsingError                     Traceback (most recent call last)
Cell In[1], line 21
      6 sim_emp.add_person(name="person", employment_income=2000 * 12)
      7 sim_emp.add_spm_unit(
      8     name="spm_unit",
      9     members=["person"],
   (...)
     16     pha_payment_standard=2748 * 12,
     17 )
     19 print(
     20     "Housing assistance: ",
---> 21     round(sim_emp.calc("housing_assistance")[0] / 12),
     22 )

File /opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/policyengine_core/simulations/individual_sim.py:240, in IndividualSim.calc(self, var, period, target, index, map_to, reform)
    227 """Calculates the value of a variable, executing any required formulas.
    228 
    229 Args:
   (...)
    237     np.array: The resulting values.
    238 """
    239 if not hasattr(self, "simulation"):
--> 240     self.build()
    242 if self.parametric_vary and reform is None:
    243     results = [
    244         self.calc(
    245             var,
   (...)
    252         for reform in self.parametric_reforms
    253     ]

File /opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/policyengine_core/simulations/individual_sim.py:88, in IndividualSim.build(self)
     84             self.situation_data[entity_metadata.plural][entity][
     85                 default_role.plural
     86             ] = members
     87     # Add missing entities with specified default roles
---> 88 self.simulation = Simulation(
     89     tax_benefit_system=self.system,
     90     situation=self.situation_data,
     91     reform=self.reform,
     92 )
     93 self.simulation.trace = True
     94 self.sim = self.simulation

File /opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/policyengine_core/simulations/simulation.py:139, in Simulation.__init__(self, tax_benefit_system, populations, situation, dataset, reform, trace)
    137     builder = SimulationBuilder()
    138     builder.default_period = self.default_input_period
--> 139     builder.build_from_dict(self.tax_benefit_system, situation, self)
    140     self.has_axes = builder.has_axes
    142 if populations is not None:

File /opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/policyengine_core/simulations/simulation_builder.py:86, in SimulationBuilder.build_from_dict(self, tax_benefit_system, input_dict, simulation)
     79 input_dict = self.explicit_singular_entities(
     80     tax_benefit_system, input_dict
     81 )
     82 if any(
     83     key in tax_benefit_system.entities_plural()
     84     for key in input_dict.keys()
     85 ):
---> 86     simulation = self.build_from_entities(
     87         tax_benefit_system, input_dict, simulation
     88     )
     89 else:
     90     simulation = self.build_from_variables(
     91         tax_benefit_system, input_dict, simulation
     92     )

File /opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/policyengine_core/simulations/simulation_builder.py:176, in SimulationBuilder.build_from_entities(self, tax_benefit_system, input_dict, simulation)
    174 instances_json = input_dict.get(entity_class.plural)
    175 if instances_json is not None:
--> 176     self.add_group_entity(
    177         self.persons_plural,
    178         persons_ids,
    179         entity_class,
    180         instances_json,
    181     )
    182 else:
    183     self.add_default_group_entity(persons_ids, entity_class)

File /opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/policyengine_core/simulations/simulation_builder.py:485, in SimulationBuilder.add_group_entity(self, persons_plural, persons_ids, entity, instances_json)
    478             person_role = (
    479                 role.subroles[index_within_role]
    480                 if role.subroles
    481                 else role
    482             )
    483             self.roles[entity.plural][person_index] = person_role
--> 485     self.init_variable_values(entity, variables_json, instance_id)
    487 if persons_to_allocate:
    488     entity_ids = entity_ids + list(persons_to_allocate)

File /opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/policyengine_core/simulations/simulation_builder.py:553, in SimulationBuilder.init_variable_values(self, entity, instance_object, instance_id)
    549     entity.check_variable_defined_for_entity(variable_name)
    550 except (
    551     ValueError
    552 ) as e:  # The variable is defined for another entity
--> 553     raise SituationParsingError(path_in_json, e.args[0])
    554 except VariableNotFoundError as e:  # The variable doesn't exist
    555     raise SituationParsingError(path_in_json, str(e), code=404)

SituationParsingError: {'spm_units': {'spm_unit': {'ami': "You tried to compute the variable 'ami' for the entity 'spm_units'; however the variable 'ami' is defined for 'households'. Learn more about entities in our documentation: <https://openfisca.org/doc/coding-the-legislation/50_entities.html>."}}}

What if their earnings change? They receive their full rent of $1,000 if they have zero earnings, and the benefit phases out at 30 cents per dollar of earnings, until it fully phases out at $3,333 monthly earnings.

sim_emp.vary("employment_income", max=4_000 * 12, step=120)

import plotly.express as px

LABELS = dict(
    employment_income="Monthly employment income",
    dividend_income="Monthly dividend income",
    income="Monthly income",
    income_source="Income source",
    housing_cost="Monthly housing cost",
    housing_assistance="Monthly housing assistance",
    hap_mtr="Marginal tax rate from HAP",
    housing_assistance_mtr="Marginal tax rate from housing assistance",
    hap="Housing assistance payment (if eligible)",
)

emp_df_full = pd.DataFrame(
    dict(
        employment_income=sim_emp.calc("employment_income")[0] / 12,
        hap=sim_emp.calc("hud_hap")[0] / 12,
        housing_assistance=sim_emp.calc("housing_assistance")[0] / 12,
        hap_mtr=-sim_emp.deriv("hud_hap", "employment_income"),
        housing_assistance_mtr=-sim_emp.deriv(
            "housing_assistance", "employment_income"
        ),
    )
)

fig = px.line(
    emp_df_full,
    "employment_income",
    "housing_assistance",
    labels=LABELS,
    title="Housing assistance for a one-person household in California with $1,000 monthly housing costs",
)
fig.update_layout(xaxis_tickformat="$,", yaxis_tickformat="$,")
fig.show()

We can also view their marginal tax rate from the program, revealing housing assistance’s 30% phase-out rate.

fig = px.line(
    emp_df_full,
    "employment_income",
    "housing_assistance_mtr",
    labels=LABELS,
    title="Housing assistance MTR for a one-person household in California with $1,000 monthly housing costs",
)
fig.update_layout(
    xaxis_tickformat="$,", yaxis_tickformat=".0%", yaxis_range=[0, 1]
)
fig.show()