diff --git a/programs/admin.py b/programs/admin.py
index 31fed005..e72b5d53 100644
--- a/programs/admin.py
+++ b/programs/admin.py
@@ -5,6 +5,7 @@
from .models import (
LegalStatus,
Program,
+ ProgramCategory,
UrgentNeed,
Navigator,
UrgentNeedFunction,
@@ -301,6 +302,35 @@ def action_buttons(self, obj):
action_buttons.allow_tags = True
+class ProgramCategoryAdmin(ModelAdmin):
+ search_fields = ("external_name",)
+ list_display = ["get_str", "external_name", "action_buttons"]
+
+ 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):
+ return format_html(
+ """
+
+ """,
+ reverse("translation_admin_url", args=[obj.name.id]),
+ reverse("translation_admin_url", args=[obj.description.id]),
+ )
+
+ 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)
@@ -315,3 +345,4 @@ def action_buttons(self, obj):
admin.site.register(Referrer, ReferrerAdmin)
admin.site.register(WebHookFunction, WebHookFunctionsAdmin)
admin.site.register(TranslationOverride, TranslationOverrideAdmin)
+admin.site.register(ProgramCategory, ProgramCategoryAdmin)
diff --git a/programs/migrations/0090_programcategory.py b/programs/migrations/0090_programcategory.py
new file mode 100644
index 00000000..bb07facf
--- /dev/null
+++ b/programs/migrations/0090_programcategory.py
@@ -0,0 +1,50 @@
+# Generated by Django 4.2.15 on 2024-10-09 18:49
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("translations", "0004_translation_no_auto"),
+ ("programs", "0089_alter_translationoverride_counties_and_more"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="ProgramCategory",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "external_name",
+ models.CharField(blank=True, max_length=120, null=True, unique=True),
+ ),
+ ("calculator", models.CharField(blank=True, max_length=120, null=True)),
+ (
+ "description",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="program_category_description",
+ to="translations.translation",
+ ),
+ ),
+ (
+ "name",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="program_category_name",
+ to="translations.translation",
+ ),
+ ),
+ ],
+ ),
+ ]
diff --git a/programs/migrations/0091_program_category_v2.py b/programs/migrations/0091_program_category_v2.py
new file mode 100644
index 00000000..eeae4b0e
--- /dev/null
+++ b/programs/migrations/0091_program_category_v2.py
@@ -0,0 +1,25 @@
+# Generated by Django 4.2.15 on 2024-10-09 18:53
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("programs", "0090_programcategory"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="program",
+ name="category_v2",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="programs",
+ to="programs.programcategory",
+ ),
+ ),
+ ]
diff --git a/programs/migrations/0092_programcategory_icon.py b/programs/migrations/0092_programcategory_icon.py
new file mode 100644
index 00000000..b23a39fd
--- /dev/null
+++ b/programs/migrations/0092_programcategory_icon.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.2.15 on 2024-10-09 20:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("programs", "0091_program_category_v2"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="programcategory",
+ name="icon",
+ field=models.CharField(default="housing", max_length=120),
+ preserve_default=False,
+ ),
+ ]
diff --git a/programs/models.py b/programs/models.py
index a7bef9ce..b8590033 100644
--- a/programs/models.py
+++ b/programs/models.py
@@ -6,7 +6,7 @@
from programs.util import Dependencies
import requests
from integrations.util.cache import Cache
-from typing import Optional, Type, TypedDict
+from typing import Optional, TypedDict
from programs.programs.translation_overrides import warning_calculators
@@ -97,6 +97,70 @@ def __str__(self):
return self.status
+class ProgramCategoryManager(models.Manager):
+ translated_fields = ("name", "description")
+
+ def new_program_category(self, external_name: str, icon: str):
+ translations = {}
+ for field in self.translated_fields:
+ translations[field] = Translation.objects.add_translation(
+ f"program_category.{external_name}_temporary_key-{field}"
+ )
+
+ program_category = self.create(external_name=external_name, icon=icon, **translations)
+
+ for [field, translation] in translations.items():
+ translation.label = f"program_category.{external_name}_{program_category.id}-{field}"
+ translation.save()
+
+ return program_category
+
+
+class ProgramCategoryDataController(ModelDataController["ProgramCategory"]):
+ _model_name = "ProgramCategory"
+
+ DataType = TypedDict(
+ "DataType",
+ {
+ "calculator": str,
+ "icon": str,
+ },
+ )
+
+ def to_model_data(self) -> DataType:
+ program_category = self.instance
+ return {"calculator": program_category.calculator, "icon": program_category.icon}
+
+ def from_model_data(self, data: DataType):
+ program_category = self.instance
+
+ program_category.calculator = data["calculator"]
+ program_category.icon = data["icon"]
+
+ @classmethod
+ def create_instance(cls, external_name: str, Model: type["ProgramCategory"]) -> "ProgramCategory":
+ return Model.objects.new_program_category(external_name, "housing")
+
+
+class ProgramCategory(models.Model):
+ external_name = models.CharField(max_length=120, blank=True, null=True, unique=True)
+ calculator = models.CharField(max_length=120, blank=True, null=True)
+ icon = models.CharField(max_length=120, blank=False, null=False)
+ name = models.ForeignKey(
+ Translation, related_name="program_category_name", blank=False, null=False, on_delete=models.PROTECT
+ )
+ description = models.ForeignKey(
+ Translation, related_name="program_category_description", blank=False, null=False, on_delete=models.PROTECT
+ )
+
+ objects = ProgramCategoryManager()
+
+ TranslationExportBuilder = ProgramCategoryDataController
+
+ def __str__(self):
+ return self.name.text
+
+
class DocumentManager(models.Manager):
translated_fields = ("text",)
@@ -128,7 +192,7 @@ class Document(models.Model):
TranslationExportBuilder = DocumentDataController
def __str__(self) -> str:
- return self.external_name
+ return self.text.text
class ProgramManager(models.Manager):
@@ -174,7 +238,7 @@ def new_program(self, name_abbreviated):
class ProgramDataController(ModelDataController["Program"]):
_model_name = "Program"
- dependencies = ["Document"]
+ dependencies = ["Document", "ProgramCategory"]
FplDataType = TypedDict("FplDataType", {"year": str, "period": str})
LegalStatusesDataType = list[TypedDict("LegalStatusDataType", {"status": str})]
@@ -187,6 +251,7 @@ class ProgramDataController(ModelDataController["Program"]):
"active": bool,
"low_confidence": bool,
"documents": list[str],
+ "category": str,
},
)
@@ -207,6 +272,7 @@ def to_model_data(self) -> DataType:
"low_confidence": program.low_confidence,
"name_abbreviated": program.name_abbreviated,
"documents": [d.external_name for d in program.documents.all()],
+ "category": program.category_v2.external_name,
}
def from_model_data(self, data: DataType):
@@ -248,6 +314,10 @@ def from_model_data(self, data: DataType):
documents.append(doc)
program.documents.set(documents)
+ # get program category
+ program_category = ProgramCategory.objects.get(external_name=data["category"])
+ program.category_v2 = program_category
+
program.save()
@classmethod
@@ -266,6 +336,9 @@ class Program(models.Model):
active = models.BooleanField(blank=True, default=True)
low_confidence = models.BooleanField(blank=True, null=False, default=False)
fpl = models.ForeignKey(FederalPoveryLimit, related_name="fpl", blank=True, null=True, on_delete=models.SET_NULL)
+ category_v2 = models.ForeignKey(
+ ProgramCategory, related_name="programs", blank=True, null=True, on_delete=models.SET_NULL
+ )
description_short = models.ForeignKey(
Translation, related_name="program_description_short", blank=False, null=False, on_delete=models.PROTECT
diff --git a/programs/programs/calc.py b/programs/programs/calc.py
index 527f83ac..8ce92ea1 100644
--- a/programs/programs/calc.py
+++ b/programs/programs/calc.py
@@ -10,7 +10,7 @@ class MemberEligibility:
def __init__(self, member: HouseholdMember) -> None:
self.member = member
self.eligible = True
- self.value = 0
+ self.value: int = 0
def condition(self, passed: bool):
"""
diff --git a/programs/programs/categories/__init__.py b/programs/programs/categories/__init__.py
new file mode 100644
index 00000000..71eb61d6
--- /dev/null
+++ b/programs/programs/categories/__init__.py
@@ -0,0 +1,7 @@
+from .base import ProgramCategoryCapCalculator
+from .co import co_category_cap_calculators
+
+category_cap_calculators: dict[str, type[ProgramCategoryCapCalculator]] = {
+ "no_cap": ProgramCategoryCapCalculator,
+ **co_category_cap_calculators,
+}
diff --git a/programs/programs/categories/base.py b/programs/programs/categories/base.py
new file mode 100644
index 00000000..2dda2e0c
--- /dev/null
+++ b/programs/programs/categories/base.py
@@ -0,0 +1,110 @@
+from collections.abc import Callable
+from programs.programs.calc import Eligibility
+from dataclasses import dataclass
+
+
+@dataclass
+class CategoryCap:
+ programs: list[str]
+ cap: int = 0
+ member_cap: bool = False
+
+
+class ProgramCategoryCapCalculator:
+ # caps with a constant max
+ static_caps: list[CategoryCap] = []
+
+ # caps where the cap is the highest program value
+ max_caps: list[CategoryCap] = []
+
+ # caps where the cap is the average value of the program
+ average_caps: list[CategoryCap] = []
+
+ def __init__(self, eligibility: dict[str, Eligibility]):
+ self.eligibility = eligibility
+
+ def caps(self) -> list[CategoryCap]:
+ static_caps = self._handle_caps(self.static_caps, self.calc_static_cap)
+ max_caps = self._handle_caps(self.max_caps, self.calc_max_cap)
+ average_caps = self._handle_caps(self.average_caps, self.calc_average_cap)
+
+ return static_caps + max_caps + average_caps + self.other_caps()
+
+ def other_caps(self) -> list[CategoryCap]:
+ """
+ Override this method to add custom caps
+ """
+ return []
+
+ def calc_static_cap(self, cap: CategoryCap, values: list[int]):
+ if any(v > 0 for v in values):
+ return cap.cap
+
+ return 0
+
+ def calc_max_cap(self, cap: CategoryCap, values: list[int]):
+ return max(*values)
+
+ def calc_average_cap(self, cap: CategoryCap, values: list[int]):
+ return sum(values) / len(values)
+
+ def _handle_caps(self, caps: list[CategoryCap], func: Callable[[CategoryCap, list[int]], int]) -> list[CategoryCap]:
+ """
+ Take a caps and a function and calculate the category caps with that function
+ """
+ calculated_caps = []
+
+ for cap in caps:
+ if cap.member_cap:
+ calculated_caps.append(self._handle_member_cap(cap, func))
+ continue
+
+ calculated_caps.append(self._handle_household_cap(cap, func))
+
+ return calculated_caps
+
+ def _handle_member_cap(self, cap: CategoryCap, func: Callable[[CategoryCap, list[int]], int]) -> CategoryCap:
+ """
+ Take a cap and a function and calculate the category cap for each member with that function
+ """
+ member_values: dict[int, list[int]] = {}
+
+ new_cap = CategoryCap(cap.programs.copy())
+
+ for program in cap.programs:
+ if program not in self.eligibility:
+ new_cap.programs.remove(program)
+ continue
+
+ eligibility = self.eligibility[program]
+ for member_eligibility in eligibility.eligible_members:
+ member_id = member_eligibility.member.id
+
+ if member_id not in member_values:
+ member_values[member_id] = []
+
+ member_values[member_id].append(member_eligibility.value)
+
+ for values in member_values.values():
+ new_cap.cap += func(cap, values)
+
+ return new_cap
+
+ def _handle_household_cap(self, cap: CategoryCap, func: Callable[[CategoryCap, list[int]], int]) -> CategoryCap:
+ """
+ Take a cap and a function and calculate the category cap for the household with that function
+ """
+ values: list[int] = []
+
+ new_cap = CategoryCap(cap.programs.copy())
+
+ for program in cap.programs:
+ if program not in self.eligibility:
+ new_cap.programs.remove(program)
+ continue
+
+ values.append(self.eligibility[program])
+
+ new_cap.cap = func(cap, values)
+
+ return new_cap
diff --git a/programs/programs/categories/co/__init__.py b/programs/programs/categories/co/__init__.py
new file mode 100644
index 00000000..15c3bca4
--- /dev/null
+++ b/programs/programs/categories/co/__init__.py
@@ -0,0 +1,4 @@
+from programs.programs.categories.co.preschool import PreschoolCategoryCap
+from ..base import ProgramCategoryCapCalculator
+
+co_category_cap_calculators: dict[str, type[ProgramCategoryCapCalculator]] = {"co_preschool": PreschoolCategoryCap}
diff --git a/programs/programs/categories/co/preschool.py b/programs/programs/categories/co/preschool.py
new file mode 100644
index 00000000..b1ac5971
--- /dev/null
+++ b/programs/programs/categories/co/preschool.py
@@ -0,0 +1,5 @@
+from programs.programs.categories.base import CategoryCap, ProgramCategoryCapCalculator
+
+
+class PreschoolCategoryCap(ProgramCategoryCapCalculator):
+ static_caps = [CategoryCap(["dpp", "upk", "chs"], cap=8_640, member_cap=True)]
diff --git a/programs/programs/co/pe/member.py b/programs/programs/co/pe/member.py
index 99cb95a1..5eb19e6a 100644
--- a/programs/programs/co/pe/member.py
+++ b/programs/programs/co/pe/member.py
@@ -85,10 +85,10 @@ class CoWic(Wic):
wic_categories = {
"NONE": 0,
"INFANT": 130,
- "CHILD": 26,
- "PREGNANT": 47,
- "POSTPARTUM": 47,
- "BREASTFEEDING": 52,
+ "CHILD": 79,
+ "PREGNANT": 104,
+ "POSTPARTUM": 88,
+ "BREASTFEEDING": 121,
}
pe_inputs = [
*Wic.pe_inputs,
diff --git a/programs/programs/federal/pe/spm.py b/programs/programs/federal/pe/spm.py
index 74393ead..1ab67cc8 100644
--- a/programs/programs/federal/pe/spm.py
+++ b/programs/programs/federal/pe/spm.py
@@ -1,6 +1,5 @@
from programs.programs.policyengine.calculators.base import PolicyEngineSpmCalulator
import programs.programs.policyengine.calculators.dependencies as dependency
-from programs.programs.policyengine.calculators.dependencies.base import Member, SpmUnit
class Snap(PolicyEngineSpmCalulator):
@@ -27,6 +26,7 @@ class Snap(PolicyEngineSpmCalulator):
dependency.member.AgeDependency,
dependency.member.MedicalExpenseDependency,
dependency.member.IsDisabledDependency,
+ dependency.member.SnapIneligibleStudentDependency,
# NOTE: remove this to always use the SUA in CO.
dependency.spm.SnapAlwaysUseSuaDependency,
]
diff --git a/programs/programs/helpers.py b/programs/programs/helpers.py
index 0bc927c5..10763ba6 100644
--- a/programs/programs/helpers.py
+++ b/programs/programs/helpers.py
@@ -1,4 +1,5 @@
from programs.programs.calc import Eligibility
+from screener.models import Screen, HouseholdMember
STATE_MEDICAID_OPTIONS = ("co_medicaid", "nc_medicaid")
@@ -8,3 +9,24 @@ def medicaid_eligible(data: dict[str, Eligibility]):
for name in STATE_MEDICAID_OPTIONS:
if name in data:
return data[name].eligible
+
+
+def snap_ineligible_student(screen: Screen, member: HouseholdMember):
+ if not member.student:
+ return False
+
+ if member.age < 18 or member.age >= 50:
+ return False
+
+ if member.disabled:
+ return False
+
+ head_or_spouse = member.is_head() or member.is_spouse()
+ if head_or_spouse and screen.num_children(age_max=5) > 0:
+ return False
+
+ single_parent = member.is_head() and not member.is_married()["is_married"]
+ if single_parent and screen.num_children(age_max=11) > 0:
+ return False
+
+ return True
diff --git a/programs/programs/policyengine/calculators/dependencies/member.py b/programs/programs/policyengine/calculators/dependencies/member.py
index 55713027..ec71b4e7 100644
--- a/programs/programs/policyengine/calculators/dependencies/member.py
+++ b/programs/programs/policyengine/calculators/dependencies/member.py
@@ -1,3 +1,4 @@
+from programs.programs.helpers import snap_ineligible_student
from .base import Member
@@ -221,6 +222,16 @@ class CommoditySupplementalFoodProgram(Member):
field = "commodity_supplemental_food_program"
+class SnapIneligibleStudentDependency(Member):
+ field = "is_snap_ineligible_student"
+
+ dependencies = ("age",)
+
+ # PE does not take the age of the children into acount, so we calculate this ourselves
+ def value(self):
+ return snap_ineligible_student(self.screen, self.member)
+
+
class IncomeDependency(Member):
dependencies = (
"income_type",
diff --git a/programs/programs/warnings/__init__.py b/programs/programs/warnings/__init__.py
index 712cfa86..7fad1a8e 100644
--- a/programs/programs/warnings/__init__.py
+++ b/programs/programs/warnings/__init__.py
@@ -1,6 +1,7 @@
from .tax_unit import TaxUnit
from .base import WarningCalculator
from .dont_show import DontShow
+from .co import co_warning_calculators
general_calculators: dict[str, type[WarningCalculator]] = {
@@ -9,6 +10,6 @@
"_tax_unit": TaxUnit,
}
-specific_calculators: dict[str, type[WarningCalculator]] = {}
+specific_calculators: dict[str, type[WarningCalculator]] = {**co_warning_calculators}
warning_calculators: dict[str, type[WarningCalculator]] = {**general_calculators, **specific_calculators}
diff --git a/programs/programs/warnings/co/__init__.py b/programs/programs/warnings/co/__init__.py
new file mode 100644
index 00000000..c1e87720
--- /dev/null
+++ b/programs/programs/warnings/co/__init__.py
@@ -0,0 +1,5 @@
+from programs.programs.warnings.base import WarningCalculator
+from programs.programs.warnings.co.snap_student import SnapStudentWarning
+
+
+co_warning_calculators: dict[str, type[WarningCalculator]] = {"co_snap_student": SnapStudentWarning}
diff --git a/programs/programs/warnings/co/snap_student.py b/programs/programs/warnings/co/snap_student.py
new file mode 100644
index 00000000..c1d7a340
--- /dev/null
+++ b/programs/programs/warnings/co/snap_student.py
@@ -0,0 +1,15 @@
+from programs.programs.helpers import snap_ineligible_student
+from programs.programs.warnings.base import WarningCalculator
+
+
+class SnapStudentWarning(WarningCalculator):
+ dependencies = [
+ "age",
+ ]
+
+ def eligible(self) -> bool:
+ for member in self.screen.household_members.all():
+ if snap_ineligible_student(self.screen, member):
+ return True
+
+ return False
diff --git a/screener/models.py b/screener/models.py
index ae27e042..78e93c65 100644
--- a/screener/models.py
+++ b/screener/models.py
@@ -449,10 +449,10 @@ def is_married(self):
def has_disability(self):
return self.disabled or self.visually_impaired or self.long_term_disability
- def is_head(self):
+ def is_head(self) -> bool:
return self.relationship == "headOfHousehold"
- def is_spouse(self):
+ def is_spouse(self) -> bool:
return self.screen.relationship_map()[self.screen.get_head().id] == self.id
def is_dependent(self):
diff --git a/screener/serializers.py b/screener/serializers.py
index 1ccb9bee..9959b654 100644
--- a/screener/serializers.py
+++ b/screener/serializers.py
@@ -275,6 +275,7 @@ class EligibilitySerializer(serializers.Serializer):
multiple_tax_units = serializers.BooleanField()
estimated_value_override = TranslationSerializer()
warning_messages = TranslationSerializer(many=True)
+ category_id = serializers.CharField()
class Meta:
fields = "__all__"
@@ -287,6 +288,18 @@ class Meta:
fields = ("translations",)
+class ProgramCategoryCapSerializer(serializers.Serializer):
+ programs = serializers.ListSerializer(child=serializers.CharField())
+ cap = serializers.IntegerField()
+
+
+class ProgramCategorySerializer(serializers.Serializer):
+ icon = serializers.CharField()
+ name = TranslationSerializer()
+ description = TranslationSerializer()
+ caps = ProgramCategoryCapSerializer(many=True)
+
+
class UrgentNeedSerializer(serializers.Serializer):
name = TranslationSerializer()
description = TranslationSerializer()
@@ -302,3 +315,4 @@ class ResultsSerializer(serializers.Serializer):
default_language = serializers.CharField()
missing_programs = serializers.BooleanField()
validations = ValidationSerializer(many=True)
+ program_categories = serializers.DictField(child=ProgramCategorySerializer())
diff --git a/screener/views.py b/screener/views.py
index 822e0c66..1381561e 100644
--- a/screener/views.py
+++ b/screener/views.py
@@ -2,6 +2,7 @@
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from integrations.services.communications import MessageUser
+from programs.programs import categories
from programs.programs.helpers import STATE_MEDICAID_OPTIONS
from programs.programs.policyengine.calculators import all_calculators
from screener.models import (
@@ -25,10 +26,20 @@
MessageSerializer,
ResultsSerializer,
)
-from programs.programs.policyengine.policy_engine import calc_pe_eligibility
+from programs.programs.policyengine.policy_engine import all_eligibility, 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, TranslationOverride
+from programs.models import (
+ Document,
+ Navigator,
+ ProgramCategory,
+ UrgentNeed,
+ Program,
+ Referrer,
+ WarningMessage,
+ TranslationOverride,
+)
+from programs.programs.categories import ProgramCategoryCapCalculator, category_cap_calculators
from django.core.exceptions import ObjectDoesNotExist
from programs.programs.warnings import warning_calculators
from validations.serializers import ValidationSerializer
@@ -124,7 +135,7 @@ def get(self, request, id):
screen = Screen.objects.prefetch_related(
"household_members", "household_members__income_streams", "household_members__insurance", "expenses"
).get(uuid=id)
- eligibility, missing_programs = eligibility_results(screen)
+ eligibility, missing_programs, categories = eligibility_results(screen)
urgent_needs = urgent_need_results(screen, eligibility)
validations = ValidationSerializer(screen.validations.all(), many=True).data
@@ -135,6 +146,7 @@ def get(self, request, id):
"default_language": screen.request_language_code,
"missing_programs": missing_programs,
"validations": validations,
+ "program_categories": categories,
}
hooks = eligibility_hooks()
if screen.submission_date is None:
@@ -187,6 +199,8 @@ def eligibility_results(screen: Screen, batch=False):
all_programs = (
Program.objects.filter(active=True)
+ # NOTE: uncomment when categories are ready
+ # .filter(active=True, category_v2__isnull=False)
.prefetch_related(
"legal_status_required",
"fpl",
@@ -204,8 +218,9 @@ def eligibility_results(screen: Screen, batch=False):
"translation_overrides",
"translation_overrides__counties",
*translations_prefetch_name("translation_overrides__", TranslationOverride.objects.translated_fields),
- )
- .exclude(id__in=excluded_programs)
+ "category_v2",
+ *translations_prefetch_name("category_v2__", ProgramCategory.objects.translated_fields),
+ ).exclude(id__in=excluded_programs)
)
data = []
@@ -360,9 +375,34 @@ def sort_first(program):
"low_confidence": program.low_confidence,
"documents": [default_message(d.text) for d in program.documents.all()],
"warning_messages": [default_message(w.message) for w in warnings],
+ "category_id": None if program.category_v2 is None else str(program.category_v2.id),
}
)
+ categories = {}
+ # NOTE: uncomment when categories are ready
+ # for program in all_programs:
+ # category = program.category_v2
+ # if category.id in categories:
+ # continue
+ #
+ # CategoryCalculator = ProgramCategoryCapCalculator
+ # if category.calculator is not None and category.calculator != "":
+ # CategoryCalculator = category_cap_calculators[category.calculator]
+ #
+ # calculator = CategoryCalculator(program_eligibility)
+ #
+ # caps = []
+ # for cap in calculator.caps():
+ # caps.append({"programs": cap.programs, "cap": cap.cap})
+ #
+ # categories[category.id] = {
+ # "icon": category.icon,
+ # "name": default_message(category.name),
+ # "description": default_message(category.description),
+ # "cap": caps,
+ # }
+
ProgramEligibilitySnapshot.objects.bulk_create(program_snapshots)
snapshot.had_error = False
snapshot.save()
@@ -373,7 +413,7 @@ def sort_first(program):
clean_program["estimated_value"] = math.trunc(clean_program["estimated_value"])
eligible_programs.append(clean_program)
- return eligible_programs, missing_programs
+ return eligible_programs, missing_programs, categories
class GetProgramTranslation:
diff --git a/translations/templates/base.html b/translations/templates/base.html
index a90dc587..84722ea6 100644
--- a/translations/templates/base.html
+++ b/translations/templates/base.html
@@ -31,6 +31,14 @@ Translations Admin