@@ -0,0 +1,216 @@
|
||||
"""Native derive формул zootech «База сырья» — WESP GfE 2001 engine."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from app.lab.calc.gfe_policies import (
|
||||
DCAB_CL,
|
||||
DCAB_K,
|
||||
DCAB_NA,
|
||||
DCAB_S,
|
||||
DEFAULT_CP_DIGEST_PCT,
|
||||
DEFAULT_FAT_DIGEST_PCT,
|
||||
DEFAULT_FIBER_DIGEST_PCT,
|
||||
DEFAULT_INSOLUBLE_PROTEIN_PCT,
|
||||
DEFAULT_NFE_DIGEST_PCT,
|
||||
DEFAULT_PROTEIN_FRACTION_PCT,
|
||||
DeriveContext,
|
||||
GE_CP,
|
||||
GE_FAT,
|
||||
GE_FIBER,
|
||||
GE_NFE,
|
||||
ME_CP,
|
||||
ME_FAT,
|
||||
ME_FIBER,
|
||||
ME_OR_RESIDUE,
|
||||
NEL_BASE,
|
||||
NEL_Q_COEFF,
|
||||
NEL_Q_REF_PCT,
|
||||
USP_FAT_THRESHOLD_G_PER_KG_DM,
|
||||
default_om_digestibility_pct,
|
||||
)
|
||||
from app.lab.calc.ingredient_catalog import (
|
||||
DISPLAY_SYNC,
|
||||
HEADER_TO_LETTER,
|
||||
INGREDIENT_HEADERS,
|
||||
LETTER_TO_HEADER,
|
||||
)
|
||||
|
||||
|
||||
def _parse_num(value: Any) -> float | None:
|
||||
if value is None or value == "":
|
||||
return None
|
||||
try:
|
||||
n = float(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
return None if n != n else n
|
||||
|
||||
|
||||
def _normalize_key(value: str) -> str:
|
||||
return " ".join((value or "").split()).strip().lower()
|
||||
|
||||
|
||||
def _v(cells: dict[str, float], letter: str, default: float = 0.0) -> float:
|
||||
return cells.get(letter, default)
|
||||
|
||||
|
||||
def _if_pos(test: float, when_true, when_false: float = 0.0) -> float:
|
||||
"""Excel IF(test>0, …) — ветка when_true не вычисляется при test<=0."""
|
||||
if test > 0:
|
||||
return when_true() if callable(when_true) else when_true
|
||||
return when_false
|
||||
|
||||
|
||||
def dict_to_cells(data: dict[str, Any] | None) -> dict[str, float]:
|
||||
"""Словарь {заголовок: значение} → {буква колонки: значение}."""
|
||||
cells: dict[str, float] = {}
|
||||
if not data:
|
||||
return cells
|
||||
norm_index = {_normalize_key(h): h for h in INGREDIENT_HEADERS}
|
||||
for raw_key, raw_val in data.items():
|
||||
n = _parse_num(raw_val)
|
||||
if n is None:
|
||||
continue
|
||||
nk = _normalize_key(str(raw_key))
|
||||
header = norm_index.get(nk)
|
||||
if header is None:
|
||||
continue
|
||||
letter = HEADER_TO_LETTER.get(header)
|
||||
if letter:
|
||||
cells[letter] = n
|
||||
return cells
|
||||
|
||||
|
||||
def cells_to_dict(cells: dict[str, float]) -> dict[str, float]:
|
||||
out: dict[str, float] = {}
|
||||
for letter, value in cells.items():
|
||||
header = LETTER_TO_HEADER.get(letter)
|
||||
if header and header not in ("№", "Наименование", "Цена 1 кг"):
|
||||
out[header] = value
|
||||
return out
|
||||
|
||||
|
||||
def derive_cells(
|
||||
cells: dict[str, float],
|
||||
*,
|
||||
context: DeriveContext | None = None,
|
||||
) -> dict[str, float]:
|
||||
"""Пересчёт derived-колонок по цепочке формул row 6 «База сырья»."""
|
||||
c = dict(cells)
|
||||
ctx = context or DeriveContext.infer_from_cells(c)
|
||||
omd_default = default_om_digestibility_pct(ctx)
|
||||
|
||||
c["AE"] = DCAB_NA * _v(c, "AA") + DCAB_K * _v(c, "AB") - DCAB_CL * _v(c, "AC") - DCAB_S * _v(c, "AD")
|
||||
c["AG"] = _v(c, "AJ") * _v(c, "AI") / 100.0
|
||||
c["AF"] = _v(c, "AI") - c["AG"] + _v(c, "AH")
|
||||
c["AX"] = _v(c, "D") - _v(c, "F") - _v(c, "K") - _v(c, "M") - _v(c, "AW")
|
||||
c["BN"] = _if_pos(_v(c, "E"), lambda: _v(c, "D") / _v(c, "E") * 1000.0)
|
||||
c["BP"] = _if_pos(
|
||||
_v(c, "BO"),
|
||||
lambda: (_v(c, "D") - _v(c, "AW")) * _v(c, "BO") / 100.0,
|
||||
(_v(c, "D") - _v(c, "AW")) * omd_default / 100.0,
|
||||
)
|
||||
c["BR"] = _if_pos(
|
||||
_v(c, "BQ"), lambda: _v(c, "F") * _v(c, "BQ") / 100.0, _v(c, "F") * DEFAULT_CP_DIGEST_PCT / 100.0
|
||||
)
|
||||
c["BT"] = _if_pos(
|
||||
_v(c, "BS"), lambda: _v(c, "M") * _v(c, "BS") / 100.0, _v(c, "M") * DEFAULT_FAT_DIGEST_PCT / 100.0
|
||||
)
|
||||
c["BV"] = _if_pos(
|
||||
_v(c, "BU"), lambda: _v(c, "K") * _v(c, "BU") / 100.0, _v(c, "K") * DEFAULT_FIBER_DIGEST_PCT / 100.0
|
||||
)
|
||||
c["BX"] = _if_pos(
|
||||
_v(c, "BW"), lambda: c["AX"] * _v(c, "BW") / 100.0, c["AX"] * DEFAULT_NFE_DIGEST_PCT / 100.0
|
||||
)
|
||||
c["BY"] = GE_CP * _v(c, "F") + GE_FAT * _v(c, "M") + GE_FIBER * _v(c, "K") + GE_NFE * c["AX"]
|
||||
c["BZ"] = (
|
||||
ME_FAT * c["BT"]
|
||||
+ ME_FIBER * c["BV"]
|
||||
+ ME_OR_RESIDUE * (c["BP"] - c["BT"] - c["BV"])
|
||||
+ ME_CP * _v(c, "F")
|
||||
)
|
||||
c["CA"] = _if_pos(
|
||||
c["BY"],
|
||||
lambda: (NEL_BASE * (1.0 + NEL_Q_COEFF * (c["BZ"] / c["BY"] * 100.0 - NEL_Q_REF_PCT)) * c["BZ"]),
|
||||
)
|
||||
c["CF"] = _if_pos(
|
||||
_v(c, "CE"),
|
||||
lambda: _v(c, "F") * _v(c, "CE") / 100.0,
|
||||
_v(c, "F") * DEFAULT_INSOLUBLE_PROTEIN_PCT / 100.0,
|
||||
)
|
||||
c["CG"] = _if_pos(_v(c, "D"), lambda: _v(c, "M") * 1000.0 / _v(c, "D"))
|
||||
c["CH"] = _if_pos(_v(c, "D"), lambda: c["CF"] * 1000.0 / _v(c, "D"))
|
||||
c["CI"] = _if_pos(_v(c, "D"), lambda: _v(c, "F") * 1000.0 / _v(c, "D"))
|
||||
c["CJ"] = _if_pos(_v(c, "D"), lambda: c["BP"] / _v(c, "D"))
|
||||
c["CK"] = _if_pos(_v(c, "D"), lambda: c["BT"] / _v(c, "D"))
|
||||
c["CL"] = _if_pos(c["CI"], lambda: (187.7 - 115.4 * c["CH"] / c["CI"]) * c["CJ"] + 1.03 * c["CH"])
|
||||
c["CM"] = _if_pos(c["CI"], lambda: (196.1 - 127.5 * c["CH"] / c["CI"]) * (c["CJ"] - c["CK"]) + 1.03 * c["CH"])
|
||||
co = _v(c, "CO")
|
||||
if c["CG"] < USP_FAT_THRESHOLD_G_PER_KG_DM + 0.01:
|
||||
c["CN"] = c["CL"]
|
||||
elif c["CG"] > USP_FAT_THRESHOLD_G_PER_KG_DM:
|
||||
c["CN"] = c["CM"]
|
||||
else:
|
||||
c["CN"] = 0.0
|
||||
if co < 1.01:
|
||||
c["CP"] = c["CN"] * _v(c, "D") / 1000.0
|
||||
elif co > 1.0:
|
||||
c["CP"] = co
|
||||
else:
|
||||
c["CP"] = 0.0
|
||||
c["CQ"] = (_v(c, "F") - c["CP"]) / 6.25
|
||||
c["CX"] = _if_pos(
|
||||
_v(c, "CT"),
|
||||
lambda: _v(c, "F") * _v(c, "CT") / 100.0,
|
||||
_v(c, "F") * DEFAULT_PROTEIN_FRACTION_PCT / 100.0,
|
||||
)
|
||||
nel = c["CA"]
|
||||
c["CY"] = nel * _v(c, "CW")
|
||||
f_val = _v(c, "F")
|
||||
bo = _v(c, "BO")
|
||||
if f_val == 0.0:
|
||||
c["CZ"] = 0.0
|
||||
c["DA"] = 0.0
|
||||
c["DB"] = 0.0
|
||||
c["DC"] = 0.0
|
||||
c["DD"] = 0.0
|
||||
c["DE"] = 0.0
|
||||
else:
|
||||
c["DA"] = c["CX"] * (_v(c, "AY") / 10.0) / (f_val / 10.0) * (bo / 100.0) + c["CY"] * 0.071 * 0.8
|
||||
c["CZ"] = _if_pos(
|
||||
bo,
|
||||
lambda: c["CX"] * (_v(c, "AZ") / 10.0) / (f_val / 10.0) * (bo / 100.0) + c["CY"] * 0.018 * 0.8,
|
||||
)
|
||||
c["DB"] = _if_pos(
|
||||
bo,
|
||||
lambda: c["CX"] * (_v(c, "BA") / 10.0) / (f_val / 10.0) * (bo / 100.0) + c["CY"] * 0.044 * 0.8,
|
||||
)
|
||||
c["DC"] = _if_pos(
|
||||
_v(c, "BD"),
|
||||
lambda: c["CX"] * (_v(c, "BD") / 10.0) / (f_val / 10.0) * (bo / 100.0) + c["CY"] * 0.063 * 0.8,
|
||||
)
|
||||
c["DD"] = _if_pos(
|
||||
_v(c, "BC"),
|
||||
lambda: c["CX"] * (_v(c, "BC") / 10.0) / (f_val / 10.0) * (bo / 100.0) + c["CY"] * 0.049 * 0.8,
|
||||
)
|
||||
c["DE"] = _if_pos(
|
||||
_v(c, "BE"),
|
||||
lambda: c["CX"] * (_v(c, "BE") / 10.0) / (f_val / 10.0) * (bo / 100.0) + c["CY"] * 0.048 * 0.8,
|
||||
)
|
||||
|
||||
for display, source in DISPLAY_SYNC:
|
||||
c[display] = c[source]
|
||||
|
||||
return c
|
||||
|
||||
|
||||
def derive_ingredient_nutrients(
|
||||
data: dict[str, Any] | None,
|
||||
*,
|
||||
context: DeriveContext | None = None,
|
||||
) -> dict[str, float]:
|
||||
"""Полный набор показателей: входные + пересчитанные derived."""
|
||||
cells = dict_to_cells(data)
|
||||
return cells_to_dict(derive_cells(cells, context=context))
|
||||
Reference in New Issue
Block a user