Files
2026-07-17 12:57:18 +03:00

87 lines
2.9 KiB
Python

"""Производные min/max норм из базовых показателей RACION."""
from __future__ import annotations
from typing import Any
from app.lab.indicators import RATION_ALL_INDICATORS, indicator_by_key
def _norm_min(bounds: dict[str, float | None] | None) -> float | None:
if not bounds:
return None
v = bounds.get("min")
return float(v) if v is not None else None
def _apply_derived_min(
norms: dict[str, dict[str, float | None]],
defn: dict[str, Any],
*,
mass_kg: float | None = None,
) -> float | None:
derived = defn.get("derived")
key = defn["key"]
if derived == "alias":
src = norms.get(defn.get("alias_of") or "")
if src and src.get("min") is not None:
return src["min"]
return None
if derived == "pct_of_dm":
src = _norm_min(norms.get(defn.get("from_key") or ""))
dm = _norm_min(norms.get("dry_matter"))
if src is None or not dm or dm <= 0:
return None
return src / dm * 100.0
if derived == "g_per_kg_dm":
src = _norm_min(norms.get(defn.get("from_key") or ""))
dm = _norm_min(norms.get("dry_matter"))
if src is None or not dm or dm <= 0:
return None
return src / (dm / 1000.0)
if derived == "nel_per_kg_dm":
dm = _norm_min(norms.get("dry_matter"))
nel = _norm_min(norms.get("nel"))
if not dm or dm <= 0 or nel is None:
return None
return nel / (dm / 1000.0)
if derived == "ratio":
num = _norm_min(norms.get(defn.get("ratio_num") or ""))
den = _norm_min(norms.get(defn.get("ratio_den") or ""))
if num is None or den is None or den == 0:
return None
return num / den
if derived == "dm_pct_bw":
dm = _norm_min(norms.get("dry_matter"))
if dm is None or not mass_kg or mass_kg <= 0:
return None
return (dm / 1000.0 / mass_kg) * 100.0
if derived == "ration_pct_bw":
return None
if derived in ("rnb", "bra_rnb"):
return _norm_min(norms.get(key))
return None
def apply_derived_norms(
norms: dict[str, dict[str, float | None]],
*,
mass_kg: float | None = None,
) -> tuple[dict[str, dict[str, float | None]], dict[str, Any]]:
"""Дополняет norms производными min; max не трогает."""
out = {k: dict(v) for k, v in norms.items()}
dynamic: dict[str, Any] = {}
for defn in RATION_ALL_INDICATORS:
if not defn.get("derived"):
continue
key = defn["key"]
if _norm_min(out.get(key)) is not None:
continue
val = _apply_derived_min(out, defn, mass_kg=mass_kg)
if val is None:
continue
rounded = round(val, 3)
out[key] = {"min": rounded, "max": out.get(key, {}).get("max")}
dynamic[key] = {"min": rounded, "derived": defn.get("derived")}
return out, dynamic