113 lines
3.4 KiB
Python
113 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from app.lab.calc.derived import apply_content_derived
|
|
from app.lab.calc.nutrients import norm_diff, weighted_average
|
|
from app.lab.indicators import RATION_ALL_INDICATORS
|
|
|
|
|
|
def _compound_active(lines: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
return [
|
|
l
|
|
for l in lines
|
|
if l.get("in_compound") 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 _compound_indicators(
|
|
lines: list[dict[str, Any]],
|
|
total_kg: float,
|
|
norms: dict[str, dict[str, float | 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"]] = weighted_average(
|
|
lines,
|
|
total_kg,
|
|
defn["nutrient_keys"],
|
|
indicator_key=defn.get("key"),
|
|
)
|
|
for defn in RATION_ALL_INDICATORS:
|
|
if not defn.get("derived"):
|
|
continue
|
|
content_by_key[defn["key"]] = apply_content_derived(
|
|
content_by_key,
|
|
defn,
|
|
compound_mode=True,
|
|
)
|
|
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 calculate_compound_feed(
|
|
lines: list[dict[str, Any]],
|
|
norms: dict[str, dict[str, float | None]] | None = None,
|
|
*,
|
|
profile_mass_kg: float | None = None,
|
|
heads_per_trip: int = 1,
|
|
) -> dict[str, Any] | None:
|
|
del profile_mass_kg, heads_per_trip
|
|
active = _compound_active(lines)
|
|
if not active:
|
|
return None
|
|
total_kg = _sum_kg(active)
|
|
cost = None
|
|
any_cost = False
|
|
for line in active:
|
|
kg = line.get("daily_kg")
|
|
price = line.get("price_per_kg")
|
|
if kg is None or price is None:
|
|
continue
|
|
cost = (cost or 0) + float(kg) * float(price)
|
|
any_cost = True
|
|
return {
|
|
"totals": [
|
|
{"key": "compound_kg", "label": "Масса комбикорма, кг", "value": total_kg},
|
|
{
|
|
"key": "compound_cost",
|
|
"label": "Стоимость комбикорма",
|
|
"value": cost if any_cost else None,
|
|
},
|
|
],
|
|
"indicators": _compound_indicators(active, total_kg, norms or {}),
|
|
"lines": [
|
|
{
|
|
"ingredient_name": line.get("ingredient_name") or "—",
|
|
"daily_kg": line.get("daily_kg"),
|
|
"share_pct": (
|
|
(float(line["daily_kg"]) / total_kg) * 100
|
|
if total_kg > 0 and line.get("daily_kg") is not None
|
|
else None
|
|
),
|
|
}
|
|
for line in active
|
|
],
|
|
}
|