@@ -0,0 +1,77 @@
|
||||
"""Canonical tests: WESP GfE 2001 ingredient derive (не Excel CSV)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from app.lab.calc.gfe_policies import DeriveContext, default_om_digestibility_pct
|
||||
from app.lab.calc.ingredient_derive import derive_ingredient_nutrients
|
||||
|
||||
_FIXTURES = Path(__file__).resolve().parent / "fixtures" / "lab_derive_canonical.json"
|
||||
|
||||
|
||||
def _load_cases() -> list[dict]:
|
||||
with _FIXTURES.open(encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
class LabIngredientDeriveTests(unittest.TestCase):
|
||||
def _assert_close(self, expected: float, actual: float, label: str) -> None:
|
||||
tol = max(1e-3, abs(expected) * 1e-4)
|
||||
self.assertTrue(
|
||||
math.isclose(expected, actual, abs_tol=tol),
|
||||
f"{label}: expected {expected}, got {actual}",
|
||||
)
|
||||
|
||||
def test_derive_canonical_cases(self) -> None:
|
||||
for case in _load_cases():
|
||||
if "expected" not in case:
|
||||
continue
|
||||
ctx_raw = case.get("context") or {}
|
||||
ctx = DeriveContext(
|
||||
feed_group=ctx_raw.get("feed_group", "unknown"),
|
||||
is_main_feed=bool(ctx_raw.get("is_main_feed")),
|
||||
)
|
||||
derived = derive_ingredient_nutrients(case["input"], context=ctx)
|
||||
for key, expected in case["expected"].items():
|
||||
self.assertIn(key, derived, msg=f"{case['id']}: missing {key}")
|
||||
self._assert_close(float(expected), derived[key], f"{case['id']}/{key}")
|
||||
|
||||
def test_rnb_from_derive(self) -> None:
|
||||
case = next(c for c in _load_cases() if c.get("id") == "rnb_formula")
|
||||
ctx = DeriveContext(feed_group="concentrate")
|
||||
derived = derive_ingredient_nutrients(case["input"], context=ctx)
|
||||
cp = derived["уСП формул"]
|
||||
rnb = (derived["Сыр. Протеин"] - cp) / 6.25
|
||||
self._assert_close(case["expected_rnb"], rnb, "rnb_formula")
|
||||
self._assert_close(case["expected_rnb"], derived["БРА"], "rnb_header")
|
||||
|
||||
def test_omd_default_by_feed_class(self) -> None:
|
||||
self.assertEqual(default_om_digestibility_pct(DeriveContext(feed_group="rough")), 65.0)
|
||||
self.assertEqual(
|
||||
default_om_digestibility_pct(DeriveContext(feed_group="succulent")),
|
||||
72.0,
|
||||
)
|
||||
self.assertEqual(
|
||||
default_om_digestibility_pct(DeriveContext(feed_group="concentrate")),
|
||||
91.0,
|
||||
)
|
||||
self.assertEqual(
|
||||
default_om_digestibility_pct(DeriveContext(is_main_feed=True)),
|
||||
65.0,
|
||||
)
|
||||
|
||||
def test_straw_without_omd_not_91_pct(self) -> None:
|
||||
case = next(c for c in _load_cases() if c.get("id") == "straw_default_omd_65")
|
||||
ctx = DeriveContext(feed_group="rough", is_main_feed=True)
|
||||
derived = derive_ingredient_nutrients(case["input"], context=ctx)
|
||||
organic = case["input"]["СВ"] - case["input"]["Сырая зола"]
|
||||
wrong = organic * 0.91
|
||||
self.assertLess(derived["Перевар Орг Вещ"], wrong - 100)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user