@@ -0,0 +1,193 @@
|
||||
"""Методика Петербург — лактирующие коровы (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"]
|
||||
Reference in New Issue
Block a user