Интегрирован 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,204 @@
from __future__ import annotations
import logging
from datetime import datetime
from typing import Any
from app import db
from app.lab.calc.nutrients import parse_num
from app.lab.indicators import label_to_indicator_key
from app.lab.models import (
LabRationCalcIndicator,
LabRationCalcTotal,
LabRationCompoundLine,
LabRecipeRation,
)
from app.models.base import default_uuid
_log = logging.getLogger("app.lab.calc")
def _indicator_key(row: dict[str, Any]) -> str | None:
key = row.get("key")
if key:
return str(key)
label = row.get("label")
if label:
return label_to_indicator_key(str(label))
return None
def _log_calc_errors(recipe_id: str, errors: list[str] | None) -> None:
for message in errors or []:
text = str(message or "").strip()
if text:
_log.warning("ration calc recipe=%s: %s", recipe_id, text)
def clear_calc(recipe_id: str) -> None:
for model in (
LabRationCalcTotal,
LabRationCalcIndicator,
LabRationCompoundLine,
):
model.query.filter_by(recipe_id=recipe_id).delete(synchronize_session=False)
def _save_totals(recipe_id: str, scope: str, totals: list[dict[str, Any]] | None) -> None:
for idx, row in enumerate(totals or []):
db.session.add(
LabRationCalcTotal(
id=default_uuid(),
recipe_id=recipe_id,
scope=scope,
metric_key=str(row.get("key") or f"metric_{idx}"),
label=str(row.get("label") or ""),
value=parse_num(row.get("value")),
sort_order=idx,
)
)
def _save_indicators(
recipe_id: str,
scope: str,
indicators: list[dict[str, Any]] | None,
) -> None:
for idx, row in enumerate(indicators or []):
db.session.add(
LabRationCalcIndicator(
id=default_uuid(),
recipe_id=recipe_id,
scope=scope,
indicator_key=_indicator_key(row),
label=str(row.get("label") or ""),
unit=str(row.get("unit") or ""),
min_value=parse_num(row.get("min")),
max_value=parse_num(row.get("max")),
content=parse_num(row.get("content")),
diff=parse_num(row.get("diff")),
sort_order=idx,
)
)
def _save_compound_lines(recipe_id: str, lines: list[dict[str, Any]] | None) -> None:
for idx, row in enumerate(lines or []):
db.session.add(
LabRationCompoundLine(
id=default_uuid(),
recipe_id=recipe_id,
row_index=idx,
ingredient_name=row.get("ingredient_name"),
daily_kg=parse_num(row.get("daily_kg")),
share_pct=parse_num(row.get("share_pct")),
)
)
def save_calc_result(recipe_id: str, result: dict[str, Any], header: LabRecipeRation) -> None:
clear_calc(recipe_id)
_log_calc_errors(recipe_id, result.get("errors"))
header.calc_engine = str(result.get("engine") or "native")
calculated_at = result.get("calculated_at")
if calculated_at:
try:
header.calculated_at = datetime.fromisoformat(str(calculated_at).replace("Z", "+00:00"))
except ValueError:
header.calculated_at = datetime.utcnow()
else:
header.calculated_at = datetime.utcnow()
_save_totals(recipe_id, "ration", result.get("totals"))
_save_indicators(recipe_id, "ration", result.get("indicators"))
compound = result.get("compound")
if compound:
_save_totals(recipe_id, "compound", compound.get("totals"))
_save_indicators(recipe_id, "compound", compound.get("indicators"))
_save_compound_lines(recipe_id, compound.get("lines"))
def _load_totals(recipe_id: str, scope: str) -> list[dict[str, Any]]:
rows = (
LabRationCalcTotal.query.filter_by(recipe_id=recipe_id, scope=scope)
.order_by(LabRationCalcTotal.sort_order)
.all()
)
return [
{"key": row.metric_key, "label": row.label, "value": row.value}
for row in rows
]
def _load_indicators(recipe_id: str, scope: str) -> list[dict[str, Any]]:
rows = (
LabRationCalcIndicator.query.filter_by(recipe_id=recipe_id, scope=scope)
.order_by(LabRationCalcIndicator.sort_order)
.all()
)
return [
{
"key": row.indicator_key,
"label": row.label,
"unit": row.unit,
"min": row.min_value,
"max": row.max_value,
"content": row.content,
"diff": row.diff,
}
for row in rows
]
def load_compound_results(recipe_id: str) -> dict[str, Any]:
totals = _load_totals(recipe_id, "compound")
indicators = _load_indicators(recipe_id, "compound")
lines = (
LabRationCompoundLine.query.filter_by(recipe_id=recipe_id)
.order_by(LabRationCompoundLine.row_index)
.all()
)
if not totals and not indicators and not lines:
return {}
return {
"totals": totals,
"indicators": indicators,
"lines": [
{
"ingredient_name": row.ingredient_name,
"daily_kg": row.daily_kg,
"share_pct": row.share_pct,
}
for row in lines
],
}
def load_ration_results(recipe_id: str, header: LabRecipeRation | None) -> dict[str, Any]:
if header is None or header.calculated_at is None:
return {}
totals = _load_totals(recipe_id, "ration")
indicators = _load_indicators(recipe_id, "ration")
if not totals and not indicators:
return {}
compound = load_compound_results(recipe_id)
payload: dict[str, Any] = {
"calculated_at": header.calculated_at.isoformat(),
"engine": header.calc_engine or "native",
"totals": totals,
"indicators": indicators,
}
if compound:
payload["compound"] = compound
return payload
def load_params(recipe_id: str, header: LabRecipeRation | None) -> dict[str, Any]:
if header is None or not header.seed_source:
return {}
if header.seed_source == "execution":
return {"seeded_from": "execution"}
if header.seed_source == "synced_from":
return {"synced_from": "execution"}
return {"source": header.seed_source}