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

200 lines
7.4 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.
"""Unit-тесты порогов feed_quality.rules."""
from __future__ import annotations
import unittest
from app import create_app, db
from app.services.feed_quality.rules import (
EVENT_LEFT_IN_MIXER,
EVENT_LOADING_FAST,
EVENT_LOADING_TIME,
EVENT_MIX_TIME,
EVENT_OVERLOAD,
EVENT_UNDERLOAD,
SEVERITY_ERROR,
SEVERITY_WARNING,
TIME_ISSUE_FAST,
classify_component_loading_time,
classify_left_in_mixer,
classify_loading_component,
classify_mix_time,
classify_unloading_group,
merge_component_loading_alerts,
)
from app.services.feed_quality.settings_store import save_feed_quality_settings
from tests.helpers.zootech_test_helpers import ZootechTestConfig
class FeedQualityRulesTests(unittest.TestCase):
def setUp(self) -> None:
self.app = create_app(ZootechTestConfig)
self.ctx = self.app.app_context()
self.ctx.push()
db.create_all()
def tearDown(self) -> None:
db.session.remove()
db.drop_all()
self.ctx.pop()
def test_loading_overload_at_threshold(self) -> None:
alerts = classify_loading_component(
component_name="Силос",
target_kg=100.0,
actual_kg=111.0,
price_rub_per_kg=10.0,
)
self.assertEqual(len(alerts), 1)
self.assertEqual(alerts[0].event_type, EVENT_OVERLOAD)
self.assertAlmostEqual(alerts[0].deviation_pct or 0, 11.0, places=1)
self.assertEqual(alerts[0].cost_deviation_rub, 110.0)
def test_loading_under_threshold_no_alert(self) -> None:
alerts = classify_loading_component(
component_name="Силос",
target_kg=100.0,
actual_kg=105.0,
)
self.assertEqual(alerts, [])
def test_loading_underload(self) -> None:
alerts = classify_loading_component(
component_name="Силос",
target_kg=100.0,
actual_kg=85.0,
)
self.assertEqual(len(alerts), 1)
self.assertEqual(alerts[0].event_type, EVENT_UNDERLOAD)
def test_overload_error_severity(self) -> None:
alerts = classify_loading_component(
component_name="X",
target_kg=100.0,
actual_kg=120.0,
)
self.assertEqual(alerts[0].severity, SEVERITY_ERROR)
def test_loading_disabled(self) -> None:
save_feed_quality_settings({"loading": {"enabled": False}})
alerts = classify_loading_component(
component_name="X",
target_kg=100.0,
actual_kg=50.0,
)
self.assertEqual(alerts, [])
def test_custom_loading_thresholds(self) -> None:
save_feed_quality_settings({"loading": {"warning_pct": 20, "critical_pct": 25}})
alerts = classify_loading_component(
component_name="X",
target_kg=100.0,
actual_kg=115.0,
)
self.assertEqual(alerts, [])
alerts2 = classify_loading_component(
component_name="X",
target_kg=100.0,
actual_kg=122.0,
)
self.assertEqual(len(alerts2), 1)
self.assertEqual(alerts2[0].severity, SEVERITY_WARNING)
def test_mix_time_alert(self) -> None:
alerts = classify_mix_time(target_sec=300, actual_sec=340)
self.assertEqual(len(alerts), 1)
self.assertEqual(alerts[0].event_type, EVENT_MIX_TIME)
self.assertEqual(alerts[0].severity, SEVERITY_WARNING)
def test_mix_time_critical(self) -> None:
alerts = classify_mix_time(target_sec=300, actual_sec=400)
self.assertEqual(alerts[0].severity, SEVERITY_ERROR)
def test_mix_time_within_tolerance(self) -> None:
self.assertEqual(classify_mix_time(target_sec=300, actual_sec=320), [])
def test_mix_time_disabled(self) -> None:
save_feed_quality_settings({"mix_time": {"enabled": False}})
self.assertEqual(classify_mix_time(target_sec=300, actual_sec=400), [])
def test_unloading_group_overload(self) -> None:
alerts = classify_unloading_group(
group_name="Группа 1",
target_kg=100.0,
unloaded_kg=108.0,
)
self.assertEqual(len(alerts), 1)
self.assertEqual(alerts[0].event_type, EVENT_OVERLOAD)
def test_left_in_mixer_warning(self) -> None:
alerts = classify_left_in_mixer(remaining_kg=8.0, total_kg=200.0)
self.assertEqual(len(alerts), 1)
self.assertEqual(alerts[0].event_type, EVENT_LEFT_IN_MIXER)
self.assertEqual(alerts[0].severity, SEVERITY_WARNING)
def test_left_in_mixer_critical(self) -> None:
alerts = classify_left_in_mixer(remaining_kg=16.0, total_kg=200.0)
self.assertEqual(alerts[0].severity, SEVERITY_ERROR)
def test_left_in_mixer_below_threshold(self) -> None:
self.assertEqual(classify_left_in_mixer(remaining_kg=2.0, total_kg=500.0), [])
def test_loading_time_too_fast_merged_not_standalone(self) -> None:
time_issue = classify_component_loading_time(component_name="Сено", duration_sec=0.9)
self.assertIsNotNone(time_issue)
self.assertEqual(time_issue.kind, TIME_ISSUE_FAST)
weight = classify_loading_component(
component_name="Сено", target_kg=62.0, actual_kg=0.0
)[0]
merged = merge_component_loading_alerts(
component_name="Сено",
weight_alert=weight,
time_issue=time_issue,
)
self.assertEqual(len(merged), 1)
self.assertEqual(merged[0].event_type, EVENT_UNDERLOAD)
self.assertIn("загрузка 0.9 с", merged[0].detail)
def test_loading_time_fast_standalone_when_weight_ok(self) -> None:
time_issue = classify_component_loading_time(component_name="Сено", duration_sec=0.9)
alerts = merge_component_loading_alerts(
component_name="Сено",
weight_alert=None,
time_issue=time_issue,
)
self.assertEqual(len(alerts), 1)
self.assertEqual(alerts[0].event_type, EVENT_LOADING_FAST)
self.assertIn("0.9 с", alerts[0].detail)
def test_loading_time_slow_standalone_when_weight_ok(self) -> None:
save_feed_quality_settings({"loading": {"warning_max_sec": 60, "critical_max_sec": 120}})
time_issue = classify_component_loading_time(component_name="Силос", duration_sec=90.0)
self.assertIsNotNone(time_issue)
alerts = merge_component_loading_alerts(
component_name="Силос",
weight_alert=None,
time_issue=time_issue,
)
self.assertEqual(len(alerts), 1)
self.assertEqual(alerts[0].event_type, EVENT_LOADING_TIME)
self.assertEqual(alerts[0].severity, SEVERITY_WARNING)
def test_loading_time_slow_merged_when_weight_bad(self) -> None:
save_feed_quality_settings({"loading": {"warning_max_sec": 60, "critical_max_sec": 120}})
time_issue = classify_component_loading_time(component_name="X", duration_sec=200.0)
weight = classify_loading_component(
component_name="X", target_kg=100.0, actual_kg=111.0
)[0]
merged = merge_component_loading_alerts(
component_name="X",
weight_alert=weight,
time_issue=time_issue,
)
self.assertEqual(len(merged), 1)
self.assertEqual(merged[0].event_type, EVENT_OVERLOAD)
self.assertEqual(merged[0].severity, SEVERITY_ERROR)
if __name__ == "__main__":
unittest.main()