Skip to content

Commit 164c418

Browse files
committed
Address PR feedback see HEA-984
1 parent bf14ae3 commit 164c418

3 files changed

Lines changed: 99 additions & 60 deletions

File tree

apps/baseline/migrations/0029_change_quantity_fields_to_float.py

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Generated by Django 5.2.12 on 2026-04-04 12:57
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("baseline", "0029_alter_meatproduction_animals_slaughtered_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="foodpurchase",
15+
name="unit_multiple",
16+
field=models.FloatField(
17+
blank=True,
18+
help_text="Multiple of the unit of measure in a single purchase",
19+
null=True,
20+
verbose_name="Unit Multiple",
21+
),
22+
),
23+
migrations.AlterField(
24+
model_name="livelihoodactivity",
25+
name="quantity_consumed",
26+
field=models.FloatField(blank=True, null=True, verbose_name="Quantity Consumed"),
27+
),
28+
migrations.AlterField(
29+
model_name="livelihoodactivity",
30+
name="quantity_other_uses",
31+
field=models.FloatField(blank=True, null=True, verbose_name="Quantity Other Uses"),
32+
),
33+
migrations.AlterField(
34+
model_name="livelihoodactivity",
35+
name="quantity_produced",
36+
field=models.FloatField(blank=True, null=True, verbose_name="Quantity Produced"),
37+
),
38+
migrations.AlterField(
39+
model_name="livelihoodactivity",
40+
name="quantity_purchased",
41+
field=models.FloatField(blank=True, null=True, verbose_name="Quantity Purchased"),
42+
),
43+
migrations.AlterField(
44+
model_name="livelihoodactivity",
45+
name="quantity_sold",
46+
field=models.FloatField(blank=True, null=True, verbose_name="Quantity Sold/Exchanged"),
47+
),
48+
migrations.AlterField(
49+
model_name="milkproduction",
50+
name="quantity_butter_production",
51+
field=models.FloatField(blank=True, null=True, verbose_name="Quantity used for Butter Production"),
52+
),
53+
migrations.AlterField(
54+
model_name="otherpurchase",
55+
name="unit_multiple",
56+
field=models.FloatField(
57+
blank=True,
58+
help_text="Multiple of the unit of measure in a single purchase",
59+
null=True,
60+
verbose_name="Unit Multiple",
61+
),
62+
),
63+
migrations.AlterField(
64+
model_name="reliefgiftother",
65+
name="unit_multiple",
66+
field=models.FloatField(
67+
blank=True,
68+
help_text="Multiple of the unit of measure received each time",
69+
null=True,
70+
verbose_name="Unit Multiple",
71+
),
72+
),
73+
]

apps/baseline/models.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import datetime
6+
import math
67
import numbers
78

89
from django.conf import settings
@@ -1356,7 +1357,7 @@ def validate_quantity_consumed(self):
13561357
)
13571358

13581359
# Check if the actual quantity_consumed matches the expected quantity_consumed
1359-
if self.quantity_consumed and round(self.quantity_consumed, 6) != round(expected_quantity_consumed, 6):
1360+
if self.quantity_consumed and not math.isclose(self.quantity_consumed, expected_quantity_consumed):
13601361
if quantity_butter_production:
13611362
message = "Quantity consumed for Milk Production must be quantity produced + quantity purchased - quantity sold - quantity used for butter production - quantity used for other things" # NOQA: E501
13621363
else:
@@ -1365,7 +1366,7 @@ def validate_quantity_consumed(self):
13651366

13661367
def validate_income(self):
13671368
if self.income and self.quantity_sold is not None and self.price is not None:
1368-
if self.income != self.quantity_sold * self.price:
1369+
if not math.isclose(self.income, self.quantity_sold * self.price):
13691370
raise ValidationError(_("Income for a Livelihood Activity must be quantity sold multiplied by price"))
13701371

13711372
def validate_expenditure(self):
@@ -1384,7 +1385,7 @@ def validate_expenditure(self):
13841385
price = self.price or 0
13851386
expenditure = self.expenditure or 0
13861387

1387-
if self.expenditure and expenditure != quantity_produced * price:
1388+
if self.expenditure and not math.isclose(expenditure, quantity_produced * price):
13881389
raise ValidationError(
13891390
_("Expenditure for a Livelihood Activity must be quantity produced multiplied by price")
13901391
)
@@ -1407,9 +1408,9 @@ def validate_kcals_consumed(self):
14071408
"product": self.livelihood_strategy.product,
14081409
}
14091410
)
1410-
if (
1411-
self.kcals_consumed
1412-
!= self.quantity_consumed * conversion_factor * self.livelihood_strategy.product.kcals_per_unit
1411+
if not math.isclose(
1412+
self.kcals_consumed,
1413+
self.quantity_consumed * conversion_factor * self.livelihood_strategy.product.kcals_per_unit,
14131414
):
14141415
raise ValidationError(
14151416
_(
@@ -1635,7 +1636,7 @@ class MilkType(models.TextChoices):
16351636
# https://unstats.un.org/unsd/classifications/Econ/Detail/EN/1074/22110. That LivelihoodActivity would have a
16361637
# `quantity_produced` equal to the amount of whole milk that was used for ButterProduction, and then the
16371638
# quantity_sold and the quantity_consumed could be tracked separately.
1638-
quantity_butter_production = models.PositiveIntegerField(
1639+
quantity_butter_production = models.FloatField(
16391640
blank=True, null=True, verbose_name=_("Quantity used for Butter Production")
16401641
) # NOQA: E501
16411642
type_of_milk_consumed = models.CharField(
@@ -1734,7 +1735,7 @@ def clean(self):
17341735

17351736
def validate_quantity_produced(self):
17361737
if self.quantity_produced is not None and self.animals_slaughtered and self.carcass_weight is not None:
1737-
if self.quantity_produced != self.animals_slaughtered * self.carcass_weight:
1738+
if not math.isclose(self.quantity_produced, self.animals_slaughtered * self.carcass_weight):
17381739
raise ValidationError(
17391740
_(
17401741
"Quantity Produced for a Meat Production must be animals slaughtered multiplied by carcass weight"
@@ -1787,7 +1788,7 @@ class FoodPurchase(LivelihoodActivity):
17871788
# NIO93 Row B422 tia = 2.5kg
17881789

17891790
# unit_multiple has names like wt_of_measure in the BSS.
1790-
unit_multiple = models.PositiveSmallIntegerField(
1791+
unit_multiple = models.FloatField(
17911792
blank=True,
17921793
null=True,
17931794
verbose_name=_("Unit Multiple"),
@@ -1816,7 +1817,9 @@ def validate_quantity_purchased(self):
18161817
and self.times_per_month is not None
18171818
and self.months_per_year is not None
18181819
):
1819-
if self.quantity_purchased != self.unit_multiple * self.times_per_month * self.months_per_year:
1820+
if not math.isclose(
1821+
self.quantity_purchased, self.unit_multiple * self.times_per_month * self.months_per_year
1822+
):
18201823
raise ValidationError(
18211824
_(
18221825
"Quantity purchased for a Food Purchase must be purchase amount * purchases per month * months per year" # NOQA: E501
@@ -1828,7 +1831,7 @@ def validate_expenditure(self):
18281831
price = self.price or 0
18291832
expenditure = self.expenditure or 0
18301833

1831-
if self.expenditure and expenditure != quantity_purchased * price:
1834+
if self.expenditure and not math.isclose(expenditure, quantity_purchased * price):
18321835
raise ValidationError(_("Expenditure for a Food Purchase must be quantity purchased multiplied by price"))
18331836

18341837
class Meta:
@@ -1910,9 +1913,9 @@ def validate_quantity_produced(self):
19101913
and self.times_per_month is not None
19111914
and self.months_per_year is not None
19121915
):
1913-
if (
1914-
self.quantity_produced
1915-
!= self.payment_per_time * self.people_per_household * self.times_per_month * self.months_per_year
1916+
if not math.isclose(
1917+
self.quantity_produced,
1918+
self.payment_per_time * self.people_per_household * self.times_per_month * self.months_per_year,
19161919
):
19171920
raise ValidationError(
19181921
_(
@@ -1935,7 +1938,7 @@ class ReliefGiftOther(LivelihoodActivity):
19351938

19361939
# Production calculation /validation is `unit_of_measure * unit_multiple * times_per_year`
19371940
# Also used for the number of children receiving school meals.
1938-
unit_multiple = models.PositiveSmallIntegerField(
1941+
unit_multiple = models.FloatField(
19391942
blank=True,
19401943
null=True,
19411944
verbose_name=_("Unit Multiple"),
@@ -1961,7 +1964,7 @@ class ReliefGiftOther(LivelihoodActivity):
19611964

19621965
def validate_quantity_produced(self):
19631966
if self.quantity_produced is not None and self.unit_multiple is not None and self.times_per_year is not None:
1964-
if self.quantity_produced != self.unit_multiple * self.times_per_year:
1967+
if not math.isclose(self.quantity_produced, self.unit_multiple * self.times_per_year):
19651968
raise ValidationError(
19661969
_("Quantity produced for Relief, Gifts, Other must be amount received * times per year")
19671970
)
@@ -2070,16 +2073,17 @@ def validate_income(self):
20702073
and self.times_per_month is not None
20712074
and self.months_per_year is not None
20722075
):
2073-
if round(self.income, 6) != round(
2074-
self.payment_per_time * self.people_per_household * self.times_per_month * self.months_per_year, 6
2076+
if not math.isclose(
2077+
self.income,
2078+
self.payment_per_time * self.people_per_household * self.times_per_month * self.months_per_year,
20752079
):
20762080
raise ValidationError(
20772081
_(
20782082
"Quantity produced for Other Cash Income must be payment per time * number of people * labor per month * months per year" # NOQA: E501
20792083
)
20802084
)
20812085
elif self.income is not None and self.payment_per_time is not None and self.times_per_year is not None:
2082-
if round(self.income, 6) != round(self.payment_per_time * self.times_per_year, 6):
2086+
if not math.isclose(self.income, self.payment_per_time * self.times_per_year):
20832087
raise ValidationError(_("Income for 'Other Cash Income' must be payment per time * times per year"))
20842088

20852089
def calculate_fields(self):
@@ -2103,7 +2107,7 @@ class OtherPurchase(LivelihoodActivity):
21032107
# individual fields must be nullable
21042108
# Do we need this, or can we use combined units of measure like FDW, e.g. 5kg
21052109
# NIO93 Row B422 tia = 2.5kg
2106-
unit_multiple = models.PositiveSmallIntegerField(
2110+
unit_multiple = models.FloatField(
21072111
blank=True,
21082112
null=True,
21092113
verbose_name=_("Unit Multiple"),
@@ -2129,7 +2133,7 @@ def validate_expenditure(self):
21292133
errors = []
21302134
if self.times_per_month is not None and self.months_per_year is not None:
21312135
expected_times_per_year = self.times_per_month * self.months_per_year
2132-
if self.times_per_year is not None and self.times_per_year != expected_times_per_year:
2136+
if self.times_per_year is not None and not math.isclose(self.times_per_year, expected_times_per_year):
21332137
errors.append(
21342138
_(
21352139
"Times per year must be times per month * months per year. Expected: %(expected)s, Found: %(found)s"
@@ -2141,7 +2145,7 @@ def validate_expenditure(self):
21412145
)
21422146
if self.price is not None and self.unit_multiple is not None and self.times_per_year is not None:
21432147
expected_expenditure = self.price * self.unit_multiple * self.times_per_year
2144-
if self.expenditure is not None and self.expenditure != expected_expenditure:
2148+
if self.expenditure is not None and not math.isclose(self.expenditure, expected_expenditure):
21452149
errors.append(
21462150
_(
21472151
"Expenditure for Other Purchases must be price * unit multiple * purchases per year. Expected: %(expected)s, Found: %(found)s"

0 commit comments

Comments
 (0)