Files
site/WESP_REL/tests/test_daily_plan_builder.py
T
2026-07-17 12:57:18 +03:00

202 lines
7.8 KiB
Python

"""Unit-тесты daily_plan/builder.py."""
from __future__ import annotations
import unittest
from app import create_app, db
from app.models import Component, FeedDispenser, Ingredient, Recipe
from app.services.daily_plan.builder import (
ALL_DISPENSERS_ID,
ALL_MILLS_ID,
build_daily_plan,
)
from app.services.setup_state import mark_setup_complete
from tests.helpers.dispenser_recipe_fixtures import (
E2E_DISP_COMP,
E2E_DISP_ID,
E2E_DISP_RECIPE_1,
E2E_PERIOD_A,
seed_dispenser_period_recipes,
)
from tests.helpers.zootech_test_helpers import ZootechTestConfig
class DailyPlanBuilderTests(unittest.TestCase):
def setUp(self) -> None:
self.app = create_app(ZootechTestConfig)
self.ctx = self.app.app_context()
self.ctx.push()
db.create_all()
mark_setup_complete(self.app)
def tearDown(self) -> None:
db.session.remove()
db.drop_all()
self.ctx.pop()
def test_build_aggregates_periods_trips_and_totals(self) -> None:
seed_dispenser_period_recipes()
plan = build_daily_plan(dispenser_id=E2E_DISP_ID, plan_date="2026-06-07")
self.assertEqual(plan["date"], "2026-06-07")
self.assertEqual(plan["dispenserId"], E2E_DISP_ID)
self.assertEqual(plan["dispenserName"], "E2E Раздатчик")
self.assertEqual(plan["dispenserType"], "dispenser")
self.assertIn("skippedTrips", plan)
self.assertEqual(plan["skippedTrips"], [])
periods = plan["periods"]
self.assertEqual(len(periods), 2)
morning = next(p for p in periods if p["id"] == E2E_PERIOD_A)
self.assertEqual(morning["name"], "E2E Утро")
self.assertEqual(len(morning["trips"]), 2)
trip1 = morning["trips"][0]
self.assertEqual(trip1["recipeId"], E2E_DISP_RECIPE_1)
self.assertEqual(trip1["headsPerTrip"], 20)
self.assertEqual(trip1["mixingTimeSec"], 3)
self.assertEqual(len(trip1["ingredients"]), 1)
self.assertEqual(trip1["ingredients"][0]["name"], "Ing 1")
self.assertEqual(trip1["ingredients"][0]["totalKg"], 20.0)
self.assertEqual(len(trip1["unloadingGroups"]), 1)
self.assertEqual(trip1["unloadingGroups"][0]["name"], "Г1")
self.assertEqual(trip1["unloadingGroups"][0]["weightKg"], 20.0)
self.assertEqual(trip1["totalWeightKg"], 20.0)
self.assertEqual(trip1["unloadingTotalKg"], 20.0)
totals = {row["name"]: row["totalKg"] for row in plan["ingredientTotals"]}
self.assertEqual(totals["Ing 1"], 20.0)
self.assertEqual(totals["Ing 2"], 30.0)
def test_all_dispensers_aggregates_with_prefixed_periods(self) -> None:
seed_dispenser_period_recipes()
plan = build_daily_plan(dispenser_id=ALL_DISPENSERS_ID, plan_date="2026-06-07")
self.assertEqual(plan["dispenserName"], "Все кормораздатчики")
self.assertTrue(plan["periods"])
self.assertTrue(any("E2E Раздатчик" in p["name"] for p in plan["periods"]))
self.assertTrue(plan["ingredientTotals"])
def test_all_mills_returns_orphan_recipes(self) -> None:
plan = build_daily_plan(dispenser_id=ALL_MILLS_ID)
self.assertEqual(plan["dispenserName"], "Все кормоцеха")
self.assertEqual(plan["dispenserType"], "mill")
def test_unloading_group_has_distribution_label(self) -> None:
seed_dispenser_period_recipes()
plan = build_daily_plan(dispenser_id=E2E_DISP_ID)
group = plan["periods"][0]["trips"][0]["unloadingGroups"][0]
self.assertEqual(group["distributionType"], "percent")
self.assertIn("%", group["distributionLabel"])
def test_missing_dispenser_raises(self) -> None:
with self.assertRaises(LookupError):
build_daily_plan(dispenser_id="missing-disp")
def test_mill_fallback_uses_orphan_recipes(self) -> None:
mill_id = "mill-disp-1"
recipe_id = "mill-recipe-1"
comp_id = "mill-comp-1"
db.session.add_all(
[
FeedDispenser(
id=mill_id,
name="Мельница",
farm="Ферма",
operator="Тест",
type="mill",
content_hash="",
),
Component(
id=comp_id,
name="Компонент",
type="grain",
dry_matter=55.0,
protein=0.0,
energy=0.0,
price=0.0,
),
Recipe(
id=recipe_id,
name="Мельничный рейс",
heads_per_trip=10,
mixing_time=5,
trip_percent=100.0,
target_component_id=comp_id,
content_hash="",
),
]
)
db.session.flush()
db.session.add(
Ingredient(
id="mill-ing-1",
name="Зерно",
weight_per_head=2.5,
amount=0.0,
dry_matter=55.0,
component_id=comp_id,
order=1,
recipe_id=recipe_id,
created_by="system",
updated_by="system",
)
)
db.session.commit()
plan = build_daily_plan(dispenser_id=mill_id)
self.assertEqual(plan["dispenserType"], "mill")
self.assertEqual(len(plan["periods"]), 1)
self.assertEqual(plan["periods"][0]["name"], "Рейсы")
self.assertEqual(len(plan["periods"][0]["trips"]), 1)
self.assertEqual(plan["periods"][0]["trips"][0]["recipeName"], "Мельничный рейс")
self.assertEqual(plan["ingredientTotals"][0]["totalKg"], 25.0)
def test_unloading_weights_follow_trip_total_after_ingredient_skip(self) -> None:
from app.services.daily_plan.skips import skip_ingredient
seed_dispenser_period_recipes()
db.session.add(
Ingredient(
id="e2e-disp-ing-extra",
name="Extra",
weight_per_head=5.0,
amount=0.0,
dry_matter=55.0,
component_id=E2E_DISP_COMP,
order=2,
recipe_id=E2E_DISP_RECIPE_1,
created_by="system",
updated_by="system",
)
)
db.session.commit()
plan_before = build_daily_plan(dispenser_id=E2E_DISP_ID, plan_date="2026-06-07")
trip_before = plan_before["periods"][0]["trips"][0]
self.assertEqual(trip_before["totalWeightKg"], 120.0)
self.assertEqual(trip_before["unloadingGroups"][0]["weightKg"], 120.0)
skip_ingredient(E2E_DISP_RECIPE_1, "e2e-disp-ing-extra", "2026-06-07")
plan_after = build_daily_plan(dispenser_id=E2E_DISP_ID, plan_date="2026-06-07")
trip_after = plan_after["periods"][0]["trips"][0]
self.assertEqual(trip_after["totalWeightKg"], 20.0)
self.assertEqual(trip_after["unloadingGroups"][0]["weightKg"], 20.0)
self.assertEqual(trip_after["unloadingTotalKg"], 20.0)
def test_skipped_ingredient_includes_baseline_weights(self) -> None:
from app.services.daily_plan.skips import skip_ingredient
seed_dispenser_period_recipes()
skip_ingredient(E2E_DISP_RECIPE_1, "e2e-disp-ing-1", "2026-06-07")
plan = build_daily_plan(dispenser_id=E2E_DISP_ID, plan_date="2026-06-07")
skipped = next(
ing
for trip in plan["periods"][0]["trips"]
for ing in trip["ingredients"]
if ing.get("skippedToday")
)
self.assertEqual(skipped["totalKg"], 0.0)
self.assertEqual(skipped["baselineWeightPerHead"], 1.0)
self.assertEqual(skipped["baselineTotalKg"], 20.0)