Files
site/apps/api/app/modules/zootech/lab/calc/nutrients.py
T
2026-07-17 12:57:18 +03:00

136 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from typing import Any, Mapping
from app.modules.zootech.lab.nutrient_schema import resolve_sv_g_per_kg
def normalize_key(value: str) -> str:
return " ".join((value or "").split()).strip().lower()
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 get_nutrient_value(
dry_matter: float | None,
nutrients: Mapping[str, Any] | None,
nutrient_keys: list[str],
*,
indicator_key: str | None = None,
) -> float | None:
"""Только точное совпадение ключа (заголовок или indicator_key slug)."""
nutrients = nutrients or {}
if indicator_key:
for k, v in nutrients.items():
if normalize_key(str(k)) == normalize_key(indicator_key):
n = parse_num(v)
if n is not None:
return n
for search in nutrient_keys:
target = normalize_key(search)
for k, v in nutrients.items():
if normalize_key(str(k)) != target:
continue
n = parse_num(v)
if n is not None:
return n
if any(normalize_key(k) == "св" for k in nutrient_keys):
return resolve_sv_g_per_kg(nutrients, dry_matter)
return None
def weighted_average(
lines: list[dict[str, Any]],
total_kg: float,
nutrient_keys: list[str],
*,
indicator_key: str | None = None,
) -> float | None:
if total_kg <= 0:
return None
total = 0.0
weight = 0.0
for line in lines:
kg = float(line.get("daily_kg") or 0)
if kg <= 0:
continue
v = get_nutrient_value(
line.get("dry_matter"),
line.get("nutrients"),
nutrient_keys,
indicator_key=indicator_key,
)
if v is None:
continue
total += kg * v
weight += kg
if weight <= 0:
return None
return total / weight
def daily_intake_total(
lines: list[dict[str, Any]],
nutrient_keys: list[str],
*,
heads_per_trip: int = 1,
indicator_key: str | None = None,
) -> float | None:
"""Суточная доза на голову (г или МДж): Σ (кг/день/гол × г/кг). Как Excel Рацион КРС."""
heads = max(int(heads_per_trip or 1), 1)
total = 0.0
any_value = False
for line in lines:
herd_kg = float(line.get("daily_kg") or 0)
if herd_kg <= 0:
continue
kg = herd_kg / heads
v = get_nutrient_value(
line.get("dry_matter"),
line.get("nutrients"),
nutrient_keys,
indicator_key=indicator_key,
)
if v is None:
continue
total += kg * v
any_value = True
return total if any_value else None
def rnb(
crude_protein: float | None,
usp: float | None,
) -> float | None:
"""RNB (ruminal nitrogen balance, GfE): (сырой протеин уСП) / 6,25."""
if crude_protein is None or usp is None:
return None
return (crude_protein - usp) / 6.25
def bra_rnb(crude_protein: float | None, usp: float | None) -> float | None:
"""Deprecated alias for :func:`rnb`."""
return rnb(crude_protein, usp)
def norm_diff(
content: float | None,
min_val: float | None,
max_val: float | None,
) -> float | None:
if content is None:
return None
if min_val is not None and content < min_val:
return content - min_val
if max_val is not None and content > max_val:
return content - max_val
return 0.0