fix weight ai insight target weight data using mr. mike instead of cp707
This commit is contained in:
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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user