Files
dashboard/HANDOVER_AI_INSIGHT.md

11 KiB
Raw Permalink Blame History

Handover — AI Insight (sesi 4 Agustus 2026)

Dokumen ini ditulis untuk agen/developer berikutnya yang melanjutkan pekerjaan AI Insight. Isinya: apa yang sudah diperbaiki dan bagaimana cara membuktikannya, apa yang belum, dan jebakan-jebakan yang sudah diketahui.

Branch: Alfreda. Baseline sebelum sesi ini: commit 2b7fbad.


1. Cara cepat memahami perubahan

git log --oneline 2b7fbad..HEAD      # commit dari sesi ini
git diff 2b7fbad..HEAD --stat        # file yang tersentuh

File yang berubah/baru di sesi ini:

File Status Isi
components/shared/EndCycleWeightVsStandardChart.tsx baru Grafik berat aktual vs standar Cobb untuk mode Akhir Siklus
utils/insightScope.ts baru Pemotongan data per timeframe untuk halaman per-menu
utils/insightParse.ts baru Normalisasi JSON respons LLM ke {kesimpulan, insight}
backend/services/dashboardInsightService.js diubah Pemotongan periode berbasis nomor hari + prompt per mode
components/AiInsightCard.tsx diubah Memasang grafik di mode end_cycle
components/shared/PageAiInsight.tsx diubah Pakai insightScope + insightParse; cache backend dilepas
components/counting/ChickenCountingAiInsight.tsx diubah Harian vs Mingguan benar-benar beda; pakai insightParse
.claude/skills/run-app/SKILL.md baru Cara menjalankan aplikasi (lihat bagian 5)

2. SUDAH selesai — dan bukti verifikasinya

2.1 Grafik Aktual vs Standar Cobb (mode Akhir Siklus)

Sebelumnya mode Akhir Siklus hanya grid angka + bullet list, tanpa satu pun grafik.

Sekarang: line chart berat aktual vs kurva Cobb, satu sumbu Y, area terarsir di antara kedua garis (hijau = di atas standar, oranye = tertinggal), plus empat angka ringkasan (selisih akhir, hari di atas/di bawah standar, rata-rata deviasi, titik terburuk).

Semua angka dihitung di frontend, tidak ada yang dari LLM — jadi grafik tetap tampil walau Ollama mati, dan narasi AI tidak mungkin berbeda angka dari grafik.

Terverifikasi: dilihat langsung di browser. Tooltip hari ke-15 menampilkan aktual 418 g, standar Cobb 587 g, selisih −169 g (−28.8%).

2.2 Harian / Mingguan / Akhir Siklus sekarang benar-benar berbeda

Masalah lamanya: timeframe hanya mengubah label di prompt, datanya dikirim utuh.

  • PageAiInsight mengirim seluruh contextData apa pun timeframe-nya.
  • Dashboard mode Harian sebenarnya mengambil 7 hari terakhir (slice(-7)).
  • Dashboard mode Mingguan memotong berdasarkan posisi array, bukan nomor hari — kalau ada hari bolong, jendelanya bergeser. Dan pemotongan itu kena ke semua array termasuk daftar perangkat/kandang yang tidak punya dimensi waktu.
  • PageAiInsight membaca cache backend getAiInsight(cycleId) yang hanya di-key per siklus — tidak per topik, tidak per timeframe. Semua halaman bisa dapat teks yang sama.

Sekarang:

Mode Perilaku
Harian (dashboard & per-menu) Hanya hari terakhir
Mingguan (per-menu) 7 hari terakhir
Mingguan N (dashboard) Hari (N-1)*7+1 s/d N*7, dicocokkan lewat field day tiap baris
Akhir Siklus Seluruh siklus; minggu 1..N-1 diringkas rata-rata, minggu terakhir mentah

Daftar snapshot (kamera, perangkat, kandang) tidak ikut dipotong di mode apa pun.

Terverifikasi lewat assertion (fungsi pruneContextPackForLocalLLM sengaja di-export dari dashboardInsightService.js untuk keperluan ini):

  • Harian → [35]
  • Minggu 1 → [1..7]
  • Minggu 7 di siklus 35 hari → [] (bukan data minggu lain)
  • Data bolong 1–5 lalu 30–35, Minggu 5 → [30..35] tanpa geser
  • Akhir Siklus: 35 hari → 11 baris (hari 7, 14, 21, 28 rata-rata + hari 29–35 mentah), dan baris terakhir persis nilai asli, bukan rata-rata

Terverifikasi lewat generasi LLM sungguhan (Ollama qwen2.5:7b):

  • Harian → model mengutip "FCR aktual 1.776" = nilai hari ke-48 di data uji (1.2 + 48×0.012), bukan rata-rata 7 hari
  • Minggu 7 → source: generated, enam area terisi
  • Akhir Siklus → source: generated, keenam area disebut, yang kosong ditandai unknown

2.3 Mode Akhir Siklus wajib mencakup enam topik

Prompt end_cycle sekarang mewajibkan model mengevaluasi hitung ayam, berat ayam, hitung karung, panel IoT, FCR, dan EEF — masing-masing minimal satu temuan dengan angkanya — dan melarang mengarang kalau datanya kosong (harus bilang "data tidak tersedia"). Heavy key (deviceWeightData, ckaleData, liveCounts) yang tadinya selalu dibuang, kini disertakan khusus di mode ini.

2.4 Parser JSON tahan skema bebas

Model sering mengarang nama field, contoh nyata yang ditemui:

{ "tren_mortalitas_harian": "...", "dampak_populasi": "...", "rekomendasi_tindakan": [...] }

Perilaku lama:

  • ChickenCountingAiInsight: if (!parsed.kesimpulan) return null → seluruh isi dibuang, UI menampilkan "Respons AI tidak dapat diproses".
  • PageAiInsight: lolos tapi jatuh ke default "Analisis operasional kandang terpantau dengan baik." — kalimat menenangkan yang dikarang kode, padahal model melaporkan populasi habis total. Ini yang paling berbahaya.

Sekarang (utils/insightParse.ts, dipakai kedua komponen):

  • Nama field bebas diselamatkan jadi label (tren_mortalitas_harian → "Tren mortalitas harian")
  • Sinonim dikenali (rekomendasi_tindakan, saran, actions, ringkasan, dll.)
  • Tidak ada lagi kalimat default yang menenangkan — kalau tidak ada kesimpulan eksplisit, field pertama dipromosikan jadi kesimpulan
  • Objek bersarang tidak lagi jadi [object Object] atau blok JSON mentah
  • Respons tanpa JSON tetap null supaya UI menampilkan error asli

3. BELUM selesai / perlu keputusan

  1. Halaman per-menu belum dibuka di browser setelah parser diganti. Parser sudah diuji unit dan build + typecheck lolos, tapi halaman FCR/EEF/Berat Ayam belum pernah dilihat merender hasil barunya. Ini prioritas pertama.

  2. Jalur hijau grafik ("Di atas standar") belum pernah muncul di data nyata, karena kandang uji tertinggal di semua 34 hari. Logikanya simetris dengan jalur oranye yang sudah terbukti, tapi belum teruji.

  3. Nilai skalar tidak ikut dipotong per periode. weight.stats.averageWeight, uniformity, fcr adalah angka tunggal "kondisi terkini", bukan deret waktu. Akibatnya di Minggu 7 model tetap mengutip bobot terkini meski riwayat berat berhenti di hari 35. Kalau ingin skalar juga ikut periode, angka itu harus dihitung ulang per jendela di frontend sebelum dikirim. Perlu keputusan.

  4. Akhir Siklus dikirim sebagai ringkasan mingguan, bukan baris harian mentah. Alasannya data mentah seluruh siklus untuk enam topik melebihi context window qwen2.5:7b. Semua topik tetap terwakili, tapi granularitasnya mingguan. Kalau mau harian penuh, perlu model dengan context lebih besar.

  5. Cache backend di halaman per-menu dimatikan karena tidak di-key per topik/timeframe. Konsekuensi: setiap halaman selalu memanggil LLM — lebih lambat, tidak ada cache lintas sesi. Cache memori + sessionStorage per topik+timeframe masih jalan. Perbaikan sebenarnya: bikin endpoint cache yang menerima topik + timeframe.

  6. Populasi = 0 di database. Dashboard menampilkan "Saldo Akhir Ayam = 0" dan model melaporkan "populasi akhir nol ekor". Kemungkinan besar ini benar-benar isi database, bukan bug kode insight. Belum diselidiki sama sekali.

  7. Data berat berhenti di H35 padahal siklus sudah H48. Label "Selisih akhir siklus" di grafik sebenarnya selisih di hari data terakhir, bukan hari panen. Pertimbangkan mengubah labelnya.


4. Jebakan yang sudah diketahui (jangan diulang)

  • Jangan potong array berdasarkan posisi. Selalu pakai field day. Bug lama menggeser jendela saat ada hari bolong.
  • Minggu tanpa data harus mengembalikan array kosong, bukan fallback ke 7 hari terakhir. Bug ini sempat saya buat lalu diperbaiki — kalau tidak, "Minggu 7" diam-diam menampilkan hari 29–35.
  • Jangan ubah array riwayat jadi objek. Kode ekstraksi di dashboardInsightService.js membacanya sebagai array; kalau dijadikan objek, dayAge dan semua metrik gagal terbaca dan sistem diam-diam jatuh ke generator fallback lokal. Gejalanya: headline berbunyi "Evaluasi Akhir Siklus Kritis (Hari ke-?)" dengan metrik N/A.
  • Jangan pernah menambahkan kalimat default yang menenangkan di parser. Itu bisa menutupi temuan kritis.
  • Cek source di respons /api/ai-insights/dashboard: generated = LLM beneran, cache = dari DB, kalau isinya template "Hari ke-?" berarti fallback lokal.

5. Cara menjalankan (ringkas)

Detail lengkap ada di .claude/skills/run-app/SKILL.md. Ringkasnya:

open -a Docker                                   # tunggu daemon siap
osascript -e 'quit app "Ollama"'                 # hindari rebutan port 11434
docker compose up -d database llm                # HANYA dua ini
bash start_all.sh                                # jalankan di background
  • Frontend sering geser ke 3001 (port 3000 dipakai proses lain). Baca URL asli dari /tmp/frontend.log.
  • Backend health ada di /health, bukan /api/health (yang terakhir 404 dan terlihat seperti backend mati).
  • Bukti DB tersambung: curl localhost:5001/api/kandangs harus mengembalikan data.
  • Model yang dipakai: qwen2.5:7b (sesuai LLM_MODEL_NAME di backend/.env).

Menguji pemotongan periode tanpa UI

const { pruneContextPackForLocalLLM } = require('./backend/services/dashboardInsightService.js');
const rows = Array.from({ length: 35 }, (_, i) => ({ day: i + 1, actual: 40 + i * 50 }));
pruneContextPackForLocalLLM({ h: rows }, 'daily'); // -> hari 35 saja
pruneContextPackForLocalLLM({ h: rows }, 'weekly', 'Minggu 7'); // -> []
pruneContextPackForLocalLLM({ h: rows }, 'end_cycle'); // -> 11 baris

Memicu generasi nyata tanpa login

curl -s -X POST http://localhost:5001/api/ai-insights/dashboard \
  -H 'Content-Type: application/json' \
  -d '{"cycleId":"CYCLE-JBW-2026-05-22","forceRefresh":true,"reportType":"weekly","reportPeriod":"Minggu 7","contextPack":{...}}'

6. Status kualitas kode

  • npx tsc --noEmit → 165 error, sama persis dengan baseline sebelum sesi ini (nol error baru). Repo ini memang belum bersih typecheck sejak sebelumnya.
  • npx eslint pada file yang disentuh → 0 error (hanya warning any yang sudah ada).
  • npx vite build → sukses.
  • node --check backend/services/dashboardInsightService.js → OK.