From 19d8efad44d88d68f6fc785ec10643dd5519ee2a Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Tue, 17 Sep 2024 13:43:45 -0600 Subject: [PATCH 01/55] Add translation override base calculator --- .../programs/translation_overrides/base.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 programs/programs/translation_overrides/base.py diff --git a/programs/programs/translation_overrides/base.py b/programs/programs/translation_overrides/base.py new file mode 100644 index 00000000..faa031a5 --- /dev/null +++ b/programs/programs/translation_overrides/base.py @@ -0,0 +1,34 @@ +# TODO: add translation override model +from programs.util import Dependencies +from screener.models import Screen + +class TranslationOverrideCalculator: + dependencies = tuple() + + def _init_(self, screen: Screen, translation_override, missing_dependencies: Dependencies): + self.screen = screen + self.translation_override = translation_override + self.missing_dependencies = missing_dependencies + + def calc(self) -> bool: + """ + Return if the translation should be overridden + """ + if not self.can_calc(): + return False + + return self.eligible() + + def eligible(self) -> bool: + """ + Custom requirement for whether or not to override the Translation + """ + return True + + def can_calc(self) -> bool: + """ + Returns whether or not we can calculate if a translation can be overriden + """ + return not self.missing_dependencies.has(*self.dependencies) + + # TODO: county eligible method after the model is defined From 6ba152bb4bbd12a32c1d4f16c9d0c5a64a9ec0c9 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Tue, 17 Sep 2024 13:58:34 -0600 Subject: [PATCH 02/55] Fix typo --- programs/programs/warnings/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/programs/warnings/__init__.py b/programs/programs/warnings/__init__.py index b0899cf9..712cfa86 100644 --- a/programs/programs/warnings/__init__.py +++ b/programs/programs/warnings/__init__.py @@ -9,6 +9,6 @@ "_tax_unit": TaxUnit, } -specific_caculators: dict[str, type[WarningCalculator]] = {} +specific_calculators: dict[str, type[WarningCalculator]] = {} -warning_calculators: dict[str, type[WarningCalculator]] = {**general_calculators, **specific_caculators} +warning_calculators: dict[str, type[WarningCalculator]] = {**general_calculators, **specific_calculators} From 49800be30ce9706ea6d3e01f7ca91fa3ebdae817 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Tue, 17 Sep 2024 13:59:07 -0600 Subject: [PATCH 03/55] Add an init file to export the override calculators --- programs/programs/translation_overrides/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 programs/programs/translation_overrides/__init__.py diff --git a/programs/programs/translation_overrides/__init__.py b/programs/programs/translation_overrides/__init__.py new file mode 100644 index 00000000..00c3ba3c --- /dev/null +++ b/programs/programs/translation_overrides/__init__.py @@ -0,0 +1,11 @@ +from programs.programs.translation_overrides.dont_show import DontShow +from .base import TranslationOverrideCalculator + +general_calculators: dict[str, type[TranslationOverrideCalculator]] = { + "_show": TranslationOverrideCalculator, + "_dont_show": DontShow, +} + +specific_calculators: dict[str, type[TranslationOverrideCalculator]] = {} + +warning_calculators: dict[str, type[TranslationOverrideCalculator]] = {**general_calculators, **specific_calculators} \ No newline at end of file From a7476c1818ec54018aef3d1efbbb28a7312c0911 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Tue, 17 Sep 2024 13:59:49 -0600 Subject: [PATCH 04/55] Add a DontShow translation override calculator --- programs/programs/translation_overrides/dont_show.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 programs/programs/translation_overrides/dont_show.py diff --git a/programs/programs/translation_overrides/dont_show.py b/programs/programs/translation_overrides/dont_show.py new file mode 100644 index 00000000..a9269f6d --- /dev/null +++ b/programs/programs/translation_overrides/dont_show.py @@ -0,0 +1,9 @@ +from programs.programs.translation_overrides.base import TranslationOverrideCalculator + + +class DontShow(TranslationOverrideCalculator): + def eligible(self) -> bool: + ''' + Never use this override + ''' + return False \ No newline at end of file From 39e791fdbb81f07572731ab3ffa1c2f468dbb9c9 Mon Sep 17 00:00:00 2001 From: Caleb Date: Wed, 18 Sep 2024 11:15:34 -0600 Subject: [PATCH 05/55] account for members in the program calculator --- programs/models.py | 17 ++----- programs/programs/calc.py | 101 +++++++++++++++++++++++++++++++++----- 2 files changed, 93 insertions(+), 25 deletions(-) diff --git a/programs/models.py b/programs/models.py index 93e67977..d68edb07 100644 --- a/programs/models.py +++ b/programs/models.py @@ -1,13 +1,12 @@ -from logging import warn from django.db import models from phonenumber_field.modelfields import PhoneNumberField from translations.model_data import ModelDataController from translations.models import Translation from programs.programs import calculators -from programs.util import Dependencies, DependencyError +from programs.util import Dependencies import requests from integrations.util.cache import Cache -from typing import Optional, Type, TypedDict +from typing import Optional, TypedDict class FplCache(Cache): @@ -307,17 +306,9 @@ class Program(models.Model): def eligibility(self, screen, data, missing_dependencies: Dependencies): Calculator = calculators[self.name_abbreviated.lower()] - if not Calculator.can_calc(missing_dependencies): - raise DependencyError() + calculator = Calculator(screen, self, data, missing_dependencies) - calculator = Calculator(screen, self, data) - - eligibility = calculator.eligible() - - eligibility.value = calculator.value(eligibility.eligible_member_count) - - if Calculator.tax_unit_dependent and screen.has_members_outside_of_tax_unit(): - eligibility.multiple_tax_units = True + eligibility = calculator.calc() return eligibility.to_dict() diff --git a/programs/programs/calc.py b/programs/programs/calc.py index 0498b0c2..e5cdbb1c 100644 --- a/programs/programs/calc.py +++ b/programs/programs/calc.py @@ -1,19 +1,34 @@ -from screener.models import Screen -from programs.util import Dependencies +from screener.models import Screen, HouseholdMember +from programs.util import Dependencies, DependencyError from typing import TYPE_CHECKING if TYPE_CHECKING: from programs.models import Program +class MemberEligibility: + def __init__(self, member: HouseholdMember) -> None: + self.member = member + self.eligible = True + self.value = 0 + + def condition(self, passed: bool): + """ + Set eligibility to False if the condition does not pass + """ + if not passed: + self.eligible = False + + class Eligibility: def __init__(self): - self.eligible = True + self.eligible: bool = True self.pass_messages = [] self.fail_messages = [] - self.value = 0 - self.eligible_member_count = 0 - self.multiple_tax_units = False + self.eligible_members: list[MemberEligibility] = [] + self.value: int = 0 + self.eligible_member_count: int = 0 + self.multiple_tax_units: bool = False def condition(self, passed: bool, message=None): """ @@ -43,6 +58,12 @@ def passed(self, msg): """ self.pass_messages.append(msg) + def add_member_eligibility(self, member_eligibility: MemberEligibility): + """ + Store a members eligibility + """ + self.eligible_members.append(member_eligibility) + def member_eligibility(self, members, conditions): """ Filter out members that do not meet the condition and make eligibility messages @@ -81,28 +102,84 @@ class ProgramCalculator: dependencies = tuple() amount = 0 - tax_unit_dependent = False + member_amount = 0 - def __init__(self, screen: Screen, program: "Program", data): + def __init__(self, screen: Screen, program: "Program", data, missing_dependencies: Dependencies): self.screen = screen self.program = program self.data = data + self.missing_dependencies = missing_dependencies def eligible(self) -> Eligibility: + """ + Combine the eligibility for the household and the members + """ + e = self.household_eligible() + + one_member_eligible = False + for member in self.screen.household_members.all(): + member_eligibility = self.member_eligible(member) + e.add_member_eligibility(member_eligibility) + + if member_eligibility.eligible: + one_member_eligible = True + + e.condition(one_member_eligible) + + return e + + def household_eligible(self) -> Eligibility: """ Returns the `Eligibility` object with whether or not the program is eligible """ return Eligibility() - def value(self, eligible_members: int): + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + return MemberEligibility(member) + + def value(self, eligibility: Eligibility): + """ + Update the eligibility with household and member values + """ + total = 0 + if eligibility.eligible: + total += self.household_value() + + for member_eligibility in eligibility.eligible_members: + if member_eligibility.eligible: + member_value = self.member_value(member_eligibility.member) + member_eligibility.value = member_value + total += member_value + + eligibility.value = total + + def household_value(self): """ Return the value of the program """ return self.amount - @classmethod - def can_calc(cls, missing_dependencies: Dependencies): + def member_value(self, member: HouseholdMember): + """ + An eligible household members eligibility + """ + return self.member_amount + + def calc(self): + """ + Calculate the eligibility and value for a screen + """ + if not self.can_calc(): + raise DependencyError() + + eligibility = self.eligible() + + self.value(eligibility) + + return eligibility + + def can_calc(self): """ Returns whether or not the program can be calculated with the missing dependencies """ - return not missing_dependencies.has(*cls.dependencies) + return not self.missing_dependencies.has(*self.dependencies) From e82d4eb2f13f49a6028ce132c1e9b9a050abeff4 Mon Sep 17 00:00:00 2001 From: ming Date: Wed, 18 Sep 2024 16:30:00 -0700 Subject: [PATCH 06/55] Use NcTanf from PE --- programs/programs/co/pe/__init__.py | 6 +++++ programs/programs/co/pe/spm.py | 12 ++++++++++ programs/programs/federal/pe/spm.py | 5 +--- programs/programs/nc/pe/__init__.py | 4 +++- programs/programs/nc/pe/spm.py | 11 +++++++++ .../policyengine/calculators/__init__.py | 6 +++-- .../calculators/dependencies/spm.py | 23 +++++++++++++++++-- screener/models.py | 1 + 8 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 programs/programs/nc/pe/spm.py diff --git a/programs/programs/co/pe/__init__.py b/programs/programs/co/pe/__init__.py index 4c6863e5..5b1c1323 100644 --- a/programs/programs/co/pe/__init__.py +++ b/programs/programs/co/pe/__init__.py @@ -1,5 +1,6 @@ import programs.programs.co.pe.tax as tax import programs.programs.co.pe.member as member +import programs.programs.co.pe.spm as spm from programs.programs.policyengine.calculators.base import PolicyEngineCalulator @@ -17,7 +18,12 @@ "coctc": tax.Coctc, } +co_spm_calculators = { + "co_tanf": spm.CoTanf, +} + co_pe_calculators: dict[str, type[PolicyEngineCalulator]] = { **co_member_calculators, **co_tax_unit_calculators, + **co_spm_calculators, } diff --git a/programs/programs/co/pe/spm.py b/programs/programs/co/pe/spm.py index e69de29b..6a2451cf 100644 --- a/programs/programs/co/pe/spm.py +++ b/programs/programs/co/pe/spm.py @@ -0,0 +1,12 @@ +import programs.programs.policyengine.calculators.dependencies as dependency +from programs.programs.federal.pe.spm import Tanf + +class CoTanf(Tanf): + pe_name = "co_tanf" + pe_inputs = [ + *Tanf.pe_inputs, + dependency.household.CoStateCode, + dependency.member.PregnancyDependency, + dependency.spm.TanfCountableGrossIncomeDependency, + dependency.spm.TanfCountableGrossUnearnedIncomeDependency, + ] diff --git a/programs/programs/federal/pe/spm.py b/programs/programs/federal/pe/spm.py index 1041703a..096b0d56 100644 --- a/programs/programs/federal/pe/spm.py +++ b/programs/programs/federal/pe/spm.py @@ -50,13 +50,10 @@ def value(self): class Tanf(PolicyEngineSpmCalulator): - pe_name = "co_tanf" + pe_name = "tanf" pe_inputs = [ dependency.member.AgeDependency, - dependency.member.PregnancyDependency, dependency.member.FullTimeCollegeStudentDependency, - dependency.spm.TanfCountableGrossIncomeDependency, - dependency.spm.TanfCountableGrossUnearnedIncomeDependency, ] pe_outputs = [dependency.spm.Tanf] diff --git a/programs/programs/nc/pe/__init__.py b/programs/programs/nc/pe/__init__.py index f215fd61..0d11b4f8 100644 --- a/programs/programs/nc/pe/__init__.py +++ b/programs/programs/nc/pe/__init__.py @@ -1,10 +1,12 @@ import programs.programs.nc.pe.member as member +import programs.programs.nc.pe.spm as spm from programs.programs.policyengine.calculators.base import PolicyEngineCalulator nc_member_calculators = {"nc_medicaid": member.NcMedicaid, "nc_wic": member.NcWic} - +nc_spm_calculators = {"nc_tanf": spm.NcTanf} nc_pe_calculators: dict[str, type[PolicyEngineCalulator]] = { **nc_member_calculators, + **nc_spm_calculators, } diff --git a/programs/programs/nc/pe/spm.py b/programs/programs/nc/pe/spm.py new file mode 100644 index 00000000..a2d9377a --- /dev/null +++ b/programs/programs/nc/pe/spm.py @@ -0,0 +1,11 @@ +import programs.programs.policyengine.calculators.dependencies as dependency +from programs.programs.federal.pe.spm import Tanf + +class NcTanf(Tanf): + pe_name = "nc_tanf" + pe_inputs = [ + *Tanf.pe_inputs, + dependency.household.NcStateCode, + dependency.spm.NcTanfCountableEarnedIncomeDependency, + dependency.spm.NcTanfCountableGrossUnearnedIncomeDependency, + ] diff --git a/programs/programs/policyengine/calculators/__init__.py b/programs/programs/policyengine/calculators/__init__.py index 7adce32a..94e97eb9 100644 --- a/programs/programs/policyengine/calculators/__init__.py +++ b/programs/programs/policyengine/calculators/__init__.py @@ -3,8 +3,8 @@ federal_spm_unit_calculators, federal_tax_unit_calculators, ) -from programs.programs.co.pe import co_member_calculators, co_tax_unit_calculators -from programs.programs.nc.pe import nc_member_calculators +from programs.programs.co.pe import co_member_calculators, co_tax_unit_calculators, co_spm_calculators +from programs.programs.nc.pe import nc_member_calculators, nc_spm_calculators from .base import ( PolicyEngineMembersCalculator, PolicyEngineSpmCalulator, @@ -21,6 +21,8 @@ all_spm_unit_calculators: dict[str, type[PolicyEngineSpmCalulator]] = { **federal_spm_unit_calculators, + **co_spm_calculators, + **nc_spm_calculators, } all_tax_unit_calculators: dict[str, type[PolicyEngineTaxUnitCalulator]] = { diff --git a/programs/programs/policyengine/calculators/dependencies/spm.py b/programs/programs/policyengine/calculators/dependencies/spm.py index fa59adcd..727a6374 100644 --- a/programs/programs/policyengine/calculators/dependencies/spm.py +++ b/programs/programs/policyengine/calculators/dependencies/spm.py @@ -152,6 +152,8 @@ class SchoolMealTier(SpmUnit): class Lifeline(SpmUnit): field = "lifeline" +class Tanf(SpmUnit): + field = "tanf" class TanfCountableGrossIncomeDependency(SpmUnit): field = "co_tanf_countable_gross_earned_income" @@ -176,10 +178,27 @@ class TanfCountableGrossUnearnedIncomeDependency(SpmUnit): def value(self): return int(self.screen.calc_gross_income("yearly", ["unearned"])) +class NcTanfCountableEarnedIncomeDependency(SpmUnit): + field = "nc_tanf_countable_earned_income" + dependencies = ( + "income_type", + "income_amount", + "income_frequency", + ) -class Tanf(SpmUnit): - field = "co_tanf" + def value(self): + return int(self.screen.calc_gross_income("yearly", ["earned"])) +class NcTanfCountableGrossUnearnedIncomeDependency(SpmUnit): + field = "nc_tanf_countable_gross_unearned_income" + dependencies = ( + "income_type", + "income_amount", + "income_frequency", + ) + + def value(self): + return int(self.screen.calc_gross_income("yearly", ["unearned"])) class BroadbandCostDependency(SpmUnit): field = "broadband_cost" diff --git a/screener/models.py b/screener/models.py index 868daa44..e1879230 100644 --- a/screener/models.py +++ b/screener/models.py @@ -263,6 +263,7 @@ def has_insurance_types(self, types, strict=True): def has_benefit(self, name_abbreviated): name_map = { "tanf": self.has_tanf, + "nc_tanf": self.has_tanf, "wic": self.has_wic, "nc_wic": self.has_wic, "snap": self.has_snap, From af1b6f54efeff37207a98d9745681a5b8ef88c79 Mon Sep 17 00:00:00 2001 From: ming Date: Wed, 18 Sep 2024 16:33:58 -0700 Subject: [PATCH 07/55] format files --- programs/programs/co/pe/spm.py | 1 + programs/programs/nc/pe/spm.py | 1 + .../programs/policyengine/calculators/dependencies/spm.py | 5 +++++ programs/programs/policyengine/engines.py | 4 +++- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/programs/programs/co/pe/spm.py b/programs/programs/co/pe/spm.py index 6a2451cf..2503b486 100644 --- a/programs/programs/co/pe/spm.py +++ b/programs/programs/co/pe/spm.py @@ -1,6 +1,7 @@ import programs.programs.policyengine.calculators.dependencies as dependency from programs.programs.federal.pe.spm import Tanf + class CoTanf(Tanf): pe_name = "co_tanf" pe_inputs = [ diff --git a/programs/programs/nc/pe/spm.py b/programs/programs/nc/pe/spm.py index a2d9377a..5e043f21 100644 --- a/programs/programs/nc/pe/spm.py +++ b/programs/programs/nc/pe/spm.py @@ -1,6 +1,7 @@ import programs.programs.policyengine.calculators.dependencies as dependency from programs.programs.federal.pe.spm import Tanf + class NcTanf(Tanf): pe_name = "nc_tanf" pe_inputs = [ diff --git a/programs/programs/policyengine/calculators/dependencies/spm.py b/programs/programs/policyengine/calculators/dependencies/spm.py index 727a6374..35e421de 100644 --- a/programs/programs/policyengine/calculators/dependencies/spm.py +++ b/programs/programs/policyengine/calculators/dependencies/spm.py @@ -152,9 +152,11 @@ class SchoolMealTier(SpmUnit): class Lifeline(SpmUnit): field = "lifeline" + class Tanf(SpmUnit): field = "tanf" + class TanfCountableGrossIncomeDependency(SpmUnit): field = "co_tanf_countable_gross_earned_income" dependencies = ( @@ -178,6 +180,7 @@ class TanfCountableGrossUnearnedIncomeDependency(SpmUnit): def value(self): return int(self.screen.calc_gross_income("yearly", ["unearned"])) + class NcTanfCountableEarnedIncomeDependency(SpmUnit): field = "nc_tanf_countable_earned_income" dependencies = ( @@ -189,6 +192,7 @@ class NcTanfCountableEarnedIncomeDependency(SpmUnit): def value(self): return int(self.screen.calc_gross_income("yearly", ["earned"])) + class NcTanfCountableGrossUnearnedIncomeDependency(SpmUnit): field = "nc_tanf_countable_gross_unearned_income" dependencies = ( @@ -200,6 +204,7 @@ class NcTanfCountableGrossUnearnedIncomeDependency(SpmUnit): def value(self): return int(self.screen.calc_gross_income("yearly", ["unearned"])) + class BroadbandCostDependency(SpmUnit): field = "broadband_cost" diff --git a/programs/programs/policyengine/engines.py b/programs/programs/policyengine/engines.py index 4e117681..062f381d 100644 --- a/programs/programs/policyengine/engines.py +++ b/programs/programs/policyengine/engines.py @@ -24,11 +24,13 @@ def members(self, unit, sub_unit): class ApiSim(Sim): method_name = "Policy Engine API" - pe_url = "https://api.policyengine.org/us/calculate" + # pe_url = "https://api.policyengine.org/us/calculate" + pe_url = "http://127.0.0.1:5000/us/calculate" def __init__(self, data) -> None: response = requests.post(self.pe_url, json=data) self.data = response.json()["result"] + print(f"~~~!!!data", self.data) def value(self, unit, sub_unit, variable, period): return self.data[unit][sub_unit][variable][period] From 55b839736867aab9ae80ef54b381f4e73e04c4ce Mon Sep 17 00:00:00 2001 From: ming Date: Wed, 18 Sep 2024 16:46:01 -0700 Subject: [PATCH 08/55] remove one not related file --- programs/programs/policyengine/engines.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/programs/programs/policyengine/engines.py b/programs/programs/policyengine/engines.py index 062f381d..4e117681 100644 --- a/programs/programs/policyengine/engines.py +++ b/programs/programs/policyengine/engines.py @@ -24,13 +24,11 @@ def members(self, unit, sub_unit): class ApiSim(Sim): method_name = "Policy Engine API" - # pe_url = "https://api.policyengine.org/us/calculate" - pe_url = "http://127.0.0.1:5000/us/calculate" + pe_url = "https://api.policyengine.org/us/calculate" def __init__(self, data) -> None: response = requests.post(self.pe_url, json=data) self.data = response.json()["result"] - print(f"~~~!!!data", self.data) def value(self, unit, sub_unit, variable, period): return self.data[unit][sub_unit][variable][period] From ef68656b5b867e7818e1cb7911cef9bc551f1b3c Mon Sep 17 00:00:00 2001 From: ming Date: Wed, 18 Sep 2024 20:08:54 -0700 Subject: [PATCH 09/55] add state related calculators --- programs/programs/co/pe/spm.py | 2 ++ programs/programs/nc/pe/spm.py | 2 ++ .../programs/policyengine/calculators/dependencies/spm.py | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/programs/programs/co/pe/spm.py b/programs/programs/co/pe/spm.py index 2503b486..26a89e81 100644 --- a/programs/programs/co/pe/spm.py +++ b/programs/programs/co/pe/spm.py @@ -11,3 +11,5 @@ class CoTanf(Tanf): dependency.spm.TanfCountableGrossIncomeDependency, dependency.spm.TanfCountableGrossUnearnedIncomeDependency, ] + + pe_outputs = [dependency.spm.CoTanf] diff --git a/programs/programs/nc/pe/spm.py b/programs/programs/nc/pe/spm.py index 5e043f21..7e7820e3 100644 --- a/programs/programs/nc/pe/spm.py +++ b/programs/programs/nc/pe/spm.py @@ -10,3 +10,5 @@ class NcTanf(Tanf): dependency.spm.NcTanfCountableEarnedIncomeDependency, dependency.spm.NcTanfCountableGrossUnearnedIncomeDependency, ] + + pe_outputs = [dependency.spm.NcTanf] diff --git a/programs/programs/policyengine/calculators/dependencies/spm.py b/programs/programs/policyengine/calculators/dependencies/spm.py index 35e421de..4aa85240 100644 --- a/programs/programs/policyengine/calculators/dependencies/spm.py +++ b/programs/programs/policyengine/calculators/dependencies/spm.py @@ -157,6 +157,14 @@ class Tanf(SpmUnit): field = "tanf" +class CoTanf(SpmUnit): + field = "co_tanf" + + +class NcTanf(SpmUnit): + field = "nc_tanf" + + class TanfCountableGrossIncomeDependency(SpmUnit): field = "co_tanf_countable_gross_earned_income" dependencies = ( From b1fd67da8809ed6d88b09bf3badbfe9c9411c5c2 Mon Sep 17 00:00:00 2001 From: Ming K Date: Fri, 20 Sep 2024 10:31:33 -0700 Subject: [PATCH 10/55] Apply suggestions from code review Co-authored-by: CalebPena <62856626+CalebPena@users.noreply.github.com> --- programs/programs/co/pe/spm.py | 4 ++-- screener/models.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/programs/programs/co/pe/spm.py b/programs/programs/co/pe/spm.py index 26a89e81..0ece8996 100644 --- a/programs/programs/co/pe/spm.py +++ b/programs/programs/co/pe/spm.py @@ -8,8 +8,8 @@ class CoTanf(Tanf): *Tanf.pe_inputs, dependency.household.CoStateCode, dependency.member.PregnancyDependency, - dependency.spm.TanfCountableGrossIncomeDependency, - dependency.spm.TanfCountableGrossUnearnedIncomeDependency, + dependency.spm.CoTanfCountableGrossIncomeDependency, + dependency.spm.CoTanfCountableGrossUnearnedIncomeDependency, ] pe_outputs = [dependency.spm.CoTanf] diff --git a/screener/models.py b/screener/models.py index e1879230..fd27011f 100644 --- a/screener/models.py +++ b/screener/models.py @@ -264,6 +264,7 @@ def has_benefit(self, name_abbreviated): name_map = { "tanf": self.has_tanf, "nc_tanf": self.has_tanf, + "co_tanf": self.has_tanf, "wic": self.has_wic, "nc_wic": self.has_wic, "snap": self.has_snap, From 5e6f1836d1aae8ab0173155ffd9a26574ef0ddd0 Mon Sep 17 00:00:00 2001 From: ming Date: Fri, 20 Sep 2024 10:32:19 -0700 Subject: [PATCH 11/55] update dependency names --- .../programs/policyengine/calculators/dependencies/spm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/programs/policyengine/calculators/dependencies/spm.py b/programs/programs/policyengine/calculators/dependencies/spm.py index 4aa85240..453d99a6 100644 --- a/programs/programs/policyengine/calculators/dependencies/spm.py +++ b/programs/programs/policyengine/calculators/dependencies/spm.py @@ -165,7 +165,7 @@ class NcTanf(SpmUnit): field = "nc_tanf" -class TanfCountableGrossIncomeDependency(SpmUnit): +class CoTanfCountableGrossIncomeDependency(SpmUnit): field = "co_tanf_countable_gross_earned_income" dependencies = ( "income_type", @@ -177,7 +177,7 @@ def value(self): return int(self.screen.calc_gross_income("yearly", ["earned"])) -class TanfCountableGrossUnearnedIncomeDependency(SpmUnit): +class CoTanfCountableGrossUnearnedIncomeDependency(SpmUnit): field = "co_tanf_countable_gross_unearned_income" dependencies = ( "income_type", From 4b69fdb9896fa225a18c95e55f04a81f82a4a3d8 Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 23 Sep 2024 16:18:07 -0600 Subject: [PATCH 12/55] add member eligibility --- programs/co_county_zips.py | 10 +++ programs/programs/calc.py | 11 ++- programs/programs/co/__init__.py | 2 - .../co/basic_cash_assistance/calculator.py | 10 +-- programs/programs/co/cash_back/calculator.py | 19 ++-- .../co/child_care_assistance/calculator.py | 87 +++++++++---------- .../co/connect_for_health/calculator.py | 42 +++++---- .../dental_health_care_seniors/calculator.py | 33 ++++--- .../co/denver_preschool_program/calculator.py | 28 +++--- .../denver_property_tax_relief/calculator.py | 56 ++++++------ .../co/denver_trash_rebate/calculator.py | 12 ++- .../emergency_rental_assistance/calculator.py | 18 ++-- .../co/energy_assistance/calculator.py | 16 ++-- .../co/energy_resource_center/calculator.py | 2 +- .../programs/co/every_day_eats/__init__.py | 0 .../programs/co/every_day_eats/calculator.py | 25 ------ programs/programs/co/every_day_eats/tests.py | 47 ---------- .../co/low_wage_covid_relief/calculator.py | 9 +- .../adult_with_disability/calculator.py | 58 ++++++------- .../child_with_disability/calculator.py | 38 +++++--- .../co/medicaid/emergency/calculator.py | 15 ++-- .../family_planning_services/calculator.py | 30 ++++--- programs/programs/co/my_spark/calculator.py | 32 +++---- programs/programs/co/mydenver/calculator.py | 43 ++++----- programs/programs/co/nfp/calculator.py | 53 ----------- .../co/nurse_family_partnership/calculator.py | 34 ++++---- .../co/nurturing_futures/calculator.py | 12 +-- programs/programs/co/omnisalud/calculator.py | 19 ++-- .../co/property_credit_rebate/calculator.py | 37 ++++---- .../co/rental_assistance_grant/calculator.py | 22 +++-- .../co/reproductive_health_care/calculator.py | 18 ++-- programs/programs/co/rtdlive/calculator.py | 80 +++++++---------- programs/programs/co/tabor/calculator.py | 18 ++-- programs/programs/co/trua/calculator.py | 27 ++---- .../co/universal_preschool/calculator.py | 62 +++++-------- .../co/utility_bill_pay/calculator.py | 2 +- .../weatherization_assistance/calculator.py | 4 +- .../programs/federal/head_start/calculator.py | 30 +++---- .../federal/medicare_savings/calculator.py | 68 ++++++--------- programs/programs/federal/ssdi/calculator.py | 74 +++++++--------- programs/programs/nc/nc_aca/calculator.py | 34 ++++---- 41 files changed, 522 insertions(+), 715 deletions(-) delete mode 100644 programs/programs/co/every_day_eats/__init__.py delete mode 100644 programs/programs/co/every_day_eats/calculator.py delete mode 100644 programs/programs/co/every_day_eats/tests.py delete mode 100644 programs/programs/co/nfp/calculator.py diff --git a/programs/co_county_zips.py b/programs/co_county_zips.py index bcb3f762..9e1ce657 100644 --- a/programs/co_county_zips.py +++ b/programs/co_county_zips.py @@ -1,3 +1,6 @@ +from screener.models import Screen + + def counties_from_zip(lookup_zip): matches = [] @@ -9,6 +12,13 @@ def counties_from_zip(lookup_zip): return matches +def counties_from_screen(screen: Screen): + if screen.county is not None: + return [screen.county] + + return counties_from_zip(screen.zipcode) + + zipcodes = { "Adams County": [ "80023", diff --git a/programs/programs/calc.py b/programs/programs/calc.py index e5cdbb1c..1ba32666 100644 --- a/programs/programs/calc.py +++ b/programs/programs/calc.py @@ -141,9 +141,12 @@ def value(self, eligibility: Eligibility): """ Update the eligibility with household and member values """ - total = 0 - if eligibility.eligible: - total += self.household_value() + if not eligibility.eligible: + # if the household is not eligible, the program has 0 value + eligibility.value = 0 + return + + total = self.household_value() for member_eligibility in eligibility.eligible_members: if member_eligibility.eligible: @@ -155,7 +158,7 @@ def value(self, eligibility: Eligibility): def household_value(self): """ - Return the value of the program + Return the value of the program for the household """ return self.amount diff --git a/programs/programs/co/__init__.py b/programs/programs/co/__init__.py index e61a2d8f..750670c6 100644 --- a/programs/programs/co/__init__.py +++ b/programs/programs/co/__init__.py @@ -11,7 +11,6 @@ from .connect_for_health.calculator import ConnectForHealth from .medicaid.family_planning_services.calculator import FamilyPlanningServices from .denver_preschool_program.calculator import DenverPreschoolProgram -from .every_day_eats.calculator import EveryDayEats from .property_credit_rebate.calculator import PropertyCreditRebate from .universal_preschool.calculator import UniversalPreschool from .my_spark.calculator import MySpark @@ -45,7 +44,6 @@ "cfhc": ConnectForHealth, "fps": FamilyPlanningServices, "dpp": DenverPreschoolProgram, - "ede": EveryDayEats, "cpcr": PropertyCreditRebate, "upk": UniversalPreschool, "myspark": MySpark, diff --git a/programs/programs/co/basic_cash_assistance/calculator.py b/programs/programs/co/basic_cash_assistance/calculator.py index 6d8e0710..498c3ed7 100644 --- a/programs/programs/co/basic_cash_assistance/calculator.py +++ b/programs/programs/co/basic_cash_assistance/calculator.py @@ -1,6 +1,6 @@ import programs.programs.messages as messages from programs.programs.calc import Eligibility, ProgramCalculator -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen, counties_from_zip class BasicCashAssistance(ProgramCalculator): @@ -8,14 +8,10 @@ class BasicCashAssistance(ProgramCalculator): county = "Denver County" dependencies = ["zipcode", "age"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() - # Lives in Denver - if self.screen.county is not None: - counties = [self.screen.county] - else: - counties = counties_from_zip(self.screen.zipcode) + counties = counties_from_screen(self.screen) in_denver = BasicCashAssistance.county in counties e.condition(in_denver, messages.location()) diff --git a/programs/programs/co/cash_back/calculator.py b/programs/programs/co/cash_back/calculator.py index 7c4fd16c..fbe3f403 100644 --- a/programs/programs/co/cash_back/calculator.py +++ b/programs/programs/co/cash_back/calculator.py @@ -1,20 +1,15 @@ -import programs.programs.messages as messages -from programs.programs.calc import ProgramCalculator, Eligibility +from screener.models import HouseholdMember +from programs.programs.calc import MemberEligibility, ProgramCalculator class CashBack(ProgramCalculator): - amount = 750 + member_amount = 750 + min_age = 18 dependencies = ["age"] - def eligible(self) -> Eligibility: - e = Eligibility() + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) - adults = self.screen.num_adults(age_max=18) - e.condition(adults > 0, messages.older_than(18)) + e.condition(member.age >= CashBack.min_age) return e - - def value(self, eligible_members: int): - adults = self.screen.num_adults(age_max=18) - value = adults * 750 - return value diff --git a/programs/programs/co/child_care_assistance/calculator.py b/programs/programs/co/child_care_assistance/calculator.py index 1254874f..4469da83 100644 --- a/programs/programs/co/child_care_assistance/calculator.py +++ b/programs/programs/co/child_care_assistance/calculator.py @@ -1,6 +1,7 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from screener.models import HouseholdMember +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from integrations.services.sheets import GoogleSheetsCache -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen, counties_from_zip import programs.programs.messages as messages @@ -27,20 +28,20 @@ class ChildCareAssistance(ProgramCalculator): dependencies = ["age", "income_amount", "income_frequency", "zipcode", "household_size"] fpl_limits = CccapFplCache() - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() - # age - cccap_children = self._num_cccap_children() - - e.condition(cccap_children > 0, messages.child(max_age=ChildCareAssistance.max_age_afterschool)) - cccap_county_limits = self.fpl_limits.fetch() # location - counties = counties_from_zip(self.screen.zipcode) - county_name = self.screen.county if self.screen.county is not None else counties[0] - e.condition(county_name in cccap_county_limits, messages.location()) + counties = counties_from_screen(self.screen) + in_county_limits = False + county_name = counties[0] + for county in counties: + if county in cccap_county_limits: + in_county_limits = True + county_name = county + e.condition(in_county_limits, messages.location()) # income frequency = "yearly" @@ -61,36 +62,34 @@ def eligible(self) -> Eligibility: return e - def value(self, eligible_members: int): - value = 0 - - household_members = self.screen.household_members.all() - for household_member in household_members: - if household_member.age <= ChildCareAssistance.max_age_preschool: - value += ChildCareAssistance.preschool_value - elif household_member.age < ChildCareAssistance.max_age_afterschool: - value += ChildCareAssistance.afterschool_value - elif ( - household_member.age >= ChildCareAssistance.max_age_afterschool - and household_member.age <= ChildCareAssistance.max_age_afterschool_disabled - and household_member.has_disability() - ): - value += ChildCareAssistance.afterschool_value - - return value - - def _num_cccap_children(self): - children = 0 - - household_members = self.screen.household_members.all() - for household_member in household_members: - if household_member.age < ChildCareAssistance.max_age_afterschool: - children += 1 - elif ( - household_member.age >= ChildCareAssistance.max_age_afterschool - and household_member.age <= ChildCareAssistance.max_age_afterschool_disabled - and household_member.has_disability() - ): - children += 1 - - return children + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # age + child_eligible = False + if member.age < ChildCareAssistance.max_age_afterschool: + child_eligible = True + elif ( + member.age >= ChildCareAssistance.max_age_afterschool + and member.age <= ChildCareAssistance.max_age_afterschool_disabled + and member.has_disability() + ): + child_eligible = True + + e.condition(child_eligible) + + return e + + def member_value(self, member: HouseholdMember): + if member.age <= ChildCareAssistance.max_age_preschool: + return ChildCareAssistance.preschool_value + elif member.age < ChildCareAssistance.max_age_afterschool: + return ChildCareAssistance.afterschool_value + elif ( + member.age >= ChildCareAssistance.max_age_afterschool + and member.age <= ChildCareAssistance.max_age_afterschool_disabled + and member.has_disability() + ): + return ChildCareAssistance.afterschool_value + + return 0 diff --git a/programs/programs/co/connect_for_health/calculator.py b/programs/programs/co/connect_for_health/calculator.py index 663127e1..2b799572 100644 --- a/programs/programs/co/connect_for_health/calculator.py +++ b/programs/programs/co/connect_for_health/calculator.py @@ -1,6 +1,8 @@ from integrations.services.sheets.sheets import GoogleSheets from integrations.util.cache import Cache -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.co_county_zips import counties_from_screen +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility +from screener.models import HouseholdMember from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages @@ -21,35 +23,37 @@ def update(self): class ConnectForHealth(ProgramCalculator): percent_of_fpl = 4 - dependencies = ["insurance", "income_amount", "income_frequency", "county", "household_size"] + dependencies = ["insurance", "income_amount", "income_frequency", "zipcode", "household_size"] + eligible_insurance_types = ["none", "private"] + ineligible_insurance_types = ["va"] county_values = CFHCache() - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # Medicade eligibility e.condition(not medicaid_eligible(self.data), messages.must_not_have_benefit("Medicaid")) - # HH member has no insurace or private insurance - e.member_eligibility( - self.screen.household_members.all(), - [ - (lambda m: m.insurance.has_insurance_types(("none", "private")), messages.has_no_insurance()), - ( - lambda m: not m.insurance.has_insurance_types(("va",)), - messages.must_not_have_benefit("VA"), - ), - ], - ) - # Income fpl = self.program.fpl.as_dict() - income_band = int(fpl[self.screen.household_size] / 12 * ConnectForHealth.percent_of_fpl) - gross_income = int(self.screen.calc_gross_income("yearly", ("all",)) / 12) + income_band = int(fpl[self.screen.household_size] * ConnectForHealth.percent_of_fpl) + gross_income = int(self.screen.calc_gross_income("yearly", ("all",))) e.condition(gross_income < income_band, messages.income(gross_income, income_band)) return e - def value(self, eligible_members: int): + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # no or private insurance + e.condition(member.insurance.has_insurance_types(ConnectForHealth.eligible_insurance_types)) + + # no va insurance + e.condition(not member.insurance.has_insurance_types(ConnectForHealth.ineligible_insurance_types)) + + return e + + def member_value(self, member: HouseholdMember): values = self.county_values.fetch() - return int(values[self.screen.county] * 12 * eligible_members) + county = counties_from_screen(self.screen)[0] + return int(values[county] * 12) diff --git a/programs/programs/co/dental_health_care_seniors/calculator.py b/programs/programs/co/dental_health_care_seniors/calculator.py index d6f26352..7e3781dc 100644 --- a/programs/programs/co/dental_health_care_seniors/calculator.py +++ b/programs/programs/co/dental_health_care_seniors/calculator.py @@ -1,28 +1,17 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import ProgramCalculator, Eligibility, MemberEligibility +from screener.models import HouseholdMember import programs.programs.messages as messages class DentalHealthCareSeniors(ProgramCalculator): - amount = 80 + member_amount = 80 * 12 min_age = 60 percent_of_fpl = 2.5 + ineligible_insurance = ["medicaid", "private"] dependencies = ["age", "income_amount", "income_frequency", "insurance", "household_size"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() - e.member_eligibility( - self.screen.household_members.all(), - [ - ( - lambda m: m.insurance.has_insurance_types(("medicaid", "private")), - messages.must_not_have_benefit("Medicaid"), - ), - ( - lambda m: m.age > DentalHealthCareSeniors.min_age, - messages.older_than(DentalHealthCareSeniors.min_age), - ), - ], - ) # Income test fpl = self.program.fpl.as_dict() @@ -32,5 +21,13 @@ def eligible(self) -> Eligibility: return e - def value(self, eligible_members: int): - return DentalHealthCareSeniors.amount * self.screen.num_adults(age_max=DentalHealthCareSeniors.min_age) * 12 + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # insurance + e.condition(not member.insurance.has_insurance_types(DentalHealthCareSeniors.eligible_insurance)) + + # age + e.condition(member.age >= DentalHealthCareSeniors.min_age) + + return e diff --git a/programs/programs/co/denver_preschool_program/calculator.py b/programs/programs/co/denver_preschool_program/calculator.py index 4fcb9a8e..8ed5d7a7 100644 --- a/programs/programs/co/denver_preschool_program/calculator.py +++ b/programs/programs/co/denver_preschool_program/calculator.py @@ -1,31 +1,29 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility import programs.programs.messages as messages -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen +from screener.models import HouseholdMember class DenverPreschoolProgram(ProgramCalculator): - amount = 788 * 12 + member_amount = 788 * 12 min_age = 3 max_age = 4 county = "Denver County" dependencies = ["age", "zipcode"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() - # Has a preschool child - num_children = self.screen.num_children( - age_min=DenverPreschoolProgram.min_age, age_max=DenverPreschoolProgram.max_age - ) + # Lives in Denver + counties = counties_from_screen(self.screen) + e.condition(DenverPreschoolProgram.county in counties, messages.location()) - e.condition(num_children >= 1, messages.child(DenverPreschoolProgram.min_age, DenverPreschoolProgram.max_age)) + return e - if self.screen.county is not None: - counties = [self.screen.county] - else: - counties = counties_from_zip(self.screen.zipcode) + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) - # Lives in Denver - e.condition(DenverPreschoolProgram.county in counties, messages.location()) + # age + e.condition(DenverPreschoolProgram.min_age >= member.age >= DenverPreschoolProgram.max_age) return e diff --git a/programs/programs/co/denver_property_tax_relief/calculator.py b/programs/programs/co/denver_property_tax_relief/calculator.py index 6ad011be..7a1241c3 100644 --- a/programs/programs/co/denver_property_tax_relief/calculator.py +++ b/programs/programs/co/denver_property_tax_relief/calculator.py @@ -1,5 +1,5 @@ -from programs.co_county_zips import counties_from_zip -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.co_county_zips import counties_from_screen, counties_from_zip +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from integrations.services.sheets import GoogleSheetsCache import programs.programs.messages as messages from screener.models import HouseholdMember @@ -52,14 +52,11 @@ class DenverPropertyTaxRelief(ProgramCalculator): "relationship", ] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # county - if self.screen.county is not None: - counties = [self.screen.county] - else: - counties = counties_from_zip(self.screen.zipcode) + counties = counties_from_screen(self.screen) e.condition(DenverPropertyTaxRelief.county in counties, messages.location()) # has rent or mortgage expense @@ -67,23 +64,6 @@ def eligible(self) -> Eligibility: has_mortgage = self.screen.has_expense(["mortgage"]) e.condition(has_rent or has_mortgage) - has_child = self.screen.num_children(age_max=DenverPropertyTaxRelief.child_max_age) > 0 - - def meets_one_condition(member: HouseholdMember): - if has_mortgage and has_child: - return True - - if member.age >= DenverPropertyTaxRelief.age_eligible: - return True - - if member.disabled or self.screen.has_benefit("ssi") or self.screen.has_benefit("ssdi"): - return True - - return False - - members: list[HouseholdMember] = self.screen.household_members.all() - e.member_eligibility(members, [(lambda m: m.is_head() or m.is_spouse(), None), (meets_one_condition, None)]) - # income multiple_adults = self.screen.num_adults(DenverPropertyTaxRelief.child_max_age + 1) >= 2 ami_percent = -1 @@ -97,16 +77,40 @@ def meets_one_condition(member: HouseholdMember): ami = DenverPropertyTaxRelief.ami.fetch() limit = ami[self.screen.household_size - 1] * ami_percent total_income = 0 - for member in members: + for member in self.screen.household_members.all(): if member.is_head() or member.is_spouse(): total_income += member.calc_gross_income("yearly", DenverPropertyTaxRelief.income_types) e.condition(total_income <= limit, messages.income(total_income, limit)) return e - def value(self, eligible_members: int): + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # head or spouse + e.condition(member.is_head() or member.is_spouse()) + + has_child = self.screen.num_children(age_max=DenverPropertyTaxRelief.child_max_age) > 0 + + # other condition + other_condition = False + if self.screen.has_expense(["mortgage"]) and has_child: + other_condition = True + + if member.age >= DenverPropertyTaxRelief.age_eligible: + other_condition = True + + if member.disabled or self.screen.has_benefit("ssi") or self.screen.has_benefit("ssdi"): + other_condition = True + + e.condition(other_condition) + + return e + + def household_value(self): if self.screen.has_expense(["mortgage"]): return DenverPropertyTaxRelief.mortgage_amount elif self.screen.has_expense(["rent"]): return DenverPropertyTaxRelief.rent_amount + return 0 diff --git a/programs/programs/co/denver_trash_rebate/calculator.py b/programs/programs/co/denver_trash_rebate/calculator.py index d1c67e6d..b7fbf4cb 100644 --- a/programs/programs/co/denver_trash_rebate/calculator.py +++ b/programs/programs/co/denver_trash_rebate/calculator.py @@ -1,4 +1,4 @@ -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen from programs.programs.calc import ProgramCalculator, Eligibility from integrations.services.sheets import GoogleSheetsCache import programs.programs.messages as messages @@ -19,16 +19,14 @@ class DenverTrashRebate(ProgramCalculator): amount = 252 county = "Denver County" ami = DenverAmiCache() + expenses = ["rent", "mortgage"] dependencies = ["zipcode", "income_amount", "income_frequency", "household_size"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # county - if self.screen.county is not None: - counties = [self.screen.county] - else: - counties = counties_from_zip(self.screen.zipcode) + counties = counties_from_screen(self.screen) e.condition(DenverTrashRebate.county in counties, messages.location()) # income @@ -38,7 +36,7 @@ def eligible(self) -> Eligibility: e.condition(income <= limit, messages.income(income, limit)) # has rent or mortgage expense - has_rent_or_mortgage = self.screen.has_expense(["rent", "mortgage"]) + has_rent_or_mortgage = self.screen.has_expense([DenverTrashRebate.expenses]) e.condition(has_rent_or_mortgage) return e diff --git a/programs/programs/co/emergency_rental_assistance/calculator.py b/programs/programs/co/emergency_rental_assistance/calculator.py index 689f544b..548a5563 100644 --- a/programs/programs/co/emergency_rental_assistance/calculator.py +++ b/programs/programs/co/emergency_rental_assistance/calculator.py @@ -1,7 +1,7 @@ from programs.programs.calc import Eligibility, ProgramCalculator import programs.programs.messages as messages from integrations.services.sheets import GoogleSheetsCache -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen class EmergencyRentalAssistanceIncomeLimitsCache(GoogleSheetsCache): @@ -17,24 +17,30 @@ def update(self): class EmergencyRentalAssistance(ProgramCalculator): amount = 13_848 + expenses = ["rent"] dependencies = ["income_amount", "income_frequency", "household_size", "zipcode"] income_cache = EmergencyRentalAssistanceIncomeLimitsCache() - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # Income test - counties = counties_from_zip(self.screen.zipcode) - county_name = self.screen.county if self.screen.county is not None else counties[0] + income_limits = EmergencyRentalAssistance.income_cache.fetch() + + counties = counties_from_screen(self.screen) + county_name = counties[0] + for county in counties: + if county in income_limits: + county_name = county + break income = self.screen.calc_gross_income("yearly", ["all"]) - income_limits = EmergencyRentalAssistance.income_cache.fetch() # NOTE: 80% to income is already applied in the sheet. income_limit = income_limits[county_name][self.screen.household_size - 1] e.condition(income < income_limit, messages.income(income, income_limit)) # has rent expense - has_rent = self.screen.has_expense(["rent"]) + has_rent = self.screen.has_expense(EmergencyRentalAssistance.expenses) e.condition(has_rent) return e diff --git a/programs/programs/co/energy_assistance/calculator.py b/programs/programs/co/energy_assistance/calculator.py index 7f29dca9..1a5a278c 100644 --- a/programs/programs/co/energy_assistance/calculator.py +++ b/programs/programs/co/energy_assistance/calculator.py @@ -1,7 +1,7 @@ from integrations.services.sheets.sheets import GoogleSheetsCache from programs.programs.calc import ProgramCalculator, Eligibility import programs.programs.messages as messages -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen import math @@ -35,11 +35,12 @@ def update(self): class EnergyAssistance(ProgramCalculator): - dependencies = ["income_frequency", "income_amount", "zipcode", "household_size"] county_values = LeapValueCache() income_bands = LeapIncomeLimitCache() # monthly + expenses = ["rent", "mortgage"] + dependencies = ["income_frequency", "income_amount", "zipcode", "household_size"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # income @@ -51,19 +52,16 @@ def eligible(self) -> Eligibility: e.condition(leap_income <= income_limit, messages.income(leap_income, income_limit)) # has rent or mortgage expense - has_rent_or_mortgage = self.screen.has_expense(["rent", "mortgage"]) + has_rent_or_mortgage = self.screen.has_expense(EnergyAssistance.expenses) e.condition(has_rent_or_mortgage) return e - def value(self, eligible_members: int): + def household_value(self): data = self.county_values.fetch() # if there is no county, then we want to estimate based off of zipcode - if self.screen.county is not None: - counties = [self.screen.county] - else: - counties = counties_from_zip(self.screen.zipcode) + counties = counties_from_screen(self.screen) values = [] for row in data: diff --git a/programs/programs/co/energy_resource_center/calculator.py b/programs/programs/co/energy_resource_center/calculator.py index 89470343..e747a0bb 100644 --- a/programs/programs/co/energy_resource_center/calculator.py +++ b/programs/programs/co/energy_resource_center/calculator.py @@ -7,7 +7,7 @@ class EnergyResourceCenter(ProgramCalculator): income_bands = {1: 2880, 2: 3766, 3: 4652, 4: 5539, 5: 6425, 6: 7311, 7: 7477, 8: 7644} dependencies = ["household_size", "income_amount", "income_frequency"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # income diff --git a/programs/programs/co/every_day_eats/__init__.py b/programs/programs/co/every_day_eats/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/programs/programs/co/every_day_eats/calculator.py b/programs/programs/co/every_day_eats/calculator.py deleted file mode 100644 index fec2df6a..00000000 --- a/programs/programs/co/every_day_eats/calculator.py +++ /dev/null @@ -1,25 +0,0 @@ -from programs.programs.calc import ProgramCalculator, Eligibility -import programs.programs.messages as messages - - -class EveryDayEats(ProgramCalculator): - amount = 600 - min_age = 60 - percent_of_fpl = 1.3 - dependencies = ["age", "income_amount", "income_frequency", "household_size"] - - def eligible(self) -> Eligibility: - e = Eligibility() - - # Someone older that 60 - num_seniors = self.screen.num_adults(age_max=EveryDayEats.min_age) - e.condition(num_seniors >= 1, messages.older_than(EveryDayEats.min_age)) - - # Income - fpl = self.program.fpl.as_dict() - income_limit = EveryDayEats.percent_of_fpl * fpl[self.screen.household_size] - gross_income = self.screen.calc_gross_income("yearly", ["all"]) - - e.condition(gross_income < income_limit, messages.income(gross_income, income_limit)) - - return e diff --git a/programs/programs/co/every_day_eats/tests.py b/programs/programs/co/every_day_eats/tests.py deleted file mode 100644 index 03606c18..00000000 --- a/programs/programs/co/every_day_eats/tests.py +++ /dev/null @@ -1,47 +0,0 @@ -from django.test import TestCase -from programs.programs.every_day_eats.calculator import EveryDayEats -from screener.models import Screen, HouseholdMember, IncomeStream - - -class TestEveryDayEatsPension(TestCase): - def setUp(self): - self.screen1 = Screen.objects.create( - agree_to_tos=True, - zipcode="80205", - county="Denver County", - household_size=1, - household_assets=0, - ) - self.person1 = HouseholdMember.objects.create( - screen=self.screen1, - relationship="headOfHousehold", - age=60, - student=False, - student_full_time=False, - pregnant=False, - unemployed=False, - worked_in_last_18_mos=True, - visually_impaired=False, - disabled=False, - veteran=False, - has_income=False, - has_expenses=False, - ) - - def test_every_day_eats_visually_impaired_is_eligible(self): - ede = EveryDayEats(self.screen1) - eligibility = ede.eligibility - - self.assertTrue(eligibility["eligible"]) - - def test_every_day_eats_failed_all_conditions(self): - income = IncomeStream.objects.create( - screen=self.screen1, household_member=self.person1, type="wages", amount=3000, frequency="monthly" - ) - self.person1.age = 30 - self.person1.save() - - ede = EveryDayEats(self.screen1) - eligibility = ede.eligibility - - self.assertFalse(eligibility["eligible"]) diff --git a/programs/programs/co/low_wage_covid_relief/calculator.py b/programs/programs/co/low_wage_covid_relief/calculator.py index 92d8653f..d5f462fb 100644 --- a/programs/programs/co/low_wage_covid_relief/calculator.py +++ b/programs/programs/co/low_wage_covid_relief/calculator.py @@ -1,7 +1,7 @@ from programs.programs.calc import ProgramCalculator, Eligibility from programs.programs.helpers import STATE_MEDICAID_OPTIONS import programs.programs.messages as messages -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen import math @@ -21,14 +21,11 @@ class LowWageCovidRelief(ProgramCalculator): county = "Adams County" dependencies = ["zipode", "household_size", "income_amount", "income_frequency"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # lives in Adams County - if self.screen.county is not None: - counties = [self.screen.county] - else: - counties = counties_from_zip(self.screen.zipcode) + counties = counties_from_screen(self.screen) in_adams_county = LowWageCovidRelief.county in counties e.condition(in_adams_county, messages.location()) diff --git a/programs/programs/co/medicaid/adult_with_disability/calculator.py b/programs/programs/co/medicaid/adult_with_disability/calculator.py index 18e3ed1f..77275de9 100644 --- a/programs/programs/co/medicaid/adult_with_disability/calculator.py +++ b/programs/programs/co/medicaid/adult_with_disability/calculator.py @@ -1,6 +1,8 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility +from programs.programs.co.medicaid.child_with_disability.calculator import MedicaidChildWithDisability from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages +from screener.models import HouseholdMember class MedicaidAdultWithDisability(ProgramCalculator): @@ -8,44 +10,40 @@ class MedicaidAdultWithDisability(ProgramCalculator): max_income_percent = 4.5 earned_deduction = 65 earned_percent = 0.5 - amount = 310 unearned_deduction = 20 min_age = 16 insurance_types = ("employer", "private", "none") dependencies = ["insurance", "age", "household_size", "income_type", "income_amount", "income_frequency"] + member_amount = 310 - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # Does not qualify for Medicaid e.condition(not medicaid_eligible(self.data), messages.must_not_have_benefit("Medicaid")) - def income_eligible(member): - fpl = self.program.fpl.as_dict() - income_limit = fpl[self.screen.household_size] * MedicaidAdultWithDisability.max_income_percent - earned_deduction = MedicaidAdultWithDisability.earned_deduction - earned_percent = MedicaidAdultWithDisability.earned_percent - earned = max( - 0, int((int(member.calc_gross_income("yearly", ["earned"])) - earned_deduction) * earned_percent) - ) - unearned_deduction = MedicaidAdultWithDisability.unearned_deduction - unearned = int(member.calc_gross_income("yearly", ["unearned"])) - unearned_deduction - return earned + unearned <= income_limit - - e.member_eligibility( - self.screen.household_members.all(), - [ - ( - lambda m: m.age >= MedicaidAdultWithDisability.min_age, - messages.older_than(min_age=MedicaidAdultWithDisability.min_age), - ), - (lambda m: m.long_term_disability or m.visually_impaired, messages.has_disability()), - (lambda m: m.insurance.has_insurance_types(MedicaidAdultWithDisability.insurance_types), None), - (income_eligible, None), - ], - ) - return e - def value(self, eligible_members: int): - return MedicaidAdultWithDisability.amount * eligible_members * 12 + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # age + e.condition(member.age >= MedicaidChildWithDisability.min_age) + + # disability + e.condition(member.long_term_disability or member.visually_impaired) + + # insurance + e.condition(member.insurance.has_insurance_types(MedicaidAdultWithDisability.insurance_types)) + + # income + fpl = self.program.fpl.as_dict() + income_limit = fpl[self.screen.household_size] * MedicaidAdultWithDisability.max_income_percent + earned_deduction = MedicaidAdultWithDisability.earned_deduction + earned_percent = MedicaidAdultWithDisability.earned_percent + earned = max(0, int((int(member.calc_gross_income("yearly", ["earned"])) - earned_deduction) * earned_percent)) + unearned_deduction = MedicaidAdultWithDisability.unearned_deduction + unearned = int(member.calc_gross_income("yearly", ["unearned"])) - unearned_deduction + e.condition(earned + unearned <= income_limit) + + return e diff --git a/programs/programs/co/medicaid/child_with_disability/calculator.py b/programs/programs/co/medicaid/child_with_disability/calculator.py index 3a189af1..2d7fe1e1 100644 --- a/programs/programs/co/medicaid/child_with_disability/calculator.py +++ b/programs/programs/co/medicaid/child_with_disability/calculator.py @@ -1,16 +1,18 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages +from screener.models import HouseholdMember class MedicaidChildWithDisability(ProgramCalculator): max_age = 18 + min_employment_age = 16 max_income_percent = 3 earned_deduction = 90 income_percent = 1 - 0.33 insurance_types = ("employer", "private", "none") - amount = 200 dependencies = ["insurance", "age", "household_size", "income_type", "income_amount", "income_frequency"] + member_amount = 200 def eligible(self) -> Eligibility: e = Eligibility() @@ -18,6 +20,7 @@ def eligible(self) -> Eligibility: # Does not qualify for Medicaid e.condition(not medicaid_eligible(self.data), messages.must_not_have_benefit("Medicaid")) + # income fpl = self.program.fpl.as_dict() income_limit = fpl[self.screen.household_size] * MedicaidChildWithDisability.max_income_percent earned = max( @@ -27,17 +30,26 @@ def eligible(self) -> Eligibility: income = (earned + unearned) * MedicaidChildWithDisability.income_percent e.condition(income <= income_limit, messages.income(income, income_limit)) - e.member_eligibility( - self.screen.household_members.all(), - [ - (lambda m: m.age <= MedicaidChildWithDisability.max_age, messages.child()), - (lambda m: m.long_term_disability or m.visually_impaired, messages.has_disability()), - (lambda m: m.insurance.has_insurance_types(MedicaidChildWithDisability.insurance_types), None), - (lambda m: not (m.calc_gross_income("yearly", ["earned"]) >= 0 and m.age >= 16), None), - ], + return e + + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # age + e.condition(member.age <= MedicaidChildWithDisability.max_age) + + # disability + e.condition(member.long_term_disability or member.visually_impaired) + + # insurance + e.condition(member.insurance.has_insurance_types(MedicaidChildWithDisability.insurance_types)) + + # no income + e.condition( + not ( + member.calc_gross_income("yearly", ["earned"]) >= 0 + and member.age >= MedicaidChildWithDisability.min_employment_age + ) ) return e - - def value(self, eligible_members: int): - return MedicaidChildWithDisability.amount * eligible_members * 12 diff --git a/programs/programs/co/medicaid/emergency/calculator.py b/programs/programs/co/medicaid/emergency/calculator.py index 998867b1..9f59f5c3 100644 --- a/programs/programs/co/medicaid/emergency/calculator.py +++ b/programs/programs/co/medicaid/emergency/calculator.py @@ -1,10 +1,12 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages +from screener.models import HouseholdMember class EmergencyMedicaid(ProgramCalculator): amount = 9_540 + insurance_types = ["none"] dependencies = ["insurance"] def eligible(self) -> Eligibility: @@ -13,9 +15,12 @@ def eligible(self) -> Eligibility: # Does qualify for Medicaid e.condition(medicaid_eligible(self.data), messages.must_have_benefit("Medicaid")) - e.member_eligibility( - self.screen.household_members.all(), - [(lambda m: m.insurance.has_insurance_types(("none",)), messages.has_no_insurance())], - ) + return e + + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # insurance + e.condition(member.insurance.has_insurance_types(EmergencyMedicaid.insurance_types)) return e diff --git a/programs/programs/co/medicaid/family_planning_services/calculator.py b/programs/programs/co/medicaid/family_planning_services/calculator.py index 8b96be68..2d61b37d 100644 --- a/programs/programs/co/medicaid/family_planning_services/calculator.py +++ b/programs/programs/co/medicaid/family_planning_services/calculator.py @@ -1,15 +1,16 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages +from screener.models import HouseholdMember class FamilyPlanningServices(ProgramCalculator): - amount = 404 + member_amount = 404 min_age = 12 fpl_percent = 2.65 dependencies = ["age", "insurance", "income_frequency", "income_amount", "household_size"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # Does not have insurance @@ -21,15 +22,6 @@ def eligible(self) -> Eligibility: # Not Medicaid eligible e.condition(not medicaid_eligible(self.data), messages.must_not_have_benefit("Medicaid")) - e.member_eligibility( - self.screen.household_members.all(), - [ - (lambda m: not m.pregnant, None), - (lambda m: m.age >= FamilyPlanningServices.min_age, None), - (lambda m: m.is_head() or m.is_spouse(), None), - ], - ) - # Income fpl = self.program.fpl income_limit = int( @@ -40,3 +32,17 @@ def eligible(self) -> Eligibility: e.condition(gross_income < income_limit, messages.income(gross_income, income_limit)) return e + + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # not pregnant + e.condition(not member.pregnant) + + # age + e.condition(member.age >= FamilyPlanningServices.min_age) + + # head or spouse + e.condition(member.is_head() or member.is_spouse()) + + return e diff --git a/programs/programs/co/my_spark/calculator.py b/programs/programs/co/my_spark/calculator.py index 9b8bd989..b4d09374 100644 --- a/programs/programs/co/my_spark/calculator.py +++ b/programs/programs/co/my_spark/calculator.py @@ -1,10 +1,11 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility import programs.programs.messages as messages -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen +from screener.models import HouseholdMember class MySpark(ProgramCalculator): - amount_per_child = 1_000 + member_amount = 1_000 max_age = 14 min_age = 11 county = "Denver County" @@ -21,26 +22,17 @@ def eligible(self) -> Eligibility: break e.condition(is_frl_eligible, messages.must_have_benefit("Free or Reduced Lunch")) - if self.screen.county is not None: - counties = [self.screen.county] - else: - counties = counties_from_zip(self.screen.zipcode) + counties = counties_from_screen(self.screen) # Denever County e.condition(MySpark.county in counties, messages.location()) - # Kid 11 - 14 - e.member_eligibility( - self.screen.household_members.all(), - [ - ( - lambda m: m.age >= MySpark.min_age and m.age <= MySpark.max_age, - messages.child(MySpark.min_age, MySpark.max_age), - ) - ], - ) - return e - def value(self, eligible_members: int): - return MySpark.amount_per_child * eligible_members + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # age + e.condition(MySpark.min_age <= member.age <= MySpark.max_age) + + return e diff --git a/programs/programs/co/mydenver/calculator.py b/programs/programs/co/mydenver/calculator.py index 7985e7a4..245095ec 100644 --- a/programs/programs/co/mydenver/calculator.py +++ b/programs/programs/co/mydenver/calculator.py @@ -1,47 +1,34 @@ -from programs.programs.calc import ProgramCalculator, Eligibility -from programs.co_county_zips import counties_from_zip +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility +from programs.co_county_zips import counties_from_screen import programs.programs.messages as messages +from screener.models import HouseholdMember class MyDenver(ProgramCalculator): eligible_counties = ["Denver County"] child_age_min = 5 child_age_max = 18 - child_relationship = ["child", "fosterChild", "stepChild", "grandChild", "relatedOther", "headOfHousehold"] - dependencies = ["age", "zipcode", "relationship"] + member_amount = 150 + dependencies = ["age", "zipcode"] def eligible(self) -> Eligibility: e = Eligibility() - # geography test + # location county_eligible = False - if not self.screen.county: - counties = counties_from_zip(self.screen.zipcode) - for county in counties: - if county in MyDenver.eligible_counties: - county_eligible = True - else: - if self.screen.county in MyDenver.eligible_counties: + counties = counties_from_screen(self.screen) + for county in counties: + if county in MyDenver.eligible_counties: county_eligible = True - e.condition(county_eligible, messages.location()) - children = self.screen.num_children( - age_max=MyDenver.child_age_max, - age_min=MyDenver.child_age_min, - child_relationship=MyDenver.child_relationship, - ) - - e.condition(children > 0, messages.child(min_age=5)) - return e - def value(self, eligible_members: int): - children = self.screen.num_children( - age_max=MyDenver.child_age_max, - age_min=MyDenver.child_age_min, - child_relationship=MyDenver.child_relationship, - ) + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility() - return children * 150 + # age + e.condition(MyDenver.child_age_min <= member.age <= MyDenver.child_age_max) + + return e diff --git a/programs/programs/co/nfp/calculator.py b/programs/programs/co/nfp/calculator.py deleted file mode 100644 index d71adeef..00000000 --- a/programs/programs/co/nfp/calculator.py +++ /dev/null @@ -1,53 +0,0 @@ -from django.utils.translation import gettext as _ -import math - - -def calculate_nfp(screen, data, program): - eligibility = eligibility_nfp(screen, program) - value = value_nfp(screen) - - calculation = {"eligibility": eligibility, "value": value} - - return calculation - - -def eligibility_nfp(screen, program): - eligibility = {"eligible": True, "passed": [], "failed": []} - - frequency = "yearly" - - # INCOME TEST -- you can apply for RTD Live with only pay stubs, - # so we limit to wages here - fpl = program.fpl.as_dict() - income_limit = 2 * fpl[screen.household_size] - income_types = ["wages", "selfEmployment"] - gross_income = screen.calc_gross_income(frequency, income_types) - - # income test - if gross_income > income_limit: - eligibility["eligible"] = False - eligibility["failed"].append( - _("Calculated income of ") - + str(math.trunc(gross_income)) - + _(" for a household with ") - + str(screen.household_size) - + _(" members is above the income limit of ") - + str(income_limit) - ) - else: - eligibility["passed"].append( - _("Calculated income of ") - + str(math.trunc(gross_income)) - + _(" for a household with ") - + str(screen.household_size) - + _(" members is below the income limit of ") - + str(income_limit) - ) - - return eligibility - - -def value_nfp(screen): - value = 750 - - return value diff --git a/programs/programs/co/nurse_family_partnership/calculator.py b/programs/programs/co/nurse_family_partnership/calculator.py index 5740c368..d1a699ff 100644 --- a/programs/programs/co/nurse_family_partnership/calculator.py +++ b/programs/programs/co/nurse_family_partnership/calculator.py @@ -1,5 +1,4 @@ -from programs.programs.calc import ProgramCalculator, Eligibility -import programs.programs.messages as messages +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from screener.models import HouseholdMember, Insurance @@ -15,32 +14,29 @@ class NurseFamilyPartnership(ProgramCalculator): "pregnant", ] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # no other children e.condition(self.screen.num_children(child_relationship=NurseFamilyPartnership.child_relationships) == 0) - def income_eligible(member: HouseholdMember): - income_limit = self.program.fpl.as_dict()[2] * NurseFamilyPartnership.fpl_percent - - income = member.calc_gross_income("yearly", ["all"]) + return e - is_income_eligible = income <= income_limit + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) - insurance: Insurance = member.insurance - has_medicaid = insurance.medicaid or insurance.emergency_medicaid + # pregnant + e.condition(member.pregnant) - has_wic = self.screen.has_benefit("wic") + # income + income_limit = self.program.fpl.as_dict()[2] * NurseFamilyPartnership.fpl_percent + income = member.calc_gross_income("yearly", ["all"]) + is_income_eligible = income <= income_limit - return is_income_eligible or has_medicaid or has_wic + insurance: Insurance = member.insurance + has_medicaid = insurance.medicaid or insurance.emergency_medicaid + has_wic = self.screen.has_benefit("wic") - e.member_eligibility( - self.screen.household_members.all(), - [ - (lambda m: m.pregnant, messages.is_pregnant()), - (income_eligible, None), - ], - ) + e.condition(is_income_eligible or has_medicaid or has_wic) return e diff --git a/programs/programs/co/nurturing_futures/calculator.py b/programs/programs/co/nurturing_futures/calculator.py index 0c20cd5d..939764db 100644 --- a/programs/programs/co/nurturing_futures/calculator.py +++ b/programs/programs/co/nurturing_futures/calculator.py @@ -1,5 +1,5 @@ from integrations.services.sheets.sheets import GoogleSheetsCache -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen from programs.programs.calc import Eligibility, ProgramCalculator import programs.programs.messages as messages @@ -23,15 +23,11 @@ class NurturingFutures(ProgramCalculator): ami_percent = 0.3 amount = 3_600 - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() - # Lives in Boulder - if self.screen.county is not None: - counties = [self.screen.county] - else: - counties = counties_from_zip(self.screen.zipcode) - + # location + counties = counties_from_screen(self.screen) e.condition(NurturingFutures.county in counties, messages.location()) # head is 18+ diff --git a/programs/programs/co/omnisalud/calculator.py b/programs/programs/co/omnisalud/calculator.py index b8d22887..d64472fc 100644 --- a/programs/programs/co/omnisalud/calculator.py +++ b/programs/programs/co/omnisalud/calculator.py @@ -1,11 +1,13 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility import programs.programs.messages as messages +from screener.models import HouseholdMember class OmniSalud(ProgramCalculator): individual_limit = 1699 family_4_limit = 3469 - amount = 610 + insurance = ["none"] + member_amount = 610 * 12 dependencies = ["income_amount", "income_frequency", "household_size", "age", "insurance"] def eligible(self) -> Eligibility: @@ -16,11 +18,12 @@ def eligible(self) -> Eligibility: income_band = OmniSalud.family_4_limit if self.screen.household_size >= 4 else OmniSalud.individual_limit e.condition(gross_income <= income_band, messages.income(gross_income, income_band)) - # No health insurance - has_no_hi = self.screen.has_insurance_types(("none",)) - e.condition(has_no_hi, messages.has_no_insurance()) - return e - def value(self, eligible_members: int): - return OmniSalud.amount * self.screen.household_size * 12 + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # insurance + e.condition(member.insurance.has_insurance_types(OmniSalud.insurance)) + + return e diff --git a/programs/programs/co/property_credit_rebate/calculator.py b/programs/programs/co/property_credit_rebate/calculator.py index 63d1b1b4..7467b6fc 100644 --- a/programs/programs/co/property_credit_rebate/calculator.py +++ b/programs/programs/co/property_credit_rebate/calculator.py @@ -1,34 +1,22 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility import programs.programs.messages as messages +from screener.models import HouseholdMember class PropertyCreditRebate(ProgramCalculator): amount = 1044 min_age = 65 disabled_min_age = 18 + expenses = ["rent", "mortgage"] income_limit = {"single": 18_026, "married": 23_345} dependencies = ["age", "income_frequency", "income_amount", "relationship"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() - # Someone is disabled - someone_disabled = False - for member in self.screen.household_members.all(): - someone_disabled = someone_disabled or ( - member.has_disability() and member.age > PropertyCreditRebate.disabled_min_age - ) - - # Someone is old enough - someone_old_enough = self.screen.num_adults(age_max=PropertyCreditRebate.min_age) >= 1 - - e.condition(someone_disabled or someone_old_enough, messages.has_disability()) - - e.condition(someone_disabled or someone_old_enough, messages.older_than(PropertyCreditRebate.min_age)) - # Income test relationship_status = "single" - for member_id, married_to in self.screen.relationship_map().items(): + for _, married_to in self.screen.relationship_map().items(): if married_to is not None: relationship_status = "married" @@ -39,7 +27,20 @@ def eligible(self) -> Eligibility: ) # has rent or mortgage expense - has_rent_or_mortgage = self.screen.has_expense(["rent", "mortgage"]) + has_rent_or_mortgage = self.screen.has_expense(PropertyCreditRebate.expenses) e.condition(has_rent_or_mortgage) return e + + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # disabled + someone_disabled = member.has_disability() and member.age > PropertyCreditRebate.disabled_min_age + + # age + someone_old_enough = member.age >= PropertyCreditRebate.min_age + + e.condition(someone_disabled or someone_old_enough) + + return e diff --git a/programs/programs/co/rental_assistance_grant/calculator.py b/programs/programs/co/rental_assistance_grant/calculator.py index a0fd0af3..302c490b 100644 --- a/programs/programs/co/rental_assistance_grant/calculator.py +++ b/programs/programs/co/rental_assistance_grant/calculator.py @@ -1,7 +1,8 @@ from programs.programs.calc import ProgramCalculator, Eligibility import programs.programs.messages as messages -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen from integrations.services.sheets import GoogleSheetsCache +import math class RAGCache(GoogleSheetsCache): @@ -20,22 +21,27 @@ class RentalAssistanceGrant(ProgramCalculator): dependencies = ["income_amount", "income_frequency", "household_size", "zipcode"] income_limits = RAGCache() - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() - # location - counties = counties_from_zip(self.screen.zipcode) - county_name = self.screen.county if self.screen.county is not None else counties[0] # income gross_income = int(self.screen.calc_gross_income("yearly", ["all"])) limits = self.income_limits.fetch() - if county_name not in limits: - return e + counties = counties_from_screen(self.screen) + county_name = counties[0] - income_limit = limits[county_name][self.screen.household_size - 1] + for county in counties: + if county in limits: + county_name = county + break + + if county_name in limits: + income_limit = limits[county_name][self.screen.household_size - 1] + else: + income_limit = -math.inf e.condition(gross_income <= income_limit, messages.income(gross_income, income_limit)) diff --git a/programs/programs/co/reproductive_health_care/calculator.py b/programs/programs/co/reproductive_health_care/calculator.py index c9933222..11255ef3 100644 --- a/programs/programs/co/reproductive_health_care/calculator.py +++ b/programs/programs/co/reproductive_health_care/calculator.py @@ -1,20 +1,26 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages +from screener.models import HouseholdMember class ReproductiveHealthCare(ProgramCalculator): amount = 268 dependencies = ["insurance"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() - # No health insurance - has_no_hi = self.screen.has_insurance_types(("none",)) - e.condition(has_no_hi, messages.has_no_insurance()) - # Medicade eligibility e.condition(medicaid_eligible(self.data), messages.must_have_benefit("Medicaid")) return e + + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # No health insurance + has_no_hi = member.insurance.has_insurance_types(("none",)) + e.condition(has_no_hi) + + return e diff --git a/programs/programs/co/rtdlive/calculator.py b/programs/programs/co/rtdlive/calculator.py index ab003a4a..3e154cd1 100644 --- a/programs/programs/co/rtdlive/calculator.py +++ b/programs/programs/co/rtdlive/calculator.py @@ -1,5 +1,5 @@ -from programs.programs.calc import ProgramCalculator, Eligibility -from programs.co_county_zips import counties_from_zip +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility +from programs.co_county_zips import counties_from_screen import programs.programs.messages as messages from screener.models import HouseholdMember @@ -18,66 +18,46 @@ class RtdLive(ProgramCalculator): max_age = 64 percent_of_fpl = 2.5 tax_unit_dependent = True - amount = 732 + member_amount = 732 dependencies = ["age", "income_amount", "income_frequency", "zipcode", "household_size"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() - main_tax_unit_members: list[HouseholdMember] = [] - secondary_tax_unit_members: list[HouseholdMember] = [] - members = self.screen.household_members.all() - for member in members: - if member.is_in_tax_unit(): - main_tax_unit_members.append(member) - else: - secondary_tax_unit_members.append(member) - def income_eligible(member: HouseholdMember): - if member in main_tax_unit_members: - tax_unit = main_tax_unit_members - elif member in secondary_tax_unit_members: - tax_unit = secondary_tax_unit_members - else: - raise Exception("member is not in a tax unit") + # location + county_eligible = False + counties = counties_from_screen(self.screen) - # income - frequency = "yearly" - income_types = ["all"] - fpl = self.program.fpl.as_dict() - income_limit = RtdLive.percent_of_fpl * fpl[len(tax_unit)] + for county in counties: + if county in RtdLive.eligible_counties: + county_eligible = True - gross_income = 0 - for member in tax_unit: - gross_income += member.calc_gross_income(frequency, income_types) + e.condition(county_eligible, messages.location()) + + return e - return gross_income <= income_limit + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) # age - e.member_eligibility( - members, - [ - (income_eligible, None), - ( - lambda m: m.age >= RtdLive.min_age and m.age <= RtdLive.max_age, - messages.adult(RtdLive.min_age, RtdLive.max_age), - ), - ], - ) + e.condition(RtdLive.min_age <= member.age <= RtdLive.max_age) - # geography - county_eligible = False - if not self.screen.county: - counties = counties_from_zip(self.screen.zipcode) + # income + if member.is_in_tax_unit(): + tax_unit = [m for m in self.screen.household_members.all() if m.is_in_tax_unit()] else: - counties = [self.screen.county] + tax_unit = [m for m in self.screen.household_members.all() if not m.is_in_tax_unit()] - for county in counties: - if county in RtdLive.eligible_counties: - county_eligible = True - - e.condition(county_eligible, messages.location()) + e.condition(self._unit_income_eligible(tax_unit)) return e - def value(self, eligible_members: int): - return RtdLive.amount * eligible_members + def _unit_income_eligible(self, members: list[HouseholdMember]) -> bool: + gross_income = 0 + for member in members: + gross_income += member.calc_gross_income("yearly", ["all"]) + + fpl = self.program.fpl.as_dict() + income_limit = RtdLive.percent_of_fpl * fpl[len(members)] + + return gross_income <= income_limit diff --git a/programs/programs/co/tabor/calculator.py b/programs/programs/co/tabor/calculator.py index ea1e4006..30720c3e 100644 --- a/programs/programs/co/tabor/calculator.py +++ b/programs/programs/co/tabor/calculator.py @@ -1,21 +1,15 @@ -from programs.programs.calc import ProgramCalculator, Eligibility -import programs.programs.messages as messages +from programs.programs.calc import MemberEligibility, ProgramCalculator +from screener.models import HouseholdMember class Tabor(ProgramCalculator): min_age = 18 - amount = 800 + member_amount = 800 dependencies = ["age"] - def eligible(self) -> Eligibility: - e = Eligibility() + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) - e.member_eligibility( - self.screen.household_members.all(), - [(lambda m: m.age >= Tabor.min_age, messages.older_than(Tabor.min_age))], - ) + e.condition(member.age >= Tabor.min_age) return e - - def value(self, eligible_members: int): - return Tabor.amount * eligible_members diff --git a/programs/programs/co/trua/calculator.py b/programs/programs/co/trua/calculator.py index f8bc875d..743db5b0 100644 --- a/programs/programs/co/trua/calculator.py +++ b/programs/programs/co/trua/calculator.py @@ -1,6 +1,6 @@ from programs.programs.calc import Eligibility, ProgramCalculator import programs.programs.messages as messages -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen class Trua(ProgramCalculator): @@ -19,28 +19,19 @@ class Trua(ProgramCalculator): amount = 6_500 dependencies = ["income_amount", "income_frequency", "household_size", "zipcode"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() - # Income test - gross_income = int(self.screen.calc_gross_income("monthly", ["all"])) - income_limit = int(Trua.income_limit[self.screen.household_size] / 12) - - # Location test - zipcode = self.screen.zipcode - location = self.screen.county - - if location is not None: - counties = [location] - else: - counties = counties_from_zip(zipcode) + # income + gross_income = int(self.screen.calc_gross_income("yearly", ["all"])) + income_limit = int(Trua.income_limit[self.screen.household_size]) + e.condition(gross_income <= income_limit, messages.income(gross_income, income_limit)) - # Denver County + # location + counties = counties_from_screen(self.screen) e.condition(Trua.county in counties, messages.location()) - e.condition(gross_income <= income_limit, messages.income(gross_income, income_limit)) - - # has rent or mortgage expense + # rent or mortgage expense has_rent_or_mortgage = self.screen.has_expense(["rent", "mortgage"]) e.condition(has_rent_or_mortgage) diff --git a/programs/programs/co/universal_preschool/calculator.py b/programs/programs/co/universal_preschool/calculator.py index 6185216c..534b441c 100644 --- a/programs/programs/co/universal_preschool/calculator.py +++ b/programs/programs/co/universal_preschool/calculator.py @@ -1,60 +1,40 @@ -from programs.programs.calc import ProgramCalculator, Eligibility -import programs.programs.messages as messages +from programs.programs.calc import MemberEligibility, ProgramCalculator +from screener.models import HouseholdMember class UniversalPreschool(ProgramCalculator): qualifying_age = 3 age = 4 percent_of_fpl = 2.7 - amount = {"10_hours": 4_837, "15_hours": 6_044, "30_hours": 10_655} + amount_by_hours = {"10_hours": 4_837, "15_hours": 6_044, "30_hours": 10_655} dependencies = ["age", "income_amount", "income_frequency", "relationship", "household_size"] - def eligible(self) -> Eligibility: - e = Eligibility() + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) - foster_children = self.screen.num_children( - age_min=UniversalPreschool.qualifying_age, - age_max=UniversalPreschool.age, - child_relationship=["fosterChild"], - ) + # qualifying condition + qualifying_condition = self._has_qualifying_condition() - income_requirement = self._meets_income_requirement() - other_factors = income_requirement or foster_children >= 1 - - # Has child - children = self.screen.num_children(age_min=UniversalPreschool.age, age_max=UniversalPreschool.age) - qualifying_children = self.screen.num_children( - age_min=UniversalPreschool.qualifying_age, age_max=UniversalPreschool.age - ) - - min_age = UniversalPreschool.qualifying_age if other_factors else UniversalPreschool.age - - e.condition( - children >= 1 or (qualifying_children >= 1 and other_factors), - messages.child(min_age, UniversalPreschool.age), - ) + # age + min_age = UniversalPreschool.qualifying_age if qualifying_condition else UniversalPreschool.age + e.condition(min_age <= member.age <= UniversalPreschool.age) return e - def value(self, eligible_members: int): - value = 0 - income_requirement = self._meets_income_requirement() + def member_value(self, member: HouseholdMember): + qualifying_condition = self._has_qualifying_condition() - for child in self.screen.household_members.all(): - if not (UniversalPreschool.qualifying_age <= child.age <= UniversalPreschool.age): - continue + if not qualifying_condition: + return UniversalPreschool.amount_by_hours["15_hours"] - if child.relationship == "fosterChild" or income_requirement: - if child.age == 3: - value += UniversalPreschool.amount["10_hours"] - else: - value += UniversalPreschool.amount["30_hours"] - else: - value += UniversalPreschool.amount["15_hours"] + if member.age == UniversalPreschool.age: + return UniversalPreschool.amount_by_hours["30_hours"] - return value + return UniversalPreschool.amount_by_hours["10_hours"] - def _meets_income_requirement(self): + def _has_qualifying_condition(self, member: HouseholdMember): fpl = self.program.fpl.as_dict() income_limit = int(UniversalPreschool.percent_of_fpl * fpl[self.screen.household_size]) - return self.screen.calc_gross_income("yearly", ["all"]) < income_limit + income_condition = self.screen.calc_gross_income("yearly", ["all"]) < income_limit + + return income_condition or member.relationship == "fosterChild" diff --git a/programs/programs/co/utility_bill_pay/calculator.py b/programs/programs/co/utility_bill_pay/calculator.py index ea510dca..a73fbc48 100644 --- a/programs/programs/co/utility_bill_pay/calculator.py +++ b/programs/programs/co/utility_bill_pay/calculator.py @@ -17,7 +17,7 @@ class UtilityBillPay(ProgramCalculator): amount = 350 dependencies = ["household_size", "income_amount", "income_frequency"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # has other programs diff --git a/programs/programs/co/weatherization_assistance/calculator.py b/programs/programs/co/weatherization_assistance/calculator.py index 70fdc6bb..0674ebcf 100644 --- a/programs/programs/co/weatherization_assistance/calculator.py +++ b/programs/programs/co/weatherization_assistance/calculator.py @@ -17,7 +17,7 @@ class WeatherizationAssistance(ProgramCalculator): amount = 350 dependencies = ["household_size", "income_amount", "income_frequency"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # income condition @@ -33,7 +33,7 @@ def eligible(self) -> Eligibility: break e.condition(income_eligible or categorical_eligible, messages.income(income, income_limit)) - # has rent or mortgage expense + # rent or mortgage expense has_rent_or_mortgage = self.screen.has_expense(["rent", "mortgage"]) e.condition(has_rent_or_mortgage) diff --git a/programs/programs/federal/head_start/calculator.py b/programs/programs/federal/head_start/calculator.py index b037fff4..10869712 100644 --- a/programs/programs/federal/head_start/calculator.py +++ b/programs/programs/federal/head_start/calculator.py @@ -1,7 +1,8 @@ from integrations.services.sheets.sheets import GoogleSheetsCache -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility import programs.programs.messages as messages -from programs.co_county_zips import counties_from_zip +from programs.co_county_zips import counties_from_screen +from screener.models import HouseholdMember class HeadStartCountyEligibleCache(GoogleSheetsCache): @@ -17,7 +18,7 @@ def update(self): class HeadStart(ProgramCalculator): - amount = 10655 + member_amount = 10655 max_age = 5 min_age = 3 counties = HeadStartCountyEligibleCache() @@ -25,19 +26,11 @@ class HeadStart(ProgramCalculator): adams_county = "Adams County" dependencies = ["age", "household_size", "income_frequency", "income_amount", "zipcode"] - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() - # has young child - num_children = self.screen.num_children(age_min=HeadStart.min_age, age_max=HeadStart.max_age) - - e.condition(num_children >= 1, messages.child(HeadStart.min_age, HeadStart.max_age)) - # location - if self.screen.county is not None: - counties = [self.screen.county] - else: - counties = counties_from_zip(self.screen.zipcode) + counties = counties_from_screen(self.screen) in_eligible_county = False eligible_counties = HeadStart.counties.fetch() @@ -48,14 +41,13 @@ def eligible(self) -> Eligibility: e.condition(in_eligible_county, messages.location()) - in_adams = HeadStart.adams_county in counties - # income fpl = self.program.fpl.as_dict() income_limit = int(fpl[self.screen.household_size] / 12) income_limit_adams_county = int(fpl[self.screen.household_size] / 12 * HeadStart.adams_percent_of_fpl) gross_income = int(self.screen.calc_gross_income("monthly", ["all"])) + in_adams = HeadStart.adams_county in counties if in_adams: e.condition( gross_income < income_limit_adams_county, messages.income(gross_income, income_limit_adams_county) @@ -64,3 +56,11 @@ def eligible(self) -> Eligibility: e.condition(gross_income < income_limit, messages.income(gross_income, income_limit)) return e + + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # age + e.condition(HeadStart.min_age >= member.age >= HeadStart.max_age) + + return e diff --git a/programs/programs/federal/medicare_savings/calculator.py b/programs/programs/federal/medicare_savings/calculator.py index b3ef1177..735a4d6b 100644 --- a/programs/programs/federal/medicare_savings/calculator.py +++ b/programs/programs/federal/medicare_savings/calculator.py @@ -1,9 +1,9 @@ -from programs.programs.calc import ProgramCalculator, Eligibility -import programs.programs.messages as messages +from programs.programs.calc import MemberEligibility, ProgramCalculator +from screener.models import HouseholdMember class MedicareSavings(ProgramCalculator): - valid_insurance = ("none", "employer", "private", "medicare") + eligible_insurance_types = ("none", "employer", "private", "medicare") asset_limit = { "single": 10_930, "married": 17_130, @@ -13,44 +13,32 @@ class MedicareSavings(ProgramCalculator): "married": 2_320, } min_age = 65 - amount = 175 + member_amount = 175 * 12 dependencies = ["household_assets", "relationship", "income_frequency", "income_amount", "age"] - def eligible(self) -> Eligibility: - e = Eligibility() - - members = self.screen.household_members.all() - - def asset_limit(member): - status = "married" if member.is_married()["is_married"] else "single" - return self.screen.household_assets < MedicareSavings.asset_limit[status] - - def income_limit(member): - is_married = member.is_married() - if not is_married["is_married"]: - status = "single" - spouse_income = 0 - else: - status = "married" - spouse_income = is_married["married_to"].calc_gross_income("monthly", ("all",)) - max_income = MedicareSavings.income_limit[status] - income = member.calc_gross_income("monthly", ("all",)) + spouse_income - return income < max_income - - e.member_eligibility( - members, - [ - (lambda m: m.age >= MedicareSavings.min_age, messages.older_than(MedicareSavings.min_age)), - ( - lambda m: m.insurance.has_insurance_types(MedicareSavings.valid_insurance), - messages.has_no_insurance(), - ), - (asset_limit, None), - (income_limit, None), - ], - ) + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) - return e + # age + e.condition(member.age >= MedicareSavings.min_age) + + # insurance + e.condition(member.insurance.has_insurance_types(MedicareSavings.eligible_insurance_types)) + + # assets + status = "married" if member.is_married()["is_married"] else "single" + e.condition(self.screen.household_assets < MedicareSavings.asset_limit[status]) - def value(self, eligible_members: int): - return MedicareSavings.amount * eligible_members * 12 + # income + is_married = member.is_married() + if not is_married["is_married"]: + status = "single" + spouse_income = 0 + else: + status = "married" + spouse_income = is_married["married_to"].calc_gross_income("monthly", ("all",)) + max_income = MedicareSavings.income_limit[status] + income = member.calc_gross_income("monthly", ("all",)) + spouse_income + e.condition(income < max_income) + + return e diff --git a/programs/programs/federal/ssdi/calculator.py b/programs/programs/federal/ssdi/calculator.py index 76fc2ea0..5017f996 100644 --- a/programs/programs/federal/ssdi/calculator.py +++ b/programs/programs/federal/ssdi/calculator.py @@ -1,6 +1,4 @@ -from programs.programs.calc import ProgramCalculator, Eligibility -import programs.programs.messages as messages -import math +from programs.programs.calc import MemberEligibility, ProgramCalculator from screener.models import HouseholdMember, Screen from typing import TYPE_CHECKING @@ -21,56 +19,28 @@ def __init__(self, screen: Screen, program: "Program", data): self.eligible_members = [] super().__init__(screen, program, data) - def eligible(self) -> Eligibility: - e = Eligibility() + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) - lowest_income = math.inf - cat_eligibile = 0 + # disability + e.condition(member.has_disability()) - def income_condition(member: HouseholdMember): - nonlocal lowest_income - nonlocal cat_eligibile + # no SSDI income + e.condition(member.calc_gross_income("yearly", ["sSDisability"]) == 0) - income_limit = Ssdi.income_limit_blind if member.visually_impaired else Ssdi.income_limit - member_income = member.calc_gross_income("monthly", ("all",)) + # income + income_limit = Ssdi.income_limit_blind if member.visually_impaired else Ssdi.income_limit + member_income = member.calc_gross_income("monthly", ("all",)) + e.condition(member_income < income_limit) - if member_income < lowest_income: - lowest_income = member_income - cat_eligibile += 1 + # age + e.condition(member.age >= Ssdi.min_age or self._child_eligible(member)) - return member_income < income_limit - - self.eligible_members = e.member_eligibility( - self.screen.household_members.all(), - [ - (lambda m: m.has_disability(), messages.has_disability()), - ( - lambda m: m.calc_gross_income("yearly", ("sSDisability",)) == 0, - messages.must_not_have_benefit("SSDI"), - ), - (income_condition, None), - (lambda m: self._child_eligible(m) or m.age >= Ssdi.min_age, None), - ], - ) - - if cat_eligibile > 0: - e.passed(messages.income(lowest_income, Ssdi.income_limit)) + if e.eligible: + self.eligible_members.append(member) return e - def value(self, eligible_members: int): - child_value = 0 - adult_value = 0 - for member in self.eligible_members: - if member.age >= Ssdi.min_age: - adult_value += Ssdi.amount - else: - child_value += Ssdi.amount / 2 - - total_value = adult_value + min(child_value, Ssdi.amount / 2) - - return total_value * 12 - def _is_parent_with_disability(self, member: HouseholdMember): # min parent age if member.age < Ssdi.min_age: @@ -101,3 +71,17 @@ def _child_eligible(self, member: HouseholdMember): return True return False + + def household_value(self): + # NOTE: use household value because the total child value has a cap + child_value = 0 + adult_value = 0 + for member in self.eligible_members: + if member.age >= Ssdi.min_age: + adult_value += Ssdi.amount + else: + child_value += Ssdi.amount / 2 + + total_value = adult_value + min(child_value, Ssdi.amount / 2) + + return total_value * 12 diff --git a/programs/programs/nc/nc_aca/calculator.py b/programs/programs/nc/nc_aca/calculator.py index ee3b9547..6ecb7de1 100644 --- a/programs/programs/nc/nc_aca/calculator.py +++ b/programs/programs/nc/nc_aca/calculator.py @@ -1,7 +1,8 @@ -from programs.programs.calc import ProgramCalculator, Eligibility +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages from integrations.services.sheets import GoogleSheetsCache +from screener.models import HouseholdMember class ACACache(GoogleSheetsCache): @@ -18,32 +19,35 @@ def update(self): class ACASubsidiesNC(ProgramCalculator): percent_of_fpl = 4 dependencies = ["insurance", "income_amount", "income_frequency", "county", "household_size"] + eligible_insurance_types = ["none", "private"] + ineligible_insurance_types = ["va"] county_values = ACACache() - def eligible(self) -> Eligibility: + def household_eligible(self) -> Eligibility: e = Eligibility() # Medicade eligibility e.condition(not medicaid_eligible(self.data), messages.must_not_have_benefit("Medicaid")) - # Someone has no health insurance - has_no_hi = self.screen.has_insurance_types(("none", "private")) - e.condition(has_no_hi, messages.has_no_insurance()) - - # HH member has no va insurance - e.member_eligibility( - self.screen.household_members.all(), - [(lambda m: not m.insurance.has_insurance_types(("va", "private")), messages.must_not_have_benefit("VA"))], - ) - # Income fpl = self.program.fpl.as_dict() - income_band = int(fpl[self.screen.household_size] / 12 * ACASubsidiesNC.percent_of_fpl) - gross_income = int(self.screen.calc_gross_income("yearly", ("all",)) / 12) + income_band = int(fpl[self.screen.household_size] * ACASubsidiesNC.percent_of_fpl) + gross_income = int(self.screen.calc_gross_income("yearly", ("all",))) e.condition(gross_income < income_band, messages.income(gross_income, income_band)) return e - def value(self, eligible_members: int): + def member_eligible(self, member: HouseholdMember) -> MemberEligibility: + e = MemberEligibility(member) + + # no or private insurance + e.condition(member.insurance.has_insurance_types(ACASubsidiesNC.eligible_insurance_types)) + + # no va insurance + e.condition(not member.insurance.has_insurance_types(ACASubsidiesNC.ineligible_insurance_types)) + + return e + + def member_value(self, member: HouseholdMember): values = self.county_values.fetch() return values[self.screen.county] * 12 From 28171ae4c4883299373e75d551f07f04e78ad77a Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 23 Sep 2024 16:18:17 -0600 Subject: [PATCH 13/55] format --- programs/programs/co/rental_assistance_grant/calculator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/programs/programs/co/rental_assistance_grant/calculator.py b/programs/programs/co/rental_assistance_grant/calculator.py index 302c490b..09a0b5bb 100644 --- a/programs/programs/co/rental_assistance_grant/calculator.py +++ b/programs/programs/co/rental_assistance_grant/calculator.py @@ -24,7 +24,6 @@ class RentalAssistanceGrant(ProgramCalculator): def household_eligible(self) -> Eligibility: e = Eligibility() - # income gross_income = int(self.screen.calc_gross_income("yearly", ["all"])) From 914976a8f16c56f6282de31d70e5b1d096d589a3 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Tue, 24 Sep 2024 10:43:43 -0600 Subject: [PATCH 14/55] Add TranslationOverride model --- .../migrations/0088_translationoverride.py | 60 +++++++++++++++++++ programs/models.py | 9 +++ 2 files changed, 69 insertions(+) create mode 100644 programs/migrations/0088_translationoverride.py diff --git a/programs/migrations/0088_translationoverride.py b/programs/migrations/0088_translationoverride.py new file mode 100644 index 00000000..da345928 --- /dev/null +++ b/programs/migrations/0088_translationoverride.py @@ -0,0 +1,60 @@ +# Generated by Django 4.2.14 on 2024-09-24 16:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("translations", "0004_translation_no_auto"), + ("programs", "0087_remove_program_warning"), + ] + + operations = [ + migrations.CreateModel( + name="TranslationOverride", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("calculator", models.CharField(max_length=120)), + ( + "external_name", + models.CharField( + blank=True, max_length=120, null=True, unique=True + ), + ), + ("field", models.CharField(max_length=64)), + ("active", models.BooleanField(blank=True, default=True)), + ( + "counties", + models.ManyToManyField( + related_name="translation_overrides", to="programs.county" + ), + ), + ( + "program", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="translation_overrides", + to="programs.program", + ), + ), + ( + "translation", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="translation_overrides", + to="translations.translation", + ), + ), + ], + ), + ] diff --git a/programs/models.py b/programs/models.py index 22503f66..9c935de9 100644 --- a/programs/models.py +++ b/programs/models.py @@ -732,3 +732,12 @@ class Referrer(models.Model): def __str__(self): return self.referrer_code + +class TranslationOverride(models.Model): + calculator = models.CharField(max_length=120, blank=False, null=False) + external_name = models.CharField(max_length=120, blank=True, null=True, unique=True) + field = models.CharField(max_length=64, blank=False, null=False) + program = models.ForeignKey(Program, related_name="translation_overrides", blank=False, null=False, on_delete=models.CASCADE) + active = models.BooleanField(blank=True, null=False, default=True) + translation = models.ForeignKey(Translation, related_name="translation_overrides", blank=False, null=False, on_delete=models.PROTECT) + counties = models.ManyToManyField(County, related_name="translation_overrides", blank=False) From 616626fd1c707b33183dfc7ee86e60aadc0b9acd Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Tue, 24 Sep 2024 11:08:31 -0600 Subject: [PATCH 15/55] Fix underscore for init method in TranslationOverrrideCalc --- programs/programs/translation_overrides/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/programs/translation_overrides/base.py b/programs/programs/translation_overrides/base.py index faa031a5..596a7022 100644 --- a/programs/programs/translation_overrides/base.py +++ b/programs/programs/translation_overrides/base.py @@ -5,7 +5,7 @@ class TranslationOverrideCalculator: dependencies = tuple() - def _init_(self, screen: Screen, translation_override, missing_dependencies: Dependencies): + def __init__(self, screen: Screen, translation_override, missing_dependencies: Dependencies): self.screen = screen self.translation_override = translation_override self.missing_dependencies = missing_dependencies From bb70ac4b7ad4586d646bdaee4f4882cd05b937fe Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Tue, 24 Sep 2024 11:08:58 -0600 Subject: [PATCH 16/55] Add TranslationOverrideAdmin to Admin --- programs/admin.py | 32 ++++++++++++++++++++++++++++++++ programs/models.py | 8 ++++++++ 2 files changed, 40 insertions(+) diff --git a/programs/admin.py b/programs/admin.py index 92e1ac02..6c741728 100644 --- a/programs/admin.py +++ b/programs/admin.py @@ -16,6 +16,7 @@ County, NavigatorLanguage, Document, + TranslationOverride, ) @@ -269,6 +270,36 @@ class ReferrerAdmin(ModelAdmin): class WebHookFunctionsAdmin(ModelAdmin): search_fields = ("name",) +class TranslationOverrideAdmin(ModelAdmin): + search_fields = ("external_name",) + list_display = ["get_str", "calculator", "action_buttons"] + filter_horizontal = ( + "counties", + ) + + def get_str(self, obj): + return str(obj) + + get_str.admin_order_field = "external_name" + get_str.short_description = "Name" + + def action_buttons(self, obj): + message = obj.translation + + return format_html( + """ + + """, + reverse("translation_admin_url", args=[message.id]), + ) + + action_buttons.short_description = "Translate:" + action_buttons.allow_tags = True admin.site.register(LegalStatus, LegalStatusAdmin) admin.site.register(Program, ProgramAdmin) @@ -283,3 +314,4 @@ class WebHookFunctionsAdmin(ModelAdmin): admin.site.register(Document, DocumentAdmin) admin.site.register(Referrer, ReferrerAdmin) admin.site.register(WebHookFunction, WebHookFunctionsAdmin) +admin.site.register(TranslationOverride, TranslationOverrideAdmin) diff --git a/programs/models.py b/programs/models.py index 9c935de9..06e22468 100644 --- a/programs/models.py +++ b/programs/models.py @@ -741,3 +741,11 @@ class TranslationOverride(models.Model): active = models.BooleanField(blank=True, null=False, default=True) translation = models.ForeignKey(Translation, related_name="translation_overrides", blank=False, null=False, on_delete=models.PROTECT) counties = models.ManyToManyField(County, related_name="translation_overrides", blank=False) + + @property + def county_names(self) -> list[str]: + """List of county names""" + return [c.name for c in self.counties.all()] + + def __str__(self): + return self.external_name if self.external_name is not None else self.calculator From efa1fab63e9d7d9792e585c8db9d802bb6793f23 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Tue, 24 Sep 2024 11:44:19 -0600 Subject: [PATCH 17/55] Allow TranslationOverride's counties to be blank --- programs/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/models.py b/programs/models.py index 06e22468..598cb1d3 100644 --- a/programs/models.py +++ b/programs/models.py @@ -740,7 +740,7 @@ class TranslationOverride(models.Model): program = models.ForeignKey(Program, related_name="translation_overrides", blank=False, null=False, on_delete=models.CASCADE) active = models.BooleanField(blank=True, null=False, default=True) translation = models.ForeignKey(Translation, related_name="translation_overrides", blank=False, null=False, on_delete=models.PROTECT) - counties = models.ManyToManyField(County, related_name="translation_overrides", blank=False) + counties = models.ManyToManyField(County, related_name="translation_overrides", blank=True) @property def county_names(self) -> list[str]: From cd11f70a7a9c2d90b30dcf14fcf7f15f0a528443 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Tue, 24 Sep 2024 11:44:49 -0600 Subject: [PATCH 18/55] Add county_eligible method to TranslationOverrideCalculator --- programs/programs/translation_overrides/base.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/programs/programs/translation_overrides/base.py b/programs/programs/translation_overrides/base.py index 596a7022..426b052f 100644 --- a/programs/programs/translation_overrides/base.py +++ b/programs/programs/translation_overrides/base.py @@ -31,4 +31,12 @@ def can_calc(self) -> bool: """ return not self.missing_dependencies.has(*self.dependencies) - # TODO: county eligible method after the model is defined + def county_eligible(self) -> bool: + """ + Returns True if the override should be applied based on county + """ + county = self.screen.county + translation_override_counties = self.translation_override.county_names + if len(translation_override_counties) > 0: + return county in translation_override_counties + return True \ No newline at end of file From 72f70c0daaaf3297452194432bb33b7dee21cd69 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Tue, 24 Sep 2024 11:45:16 -0600 Subject: [PATCH 19/55] Refactor calc method to include self.county.eligible condition --- programs/programs/translation_overrides/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/programs/programs/translation_overrides/base.py b/programs/programs/translation_overrides/base.py index 426b052f..18e7a3b1 100644 --- a/programs/programs/translation_overrides/base.py +++ b/programs/programs/translation_overrides/base.py @@ -1,11 +1,11 @@ -# TODO: add translation override model from programs.util import Dependencies from screener.models import Screen +from programs.models import TranslationOverride class TranslationOverrideCalculator: dependencies = tuple() - def __init__(self, screen: Screen, translation_override, missing_dependencies: Dependencies): + def __init__(self, screen: Screen, translation_override: TranslationOverride, missing_dependencies: Dependencies): self.screen = screen self.translation_override = translation_override self.missing_dependencies = missing_dependencies @@ -17,7 +17,7 @@ def calc(self) -> bool: if not self.can_calc(): return False - return self.eligible() + return self.eligible() and self.county_eligible() def eligible(self) -> bool: """ From 716e44fa1e64343193772741d7cdb804d13bc82e Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Wed, 25 Sep 2024 15:38:21 -0600 Subject: [PATCH 20/55] Add get_translation method to the Program model --- programs/models.py | 18 +++++++++++++++++- .../programs/translation_overrides/base.py | 7 +++++-- screener/views.py | 3 +-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/programs/models.py b/programs/models.py index 598cb1d3..1b21b3ca 100644 --- a/programs/models.py +++ b/programs/models.py @@ -8,7 +8,7 @@ import requests from integrations.util.cache import Cache from typing import Optional, Type, TypedDict - +from programs.programs.translation_overrides import warning_calculators class FplCache(Cache): expire_time = 60 * 60 * 24 # 24 hours @@ -327,6 +327,22 @@ def __str__(self): def __unicode__(self): return self.name.text + def get_translation(self, screen, missing_dependencies: Dependencies, field:str): + if field not in Program.objects.translated_fields: + raise ValueError(f"translation with name {field} does not exist") + + translation_overrides: list[TranslationOverride] = self.translation_overrides.all() + for translation_override in translation_overrides: + if translation_override.field != field: + continue + + Calculator = warning_calculators[translation_override.calculator] + calculator = Calculator(screen, translation_override, missing_dependencies) + if calculator.calc() is True: + return translation_override.translation + + return getattr(self, field) + class UrgentNeedFunction(models.Model): name = models.CharField(max_length=32) diff --git a/programs/programs/translation_overrides/base.py b/programs/programs/translation_overrides/base.py index 18e7a3b1..60fbcc37 100644 --- a/programs/programs/translation_overrides/base.py +++ b/programs/programs/translation_overrides/base.py @@ -1,11 +1,14 @@ from programs.util import Dependencies from screener.models import Screen -from programs.models import TranslationOverride +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from programs.models import TranslationOverride class TranslationOverrideCalculator: dependencies = tuple() - def __init__(self, screen: Screen, translation_override: TranslationOverride, missing_dependencies: Dependencies): + def __init__(self, screen: Screen, translation_override: "TranslationOverride", missing_dependencies: Dependencies): self.screen = screen self.translation_override = translation_override self.missing_dependencies = missing_dependencies diff --git a/screener/views.py b/screener/views.py index 003c2c73..28d598ec 100644 --- a/screener/views.py +++ b/screener/views.py @@ -339,7 +339,7 @@ def sort_first(program): "description": default_message(program.description), "value_type": default_message(program.value_type), "learn_more_link": default_message(program.learn_more_link), - "apply_button_link": default_message(program.apply_button_link), + "apply_button_link": default_message(program.get_translation(screen, missing_dependencies, "apply_button_link")), "legal_status_required": legal_status, "category": default_message(program.category), "estimated_value_override": default_message(program.estimated_value), @@ -368,7 +368,6 @@ def sort_first(program): return eligible_programs, missing_programs - def default_message(translation): translation.set_current_language(settings.LANGUAGE_CODE) d = {"default_message": translation.text, "label": translation.label} From 7ae8b9d764745404b747d38b0f0adac7c65a0852 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Wed, 25 Sep 2024 15:48:52 -0600 Subject: [PATCH 21/55] Override other program translation fields --- screener/views.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/screener/views.py b/screener/views.py index 28d598ec..fbca34c9 100644 --- a/screener/views.py +++ b/screener/views.py @@ -26,7 +26,7 @@ ResultsSerializer, ) from programs.programs.policyengine.policy_engine import calc_pe_eligibility -from programs.util import DependencyError +from programs.util import DependencyError, Dependencies from programs.programs.urgent_needs.urgent_need_functions import urgent_need_functions from programs.models import Document, Navigator, UrgentNeed, Program, Referrer, WarningMessage from django.core.exceptions import ObjectDoesNotExist @@ -325,24 +325,25 @@ def sort_first(program): new=new, ) ) + program_translations = GetProgramTranslation(screen, program, missing_dependencies) data.append( { "program_id": program.id, - "name": default_message(program.name), + "name": program_translations.get_translation("name"), "name_abbreviated": program.name_abbreviated, "external_name": program.external_name, "estimated_value": eligibility["estimated_value"], - "estimated_delivery_time": default_message(program.estimated_delivery_time), - "estimated_application_time": default_message(program.estimated_application_time), - "description_short": default_message(program.description_short), + "estimated_delivery_time": program_translations.get_translation("estimated_delivery_time"), + "estimated_application_time": program_translations.get_translation("estimated_application_time"), + "description_short": program_translations.get_translation("description_short"), "short_name": program.name_abbreviated, - "description": default_message(program.description), - "value_type": default_message(program.value_type), - "learn_more_link": default_message(program.learn_more_link), - "apply_button_link": default_message(program.get_translation(screen, missing_dependencies, "apply_button_link")), + "description": program_translations.get_translation("description"), + "value_type": program_translations.get_translation("value_type"), + "learn_more_link": program_translations.get_translation("learn_more_link"), + "apply_button_link": program_translations.get_translation("apply_button_link"), "legal_status_required": legal_status, - "category": default_message(program.category), - "estimated_value_override": default_message(program.estimated_value), + "category": program_translations.get_translation("category"), + "estimated_value_override": program_translations.get_translation("estimated_value"), "eligible": eligibility["eligible"], "failed_tests": eligibility["failed"], "passed_tests": eligibility["passed"], @@ -368,6 +369,14 @@ def sort_first(program): return eligible_programs, missing_programs +class GetProgramTranslation: + def __init__(self, screen: Screen, program: Program, missing_dependencies: Dependencies): + self.screen = screen + self.program = program + self.missing_dependencies = missing_dependencies + + def get_translation(self, field: str): + return default_message(self.program.get_translation(self.screen, self.missing_dependencies, field)) def default_message(translation): translation.set_current_language(settings.LANGUAGE_CODE) d = {"default_message": translation.text, "label": translation.label} From 090100ded27091d597b87590e020b832f248bde4 Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 26 Sep 2024 09:57:40 -0600 Subject: [PATCH 22/55] pass in eligibility to the eligibility methods --- programs/programs/calc.py | 33 ++++++++++++------- .../co/basic_cash_assistance/calculator.py | 8 ++--- programs/programs/co/cash_back/calculator.py | 8 ++--- .../co/child_care_assistance/calculator.py | 14 +++----- .../co/connect_for_health/calculator.py | 12 ++----- .../dental_health_care_seniors/calculator.py | 13 ++------ .../co/denver_preschool_program/calculator.py | 13 ++------ .../denver_property_tax_relief/calculator.py | 15 +++------ .../co/denver_trash_rebate/calculator.py | 6 +--- .../emergency_rental_assistance/calculator.py | 6 +--- .../co/energy_assistance/calculator.py | 6 +--- .../co/energy_resource_center/calculator.py | 6 +--- .../co/low_wage_covid_relief/calculator.py | 9 ++--- .../adult_with_disability/calculator.py | 13 ++------ .../child_with_disability/calculator.py | 13 ++------ .../co/medicaid/emergency/calculator.py | 13 ++------ .../family_planning_services/calculator.py | 15 +++------ programs/programs/co/my_spark/calculator.py | 13 ++------ programs/programs/co/mydenver/calculator.py | 13 ++------ .../co/nurse_family_partnership/calculator.py | 14 +++----- .../co/nurturing_futures/calculator.py | 6 +--- programs/programs/co/omnisalud/calculator.py | 13 ++------ .../co/property_credit_rebate/calculator.py | 13 ++------ .../co/rental_assistance_grant/calculator.py | 6 +--- .../co/reproductive_health_care/calculator.py | 13 ++------ programs/programs/co/rtdlive/calculator.py | 12 ++----- programs/programs/co/tabor/calculator.py | 9 ++--- programs/programs/co/trua/calculator.py | 6 +--- .../co/universal_preschool/calculator.py | 6 ++-- .../co/utility_bill_pay/calculator.py | 6 +--- .../weatherization_assistance/calculator.py | 6 +--- .../programs/federal/head_start/calculator.py | 13 ++------ .../federal/medicare_savings/calculator.py | 7 ++-- programs/programs/federal/ssdi/calculator.py | 6 ++-- programs/programs/nc/nc_aca/calculator.py | 12 ++----- 35 files changed, 104 insertions(+), 273 deletions(-) diff --git a/programs/programs/calc.py b/programs/programs/calc.py index 1ba32666..8991afe5 100644 --- a/programs/programs/calc.py +++ b/programs/programs/calc.py @@ -114,11 +114,13 @@ def eligible(self) -> Eligibility: """ Combine the eligibility for the household and the members """ - e = self.household_eligible() + + e = Eligibility() one_member_eligible = False for member in self.screen.household_members.all(): - member_eligibility = self.member_eligible(member) + member_eligibility = MemberEligibility(member) + self.member_eligible(member_eligibility) e.add_member_eligibility(member_eligibility) if member_eligibility.eligible: @@ -126,35 +128,42 @@ def eligible(self) -> Eligibility: e.condition(one_member_eligible) + # calculate the household eligibility last so that, + # it has access to the member eligibility + self.household_eligible(e) + return e - def household_eligible(self) -> Eligibility: + def household_eligible(self, e: Eligibility): """ - Returns the `Eligibility` object with whether or not the program is eligible + Updates the eligibility object with the household eligibility """ - return Eligibility() + pass - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - return MemberEligibility(member) + def member_eligible(self, e: HouseholdMember): + """ + Updates the eligibility object with the member eligibility + """ + pass - def value(self, eligibility: Eligibility): + def value(self, e: Eligibility): """ Update the eligibility with household and member values """ - if not eligibility.eligible: + if not e.eligible: # if the household is not eligible, the program has 0 value - eligibility.value = 0 + e.value = 0 return total = self.household_value() - for member_eligibility in eligibility.eligible_members: + for member_eligibility in e.eligible_members: if member_eligibility.eligible: member_value = self.member_value(member_eligibility.member) member_eligibility.value = member_value total += member_value - eligibility.value = total + e.value = total def household_value(self): """ diff --git a/programs/programs/co/basic_cash_assistance/calculator.py b/programs/programs/co/basic_cash_assistance/calculator.py index 498c3ed7..bb9744a8 100644 --- a/programs/programs/co/basic_cash_assistance/calculator.py +++ b/programs/programs/co/basic_cash_assistance/calculator.py @@ -1,6 +1,6 @@ import programs.programs.messages as messages from programs.programs.calc import Eligibility, ProgramCalculator -from programs.co_county_zips import counties_from_screen, counties_from_zip +from programs.co_county_zips import counties_from_screen class BasicCashAssistance(ProgramCalculator): @@ -8,9 +8,7 @@ class BasicCashAssistance(ProgramCalculator): county = "Denver County" dependencies = ["zipcode", "age"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): counties = counties_from_screen(self.screen) in_denver = BasicCashAssistance.county in counties @@ -19,5 +17,3 @@ def household_eligible(self) -> Eligibility: # Has a child num_children = self.screen.num_children() e.condition(num_children >= 1, messages.child()) - - return e diff --git a/programs/programs/co/cash_back/calculator.py b/programs/programs/co/cash_back/calculator.py index fbe3f403..80ed6f62 100644 --- a/programs/programs/co/cash_back/calculator.py +++ b/programs/programs/co/cash_back/calculator.py @@ -1,4 +1,3 @@ -from screener.models import HouseholdMember from programs.programs.calc import MemberEligibility, ProgramCalculator @@ -7,9 +6,8 @@ class CashBack(ProgramCalculator): min_age = 18 dependencies = ["age"] - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member + # age e.condition(member.age >= CashBack.min_age) - - return e diff --git a/programs/programs/co/child_care_assistance/calculator.py b/programs/programs/co/child_care_assistance/calculator.py index 4469da83..b4a9d93d 100644 --- a/programs/programs/co/child_care_assistance/calculator.py +++ b/programs/programs/co/child_care_assistance/calculator.py @@ -1,7 +1,7 @@ from screener.models import HouseholdMember from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from integrations.services.sheets import GoogleSheetsCache -from programs.co_county_zips import counties_from_screen, counties_from_zip +from programs.co_county_zips import counties_from_screen import programs.programs.messages as messages @@ -28,9 +28,7 @@ class ChildCareAssistance(ProgramCalculator): dependencies = ["age", "income_amount", "income_frequency", "zipcode", "household_size"] fpl_limits = CccapFplCache() - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): cccap_county_limits = self.fpl_limits.fetch() # location @@ -60,10 +58,8 @@ def household_eligible(self) -> Eligibility: messages.assets(ChildCareAssistance.asset_limit), ) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # age child_eligible = False @@ -78,8 +74,6 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: e.condition(child_eligible) - return e - def member_value(self, member: HouseholdMember): if member.age <= ChildCareAssistance.max_age_preschool: return ChildCareAssistance.preschool_value diff --git a/programs/programs/co/connect_for_health/calculator.py b/programs/programs/co/connect_for_health/calculator.py index 2b799572..2c5c75cd 100644 --- a/programs/programs/co/connect_for_health/calculator.py +++ b/programs/programs/co/connect_for_health/calculator.py @@ -28,9 +28,7 @@ class ConnectForHealth(ProgramCalculator): ineligible_insurance_types = ["va"] county_values = CFHCache() - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Medicade eligibility e.condition(not medicaid_eligible(self.data), messages.must_not_have_benefit("Medicaid")) @@ -40,10 +38,8 @@ def household_eligible(self) -> Eligibility: gross_income = int(self.screen.calc_gross_income("yearly", ("all",))) e.condition(gross_income < income_band, messages.income(gross_income, income_band)) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # no or private insurance e.condition(member.insurance.has_insurance_types(ConnectForHealth.eligible_insurance_types)) @@ -51,8 +47,6 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: # no va insurance e.condition(not member.insurance.has_insurance_types(ConnectForHealth.ineligible_insurance_types)) - return e - def member_value(self, member: HouseholdMember): values = self.county_values.fetch() county = counties_from_screen(self.screen)[0] diff --git a/programs/programs/co/dental_health_care_seniors/calculator.py b/programs/programs/co/dental_health_care_seniors/calculator.py index 7e3781dc..0fd4433e 100644 --- a/programs/programs/co/dental_health_care_seniors/calculator.py +++ b/programs/programs/co/dental_health_care_seniors/calculator.py @@ -1,5 +1,4 @@ from programs.programs.calc import ProgramCalculator, Eligibility, MemberEligibility -from screener.models import HouseholdMember import programs.programs.messages as messages @@ -10,24 +9,18 @@ class DentalHealthCareSeniors(ProgramCalculator): ineligible_insurance = ["medicaid", "private"] dependencies = ["age", "income_amount", "income_frequency", "insurance", "household_size"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Income test fpl = self.program.fpl.as_dict() gross_income = int(self.screen.calc_gross_income("monthly", ["all"])) income_band = int(DentalHealthCareSeniors.percent_of_fpl * fpl[self.screen.household_size] / 12) e.condition(gross_income <= income_band, messages.income(gross_income, income_band)) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # insurance e.condition(not member.insurance.has_insurance_types(DentalHealthCareSeniors.eligible_insurance)) # age e.condition(member.age >= DentalHealthCareSeniors.min_age) - - return e diff --git a/programs/programs/co/denver_preschool_program/calculator.py b/programs/programs/co/denver_preschool_program/calculator.py index 8ed5d7a7..eedeb6b3 100644 --- a/programs/programs/co/denver_preschool_program/calculator.py +++ b/programs/programs/co/denver_preschool_program/calculator.py @@ -1,7 +1,6 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility import programs.programs.messages as messages from programs.co_county_zips import counties_from_screen -from screener.models import HouseholdMember class DenverPreschoolProgram(ProgramCalculator): @@ -11,19 +10,13 @@ class DenverPreschoolProgram(ProgramCalculator): county = "Denver County" dependencies = ["age", "zipcode"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Lives in Denver counties = counties_from_screen(self.screen) e.condition(DenverPreschoolProgram.county in counties, messages.location()) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # age e.condition(DenverPreschoolProgram.min_age >= member.age >= DenverPreschoolProgram.max_age) - - return e diff --git a/programs/programs/co/denver_property_tax_relief/calculator.py b/programs/programs/co/denver_property_tax_relief/calculator.py index 7a1241c3..f9dafdab 100644 --- a/programs/programs/co/denver_property_tax_relief/calculator.py +++ b/programs/programs/co/denver_property_tax_relief/calculator.py @@ -1,8 +1,7 @@ -from programs.co_county_zips import counties_from_screen, counties_from_zip +from programs.co_county_zips import counties_from_screen from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from integrations.services.sheets import GoogleSheetsCache import programs.programs.messages as messages -from screener.models import HouseholdMember class DenverAmiCache(GoogleSheetsCache): @@ -52,9 +51,7 @@ class DenverPropertyTaxRelief(ProgramCalculator): "relationship", ] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # county counties = counties_from_screen(self.screen) e.condition(DenverPropertyTaxRelief.county in counties, messages.location()) @@ -82,10 +79,8 @@ def household_eligible(self) -> Eligibility: total_income += member.calc_gross_income("yearly", DenverPropertyTaxRelief.income_types) e.condition(total_income <= limit, messages.income(total_income, limit)) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # head or spouse e.condition(member.is_head() or member.is_spouse()) @@ -105,8 +100,6 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: e.condition(other_condition) - return e - def household_value(self): if self.screen.has_expense(["mortgage"]): return DenverPropertyTaxRelief.mortgage_amount diff --git a/programs/programs/co/denver_trash_rebate/calculator.py b/programs/programs/co/denver_trash_rebate/calculator.py index b7fbf4cb..8e25b8da 100644 --- a/programs/programs/co/denver_trash_rebate/calculator.py +++ b/programs/programs/co/denver_trash_rebate/calculator.py @@ -22,9 +22,7 @@ class DenverTrashRebate(ProgramCalculator): expenses = ["rent", "mortgage"] dependencies = ["zipcode", "income_amount", "income_frequency", "household_size"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # county counties = counties_from_screen(self.screen) e.condition(DenverTrashRebate.county in counties, messages.location()) @@ -38,5 +36,3 @@ def household_eligible(self) -> Eligibility: # has rent or mortgage expense has_rent_or_mortgage = self.screen.has_expense([DenverTrashRebate.expenses]) e.condition(has_rent_or_mortgage) - - return e diff --git a/programs/programs/co/emergency_rental_assistance/calculator.py b/programs/programs/co/emergency_rental_assistance/calculator.py index 548a5563..2494a010 100644 --- a/programs/programs/co/emergency_rental_assistance/calculator.py +++ b/programs/programs/co/emergency_rental_assistance/calculator.py @@ -21,9 +21,7 @@ class EmergencyRentalAssistance(ProgramCalculator): dependencies = ["income_amount", "income_frequency", "household_size", "zipcode"] income_cache = EmergencyRentalAssistanceIncomeLimitsCache() - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Income test income_limits = EmergencyRentalAssistance.income_cache.fetch() @@ -42,5 +40,3 @@ def household_eligible(self) -> Eligibility: # has rent expense has_rent = self.screen.has_expense(EmergencyRentalAssistance.expenses) e.condition(has_rent) - - return e diff --git a/programs/programs/co/energy_assistance/calculator.py b/programs/programs/co/energy_assistance/calculator.py index 1a5a278c..a7b40818 100644 --- a/programs/programs/co/energy_assistance/calculator.py +++ b/programs/programs/co/energy_assistance/calculator.py @@ -40,9 +40,7 @@ class EnergyAssistance(ProgramCalculator): expenses = ["rent", "mortgage"] dependencies = ["income_frequency", "income_amount", "zipcode", "household_size"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # income frequency = "monthly" income_types = ["all"] @@ -55,8 +53,6 @@ def household_eligible(self) -> Eligibility: has_rent_or_mortgage = self.screen.has_expense(EnergyAssistance.expenses) e.condition(has_rent_or_mortgage) - return e - def household_value(self): data = self.county_values.fetch() diff --git a/programs/programs/co/energy_resource_center/calculator.py b/programs/programs/co/energy_resource_center/calculator.py index e747a0bb..ae0abe9c 100644 --- a/programs/programs/co/energy_resource_center/calculator.py +++ b/programs/programs/co/energy_resource_center/calculator.py @@ -7,12 +7,8 @@ class EnergyResourceCenter(ProgramCalculator): income_bands = {1: 2880, 2: 3766, 3: 4652, 4: 5539, 5: 6425, 6: 7311, 7: 7477, 8: 7644} dependencies = ["household_size", "income_amount", "income_frequency"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # income gross_income = self.screen.calc_gross_income("monthly", ["all"]) income_band = EnergyResourceCenter.income_bands[self.screen.household_size] e.condition(gross_income <= income_band, messages.income(gross_income, income_band)) - - return e diff --git a/programs/programs/co/low_wage_covid_relief/calculator.py b/programs/programs/co/low_wage_covid_relief/calculator.py index d5f462fb..9156c9ae 100644 --- a/programs/programs/co/low_wage_covid_relief/calculator.py +++ b/programs/programs/co/low_wage_covid_relief/calculator.py @@ -21,9 +21,7 @@ class LowWageCovidRelief(ProgramCalculator): county = "Adams County" dependencies = ["zipode", "household_size", "income_amount", "income_frequency"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # lives in Adams County counties = counties_from_screen(self.screen) @@ -44,7 +42,4 @@ def household_eligible(self) -> Eligibility: income = self.screen.calc_gross_income("monthly", ["all"]) meets_income_limit = income <= income_limit - if not (meets_income_limit or has_benefit): - e.eligible = False - - return e + e.condition(meets_income_limit or has_benefit) diff --git a/programs/programs/co/medicaid/adult_with_disability/calculator.py b/programs/programs/co/medicaid/adult_with_disability/calculator.py index 77275de9..5361e689 100644 --- a/programs/programs/co/medicaid/adult_with_disability/calculator.py +++ b/programs/programs/co/medicaid/adult_with_disability/calculator.py @@ -2,7 +2,6 @@ from programs.programs.co.medicaid.child_with_disability.calculator import MedicaidChildWithDisability from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages -from screener.models import HouseholdMember class MedicaidAdultWithDisability(ProgramCalculator): @@ -16,16 +15,12 @@ class MedicaidAdultWithDisability(ProgramCalculator): dependencies = ["insurance", "age", "household_size", "income_type", "income_amount", "income_frequency"] member_amount = 310 - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Does not qualify for Medicaid e.condition(not medicaid_eligible(self.data), messages.must_not_have_benefit("Medicaid")) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # age e.condition(member.age >= MedicaidChildWithDisability.min_age) @@ -45,5 +40,3 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: unearned_deduction = MedicaidAdultWithDisability.unearned_deduction unearned = int(member.calc_gross_income("yearly", ["unearned"])) - unearned_deduction e.condition(earned + unearned <= income_limit) - - return e diff --git a/programs/programs/co/medicaid/child_with_disability/calculator.py b/programs/programs/co/medicaid/child_with_disability/calculator.py index 2d7fe1e1..5aff3a05 100644 --- a/programs/programs/co/medicaid/child_with_disability/calculator.py +++ b/programs/programs/co/medicaid/child_with_disability/calculator.py @@ -1,7 +1,6 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages -from screener.models import HouseholdMember class MedicaidChildWithDisability(ProgramCalculator): @@ -14,9 +13,7 @@ class MedicaidChildWithDisability(ProgramCalculator): dependencies = ["insurance", "age", "household_size", "income_type", "income_amount", "income_frequency"] member_amount = 200 - def eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Does not qualify for Medicaid e.condition(not medicaid_eligible(self.data), messages.must_not_have_benefit("Medicaid")) @@ -30,10 +27,8 @@ def eligible(self) -> Eligibility: income = (earned + unearned) * MedicaidChildWithDisability.income_percent e.condition(income <= income_limit, messages.income(income, income_limit)) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # age e.condition(member.age <= MedicaidChildWithDisability.max_age) @@ -51,5 +46,3 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: and member.age >= MedicaidChildWithDisability.min_employment_age ) ) - - return e diff --git a/programs/programs/co/medicaid/emergency/calculator.py b/programs/programs/co/medicaid/emergency/calculator.py index 9f59f5c3..150b165b 100644 --- a/programs/programs/co/medicaid/emergency/calculator.py +++ b/programs/programs/co/medicaid/emergency/calculator.py @@ -1,7 +1,6 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages -from screener.models import HouseholdMember class EmergencyMedicaid(ProgramCalculator): @@ -9,18 +8,12 @@ class EmergencyMedicaid(ProgramCalculator): insurance_types = ["none"] dependencies = ["insurance"] - def eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Does qualify for Medicaid e.condition(medicaid_eligible(self.data), messages.must_have_benefit("Medicaid")) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # insurance e.condition(member.insurance.has_insurance_types(EmergencyMedicaid.insurance_types)) - - return e diff --git a/programs/programs/co/medicaid/family_planning_services/calculator.py b/programs/programs/co/medicaid/family_planning_services/calculator.py index 2d61b37d..bab36999 100644 --- a/programs/programs/co/medicaid/family_planning_services/calculator.py +++ b/programs/programs/co/medicaid/family_planning_services/calculator.py @@ -1,7 +1,6 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages -from screener.models import HouseholdMember class FamilyPlanningServices(ProgramCalculator): @@ -10,9 +9,7 @@ class FamilyPlanningServices(ProgramCalculator): fpl_percent = 2.65 dependencies = ["age", "insurance", "income_frequency", "income_amount", "household_size"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Does not have insurance has_no_insurance = False for member in self.screen.household_members.all(): @@ -25,16 +22,14 @@ def household_eligible(self) -> Eligibility: # Income fpl = self.program.fpl income_limit = int( - FamilyPlanningServices.fpl_percent * fpl.get_limit(self.screen.household_size + e.eligible_member_count) + FamilyPlanningServices.fpl_percent * fpl.get_limit(self.screen.household_size + len(e.eligible_members)) ) gross_income = int(self.screen.calc_gross_income("yearly", ["all"])) e.condition(gross_income < income_limit, messages.income(gross_income, income_limit)) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # not pregnant e.condition(not member.pregnant) @@ -44,5 +39,3 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: # head or spouse e.condition(member.is_head() or member.is_spouse()) - - return e diff --git a/programs/programs/co/my_spark/calculator.py b/programs/programs/co/my_spark/calculator.py index b4d09374..50f7ab9a 100644 --- a/programs/programs/co/my_spark/calculator.py +++ b/programs/programs/co/my_spark/calculator.py @@ -1,7 +1,6 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility import programs.programs.messages as messages from programs.co_county_zips import counties_from_screen -from screener.models import HouseholdMember class MySpark(ProgramCalculator): @@ -11,9 +10,7 @@ class MySpark(ProgramCalculator): county = "Denver County" dependencies = ["age", "zipcode"] - def eligible(self) -> Eligibility: - e = Eligibility() - + def eligible(self, e: Eligibility): # Qualify for FRL is_frl_eligible = False for benefit in self.data: @@ -27,12 +24,8 @@ def eligible(self) -> Eligibility: # Denever County e.condition(MySpark.county in counties, messages.location()) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # age e.condition(MySpark.min_age <= member.age <= MySpark.max_age) - - return e diff --git a/programs/programs/co/mydenver/calculator.py b/programs/programs/co/mydenver/calculator.py index 245095ec..54e6c87b 100644 --- a/programs/programs/co/mydenver/calculator.py +++ b/programs/programs/co/mydenver/calculator.py @@ -1,7 +1,6 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from programs.co_county_zips import counties_from_screen import programs.programs.messages as messages -from screener.models import HouseholdMember class MyDenver(ProgramCalculator): @@ -11,9 +10,7 @@ class MyDenver(ProgramCalculator): member_amount = 150 dependencies = ["age", "zipcode"] - def eligible(self) -> Eligibility: - e = Eligibility() - + def eligible(self, e: Eligibility): # location county_eligible = False @@ -23,12 +20,8 @@ def eligible(self) -> Eligibility: county_eligible = True e.condition(county_eligible, messages.location()) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility() + def member_eligible(self, e: MemberEligibility): + member = e.member # age e.condition(MyDenver.child_age_min <= member.age <= MyDenver.child_age_max) - - return e diff --git a/programs/programs/co/nurse_family_partnership/calculator.py b/programs/programs/co/nurse_family_partnership/calculator.py index d1a699ff..29785b52 100644 --- a/programs/programs/co/nurse_family_partnership/calculator.py +++ b/programs/programs/co/nurse_family_partnership/calculator.py @@ -1,5 +1,5 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility -from screener.models import HouseholdMember, Insurance +from screener.models import Insurance class NurseFamilyPartnership(ProgramCalculator): @@ -14,16 +14,12 @@ class NurseFamilyPartnership(ProgramCalculator): "pregnant", ] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # no other children e.condition(self.screen.num_children(child_relationship=NurseFamilyPartnership.child_relationships) == 0) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # pregnant e.condition(member.pregnant) @@ -38,5 +34,3 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: has_wic = self.screen.has_benefit("wic") e.condition(is_income_eligible or has_medicaid or has_wic) - - return e diff --git a/programs/programs/co/nurturing_futures/calculator.py b/programs/programs/co/nurturing_futures/calculator.py index 939764db..dfff5445 100644 --- a/programs/programs/co/nurturing_futures/calculator.py +++ b/programs/programs/co/nurturing_futures/calculator.py @@ -23,9 +23,7 @@ class NurturingFutures(ProgramCalculator): ami_percent = 0.3 amount = 3_600 - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # location counties = counties_from_screen(self.screen) e.condition(NurturingFutures.county in counties, messages.location()) @@ -40,5 +38,3 @@ def household_eligible(self) -> Eligibility: income_limit = NurturingFutures.ami.fetch()[self.screen.household_size - 1] * NurturingFutures.ami_percent income = self.screen.calc_gross_income("yearly", ["all"]) e.condition(income <= income_limit, messages.income(income, income_limit)) - - return e diff --git a/programs/programs/co/omnisalud/calculator.py b/programs/programs/co/omnisalud/calculator.py index d64472fc..4ba4b380 100644 --- a/programs/programs/co/omnisalud/calculator.py +++ b/programs/programs/co/omnisalud/calculator.py @@ -1,6 +1,5 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility import programs.programs.messages as messages -from screener.models import HouseholdMember class OmniSalud(ProgramCalculator): @@ -10,20 +9,14 @@ class OmniSalud(ProgramCalculator): member_amount = 610 * 12 dependencies = ["income_amount", "income_frequency", "household_size", "age", "insurance"] - def eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Income test gross_income = self.screen.calc_gross_income("monthly", ["all"]) income_band = OmniSalud.family_4_limit if self.screen.household_size >= 4 else OmniSalud.individual_limit e.condition(gross_income <= income_band, messages.income(gross_income, income_band)) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # insurance e.condition(member.insurance.has_insurance_types(OmniSalud.insurance)) - - return e diff --git a/programs/programs/co/property_credit_rebate/calculator.py b/programs/programs/co/property_credit_rebate/calculator.py index 7467b6fc..9939a34f 100644 --- a/programs/programs/co/property_credit_rebate/calculator.py +++ b/programs/programs/co/property_credit_rebate/calculator.py @@ -1,6 +1,5 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility import programs.programs.messages as messages -from screener.models import HouseholdMember class PropertyCreditRebate(ProgramCalculator): @@ -11,9 +10,7 @@ class PropertyCreditRebate(ProgramCalculator): income_limit = {"single": 18_026, "married": 23_345} dependencies = ["age", "income_frequency", "income_amount", "relationship"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Income test relationship_status = "single" for _, married_to in self.screen.relationship_map().items(): @@ -30,10 +27,8 @@ def household_eligible(self) -> Eligibility: has_rent_or_mortgage = self.screen.has_expense(PropertyCreditRebate.expenses) e.condition(has_rent_or_mortgage) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # disabled someone_disabled = member.has_disability() and member.age > PropertyCreditRebate.disabled_min_age @@ -42,5 +37,3 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: someone_old_enough = member.age >= PropertyCreditRebate.min_age e.condition(someone_disabled or someone_old_enough) - - return e diff --git a/programs/programs/co/rental_assistance_grant/calculator.py b/programs/programs/co/rental_assistance_grant/calculator.py index 09a0b5bb..b0dec516 100644 --- a/programs/programs/co/rental_assistance_grant/calculator.py +++ b/programs/programs/co/rental_assistance_grant/calculator.py @@ -21,9 +21,7 @@ class RentalAssistanceGrant(ProgramCalculator): dependencies = ["income_amount", "income_frequency", "household_size", "zipcode"] income_limits = RAGCache() - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # income gross_income = int(self.screen.calc_gross_income("yearly", ["all"])) @@ -43,5 +41,3 @@ def household_eligible(self) -> Eligibility: income_limit = -math.inf e.condition(gross_income <= income_limit, messages.income(gross_income, income_limit)) - - return e diff --git a/programs/programs/co/reproductive_health_care/calculator.py b/programs/programs/co/reproductive_health_care/calculator.py index 11255ef3..0c8c0328 100644 --- a/programs/programs/co/reproductive_health_care/calculator.py +++ b/programs/programs/co/reproductive_health_care/calculator.py @@ -1,26 +1,19 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility from programs.programs.helpers import medicaid_eligible import programs.programs.messages as messages -from screener.models import HouseholdMember class ReproductiveHealthCare(ProgramCalculator): amount = 268 dependencies = ["insurance"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Medicade eligibility e.condition(medicaid_eligible(self.data), messages.must_have_benefit("Medicaid")) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # No health insurance has_no_hi = member.insurance.has_insurance_types(("none",)) e.condition(has_no_hi) - - return e diff --git a/programs/programs/co/rtdlive/calculator.py b/programs/programs/co/rtdlive/calculator.py index 3e154cd1..c46a17df 100644 --- a/programs/programs/co/rtdlive/calculator.py +++ b/programs/programs/co/rtdlive/calculator.py @@ -21,9 +21,7 @@ class RtdLive(ProgramCalculator): member_amount = 732 dependencies = ["age", "income_amount", "income_frequency", "zipcode", "household_size"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # location county_eligible = False counties = counties_from_screen(self.screen) @@ -34,10 +32,8 @@ def household_eligible(self) -> Eligibility: e.condition(county_eligible, messages.location()) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # age e.condition(RtdLive.min_age <= member.age <= RtdLive.max_age) @@ -50,8 +46,6 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: e.condition(self._unit_income_eligible(tax_unit)) - return e - def _unit_income_eligible(self, members: list[HouseholdMember]) -> bool: gross_income = 0 for member in members: diff --git a/programs/programs/co/tabor/calculator.py b/programs/programs/co/tabor/calculator.py index 30720c3e..f50e79e6 100644 --- a/programs/programs/co/tabor/calculator.py +++ b/programs/programs/co/tabor/calculator.py @@ -1,4 +1,4 @@ -from programs.programs.calc import MemberEligibility, ProgramCalculator +from programs.programs.calc import ProgramCalculator from screener.models import HouseholdMember @@ -7,9 +7,6 @@ class Tabor(ProgramCalculator): member_amount = 800 dependencies = ["age"] - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) - + def member_eligible(self, member: HouseholdMember, e: Eligibility): + # age e.condition(member.age >= Tabor.min_age) - - return e diff --git a/programs/programs/co/trua/calculator.py b/programs/programs/co/trua/calculator.py index 743db5b0..38b3456c 100644 --- a/programs/programs/co/trua/calculator.py +++ b/programs/programs/co/trua/calculator.py @@ -19,9 +19,7 @@ class Trua(ProgramCalculator): amount = 6_500 dependencies = ["income_amount", "income_frequency", "household_size", "zipcode"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # income gross_income = int(self.screen.calc_gross_income("yearly", ["all"])) income_limit = int(Trua.income_limit[self.screen.household_size]) @@ -34,5 +32,3 @@ def household_eligible(self) -> Eligibility: # rent or mortgage expense has_rent_or_mortgage = self.screen.has_expense(["rent", "mortgage"]) e.condition(has_rent_or_mortgage) - - return e diff --git a/programs/programs/co/universal_preschool/calculator.py b/programs/programs/co/universal_preschool/calculator.py index 534b441c..e9dc1990 100644 --- a/programs/programs/co/universal_preschool/calculator.py +++ b/programs/programs/co/universal_preschool/calculator.py @@ -9,8 +9,8 @@ class UniversalPreschool(ProgramCalculator): amount_by_hours = {"10_hours": 4_837, "15_hours": 6_044, "30_hours": 10_655} dependencies = ["age", "income_amount", "income_frequency", "relationship", "household_size"] - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # qualifying condition qualifying_condition = self._has_qualifying_condition() @@ -19,8 +19,6 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: min_age = UniversalPreschool.qualifying_age if qualifying_condition else UniversalPreschool.age e.condition(min_age <= member.age <= UniversalPreschool.age) - return e - def member_value(self, member: HouseholdMember): qualifying_condition = self._has_qualifying_condition() diff --git a/programs/programs/co/utility_bill_pay/calculator.py b/programs/programs/co/utility_bill_pay/calculator.py index a73fbc48..188378e9 100644 --- a/programs/programs/co/utility_bill_pay/calculator.py +++ b/programs/programs/co/utility_bill_pay/calculator.py @@ -17,9 +17,7 @@ class UtilityBillPay(ProgramCalculator): amount = 350 dependencies = ["household_size", "income_amount", "income_frequency"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # has other programs presumptive_eligible = False for benefit in UtilityBillPay.presumptive_eligibility: @@ -35,5 +33,3 @@ def household_eligible(self) -> Eligibility: # has rent or mortgage expense has_rent_or_mortgage = self.screen.has_expense(["rent", "mortgage"]) e.condition(has_rent_or_mortgage) - - return e diff --git a/programs/programs/co/weatherization_assistance/calculator.py b/programs/programs/co/weatherization_assistance/calculator.py index 0674ebcf..3c89417e 100644 --- a/programs/programs/co/weatherization_assistance/calculator.py +++ b/programs/programs/co/weatherization_assistance/calculator.py @@ -17,9 +17,7 @@ class WeatherizationAssistance(ProgramCalculator): amount = 350 dependencies = ["household_size", "income_amount", "income_frequency"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # income condition income_limit = WeatherizationAssistance.income_limits[self.screen.household_size - 1] income = int(self.screen.calc_gross_income("yearly", ["all"])) @@ -36,5 +34,3 @@ def household_eligible(self) -> Eligibility: # rent or mortgage expense has_rent_or_mortgage = self.screen.has_expense(["rent", "mortgage"]) e.condition(has_rent_or_mortgage) - - return e diff --git a/programs/programs/federal/head_start/calculator.py b/programs/programs/federal/head_start/calculator.py index 10869712..57c4119d 100644 --- a/programs/programs/federal/head_start/calculator.py +++ b/programs/programs/federal/head_start/calculator.py @@ -2,7 +2,6 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility import programs.programs.messages as messages from programs.co_county_zips import counties_from_screen -from screener.models import HouseholdMember class HeadStartCountyEligibleCache(GoogleSheetsCache): @@ -26,9 +25,7 @@ class HeadStart(ProgramCalculator): adams_county = "Adams County" dependencies = ["age", "household_size", "income_frequency", "income_amount", "zipcode"] - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # location counties = counties_from_screen(self.screen) @@ -55,12 +52,8 @@ def household_eligible(self) -> Eligibility: else: e.condition(gross_income < income_limit, messages.income(gross_income, income_limit)) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # age e.condition(HeadStart.min_age >= member.age >= HeadStart.max_age) - - return e diff --git a/programs/programs/federal/medicare_savings/calculator.py b/programs/programs/federal/medicare_savings/calculator.py index 735a4d6b..be0c28d5 100644 --- a/programs/programs/federal/medicare_savings/calculator.py +++ b/programs/programs/federal/medicare_savings/calculator.py @@ -1,5 +1,4 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator -from screener.models import HouseholdMember class MedicareSavings(ProgramCalculator): @@ -16,8 +15,8 @@ class MedicareSavings(ProgramCalculator): member_amount = 175 * 12 dependencies = ["household_assets", "relationship", "income_frequency", "income_amount", "age"] - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # age e.condition(member.age >= MedicareSavings.min_age) @@ -40,5 +39,3 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: max_income = MedicareSavings.income_limit[status] income = member.calc_gross_income("monthly", ("all",)) + spouse_income e.condition(income < max_income) - - return e diff --git a/programs/programs/federal/ssdi/calculator.py b/programs/programs/federal/ssdi/calculator.py index 5017f996..a943ad55 100644 --- a/programs/programs/federal/ssdi/calculator.py +++ b/programs/programs/federal/ssdi/calculator.py @@ -19,8 +19,8 @@ def __init__(self, screen: Screen, program: "Program", data): self.eligible_members = [] super().__init__(screen, program, data) - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # disability e.condition(member.has_disability()) @@ -39,8 +39,6 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: if e.eligible: self.eligible_members.append(member) - return e - def _is_parent_with_disability(self, member: HouseholdMember): # min parent age if member.age < Ssdi.min_age: diff --git a/programs/programs/nc/nc_aca/calculator.py b/programs/programs/nc/nc_aca/calculator.py index 6ecb7de1..0d8c1ffd 100644 --- a/programs/programs/nc/nc_aca/calculator.py +++ b/programs/programs/nc/nc_aca/calculator.py @@ -23,9 +23,7 @@ class ACASubsidiesNC(ProgramCalculator): ineligible_insurance_types = ["va"] county_values = ACACache() - def household_eligible(self) -> Eligibility: - e = Eligibility() - + def household_eligible(self, e: Eligibility): # Medicade eligibility e.condition(not medicaid_eligible(self.data), messages.must_not_have_benefit("Medicaid")) @@ -35,10 +33,8 @@ def household_eligible(self) -> Eligibility: gross_income = int(self.screen.calc_gross_income("yearly", ("all",))) e.condition(gross_income < income_band, messages.income(gross_income, income_band)) - return e - - def member_eligible(self, member: HouseholdMember) -> MemberEligibility: - e = MemberEligibility(member) + def member_eligible(self, e: MemberEligibility): + member = e.member # no or private insurance e.condition(member.insurance.has_insurance_types(ACASubsidiesNC.eligible_insurance_types)) @@ -46,8 +42,6 @@ def member_eligible(self, member: HouseholdMember) -> MemberEligibility: # no va insurance e.condition(not member.insurance.has_insurance_types(ACASubsidiesNC.ineligible_insurance_types)) - return e - def member_value(self, member: HouseholdMember): values = self.county_values.fetch() return values[self.screen.county] * 12 From bad66b0e69ffe92da3f4d25badac5d75936aaf6a Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 26 Sep 2024 15:21:52 -0600 Subject: [PATCH 23/55] update pe eligibility --- programs/models.py | 2 +- programs/programs/calc.py | 40 ++--------- programs/programs/co/pe/member.py | 13 ++-- programs/programs/co/rtdlive/calculator.py | 1 - programs/programs/federal/pe/member.py | 68 ++++++------------- programs/programs/federal/pe/spm.py | 10 +-- .../programs/policyengine/calculators/base.py | 55 ++++++++------- .../programs/policyengine/policy_engine.py | 16 ++--- screener/views.py | 21 +++--- 9 files changed, 84 insertions(+), 142 deletions(-) diff --git a/programs/models.py b/programs/models.py index d68edb07..8029ac7c 100644 --- a/programs/models.py +++ b/programs/models.py @@ -310,7 +310,7 @@ def eligibility(self, screen, data, missing_dependencies: Dependencies): eligibility = calculator.calc() - return eligibility.to_dict() + return eligibility def __str__(self): return self.name.text diff --git a/programs/programs/calc.py b/programs/programs/calc.py index 8991afe5..a62c3b2b 100644 --- a/programs/programs/calc.py +++ b/programs/programs/calc.py @@ -27,8 +27,6 @@ def __init__(self): self.fail_messages = [] self.eligible_members: list[MemberEligibility] = [] self.value: int = 0 - self.eligible_member_count: int = 0 - self.multiple_tax_units: bool = False def condition(self, passed: bool, message=None): """ @@ -64,36 +62,6 @@ def add_member_eligibility(self, member_eligibility: MemberEligibility): """ self.eligible_members.append(member_eligibility) - def member_eligibility(self, members, conditions): - """ - Filter out members that do not meet the condition and make eligibility messages - """ - if len(conditions) <= 0: - self.eligible_member_count = len(members) - return members - - [condition, message] = conditions.pop() - eligible_members = list(filter(condition, members)) - - if message: - self.condition(len(eligible_members) >= 1, message) - elif len(eligible_members) <= 0: - self.eligible = False - - return self.member_eligibility(eligible_members, conditions) - - def to_dict(self): - """ - Return the eligibility as a dictionary - """ - return { - "eligible": self.eligible, - "passed": self.pass_messages, - "failed": self.fail_messages, - "estimated_value": self.value if self.eligible else 0, - "multiple_tax_units": self.multiple_tax_units, - } - class ProgramCalculator: """ @@ -140,7 +108,7 @@ def household_eligible(self, e: Eligibility): """ pass - def member_eligible(self, e: HouseholdMember): + def member_eligible(self, e: MemberEligibility): """ Updates the eligibility object with the member eligibility """ @@ -165,19 +133,19 @@ def value(self, e: Eligibility): e.value = total - def household_value(self): + def household_value(self) -> int: """ Return the value of the program for the household """ return self.amount - def member_value(self, member: HouseholdMember): + def member_value(self, member: HouseholdMember) -> int: """ An eligible household members eligibility """ return self.member_amount - def calc(self): + def calc(self) -> Eligibility: """ Calculate the eligibility and value for a screen """ diff --git a/programs/programs/co/pe/member.py b/programs/programs/co/pe/member.py index 3f232a7d..5c2da4c5 100644 --- a/programs/programs/co/pe/member.py +++ b/programs/programs/co/pe/member.py @@ -2,6 +2,7 @@ from programs.programs.federal.pe.member import Medicaid from programs.programs.federal.pe.member import Wic import programs.programs.policyengine.calculators.dependencies as dependency +from screener.models import HouseholdMember class CoMedicaid(Medicaid): @@ -59,15 +60,13 @@ class Chp(PolicyEngineMembersCalculator): amount = 200 * 12 - def value(self): - total = 0 + def member_value(self, member: HouseholdMember): + chp_eligible = self.sim.value(self.pe_category, str(member.id), "co_chp_eligible", self.pe_period) > 0 - for member in self.screen.household_members.all(): - chp_eligible = self.sim.value(self.pe_category, str(member.id), "co_chp_eligible", self.pe_period) > 0 - if chp_eligible and self.screen.has_insurance_types(("none",)): - total += self.amount + if chp_eligible and self.screen.has_insurance_types(("none",)): + return self.amount - return total + return 0 class FamilyAffordabilityTaxCredit(PolicyEngineMembersCalculator): diff --git a/programs/programs/co/rtdlive/calculator.py b/programs/programs/co/rtdlive/calculator.py index c46a17df..0bd164bb 100644 --- a/programs/programs/co/rtdlive/calculator.py +++ b/programs/programs/co/rtdlive/calculator.py @@ -17,7 +17,6 @@ class RtdLive(ProgramCalculator): min_age = 20 max_age = 64 percent_of_fpl = 2.5 - tax_unit_dependent = True member_amount = 732 dependencies = ["age", "income_amount", "income_frequency", "zipcode", "household_size"] diff --git a/programs/programs/federal/pe/member.py b/programs/programs/federal/pe/member.py index 0b0b5d59..ad0188bc 100644 --- a/programs/programs/federal/pe/member.py +++ b/programs/programs/federal/pe/member.py @@ -1,5 +1,6 @@ from programs.programs.policyengine.calculators.base import PolicyEngineMembersCalculator import programs.programs.policyengine.calculators.dependencies as dependency +from screener.models import HouseholdMember class Wic(PolicyEngineMembersCalculator): @@ -18,17 +19,13 @@ class Wic(PolicyEngineMembersCalculator): *dependency.school_lunch_income, ] pe_outputs = [dependency.member.Wic, dependency.member.WicCategory] - tax_unit_dependent = False - def value(self): - total = 0 + def member_value(self, member: HouseholdMember): + if self.get_member_variable(member.id) <= 0: + return 0 - for member in self.screen.household_members.all(): - if self.get_member_variable(member.id) > 0: - wic_category = self.sim.value("people", str(member.id), "wic_category", self.pe_period) - total += self.wic_categories[wic_category] * 12 - - return total + wic_category = self.sim.value("people", str(member.id), "wic_category", self.pe_period) + return self.wic_categories[wic_category] * 12 class Medicaid(PolicyEngineMembersCalculator): @@ -65,43 +62,22 @@ def _value_by_age(self, age: int): return medicaid_estimated_value - def value(self): - total = 0 - - members = self.screen.household_members.all() - - for member in members: - if self.get_member_variable(member.id) <= 0: - continue - - # here we need to adjust for children as policy engine - # just uses the average which skews very high for adults and - # aged adults - - if self._get_age(member.id) <= 18: - medicaid_estimated_value = self.child_medicaid_average - elif self._get_age(member.id) > 18 and self._get_age(member.id) < 65: - medicaid_estimated_value = self.adult_medicaid_average - elif self._get_age(member.id) >= 65: - medicaid_estimated_value = self.aged_medicaid_average - else: - medicaid_estimated_value = 0 - - total += medicaid_estimated_value - - in_wic_demographic = False - for member in members: - if member.pregnant is True or member.age <= 5: - in_wic_demographic = True - if total == 0 and in_wic_demographic: - if ( - self.screen.has_benefit("medicaid") is True - or self.screen.has_benefit("tanf") is True - or self.screen.has_benefit("snap") is True - ): - total = self.presumptive_amount - - return total + def member_value(self, member: HouseholdMember): + if self.get_member_variable(member.id) <= 0: + return 0 + + # here we need to adjust for children as policy engine + # just uses the average which skews very high for adults and + # aged adults + + if self._get_age(member.id) <= 18: + return self.child_medicaid_average + elif self._get_age(member.id) > 18 and self._get_age(member.id) < 65: + return self.adult_medicaid_average + elif self._get_age(member.id) >= 65: + return self.aged_medicaid_average + + return 0 def _get_age(self, member_id: int) -> int: return self.sim.value(self.pe_category, str(member_id), "age", self.pe_period) diff --git a/programs/programs/federal/pe/spm.py b/programs/programs/federal/pe/spm.py index 1041703a..b946f713 100644 --- a/programs/programs/federal/pe/spm.py +++ b/programs/programs/federal/pe/spm.py @@ -27,7 +27,7 @@ class Snap(PolicyEngineSpmCalulator): def pe_output_period(self): return self.pe_period + "-" + self.pe_period_month - def value(self): + def household_value(self): return int(self.sim.value(self.pe_category, self.pe_sub_category, self.pe_name, self.pe_output_period)) * 12 @@ -38,15 +38,15 @@ class SchoolLunch(PolicyEngineSpmCalulator): amount = 120 - def value(self): - total = 0 + def household_value(self): + value = 0 num_children = self.screen.num_children(3, 18) if self.get_variable() > 0 and num_children > 0: if self.sim.value(self.pe_category, self.pe_sub_category, "school_meal_tier", self.pe_period) != "PAID": - total = SchoolLunch.amount * num_children + value = SchoolLunch.amount * num_children - return total + return value class Tanf(PolicyEngineSpmCalulator): diff --git a/programs/programs/policyengine/calculators/base.py b/programs/programs/policyengine/calculators/base.py index bde120a0..1113e6cd 100644 --- a/programs/programs/policyengine/calculators/base.py +++ b/programs/programs/policyengine/calculators/base.py @@ -1,8 +1,8 @@ from programs.models import Program from programs.programs.policyengine.calculators.constants import MAIN_TAX_UNIT, SECONDARY_TAX_UNIT from programs.util import Dependencies -from screener.models import Screen -from programs.programs.calc import Eligibility, ProgramCalculator +from screener.models import HouseholdMember, Screen +from programs.programs.calc import Eligibility, MemberEligibility, ProgramCalculator from .dependencies.base import PolicyEngineScreenInput from typing import List from ..engines import Sim @@ -20,25 +20,38 @@ class PolicyEngineCalulator(ProgramCalculator): pe_category = "" pe_sub_category = "" - def __init__(self, screen: Screen, program: Program): + def __init__(self, screen: Screen, program: "Program", missing_dependencies: Dependencies): self.screen = screen self.program = program + self.missing_dependencies = missing_dependencies self._sim = None def set_engine(self, sim: Sim): self._sim = sim - def eligible(self) -> Eligibility: - e = Eligibility() + def household_eligible(self, e: Eligibility): + household_value = self.household_value() - e.value = self.value() - e.eligible = e.value > 0 + e.value = household_value - return e + e.condition(household_value > 0) - def value(self): + def member_eligible(self, e: MemberEligibility): + member = e.member + + member_value = self.member_value(member) + + e.value = member_value + + e.condition(member_value > 0) + + def household_value(self): return int(self.get_variable()) + def value(self, e: Eligibility): + for member_eligibility in e.eligible_members: + e.value += member_eligibility.value + @property def pe_period(self) -> str: if self.program.fpl is None: @@ -62,13 +75,12 @@ def get_variable(self): def get_tax_variable(self, unit: str): return self.sim.value(self.pe_category, unit, self.pe_name, self.pe_period) - @classmethod - def can_calc(cls, missing_dependencies: Dependencies): - for input in cls.pe_inputs: - if missing_dependencies.has(*input.dependencies): + def can_calc(self): + for input in self.pe_inputs: + if self.missing_dependencies.has(*input.dependencies): return False - return True + return super().can_calc() class PolicyEngineSpmCalulator(PolicyEngineCalulator): @@ -78,9 +90,8 @@ class PolicyEngineSpmCalulator(PolicyEngineCalulator): class PolicyEngineTaxUnitCalulator(PolicyEngineCalulator): pe_category = "tax_units" - tax_unit_dependent = True - def value(self): + def household_value(self): return self.tax_unit_value(MAIN_TAX_UNIT) + self.tax_unit_value(SECONDARY_TAX_UNIT) def tax_unit_value(self, unit: str): @@ -91,17 +102,13 @@ def tax_unit_value(self, unit: str): class PolicyEngineMembersCalculator(PolicyEngineCalulator): - tax_unit_dependent = True pe_category = "people" - def value(self): - total = 0 - for member in self.screen.household_members.all(): - pe_value = self.get_member_variable(member.id) - - total += pe_value + def household_value(self): + return 0 - return total + def member_value(self, member: HouseholdMember): + return self.get_member_variable(member.id) def get_member_variable(self, member_id: int): return self.sim.value(self.pe_category, str(member_id), self.pe_name, self.pe_period) diff --git a/programs/programs/policyengine/policy_engine.py b/programs/programs/policyengine/policy_engine.py index 5bd4d347..9dc6918d 100644 --- a/programs/programs/policyengine/policy_engine.py +++ b/programs/programs/policyengine/policy_engine.py @@ -1,7 +1,6 @@ from screener.models import HouseholdMember, Screen from .calculators import PolicyEngineCalulator from programs.programs.calc import Eligibility -from programs.util import Dependencies from .calculators.dependencies.base import DependencyError, Member, TaxUnit from typing import List from sentry_sdk import capture_exception, capture_message @@ -11,13 +10,12 @@ def calc_pe_eligibility( screen: Screen, - missing_fields: Dependencies, calculators: dict[str, PolicyEngineCalulator], ) -> dict[str, Eligibility]: valid_programs: dict[str, PolicyEngineCalulator] = {} for name_abbr, calculator in calculators.items(): - if not calculator.can_calc(missing_fields): + if not calculator.can_calc(): continue valid_programs[name_abbr] = calculator @@ -29,7 +27,7 @@ def calc_pe_eligibility( for Method in pe_engines: try: - return all_eligibility(Method(input_data), valid_programs, screen) + return all_eligibility(Method(input_data), valid_programs) except Exception as e: capture_exception(e, level="warning", message="") capture_message(f"Failed to calculate eligibility with the {Method.method_name} method", level="warning") @@ -37,18 +35,14 @@ def calc_pe_eligibility( raise Exception("Failed to calculate Policy Engine eligibility") -def all_eligibility(method: Sim, valid_programs: dict[str, PolicyEngineCalulator], screen: Screen): +def all_eligibility(method: Sim, valid_programs: dict[str, PolicyEngineCalulator]): all_eligibility: dict[str, Eligibility] = {} - has_non_tax_unit_members = screen.has_members_outside_of_tax_unit() for name_abbr, calculator in valid_programs.items(): calculator.set_engine(method) - e = calculator.eligible() + e = calculator.calc() - if calculator.tax_unit_dependent and has_non_tax_unit_members: - e.multiple_tax_units = True - - all_eligibility[name_abbr] = e.to_dict() + all_eligibility[name_abbr] = e return all_eligibility diff --git a/screener/views.py b/screener/views.py index 003c2c73..51b94b0d 100644 --- a/screener/views.py +++ b/screener/views.py @@ -228,9 +228,9 @@ def eligibility_results(screen: Screen, batch=False): program = p if program is not None: - pe_calculators[calculator_name] = Calculator(screen, program) + pe_calculators[calculator_name] = Calculator(screen, program, missing_dependencies) - pe_eligibility = calc_pe_eligibility(screen, missing_dependencies, pe_calculators) + pe_eligibility = calc_pe_eligibility(screen, pe_calculators) pe_programs = pe_calculators.keys() def sort_first(program): @@ -316,12 +316,12 @@ def sort_first(program): name=program.name.text, name_abbreviated=program.name_abbreviated, value_type=program.value_type.text, - estimated_value=eligibility["estimated_value"], + estimated_value=eligibility.value, estimated_delivery_time=program.estimated_delivery_time.text, estimated_application_time=program.estimated_application_time.text, - eligible=eligibility["eligible"], - failed_tests=json.dumps(eligibility["failed"]), - passed_tests=json.dumps(eligibility["passed"]), + eligible=eligibility.eligible, + failed_tests=json.dumps(eligibility.fail_messages), + passed_tests=json.dumps(eligibility.pass_messages), new=new, ) ) @@ -331,7 +331,7 @@ def sort_first(program): "name": default_message(program.name), "name_abbreviated": program.name_abbreviated, "external_name": program.external_name, - "estimated_value": eligibility["estimated_value"], + "estimated_value": eligibility.value, "estimated_delivery_time": default_message(program.estimated_delivery_time), "estimated_application_time": default_message(program.estimated_application_time), "description_short": default_message(program.description_short), @@ -343,15 +343,14 @@ def sort_first(program): "legal_status_required": legal_status, "category": default_message(program.category), "estimated_value_override": default_message(program.estimated_value), - "eligible": eligibility["eligible"], - "failed_tests": eligibility["failed"], - "passed_tests": eligibility["passed"], + "eligible": eligibility.eligible, + "failed_tests": eligibility.fail_messages, + "passed_tests": eligibility.pass_messages, "navigators": [serialized_navigator(navigator) for navigator in navigators], "already_has": screen.has_benefit(program.name_abbreviated), "new": new, "low_confidence": program.low_confidence, "documents": [default_message(d.text) for d in program.documents.all()], - "multiple_tax_units": eligibility["multiple_tax_units"], "warning_messages": [default_message(w.message) for w in warnings], } ) From fb6793841b5123739f56bdbba36fa9ff4af59921 Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 26 Sep 2024 15:23:52 -0600 Subject: [PATCH 24/55] fix tabor eligibilty method --- programs/programs/co/tabor/calculator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/programs/programs/co/tabor/calculator.py b/programs/programs/co/tabor/calculator.py index f50e79e6..d0378d70 100644 --- a/programs/programs/co/tabor/calculator.py +++ b/programs/programs/co/tabor/calculator.py @@ -1,5 +1,4 @@ -from programs.programs.calc import ProgramCalculator -from screener.models import HouseholdMember +from programs.programs.calc import MemberEligibility, ProgramCalculator class Tabor(ProgramCalculator): @@ -7,6 +6,8 @@ class Tabor(ProgramCalculator): member_amount = 800 dependencies = ["age"] - def member_eligible(self, member: HouseholdMember, e: Eligibility): + def member_eligible(self, e: MemberEligibility): + member = e.member + # age e.condition(member.age >= Tabor.min_age) From 339736a219f4845b98ab6f371cdf4907937efcf7 Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 26 Sep 2024 16:12:02 -0600 Subject: [PATCH 25/55] fix some errors --- programs/programs/co/__init__.py | 2 ++ .../programs/co/every_day_eats/__init__.py | 0 .../programs/co/every_day_eats/calculator.py | 23 +++++++++++++++++++ .../co/universal_preschool/calculator.py | 4 ++-- programs/programs/federal/ssdi/calculator.py | 5 ++-- 5 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 programs/programs/co/every_day_eats/__init__.py create mode 100644 programs/programs/co/every_day_eats/calculator.py diff --git a/programs/programs/co/__init__.py b/programs/programs/co/__init__.py index 750670c6..e61a2d8f 100644 --- a/programs/programs/co/__init__.py +++ b/programs/programs/co/__init__.py @@ -11,6 +11,7 @@ from .connect_for_health.calculator import ConnectForHealth from .medicaid.family_planning_services.calculator import FamilyPlanningServices from .denver_preschool_program.calculator import DenverPreschoolProgram +from .every_day_eats.calculator import EveryDayEats from .property_credit_rebate.calculator import PropertyCreditRebate from .universal_preschool.calculator import UniversalPreschool from .my_spark.calculator import MySpark @@ -44,6 +45,7 @@ "cfhc": ConnectForHealth, "fps": FamilyPlanningServices, "dpp": DenverPreschoolProgram, + "ede": EveryDayEats, "cpcr": PropertyCreditRebate, "upk": UniversalPreschool, "myspark": MySpark, diff --git a/programs/programs/co/every_day_eats/__init__.py b/programs/programs/co/every_day_eats/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/programs/programs/co/every_day_eats/calculator.py b/programs/programs/co/every_day_eats/calculator.py new file mode 100644 index 00000000..f6c694fa --- /dev/null +++ b/programs/programs/co/every_day_eats/calculator.py @@ -0,0 +1,23 @@ +from programs.programs.calc import MemberEligibility, ProgramCalculator, Eligibility +import programs.programs.messages as messages + + +class EveryDayEats(ProgramCalculator): + member_amount = 600 + min_age = 60 + percent_of_fpl = 1.3 + dependencies = ["age", "income_amount", "income_frequency", "household_size"] + + def household_eligible(self, e: Eligibility): + # Income + fpl = self.program.fpl.as_dict() + income_limit = EveryDayEats.percent_of_fpl * fpl[self.screen.household_size] + gross_income = self.screen.calc_gross_income("yearly", ["all"]) + + e.condition(gross_income < income_limit, messages.income(gross_income, income_limit)) + + def member_eligible(self, e: MemberEligibility): + member = e.member + + # age + e.condition(member.age >= EveryDayEats.min_age) diff --git a/programs/programs/co/universal_preschool/calculator.py b/programs/programs/co/universal_preschool/calculator.py index e9dc1990..3c51f75e 100644 --- a/programs/programs/co/universal_preschool/calculator.py +++ b/programs/programs/co/universal_preschool/calculator.py @@ -13,14 +13,14 @@ def member_eligible(self, e: MemberEligibility): member = e.member # qualifying condition - qualifying_condition = self._has_qualifying_condition() + qualifying_condition = self._has_qualifying_condition(member) # age min_age = UniversalPreschool.qualifying_age if qualifying_condition else UniversalPreschool.age e.condition(min_age <= member.age <= UniversalPreschool.age) def member_value(self, member: HouseholdMember): - qualifying_condition = self._has_qualifying_condition() + qualifying_condition = self._has_qualifying_condition(member) if not qualifying_condition: return UniversalPreschool.amount_by_hours["15_hours"] diff --git a/programs/programs/federal/ssdi/calculator.py b/programs/programs/federal/ssdi/calculator.py index 495f2de6..54cffa8c 100644 --- a/programs/programs/federal/ssdi/calculator.py +++ b/programs/programs/federal/ssdi/calculator.py @@ -1,4 +1,5 @@ from programs.programs.calc import MemberEligibility, ProgramCalculator +from programs.util import Dependencies from screener.models import HouseholdMember, Screen from typing import TYPE_CHECKING @@ -15,9 +16,9 @@ class Ssdi(ProgramCalculator): parent_relationships = ["spouse", "domesticPartner", "headOfHousehold"] dependencies = ["income_amount", "income_frequency", "household_size"] - def __init__(self, screen: Screen, program: "Program", data): + def __init__(self, screen: Screen, program: "Program", data, missing_dependencies: Dependencies): self.eligible_members = [] - super().__init__(screen, program, data) + super().__init__(screen, program, data, missing_dependencies) def member_eligible(self, e: MemberEligibility): member = e.member From 83fd80a623189c670148a62a51af115638abc802 Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 26 Sep 2024 16:16:41 -0600 Subject: [PATCH 26/55] cache fetch type --- integrations/util/cache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integrations/util/cache.py b/integrations/util/cache.py index 08f12253..1d624f79 100644 --- a/integrations/util/cache.py +++ b/integrations/util/cache.py @@ -1,3 +1,4 @@ +from typing import Any from sentry_sdk import capture_exception import datetime @@ -31,7 +32,7 @@ def should_update(self): return datetime.datetime.now() > self.last_update + datetime.timedelta(seconds=self.expire_time) - def fetch(self): + def fetch(self) -> Any: if self.should_update(): self._update_cache() From 336823d95520b1b3319cbd0ad770753dd824c7fe Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 26 Sep 2024 16:34:17 -0600 Subject: [PATCH 27/55] fix eligibility for pe --- .../programs/policyengine/calculators/base.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/programs/programs/policyengine/calculators/base.py b/programs/programs/policyengine/calculators/base.py index 1113e6cd..aa76d664 100644 --- a/programs/programs/policyengine/calculators/base.py +++ b/programs/programs/policyengine/calculators/base.py @@ -36,15 +36,6 @@ def household_eligible(self, e: Eligibility): e.condition(household_value > 0) - def member_eligible(self, e: MemberEligibility): - member = e.member - - member_value = self.member_value(member) - - e.value = member_value - - e.condition(member_value > 0) - def household_value(self): return int(self.get_variable()) @@ -104,6 +95,15 @@ def tax_unit_value(self, unit: str): class PolicyEngineMembersCalculator(PolicyEngineCalulator): pe_category = "people" + def member_eligible(self, e: MemberEligibility): + member = e.member + + member_value = self.member_value(member) + + e.value = member_value + + e.condition(member_value > 0) + def household_value(self): return 0 From fc2fa1afe63deed40f7b528538cf166e1fc7649f Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 26 Sep 2024 16:44:00 -0600 Subject: [PATCH 28/55] refactor medicaid calculator --- programs/programs/federal/pe/member.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/programs/programs/federal/pe/member.py b/programs/programs/federal/pe/member.py index ad0188bc..9df5c5a1 100644 --- a/programs/programs/federal/pe/member.py +++ b/programs/programs/federal/pe/member.py @@ -44,23 +44,19 @@ class Medicaid(PolicyEngineMembersCalculator): adult_medicaid_average = 0 aged_medicaid_average = 0 - presumptive_amount = 74 * 12 - def _value_by_age(self, age: int): # here we need to adjust for children as policy engine # just uses the average which skews very high for adults and # aged adults if age <= 18: - medicaid_estimated_value = self.child_medicaid_average + return self.child_medicaid_average elif age > 18 and age < 65: - medicaid_estimated_value = self.adult_medicaid_average + return self.adult_medicaid_average elif age >= 65: - medicaid_estimated_value = self.aged_medicaid_average - else: - medicaid_estimated_value = 0 + return self.aged_medicaid_average - return medicaid_estimated_value + return 0 def member_value(self, member: HouseholdMember): if self.get_member_variable(member.id) <= 0: @@ -69,15 +65,7 @@ def member_value(self, member: HouseholdMember): # here we need to adjust for children as policy engine # just uses the average which skews very high for adults and # aged adults - - if self._get_age(member.id) <= 18: - return self.child_medicaid_average - elif self._get_age(member.id) > 18 and self._get_age(member.id) < 65: - return self.adult_medicaid_average - elif self._get_age(member.id) >= 65: - return self.aged_medicaid_average - - return 0 + return self._value_by_age(self._get_age(member.id)) def _get_age(self, member_id: int) -> int: return self.sim.value(self.pe_category, str(member_id), "age", self.pe_period) From 8b36f185326224c1a3fd45dfd5986232e2a74dda Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 26 Sep 2024 16:52:20 -0600 Subject: [PATCH 29/55] fix pe calc to accuratly account for member eligibility --- .../programs/policyengine/calculators/base.py | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/programs/programs/policyengine/calculators/base.py b/programs/programs/policyengine/calculators/base.py index aa76d664..8912fbd5 100644 --- a/programs/programs/policyengine/calculators/base.py +++ b/programs/programs/policyengine/calculators/base.py @@ -1,6 +1,6 @@ from programs.models import Program from programs.programs.policyengine.calculators.constants import MAIN_TAX_UNIT, SECONDARY_TAX_UNIT -from programs.util import Dependencies +from programs.util import Dependencies, DependencyError from screener.models import HouseholdMember, Screen from programs.programs.calc import Eligibility, MemberEligibility, ProgramCalculator from .dependencies.base import PolicyEngineScreenInput @@ -29,12 +29,27 @@ def __init__(self, screen: Screen, program: "Program", missing_dependencies: Dep def set_engine(self, sim: Sim): self._sim = sim + def eligible(self) -> Eligibility: + e = super().eligible() + + self.value(e) + + e.eligible = e.value > 0 + + return e + def household_eligible(self, e: Eligibility): household_value = self.household_value() e.value = household_value - e.condition(household_value > 0) + def member_eligible(self, e: MemberEligibility): + member = e.member + + member_value = self.member_value(member) + + e.value = member_value + e.condition(member_value > 0) def household_value(self): return int(self.get_variable()) @@ -43,6 +58,14 @@ def value(self, e: Eligibility): for member_eligibility in e.eligible_members: e.value += member_eligibility.value + def calc(self) -> Eligibility: + if not self.can_calc(): + raise DependencyError() + + eligibility = self.eligible() + + return eligibility + @property def pe_period(self) -> str: if self.program.fpl is None: @@ -95,15 +118,6 @@ def tax_unit_value(self, unit: str): class PolicyEngineMembersCalculator(PolicyEngineCalulator): pe_category = "people" - def member_eligible(self, e: MemberEligibility): - member = e.member - - member_value = self.member_value(member) - - e.value = member_value - - e.condition(member_value > 0) - def household_value(self): return 0 From 2f27866b430a176e5993b8216d3d56af1272cf9b Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 26 Sep 2024 17:21:25 -0600 Subject: [PATCH 30/55] fix ssdi --- programs/programs/federal/ssdi/calculator.py | 12 +++++++++++- screener/views.py | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/programs/programs/federal/ssdi/calculator.py b/programs/programs/federal/ssdi/calculator.py index 54cffa8c..f049ab78 100644 --- a/programs/programs/federal/ssdi/calculator.py +++ b/programs/programs/federal/ssdi/calculator.py @@ -80,15 +80,25 @@ def _child_eligible(self, member: HouseholdMember): return False + def _parents_with_disability_ssdi_value(self): + total = 0 + for member in self.screen.household_members.all(): + if not self._is_parent_with_disability(member): + continue + total += member.calc_gross_income("monthly", ("sSDisability",)) + + return total + def household_value(self): # NOTE: use household value because the total child value has a cap child_value = 0 adult_value = 0 + child_ssdi_value = (self._parents_with_disability_ssdi_value() or Ssdi.amount) / 2 for member in self.eligible_members: if member.age >= Ssdi.min_age: adult_value += Ssdi.amount else: - child_value += Ssdi.amount / 2 + child_value += child_ssdi_value total_value = adult_value + min(child_value, Ssdi.amount / 2) diff --git a/screener/views.py b/screener/views.py index 780908fb..e6e6e860 100644 --- a/screener/views.py +++ b/screener/views.py @@ -277,6 +277,11 @@ def sort_first(program): warnings = [] navigators = [] + try: + print(program.name.text, eligibility["estimated_value"], eligibility["eligible"]) + except: + print(program.name.text, eligibility.value, eligibility.eligible) + # don't calculate navigator and warnings for ineligible programs if eligibility.eligible: all_navigators = program.navigator.all() From 15d5037dd37f922d5e7d61e2d90f2cf61c7c48bb Mon Sep 17 00:00:00 2001 From: h0und <79583632+msrezaie@users.noreply.github.com> Date: Fri, 27 Sep 2024 12:10:18 -0400 Subject: [PATCH 31/55] made snap spm calculators state specific --- programs/programs/co/pe/__init__.py | 5 +++++ programs/programs/co/pe/spm.py | 9 +++++++++ programs/programs/federal/pe/spm.py | 1 - programs/programs/nc/pe/__init__.py | 3 +++ programs/programs/nc/pe/spm.py | 10 ++++++++++ programs/programs/policyengine/calculators/__init__.py | 6 ++++-- .../policyengine/calculators/dependencies/spm.py | 7 +++++++ screener/models.py | 2 ++ 8 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 programs/programs/nc/pe/spm.py diff --git a/programs/programs/co/pe/__init__.py b/programs/programs/co/pe/__init__.py index 4c6863e5..48290f64 100644 --- a/programs/programs/co/pe/__init__.py +++ b/programs/programs/co/pe/__init__.py @@ -1,3 +1,4 @@ +from programs.programs.co.pe import spm import programs.programs.co.pe.tax as tax import programs.programs.co.pe.member as member from programs.programs.policyengine.calculators.base import PolicyEngineCalulator @@ -17,6 +18,10 @@ "coctc": tax.Coctc, } +co_spm_calculators = { + "co_snap": spm.CoSnap, +} + co_pe_calculators: dict[str, type[PolicyEngineCalulator]] = { **co_member_calculators, **co_tax_unit_calculators, diff --git a/programs/programs/co/pe/spm.py b/programs/programs/co/pe/spm.py index e69de29b..86091405 100644 --- a/programs/programs/co/pe/spm.py +++ b/programs/programs/co/pe/spm.py @@ -0,0 +1,9 @@ +import programs.programs.policyengine.calculators.dependencies as dependency +from programs.programs.federal.pe.spm import Snap + + +class CoSnap(Snap): + pe_inputs = [ + *Snap.pe_inputs, + dependency.spm.ElectricityExpenseDependency, + ] diff --git a/programs/programs/federal/pe/spm.py b/programs/programs/federal/pe/spm.py index cc16ceea..6d5e2fb1 100644 --- a/programs/programs/federal/pe/spm.py +++ b/programs/programs/federal/pe/spm.py @@ -16,7 +16,6 @@ class Snap(PolicyEngineSpmCalulator): dependency.spm.HasPhoneExpenseDependency, dependency.spm.HasHeatingCoolingExpenseDependency, dependency.spm.HeatingCoolingExpenseDependency, - dependency.spm.ElectricityExpenseDependency, dependency.spm.SnapEarnedIncomeDependency, dependency.spm.MeetsSnapAssetTestDependency, dependency.spm.SnapDependentCareDeductionDependency, diff --git a/programs/programs/nc/pe/__init__.py b/programs/programs/nc/pe/__init__.py index f215fd61..75f27edd 100644 --- a/programs/programs/nc/pe/__init__.py +++ b/programs/programs/nc/pe/__init__.py @@ -1,10 +1,13 @@ +from programs.programs.nc.pe import spm import programs.programs.nc.pe.member as member from programs.programs.policyengine.calculators.base import PolicyEngineCalulator nc_member_calculators = {"nc_medicaid": member.NcMedicaid, "nc_wic": member.NcWic} +nc_spm_calculators = {"nc_snap": spm.NcSnap} nc_pe_calculators: dict[str, type[PolicyEngineCalulator]] = { **nc_member_calculators, + **nc_spm_calculators, } diff --git a/programs/programs/nc/pe/spm.py b/programs/programs/nc/pe/spm.py new file mode 100644 index 00000000..267e292e --- /dev/null +++ b/programs/programs/nc/pe/spm.py @@ -0,0 +1,10 @@ +import programs.programs.policyengine.calculators.dependencies as dependency +from programs.programs.federal.pe.spm import Snap + + +class NcSnap(Snap): + pe_inputs = [ + *Snap.pe_inputs, + dependency.spm.WaterExpenseDependency, + dependency.spm.PhoneExpenseDependency, + ] diff --git a/programs/programs/policyengine/calculators/__init__.py b/programs/programs/policyengine/calculators/__init__.py index 7adce32a..e304c4f5 100644 --- a/programs/programs/policyengine/calculators/__init__.py +++ b/programs/programs/policyengine/calculators/__init__.py @@ -3,8 +3,8 @@ federal_spm_unit_calculators, federal_tax_unit_calculators, ) -from programs.programs.co.pe import co_member_calculators, co_tax_unit_calculators -from programs.programs.nc.pe import nc_member_calculators +from programs.programs.co.pe import co_member_calculators, co_tax_unit_calculators, co_spm_calculators +from programs.programs.nc.pe import nc_member_calculators, nc_spm_calculators from .base import ( PolicyEngineMembersCalculator, PolicyEngineSpmCalulator, @@ -21,6 +21,8 @@ all_spm_unit_calculators: dict[str, type[PolicyEngineSpmCalulator]] = { **federal_spm_unit_calculators, + **nc_spm_calculators, + **co_spm_calculators, } all_tax_unit_calculators: dict[str, type[PolicyEngineTaxUnitCalulator]] = { diff --git a/programs/programs/policyengine/calculators/dependencies/spm.py b/programs/programs/policyengine/calculators/dependencies/spm.py index fa59adcd..495e3bb8 100644 --- a/programs/programs/policyengine/calculators/dependencies/spm.py +++ b/programs/programs/policyengine/calculators/dependencies/spm.py @@ -126,6 +126,13 @@ def value(self): return self.screen.calc_expenses("yearly", ["otherUtilities"]) +class WaterExpenseDependency(SpmUnit): + field = "water_expense" + + def value(self): + return self.screen.calc_expenses("yearly", ["otherUtilities"]) + + class SnapEmergencyAllotmentDependency(SpmUnit): field = "snap_emergency_allotment" diff --git a/screener/models.py b/screener/models.py index 868daa44..5b57f6ee 100644 --- a/screener/models.py +++ b/screener/models.py @@ -266,6 +266,8 @@ def has_benefit(self, name_abbreviated): "wic": self.has_wic, "nc_wic": self.has_wic, "snap": self.has_snap, + "co_snap": self.has_snap, + "nc_snap": self.has_snap, "lifeline": self.has_lifeline, "acp": self.has_acp, "eitc": self.has_eitc, From ead5167ba06d12eae9a94fc3b36c5e7aef43da91 Mon Sep 17 00:00:00 2001 From: Caleb Date: Fri, 27 Sep 2024 10:10:58 -0600 Subject: [PATCH 32/55] remove print statement --- screener/views.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/screener/views.py b/screener/views.py index e6e6e860..780908fb 100644 --- a/screener/views.py +++ b/screener/views.py @@ -277,11 +277,6 @@ def sort_first(program): warnings = [] navigators = [] - try: - print(program.name.text, eligibility["estimated_value"], eligibility["eligible"]) - except: - print(program.name.text, eligibility.value, eligibility.eligible) - # don't calculate navigator and warnings for ineligible programs if eligibility.eligible: all_navigators = program.navigator.all() From 2b53eed15333657839c238f247dd7f1ac5a114dc Mon Sep 17 00:00:00 2001 From: Caleb Date: Fri, 27 Sep 2024 10:24:06 -0600 Subject: [PATCH 33/55] fix age range for dpp and chs --- programs/programs/co/denver_preschool_program/calculator.py | 2 +- programs/programs/federal/head_start/calculator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/programs/co/denver_preschool_program/calculator.py b/programs/programs/co/denver_preschool_program/calculator.py index eedeb6b3..35774863 100644 --- a/programs/programs/co/denver_preschool_program/calculator.py +++ b/programs/programs/co/denver_preschool_program/calculator.py @@ -19,4 +19,4 @@ def member_eligible(self, e: MemberEligibility): member = e.member # age - e.condition(DenverPreschoolProgram.min_age >= member.age >= DenverPreschoolProgram.max_age) + e.condition(DenverPreschoolProgram.min_age <= member.age <= DenverPreschoolProgram.max_age) diff --git a/programs/programs/federal/head_start/calculator.py b/programs/programs/federal/head_start/calculator.py index 57c4119d..df636aa3 100644 --- a/programs/programs/federal/head_start/calculator.py +++ b/programs/programs/federal/head_start/calculator.py @@ -56,4 +56,4 @@ def member_eligible(self, e: MemberEligibility): member = e.member # age - e.condition(HeadStart.min_age >= member.age >= HeadStart.max_age) + e.condition(HeadStart.min_age <= member.age <= HeadStart.max_age) From d155f537e7e8130105288d9181a5742b6bc2fef5 Mon Sep 17 00:00:00 2001 From: Caleb Date: Fri, 27 Sep 2024 12:26:34 -0600 Subject: [PATCH 34/55] pass in a dict of eligibility instead of a list. also add cfhc no chp per member condition --- programs/programs/calc.py | 2 +- programs/programs/co/connect_for_health/calculator.py | 5 +++++ .../programs/co/low_wage_covid_relief/calculator.py | 9 ++++----- programs/programs/co/my_spark/calculator.py | 6 ++---- programs/programs/helpers.py | 11 +++++++---- screener/views.py | 8 ++++++-- 6 files changed, 25 insertions(+), 16 deletions(-) diff --git a/programs/programs/calc.py b/programs/programs/calc.py index a62c3b2b..3e6544e8 100644 --- a/programs/programs/calc.py +++ b/programs/programs/calc.py @@ -72,7 +72,7 @@ class ProgramCalculator: amount = 0 member_amount = 0 - def __init__(self, screen: Screen, program: "Program", data, missing_dependencies: Dependencies): + def __init__(self, screen: Screen, program: "Program", data: dict[str, Eligibility], missing_dependencies: Dependencies): self.screen = screen self.program = program self.data = data diff --git a/programs/programs/co/connect_for_health/calculator.py b/programs/programs/co/connect_for_health/calculator.py index 2c5c75cd..0033024b 100644 --- a/programs/programs/co/connect_for_health/calculator.py +++ b/programs/programs/co/connect_for_health/calculator.py @@ -41,6 +41,11 @@ def household_eligible(self, e: Eligibility): def member_eligible(self, e: MemberEligibility): member = e.member + # not CHP+ eligible + for member_eligibility in self.data["chp"].eligible_members: + if member_eligibility.member.id == member.id: + e.condition(not member_eligibility.eligible) + # no or private insurance e.condition(member.insurance.has_insurance_types(ConnectForHealth.eligible_insurance_types)) diff --git a/programs/programs/co/low_wage_covid_relief/calculator.py b/programs/programs/co/low_wage_covid_relief/calculator.py index 9156c9ae..f2b71180 100644 --- a/programs/programs/co/low_wage_covid_relief/calculator.py +++ b/programs/programs/co/low_wage_covid_relief/calculator.py @@ -29,12 +29,11 @@ def household_eligible(self, e: Eligibility): e.condition(in_adams_county, messages.location()) # other benefits - for benefit in LowWageCovidRelief.auto_eligible_benefits: - has_benefit = self.screen.has_benefit(benefit) + has_benefit = False - for benefit in self.data: - if benefit["name_abbreviated"] in LowWageCovidRelief.auto_eligible_benefits and benefit["eligible"]: - has_benefit = True + for benefit in LowWageCovidRelief.auto_eligible_benefits: + if self.screen.has_benefit(benefit) or self.data[benefit].eligible: + has_benefit = self.screen.has_benefit(benefit) break # meets income limit diff --git a/programs/programs/co/my_spark/calculator.py b/programs/programs/co/my_spark/calculator.py index fd141019..7bc00b68 100644 --- a/programs/programs/co/my_spark/calculator.py +++ b/programs/programs/co/my_spark/calculator.py @@ -13,10 +13,8 @@ class MySpark(ProgramCalculator): def household_eligible(self, e: Eligibility): # Qualify for FRL is_frl_eligible = False - for benefit in self.data: - if benefit["name_abbreviated"] == "nslp": - is_frl_eligible = benefit["eligible"] - break + + is_frl_eligible = self.data["nslp"].eligible e.condition(is_frl_eligible, messages.must_have_benefit("Free or Reduced Lunch")) counties = counties_from_screen(self.screen) diff --git a/programs/programs/helpers.py b/programs/programs/helpers.py index cfd41626..0bc927c5 100644 --- a/programs/programs/helpers.py +++ b/programs/programs/helpers.py @@ -1,7 +1,10 @@ +from programs.programs.calc import Eligibility + + STATE_MEDICAID_OPTIONS = ("co_medicaid", "nc_medicaid") -def medicaid_eligible(data): - for program in data: - if program["name_abbreviated"] in STATE_MEDICAID_OPTIONS: - return program["eligible"] +def medicaid_eligible(data: dict[str, Eligibility]): + for name in STATE_MEDICAID_OPTIONS: + if name in data: + return data[name].eligible diff --git a/screener/views.py b/screener/views.py index 780908fb..7533752d 100644 --- a/screener/views.py +++ b/screener/views.py @@ -234,7 +234,7 @@ def eligibility_results(screen: Screen, batch=False): pe_programs = pe_calculators.keys() def sort_first(program): - calc_first = ("tanf", "ssi", "nslp", "leap", *STATE_MEDICAID_OPTIONS) + calc_first = ("tanf", "ssi", "nslp", "leap", "chp", *STATE_MEDICAID_OPTIONS) if program.name_abbreviated in calc_first: return 0 @@ -248,11 +248,13 @@ def sort_first(program): program_snapshots = [] + program_eligibility = {} + for program in all_programs: skip = False if program.name_abbreviated not in pe_programs and program.active: try: - eligibility = program.eligibility(screen, data, missing_dependencies) + eligibility = program.eligibility(screen, program_eligibility, missing_dependencies) except DependencyError: missing_programs = True continue @@ -263,6 +265,8 @@ def sort_first(program): eligibility = pe_eligibility[program.name_abbreviated] + program_eligibility[program.name_abbreviated] = eligibility + if previous_snapshot is not None: new = True for previous_snapshot in previous_results: From 89e74c72b072efe02601a0a8b5d1117cd87488da Mon Sep 17 00:00:00 2001 From: Caleb Date: Fri, 27 Sep 2024 12:26:44 -0600 Subject: [PATCH 35/55] format --- programs/programs/calc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/programs/programs/calc.py b/programs/programs/calc.py index 3e6544e8..527f83ac 100644 --- a/programs/programs/calc.py +++ b/programs/programs/calc.py @@ -72,7 +72,9 @@ class ProgramCalculator: amount = 0 member_amount = 0 - def __init__(self, screen: Screen, program: "Program", data: dict[str, Eligibility], missing_dependencies: Dependencies): + def __init__( + self, screen: Screen, program: "Program", data: dict[str, Eligibility], missing_dependencies: Dependencies + ): self.screen = screen self.program = program self.data = data From 1fab72be8d21327cc364d3a12f118f93fbe65efe Mon Sep 17 00:00:00 2001 From: Caleb Date: Fri, 27 Sep 2024 14:20:48 -0600 Subject: [PATCH 36/55] refetch fpl year if it is not cached --- programs/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/programs/models.py b/programs/models.py index f2e875e4..6046fd40 100644 --- a/programs/models.py +++ b/programs/models.py @@ -77,7 +77,12 @@ def get_limit(self, household_size: int): return limits[self.MAX_DEFINED_SIZE] + limits["additional"] * additional_member_count def as_dict(self): - return self.fpl_cache.fetch()[self.period] + try: + return self.fpl_cache.fetch()[self.period] + except KeyError: + # the year is not cached, so invalidate the cache + self.fpl_cache.invalid = True + return self.fpl_cache.fetch()[self.period] def __str__(self): return self.year From f1658d3a7bbbcbe4c75bc38ce118235488646215 Mon Sep 17 00:00:00 2001 From: Caleb Date: Fri, 27 Sep 2024 14:21:18 -0600 Subject: [PATCH 37/55] change the period of the fpl when importing --- programs/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/programs/models.py b/programs/models.py index 6046fd40..6dddc753 100644 --- a/programs/models.py +++ b/programs/models.py @@ -221,6 +221,8 @@ def from_model_data(self, data: DataType): if fpl is not None: try: fpl_instance = FederalPoveryLimit.objects.get(year=fpl["year"]) + fpl_instance.period = fpl["period"] + fpl_instance.save() except FederalPoveryLimit.DoesNotExist: fpl_instance = FederalPoveryLimit.objects.create(year=fpl["year"], period=fpl["period"]) program.fpl = fpl_instance From 75bad35598a940e4d7be21d6f30693c6b767cf84 Mon Sep 17 00:00:00 2001 From: Mo S Rezaie <79583632+msrezaie@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:09:21 -0400 Subject: [PATCH 38/55] Update programs/programs/co/pe/spm.py Co-authored-by: CalebPena <62856626+CalebPena@users.noreply.github.com> --- programs/programs/co/pe/spm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/programs/programs/co/pe/spm.py b/programs/programs/co/pe/spm.py index a4b84aa5..eaf72737 100644 --- a/programs/programs/co/pe/spm.py +++ b/programs/programs/co/pe/spm.py @@ -6,6 +6,7 @@ class CoSnap(Snap): pe_inputs = [ *Snap.pe_inputs, dependency.spm.ElectricityExpenseDependency, + dependency.household.CoStateCode, ] From 60bbe644df621997f473483f5e8edc032793451d Mon Sep 17 00:00:00 2001 From: Mo S Rezaie <79583632+msrezaie@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:09:32 -0400 Subject: [PATCH 39/55] Update programs/programs/nc/pe/spm.py Co-authored-by: CalebPena <62856626+CalebPena@users.noreply.github.com> --- programs/programs/nc/pe/spm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/programs/programs/nc/pe/spm.py b/programs/programs/nc/pe/spm.py index 50cf7f4b..3bbaf416 100644 --- a/programs/programs/nc/pe/spm.py +++ b/programs/programs/nc/pe/spm.py @@ -7,6 +7,7 @@ class NcSnap(Snap): *Snap.pe_inputs, dependency.spm.WaterExpenseDependency, dependency.spm.PhoneExpenseDependency, + dependency.household.NcStateCode, ] From 1f5c7984dd02c8dfa02ec894a42a0bccc18dc8a1 Mon Sep 17 00:00:00 2001 From: h0und <79583632+msrezaie@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:45:34 -0400 Subject: [PATCH 40/55] refined dependencies list --- programs/programs/co/pe/spm.py | 1 - programs/programs/federal/pe/spm.py | 2 ++ programs/programs/nc/pe/spm.py | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/programs/programs/co/pe/spm.py b/programs/programs/co/pe/spm.py index eaf72737..e75df5c8 100644 --- a/programs/programs/co/pe/spm.py +++ b/programs/programs/co/pe/spm.py @@ -5,7 +5,6 @@ class CoSnap(Snap): pe_inputs = [ *Snap.pe_inputs, - dependency.spm.ElectricityExpenseDependency, dependency.household.CoStateCode, ] diff --git a/programs/programs/federal/pe/spm.py b/programs/programs/federal/pe/spm.py index 9435507c..c69a0579 100644 --- a/programs/programs/federal/pe/spm.py +++ b/programs/programs/federal/pe/spm.py @@ -19,6 +19,8 @@ class Snap(PolicyEngineSpmCalulator): dependency.spm.SnapEarnedIncomeDependency, dependency.spm.MeetsSnapAssetTestDependency, dependency.spm.SnapDependentCareDeductionDependency, + dependency.spm.WaterExpenseDependency, + dependency.spm.PhoneExpenseDependency ] pe_outputs = [dependency.spm.Snap] pe_period_month = "01" diff --git a/programs/programs/nc/pe/spm.py b/programs/programs/nc/pe/spm.py index 3bbaf416..2470157d 100644 --- a/programs/programs/nc/pe/spm.py +++ b/programs/programs/nc/pe/spm.py @@ -5,8 +5,6 @@ class NcSnap(Snap): pe_inputs = [ *Snap.pe_inputs, - dependency.spm.WaterExpenseDependency, - dependency.spm.PhoneExpenseDependency, dependency.household.NcStateCode, ] From 2576ff94af73a93e2999a50884f4f5a6d7e0c2d0 Mon Sep 17 00:00:00 2001 From: h0und <79583632+msrezaie@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:54:13 -0400 Subject: [PATCH 41/55] fixed formatting --- programs/programs/federal/pe/spm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/programs/federal/pe/spm.py b/programs/programs/federal/pe/spm.py index c69a0579..e8e067bf 100644 --- a/programs/programs/federal/pe/spm.py +++ b/programs/programs/federal/pe/spm.py @@ -20,7 +20,7 @@ class Snap(PolicyEngineSpmCalulator): dependency.spm.MeetsSnapAssetTestDependency, dependency.spm.SnapDependentCareDeductionDependency, dependency.spm.WaterExpenseDependency, - dependency.spm.PhoneExpenseDependency + dependency.spm.PhoneExpenseDependency, ] pe_outputs = [dependency.spm.Snap] pe_period_month = "01" From aaf390dcb3656282d7c93dccfeac3714aca1ec52 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Mon, 30 Sep 2024 13:41:59 -0600 Subject: [PATCH 42/55] Refactor get_translation method to only use active translation_overrides --- programs/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/models.py b/programs/models.py index 1b21b3ca..6715bb1e 100644 --- a/programs/models.py +++ b/programs/models.py @@ -331,7 +331,7 @@ def get_translation(self, screen, missing_dependencies: Dependencies, field:str) if field not in Program.objects.translated_fields: raise ValueError(f"translation with name {field} does not exist") - translation_overrides: list[TranslationOverride] = self.translation_overrides.all() + translation_overrides: list[TranslationOverride] = self.translation_overrides.filter(active=True) for translation_override in translation_overrides: if translation_override.field != field: continue From 14a201a067f3123143f870480c505a7df739e069 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Mon, 30 Sep 2024 14:03:52 -0600 Subject: [PATCH 43/55] Add Translation Overrides link to the Navigation Bar --- benefits/settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/benefits/settings.py b/benefits/settings.py index 49c8eb10..b7a06b3b 100644 --- a/benefits/settings.py +++ b/benefits/settings.py @@ -274,6 +274,11 @@ "icon": "tune", "link": reverse_lazy("admin:configuration_configuration_changelist"), }, + { + "title": _("Translation Overrides"), + "icon": "letter_switch", + "link": reverse_lazy("admin:programs_translationoverride_changelist"), + }, ], }, { From d23d422b87fbf36fcb3fa8441567344f1c357c66 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Mon, 30 Sep 2024 14:45:37 -0600 Subject: [PATCH 44/55] Add TranslationOverrideManager, add objects to TranslationOverride --- ...r_translationoverride_counties_and_more.py | 31 +++++++++++++++ programs/models.py | 39 +++++++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 programs/migrations/0089_alter_translationoverride_counties_and_more.py diff --git a/programs/migrations/0089_alter_translationoverride_counties_and_more.py b/programs/migrations/0089_alter_translationoverride_counties_and_more.py new file mode 100644 index 00000000..f0b09f8b --- /dev/null +++ b/programs/migrations/0089_alter_translationoverride_counties_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.14 on 2024-09-30 20:42 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("programs", "0088_translationoverride"), + ] + + operations = [ + migrations.AlterField( + model_name="translationoverride", + name="counties", + field=models.ManyToManyField( + blank=True, related_name="translation_overrides", to="programs.county" + ), + ), + migrations.AlterField( + model_name="translationoverride", + name="program", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="translation_overrides", + to="programs.program", + ), + ), + ] diff --git a/programs/models.py b/programs/models.py index 6715bb1e..7875a2fd 100644 --- a/programs/models.py +++ b/programs/models.py @@ -656,7 +656,7 @@ def new_warning(self, calculator, external_name=None): ) for [field, translation] in translations.items(): - translation.label = f"navigator.{calculator}_{warning.id}-{field}" + translation.label = f"warning.{calculator}_{warning.id}-{field}" translation.save() return warning @@ -749,14 +749,45 @@ class Referrer(models.Model): def __str__(self): return self.referrer_code +class TranslationOverrideManager(models.Manager): + translated_fields = ("translation",) + + def new_translation_override(self, calculator:str, program_field:str, external_name: Optional[str]=None): + '''Make a new translation override with the calculator, field, and external_name''' + + translations = {} + for field in self.translated_fields: + translations[field] = Translation.objects.add_translation(f"translation_override.{calculator}_temporary_key-{field}") + + if external_name is None: + external_name = calculator + + # try to set the external_name to the name + external_name_exists = self.filter(external_name=external_name).count() > 0 + + translation_override = self.create( + external_name=external_name if not external_name_exists else None, + calculator=calculator, + field=program_field, + **translations, + ) + + for [field, translation] in translations.items(): + translation.label = f"translation_override.{calculator}_{translation_override.id}-{field}" + translation.save() + + return translation_override + class TranslationOverride(models.Model): - calculator = models.CharField(max_length=120, blank=False, null=False) external_name = models.CharField(max_length=120, blank=True, null=True, unique=True) + calculator = models.CharField(max_length=120, blank=False, null=False) field = models.CharField(max_length=64, blank=False, null=False) - program = models.ForeignKey(Program, related_name="translation_overrides", blank=False, null=False, on_delete=models.CASCADE) + program = models.ForeignKey(Program, related_name="translation_overrides", blank=False, null=True, on_delete=models.CASCADE) active = models.BooleanField(blank=True, null=False, default=True) - translation = models.ForeignKey(Translation, related_name="translation_overrides", blank=False, null=False, on_delete=models.PROTECT) counties = models.ManyToManyField(County, related_name="translation_overrides", blank=True) + translation = models.ForeignKey(Translation, related_name="translation_overrides", blank=False, null=False, on_delete=models.PROTECT) + + objects = TranslationOverrideManager() @property def county_names(self) -> list[str]: From a25f7857219560f1a485ef13574ac4a3fa65b227 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Mon, 30 Sep 2024 15:46:52 -0600 Subject: [PATCH 45/55] Add translation override to translation admin --- translations/templates/base.html | 8 ++ .../translation_overrides/filter.html | 20 +++++ .../templates/translation_overrides/list.html | 74 +++++++++++++++++++ .../templates/translation_overrides/main.html | 19 +++++ .../translation_override.html | 33 +++++++++ translations/urls.py | 4 + translations/views.py | 64 +++++++++++++++- 7 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 translations/templates/translation_overrides/filter.html create mode 100644 translations/templates/translation_overrides/list.html create mode 100644 translations/templates/translation_overrides/main.html create mode 100644 translations/templates/translation_overrides/translation_override.html diff --git a/translations/templates/base.html b/translations/templates/base.html index df0dacd1..a90dc587 100644 --- a/translations/templates/base.html +++ b/translations/templates/base.html @@ -47,6 +47,14 @@

Translations Admin

+
  • + +
    + letter_switch + Translation Override +
    +
    +
  • diff --git a/translations/templates/translation_overrides/filter.html b/translations/templates/translation_overrides/filter.html new file mode 100644 index 00000000..0b0dfd74 --- /dev/null +++ b/translations/templates/translation_overrides/filter.html @@ -0,0 +1,20 @@ +{% load static %} +
    +
    + + + +
    +
    diff --git a/translations/templates/translation_overrides/list.html b/translations/templates/translation_overrides/list.html new file mode 100644 index 00000000..fb547294 --- /dev/null +++ b/translations/templates/translation_overrides/list.html @@ -0,0 +1,74 @@ +{% load static %} +
    + + + + + + + + + + + {% for translation_override in page_obj %} + + + + + + + {% empty %} + + + + {% endfor %} + + + + + + +
    IDExternal NameCalculatorActions
    {{ translation_override.id }}{{ translation_override.external_name }}{{ translation_override.calculator }} + +
    No results
    Total: {{ page_obj.paginator.count }}
    + + {% include "../pagination.html" %} + + +
    diff --git a/translations/templates/translation_overrides/main.html b/translations/templates/translation_overrides/main.html new file mode 100644 index 00000000..d8eebf9b --- /dev/null +++ b/translations/templates/translation_overrides/main.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} {% block content %} +
    +

    Translation Overrides

    +
    +
    +
    +{% include "./list.html" %} {% endblock content %} diff --git a/translations/templates/translation_overrides/translation_override.html b/translations/templates/translation_overrides/translation_override.html new file mode 100644 index 00000000..b31e0fbe --- /dev/null +++ b/translations/templates/translation_overrides/translation_override.html @@ -0,0 +1,33 @@ +{% extends "base.html" %}{% block content %} +
    +

    Translation Override Actions

    +
    +
    +
    + + + + + + + + + + + + + +
    External NameActions (add)
    {{ translation_override.external_name }} + +
    +
    +{% endblock content %} diff --git a/translations/urls.py b/translations/urls.py index 29814c4c..ebfd5a4e 100644 --- a/translations/urls.py +++ b/translations/urls.py @@ -23,6 +23,10 @@ path("admin/warning_messages/filter", views.warning_messages_filter_view), path("admin/warning_messages/create", views.create_warning_message_view), path("admin/warning_messages/", views.warning_message_view), + path("admin/translation_overrides", views.translation_overrides_view), + path("admin/translation_overrides/filter", views.translation_override_filter_view), + path("admin/translation_overrides/create", views.create_translation_override_view), + path("admin/translation_overrides/", views.translation_override_view), path("admin/urgent_needs", views.urgent_needs_view), path("admin/urgent_needs/filter", views.urgent_need_filter_view), path("admin/urgent_needs/create", views.create_urgent_need_view), diff --git a/translations/views.py b/translations/views.py index 7e60ecbc..75e5de2d 100644 --- a/translations/views.py +++ b/translations/views.py @@ -8,7 +8,7 @@ from django import forms from django.http import HttpResponse from django.db.models import ProtectedError -from programs.models import Program, Navigator, UrgentNeed, Document, WarningMessage +from programs.models import Program, Navigator, UrgentNeed, Document, WarningMessage, TranslationOverride from phonenumber_field.formfields import PhoneNumberField from phonenumber_field.widgets import PhoneNumberPrefixWidget from django.contrib.auth.decorators import login_required @@ -534,3 +534,65 @@ def warning_messages_filter_view(request): context = {"page_obj": page_obj} return render(request, "warning_messages/list.html", context) + + +class NewTranslationOverrideForm(forms.Form): + external_name = forms.CharField(max_length=120, widget=forms.TextInput(attrs={"class": "input"})) + calculator_name = forms.CharField(max_length=120, widget=forms.TextInput(attrs={"class": "input"})) + field_name = forms.CharField(max_length=120, widget=forms.TextInput(attrs={"class": "input"})) + + +@login_required(login_url="/admin/login") +@staff_member_required +def translation_overrides_view(request): + if request.method == "GET": + translation_overrides = TranslationOverride.objects.all().order_by("external_name") + + paginator = Paginator(translation_overrides, 50) + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) + + context = {"page_obj": page_obj} + return render(request, "translation_overrides/main.html", context) + if request.method == "POST": + form = NewTranslationOverrideForm(request.POST) + if form.is_valid(): + translation_override = TranslationOverride.objects.new_translation_override(form["calculator_name"].value(), form["field_name"].value(), form["external_name"].value()) + response = HttpResponse() + response.headers["HX-Redirect"] = f"/api/translations/admin/translation_overrides/{translation_override.id}" + return response + + +@login_required(login_url="/admin/login") +@staff_member_required +def create_translation_override_view(request): + if request.method == "GET": + context = {"form": NewTranslationOverrideForm(), "route": "/api/translations/admin/translation_overrides"} + + return render(request, "util/create_form.html", context) + + +@login_required(login_url="/admin/login") +@staff_member_required +def translation_override_view(request, id=0): + if request.method == "GET": + translation_override = TranslationOverride.objects.get(pk=id) + context = {"translation_override": translation_override} + + return render(request, "translation_overrides/translation_override.html", context) + + +@login_required(login_url="/admin/login") +@staff_member_required +def translation_override_filter_view(request): + if request.method == "GET": + query = request.GET.get("name", "") + translation_overrides = TranslationOverride.objects.filter(external_name__contains=query).order_by("external_name") + + paginator = Paginator(translation_overrides, 50) + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) + + context = {"page_obj": page_obj} + + return render(request, "translation_overrides/list.html", context) From b719c14494f10964eff0f359ed109ce6de579964 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Mon, 30 Sep 2024 16:11:34 -0600 Subject: [PATCH 46/55] Add a function to import and export TranslationOverrides --- programs/models.py | 52 +++++++++++++++++++++++- translations/bulk_import_translations.py | 3 +- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/programs/models.py b/programs/models.py index 7875a2fd..4c0d3d71 100644 --- a/programs/models.py +++ b/programs/models.py @@ -706,7 +706,7 @@ def from_model_data(self, data: DataType): @classmethod def create_instance(cls, external_name: str, Model: type["WarningMessage"]) -> "WarningMessage": - return Model.objects.create("_show", external_name) + return Model.objects.new_warning("_show", external_name) class WarningMessage(models.Model): @@ -778,6 +778,54 @@ def new_translation_override(self, calculator:str, program_field:str, external_n return translation_override + +class TranslationOverrideDataController(ModelDataController["TranslationOverride"]): + _model_name = "TranslationOverride" + dependencies = ["Program"] + + CountiesType = list[TypedDict("CountyType", {"name": str})] + DataType = TypedDict("DataType", {"calculator": str, "field": str, "active": bool, "counties": CountiesType, "program": str}) + + def _counties(self) -> CountiesType: + return [{"name": c.name} for c in self.instance.counties.all()] + + def to_model_data(self) -> DataType: + translation_override = self.instance + return { + "calculator": translation_override.calculator, + "field": translation_override.field, + "active": translation_override.active, + "counties": self._counties(), + "program": translation_override.program.external_name, + } + + def from_model_data(self, data: DataType): + translation_override = self.instance + + translation_override.calculator = data["calculator"] + translation_override.field = data["field"] + translation_override.active = data["active"] + + # get or create counties + counties = [] + for county in data["counties"]: + try: + county_instance = County.objects.get(name=county["name"]) + except County.DoesNotExist: + county_instance = County.objects.create(name=county["name"]) + counties.append(county_instance) + translation_override.counties.set(counties) + + # get programs + translation_override.program = Program.objects.get(external_name=data["program"]) + + translation_override.save() + + @classmethod + def create_instance(cls, external_name: str, Model: type["TranslationOverride"]) -> "TranslationOverride": + return Model.objects.new_translation_override("_show", "", external_name) + + class TranslationOverride(models.Model): external_name = models.CharField(max_length=120, blank=True, null=True, unique=True) calculator = models.CharField(max_length=120, blank=False, null=False) @@ -789,6 +837,8 @@ class TranslationOverride(models.Model): objects = TranslationOverrideManager() + TranslationExportBuilder = TranslationOverrideDataController + @property def county_names(self) -> list[str]: """List of county names""" diff --git a/translations/bulk_import_translations.py b/translations/bulk_import_translations.py index 6f8ef8c6..d4834067 100644 --- a/translations/bulk_import_translations.py +++ b/translations/bulk_import_translations.py @@ -1,6 +1,6 @@ from translations.model_data import ModelDataController from .models import Translation -from programs.models import Program, Navigator, UrgentNeed, Document, WarningMessage +from programs.models import Program, Navigator, UrgentNeed, Document, WarningMessage, TranslationOverride from django.db import transaction from django.conf import settings from django.core.exceptions import ObjectDoesNotExist @@ -14,6 +14,7 @@ "Navigator": Navigator, "Document": Document, "WarningMessage": WarningMessage, + "TranslationOverride": TranslationOverride, } TRANSLATED_MODELS = TRANSLATED_MODEL_MAP.values() From 2535bce7241bbc4bf094d683879262577fdaef28 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Tue, 1 Oct 2024 13:19:06 -0600 Subject: [PATCH 47/55] Fix formatting --- programs/admin.py | 6 +- .../migrations/0088_translationoverride.py | 8 +- ...r_translationoverride_counties_and_more.py | 4 +- programs/models.py | 26 +++++-- .../translation_overrides/__init__.py | 6 +- .../programs/translation_overrides/base.py | 77 ++++++++++--------- .../translation_overrides/dont_show.py | 10 +-- screener/views.py | 3 + translations/views.py | 8 +- 9 files changed, 80 insertions(+), 68 deletions(-) diff --git a/programs/admin.py b/programs/admin.py index 6c741728..31fed005 100644 --- a/programs/admin.py +++ b/programs/admin.py @@ -270,12 +270,11 @@ class ReferrerAdmin(ModelAdmin): class WebHookFunctionsAdmin(ModelAdmin): search_fields = ("name",) + class TranslationOverrideAdmin(ModelAdmin): search_fields = ("external_name",) list_display = ["get_str", "calculator", "action_buttons"] - filter_horizontal = ( - "counties", - ) + filter_horizontal = ("counties",) def get_str(self, obj): return str(obj) @@ -301,6 +300,7 @@ def action_buttons(self, obj): action_buttons.short_description = "Translate:" action_buttons.allow_tags = True + admin.site.register(LegalStatus, LegalStatusAdmin) admin.site.register(Program, ProgramAdmin) admin.site.register(County, CountiesAdmin) diff --git a/programs/migrations/0088_translationoverride.py b/programs/migrations/0088_translationoverride.py index da345928..af358680 100644 --- a/programs/migrations/0088_translationoverride.py +++ b/programs/migrations/0088_translationoverride.py @@ -27,17 +27,13 @@ class Migration(migrations.Migration): ("calculator", models.CharField(max_length=120)), ( "external_name", - models.CharField( - blank=True, max_length=120, null=True, unique=True - ), + models.CharField(blank=True, max_length=120, null=True, unique=True), ), ("field", models.CharField(max_length=64)), ("active", models.BooleanField(blank=True, default=True)), ( "counties", - models.ManyToManyField( - related_name="translation_overrides", to="programs.county" - ), + models.ManyToManyField(related_name="translation_overrides", to="programs.county"), ), ( "program", diff --git a/programs/migrations/0089_alter_translationoverride_counties_and_more.py b/programs/migrations/0089_alter_translationoverride_counties_and_more.py index f0b09f8b..6077744e 100644 --- a/programs/migrations/0089_alter_translationoverride_counties_and_more.py +++ b/programs/migrations/0089_alter_translationoverride_counties_and_more.py @@ -14,9 +14,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name="translationoverride", name="counties", - field=models.ManyToManyField( - blank=True, related_name="translation_overrides", to="programs.county" - ), + field=models.ManyToManyField(blank=True, related_name="translation_overrides", to="programs.county"), ), migrations.AlterField( model_name="translationoverride", diff --git a/programs/models.py b/programs/models.py index 4c0d3d71..4f868f97 100644 --- a/programs/models.py +++ b/programs/models.py @@ -10,6 +10,7 @@ from typing import Optional, Type, TypedDict from programs.programs.translation_overrides import warning_calculators + class FplCache(Cache): expire_time = 60 * 60 * 24 # 24 hours default = {} @@ -327,7 +328,7 @@ def __str__(self): def __unicode__(self): return self.name.text - def get_translation(self, screen, missing_dependencies: Dependencies, field:str): + def get_translation(self, screen, missing_dependencies: Dependencies, field: str): if field not in Program.objects.translated_fields: raise ValueError(f"translation with name {field} does not exist") @@ -749,15 +750,18 @@ class Referrer(models.Model): def __str__(self): return self.referrer_code + class TranslationOverrideManager(models.Manager): translated_fields = ("translation",) - def new_translation_override(self, calculator:str, program_field:str, external_name: Optional[str]=None): - '''Make a new translation override with the calculator, field, and external_name''' + def new_translation_override(self, calculator: str, program_field: str, external_name: Optional[str] = None): + """Make a new translation override with the calculator, field, and external_name""" translations = {} for field in self.translated_fields: - translations[field] = Translation.objects.add_translation(f"translation_override.{calculator}_temporary_key-{field}") + translations[field] = Translation.objects.add_translation( + f"translation_override.{calculator}_temporary_key-{field}" + ) if external_name is None: external_name = calculator @@ -784,7 +788,9 @@ class TranslationOverrideDataController(ModelDataController["TranslationOverride dependencies = ["Program"] CountiesType = list[TypedDict("CountyType", {"name": str})] - DataType = TypedDict("DataType", {"calculator": str, "field": str, "active": bool, "counties": CountiesType, "program": str}) + DataType = TypedDict( + "DataType", {"calculator": str, "field": str, "active": bool, "counties": CountiesType, "program": str} + ) def _counties(self) -> CountiesType: return [{"name": c.name} for c in self.instance.counties.all()] @@ -830,10 +836,14 @@ class TranslationOverride(models.Model): external_name = models.CharField(max_length=120, blank=True, null=True, unique=True) calculator = models.CharField(max_length=120, blank=False, null=False) field = models.CharField(max_length=64, blank=False, null=False) - program = models.ForeignKey(Program, related_name="translation_overrides", blank=False, null=True, on_delete=models.CASCADE) + program = models.ForeignKey( + Program, related_name="translation_overrides", blank=False, null=True, on_delete=models.CASCADE + ) active = models.BooleanField(blank=True, null=False, default=True) - counties = models.ManyToManyField(County, related_name="translation_overrides", blank=True) - translation = models.ForeignKey(Translation, related_name="translation_overrides", blank=False, null=False, on_delete=models.PROTECT) + counties = models.ManyToManyField(County, related_name="translation_overrides", blank=True) + translation = models.ForeignKey( + Translation, related_name="translation_overrides", blank=False, null=False, on_delete=models.PROTECT + ) objects = TranslationOverrideManager() diff --git a/programs/programs/translation_overrides/__init__.py b/programs/programs/translation_overrides/__init__.py index 00c3ba3c..e5782115 100644 --- a/programs/programs/translation_overrides/__init__.py +++ b/programs/programs/translation_overrides/__init__.py @@ -2,10 +2,10 @@ from .base import TranslationOverrideCalculator general_calculators: dict[str, type[TranslationOverrideCalculator]] = { - "_show": TranslationOverrideCalculator, - "_dont_show": DontShow, + "_show": TranslationOverrideCalculator, + "_dont_show": DontShow, } specific_calculators: dict[str, type[TranslationOverrideCalculator]] = {} -warning_calculators: dict[str, type[TranslationOverrideCalculator]] = {**general_calculators, **specific_calculators} \ No newline at end of file +warning_calculators: dict[str, type[TranslationOverrideCalculator]] = {**general_calculators, **specific_calculators} diff --git a/programs/programs/translation_overrides/base.py b/programs/programs/translation_overrides/base.py index 60fbcc37..492d3b18 100644 --- a/programs/programs/translation_overrides/base.py +++ b/programs/programs/translation_overrides/base.py @@ -3,43 +3,44 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from programs.models import TranslationOverride + from programs.models import TranslationOverride + class TranslationOverrideCalculator: - dependencies = tuple() - - def __init__(self, screen: Screen, translation_override: "TranslationOverride", missing_dependencies: Dependencies): - self.screen = screen - self.translation_override = translation_override - self.missing_dependencies = missing_dependencies - - def calc(self) -> bool: - """ - Return if the translation should be overridden - """ - if not self.can_calc(): - return False - - return self.eligible() and self.county_eligible() - - def eligible(self) -> bool: - """ - Custom requirement for whether or not to override the Translation - """ - return True - - def can_calc(self) -> bool: - """ - Returns whether or not we can calculate if a translation can be overriden - """ - return not self.missing_dependencies.has(*self.dependencies) - - def county_eligible(self) -> bool: - """ - Returns True if the override should be applied based on county - """ - county = self.screen.county - translation_override_counties = self.translation_override.county_names - if len(translation_override_counties) > 0: - return county in translation_override_counties - return True \ No newline at end of file + dependencies = tuple() + + def __init__(self, screen: Screen, translation_override: "TranslationOverride", missing_dependencies: Dependencies): + self.screen = screen + self.translation_override = translation_override + self.missing_dependencies = missing_dependencies + + def calc(self) -> bool: + """ + Return if the translation should be overridden + """ + if not self.can_calc(): + return False + + return self.eligible() and self.county_eligible() + + def eligible(self) -> bool: + """ + Custom requirement for whether or not to override the Translation + """ + return True + + def can_calc(self) -> bool: + """ + Returns whether or not we can calculate if a translation can be overriden + """ + return not self.missing_dependencies.has(*self.dependencies) + + def county_eligible(self) -> bool: + """ + Returns True if the override should be applied based on county + """ + county = self.screen.county + translation_override_counties = self.translation_override.county_names + if len(translation_override_counties) > 0: + return county in translation_override_counties + return True diff --git a/programs/programs/translation_overrides/dont_show.py b/programs/programs/translation_overrides/dont_show.py index a9269f6d..4895551a 100644 --- a/programs/programs/translation_overrides/dont_show.py +++ b/programs/programs/translation_overrides/dont_show.py @@ -2,8 +2,8 @@ class DontShow(TranslationOverrideCalculator): - def eligible(self) -> bool: - ''' - Never use this override - ''' - return False \ No newline at end of file + def eligible(self) -> bool: + """ + Never use this override + """ + return False diff --git a/screener/views.py b/screener/views.py index fbca34c9..b0c54eb9 100644 --- a/screener/views.py +++ b/screener/views.py @@ -369,6 +369,7 @@ def sort_first(program): return eligible_programs, missing_programs + class GetProgramTranslation: def __init__(self, screen: Screen, program: Program, missing_dependencies: Dependencies): self.screen = screen @@ -377,6 +378,8 @@ def __init__(self, screen: Screen, program: Program, missing_dependencies: Depen def get_translation(self, field: str): return default_message(self.program.get_translation(self.screen, self.missing_dependencies, field)) + + def default_message(translation): translation.set_current_language(settings.LANGUAGE_CODE) d = {"default_message": translation.text, "label": translation.label} diff --git a/translations/views.py b/translations/views.py index 75e5de2d..0ef1f4f6 100644 --- a/translations/views.py +++ b/translations/views.py @@ -557,7 +557,9 @@ def translation_overrides_view(request): if request.method == "POST": form = NewTranslationOverrideForm(request.POST) if form.is_valid(): - translation_override = TranslationOverride.objects.new_translation_override(form["calculator_name"].value(), form["field_name"].value(), form["external_name"].value()) + translation_override = TranslationOverride.objects.new_translation_override( + form["calculator_name"].value(), form["field_name"].value(), form["external_name"].value() + ) response = HttpResponse() response.headers["HX-Redirect"] = f"/api/translations/admin/translation_overrides/{translation_override.id}" return response @@ -587,7 +589,9 @@ def translation_override_view(request, id=0): def translation_override_filter_view(request): if request.method == "GET": query = request.GET.get("name", "") - translation_overrides = TranslationOverride.objects.filter(external_name__contains=query).order_by("external_name") + translation_overrides = TranslationOverride.objects.filter(external_name__contains=query).order_by( + "external_name" + ) paginator = Paginator(translation_overrides, 50) page_number = request.GET.get("page") From 20db06f17265f19382749c4543af67455bb8a5a9 Mon Sep 17 00:00:00 2001 From: Caleb Date: Tue, 1 Oct 2024 14:24:42 -0600 Subject: [PATCH 48/55] calculate the school lunch income instead of just the parts of it --- programs/programs/federal/pe/member.py | 2 +- programs/programs/federal/pe/spm.py | 2 +- .../calculators/dependencies/__init__.py | 8 -------- .../calculators/dependencies/spm.py | 18 ++++++++++++++++++ 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/programs/programs/federal/pe/member.py b/programs/programs/federal/pe/member.py index 0b0b5d59..cf6e4a4c 100644 --- a/programs/programs/federal/pe/member.py +++ b/programs/programs/federal/pe/member.py @@ -15,7 +15,7 @@ class Wic(PolicyEngineMembersCalculator): pe_inputs = [ dependency.member.PregnancyDependency, dependency.member.AgeDependency, - *dependency.school_lunch_income, + dependency.spm.SchoolMealCountableIncomeDependency, ] pe_outputs = [dependency.member.Wic, dependency.member.WicCategory] tax_unit_dependent = False diff --git a/programs/programs/federal/pe/spm.py b/programs/programs/federal/pe/spm.py index 43acb2af..43f27ba2 100644 --- a/programs/programs/federal/pe/spm.py +++ b/programs/programs/federal/pe/spm.py @@ -34,7 +34,7 @@ def value(self): class SchoolLunch(PolicyEngineSpmCalulator): pe_name = "school_meal_daily_subsidy" - pe_inputs = dependency.school_lunch_income + pe_inputs = [dependency.spm.SchoolMealCountableIncomeDependency] pe_outputs = [dependency.spm.SchoolMealDailySubsidy, dependency.spm.SchoolMealTier] amount = 120 diff --git a/programs/programs/policyengine/calculators/dependencies/__init__.py b/programs/programs/policyengine/calculators/dependencies/__init__.py index 1023c7f6..1dbd7382 100644 --- a/programs/programs/policyengine/calculators/dependencies/__init__.py +++ b/programs/programs/policyengine/calculators/dependencies/__init__.py @@ -11,11 +11,3 @@ member.PensionIncomeDependency, member.SocialSecurityIncomeDependency, ] - -school_lunch_income = [ - member.EmploymentIncomeDependency, - member.SelfEmploymentIncomeDependency, - member.RentalIncomeDependency, - member.PensionIncomeDependency, - member.SocialSecurityIncomeDependency, -] diff --git a/programs/programs/policyengine/calculators/dependencies/spm.py b/programs/programs/policyengine/calculators/dependencies/spm.py index 453d99a6..6c844c0b 100644 --- a/programs/programs/policyengine/calculators/dependencies/spm.py +++ b/programs/programs/policyengine/calculators/dependencies/spm.py @@ -218,3 +218,21 @@ class BroadbandCostDependency(SpmUnit): def value(self): return 500 + + +class SchoolMealCountableIncomeDependency(SpmUnit): + field = "school_meal_countable_income" + income_types = [ + "wages", + "selfEmployment", + "rental", + "pension", + "veteran", + "sSDisability", + "sSSurvivor", + "sSRetirement", + "sSDependent", + ] + + def value(self): + return self.screen.calc_gross_income("yearly", self.income_types) From 91f898a203b1396ac13790f60e7e0edad252c3a3 Mon Sep 17 00:00:00 2001 From: Caleb Date: Tue, 1 Oct 2024 14:27:43 -0600 Subject: [PATCH 49/55] Revert "Revert "Every Day Eats from PE"" This reverts commit c7d1fa57d7068e8d5c715ea4a030cdfe8ea22bd0. --- programs/programs/co/__init__.py | 2 - .../programs/co/every_day_eats/__init__.py | 0 .../programs/co/every_day_eats/calculator.py | 25 ---------- programs/programs/co/every_day_eats/tests.py | 47 ------------------- programs/programs/co/pe/__init__.py | 1 + programs/programs/co/pe/member.py | 17 ++++++- programs/programs/federal/pe/__init__.py | 1 + programs/programs/federal/pe/member.py | 9 ++++ .../calculators/dependencies/member.py | 4 ++ 9 files changed, 31 insertions(+), 75 deletions(-) delete mode 100644 programs/programs/co/every_day_eats/__init__.py delete mode 100644 programs/programs/co/every_day_eats/calculator.py delete mode 100644 programs/programs/co/every_day_eats/tests.py diff --git a/programs/programs/co/__init__.py b/programs/programs/co/__init__.py index e61a2d8f..750670c6 100644 --- a/programs/programs/co/__init__.py +++ b/programs/programs/co/__init__.py @@ -11,7 +11,6 @@ from .connect_for_health.calculator import ConnectForHealth from .medicaid.family_planning_services.calculator import FamilyPlanningServices from .denver_preschool_program.calculator import DenverPreschoolProgram -from .every_day_eats.calculator import EveryDayEats from .property_credit_rebate.calculator import PropertyCreditRebate from .universal_preschool.calculator import UniversalPreschool from .my_spark.calculator import MySpark @@ -45,7 +44,6 @@ "cfhc": ConnectForHealth, "fps": FamilyPlanningServices, "dpp": DenverPreschoolProgram, - "ede": EveryDayEats, "cpcr": PropertyCreditRebate, "upk": UniversalPreschool, "myspark": MySpark, diff --git a/programs/programs/co/every_day_eats/__init__.py b/programs/programs/co/every_day_eats/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/programs/programs/co/every_day_eats/calculator.py b/programs/programs/co/every_day_eats/calculator.py deleted file mode 100644 index fec2df6a..00000000 --- a/programs/programs/co/every_day_eats/calculator.py +++ /dev/null @@ -1,25 +0,0 @@ -from programs.programs.calc import ProgramCalculator, Eligibility -import programs.programs.messages as messages - - -class EveryDayEats(ProgramCalculator): - amount = 600 - min_age = 60 - percent_of_fpl = 1.3 - dependencies = ["age", "income_amount", "income_frequency", "household_size"] - - def eligible(self) -> Eligibility: - e = Eligibility() - - # Someone older that 60 - num_seniors = self.screen.num_adults(age_max=EveryDayEats.min_age) - e.condition(num_seniors >= 1, messages.older_than(EveryDayEats.min_age)) - - # Income - fpl = self.program.fpl.as_dict() - income_limit = EveryDayEats.percent_of_fpl * fpl[self.screen.household_size] - gross_income = self.screen.calc_gross_income("yearly", ["all"]) - - e.condition(gross_income < income_limit, messages.income(gross_income, income_limit)) - - return e diff --git a/programs/programs/co/every_day_eats/tests.py b/programs/programs/co/every_day_eats/tests.py deleted file mode 100644 index 03606c18..00000000 --- a/programs/programs/co/every_day_eats/tests.py +++ /dev/null @@ -1,47 +0,0 @@ -from django.test import TestCase -from programs.programs.every_day_eats.calculator import EveryDayEats -from screener.models import Screen, HouseholdMember, IncomeStream - - -class TestEveryDayEatsPension(TestCase): - def setUp(self): - self.screen1 = Screen.objects.create( - agree_to_tos=True, - zipcode="80205", - county="Denver County", - household_size=1, - household_assets=0, - ) - self.person1 = HouseholdMember.objects.create( - screen=self.screen1, - relationship="headOfHousehold", - age=60, - student=False, - student_full_time=False, - pregnant=False, - unemployed=False, - worked_in_last_18_mos=True, - visually_impaired=False, - disabled=False, - veteran=False, - has_income=False, - has_expenses=False, - ) - - def test_every_day_eats_visually_impaired_is_eligible(self): - ede = EveryDayEats(self.screen1) - eligibility = ede.eligibility - - self.assertTrue(eligibility["eligible"]) - - def test_every_day_eats_failed_all_conditions(self): - income = IncomeStream.objects.create( - screen=self.screen1, household_member=self.person1, type="wages", amount=3000, frequency="monthly" - ) - self.person1.age = 30 - self.person1.save() - - ede = EveryDayEats(self.screen1) - eligibility = ede.eligibility - - self.assertFalse(eligibility["eligible"]) diff --git a/programs/programs/co/pe/__init__.py b/programs/programs/co/pe/__init__.py index 5b1c1323..007f4092 100644 --- a/programs/programs/co/pe/__init__.py +++ b/programs/programs/co/pe/__init__.py @@ -11,6 +11,7 @@ "chp": member.Chp, "fatc": member.FamilyAffordabilityTaxCredit, "co_wic": member.CoWic, + "ede": member.EveryDayEats, } co_tax_unit_calculators = { diff --git a/programs/programs/co/pe/member.py b/programs/programs/co/pe/member.py index 3f232a7d..90f6ad96 100644 --- a/programs/programs/co/pe/member.py +++ b/programs/programs/co/pe/member.py @@ -1,5 +1,5 @@ from programs.programs.policyengine.calculators.base import PolicyEngineMembersCalculator -from programs.programs.federal.pe.member import Medicaid +from programs.programs.federal.pe.member import CommoditySupplementalFoodProgram, Medicaid from programs.programs.federal.pe.member import Wic import programs.programs.policyengine.calculators.dependencies as dependency @@ -95,3 +95,18 @@ class CoWic(Wic): *Wic.pe_inputs, dependency.household.CoStateCode, ] + + +class EveryDayEats(CommoditySupplementalFoodProgram): + amount = 600 + + def value(self): + value = 0 + + for member in self.screen.household_members.all(): + ede_eligible = self.sim.value(self.pe_category, str(member.id), self.pe_name, self.pe_period) > 0 + + if ede_eligible: + value += self.amount + + return value diff --git a/programs/programs/federal/pe/__init__.py b/programs/programs/federal/pe/__init__.py index bafc4afa..62d5c3c2 100644 --- a/programs/programs/federal/pe/__init__.py +++ b/programs/programs/federal/pe/__init__.py @@ -8,6 +8,7 @@ "wic": member.Wic, "pell_grant": member.PellGrant, "ssi": member.Ssi, + "csfp": member.CommoditySupplementalFoodProgram, } federal_spm_unit_calculators = { diff --git a/programs/programs/federal/pe/member.py b/programs/programs/federal/pe/member.py index cf6e4a4c..597aed99 100644 --- a/programs/programs/federal/pe/member.py +++ b/programs/programs/federal/pe/member.py @@ -138,3 +138,12 @@ class Ssi(PolicyEngineMembersCalculator): dependency.member.TaxUnitDependentDependency, ] pe_outputs = [dependency.member.Ssi] + + +class CommoditySupplementalFoodProgram(PolicyEngineMembersCalculator): + pe_name = "commodity_supplemental_food_program" + pe_inputs = [ + dependency.member.AgeDependency, + *dependency.school_lunch_income, + ] + pe_outputs = [dependency.member.CommoditySupplementalFoodProgram] diff --git a/programs/programs/policyengine/calculators/dependencies/member.py b/programs/programs/policyengine/calculators/dependencies/member.py index 19f3fc9f..6c061f40 100644 --- a/programs/programs/policyengine/calculators/dependencies/member.py +++ b/programs/programs/policyengine/calculators/dependencies/member.py @@ -189,6 +189,10 @@ class ChpEligible(Member): field = "co_chp_eligible" +class CommoditySupplementalFoodProgram(Member): + field = "commodity_supplemental_food_program" + + class IncomeDependency(Member): dependencies = ( "income_type", From 55442a591ee8a1a04024cf9a72a3d27f2aaddc09 Mon Sep 17 00:00:00 2001 From: Caleb Date: Tue, 1 Oct 2024 14:30:06 -0600 Subject: [PATCH 50/55] fix ede income dependency --- programs/programs/federal/pe/member.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/programs/federal/pe/member.py b/programs/programs/federal/pe/member.py index 597aed99..8b2ca3f0 100644 --- a/programs/programs/federal/pe/member.py +++ b/programs/programs/federal/pe/member.py @@ -144,6 +144,6 @@ class CommoditySupplementalFoodProgram(PolicyEngineMembersCalculator): pe_name = "commodity_supplemental_food_program" pe_inputs = [ dependency.member.AgeDependency, - *dependency.school_lunch_income, + dependency.spm.SchoolMealCountableIncomeDependency, ] pe_outputs = [dependency.member.CommoditySupplementalFoodProgram] From ce153d6006ca56d6dc7f74fb7b7aa4b0b9b5339b Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Thu, 3 Oct 2024 09:29:02 -0600 Subject: [PATCH 51/55] Add translation_overrides to the eligibility_results method --- screener/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/screener/views.py b/screener/views.py index b0c54eb9..f0770aa4 100644 --- a/screener/views.py +++ b/screener/views.py @@ -201,6 +201,9 @@ def eligibility_results(screen: Screen, batch=False): "warning_messages", "warning_messages__counties", *translations_prefetch_name("warning_messages__", WarningMessage.objects.translated_fields), + "translation_overrides", + "translation_overrides__counties", + *translations_prefetch_name("translation_overrides__", TranslationOverride.objects.translated_fields), ) .exclude(id__in=excluded_programs) ) From e992f9d1083014c1c10804aa090ef9b5dc225498 Mon Sep 17 00:00:00 2001 From: Ivonne Hernandez Date: Thu, 3 Oct 2024 09:35:35 -0600 Subject: [PATCH 52/55] Fix formatting --- programs/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/programs/models.py b/programs/models.py index 3ecdb0ff..18404899 100644 --- a/programs/models.py +++ b/programs/models.py @@ -9,6 +9,7 @@ from typing import Optional, Type, TypedDict from programs.programs.translation_overrides import warning_calculators + class FplCache(Cache): expire_time = 60 * 60 * 24 # 24 hours default = {} From b358346995b27c8d2fd416c9c96bf78bf7e50efa Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 3 Oct 2024 10:24:27 -0600 Subject: [PATCH 53/55] fix ede --- programs/programs/co/pe/member.py | 13 +++++-------- programs/programs/policyengine/policy_engine.py | 1 + screener/views.py | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/programs/programs/co/pe/member.py b/programs/programs/co/pe/member.py index 1b0d6622..99cb95a1 100644 --- a/programs/programs/co/pe/member.py +++ b/programs/programs/co/pe/member.py @@ -99,13 +99,10 @@ class CoWic(Wic): class EveryDayEats(CommoditySupplementalFoodProgram): amount = 600 - def value(self): - value = 0 - - for member in self.screen.household_members.all(): - ede_eligible = self.sim.value(self.pe_category, str(member.id), self.pe_name, self.pe_period) > 0 + def member_value(self, member: HouseholdMember): + ede_eligible = self.sim.value(self.pe_category, str(member.id), self.pe_name, self.pe_period) > 0 - if ede_eligible: - value += self.amount + if ede_eligible: + return self.amount - return value + return 0 diff --git a/programs/programs/policyengine/policy_engine.py b/programs/programs/policyengine/policy_engine.py index 9dc6918d..bd9f95b3 100644 --- a/programs/programs/policyengine/policy_engine.py +++ b/programs/programs/policyengine/policy_engine.py @@ -29,6 +29,7 @@ def calc_pe_eligibility( try: return all_eligibility(Method(input_data), valid_programs) except Exception as e: + print(e) capture_exception(e, level="warning", message="") capture_message(f"Failed to calculate eligibility with the {Method.method_name} method", level="warning") diff --git a/screener/views.py b/screener/views.py index 573a64ba..0135ac7b 100644 --- a/screener/views.py +++ b/screener/views.py @@ -28,7 +28,7 @@ from programs.programs.policyengine.policy_engine import calc_pe_eligibility from programs.util import DependencyError, Dependencies from programs.programs.urgent_needs.urgent_need_functions import urgent_need_functions -from programs.models import Document, Navigator, UrgentNeed, Program, Referrer, WarningMessage +from programs.models import Document, Navigator, UrgentNeed, Program, Referrer, WarningMessage, TranslationOverride from django.core.exceptions import ObjectDoesNotExist from programs.programs.warnings import warning_calculators from validations.serializers import ValidationSerializer From c7f07616846927e31a070aa26544eda0d2b512d3 Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 3 Oct 2024 12:36:02 -0600 Subject: [PATCH 54/55] update medicaid programs to have the correct value --- .../programs/co/medicaid/adult_with_disability/calculator.py | 2 +- .../programs/co/medicaid/child_with_disability/calculator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/programs/co/medicaid/adult_with_disability/calculator.py b/programs/programs/co/medicaid/adult_with_disability/calculator.py index 2a338e48..a12ccbfb 100644 --- a/programs/programs/co/medicaid/adult_with_disability/calculator.py +++ b/programs/programs/co/medicaid/adult_with_disability/calculator.py @@ -12,7 +12,7 @@ class MedicaidAdultWithDisability(ProgramCalculator): min_age = 16 insurance_types = ("employer", "private", "none") dependencies = ["insurance", "age", "household_size", "income_type", "income_amount", "income_frequency"] - member_amount = 310 + member_amount = 310 * 12 def household_eligible(self, e: Eligibility): # Does not qualify for Medicaid diff --git a/programs/programs/co/medicaid/child_with_disability/calculator.py b/programs/programs/co/medicaid/child_with_disability/calculator.py index 5aff3a05..87be7222 100644 --- a/programs/programs/co/medicaid/child_with_disability/calculator.py +++ b/programs/programs/co/medicaid/child_with_disability/calculator.py @@ -11,7 +11,7 @@ class MedicaidChildWithDisability(ProgramCalculator): income_percent = 1 - 0.33 insurance_types = ("employer", "private", "none") dependencies = ["insurance", "age", "household_size", "income_type", "income_amount", "income_frequency"] - member_amount = 200 + member_amount = 200 * 12 def household_eligible(self, e: Eligibility): # Does not qualify for Medicaid From d3f3dacb2e2b62b74174f7281923898b34895224 Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 10 Oct 2024 12:01:14 -0600 Subject: [PATCH 55/55] fix snap --- programs/programs/federal/pe/spm.py | 2 ++ .../programs/policyengine/calculators/dependencies/spm.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/programs/programs/federal/pe/spm.py b/programs/programs/federal/pe/spm.py index cd421939..df6f649c 100644 --- a/programs/programs/federal/pe/spm.py +++ b/programs/programs/federal/pe/spm.py @@ -21,6 +21,8 @@ class Snap(PolicyEngineSpmCalulator): dependency.spm.SnapDependentCareDeductionDependency, dependency.spm.WaterExpenseDependency, dependency.spm.PhoneExpenseDependency, + # WARN: if you remove check that SNAP is still showing up + dependency.spm.TakesUpSnapIfEligibleDependency, ] pe_outputs = [dependency.spm.Snap] pe_period_month = "01" diff --git a/programs/programs/policyengine/calculators/dependencies/spm.py b/programs/programs/policyengine/calculators/dependencies/spm.py index fb8adf3f..fddc01d4 100644 --- a/programs/programs/policyengine/calculators/dependencies/spm.py +++ b/programs/programs/policyengine/calculators/dependencies/spm.py @@ -70,6 +70,13 @@ def value(self): return snap_gross_income < snap_gross_limit +class TakesUpSnapIfEligibleDependency(SpmUnit): + field = "takes_up_snap_if_eligible" + + def value(self): + return True + + class MeetsSnapAssetTestDependency(SpmUnit): field = "meets_snap_asset_test"