"""Подготовка параметров 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, )