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

280 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Интеграционные тесты feed_quality.evaluator."""
from __future__ import annotations
import unittest
from datetime import datetime
from sqlalchemy import select
from app import create_app, db
from app.models import (
ComponentLoadingTime,
LoadingReport,
LoadingReportComponent,
Recipe,
UnloadingReport,
UnloadingReportGroup,
)
from app.models.feed_alert import FeedAlert
from app.services.feed_quality.evaluator import evaluate_loading_report, evaluate_unloading_report
from app.services.feed_quality.rules import (
EVENT_LEFT_IN_MIXER,
EVENT_LOADING_FAST,
EVENT_LOADING_TIME,
EVENT_OVERLOAD,
EVENT_UNDERLOAD,
)
from app.services.feed_quality.settings_store import save_feed_quality_settings
from app.services.setup_state import mark_setup_complete
from tests.helpers.zootech_test_helpers import ZootechTestConfig
class FeedQualityEvaluatorTests(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)
db.session.add(
Recipe(
id="fq-r1",
name="Тестовый рейс",
heads_per_trip=10,
mixing_time=5,
content_hash="",
)
)
db.session.commit()
def tearDown(self) -> None:
db.session.remove()
db.drop_all()
self.ctx.pop()
def _loading_with_overload(self) -> str:
report = LoadingReport(
id="fq-lr-1",
recipe_id="fq-r1",
recipe_name="Тестовый рейс",
start_time=datetime.now(),
target_mixing_time=300,
actual_mixing_time=350,
total_weight=100.0,
created_by="system",
updated_by="system",
)
db.session.add(report)
db.session.flush()
db.session.add(
LoadingReportComponent(
report_id=report.id,
component_name="Силос",
target_weight=100.0,
actual_weight=115.0,
overload=15.0,
loading_order=1,
created_by="system",
updated_by="system",
)
)
db.session.commit()
return report.id
def test_evaluate_loading_creates_alerts(self) -> None:
report_id = self._loading_with_overload()
rows = evaluate_loading_report(report_id, send_notifications=False)
self.assertGreaterEqual(len(rows), 2)
types = {r.event_type for r in rows}
self.assertIn(EVENT_OVERLOAD, types)
def test_idempotent_re_evaluate(self) -> None:
report_id = self._loading_with_overload()
first = evaluate_loading_report(report_id, send_notifications=False)
evaluate_loading_report(report_id, send_notifications=False)
second_count = len(
db.session.execute(
select(FeedAlert).where(FeedAlert.loading_report_id == report_id)
)
.scalars()
.all()
)
self.assertEqual(second_count, len(first))
def test_fast_loading_merged_with_underload(self) -> None:
report = LoadingReport(
id="fq-lr-fast",
recipe_id="fq-r1",
recipe_name="Тестовый рейс",
start_time=datetime.now(),
target_mixing_time=60,
actual_mixing_time=60,
total_weight=62.0,
created_by="system",
updated_by="system",
)
db.session.add(report)
db.session.flush()
db.session.add(
LoadingReportComponent(
report_id=report.id,
component_name="Сено",
target_weight=62.0,
actual_weight=0.0,
overload=-62.0,
loading_order=1,
created_by="system",
updated_by="system",
)
)
now = datetime.now()
db.session.add(
ComponentLoadingTime(
report_id=report.id,
component_name="Сено",
start_time=now,
end_time=now,
loading_duration=0.9,
loading_order=1,
created_by="system",
updated_by="system",
)
)
db.session.commit()
rows = evaluate_loading_report(report.id, send_notifications=False)
component_rows = [r for r in rows if r.component_name == "Сено"]
self.assertEqual(len(component_rows), 1)
self.assertEqual(component_rows[0].event_type, EVENT_UNDERLOAD)
self.assertIn("0.9 с", component_rows[0].detail)
def test_fast_loading_separate_when_weight_ok(self) -> None:
report = LoadingReport(
id="fq-lr-fast-ok",
recipe_id="fq-r1",
recipe_name="Тестовый рейс",
start_time=datetime.now(),
target_mixing_time=60,
actual_mixing_time=60,
total_weight=62.0,
created_by="system",
updated_by="system",
)
db.session.add(report)
db.session.flush()
db.session.add(
LoadingReportComponent(
report_id=report.id,
component_name="Сено",
target_weight=62.0,
actual_weight=62.0,
overload=0.0,
loading_order=1,
created_by="system",
updated_by="system",
)
)
now = datetime.now()
db.session.add(
ComponentLoadingTime(
report_id=report.id,
component_name="Сено",
start_time=now,
end_time=now,
loading_duration=0.9,
loading_order=1,
created_by="system",
updated_by="system",
)
)
db.session.commit()
rows = evaluate_loading_report(report.id, send_notifications=False)
fast = [r for r in rows if r.component_name == "Сено"]
self.assertEqual(len(fast), 1)
self.assertEqual(fast[0].event_type, EVENT_LOADING_FAST)
def test_slow_loading_separate_when_weight_ok(self) -> None:
save_feed_quality_settings({"loading": {"warning_max_sec": 30, "critical_max_sec": 120}})
report = LoadingReport(
id="fq-lr-slow",
recipe_id="fq-r1",
recipe_name="Тестовый рейс",
start_time=datetime.now(),
target_mixing_time=60,
actual_mixing_time=60,
total_weight=100.0,
created_by="system",
updated_by="system",
)
db.session.add(report)
db.session.flush()
db.session.add(
LoadingReportComponent(
report_id=report.id,
component_name="Силос",
target_weight=100.0,
actual_weight=100.0,
overload=0.0,
loading_order=1,
created_by="system",
updated_by="system",
)
)
now = datetime.now()
db.session.add(
ComponentLoadingTime(
report_id=report.id,
component_name="Силос",
start_time=now,
end_time=now,
loading_duration=90.0,
loading_order=1,
created_by="system",
updated_by="system",
)
)
db.session.commit()
rows = evaluate_loading_report(report.id, send_notifications=False)
slow = [r for r in rows if r.component_name == "Силос"]
self.assertEqual(len(slow), 1)
self.assertEqual(slow[0].event_type, EVENT_LOADING_TIME)
def test_left_in_mixer_on_unloading(self) -> None:
report_id = self._loading_with_overload()
unloading = UnloadingReport(
id="fq-ur-1",
recipe_id="fq-r1",
recipe_name="Тестовый рейс",
loading_report_id=report_id,
start_time=datetime.now(),
total_weight=100.0,
total_unloaded_weight=90.0,
remaining_weight=10.0,
created_by="system",
updated_by="system",
)
db.session.add(unloading)
db.session.flush()
db.session.add(
UnloadingReportGroup(
report_id=unloading.id,
name="G1",
target_weight=100.0,
unloaded_weight=90.0,
remaining_weight=10.0,
distribution_type="percent",
distribution_value=100,
order=1,
created_by="system",
updated_by="system",
)
)
db.session.commit()
rows = evaluate_unloading_report(unloading.id, send_notifications=False)
types = {r.event_type for r in rows}
self.assertIn(EVENT_LEFT_IN_MIXER, types)
if __name__ == "__main__":
unittest.main()