fix weight ai insight target weight data using mr. mike instead of cp707

This commit is contained in:
Alberto-Audrix committed 2026-10-01 16:59:58 +07:00
1 parent bc2b260065
commit 05465b0382
5 files changed
+83 -11

No files matched your search

@@ -394,7 +394,7 @@ def analyze_fcr(actual_fcr: Any, day_age: Any) -> dict[str, Any]:
}
def analyze_bw(actual_bw_g: Any, day_age: Any) -> dict[str, Any]:
def analyze_bw(actual_bw_g: Any, day_age: Any, target_bw_g: Any = None) -> dict[str, Any]:
try:
day_int = int(day_age)
except (TypeError, ValueError):
@@ -420,7 +420,11 @@ def analyze_bw(actual_bw_g: Any, day_age: Any) -> dict[str, Any]:
# 0 g is a missing/placeholder reading, not a real bird weight.
if actual is not None and actual <= 0:
actual = None
std_bw = get_bw_standard_by_day(day_age)
std_bw = _as_float(target_bw_g)
if std_bw is None or std_bw <= 0:
std_bw = get_bw_standard_by_day(day_age)
if actual is None or std_bw is None:
return {
"actual": actual,
@@ -437,24 +441,24 @@ def analyze_bw(actual_bw_g: Any, day_age: Any) -> dict[str, Any]:
if delta_pct < -15:
status = "critical"
message = (
f"Bobot badan aktual {actual:g}g jauh di bawah standar CP 707 ({std_bw:g}g) "
f"Bobot badan aktual {actual:g}g jauh di bawah standar ({std_bw:g}g) "
f"pada hari ke-{day_age}. Deviasi {abs(delta_pct):.1f}%. Lakukan grading segera."
)
elif delta_pct < -5:
status = "warning"
message = (
f"Bobot badan aktual {actual:g}g sedikit di bawah standar CP 707 ({std_bw:g}g) "
f"Bobot badan aktual {actual:g}g sedikit di bawah standar ({std_bw:g}g) "
f"pada hari ke-{day_age}. Deviasi {abs(delta_pct):.1f}%. Tingkatkan stimulasi pakan."
)
elif delta_pct > 10:
status = "ok"
message = (
f"Bobot badan aktual {actual:g}g melampaui standar CP 707 ({std_bw:g}g) "
f"Bobot badan aktual {actual:g}g melampaui standar ({std_bw:g}g) "
f"pada hari ke-{day_age}. Pertumbuhan sangat baik."
)
else:
status = "ok"
message = f"Bobot badan aktual {actual:g}g sesuai standar CP 707 ({std_bw:g}g) pada hari ke-{day_age}."
message = f"Bobot badan aktual {actual:g}g sesuai standar ({std_bw:g}g) pada hari ke-{day_age}."
return {
"actual": actual,
@@ -1089,7 +1093,8 @@ def build_cp707_standard_block(context_data: dict[str, Any] | None) -> str:
ip = get_ip_standard_by_day(day)
ip_range = get_ip_standard_range()
bw = get_bw_standard_by_day(day)
target_bw_ctx = _resolve_target_bw(context_data, day)
bw = target_bw_ctx if target_bw_ctx is not None else get_bw_standard_by_day(day)
fcr = get_fcr_standard_by_day(day)
mort = get_mortality_cum_std_by_day(day)
@@ -1279,6 +1284,30 @@ def _resolve_iot(context_pack: dict[str, Any]) -> dict[str, Any]:
return context_pack
def _resolve_target_bw(context_pack: dict[str, Any] | None, day_age: int | None) -> float | None:
if not isinstance(context_pack, dict):
return None
direct = _first_positive_number(
context_pack.get("target_bw_gram"),
context_pack.get("targetWeight"),
context_pack.get("bobot_target_gram"),
context_pack.get("target_gram"),
context_pack.get("target_weight"),
)
if direct is not None:
return direct
tren = context_pack.get("tren_harian")
if isinstance(tren, list) and day_age is not None:
for row in tren:
if isinstance(row, dict) and row.get("hari") == day_age:
t = _first_positive_number(row.get("target_gram"), row.get("target_bw_gram"), row.get("target"))
if t is not None:
return t
return None
def analyze_with_cp707_standards(context_pack: dict[str, Any] | None, topic: str | None = None) -> dict[str, Any]:
"""Analisis komprehensif; toleran terhadap field InsightContext rebuild dan pack lama.
Topic-scoped: only grades metrics in TOPIC_METRICS[topic]. Unknown emitted only for
@@ -1294,8 +1323,9 @@ def analyze_with_cp707_standards(context_pack: dict[str, Any] | None, topic: str
# Body weight grading
if "bw" in metrics_to_grade:
actual_bw = _resolve_bw_grams(pack)
target_bw = _resolve_target_bw(pack, day_age)
if day_age is not None:
result["analyses"]["bw"] = analyze_bw(actual_bw, day_age)
result["analyses"]["bw"] = analyze_bw(actual_bw, day_age, target_bw)
else:
result["analyses"]["bw"] = {"status": "unknown", "message": "Umur tidak tersedia untuk grading bobot badan."}
@@ -776,6 +776,36 @@ def enforce_mortality_wording(
return out
def enforce_bw_wording(
parsed: dict[str, Any],
graded: dict[str, Any] | None,
) -> dict[str, Any]:
"""Ensure body weight status and narrative match graded facts."""
bw = ((graded or {}).get("analyses") or {}).get("bw") or {}
status = str(bw.get("status") or "").lower()
if not status:
return parsed
message = str(bw.get("message") or "").strip()
actual = bw.get("actual")
std = bw.get("standard")
out = dict(parsed)
kesimpulan = str(out.get("kesimpulan") or "")
# If graded status is OK, but summary claims it is below standard / critical:
if status == "ok" and any(
w in kesimpulan.lower()
for w in ("di bawah standar", "sangat rendah", "critical", "kritis", "jauh di bawah")
):
if message:
out["kesimpulan"] = message
elif actual is not None and std is not None:
out["kesimpulan"] = f"Bobot badan ayam ({actual:g}g) sesuai dengan target standar ({std:g}g)."
return out
_DUP_KANDANG = re.compile(r"\b[Kk]andang(?:\s+[Kk]andang)+\b")
@@ -1111,6 +1141,7 @@ def generate_insight(
"insight": str(parsed_raw.get("insight") or ""),
}
soft = enforce_mortality_wording(soft, graded)
soft = enforce_bw_wording(soft, graded)
soft = sanitize_insight_narrative(soft)
parsed_raw["kesimpulan"] = soft.get("kesimpulan")
parsed_raw["insight"] = soft.get("insight")
@@ -1143,6 +1174,7 @@ def generate_insight(
"Output LLM tidak sesuai format JSON yang diminta: " + repr(str(raw)[:200])
)
parsed = enforce_mortality_wording(parsed, graded)
parsed = enforce_bw_wording(parsed, graded)
parsed = sanitize_insight_narrative(parsed)
insight_text = parsed["insight"]
summary = parsed["kesimpulan"]
+8
View File
@@ -365,6 +365,14 @@ export const AiInsightCard: React.FC<AiInsightCardProps> = ({
contextPayload.uniformity_persen = scopedMetrics.uniformity_persen;
contextPayload.pakan_kumulatif_karung = scopedMetrics.pakan_kumulatif_karung;
const currentTarget =
scopedMetrics.weightSeries.find((row) => row.hari === contextPayload.hari_ke)?.target ??
[...scopedMetrics.weightSeries].reverse().find((row) => row.target != null)?.target ??
null;
if (currentTarget != null && currentTarget > 0) {
contextPayload.target_bw_gram = currentTarget;
}
// EEF series for trend computation (eef, dashboard, end_cycle topics)
if (topic === 'eef' || topic === 'dashboard' || reportType === 'end_cycle') {
contextPayload.eef_seri = scopedMetrics.kpiSeries
+2 -1
View File
@@ -132,12 +132,13 @@ const WeightMonitoringPage: React.FC<{ onSettings: () => void }> = ({ onSettings
kandangName: selectedKandang.kandang_name,
cycleId: selectedCycle?.id,
totalDays: selectedCycle?.total_days ?? null,
docWeight: docWeight,
weightRows: chartRows,
averageWeight: kpiLatest?.average_weight ?? null,
uniformity: kpiLatest?.uniformity ?? null,
adg: kpiLatest?.average_daily_gain ?? null,
});
}, [selectedKandang, selectedCycle, chartRows, kpiLatest]);
}, [selectedKandang, selectedCycle, docWeight, chartRows, kpiLatest]);
if (!selectedCycle) return <EmptyFarm onSettings={onSettings} />;
+3 -2
View File
@@ -209,6 +209,7 @@ export type BuildWeightInsightContextArgs = {
cycleId?: number;
hari_ke?: number | null;
totalDays?: number | null;
docWeight?: number | null;
weightRows?: WeightInsightRow[];
averageWeight?: number | null;
targetWeight?: number | null;
@@ -226,8 +227,8 @@ export function buildWeightInsightContext(args: BuildWeightInsightContextArgs):
const hari = dayOf(row);
if (hari === null) return null;
const bobot = num(row.average_weight) ?? num(row.bobot_gram);
const target =
num(row.target_gram) ?? (totalDays != null ? getTargetWeightForAge(hari, totalDays) : null);
const doc = num(row.doc_weight) ?? num(args.docWeight) ?? 40;
const target = num(row.target_gram) ?? (hari > 0 ? getTargetWeightForAge(hari, doc) : null);
return {
hari,
bobot_gram: bobot != null && bobot > 0 ? bobot : null,