@@ -0,0 +1 @@
|
||||
"""Российские методики суточных норм (Москва / Петербург)."""
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Линейная интерполяция (аналог FRAC в методичке)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def frac(numerator: float, denominator: float) -> float:
|
||||
if denominator == 0:
|
||||
return 0.0
|
||||
return numerator / denominator
|
||||
|
||||
|
||||
def lerp(x: float, x1: float, n1: float, x2: float, n2: float) -> float:
|
||||
"""Norma = n1 + frac(n2 - n1, x2 - x1) * (x - x1)."""
|
||||
if x2 == x1:
|
||||
return n1
|
||||
return n1 + frac(n2 - n1, x2 - x1) * (x - x1)
|
||||
|
||||
|
||||
def popr_index(udoy: float, boundaries: list[float]) -> int:
|
||||
"""1-based индекс столбца POPR_K по суточному удою."""
|
||||
idx = 1
|
||||
for i, bound in enumerate(boundaries, start=1):
|
||||
if udoy >= bound:
|
||||
idx = i + 1
|
||||
return min(idx, len(boundaries) + 1)
|
||||
@@ -0,0 +1,107 @@
|
||||
"""Методика Москва — лактирующие коровы (NORM_1_1_CALC)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from app.modules.zootech.lab.calc.racion.interp import popr_index
|
||||
from app.modules.zootech.lab.calc.norms_derived import apply_derived_norms
|
||||
from app.modules.zootech.lab.calc.racion.npitv_map import DAIRY_LACTIR_NPITV, NPITV_TO_INDICATOR
|
||||
from app.modules.zootech.lab.calc.racion.tables import load_moskwa_lactir
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MoscowDairyParams:
|
||||
mass_kg: float
|
||||
milk_yield_kg: float
|
||||
milk_fat_pct: float = 4.0
|
||||
lactation_no: int = 2
|
||||
body_condition: int = 1
|
||||
housing_system: int = 1
|
||||
|
||||
|
||||
def _row_lookup(rows: list[dict], npitv: int) -> dict | None:
|
||||
for row in rows:
|
||||
if row["npitv"] == npitv and row["pom"] == 1:
|
||||
return row
|
||||
return None
|
||||
|
||||
|
||||
def _popr_value(row: dict, udoy: float, boundaries: list[float]) -> tuple[float | None, float | None]:
|
||||
popr = row.get("popr_k") or []
|
||||
if not popr:
|
||||
return None, None
|
||||
idx = popr_index(udoy, boundaries) - 1
|
||||
idx = max(0, min(idx, len(popr) - 1))
|
||||
p_k = popr[idx]
|
||||
koef = float(row.get("koef") or 0)
|
||||
return p_k, koef
|
||||
|
||||
|
||||
def compute_moscow_norm(npitv: int, params: MoscowDairyParams) -> float | None:
|
||||
data = load_moskwa_lactir()
|
||||
boundaries = data.get("udoy_boundaries") or []
|
||||
row = _row_lookup(data.get("rows") or [], npitv)
|
||||
if row is None:
|
||||
return None
|
||||
|
||||
mass = params.mass_kg
|
||||
udoy = params.milk_yield_kg
|
||||
jir = params.milk_fat_pct
|
||||
wmassa = mass * 1.02 if params.body_condition > 1 else mass
|
||||
|
||||
p_k, koef = _popr_value(row, udoy, boundaries)
|
||||
if p_k is None:
|
||||
return None
|
||||
|
||||
norma: float | None = None
|
||||
if npitv == 1:
|
||||
temp = 0.005 if udoy <= 22 else 0.0025
|
||||
norma = temp * (wmassa - 500) + p_k * udoy - ((4 - jir) * udoy) / 148
|
||||
elif npitv == 2:
|
||||
temp = 0.09 if udoy <= 22 else 0.065
|
||||
norma = temp * (wmassa - 500) + p_k * udoy - ((4 - jir) * udoy) / 15
|
||||
elif npitv == 3:
|
||||
temp = 0.017 if udoy <= 22 else 0.015
|
||||
norma = temp * (wmassa - 500) + p_k * udoy
|
||||
elif 4 <= npitv <= 24:
|
||||
norma = p_k * udoy + (wmassa - 500) * koef
|
||||
if npitv == 10:
|
||||
norma *= 0.393
|
||||
else:
|
||||
return None
|
||||
|
||||
if norma is None:
|
||||
return None
|
||||
if params.lactation_no == 1:
|
||||
norma *= 0.95
|
||||
elif params.lactation_no == 3:
|
||||
norma *= 1.05
|
||||
if params.housing_system == 2:
|
||||
norma *= 1.1
|
||||
return round(norma, 3)
|
||||
|
||||
|
||||
def resolve_moscow_dairy_norms(params: MoscowDairyParams) -> tuple[dict[str, dict[str, float | None]], dict[str, Any]]:
|
||||
resolved: dict[str, dict[str, float | None]] = {}
|
||||
dynamic: dict[str, Any] = {}
|
||||
for npitv in DAIRY_LACTIR_NPITV:
|
||||
value = compute_moscow_norm(npitv, params)
|
||||
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_moscow", "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_moscow", "source": "derived"}
|
||||
meta = {
|
||||
"method": "racion_moscow",
|
||||
"massKg": params.mass_kg,
|
||||
"milkYieldKg": params.milk_yield_kg,
|
||||
"milkFatPct": params.milk_fat_pct,
|
||||
}
|
||||
return resolved, {"meta": meta, "dynamic": dynamic}
|
||||
@@ -0,0 +1,42 @@
|
||||
"""NPitV (справочник питательных веществ) → ключи показателей WESP."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# NORMY_MOSKWA_LACTIR / NORM_1_1_CALC: NPitV 1–24 (pom=1)
|
||||
NPITV_TO_INDICATOR: dict[int, str] = {
|
||||
1: "feed_units",
|
||||
2: "oe",
|
||||
3: "dry_matter",
|
||||
4: "crude_protein",
|
||||
5: "digestible_protein",
|
||||
6: "crude_fat",
|
||||
7: "nel",
|
||||
8: "rnb",
|
||||
9: "usp",
|
||||
10: "sodium",
|
||||
11: "magnesium",
|
||||
12: "starch",
|
||||
13: "potassium",
|
||||
14: "calcium",
|
||||
15: "phosphorus",
|
||||
16: "iron",
|
||||
17: "copper",
|
||||
18: "zinc",
|
||||
19: "manganese",
|
||||
20: "cobalt",
|
||||
21: "iodine",
|
||||
22: "carotene",
|
||||
23: "vitamin_d",
|
||||
24: "vitamin_e",
|
||||
}
|
||||
|
||||
DAIRY_LACTIR_NPITV: tuple[int, ...] = tuple(range(1, 25))
|
||||
|
||||
# Обратная совместимость
|
||||
DAIRY_CORE_NPITV: tuple[int, ...] = (2, 3, 4, 5, 7, 8, 9, 10, 12, 14, 15)
|
||||
|
||||
INDICATOR_TO_NPITV: dict[str, int] = {v: k for k, v in NPITV_TO_INDICATOR.items()}
|
||||
|
||||
|
||||
def indicator_for_npitv(npitv: int) -> str | None:
|
||||
return NPITV_TO_INDICATOR.get(npitv)
|
||||
@@ -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"]
|
||||
@@ -0,0 +1,202 @@
|
||||
"""Подготовка параметров NORM_1_2_PREP (интервалы массы и концентрации)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from app.modules.zootech.lab.calc.racion.interp import lerp
|
||||
from app.modules.zootech.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,
|
||||
)
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Загрузка справочников норм RACION (БД → JSON fallback)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
_SEED_DIR = Path(__file__).resolve().parents[4] / "data" / "seed" / "racion"
|
||||
|
||||
|
||||
def _read_json(name: str) -> dict:
|
||||
path = _SEED_DIR / name
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"Справочник не найден: {path}")
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _load_from_db(loader_name: str) -> dict | None:
|
||||
try:
|
||||
from flask import has_app_context
|
||||
|
||||
if not has_app_context():
|
||||
return None
|
||||
from app.modules.zootech.lab.services.racion_reference import (
|
||||
load_moskwa_lactir_from_db,
|
||||
load_normy_info_from_db,
|
||||
load_piter_lactir_from_db,
|
||||
)
|
||||
|
||||
loaders = {
|
||||
"moskwa": load_moskwa_lactir_from_db,
|
||||
"piter": load_piter_lactir_from_db,
|
||||
"info": load_normy_info_from_db,
|
||||
}
|
||||
fn = loaders.get(loader_name)
|
||||
if fn is None:
|
||||
return None
|
||||
data = fn()
|
||||
return data if data else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_moskwa_lactir() -> dict:
|
||||
data = _load_from_db("moskwa")
|
||||
if data:
|
||||
return data
|
||||
return _read_json("moskwa_lactir.json")
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_piter_lactir() -> dict:
|
||||
data = _load_from_db("piter")
|
||||
if data:
|
||||
return data
|
||||
return _read_json("piter_lactir.json")
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_normy_info() -> dict:
|
||||
data = _load_from_db("info")
|
||||
if data:
|
||||
return data
|
||||
try:
|
||||
return _read_json("normy_info.json")
|
||||
except FileNotFoundError:
|
||||
return {"rows": [], "mass_kg_values": [400, 450, 500, 550, 600, 650, 700, 750]}
|
||||
|
||||
|
||||
def clear_tables_cache() -> None:
|
||||
load_moskwa_lactir.cache_clear()
|
||||
load_piter_lactir.cache_clear()
|
||||
load_normy_info.cache_clear()
|
||||
Reference in New Issue
Block a user