"""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))