203 lines
7.9 KiB
Python
203 lines
7.9 KiB
Python
"""Подготовка параметров NORM_1_2_PREP (интервалы массы и концентрации)."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
|
||
from app.lab.calc.racion.interp import lerp
|
||
from app.lab.calc.racion.tables import load_normy_info
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class PiterPrepResult:
|
||
mass_ind: int
|
||
konc_pred: float
|
||
konc_sled: float
|
||
m_a: float
|
||
m_b: float
|
||
udoy_jir: float
|
||
wmassa: float
|
||
|
||
|
||
class PiterPrepError(ValueError):
|
||
def __init__(self, code: int, message: str, *, info: str | None = None) -> None:
|
||
super().__init__(message)
|
||
self.code = code
|
||
self.info = info
|
||
|
||
|
||
def _info_values(nperem: int) -> list[float]:
|
||
data = load_normy_info()
|
||
for row in data.get("rows") or []:
|
||
if row.get("nperem") == nperem:
|
||
return list(row.get("znachenie") or [])
|
||
return []
|
||
|
||
|
||
def _fl_from_str(values: list[float], index: int) -> float | None:
|
||
"""1-based index как FlFromStr в RACION."""
|
||
i = index - 1
|
||
if i < 0 or i >= len(values):
|
||
return None
|
||
return float(values[i])
|
||
|
||
|
||
def _wmassa(mass_kg: float, body_condition: int) -> float:
|
||
if body_condition > 1:
|
||
return mass_kg * 1.02
|
||
return mass_kg
|
||
|
||
|
||
def prepare_piter_calc(
|
||
*,
|
||
mass_kg: float,
|
||
milk_yield_kg: float,
|
||
milk_fat_pct: float,
|
||
konc_oe_sv: float,
|
||
body_condition: int = 1,
|
||
) -> PiterPrepResult:
|
||
"""Порт NORM_1_2_PREP: интервалы для NORM_1_2_CALC."""
|
||
if milk_yield_kg <= 0:
|
||
raise PiterPrepError(-11, "Укажите суточный удой, кг")
|
||
if milk_fat_pct <= 0:
|
||
raise PiterPrepError(-12, "Укажите жирность молока, %")
|
||
if mass_kg <= 0:
|
||
raise PiterPrepError(-14, "Укажите живую массу, кг")
|
||
if konc_oe_sv <= 0:
|
||
raise PiterPrepError(-15, "Укажите концентрацию ОЭ/СВ, МДж/кг СВ")
|
||
|
||
wmassa = _wmassa(mass_kg, body_condition)
|
||
udoy_jir = milk_yield_kg * milk_fat_pct * 0.25
|
||
|
||
kol_konc_vals = _info_values(7)
|
||
kol_mass_vals = _info_values(7)
|
||
if len(kol_konc_vals) < 2 or len(kol_mass_vals) < 2:
|
||
raise PiterPrepError(-1, "Справочник NORMY_INFO (NPerem=7) не задан")
|
||
kol_konc = int(kol_konc_vals[0])
|
||
kol_mass = int(kol_mass_vals[1])
|
||
if kol_konc < 2 or kol_mass < 2:
|
||
raise PiterPrepError(-1, "Некорректные размеры таблицы концентраций/масс")
|
||
|
||
masses = _info_values(6) or _info_values(14)
|
||
koncss = _info_values(3)
|
||
if not masses or not koncss or len(masses) < 2 or len(koncss) < 2:
|
||
raise PiterPrepError(-1, "Справочник NORMY_INFO: массы или концентрации не заданы")
|
||
|
||
# Интервал концентрации
|
||
konc_ind = 1
|
||
for i in range(2, kol_konc):
|
||
v = _fl_from_str(koncss, i)
|
||
if v is not None and konc_oe_sv >= v:
|
||
konc_ind = i
|
||
konc_pred = _fl_from_str(koncss, konc_ind)
|
||
konc_sled = _fl_from_str(koncss, konc_ind + 1)
|
||
if konc_pred is None or konc_sled is None or konc_pred < 0 or konc_sled < 0 or konc_pred == konc_sled:
|
||
raise PiterPrepError(-1, "Не удалось определить интервал концентрации")
|
||
|
||
# Интервал массы
|
||
mass_ind = 1
|
||
for i in range(2, kol_mass):
|
||
v = _fl_from_str(masses, i)
|
||
if v is not None and wmassa >= v:
|
||
mass_ind = i
|
||
m_a = _fl_from_str(masses, mass_ind)
|
||
m_b = _fl_from_str(masses, mass_ind + 1)
|
||
if m_a is None or m_b is None or m_a < 0 or m_b < 0 or m_a == m_b:
|
||
raise PiterPrepError(-1, "Не удалось определить интервал массы")
|
||
|
||
# Проверка удоя (NPerem=4,5)
|
||
udoy_str_4 = _info_values(4)
|
||
udoy_str_5 = _info_values(5)
|
||
udoy_min = _fl_from_str(udoy_str_4, 1) if udoy_str_4 else None
|
||
udoy_max = _fl_from_str(udoy_str_5, kol_konc) if udoy_str_5 else None
|
||
|
||
if udoy_min is not None and udoy_max is not None:
|
||
if udoy_jir < udoy_min or udoy_jir > udoy_max:
|
||
jir_str = _info_values(2)
|
||
gr_udoy1 = max(udoy_min * 4 / milk_fat_pct, udoy_min)
|
||
gr_udoy2 = min(udoy_max * 4 / milk_fat_pct, udoy_max)
|
||
gr_jir1 = udoy_min * 4 / milk_yield_kg
|
||
gr_jir2 = udoy_max * 4 / milk_yield_kg
|
||
if jir_str:
|
||
if len(jir_str) >= 1:
|
||
gr_jir1 = max(gr_jir1, jir_str[0])
|
||
if len(jir_str) >= 2:
|
||
gr_jir2 = min(gr_jir2, jir_str[1])
|
||
code = -4 if udoy_jir < udoy_min else -5
|
||
info = f"{gr_udoy1:.4f};{gr_udoy2:.4f};{gr_jir1:.3f};{gr_jir2:.3f};"
|
||
raise PiterPrepError(code, "Удой вне допустимого диапазона для жирности", info=info)
|
||
|
||
# Допустимый диапазон концентрации для udoy_jir
|
||
i = 1
|
||
if udoy_jir >= (_fl_from_str(udoy_str_4, 1) or 0):
|
||
while i < kol_konc - 1:
|
||
nxt = _fl_from_str(udoy_str_4, i + 1)
|
||
if nxt is None or nxt > udoy_jir:
|
||
break
|
||
i += 1
|
||
else:
|
||
while i < kol_konc - 1:
|
||
nxt = _fl_from_str(udoy_str_4, i + 1)
|
||
cur = _fl_from_str(udoy_str_4, i)
|
||
if nxt is None or cur is None or nxt != cur:
|
||
break
|
||
i += 1
|
||
ud_a = _fl_from_str(udoy_str_4, i)
|
||
ud_b = _fl_from_str(udoy_str_4, i + 1)
|
||
konc_a = _fl_from_str(koncss, i)
|
||
konc_b = _fl_from_str(koncss, i + 1)
|
||
if ud_a is not None and ud_b is not None and konc_a is not None and konc_b is not None:
|
||
if ud_a == ud_b:
|
||
konc_max = konc_b
|
||
else:
|
||
konc_max = lerp(udoy_jir, ud_a, konc_a, ud_b, konc_b)
|
||
|
||
i = kol_konc
|
||
if udoy_jir <= (_fl_from_str(udoy_str_5, kol_konc) or udoy_jir):
|
||
while i > 2:
|
||
prev = _fl_from_str(udoy_str_5, i - 1)
|
||
if prev is None or prev < udoy_jir:
|
||
break
|
||
i -= 1
|
||
else:
|
||
while i > 2:
|
||
prev = _fl_from_str(udoy_str_5, i - 1)
|
||
last = _fl_from_str(udoy_str_5, kol_konc)
|
||
if prev is None or last is None or prev != last:
|
||
break
|
||
i -= 1
|
||
ud_a2 = _fl_from_str(udoy_str_5, i - 1)
|
||
ud_b2 = _fl_from_str(udoy_str_5, i)
|
||
konc_a2 = _fl_from_str(koncss, i - 1)
|
||
konc_b2 = _fl_from_str(koncss, i)
|
||
if ud_a2 is not None and ud_b2 is not None and konc_a2 is not None and konc_b2 is not None:
|
||
if ud_a2 == ud_b2:
|
||
konc_min = konc_b2
|
||
else:
|
||
konc_min = lerp(udoy_jir, ud_a2, konc_a2, ud_b2, konc_b2)
|
||
|
||
konc_lo = _fl_from_str(koncss, 1) or konc_min
|
||
konc_hi = _fl_from_str(koncss, kol_konc) or konc_max
|
||
if konc_min < konc_lo:
|
||
konc_min = konc_lo
|
||
if konc_max > konc_hi:
|
||
konc_max = konc_hi
|
||
if round(konc_oe_sv, 1) < round(konc_min, 1) or round(konc_oe_sv, 1) > round(konc_max, 1):
|
||
code = -2 if round(konc_oe_sv, 1) < round(konc_min, 1) else -3
|
||
info = f"{round(konc_min, 1)};{round(konc_max, 1)};"
|
||
raise PiterPrepError(
|
||
code,
|
||
f"Концентрация ОЭ/СВ вне допустимого диапазона ({round(konc_min, 1)}–{round(konc_max, 1)})",
|
||
info=info,
|
||
)
|
||
|
||
return PiterPrepResult(
|
||
mass_ind=mass_ind,
|
||
konc_pred=float(konc_pred),
|
||
konc_sled=float(konc_sled),
|
||
m_a=float(m_a),
|
||
m_b=float(m_b),
|
||
udoy_jir=udoy_jir,
|
||
wmassa=wmassa,
|
||
)
|