Интегрирован wesp в сайт
CI / quality (push) Canceled after 0s

This commit is contained in:
влад
2026-07-17 12:57:18 +03:00
parent 5dfa06ddbe
commit 355c0ef9f1
883 changed files with 194576 additions and 177 deletions
@@ -0,0 +1,163 @@
"""Канонические поля zootech-показателей компонента (parity tab INGREDIENT_NUTRIENT_EDIT_ROWS)."""
from __future__ import annotations
from typing import Any
# column_name -> ключи в API/calc (первый — канон для UI)
NUTRIENT_FIELD_SPECS: tuple[tuple[str, tuple[str, ...]], ...] = (
("crude_protein", ("Сыр. Протеин",)),
("usp", ("уСП",)),
("rnb", ("RNB", "БРА", " БРА ")),
("nel_cattle", ("ЧЭЛ- КРС", " ЧЭЛ- КРС", "ЧЭЛ - КРС Форм")),
("oe_cattle", ("ОЭ-КРС", " ОЭ-КРС", "OЭ КРС форм")),
("ndf", ("Сырая клетчатка", "Сырая клетч")),
("structural_fiber", ("Структур. клетчатка", "Структур клетч")),
("crude_fat", ("Сырой жир",)),
)
def _normalize_key(value: str) -> str:
return " ".join((value or "").split()).strip().lower()
def _parse_num(value: Any) -> float | None:
if value is None or value == "":
return None
try:
n = float(value)
except (TypeError, ValueError):
return None
return None if n != n else n
def dry_matter_g_per_kg(dry_matter_pct: float | None) -> float | None:
"""СВ г/кг из канонического component.dry_matter (%): 88.7% → 887 г/кг."""
dm = _parse_num(dry_matter_pct)
if dm is None:
return None
if 0 < dm <= 100:
return dm * 10.0
if dm > 100:
return dm
return None
def resolve_sv_g_per_kg(
nutrients: dict[str, Any] | None,
dry_matter_pct: float | None,
) -> float | None:
"""Deprecated alias: СВ только из component.dry_matter (%). nutrients игнорируется."""
return dry_matter_g_per_kg(dry_matter_pct)
def read_from_mapping(data: dict[str, Any] | None, keys: tuple[str, ...]) -> float | None:
if not data:
return None
for search in keys:
target = _normalize_key(search)
for k, v in data.items():
if _normalize_key(str(k)) != target:
continue
n = _parse_num(v)
if n is not None:
return n
return None
def api_dict_to_column_values(data: dict[str, Any] | None) -> dict[str, float | None]:
payload = data or {}
out: dict[str, float | None] = {}
for col, keys in NUTRIENT_FIELD_SPECS:
out[col] = read_from_mapping(payload, keys)
return out
def derived_to_column_values(data: dict[str, Any] | None) -> dict[str, float | None]:
return api_dict_to_column_values(data)
def column_values_to_mapping(values: dict[str, float | None]) -> dict[str, float]:
out: dict[str, float] = {}
for col, keys in NUTRIENT_FIELD_SPECS:
val = values.get(col)
n = _parse_num(val)
if n is not None:
out[keys[0]] = n
return out
def mapping_to_calc_dict(data: dict[str, Any] | None) -> dict[str, float]:
if not data:
return {}
from app.modules.zootech.lab.indicators import RATION_ALL_INDICATORS
result: dict[str, float] = {}
for _col, keys in NUTRIENT_FIELD_SPECS:
val = read_from_mapping(data, keys)
if val is None:
continue
for key in keys:
result[key] = val
seen: set[str] = set()
for defn in RATION_ALL_INDICATORS:
for key in defn.get("nutrient_keys") or []:
if key in seen:
continue
seen.add(key)
val = read_from_mapping(data, (key,))
if val is not None:
result[key] = val
return result
def row_to_calc_dict(row: Any | None) -> dict[str, float]:
if row is None:
return {}
result: dict[str, float] = {}
for col, keys in NUTRIENT_FIELD_SPECS:
val = getattr(row, col, None)
if val is None:
continue
n = _parse_num(val)
if n is None:
continue
for key in keys:
result[key] = n
return result
def row_to_api_dict(row: Any | None) -> dict[str, float]:
"""API nutrients — канонические ключи tab."""
if row is None:
return {}
out: dict[str, float] = {}
for col, keys in NUTRIENT_FIELD_SPECS:
val = getattr(row, col, None)
n = _parse_num(val)
if n is not None:
out[keys[0]] = n
return out
def row_is_empty(row: Any | None) -> bool:
if row is None:
return True
for col, _keys in NUTRIENT_FIELD_SPECS:
if _parse_num(getattr(row, col, None)) is not None:
return False
return True
def legacy_json_to_column_values(raw: str | None) -> dict[str, float | None]:
if not raw or not str(raw).strip() or str(raw).strip() == "{}":
return {col: None for col, _ in NUTRIENT_FIELD_SPECS}
import json
try:
data = json.loads(raw)
except (json.JSONDecodeError, TypeError):
data = {}
if not isinstance(data, dict):
data = {}
return api_dict_to_column_values(data)