Files
dashboard-cpsp/backend/apps/operations/tests_insight_root_cause.py
T

153 lines
5.5 KiB
Python

"""Unit tests for deterministic end-cycle root-cause candidates."""
from django.test import SimpleTestCase
from apps.operations.services import root_cause as rc
class RootCauseHypothesisTests(SimpleTestCase):
def test_early_mortality_plus_cold_hours_yields_brooding_suhu(self):
graded = {
"analyses": {
"mortality": {
"status": "critical",
"severity_label": "SANGAT TINGGI",
"actual_pct": 9.5,
"message": "Mortalitas kumulatif 9.50% adalah SANGAT TINGGI",
}
}
}
context = {
"weekly_summaries": [
{
"minggu_ke": 1,
"mortalitas_delta_persen": 2.5,
"jam_suhu_di_bawah_standar": 12,
},
{"minggu_ke": 2, "mortalitas_delta_persen": 1.2, "jam_suhu_di_bawah_standar": 6},
]
}
hyps = rc.build_root_cause_hypotheses(graded, context)
self.assertTrue(hyps)
self.assertEqual(hyps[0]["id"], "brooding_suhu")
self.assertEqual(hyps[0]["confidence"], "high")
self.assertEqual(hyps[0]["fase"], "brooding")
def test_fcr_and_bw_lag_yields_pakan(self):
graded = {
"analyses": {
"fcr": {"status": "critical", "message": "FCR di atas standar"},
"bw": {
"status": "warning",
"direction": "di_bawah_standar",
"message": "Bobot di bawah standar",
},
"mortality": {"status": "ok", "message": "Mortalitas normal"},
}
}
hyps = rc.build_root_cause_hypotheses(graded, {})
ids = [h["id"] for h in hyps]
self.assertIn("pakan_akses", ids)
def test_high_mortality_alone_is_low_confidence_biosecurity(self):
graded = {
"analyses": {
"mortality": {
"status": "critical",
"actual_pct": 9.49,
"message": "Mortalitas kumulatif 9.49% adalah SANGAT TINGGI",
}
}
}
hyps = rc.build_root_cause_hypotheses(graded, {})
self.assertEqual(len(hyps), 1)
self.assertEqual(hyps[0]["id"], "penyakit_biosekuriti")
self.assertEqual(hyps[0]["confidence"], "low")
class NextCycleActionsTests(SimpleTestCase):
def test_actions_map_from_top_hypotheses(self):
hyps = [
{
"id": "brooding_suhu",
"hipotesis": "Brooding / kontrol suhu",
"confidence": "high",
"fase": "brooding",
"bukti": [],
},
{
"id": "pakan_akses",
"hipotesis": "Pakan / akses pakan",
"confidence": "med",
"fase": "growth",
"bukti": [],
},
]
actions = rc.build_next_cycle_actions(hyps)
self.assertEqual(len(actions), 2)
self.assertEqual(actions[0]["fase"], "brooding")
self.assertIn("suhu", actions[0]["aksi"].lower())
self.assertTrue(actions[0]["metrik_pantau"])
class SanitizeEndCycleTests(SimpleTestCase):
def test_drops_invented_causes(self):
hyps = [
{
"id": "penyakit_biosekuriti",
"hipotesis": "Penyakit / biosekuriti",
"confidence": "low",
"fase": "brooding",
"bukti": ["mort high"],
}
]
actions = rc.build_next_cycle_actions(hyps)
parsed = {
"kesimpulan": "Ada masalah mortalitas",
"masalah": ["Mortalitas tinggi"],
"akar_penyebab": [
{"id": "alien_cause", "hipotesis": "Serangan alien", "bukti": ["X"]},
{
"id": "penyakit_biosekuriti",
"hipotesis": "Penyakit / biosekuriti",
"bukti": ["Nekropsi diperlukan"],
},
],
"perbaikan_siklus_berikutnya": [
{"id": "alien_cause", "fase": "brooding", "aksi": "Do nothing"},
{
"id": "penyakit_biosekuriti",
"fase": "brooding",
"aksi": "Perketat biosekuriti custom",
"metrik_pantau": "mortalitas harian",
},
],
"insight": "Fokus biosekuriti",
}
out = rc.sanitize_end_cycle_payload(
parsed, hypotheses=hyps, actions=actions, masalah=["Mortalitas tinggi"]
)
ids = [a["id"] for a in out["akar_penyebab"]]
self.assertEqual(ids, ["penyakit_biosekuriti"])
self.assertNotIn("alien_cause", [p["id"] for p in out["perbaikan_siklus_berikutnya"]])
self.assertIn("biosekuriti", out["perbaikan_siklus_berikutnya"][0]["aksi"].lower())
def test_format_embeds_json_marker(self):
payload = rc.local_fallback_end_cycle(
{
"analyses": {
"mortality": {
"status": "critical",
"actual_pct": 9.0,
"message": "Mortalitas kritis",
}
}
},
{},
)
text = rc.format_end_cycle_insight_text(payload)
self.assertIn(rc.END_CYCLE_JSON_MARKER, text)
parsed = rc.parse_embedded_end_cycle(text)
self.assertIsNotNone(parsed)
self.assertEqual(parsed["kesimpulan"], payload["kesimpulan"])