"""Производные 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