Skip to content

Commit

Permalink
Merge pull request #589 from Gary-Community-Ventures/dev
Browse files Browse the repository at this point in the history
Release 10/11/2024
  • Loading branch information
CalebPena authored Oct 18, 2024
2 parents 14f70c8 + e76f683 commit e3ea200
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 5 deletions.
6 changes: 6 additions & 0 deletions configuration/management/commands/add_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,12 @@ class Command(BaseCommand):
"heating": {"_label": "expenseOptions.heating", "_default_message": "Heating"},
"creditCard": {"_label": "expenseOptions.creditCard", "_default_message": "Credit Card Debt"},
"mortgage": {"_label": "expenseOptions.mortgage", "_default_message": "Mortgage"},
"propertyTax": {"_label": "expenseOptions.propertyTax", "_default_message": "Property Taxes"},
"hoa": {"_label": "expenseOptions.hoa", "_default_message": "Homeowners or Condo Association Fees and Dues"},
"homeownersInsurance": {
"_label": "expenseOptions.homeownersInsurance",
"_default_message": "Homeowners Insurance",
},
"medical": {"_label": "expenseOptions.medical", "_default_message": "Medical Insurance Premium &/or Bills"},
"personalLoan": {"_label": "expenseOptions.personalLoan", "_default_message": "Personal Loan"},
"studentLoans": {"_label": "expenseOptions.studentLoans", "_default_message": "Student Loans"},
Expand Down
10 changes: 8 additions & 2 deletions programs/programs/federal/pe/spm.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@ 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,
dependency.spm.HoaFeesExpenseDependency,
dependency.spm.HomeownersInsuranceExpenseDependency,
dependency.member.PropertyTaxExpenseDependency,
dependency.member.AgeDependency,
dependency.member.MedicalExpenseDependency,
dependency.member.IsDisabledDependency,
# NOTE: remove this to always use the SUA in CO.
dependency.spm.SnapAlwaysUseSuaDependency,
]
pe_outputs = [dependency.spm.Snap]
pe_period_month = "01"
Expand Down
28 changes: 28 additions & 0 deletions programs/programs/policyengine/calculators/dependencies/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,34 @@ def value(self):
return self.member.disabled or self.member.long_term_disability


# The Member class runs once per each household member, to ensure that the medical expenses
# are only counted once and only if a member is elderly or disabled; the medical expense is divided
# by the total number of elderly or disabled members.
class MedicalExpenseDependency(Member):
field = "medical_out_of_pocket_expenses"

def value(self):
elderly_or_disabled_members = [
member for member in self.screen.household_members.all() if member.age >= 60 or member.has_disability()
]
count_of_elderly_or_disabled_members = len(elderly_or_disabled_members)

if self.member.age >= 60 or self.member.has_disability():
return self.screen.calc_expenses("yearly", ["medical"]) / count_of_elderly_or_disabled_members

return 0


class PropertyTaxExpenseDependency(Member):
field = "real_estate_taxes"

def value(self):
if self.member.age >= 18:
return self.screen.calc_expenses("yearly", ["propertyTax"]) / self.screen.num_adults(18)

return 0


class IsBlindDependency(Member):
field = "is_blind"

Expand Down
21 changes: 21 additions & 0 deletions programs/programs/policyengine/calculators/dependencies/spm.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ def value(self):
return snap_gross_income < snap_gross_limit


class SnapAlwaysUseSuaDependency(SpmUnit):
field = "snap_state_using_standard_utility_allowance"

def value(self):
return False


class TakesUpSnapIfEligibleDependency(SpmUnit):
field = "takes_up_snap_if_eligible"

Expand Down Expand Up @@ -140,6 +147,20 @@ def value(self):
return self.screen.calc_expenses("yearly", ["otherUtilities"])


class HoaFeesExpenseDependency(SpmUnit):
field = "homeowners_association_fees"

def value(self):
return self.screen.calc_expenses("yearly", ["hoa"])


class HomeownersInsuranceExpenseDependency(SpmUnit):
field = "homeowners_insurance"

def value(self):
return self.screen.calc_expenses("yearly", ["homeownersInsurance"])


class SnapEmergencyAllotmentDependency(SpmUnit):
field = "snap_emergency_allotment"

Expand Down
1 change: 0 additions & 1 deletion programs/programs/policyengine/policy_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ 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")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2.15 on 2024-10-01 22:01

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("screener", "0084_screen_has_fatc"),
]

operations = [
migrations.AddField(
model_name="householdmember",
name="birth_year_month",
field=models.DateField(blank=True, null=True),
),
migrations.AlterField(
model_name="householdmember",
name="age",
field=models.PositiveIntegerField(blank=True, null=True),
),
]
34 changes: 33 additions & 1 deletion screener/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import datetime
from typing import Optional
from django.db import models
from decimal import Decimal
import uuid
Expand Down Expand Up @@ -380,7 +382,8 @@ class Message(models.Model):
class HouseholdMember(models.Model):
screen = models.ForeignKey(Screen, related_name="household_members", on_delete=models.CASCADE)
relationship = models.CharField(max_length=30, blank=True, null=True)
age = models.IntegerField(blank=True, null=True)
age = models.PositiveIntegerField(blank=True, null=True)
birth_year_month = models.DateField(blank=True, null=True)
student = models.BooleanField(blank=True, null=True)
student_full_time = models.BooleanField(blank=True, null=True)
pregnant = models.BooleanField(blank=True, null=True)
Expand Down Expand Up @@ -466,6 +469,35 @@ def is_dependent(self):
def is_in_tax_unit(self):
return self.is_head() or self.is_spouse() or self.is_dependent()

@property
def birth_year(self) -> Optional[int]:
if self.birth_year_month is None:
return None

return self.birth_year_month.year

@property
def birth_month(self) -> Optional[int]:
if self.birth_year_month is None:
return None

return self.birth_year_month.month

def calc_age(self) -> int:
if self.birth_year_month is None:
return self.age

return self.age_from_date(self.birth_year_month)

@staticmethod
def age_from_date(birth_year_month: datetime) -> int:
today = datetime.now()

if today.month >= birth_year_month.month:
return today.year - birth_year_month.year

return today.year - birth_year_month.year - 1

def missing_fields(self):
member_fields = (
"relationship",
Expand Down
30 changes: 30 additions & 0 deletions screener/serializers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime, timedelta, date
from screener.models import Screen, HouseholdMember, IncomeStream, Expense, Message, Insurance
from authentication.serializers import UserOffersSerializer
from rest_framework import serializers
Expand Down Expand Up @@ -43,6 +44,33 @@ class Meta:
class HouseholdMemberSerializer(serializers.ModelSerializer):
income_streams = IncomeStreamSerializer(many=True)
insurance = InsuranceSerializer()
birth_year = serializers.IntegerField(required=False, allow_null=True)
birth_month = serializers.IntegerField(required=False, allow_null=True)

def validate(self, data):
birth_year = data.pop("birth_year", None)
birth_month = data.pop("birth_month", None)

if birth_year is None or birth_month is None:
return data

if birth_month < 1 or birth_month > 12:
raise serializers.ValidationError("Birth month must be between 1 and 12")

birth_year_month = datetime(year=birth_year, month=birth_month, day=1)

# add a day for timezones
today = datetime.now() + timedelta(days=1)

if birth_year_month > today:
raise serializers.ValidationError("Birth year and month are in the future")

data["birth_year_month"] = birth_year_month.date()

if "age" not in data or data["age"] is None:
data["age"] = HouseholdMember.age_from_date(birth_year_month)

return data

class Meta:
model = HouseholdMember
Expand All @@ -65,6 +93,8 @@ class Meta:
"has_income",
"income_streams",
"insurance",
"birth_year",
"birth_month",
)
read_only_fields = ("screen", "id")

Expand Down
2 changes: 1 addition & 1 deletion screener/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def update(self, request, pk=None):
user = get_object_or_404(queryset, uuid=pk)
body = json.loads(request.body.decode())
serializer = ScreenSerializer(user, data=body)
serializer.is_valid()
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)

Expand Down

0 comments on commit e3ea200

Please sign in to comment.