136 lines
5.3 KiB
Python
136 lines
5.3 KiB
Python
"""Regression: dashboard field names must grade; prompt must not invent ids/names."""
|
|
|
|
from django.test import SimpleTestCase
|
|
|
|
from apps.operations.services import cp707_knowledge as cp707
|
|
from apps.operations.services.insight_service import (
|
|
ANTI_HALLUCINATION_RULES,
|
|
build_insight_user_prompt,
|
|
enforce_mortality_wording,
|
|
grade_context,
|
|
)
|
|
|
|
|
|
class ResolveDashboardFieldsTests(SimpleTestCase):
|
|
def test_resolve_bw_from_bobot_avg_gram(self):
|
|
self.assertEqual(cp707._resolve_bw_grams({"bobot_avg_gram": 2100}), 2100.0)
|
|
|
|
def test_resolve_bw_from_average_weight(self):
|
|
self.assertEqual(cp707._resolve_bw_grams({"averageWeight": 1950}), 1950.0)
|
|
|
|
def test_resolve_mortality_from_mortalitas_kumulatif_persen(self):
|
|
self.assertEqual(
|
|
cp707._resolve_mortality({"mortalitas_kumulatif_persen": 2.4}),
|
|
2.4,
|
|
)
|
|
|
|
def test_resolve_mortality_from_persen_hidup(self):
|
|
self.assertAlmostEqual(
|
|
cp707._resolve_mortality({"persen_hidup": 97.5}),
|
|
2.5,
|
|
)
|
|
|
|
def test_resolve_mortality_from_deaths_over_doc(self):
|
|
self.assertAlmostEqual(
|
|
cp707._resolve_mortality(
|
|
{"kematian_kumulatif_ekor": 50, "doc_in_ekor": 10000}
|
|
),
|
|
0.5,
|
|
)
|
|
|
|
def test_dashboard_shaped_context_grades_bw_and_mortality(self):
|
|
"""Reproduce Kandang 2 day-48 payload: FCR present, BW/mort via dashboard keys."""
|
|
ctx = {
|
|
"hari_ke": 48,
|
|
"fcr_terakhir": 1.71,
|
|
"bobot_avg_gram": 2100,
|
|
"persen_hidup": 98.0,
|
|
"mortalitas_hari_ini_ekor": 0,
|
|
"kematian_kumulatif_ekor": 200,
|
|
"doc_in_ekor": 10000,
|
|
}
|
|
graded = grade_context(ctx)
|
|
analyses = graded["analyses"]
|
|
self.assertIn("fcr", analyses)
|
|
self.assertIn("bw", analyses)
|
|
self.assertIn("mortality", analyses)
|
|
self.assertEqual(analyses["bw"]["actual"], 2100.0)
|
|
self.assertEqual(analyses["mortality"]["status"], "ok")
|
|
self.assertIn("2.00%", analyses["mortality"]["message"])
|
|
self.assertEqual(graded.get("mortalitas_hari_ini_ekor"), 0)
|
|
|
|
|
|
class MissingMetricsGradedAsUnknownTests(SimpleTestCase):
|
|
def test_missing_bw_and_mortality_emit_unknown_not_silent(self):
|
|
"""When fields are absent, graded facts must say unknown so the LLM cannot use standards as actuals."""
|
|
ctx = {"hari_ke": 48, "fcr_terakhir": 1.71}
|
|
graded = grade_context(ctx)
|
|
self.assertEqual(graded["analyses"]["bw"]["status"], "unknown")
|
|
self.assertEqual(graded["analyses"]["mortality"]["status"], "unknown")
|
|
self.assertIn("tidak tersedia", graded["analyses"]["bw"]["message"].lower())
|
|
|
|
|
|
class InsightPromptNamingTests(SimpleTestCase):
|
|
def test_user_prompt_uses_name_without_kandang_prefix_or_id(self):
|
|
prompt = build_insight_user_prompt(
|
|
topic="dashboard",
|
|
kandang_name="Kandang 2",
|
|
report_type="page",
|
|
report_period="current",
|
|
context={"hari_ke": 48, "kandangId": 6, "cycleId": 99},
|
|
)
|
|
self.assertIn("untuk `Kandang 2`", prompt)
|
|
self.assertNotIn("untuk kandang `Kandang 2`", prompt)
|
|
self.assertNotIn("id=", prompt)
|
|
self.assertNotIn('"kandangId"', prompt)
|
|
self.assertNotIn('"cycleId"', prompt)
|
|
|
|
def test_anti_hallucination_forbids_standard_as_actual_and_confusing_today_mortality(self):
|
|
rules = ANTI_HALLUCINATION_RULES.lower()
|
|
self.assertIn("standar", rules)
|
|
self.assertIn("bukan nilai aktual", rules)
|
|
self.assertIn("mortalitas_hari_ini", rules)
|
|
self.assertIn("jangan menyebut id", rules)
|
|
self.assertIn("severity_label", rules)
|
|
|
|
|
|
class MortalityWordingConsistencyTests(SimpleTestCase):
|
|
def test_critical_mortality_has_explicit_severity_label(self):
|
|
result = cp707.analyze_mortality(9.49, 48)
|
|
self.assertEqual(result["status"], "critical")
|
|
self.assertEqual(result["severity_label"], "SANGAT TINGGI")
|
|
self.assertEqual(result["actual_pct"], 9.49)
|
|
self.assertIn("rendah", result["dilarang_kata"])
|
|
|
|
def test_enforce_rewrites_rendah_when_mortality_is_critical(self):
|
|
graded = {
|
|
"analyses": {
|
|
"mortality": cp707.analyze_mortality(9.49, 48),
|
|
}
|
|
}
|
|
parsed = {
|
|
"kesimpulan": (
|
|
"Kandang 1 menunjukkan mortalitas kumulatif yang rendah pada hari ke-48, "
|
|
"mencapai 9.49%, melebihi batas kritis CP707 (>7%)."
|
|
),
|
|
"insight": "Lakukan nekropsi darurat.",
|
|
}
|
|
fixed = enforce_mortality_wording(parsed, graded)
|
|
text = f"{fixed['kesimpulan']} {fixed['insight']}".lower()
|
|
self.assertNotIn("rendah", text)
|
|
self.assertIn("sangat tinggi", text)
|
|
self.assertIn("9.49", fixed["kesimpulan"])
|
|
|
|
def test_enforce_leaves_ok_mortality_narrative_alone(self):
|
|
graded = {
|
|
"analyses": {
|
|
"mortality": cp707.analyze_mortality(2.1, 20),
|
|
}
|
|
}
|
|
parsed = {
|
|
"kesimpulan": "Mortalitas kumulatif rendah (2.10%), masih dalam batas normal.",
|
|
"insight": "Pertahankan biosekuriti.",
|
|
}
|
|
fixed = enforce_mortality_wording(parsed, graded)
|
|
self.assertEqual(fixed["kesimpulan"], parsed["kesimpulan"])
|