Интегрирован 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
+184
View File
@@ -0,0 +1,184 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any
from app.lab.calc.compound import calculate_compound_feed
from app.lab.calc.derived import apply_content_derived
from app.lab.calc.nutrients import daily_intake_total, get_nutrient_value, norm_diff, weighted_average
from app.lab.constants import RATION_TOTAL_KEYS
from app.lab.indicators import RATION_ALL_INDICATORS
def _active_lines(lines: list[dict[str, Any]]) -> list[dict[str, Any]]:
return [
l
for l in lines
if l.get("in_ration") and (l.get("daily_kg") or 0) > 0
]
def _sum_kg(lines: list[dict[str, Any]]) -> float:
return sum(float(l.get("daily_kg") or 0) for l in lines)
def _sum_cost(lines: list[dict[str, Any]]) -> float | None:
total = 0.0
any_cost = False
for line in lines:
kg = line.get("daily_kg")
price = line.get("price_per_kg")
if kg is None or price is None:
continue
total += float(kg) * float(price)
any_cost = True
return total if any_cost else None
def _sum_ration_percent(lines: list[dict[str, Any]], total_kg: float) -> float | None:
if total_kg <= 0:
return None
return sum((float(l.get("daily_kg") or 0) / total_kg) * 100 for l in lines)
def _compute_totals(
ration_type: str,
active: list[dict[str, Any]],
total_kg: float,
) -> list[dict[str, Any]]:
defs = RATION_TOTAL_KEYS.get(ration_type, RATION_TOTAL_KEYS["BEEF"])
ration_kg = _sum_kg(active)
values = {
"total_kg": total_kg if total_kg > 0 else None,
"ration_kg": ration_kg if ration_kg > 0 else None,
"ration_pct_sum": _sum_ration_percent(active, total_kg),
"cost_total": _sum_cost(active),
}
return [{"key": d["key"], "label": d["label"], "value": values.get(d["key"])} for d in defs]
def _indicator_content(
defn: dict[str, Any],
active: list[dict[str, Any]],
total_kg: float,
*,
heads_per_trip: int,
) -> float | None:
key = defn.get("key")
if defn.get("aggregation") == "weighted_avg":
return weighted_average(
active,
total_kg,
defn["nutrient_keys"],
indicator_key=key,
)
return daily_intake_total(
active,
defn["nutrient_keys"],
heads_per_trip=heads_per_trip,
indicator_key=key,
)
def _compute_indicators(
active: list[dict[str, Any]],
total_kg: float,
norms: dict[str, dict[str, float | None]],
*,
heads_per_trip: int = 1,
profile_mass_kg: float | None = None,
) -> list[dict[str, Any]]:
content_by_key: dict[str, float | None] = {}
for defn in RATION_ALL_INDICATORS:
if defn.get("derived"):
continue
content_by_key[defn["key"]] = _indicator_content(
defn, active, total_kg, heads_per_trip=heads_per_trip
)
for defn in RATION_ALL_INDICATORS:
if not defn.get("derived"):
continue
content_by_key[defn["key"]] = apply_content_derived(
content_by_key,
defn,
total_kg=total_kg,
heads_per_trip=heads_per_trip,
profile_mass_kg=profile_mass_kg,
)
rows = []
for defn in RATION_ALL_INDICATORS:
key = defn["key"]
content = content_by_key.get(key)
bounds = norms.get(key, {})
min_v = bounds.get("min")
max_v = bounds.get("max")
diff = norm_diff(content, min_v, max_v)
if content is None and min_v is None and max_v is None:
continue
rows.append(
{
"key": key,
"label": defn["label"],
"unit": defn["unit"],
"min": min_v,
"max": max_v,
"content": content,
"diff": diff,
}
)
return rows
def _missing_nutrient_warnings(active: list[dict[str, Any]]) -> list[str]:
warnings: list[str] = []
for line in active:
nutrients = line.get("nutrients") or {}
cp = get_nutrient_value(
line.get("dry_matter"),
nutrients,
["Сыр. Протеин"],
indicator_key="crude_protein",
)
if cp is not None:
continue
name = line.get("ingredient_name") or line.get("component_id") or "?"
warnings.append(f"nutrients_missing:{name}")
return warnings
def calculate_ration(
ration_type: str,
lines: list[dict[str, Any]],
norms: dict[str, dict[str, float | None]] | None = None,
*,
heads_per_trip: int = 1,
profile_mass_kg: float | None = None,
) -> dict[str, Any]:
norms = norms or {}
errors: list[str] = []
active = _active_lines(lines)
total_kg = _sum_kg(active)
heads = max(int(heads_per_trip or 1), 1)
if not active:
errors.append("Нет строк сырья «в рационе» с дозировкой кг/день")
warnings = _missing_nutrient_warnings(active)
compound = calculate_compound_feed(
lines, norms, profile_mass_kg=profile_mass_kg, heads_per_trip=heads
)
return {
"calculated_at": datetime.now(timezone.utc).isoformat(),
"engine": "native",
"totals": _compute_totals(ration_type, active, total_kg),
"indicators": _compute_indicators(
active,
total_kg,
norms,
heads_per_trip=heads,
profile_mass_kg=profile_mass_kg,
),
"compound": compound,
"errors": errors,
"warnings": warnings,
}