194 lines
6.8 KiB
Python
194 lines
6.8 KiB
Python
"""Методика Петербург — лактирующие коровы (NORM_1_2_CALC)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from app.modules.zootech.lab.calc.norms_derived import apply_derived_norms
|
|
from app.modules.zootech.lab.calc.racion.interp import lerp
|
|
from app.modules.zootech.lab.calc.racion.moscow import MoscowDairyParams
|
|
from app.modules.zootech.lab.calc.racion.npitv_map import DAIRY_LACTIR_NPITV, NPITV_TO_INDICATOR
|
|
from app.modules.zootech.lab.calc.racion.piter_prep import PiterPrepError, PiterPrepResult, prepare_piter_calc
|
|
from app.modules.zootech.lab.calc.racion.tables import load_piter_lactir
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PiterDairyParams(MoscowDairyParams):
|
|
konc_oe_sv: float = 10.3
|
|
|
|
|
|
def _konc_bracket(konc: float, konc_values: list[float]) -> tuple[float, float]:
|
|
sorted_k = sorted(set(konc_values))
|
|
positive = [k for k in sorted_k if k > 0]
|
|
if not positive:
|
|
return konc, konc
|
|
if konc <= positive[0]:
|
|
return positive[0], positive[min(1, len(positive) - 1)]
|
|
for i in range(len(positive) - 1):
|
|
if positive[i] <= konc <= positive[i + 1]:
|
|
return positive[i], positive[i + 1]
|
|
return positive[-2], positive[-1]
|
|
|
|
|
|
def _udoy_bracket(udoy_jir: float, udoys: list[float]) -> tuple[float, float]:
|
|
sorted_u = sorted(set(udoys))
|
|
if len(sorted_u) < 2:
|
|
return sorted_u[0], sorted_u[0]
|
|
if udoy_jir <= sorted_u[0]:
|
|
return sorted_u[0], sorted_u[1]
|
|
for i in range(len(sorted_u) - 1):
|
|
if sorted_u[i] <= udoy_jir < sorted_u[i + 1]:
|
|
return sorted_u[i], sorted_u[i + 1]
|
|
return sorted_u[-2], sorted_u[-1]
|
|
|
|
|
|
def _norm_at_mass(
|
|
entry_normy: list[float],
|
|
mass_ind: int,
|
|
wmassa: float,
|
|
m_a: float,
|
|
m_b: float,
|
|
) -> float | None:
|
|
i = mass_ind - 1
|
|
if i < 0 or i + 1 >= len(entry_normy):
|
|
return None
|
|
n_a, n_b = entry_normy[i], entry_normy[i + 1]
|
|
if n_a <= 0 or n_b <= 0:
|
|
return None
|
|
return lerp(wmassa, m_a, n_a, m_b, n_b)
|
|
|
|
|
|
def _entries_for(data: dict, npitv: int) -> list[dict]:
|
|
npv = 4 if npitv == 5 else npitv
|
|
return [e for e in data.get("entries") or [] if e["npitv"] == npv]
|
|
|
|
|
|
def _compute_with_konc(
|
|
entries: list[dict],
|
|
npitv: int,
|
|
params: PiterDairyParams,
|
|
prep: PiterPrepResult,
|
|
konc_pred: float,
|
|
konc_sled: float,
|
|
) -> float | None:
|
|
by_konc = {k: [e for e in entries if abs(e["konc"] - k) < 1e-6] for k in (konc_pred, konc_sled)}
|
|
|
|
def _at_konc(konc: float) -> float | None:
|
|
rows = by_konc.get(konc) or []
|
|
if not rows:
|
|
return None
|
|
udoys = [e["udoy"] for e in rows]
|
|
ud_pred, ud_sled = _udoy_bracket(prep.udoy_jir, udoys)
|
|
if ud_pred == ud_sled:
|
|
return None
|
|
e_pred = next((e for e in rows if e["udoy"] == ud_pred), None)
|
|
e_sled = next((e for e in rows if e["udoy"] == ud_sled), None)
|
|
if not e_pred or not e_sled:
|
|
return None
|
|
n01 = _norm_at_mass(e_pred["normy"], prep.mass_ind, prep.wmassa, prep.m_a, prep.m_b)
|
|
n02 = _norm_at_mass(e_sled["normy"], prep.mass_ind, prep.wmassa, prep.m_a, prep.m_b)
|
|
if n01 is None or n02 is None:
|
|
return None
|
|
return lerp(prep.udoy_jir, ud_pred, n01, ud_sled, n02)
|
|
|
|
norma1 = _at_konc(konc_pred)
|
|
norma2 = _at_konc(konc_sled)
|
|
if norma1 is None or norma2 is None or konc_pred == konc_sled:
|
|
return None
|
|
norma = lerp(params.konc_oe_sv, konc_pred, norma1, konc_sled, norma2)
|
|
if npitv == 5:
|
|
norma *= 0.65
|
|
return round(norma, 3)
|
|
|
|
|
|
def _compute_konc_independent(
|
|
entries: list[dict],
|
|
params: PiterDairyParams,
|
|
prep: PiterPrepResult,
|
|
) -> float | None:
|
|
rows = [e for e in entries if e["konc"] == -10]
|
|
if not rows:
|
|
return None
|
|
udoys = [e["udoy"] for e in rows]
|
|
ud_pred, ud_sled = _udoy_bracket(prep.udoy_jir, udoys)
|
|
if ud_pred == ud_sled:
|
|
return None
|
|
e_pred = next((e for e in rows if e["udoy"] == ud_pred), None)
|
|
e_sled = next((e for e in rows if e["udoy"] == ud_sled), None)
|
|
if not e_pred or not e_sled:
|
|
return None
|
|
n01 = _norm_at_mass(e_pred["normy"], prep.mass_ind, prep.wmassa, prep.m_a, prep.m_b)
|
|
n02 = _norm_at_mass(e_sled["normy"], prep.mass_ind, prep.wmassa, prep.m_a, prep.m_b)
|
|
if n01 is None or n02 is None:
|
|
return None
|
|
norma = lerp(prep.udoy_jir, ud_pred, n01, ud_sled, n02)
|
|
if params.housing_system == 2:
|
|
norma *= 1.1
|
|
return round(norma, 3)
|
|
|
|
|
|
def compute_piter_norm(npitv: int, params: PiterDairyParams, prep: PiterPrepResult | None = None) -> float | None:
|
|
data = load_piter_lactir()
|
|
entries = _entries_for(data, npitv)
|
|
if not entries:
|
|
return None
|
|
|
|
if prep is None:
|
|
prep = prepare_piter_calc(
|
|
mass_kg=params.mass_kg,
|
|
milk_yield_kg=params.milk_yield_kg,
|
|
milk_fat_pct=params.milk_fat_pct,
|
|
konc_oe_sv=params.konc_oe_sv,
|
|
body_condition=params.body_condition,
|
|
)
|
|
|
|
konc_vals = sorted({e["konc"] for e in entries if e["konc"] > 0})
|
|
if konc_vals:
|
|
min_k = min(konc_vals)
|
|
if min_k > 0:
|
|
konc_pred, konc_sled = _konc_bracket(params.konc_oe_sv, konc_vals)
|
|
return _compute_with_konc(entries, npitv, params, prep, konc_pred, konc_sled)
|
|
return _compute_konc_independent(entries, params, prep)
|
|
|
|
|
|
def resolve_piter_dairy_norms(params: PiterDairyParams) -> tuple[dict[str, dict[str, float | None]], dict[str, Any]]:
|
|
prep = prepare_piter_calc(
|
|
mass_kg=params.mass_kg,
|
|
milk_yield_kg=params.milk_yield_kg,
|
|
milk_fat_pct=params.milk_fat_pct,
|
|
konc_oe_sv=params.konc_oe_sv,
|
|
body_condition=params.body_condition,
|
|
)
|
|
resolved: dict[str, dict[str, float | None]] = {}
|
|
dynamic: dict[str, Any] = {}
|
|
for npitv in DAIRY_LACTIR_NPITV:
|
|
value = compute_piter_norm(npitv, params, prep=prep)
|
|
if value is None:
|
|
continue
|
|
key = NPITV_TO_INDICATOR.get(npitv)
|
|
if not key:
|
|
continue
|
|
resolved[key] = {"min": value, "max": None}
|
|
dynamic[key] = {"min": value, "npitv": npitv, "method": "racion_piter", "source": "racion"}
|
|
resolved, derived_dyn = apply_derived_norms(resolved, mass_kg=params.mass_kg)
|
|
for k, v in derived_dyn.items():
|
|
dynamic[k] = {**v, "method": "racion_piter", "source": "derived"}
|
|
meta = {
|
|
"method": "racion_piter",
|
|
"massKg": params.mass_kg,
|
|
"milkYieldKg": params.milk_yield_kg,
|
|
"koncOeSv": params.konc_oe_sv,
|
|
"prep": {
|
|
"massInd": prep.mass_ind,
|
|
"mA": prep.m_a,
|
|
"mB": prep.m_b,
|
|
"koncPred": prep.konc_pred,
|
|
"koncSled": prep.konc_sled,
|
|
},
|
|
}
|
|
return resolved, {"meta": meta, "dynamic": dynamic}
|
|
|
|
|
|
__all__ = ["PiterDairyParams", "PiterPrepError", "compute_piter_norm", "resolve_piter_dairy_norms"]
|