87 lines
2.9 KiB
Python
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
|