import { mdiBrain, mdiChartTimelineVariant, mdiClipboardTextClockOutline, mdiLightbulbOnOutline, mdiPlus, } from '@mdi/js'; import axios from 'axios'; import Head from 'next/head'; import React, { ReactElement, useEffect, useMemo, useState } from 'react'; import BaseButton from '../components/BaseButton'; import BaseIcon from '../components/BaseIcon'; import CardBox from '../components/CardBox'; import SectionMain from '../components/SectionMain'; import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; import { getPageTitle } from '../config'; import LayoutAuthenticated from '../layouts/Authenticated'; import { useAppSelector } from '../stores/hooks'; type Cause = { id: string; label: string; category: | 'people' | 'process' | 'technology' | 'policy' | 'environment' | 'materials' | 'measurement' | 'other'; contribution: number; description: string; }; type DataSource = { id: string; title: string; category: 'input' | 'metric' | 'pattern'; summary: string; confidence: number; }; type Solution = { id: string; title: string; description: string; rationale: string; executionSteps: string[]; kpis: string[]; risks: string[]; resources: string[]; type: | 'marketing' | 'sales' | 'operations' | 'finance' | 'product' | 'hr' | 'technology' | 'other'; impact: number; efficiency: number; speed: number; lowRisk: number; score: number; success: number; rootCauseId: string; duration: number; }; type PlanTask = { id: string; day: number; title: string; description: string; }; type Run = { id: string; caseId: string; planId: string; createdAt: string; title: string; problem: string; urgency: number; financialImpact: 'low' | 'medium' | 'high' | 'unknown'; priority: 'low' | 'medium' | 'high' | 'critical'; priorityScore: number; causes: Cause[]; dataSources: DataSource[]; solutions: Solution[]; selectedSolution: Solution; tasks: PlanTask[]; conclusion: string; }; type ProblemKnowledge = { key: 'pengangguran' | 'kemiskinan' | 'keluarga_hukum_waris'; label: string; keywords: string[]; severity: 'Tinggi' | 'Kritis'; penyebab: string[]; hipotesis: string[]; solusi: string[]; kesimpulan: string; causeCategories: Cause['category'][]; }; type SolutionDraft = Omit< Solution, 'id' | 'score' | 'success' | 'rootCauseId' > & { rootCauseId?: string; }; type QuestionSolutionFit = { focus: string; keywords: string[]; }; const STORAGE_KEY = 'optema-ai-runs'; const MAX_DATA_SOURCES = 3; const PROBLEM_KNOWLEDGE: ProblemKnowledge[] = [ { key: 'pengangguran', label: 'Pengangguran', keywords: [ 'pengangguran', 'menganggur', 'nganggur', 'pencari kerja', 'cari kerja', 'pekerjaan', 'lamaran', 'cv', 'interview', 'wawancara', 'lowongan kerja', 'lapangan kerja', 'jobless', ], severity: 'Tinggi', penyebab: [ 'Kurangnya lapangan kerja', 'Skill tidak sesuai kebutuhan industri', 'Kurangnya pengalaman', 'Pendidikan rendah', 'Otomatisasi pekerjaan', 'Pertumbuhan ekonomi lambat', 'Kurangnya informasi lowongan', 'Mobilitas rendah', 'Kurangnya sertifikasi', 'Persaingan tinggi', ], hipotesis: [ 'Skill tidak relevan', 'Lokasi pekerjaan terbatas', 'Kurangnya pelatihan', 'Kurangnya akses informasi', 'Ekonomi melambat', 'Perusahaan mengurangi perekrutan', 'Kurangnya jaringan profesional', 'Kurangnya pengalaman kerja', 'Teknologi menggantikan pekerjaan', 'Sertifikasi belum memadai', ], solusi: [ 'Pelatihan keterampilan', 'Mengikuti sertifikasi', 'Membuat CV profesional', 'Magang', 'Networking', 'Pelatihan digital', 'Job matching', 'Wirausaha', 'Peningkatan pendidikan', 'Pencarian kerja aktif', ], kesimpulan: 'Pengangguran dapat dikurangi dengan peningkatan kompetensi, akses informasi kerja, dan penciptaan lapangan kerja.', causeCategories: ['policy', 'people', 'process', 'technology'], }, { key: 'kemiskinan', label: 'Kemiskinan', keywords: [ 'kemiskinan', 'miskin', 'pendapatan rendah', 'kredit mikro', 'modal usaha', 'bantuan modal', 'usaha kecil', 'umkm', 'ekonomi keluarga', ], severity: 'Kritis', penyebab: [ 'Pendapatan rendah', 'Pendidikan rendah', 'Pengangguran', 'Kurangnya modal', 'Akses layanan terbatas', 'Produktivitas rendah', 'Ketimpangan ekonomi', 'Inflasi', 'Beban keluarga tinggi', 'Kurangnya keterampilan', ], hipotesis: [ 'Kurangnya peluang ekonomi', 'Kurangnya akses pendidikan', 'Tidak memiliki aset produktif', 'Akses modal sulit', 'Kesehatan buruk', 'Kondisi wilayah tertinggal', 'Kurangnya pelatihan', 'Harga kebutuhan meningkat', 'Pendapatan tidak stabil', 'Kesempatan kerja terbatas', ], solusi: [ 'Pelatihan kerja', 'Bantuan modal usaha', 'Pendidikan vokasi', 'Pendampingan UMKM', 'Program padat karya', 'Akses kredit mikro', 'Peningkatan produktivitas', 'Digitalisasi usaha', 'Kemitraan usaha', 'Penciptaan lapangan kerja', ], kesimpulan: 'Kemiskinan membutuhkan kombinasi peningkatan pendapatan, pendidikan, keterampilan, dan akses ekonomi.', causeCategories: ['environment', 'people', 'policy', 'process'], }, { key: 'keluarga_hukum_waris', label: 'Hukum keluarga, perceraian, dan warisan', keywords: [ 'selingkuh', 'perselingkuhan', 'cerai', 'perceraian', 'warisan', 'waris', 'ahli waris', 'harta waris', 'harta bersama', 'harta gono gini', 'gono gini', 'gono-gini', 'hak asuh', 'nafkah anak', 'hak anak', 'sengketa keluarga', 'rumah tangga', 'suami', 'istri', ], severity: 'Kritis', penyebab: [ 'Konflik rumah tangga dan dugaan perselingkuhan', 'Perceraian, hak asuh, dan nafkah anak belum tertata', 'Pembagian harta bersama dan warisan belum jelas', 'Hak dan kebutuhan 10 anak berisiko terabaikan', 'Dokumen keluarga, aset, dan ahli waris belum diinventarisasi', 'Komunikasi keluarga rentan memanas tanpa mediator netral', 'Keputusan emosional berisiko memperburuk sengketa hukum', 'Belum ada rencana perlindungan anak jangka pendek', 'Potensi pengalihan aset sebelum status hukum jelas', 'Kebutuhan pendamping hukum dan psikososial belum dipenuhi', ], hipotesis: [ 'Isu perceraian, warisan, harta bersama, nafkah, dan hak anak masih tercampur menjadi satu masalah', 'Keluarga belum memiliki kronologi, bukti, dan dokumen pendukung yang rapi', 'Ahli waris, aset, utang, dan status harta perlu dipetakan sebelum mengambil keputusan', 'Mediasi awal dibutuhkan agar konflik tidak langsung merugikan anak-anak', 'Konsultasi advokat, pos bantuan hukum, mediator, atau notaris perlu dilakukan sebelum membuat kesepakatan', 'Anak-anak membutuhkan rencana sementara untuk biaya hidup, sekolah, kesehatan, dan tempat tinggal', 'Kesepakatan lisan berisiko lemah jika tidak dituangkan dalam dokumen yang benar', 'Ada risiko aset keluarga dipindahkan atau dipakai tanpa persetujuan pihak yang berhak', 'Perlu pemisahan antara masalah moral/perselingkuhan dan hak perdata keluarga', 'Keputusan paling aman adalah langkah hukum bertahap berbasis dokumen', ], solusi: [ 'Konsultasi hukum keluarga dan waris', 'Mediasi keluarga dengan pihak netral', 'Inventarisasi aset, utang, dan ahli waris', 'Rencana hak asuh dan nafkah anak', 'Pengamanan dokumen keluarga dan aset', 'Pendampingan psikososial anak', 'Penyusunan kronologi dan bukti', 'Opsi penyelesaian damai tertulis', 'Rujukan advokat atau pos bantuan hukum', 'Koordinasi notaris/PPAT untuk urusan aset bila diperlukan', ], kesimpulan: 'Kasus perselingkuhan, perceraian, warisan, dan banyak anak harus dipisahkan menjadi isu hukum keluarga, harta/waris, dan perlindungan anak; langkah aman adalah dokumentasi, mediasi, dan konsultasi hukum profesional.', causeCategories: ['people', 'policy', 'process', 'other'], }, ]; const uuid = () => typeof crypto !== 'undefined' && 'randomUUID' in crypto ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`; const clamp = (value: number) => Math.max(1, Math.min(100, Math.round(value))); const scoreSolution = ( impact: number, efficiency: number, speed: number, lowRisk: number, ) => Math.round(impact * 0.4 + efficiency * 0.3 + speed * 0.2 + lowRisk * 0.1); const titleFromProblem = (problem: string) => { const clean = problem.trim().replace(/\s+/g, ' '); if (!clean) return 'Kasus keputusan baru'; return clean.length > 64 ? `${clean.slice(0, 61)}...` : clean; }; const shortList = (items: string[], limit = 3) => { const selected = items.slice(0, limit); return selected.length ? selected.join(', ') : 'data belum tersedia'; }; const cleanText = (value: string) => value.trim().replace(/\s+/g, ' '); const problemSnippet = (problem: string, maxLength = 150) => { const clean = cleanText(problem); return clean.length > maxLength ? `${clean.slice(0, maxLength - 3)}...` : clean; }; const trimSentenceEnd = (value: string) => cleanText(value).replace(/[.!?]+$/u, ''); const keywordFitScore = (problem: string, keywords: string[]) => { const text = cleanText(problem).toLowerCase(); return keywords.reduce((score, keyword) => { const normalizedKeyword = cleanText(keyword).toLowerCase(); if (!normalizedKeyword || !text.includes(normalizedKeyword)) { return score; } return score + (normalizedKeyword.includes(' ') ? 3 : 1); }, 0); }; const applyQuestionFitToDrafts = ( drafts: SolutionDraft[], fits: QuestionSolutionFit[], problem: string, ): SolutionDraft[] => { const scores = fits.map((fit) => keywordFitScore(problem, fit.keywords)); const maxScore = Math.max(...scores, 0); const hasExplicitFit = maxScore > 0; const snippet = problemSnippet(problem); return drafts.map((draft, index) => { const fit = fits[index]; const fitScore = scores[index] || 0; const isBestFit = hasExplicitFit && fitScore === maxScore && fitScore > 0; const scoreBoost = isBestFit ? 10 + Math.min(fitScore, 8) : fitScore > 0 ? 4 + Math.min(fitScore, 4) : hasExplicitFit ? -3 : 0; const questionContext = fit ? fitScore > 0 ? `Kesesuaian pertanyaan: solusi ini menjawab fokus “${fit.focus}” yang muncul pada input pengguna: “${snippet}”.` : hasExplicitFit ? 'Catatan kesesuaian: solusi ini hanya alternatif pendukung, bukan jawaban paling langsung dari pertanyaan pengguna.' : `Kesesuaian pertanyaan: solusi ini disusun dari pola masalah yang ditulis pengguna: “${snippet}”.` : ''; return { ...draft, title: isBestFit ? `${draft.title} — paling sesuai pertanyaan` : draft.title, rationale: [questionContext, draft.rationale].filter(Boolean).join(' '), impact: draft.impact + scoreBoost, efficiency: draft.efficiency + (isBestFit ? 4 : fitScore > 0 ? 2 : 0), speed: draft.speed + (isBestFit ? 3 : fitScore > 0 ? 1 : 0), lowRisk: draft.lowRisk + (isBestFit ? 2 : 0), }; }); }; const findProblemKnowledge = (problem: string) => { const scoredKnowledge = PROBLEM_KNOWLEDGE.map((knowledge) => ({ knowledge, score: keywordFitScore(problem, knowledge.keywords), })) .filter((item) => item.score > 0) .sort((a, b) => b.score - a.score); return scoredKnowledge[0]?.knowledge; }; const buildKnowledgeCauses = ( knowledge: ProblemKnowledge, urgency: number, financialImpact: Run['financialImpact'], ): Cause[] => { const impactBoost = financialImpact === 'high' ? 9 : financialImpact === 'medium' ? 5 : 2; const severityBoost = knowledge.severity === 'Kritis' ? 10 : 6; const base = 56 + urgency * 5 + impactBoost + severityBoost; return knowledge.penyebab.slice(0, 4).map((cause, index) => ({ id: uuid(), label: cause, category: knowledge.causeCategories[index] || 'other', contribution: clamp(base + 14 - index * 7), description: `Penyebab dari knowledge base ${knowledge.label}. Hipotesis pendukung: ${ knowledge.hipotesis[index] || knowledge.hipotesis[0] }.`, })); }; const finalizeSolutions = ( raw: SolutionDraft[], causes: Cause[], ): Solution[] => raw .map((solution) => { const score = scoreSolution( solution.impact, solution.efficiency, solution.speed, solution.lowRisk, ); return { ...solution, id: uuid(), rootCauseId: solution.rootCauseId || causes[0]?.id || uuid(), impact: clamp(solution.impact), efficiency: clamp(solution.efficiency), speed: clamp(solution.speed), lowRisk: clamp(solution.lowRisk), score, success: clamp(score + (solution.lowRisk - 70) * 0.25), } as Solution; }) .sort((a, b) => b.score - a.score); const inferCauses = ( problem: string, urgency: number, financialImpact: Run['financialImpact'], ): Cause[] => { const matchedKnowledge = findProblemKnowledge(problem); if (matchedKnowledge) { return buildKnowledgeCauses(matchedKnowledge, urgency, financialImpact); } const text = problem.toLowerCase(); const impactBoost = financialImpact === 'high' ? 8 : financialImpact === 'medium' ? 4 : 0; const base = 58 + urgency * 5 + impactBoost; const candidates: Cause[] = [ { id: uuid(), label: text.match(/jual|sales|revenue|omzet|penjualan|conversion/) ? 'Akuisisi & konversi penjualan melemah' : 'Sinyal performa utama belum stabil', category: 'sales' as Cause['category'], contribution: clamp(base + 16), description: 'Indikasi utama datang dari funnel permintaan, kualitas prospek, atau kemampuan tim mengubah peluang menjadi transaksi.', }, { id: uuid(), label: text.match(/iklan|marketing|campaign|brand|traffic/) ? 'Pesan marketing kurang tepat sasaran' : 'Eksperimen marketing belum cukup terukur', category: 'measurement', contribution: clamp(base + 9), description: 'Channel, pesan, dan metrik evaluasi perlu dipisahkan agar keputusan tidak hanya berdasarkan asumsi.', }, { id: uuid(), label: text.match(/harga|margin|biaya|cash|modal/) ? 'Tekanan harga, margin, atau biaya' : 'Prioritas operasional belum selaras dengan target', category: 'process', contribution: clamp(base + 3), description: 'Ada peluang menata ulang proses, prioritas kerja, dan batasan biaya sebelum menjalankan solusi besar.', }, { id: uuid(), label: text.match(/produk|fitur|quality|kualitas|stok/) ? 'Kesesuaian produk dan pengalaman pelanggan perlu divalidasi' : 'Risiko eksekusi perlu dikendalikan sejak awal', category: 'product' as Cause['category'], contribution: clamp(base - 5), description: 'Solusi harus diuji cepat agar tim tidak menghabiskan biaya pada arah yang belum terbukti.', }, ]; return candidates .map((cause) => ({ ...cause, category: cause.category === 'sales' || cause.category === 'product' ? 'other' : cause.category, })) .sort((a, b) => b.contribution - a.contribution); }; const buildDataSources = ( problem: string, urgency: number, financialImpact: Run['financialImpact'], causes: Cause[], ): DataSource[] => { const cleanProblem = problem.trim().replace(/\s+/g, ' '); const financialLabel = financialImpact === 'high' ? 'besar' : financialImpact === 'medium' ? 'sedang' : financialImpact === 'low' ? 'rendah' : 'belum diketahui'; const topCauses = causes .slice(0, 2) .map((cause) => cause.label.toLowerCase()) .join(' dan '); const matchedKnowledge = findProblemKnowledge(problem); if (matchedKnowledge) { const sources: DataSource[] = [ { id: uuid(), title: 'Input masalah dari pengguna', category: 'input', summary: `Narasi utama terdeteksi sebagai topik ${matchedKnowledge.label}: “${ cleanProblem.length > 132 ? `${cleanProblem.slice(0, 129)}...` : cleanProblem }”.`, confidence: clamp(90 + Math.min(cleanProblem.length / 50, 6)), }, { id: uuid(), title: `Knowledge base ${matchedKnowledge.label}`, category: 'pattern', summary: `Severity ${matchedKnowledge.severity}. Penyebab dominan: ${shortList( matchedKnowledge.penyebab, MAX_DATA_SOURCES, )}.`, confidence: matchedKnowledge.severity === 'Kritis' ? 94 : 91, }, { id: uuid(), title: 'Hipotesis dan prioritas eksekusi', category: 'metric', summary: `Hipotesis utama: ${shortList( matchedKnowledge.hipotesis, MAX_DATA_SOURCES, )}. Urgensi ${urgency}/5 dan dampak finansial ${financialLabel}.`, confidence: clamp(84 + urgency * 2), }, ]; return sources.slice(0, MAX_DATA_SOURCES); } const sources: DataSource[] = [ { id: uuid(), title: 'Input masalah dari pengguna', category: 'input', summary: `Narasi utama yang dianalisis: “${cleanProblem.length > 132 ? `${cleanProblem.slice(0, 129)}...` : cleanProblem}”.`, confidence: clamp(88 + Math.min(cleanProblem.length / 40, 8)), }, { id: uuid(), title: 'Parameter prioritas bisnis', category: 'metric', summary: `Urgensi berada di level ${urgency}/5 dengan dampak finansial ${financialLabel}; parameter ini menaikkan bobot prioritas dan kecepatan eksekusi.`, confidence: clamp(82 + urgency * 2), }, { id: uuid(), title: 'Pola akar masalah terdeteksi', category: 'pattern', summary: topCauses ? `OPTEMA membaca pola dominan pada ${topCauses}; pola ini menjadi dasar pemilihan solusi dan action plan.` : 'OPTEMA belum menemukan pola dominan yang kuat; solusi diarahkan ke validasi cepat agar data berikutnya lebih presisi.', confidence: clamp(causes[0]?.contribution || 72), }, ]; return sources.slice(0, MAX_DATA_SOURCES); }; const buildKnowledgeSolutionDrafts = ( problem: string, knowledge: ProblemKnowledge, urgency: number, causes: Cause[], ): SolutionDraft[] => { const allSolutionOptions = shortList(knowledge.solusi, 10); const topHypotheses = shortList(knowledge.hipotesis, MAX_DATA_SOURCES); const topCauses = shortList(knowledge.penyebab, MAX_DATA_SOURCES); const withQuestionFit = ( drafts: SolutionDraft[], fits: QuestionSolutionFit[], ) => applyQuestionFitToDrafts(drafts, fits, problem); if (knowledge.key === 'pengangguran') { return withQuestionFit([ { title: 'Pelatihan keterampilan dan sertifikasi kerja terarah', description: 'Membangun jalur peningkatan kompetensi yang langsung dihubungkan dengan kebutuhan industri, sertifikasi, dan target penempatan kerja.', rationale: `Diprioritaskan karena penyebab utama adalah ${topCauses}. Hipotesis yang diuji: ${topHypotheses}. Paket solusi dasar: ${allSolutionOptions}.`, executionSteps: [ 'Petakan 3–5 sektor industri yang masih membuka lowongan dan daftar skill yang paling sering diminta.', 'Kelompokkan pencari kerja berdasarkan level skill, pengalaman, pendidikan, dan kesiapan mengikuti sertifikasi.', 'Jalankan pelatihan digital/vokasi pendek berbasis proyek agar peserta punya portofolio nyata.', 'Fasilitasi sertifikasi yang relevan dengan lowongan prioritas, bukan sertifikasi umum yang sulit dikonversi menjadi pekerjaan.', 'Hubungkan peserta tersertifikasi ke job matching, magang, dan interview terjadwal dengan perusahaan/UMKM lokal.', 'Review hasil setiap 2 minggu: peserta lulus, lamaran terkirim, interview, penempatan, dan gap skill yang tersisa.', ], kpis: [ 'Jumlah peserta menyelesaikan pelatihan', 'Persentase peserta memperoleh sertifikasi', 'Jumlah peserta masuk interview/magang', 'Tingkat penempatan kerja setelah 30–60 hari', ], risks: [ 'Pelatihan tidak sesuai kebutuhan industri; mitigasi: validasi kurikulum dengan lowongan aktif dan HR/perusahaan lokal.', 'Peserta berhenti di tengah program; mitigasi: gunakan kelas pendek, mentor, dan target portofolio mingguan.', 'Sertifikasi tidak meningkatkan peluang kerja; mitigasi: pilih sertifikasi yang disebut eksplisit dalam lowongan prioritas.', ], resources: [ 'Data lowongan aktif', 'Trainer vokasi/digital', 'Mitra sertifikasi', 'PIC job matching', ], type: 'hr', impact: 92 + urgency, efficiency: 86, speed: 78, lowRisk: 82, duration: 30, rootCauseId: causes[1]?.id || causes[0]?.id, }, { title: 'Job matching, CV profesional, dan pencarian kerja aktif', description: 'Memperbaiki akses informasi kerja, kualitas CV, kesiapan interview, dan ritme lamaran agar peluang kerja meningkat cepat.', rationale: 'Cocok untuk mengatasi kurangnya informasi lowongan, jaringan profesional terbatas, dan rendahnya pengalaman mencari kerja secara terstruktur.', executionSteps: [ 'Bangun database lowongan yang dikurasi berdasarkan lokasi, skill minimum, gaji, dan peluang entry-level.', 'Buat template CV profesional, LinkedIn/profil digital, dan surat lamaran yang disesuaikan per jenis posisi.', 'Latih simulasi interview, komunikasi profesional, dan cara menjelaskan pengalaman nonformal atau proyek portofolio.', 'Tetapkan target pencarian kerja aktif: jumlah lamaran, follow-up, networking, dan evaluasi penolakan per minggu.', 'Pasangkan kandidat dengan mentor atau alumni agar mereka mendapat referensi dan informasi lowongan lebih cepat.', ], kpis: [ 'Jumlah CV/profil diperbaiki', 'Jumlah lamaran berkualitas per minggu', 'Rasio panggilan interview', 'Jumlah kandidat mendapat offer/magang', ], risks: [ 'Lamaran banyak tetapi tidak relevan; mitigasi: pakai daftar lowongan terkurasi dan scoring kecocokan skill.', 'Kandidat kurang percaya diri saat interview; mitigasi: lakukan simulasi dan feedback sebelum interview nyata.', ], resources: [ 'Database lowongan', 'Template CV dan profil digital', 'Mentor karier', 'Kemitraan perusahaan/HR', ], type: 'hr', impact: 87, efficiency: 89, speed: 92, lowRisk: 78, duration: 14, rootCauseId: causes[2]?.id || causes[0]?.id, }, { title: 'Magang, networking, dan wirausaha mikro', description: 'Membuka jalur pengalaman kerja melalui magang, jejaring profesional, dan opsi wirausaha kecil untuk mengurangi ketergantungan pada lowongan formal.', rationale: 'Menjawab hambatan kurang pengalaman, persaingan tinggi, dan keterbatasan lapangan kerja dengan jalur pengalaman serta pendapatan alternatif.', executionSteps: [ 'Identifikasi UMKM/perusahaan lokal yang bisa menerima magang pendek berbasis proyek dan target output jelas.', 'Bentuk komunitas networking pencari kerja, mentor, pelaku usaha, dan perusahaan lokal.', 'Siapkan proyek mikro: jasa digital, produksi kecil, reseller, atau layanan lokal yang bisa menghasilkan portofolio dan pendapatan awal.', 'Berikan pendampingan dasar keuangan, pemasaran, dan layanan pelanggan untuk peserta yang memilih wirausaha.', 'Dokumentasikan pengalaman peserta menjadi portofolio agar tetap berguna untuk melamar kerja formal.', ], kpis: [ 'Jumlah slot magang aktif', 'Jumlah proyek portofolio selesai', 'Pendapatan awal dari usaha mikro', 'Jumlah koneksi profesional baru', ], risks: [ 'Magang tidak berkualitas; mitigasi: tetapkan output, mentor, dan evaluasi mingguan.', 'Usaha mikro tidak berlanjut; mitigasi: mulai kecil, validasi permintaan, dan batasi modal awal.', ], resources: [ 'Mitra UMKM/perusahaan', 'Mentor bisnis/karier', 'Modul wirausaha mikro', 'Komunitas alumni/pencari kerja', ], type: 'hr', impact: 82, efficiency: 76, speed: 84, lowRisk: 74, duration: 21, rootCauseId: causes[3]?.id || causes[0]?.id, }, ], [ { focus: 'peningkatan skill, pelatihan, sertifikasi, dan kesiapan industri', keywords: [ 'skill', 'keterampilan', 'kompetensi', 'sertifikasi', 'pelatihan', 'pendidikan', 'industri', 'otomatisasi', 'digital', ], }, { focus: 'akses informasi lowongan, CV, lamaran, interview, dan job matching', keywords: [ 'lowongan', 'informasi', 'cv', 'lamaran', 'interview', 'wawancara', 'job matching', 'pencarian kerja', 'pencari kerja', 'lokasi', ], }, { focus: 'pengalaman kerja, magang, networking, portofolio, atau wirausaha', keywords: [ 'pengalaman', 'magang', 'networking', 'jaringan', 'wirausaha', 'usaha', 'umkm', 'lapangan kerja', 'modal', 'portofolio', ], }, ]); } if (knowledge.key === 'keluarga_hukum_waris') { return withQuestionFit([ { title: 'Konsultasi hukum keluarga dan waris terpadu', description: 'Memisahkan isu perselingkuhan, perceraian, hak anak, harta bersama, dan warisan agar keluarga punya langkah hukum yang aman dan tidak merugikan 10 anak.', rationale: `Diprioritaskan karena penyebab utama adalah ${topCauses}. Hipotesis yang diuji: ${topHypotheses}. Paket solusi dasar: ${allSolutionOptions}. Rekomendasi ini bersifat arahan awal dan perlu divalidasi dengan advokat, pos bantuan hukum, mediator, notaris, atau pejabat berwenang.`, executionSteps: [ 'Tulis kronologi singkat kejadian perselingkuhan, konflik rumah tangga, proses cerai, isu warisan, dan posisi 10 anak tanpa menyebarkan tuduhan ke pihak luar.', 'Kumpulkan dokumen inti: buku nikah/akta cerai bila ada, kartu keluarga, akta kelahiran anak, identitas pihak terkait, sertifikat aset, rekening, surat utang, dan dokumen waris.', 'Pisahkan masalah menjadi 4 jalur: status perkawinan/perceraian, hak asuh dan nafkah anak, harta bersama, serta warisan/ahli waris.', 'Buat daftar ahli waris, aset, utang, dan pihak yang menguasai aset saat ini agar pembahasan tidak bercampur dengan emosi konflik.', 'Jadwalkan konsultasi dengan advokat/pos bantuan hukum/mediator keluarga dan bawa dokumen serta pertanyaan tertulis.', 'Tentukan langkah aman berikutnya: mediasi tertulis, perlindungan hak anak sementara, pengamanan dokumen, atau jalur pengadilan bila damai tidak memungkinkan.', ], kpis: [ 'Kronologi dan dokumen inti terkumpul', 'Daftar aset, utang, ahli waris, dan kebutuhan 10 anak tersusun', 'Jadwal konsultasi hukum atau mediasi ditetapkan', 'Rencana sementara hak asuh, nafkah, sekolah, kesehatan, dan tempat tinggal anak tersedia', ], risks: [ 'Konflik keluarga memanas; mitigasi: gunakan mediator netral dan hindari keputusan sepihak saat emosi tinggi.', 'Dokumen atau aset hilang/dialihkan; mitigasi: buat salinan dokumen, catat penguasaan aset, dan konsultasikan langkah pengamanan hukum.', 'Hak anak terabaikan; mitigasi: dahulukan biaya hidup, sekolah, kesehatan, dan tempat tinggal anak sebelum sengketa harta diperluas.', ], resources: [ 'Dokumen keluarga dan aset', 'Advokat atau pos bantuan hukum', 'Mediator keluarga', 'Notaris/PPAT bila menyangkut aset', 'Pendamping psikososial anak', ], type: 'other', impact: 94 + urgency, efficiency: 86, speed: 82, lowRisk: 78, duration: 7, rootCauseId: causes[0]?.id, }, { title: 'Mediasi keluarga dan rencana perlindungan anak', description: 'Menurunkan eskalasi konflik dengan forum mediasi yang fokus pada keselamatan, kebutuhan, hak asuh, nafkah, dan stabilitas 10 anak.', rationale: 'Cocok saat konflik rumah tangga dan perceraian mulai memengaruhi anak-anak; mediasi membantu membuat kesepakatan sementara sebelum proses hukum berjalan lebih jauh.', executionSteps: [ 'Tentukan pihak yang wajib hadir dan pihak netral yang dipercaya, seperti mediator keluarga, tokoh yang disepakati, konselor, atau pendamping hukum.', 'Buat agenda mediasi terbatas: kebutuhan anak, jadwal pengasuhan, nafkah sementara, akses sekolah/kesehatan, dan larangan mengalihkan aset tanpa persetujuan.', 'Pisahkan pembahasan dugaan perselingkuhan dari pembahasan kebutuhan anak agar anak tidak menjadi alat tekanan konflik orang tua.', 'Tuliskan hasil mediasi sementara secara jelas: siapa membayar apa, kapan, bukti pembayaran, akses komunikasi anak, dan tanggal review.', 'Jika mediasi gagal atau ada ancaman/kekerasan, eskalasi ke pendamping hukum atau lembaga perlindungan yang berwenang.', ], kpis: [ 'Agenda mediasi dan pihak netral disepakati', 'Kesepakatan sementara nafkah dan pengasuhan anak tertulis', 'Kebutuhan sekolah, kesehatan, dan tempat tinggal anak terpetakan', 'Risiko konflik atau ancaman berkurang', ], risks: [ 'Salah satu pihak menolak hadir; mitigasi: minta mediator mengirim undangan tertulis dan siapkan opsi konsultasi hukum.', 'Anak terseret konflik; mitigasi: batasi pembicaraan orang dewasa dan libatkan pendamping anak bila perlu.', 'Kesepakatan tidak dipatuhi; mitigasi: dokumentasikan pelanggaran dan konsultasikan penguatan formalnya.', ], resources: [ 'Mediator keluarga', 'Daftar kebutuhan anak', 'Catatan komunikasi para pihak', 'Pendamping hukum atau konselor', ], type: 'other', impact: 90, efficiency: 84, speed: 88, lowRisk: 82, duration: 5, rootCauseId: causes[1]?.id || causes[0]?.id, }, { title: 'Inventarisasi harta bersama, warisan, dan ahli waris', description: 'Membuat peta aset, utang, status kepemilikan, ahli waris, dan dokumen pendukung sebelum ada pembagian atau keputusan atas warisan.', rationale: 'Cocok ketika perceraian dan warisan bercampur; inventarisasi mencegah aset diperebutkan tanpa data dan membantu menentukan isu mana yang perlu notaris, mediator, atau pengadilan.', executionSteps: [ 'Buat tabel aset: tanah, rumah, kendaraan, tabungan, usaha, barang berharga, piutang, dan utang beserta bukti dokumennya.', 'Tandai status setiap aset: milik pribadi, harta bersama, harta warisan, masih sengketa, atau belum jelas.', 'Susun daftar calon ahli waris dan hubungan keluarga berdasarkan dokumen resmi, lalu catat pihak yang masih perlu diverifikasi.', 'Amankan salinan dokumen dan bukti penguasaan aset agar tidak hilang ketika konflik meningkat.', 'Konsultasikan tabel aset dan ahli waris ke notaris/PPAT atau advokat agar langkah pembagian tidak salah prosedur.', ], kpis: [ 'Tabel aset dan utang selesai', 'Status harta bersama dan warisan dipisahkan', 'Daftar ahli waris awal tersusun', 'Dokumen aset tersalin dan aman', ], risks: [ 'Data aset disembunyikan; mitigasi: kumpulkan bukti dari dokumen resmi, saksi, catatan transaksi, dan konsultasi hukum.', 'Pembagian dilakukan sebelum status jelas; mitigasi: tahan keputusan pembagian sampai ada validasi pihak berwenang.', 'Utang tidak dihitung; mitigasi: masukkan semua kewajiban sebelum menghitung bagian bersih.', ], resources: [ 'Sertifikat dan bukti aset', 'Dokumen ahli waris', 'Catatan utang/piutang', 'Notaris/PPAT atau advokat', ], type: 'other', impact: 86, efficiency: 82, speed: 76, lowRisk: 86, duration: 10, rootCauseId: causes[2]?.id || causes[0]?.id, }, ], [ { focus: 'perselingkuhan, perceraian, warisan, hak anak, harta bersama, dan perlindungan 10 anak', keywords: [ 'selingkuh', 'perselingkuhan', 'cerai', 'perceraian', 'warisan', 'waris', '10 anak', 'anak', 'hak anak', 'hak asuh', 'nafkah', 'nafkah anak', 'harta bersama', 'harta gono gini', 'gono gini', 'gono-gini', 'ahli waris', ], }, { focus: 'mediasi keluarga, konflik rumah tangga, pengasuhan, nafkah, dan stabilitas anak', keywords: [ 'mediasi', 'konflik', 'keluarga', 'rumah tangga', 'suami', 'istri', 'anak', 'hak asuh', 'nafkah', 'damai', 'konselor', ], }, { focus: 'inventarisasi aset, utang, harta warisan, ahli waris, sertifikat, dan prosedur notaris/pengadilan', keywords: [ 'aset', 'harta', 'warisan', 'waris', 'ahli waris', 'sertifikat', 'tanah', 'rumah', 'utang', 'notaris', 'ppat', 'pengadilan', ], }, ]); } return withQuestionFit([ { title: 'Pendampingan UMKM, modal mikro, dan digitalisasi usaha', description: 'Menaikkan pendapatan keluarga melalui akses modal produktif, pendampingan usaha, pencatatan keuangan, dan kanal penjualan digital.', rationale: `Diprioritaskan karena penyebab utama adalah ${topCauses}. Hipotesis yang diuji: ${topHypotheses}. Paket solusi dasar: ${allSolutionOptions}.`, executionSteps: [ 'Identifikasi keluarga/UMKM sasaran berdasarkan pendapatan, aset produktif, jenis usaha, dan kesiapan menjalankan usaha.', 'Tentukan kebutuhan modal produktif yang spesifik: stok, alat kerja, bahan baku, atau akses distribusi, bukan bantuan konsumtif umum.', 'Berikan kredit mikro/bantuan modal bertahap dengan pendampingan pencatatan cash-in, cash-out, margin, dan stok.', 'Digitalisasikan usaha secara sederhana: katalog WhatsApp, marketplace lokal, pembayaran digital, dan promosi komunitas.', 'Pasangkan UMKM dengan mentor dan kemitraan penjualan agar modal berubah menjadi omzet, bukan hanya tambahan utang.', 'Evaluasi setiap 2 minggu: omzet, margin, repeat order, arus kas, dan kemampuan membayar kembali modal.', ], kpis: [ 'Kenaikan pendapatan bersih keluarga', 'Jumlah UMKM aktif setelah pendampingan', 'Rasio modal menjadi omzet', 'Jumlah transaksi digital/kemitraan penjualan', ], risks: [ 'Modal dipakai untuk konsumsi; mitigasi: salurkan bertahap berdasarkan rencana usaha dan bukti pembelian produktif.', 'Usaha tidak punya pasar; mitigasi: validasi pembeli sebelum modal besar diberikan.', 'Pencatatan keuangan tidak disiplin; mitigasi: gunakan format harian sangat sederhana dan review mingguan.', ], resources: [ 'Dana/kredit mikro', 'Mentor UMKM', 'Template pencatatan keuangan', 'Kanal penjualan digital', ], type: 'finance', impact: 94 + urgency, efficiency: 84, speed: 76, lowRisk: 76, duration: 45, rootCauseId: causes[3]?.id || causes[0]?.id, }, { title: 'Pelatihan kerja, pendidikan vokasi, dan program padat karya', description: 'Menghubungkan keluarga berpendapatan rendah dengan keterampilan kerja cepat, pekerjaan sementara, dan jalur vokasi menuju pekerjaan lebih stabil.', rationale: 'Menjawab pengangguran, pendidikan rendah, keterampilan terbatas, dan pendapatan tidak stabil dengan kombinasi income cepat dan skill jangka menengah.', executionSteps: [ 'Pilih skill vokasi yang cepat diserap pasar lokal: konstruksi ringan, kuliner, perawatan, administrasi, digital dasar, atau logistik.', 'Jalankan program padat karya untuk pendapatan cepat sambil peserta mengikuti pelatihan terjadwal.', 'Hubungkan peserta dengan sertifikasi sederhana, magang, atau kontrak kerja lokal setelah pelatihan.', 'Sediakan dukungan dasar seperti transportasi, jadwal fleksibel, atau childcare bila menjadi penghalang partisipasi.', 'Monitor transisi dari padat karya ke kerja tetap/usaha produktif agar bantuan tidak berhenti sebagai program sementara.', ], kpis: [ 'Jumlah peserta mendapat pendapatan cepat', 'Jumlah peserta menyelesaikan vokasi', 'Jumlah penempatan kerja/magang', 'Kenaikan pendapatan 30–90 hari', ], risks: [ 'Program padat karya berhenti tanpa transisi; mitigasi: sejak awal pasangkan dengan pelatihan dan job matching.', 'Peserta tidak bisa hadir konsisten; mitigasi: jadwal fleksibel dan dukungan transportasi/keluarga.', ], resources: [ 'Mitra pelatihan vokasi', 'Anggaran padat karya', 'Instruktur/mentor', 'Mitra penempatan kerja', ], type: 'hr', impact: 90, efficiency: 80, speed: 86, lowRisk: 78, duration: 30, rootCauseId: causes[1]?.id || causes[0]?.id, }, { title: 'Kemitraan usaha dan peningkatan produktivitas komunitas', description: 'Membangun akses ekonomi melalui kemitraan pemasok, koperasi/kelompok usaha, peningkatan produktivitas, dan akses layanan pendukung.', rationale: 'Cocok untuk wilayah dengan akses layanan terbatas, produktivitas rendah, dan ketimpangan ekonomi yang tidak selesai hanya dengan bantuan tunai.', executionSteps: [ 'Petakan komoditas, jasa, atau keterampilan lokal yang bisa dikembangkan sebagai produk komunitas.', 'Bentuk kelompok usaha/koperasi kecil untuk pembelian bahan baku, produksi, pemasaran, dan negosiasi harga bersama.', 'Cari kemitraan dengan off-taker, toko lokal, BUMDes, komunitas, atau marketplace agar ada permintaan yang jelas.', 'Perbaiki produktivitas melalui alat sederhana, standar kualitas, jadwal produksi, dan pembagian peran.', 'Hubungkan keluarga miskin dengan layanan pendukung: kesehatan dasar, administrasi, pendidikan, dan akses modal lanjutan.', ], kpis: [ 'Jumlah kemitraan/off-taker aktif', 'Produktivitas per kelompok usaha', 'Jumlah keluarga mendapat akses layanan pendukung', 'Kenaikan margin/pendapatan komunitas', ], risks: [ 'Kelompok usaha konflik internal; mitigasi: aturan peran, pembukuan transparan, dan fasilitator netral.', 'Permintaan pasar tidak stabil; mitigasi: mulai dengan kontrak kecil/off-taker yang sudah teridentifikasi.', ], resources: [ 'Fasilitator komunitas', 'Mitra off-taker', 'Alat produktivitas sederhana', 'Akses layanan sosial/ekonomi', ], type: 'operations', impact: 84, efficiency: 78, speed: 70, lowRisk: 82, duration: 60, rootCauseId: causes[2]?.id || causes[0]?.id, }, ], [ { focus: 'modal usaha, kredit mikro, UMKM, dan digitalisasi pendapatan', keywords: [ 'modal', 'modal usaha', 'kredit mikro', 'bantuan modal', 'umkm', 'usaha', 'pendapatan', 'ekonomi keluarga', 'digitalisasi', 'produktif', ], }, { focus: 'pelatihan kerja, pendidikan vokasi, pekerjaan cepat, dan padat karya', keywords: [ 'pelatihan', 'kerja', 'pendidikan', 'vokasi', 'padat karya', 'skill', 'keterampilan', 'pengangguran', 'pekerjaan', 'pendapatan tidak stabil', ], }, { focus: 'kemitraan usaha, produktivitas komunitas, akses layanan, dan pasar', keywords: [ 'kemitraan', 'produktifitas', 'produktivitas', 'komunitas', 'koperasi', 'pasar', 'akses layanan', 'ketimpangan', 'wilayah tertinggal', 'aset produktif', ], }, ]); }; const buildSolutions = ( problem: string, urgency: number, causes: Cause[], ): Solution[] => { const matchedKnowledge = findProblemKnowledge(problem); if (matchedKnowledge) { return finalizeSolutions( buildKnowledgeSolutionDrafts(problem, matchedKnowledge, urgency, causes), causes, ); } const text = problem.toLowerCase(); const isSales = Boolean( text.match(/jual|sales|omzet|penjualan|conversion|revenue/), ); const isOps = Boolean( text.match(/operasi|stok|logistik|produksi|delivery|proses/), ); const isCost = Boolean(text.match(/biaya|cash|margin|modal|cost|budget/)); const raw = [ { title: isSales ? 'Optimasi funnel penjualan 7 hari' : 'Sprint validasi peluang tertinggi', description: 'Audit cepat data, segmentasi pelanggan, perbaiki pesan utama, lalu jalankan eksperimen kecil dengan metrik harian.', rationale: isSales ? 'Dipilih karena masalah penjualan biasanya paling cepat diperbaiki dengan memperjelas segmen, memperketat follow-up, dan mengukur conversion rate harian.' : 'Dipilih karena membantu tim menguji peluang dengan biaya kecil sebelum mengambil keputusan yang lebih besar.', executionSteps: [ 'Tarik data 30–90 hari terakhir: sumber lead, penawaran, conversion, nilai transaksi, dan alasan gagal closing.', 'Kelompokkan pelanggan/lead berdasarkan segmen paling bernilai, lalu pilih satu segmen prioritas untuk eksperimen cepat.', 'Perbaiki value proposition, CTA, skrip follow-up, dan paket penawaran agar hambatan utama langsung dijawab.', 'Jalankan eksperimen 7 hari dengan dashboard harian untuk melihat lead masuk, conversion, biaya, dan omzet tambahan.', ], kpis: [ 'Conversion rate harian', 'Cost per qualified lead', 'Omzet tambahan', 'Jumlah follow-up selesai', ], risks: [ 'Data penjualan tidak lengkap; mitigasi: gunakan baseline sederhana dan catat manual selama 7 hari.', 'Tim terlalu banyak mencoba kanal; mitigasi: fokus hanya pada 1–2 kanal utama dulu.', ], resources: [ '1 PIC sales/marketing', 'Data transaksi dan lead', 'Template pesan/skrip follow-up', 'Budget eksperimen kecil', ], type: isSales ? 'sales' : 'operations', impact: 88 + urgency, efficiency: 76, speed: 90, lowRisk: 72, duration: 7, rootCauseId: causes[0]?.id, }, { title: isCost ? 'Kontrol biaya dan prioritas cash-flow' : 'Program reseller/kemitraan mikro', description: isCost ? 'Pisahkan biaya wajib dan variabel, hentikan pengeluaran rendah ROI, lalu alihkan anggaran ke kanal yang terukur.' : 'Bangun penawaran sederhana untuk partner, siapkan materi jual, dan ukur performa tiap calon kanal baru.', rationale: isCost ? 'Dipilih untuk menjaga ruang napas bisnis: arus kas diamankan lebih dulu, lalu biaya dialihkan ke aktivitas yang dampaknya bisa dibuktikan.' : 'Dipilih karena kemitraan kecil bisa memperluas distribusi tanpa menambah beban fixed cost besar di awal.', executionSteps: isCost ? [ 'Klasifikasikan biaya menjadi wajib, pendukung pertumbuhan, eksperimen, dan biaya yang bisa dihentikan sementara.', 'Tentukan batas biaya mingguan dan daftar pengeluaran yang harus disetujui sebelum berjalan.', 'Alihkan budget dari aktivitas rendah ROI ke kanal/produk yang memiliki bukti penjualan terbaik.', 'Review cash-in dan cash-out setiap 3 hari untuk memastikan keputusan biaya langsung terlihat efeknya.', ] : [ 'Definisikan profil partner ideal: reseller, komunitas, kreator, atau bisnis komplementer dengan audiens relevan.', 'Siapkan paket kemitraan: margin, materi promosi, contoh pesan, aturan komisi, dan target minimum.', 'Rekrut 5–10 partner mikro, mulai dari jaringan terdekat yang sudah dipercaya pelanggan.', 'Bandingkan performa partner berdasarkan leads, transaksi, repeat order, dan biaya insentif.', ], kpis: isCost ? [ 'Cash runway', 'Biaya rendah ROI yang dihentikan', 'Margin kontribusi', 'ROI kanal prioritas', ] : [ 'Jumlah partner aktif', 'Lead dari partner', 'Penjualan per partner', 'Biaya komisi per transaksi', ], risks: isCost ? [ 'Pemotongan biaya mengganggu kualitas layanan; mitigasi: jangan hentikan biaya yang langsung menjaga pengalaman pelanggan.', 'Tim kehilangan momentum growth; mitigasi: sisakan budget eksperimen kecil yang terukur.', ] : [ 'Partner tidak aktif setelah onboarding; mitigasi: berikan target 7 hari dan materi siap pakai.', 'Komisi terlalu besar menekan margin; mitigasi: batasi promo pada produk/margin tertentu.', ], resources: isCost ? [ 'Laporan biaya', 'Data margin produk', 'PIC finance/owner', 'Dashboard cash-flow sederhana', ] : [ 'Materi penawaran', 'Kode/referral partner', 'PIC partner relations', 'Template pelaporan mingguan', ], type: isCost ? 'finance' : 'marketing', impact: 84, efficiency: 84, speed: 68, lowRisk: 80, duration: 14, rootCauseId: causes[1]?.id, }, { title: isOps ? 'Perbaikan proses operasional kritis' : 'Promo bundling taktis berbasis data', description: isOps ? 'Petakan bottleneck, tetapkan SLA harian, dan buat ritme monitoring singkat agar hambatan terlihat lebih cepat.' : 'Uji bundling dengan batas waktu, stok, dan target margin yang jelas agar efek terhadap omzet dan profit bisa dibandingkan.', rationale: isOps ? 'Dipilih karena bottleneck operasional sering membuat biaya naik, pelanggan kecewa, dan peluang penjualan hilang meski demand ada.' : 'Dipilih untuk menghasilkan sinyal pasar cepat tanpa mengubah produk inti; cocok saat bisnis butuh dorongan omzet jangka pendek.', executionSteps: isOps ? [ 'Petakan proses dari permintaan masuk sampai delivery/penyelesaian, lalu tandai titik tunggu dan rework terbesar.', 'Tetapkan SLA harian untuk titik kritis dan buat papan monitoring sederhana yang terlihat oleh semua PIC.', 'Hilangkan satu hambatan terbesar terlebih dahulu melalui perubahan SOP, pembagian tugas, atau checklist kualitas.', 'Lakukan review singkat setiap akhir hari untuk memutuskan lanjut, koreksi, atau eskalasi.', ] : [ 'Pilih produk/jasa dengan margin sehat dan produk pelengkap yang menaikkan nilai transaksi.', 'Buat 2 variasi bundling: satu fokus volume cepat dan satu fokus margin/upsell.', 'Jalankan promo terbatas 5 hari dengan batas stok, batas waktu, dan pesan manfaat yang jelas.', 'Bandingkan hasil bundling dengan penjualan normal dari sisi omzet, margin, dan repeat interest.', ], kpis: isOps ? [ 'Lead time proses', 'Jumlah bottleneck terselesaikan', 'SLA harian tercapai', 'Keluhan pelanggan', ] : [ 'Average order value', 'Margin per bundle', 'Jumlah transaksi promo', 'Repeat inquiry', ], risks: isOps ? [ 'SOP baru tidak dipakai konsisten; mitigasi: jadikan checklist harian dan tunjuk satu owner.', 'Perubahan terlalu banyak sekaligus; mitigasi: pilih satu bottleneck paling kritis dulu.', ] : [ 'Diskon menurunkan persepsi nilai; mitigasi: tekankan bonus/manfaat, bukan sekadar potongan harga.', 'Stok tidak cukup; mitigasi: tetapkan kuota dan alternatif produk.', ], resources: isOps ? [ 'Peta proses', 'Checklist SLA', 'PIC operasional', 'Data komplain/lead time', ] : [ 'Data produk dan margin', 'Materi promo', 'Stok/kapasitas layanan', 'Kanal komunikasi pelanggan', ], type: isOps ? 'operations' : 'sales', impact: 76, efficiency: 69, speed: 95, lowRisk: 88, duration: 5, rootCauseId: causes[2]?.id, }, ] as SolutionDraft[]; const genericFits: QuestionSolutionFit[] = [ { focus: isSales ? 'penjualan, omzet, funnel, pelanggan, conversion, dan revenue' : 'validasi peluang atau keputusan paling penting dari masalah pengguna', keywords: isSales ? [ 'jual', 'sales', 'omzet', 'penjualan', 'conversion', 'revenue', 'pelanggan', 'lead', 'toko', 'promosi', ] : [ 'validasi', 'peluang', 'target', 'strategi', 'keputusan', 'prioritas', 'masalah', ], }, { focus: isCost ? 'biaya, modal, budget, margin, cash-flow, dan kondisi keuangan' : 'kemitraan, reseller, partner, distribusi, dan kanal baru', keywords: isCost ? [ 'biaya', 'cash', 'cash-flow', 'arus kas', 'margin', 'modal', 'cost', 'budget', 'uang', 'keuangan', ] : [ 'reseller', 'kemitraan', 'partner', 'distribusi', 'kanal', 'channel', 'komunitas', 'affiliate', ], }, { focus: isOps ? 'operasi, stok, logistik, produksi, delivery, SOP, dan bottleneck proses' : 'promo, bundling, penawaran, paket, dan dorongan transaksi cepat', keywords: isOps ? [ 'operasi', 'stok', 'stock', 'logistik', 'produksi', 'delivery', 'pengiriman', 'proses', 'sop', 'bottleneck', ] : [ 'promo', 'bundling', 'diskon', 'penawaran', 'paket', 'transaksi', 'stok', 'jualan', ], }, ]; return finalizeSolutions(applyQuestionFitToDrafts(raw, genericFits, problem), causes); }; const buildTasks = (solution: Solution): PlanTask[] => { const steps = solution.executionSteps || []; const kpis = solution.kpis || []; const risks = solution.risks || []; const resources = solution.resources || []; const firstStep = trimSentenceEnd(steps[0] || solution.description); const secondStep = trimSentenceEnd( steps[1] || steps[0] || solution.description, ); const mainRisk = trimSentenceEnd( risks[0] || 'risiko eksekusi yang muncul selama uji coba', ); const isLegalFamilyCase = Boolean( `${solution.title} ${solution.description}`.match( /hukum|waris|cerai|perceraian|hak asuh|nafkah|mediasi keluarga|anak/i, ), ); if (isLegalFamilyCase) { return [ { id: uuid(), day: 1, title: 'Amankan fakta dan dokumen inti', description: `Kumpulkan fakta dan dokumen awal untuk solusi “${solution.title}”: ${resources.slice(0, 3).join(', ') || 'dokumen keluarga, dokumen aset, dan daftar pihak terkait'}.`, }, { id: uuid(), day: 2, title: 'Pisahkan isu hukum utama', description: `Turunkan kasus menjadi langkah hukum pertama yang jelas: ${firstStep}. Pisahkan isu perceraian, warisan, harta, dan hak anak agar tidak bercampur.`, }, { id: uuid(), day: 3, title: 'Mulai konsultasi atau mediasi', description: `Jalankan langkah awal yang aman: ${secondStep}. Catat posisi tiap pihak, dokumen yang kurang, dan risiko untuk anak.`, }, { id: uuid(), day: 4, title: 'Cek perlindungan anak dan aset', description: `Bandingkan progress dengan indikator utama: ${kpis.slice(0, 3).join(', ') || 'dokumen, mediasi, perlindungan anak, dan status aset'}. Koreksi jika ada hak anak atau aset yang belum terlindungi.`, }, { id: uuid(), day: 5, title: 'Putuskan jalur damai atau hukum formal', description: `Pilih langkah lanjut berdasarkan dokumen, hasil konsultasi/mediasi, dan risiko utama: ${mainRisk}.`, }, ]; } return [ { id: uuid(), day: 1, title: 'Audit fakta dan baseline', description: `Kumpulkan data awal yang langsung terkait solusi “${solution.title}”: ${resources.slice(0, 3).join(', ') || 'data performa, PIC, dan baseline masalah'}.`, }, { id: uuid(), day: 2, title: 'Susun eksperimen solusi sesuai pertanyaan', description: `Turunkan strategi “${solution.title}” menjadi langkah pertama yang spesifik: ${firstStep}.`, }, { id: uuid(), day: 3, title: 'Eksekusi batch pertama', description: `Jalankan batch kecil sesuai langkah implementasi: ${secondStep}. Catat hasil dan hambatan.`, }, { id: uuid(), day: 4, title: 'Monitoring dan koreksi cepat', description: `Bandingkan hasil dengan KPI utama: ${kpis.slice(0, 3).join(', ') || 'progress, dampak, dan risiko'}. Koreksi aktivitas yang tidak menjawab masalah.`, }, { id: uuid(), day: 5, title: 'Review keputusan lanjut', description: `Putuskan scale, iterate, atau stop berdasarkan KPI dan risiko utama: ${mainRisk}.`, }, ]; }; const buildConclusion = ( selectedSolution: Solution, priority: Run['priority'], priorityScore: number, causes: Cause[], dataSources: DataSource[], knowledge?: ProblemKnowledge, ) => { const mainCause = causes[0]?.label || 'akar masalah utama belum tervalidasi'; const evidenceText = dataSources.length > 0 ? `berdasarkan ${Math.min(dataSources.length, MAX_DATA_SOURCES)} sumber data ringkas yang tersedia` : 'berdasarkan sinyal awal yang tersedia'; const knowledgeConclusion = knowledge ? ` Knowledge base ${knowledge.label} memiliki severity ${knowledge.severity} dan kesimpulan domain: ${knowledge.kesimpulan}` : ''; if (knowledge?.key === 'keluarga_hukum_waris') { return `Kesimpulan: kasus ini berada pada prioritas ${priority} (${priorityScore}/100) dan perlu ditangani dengan pendampingan hukum/mediasi bertahap, bukan keputusan emosional atau sepihak. ${evidenceText}, akar masalah paling dominan adalah ${mainCause.toLowerCase()}.${knowledgeConclusion} Rekomendasi utama adalah “${selectedSolution.title}” karena memiliki Decision Score ${selectedSolution.score}/100, estimasi success rate ${selectedSolution.success}%, dan dapat mulai dieksekusi dalam ${selectedSolution.duration} hari. Fokus 5 hari pertama adalah mengamankan dokumen, memetakan perceraian/warisan/harta/hak anak, konsultasi dengan pihak profesional, lalu memilih jalur damai tertulis atau hukum formal.`; } return `Kesimpulan: kasus ini berada pada prioritas ${priority} (${priorityScore}/100) dan perlu ditangani dengan eksperimen terukur, bukan keputusan besar tanpa validasi. ${evidenceText}, akar masalah paling dominan adalah ${mainCause.toLowerCase()}.${knowledgeConclusion} Rekomendasi utama adalah “${selectedSolution.title}” karena memiliki Decision Score ${selectedSolution.score}/100, estimasi success rate ${selectedSolution.success}%, dan dapat mulai dieksekusi dalam ${selectedSolution.duration} hari. Fokus 5 hari pertama adalah mengunci baseline, menjalankan eksperimen kecil, mengukur KPI harian, lalu memutuskan scale, iterate, atau stop.`; }; const createRun = ( problem: string, urgency: number, financialImpact: Run['financialImpact'], ): Run => { const matchedKnowledge = findProblemKnowledge(problem); const causes = inferCauses(problem, urgency, financialImpact); const dataSources = buildDataSources( problem, urgency, financialImpact, causes, ); const solutions = buildSolutions(problem, urgency, causes); const selectedSolution = solutions[0]; const impactBoost = financialImpact === 'high' ? 18 : financialImpact === 'medium' ? 10 : financialImpact === 'low' ? 3 : 6; const knowledgeBoost = matchedKnowledge ? matchedKnowledge.severity === 'Kritis' ? 10 : 6 : 0; const priorityScore = clamp( 52 + urgency * 7 + impactBoost + knowledgeBoost + Math.min(problem.length / 18, 8), ); const priority: Run['priority'] = priorityScore >= 88 ? 'critical' : priorityScore >= 76 ? 'high' : priorityScore >= 58 ? 'medium' : 'low'; const conclusion = buildConclusion( selectedSolution, priority, priorityScore, causes, dataSources, matchedKnowledge, ); return { id: uuid(), caseId: uuid(), planId: uuid(), createdAt: new Date().toISOString(), title: titleFromProblem(problem), problem, urgency, financialImpact, priority, priorityScore, causes, dataSources, solutions, selectedSolution, tasks: buildTasks(selectedSolution), conclusion, }; }; const saveRunToBackend = async (run: Run, currentUserId?: string) => { await axios.post('cases', { data: { id: run.caseId, title: run.title, problem_statement: run.problem, status: 'ready', priority: run.priority, urgency_level: run.urgency, priority_score: run.priorityScore, financial_impact_level: run.financialImpact, currency: 'IDR', opened_at: run.createdAt, notes: [ 'Generated from OPTEMA AI Decision Lab MVP workflow.', run.conclusion, `Sumber data: ${(run.dataSources || []) .slice(0, MAX_DATA_SOURCES) .map((source) => source.title) .join(', ')}`, ].join('\n'), created_by_user: currentUserId || null, }, }); await Promise.all( run.causes.map((cause) => axios.post('root_cause_nodes', { data: { id: cause.id, label: cause.label, description: cause.description, analysis_method: 'mixed', category: cause.category, depth_level: 1, contribution_score: cause.contribution, validated: false, case: run.caseId, }, }), ), ); await Promise.all( run.solutions.map((solution) => axios.post('solutions', { data: { id: solution.id, title: solution.title, description: [ solution.description, `Alasan: ${solution.rationale || '-'}`, `Langkah utama: ${(solution.executionSteps || []).join(' | ')}`, `KPI: ${(solution.kpis || []).join(', ')}`, `Risiko/mitigasi: ${(solution.risks || []).join(' | ')}`, `Resource: ${(solution.resources || []).join(', ')}`, ].join('\n'), solution_type: solution.type, impact_score: solution.impact, efficiency_score: solution.efficiency, speed_score: solution.speed, low_risk_score: solution.lowRisk, decision_score: solution.score, success_probability: solution.success, status: solution.id === run.selectedSolution.id ? 'selected' : 'proposed', expected_duration_days: solution.duration, case: run.caseId, linked_root_cause: solution.rootCauseId, }, }), ), ); await axios.post('action_plans', { data: { id: run.planId, title: `Rencana aksi: ${run.selectedSolution.title}`, objective: `Menjalankan solusi terpilih untuk kasus “${run.title}” dengan ritme 5 hari kerja. ${run.conclusion}`, status: 'active', start_at: run.createdAt, budget_currency: 'IDR', solution: run.selectedSolution.id, owner_user: currentUserId || null, }, }); await Promise.all( run.tasks.map((task) => axios.post('action_tasks', { data: { id: task.id, title: task.title, description: task.description, status: task.day === 1 ? 'doing' : 'todo', priority: task.day <= 2 ? 'high' : 'medium', day_number: task.day, estimated_hours: task.day === 1 ? 3 : 2, action_plan: run.planId, }, }), ), ); }; const loadRuns = (): Run[] => { if (typeof window === 'undefined') return []; try { const raw = window.localStorage.getItem(STORAGE_KEY); return raw ? (JSON.parse(raw) as Run[]) : []; } catch (error) { console.error('Failed to load OPTEMA AI local history', error); return []; } }; const persistRuns = (runs: Run[]) => { if (typeof window === 'undefined') return; window.localStorage.setItem(STORAGE_KEY, JSON.stringify(runs.slice(0, 8))); }; const Pill = ({ children, tone = 'blue', }: { children: React.ReactNode; tone?: 'blue' | 'green' | 'orange' | 'slate'; }) => { const tones = { blue: 'bg-blue-50 text-blue-700 ring-blue-200 dark:bg-blue-900/30 dark:text-blue-200', green: 'bg-emerald-50 text-emerald-700 ring-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-200', orange: 'bg-orange-50 text-orange-700 ring-orange-200 dark:bg-orange-900/30 dark:text-orange-200', slate: 'bg-slate-100 text-slate-700 ring-slate-200 dark:bg-slate-800 dark:text-slate-200', }; return ( {children} ); }; const OptemaAiPage = () => { const { currentUser } = useAppSelector((state) => state.auth); const [problem, setProblem] = useState( 'Pengangguran di wilayah saya meningkat karena skill warga tidak sesuai kebutuhan industri dan informasi lowongan sulit diakses.', ); const [urgency, setUrgency] = useState(3); const [financialImpact, setFinancialImpact] = useState('high'); const [runs, setRuns] = useState([]); const [selectedId, setSelectedId] = useState(null); const [isSaving, setIsSaving] = useState(false); const [notice, setNotice] = useState<{ type: 'success' | 'error'; message: string; } | null>(null); useEffect(() => { const stored = loadRuns(); setRuns(stored); setSelectedId(stored[0]?.id || null); }, []); const selectedRun = useMemo( () => runs.find((run) => run.id === selectedId) || runs[0], [runs, selectedId], ); const selectedDataSources = useMemo(() => { if (!selectedRun) return []; const sources = selectedRun.dataSources?.length ? selectedRun.dataSources : buildDataSources( selectedRun.problem, selectedRun.urgency, selectedRun.financialImpact, selectedRun.causes || [], ); return sources.slice(0, MAX_DATA_SOURCES); }, [selectedRun]); const selectedConclusion = useMemo(() => { if (!selectedRun) return ''; return ( selectedRun.conclusion || buildConclusion( selectedRun.selectedSolution, selectedRun.priority, selectedRun.priorityScore, selectedRun.causes || [], selectedDataSources, findProblemKnowledge(selectedRun.problem), ) ); }, [selectedRun, selectedDataSources]); const handleAnalyze = async () => { setNotice(null); if (problem.trim().length < 20) { setNotice({ type: 'error', message: 'Tuliskan masalah minimal 20 karakter agar analisis lebih bermakna.', }); return; } const run = createRun(problem, urgency, financialImpact); setIsSaving(true); try { await saveRunToBackend(run, currentUser?.id); const nextRuns = [run, ...runs].slice(0, 8); setRuns(nextRuns); setSelectedId(run.id); persistRuns(nextRuns); setNotice({ type: 'success', message: 'Analisis berhasil dibuat dan disimpan sebagai Case, Solutions, Root Causes, Action Plan, dan Tasks.', }); } catch (error) { console.error('OPTEMA AI workflow failed', error); setNotice({ type: 'error', message: 'Analisis gagal disimpan. Pastikan akun Anda memiliki izin membuat Cases, Solutions, Root Causes, Action Plans, dan Tasks.', }); } finally { setIsSaving(false); } }; return ( <> {getPageTitle('OPTEMA AI Decision Lab')}

Problem Intake

Ubah masalah jadi keputusan.