"""Методика Москва — лактирующие коровы (NORM_1_1_CALC).""" from __future__ import annotations from dataclasses import dataclass from typing import Any from app.lab.calc.racion.interp import popr_index from app.lab.calc.norms_derived import apply_derived_norms from app.lab.calc.racion.npitv_map import DAIRY_LACTIR_NPITV, NPITV_TO_INDICATOR from app.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}