@@ -0,0 +1,202 @@
|
||||
"""Подготовка параметров 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,
|
||||
)
|
||||
Reference in New Issue
Block a user