78 lines
3.1 KiB
Python
78 lines
3.1 KiB
Python
"""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()
|