164 lines
5.5 KiB
Python
164 lines
5.5 KiB
Python
"""Excel-verified FCR/EEF formula tests (Sukawarna Kandang Atas days 30–35)."""
|
||
|
||
from django.test import SimpleTestCase
|
||
|
||
from apps.operations.services.kpi_formulas import (
|
||
compute_average_harvest_day,
|
||
compute_average_harvest_weight,
|
||
compute_chicken_life,
|
||
compute_chicken_life_percentage,
|
||
compute_eef,
|
||
compute_fcr,
|
||
compute_stock_akhir,
|
||
)
|
||
|
||
# From Laporan_Akhir-Siklus_Sukawarna_Kandang-Atas … FCR + EEF sheets.
|
||
# harvest_total / harvest_age_sum reconstructed so live-blend avgs match Excel.
|
||
EXCEL_DAYS = [
|
||
{
|
||
"age": 30,
|
||
"feed_total": 928,
|
||
"iot_weight": 1480.51,
|
||
"stock_akhir": 17658,
|
||
"harvest_weight_total_g": 6272.1 * 1000,
|
||
"harvest_total": 6400,
|
||
"life_pct": 96.232,
|
||
"excel_fcr": 1.4314384667247064,
|
||
"excel_avg_bw": 1347.3665965583173,
|
||
"excel_avg_day": 29.112145648017293,
|
||
"excel_eef": 311.14182093425177,
|
||
},
|
||
{
|
||
"age": 31,
|
||
"feed_total": 970,
|
||
"iot_weight": 1524.39,
|
||
"stock_akhir": 16419,
|
||
"harvest_weight_total_g": 7665.3 * 1000,
|
||
"harvest_total": 7600,
|
||
"life_pct": 96.076,
|
||
"excel_fcr": 1.4834408509392811,
|
||
"excel_avg_bw": 1361.183205379075,
|
||
"excel_avg_day": 29.844248303426454,
|
||
"excel_eef": 295.39328356931856,
|
||
},
|
||
{
|
||
"age": 32,
|
||
"feed_total": 1013,
|
||
"iot_weight": 1578.85,
|
||
"stock_akhir": 15929,
|
||
"harvest_weight_total_g": 8229.1 * 1000,
|
||
"harvest_total": 8045,
|
||
"life_pct": 95.896,
|
||
"excel_fcr": 1.5174392423955845,
|
||
"excel_avg_bw": 1392.2833757403853,
|
||
"excel_avg_day": 30.52506882455994,
|
||
"excel_eef": 288.2439276697344,
|
||
},
|
||
{
|
||
"age": 33,
|
||
"feed_total": 1031,
|
||
"iot_weight": 1609.98,
|
||
"stock_akhir": 10458,
|
||
"harvest_weight_total_g": 16476 * 1000,
|
||
"harvest_total": 13421,
|
||
"life_pct": 95.516,
|
||
"excel_fcr": 1.5474360050440639,
|
||
"excel_avg_bw": 1395.0823250554881,
|
||
"excel_avg_day": 31.18229406591566,
|
||
"excel_eef": 276.15645742250786,
|
||
},
|
||
{
|
||
"age": 34,
|
||
"feed_total": 1052,
|
||
"iot_weight": 1720.44,
|
||
"stock_akhir": 10347,
|
||
"harvest_weight_total_g": 16476 * 1000,
|
||
"harvest_total": 13421,
|
||
"life_pct": 95.072,
|
||
"excel_fcr": 1.5345391200273755,
|
||
"excel_avg_bw": 1442.1656294177044,
|
||
"excel_avg_day": 31.60913833726018,
|
||
"excel_eef": 282.668327683184,
|
||
},
|
||
{
|
||
"age": 35,
|
||
"feed_total": 1059,
|
||
"iot_weight": 1561.1,
|
||
"stock_akhir": 5198,
|
||
"harvest_weight_total_g": 25032.8 * 1000,
|
||
"harvest_total": 18413,
|
||
"life_pct": 94.444,
|
||
"excel_fcr": 1.5974104609804394,
|
||
"excel_avg_bw": 1403.8963957477447,
|
||
"excel_avg_day": 32.02481894032442,
|
||
"excel_eef": 259.1828280867179,
|
||
},
|
||
]
|
||
|
||
|
||
def _harvest_age_sum(row: dict) -> float:
|
||
"""Invert Excel avg_day blend: (sum + age*stock) / (h + stock) = avg_day."""
|
||
age = row["age"]
|
||
stock = row["stock_akhir"]
|
||
h = row["harvest_total"]
|
||
return row["excel_avg_day"] * (h + stock) - age * stock
|
||
|
||
|
||
class KpiFormulaTests(SimpleTestCase):
|
||
def test_chicken_life_excludes_harvest(self):
|
||
self.assertEqual(compute_chicken_life(25000, 942), 24058)
|
||
self.assertEqual(compute_chicken_life_percentage(25000, 24058), 96.232)
|
||
self.assertEqual(compute_stock_akhir(24058, 6400), 17658)
|
||
|
||
def test_fcr_matches_excel_days_30_to_35(self):
|
||
for row in EXCEL_DAYS:
|
||
with self.subTest(age=row["age"]):
|
||
got = compute_fcr(
|
||
row["feed_total"],
|
||
row["stock_akhir"],
|
||
row["iot_weight"],
|
||
row["harvest_weight_total_g"],
|
||
)
|
||
self.assertAlmostEqual(got, row["excel_fcr"], places=9)
|
||
|
||
def test_average_harvest_blend_matches_excel_days_30_to_35(self):
|
||
for row in EXCEL_DAYS:
|
||
with self.subTest(age=row["age"]):
|
||
avg_bw = compute_average_harvest_weight(
|
||
row["harvest_weight_total_g"],
|
||
row["stock_akhir"],
|
||
row["iot_weight"],
|
||
row["harvest_total"],
|
||
)
|
||
avg_day = compute_average_harvest_day(
|
||
_harvest_age_sum(row),
|
||
row["age"],
|
||
row["stock_akhir"],
|
||
row["harvest_total"],
|
||
)
|
||
self.assertAlmostEqual(avg_bw, row["excel_avg_bw"], places=6)
|
||
self.assertAlmostEqual(avg_day, row["excel_avg_day"], places=6)
|
||
|
||
def test_eef_matches_excel_days_30_to_35(self):
|
||
# Use Excel avg columns + computed FCR (same path as verified against the sheet).
|
||
for row in EXCEL_DAYS:
|
||
with self.subTest(age=row["age"]):
|
||
fcr = compute_fcr(
|
||
row["feed_total"],
|
||
row["stock_akhir"],
|
||
row["iot_weight"],
|
||
row["harvest_weight_total_g"],
|
||
)
|
||
got = compute_eef(
|
||
row["life_pct"],
|
||
row["excel_avg_bw"],
|
||
row["excel_avg_day"],
|
||
fcr,
|
||
)
|
||
self.assertAlmostEqual(got, row["excel_eef"], places=9)
|
||
|
||
def test_fcr_and_eef_zero_when_denom_invalid(self):
|
||
self.assertEqual(compute_fcr(100, 0, 0, 0), 0.0)
|
||
self.assertEqual(compute_eef(99.0, 1000.0, 0, 1.2), 0.0)
|
||
self.assertEqual(compute_eef(99.0, 1000.0, 10, 0), 0.0)
|