Fix: remove some and add some
This commit is contained in:
@@ -6,17 +6,23 @@ import {
|
||||
Download,
|
||||
Eye,
|
||||
Calendar,
|
||||
Clock,
|
||||
Code,
|
||||
Activity,
|
||||
DollarSign,
|
||||
TrendingUp,
|
||||
AlertCircle,
|
||||
Building2,
|
||||
Wand2,
|
||||
ShieldCheck,
|
||||
CheckCircle2,
|
||||
ClipboardList,
|
||||
} from "lucide-react";
|
||||
|
||||
interface DiagnoseCode {
|
||||
id: string;
|
||||
icdCode: string;
|
||||
bpjsCode: string;
|
||||
description: string;
|
||||
category: string;
|
||||
severity: "ringan" | "sedang" | "berat";
|
||||
@@ -26,14 +32,16 @@ interface DiagnoseCode {
|
||||
department: string;
|
||||
}
|
||||
|
||||
interface ProcedureCode {
|
||||
// removed legacy ICP-9 procedure model
|
||||
|
||||
interface ICD9DiagnoseCode {
|
||||
id: string;
|
||||
icp9Code: string;
|
||||
icd9Code: string;
|
||||
bpjsCode: string;
|
||||
description: string;
|
||||
category: string;
|
||||
complexity: "sederhana" | "kompleks" | "sangat_kompleks";
|
||||
severity: "ringan" | "sedang" | "berat";
|
||||
bpjsRate: number;
|
||||
duration: number; // in minutes
|
||||
usageCount: number;
|
||||
lastUsed: string;
|
||||
department: string;
|
||||
@@ -48,12 +56,44 @@ interface CodeUsageStats {
|
||||
averageClaimValue: number;
|
||||
}
|
||||
|
||||
interface AssistInput {
|
||||
clinicalNotes: string;
|
||||
labResults: string;
|
||||
procedures: string;
|
||||
}
|
||||
|
||||
interface RecommendedCode {
|
||||
type: "ICD10" | "ICD9CM";
|
||||
code: string;
|
||||
description: string;
|
||||
confidence: number;
|
||||
rationale: string;
|
||||
}
|
||||
|
||||
interface InaCbgsMapping {
|
||||
group: string;
|
||||
code: string;
|
||||
description: string;
|
||||
estTariff: number;
|
||||
}
|
||||
|
||||
interface AssistResult {
|
||||
preprocessed: AssistInput;
|
||||
recommendedCodes: RecommendedCode[];
|
||||
inaCbgs: InaCbgsMapping | null;
|
||||
references: string[];
|
||||
}
|
||||
|
||||
export default function BPJSCode() {
|
||||
const [activeTab, setActiveTab] = useState<"diagnose" | "procedure">(
|
||||
const [activeTab, setActiveTab] = useState<"diagnose" | "procedure" | "assist">(
|
||||
"diagnose"
|
||||
);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [categoryFilter, setCategoryFilter] = useState("all");
|
||||
const [assistInput, setAssistInput] = useState<AssistInput>({ clinicalNotes: "", labResults: "", procedures: "" });
|
||||
const [assistLoading, setAssistLoading] = useState(false);
|
||||
const [assistError, setAssistError] = useState<string | null>(null);
|
||||
const [assistResult, setAssistResult] = useState<AssistResult | null>(null);
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
@@ -74,6 +114,92 @@ export default function BPJSCode() {
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
const formatDateOnly = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString("id-ID", {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const formatTimeOnly = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleTimeString("id-ID", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
// Mock pipeline: preprocess -> LLM -> RAG -> INA-CBGs mapping
|
||||
const preprocessInput = (input: AssistInput): AssistInput => {
|
||||
const normalize = (s: string) => s.replace(/\s+/g, " ").trim();
|
||||
return {
|
||||
clinicalNotes: normalize(input.clinicalNotes),
|
||||
labResults: normalize(input.labResults),
|
||||
procedures: normalize(input.procedures),
|
||||
};
|
||||
};
|
||||
|
||||
const mockLLMRecommend = async (input: AssistInput): Promise<RecommendedCode[]> => {
|
||||
const text = `${input.clinicalNotes} ${input.labResults} ${input.procedures}`.toLowerCase();
|
||||
const recs: RecommendedCode[] = [];
|
||||
if (text.includes("hipertensi") || text.includes("bp 150/95") || text.includes("tekanan darah")) {
|
||||
recs.push({ type: "ICD10", code: "I10", description: "Essential (primary) hypertension", confidence: 90, rationale: "Temuan tekanan darah tinggi/hipertensi pada catatan klinis." });
|
||||
}
|
||||
if (text.includes("hba1c") || text.includes("diabetes") || text.includes("glukosa puasa")) {
|
||||
recs.push({ type: "ICD10", code: "E11", description: "Type 2 diabetes mellitus", confidence: 86, rationale: "Hasil lab HbA1c/glukosa dan kata kunci diabetes terdeteksi." });
|
||||
}
|
||||
if (text.includes("pneumonia") || text.includes("infiltrat") || text.includes("nyeri dada batuk demam")) {
|
||||
recs.push({ type: "ICD10", code: "J18.9", description: "Pneumonia, unspecified organism", confidence: 80, rationale: "Gambaran klinis/temuan imaging mendukung pneumonia." });
|
||||
}
|
||||
if (text.includes("endoskopi") || text.includes("kateterisasi") || text.includes("fiksasi")) {
|
||||
recs.push({ type: "ICD9CM", code: "45.13", description: "Endoskopi lambung (contoh)", confidence: 72, rationale: "Prosedur terekstrak dari tindakan/operasi." });
|
||||
}
|
||||
if (recs.length === 0) {
|
||||
recs.push({ type: "ICD10", code: "R69", description: "Illness, unspecified", confidence: 55, rationale: "Tidak ada sinyal kuat; butuh klarifikasi klinis." });
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
return recs.sort((a, b) => b.confidence - a.confidence).slice(0, 6);
|
||||
};
|
||||
|
||||
const mockRAGValidate = async (recs: RecommendedCode[]): Promise<{ validated: RecommendedCode[]; references: string[] }> => {
|
||||
// Anggap melakukan pencarian ke referensi nasional (Kemenkes/BPJS)
|
||||
const refs: string[] = recs.map((r) => `Ref:${r.type}:${r.code}`);
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
return { validated: recs, references: refs };
|
||||
};
|
||||
|
||||
const mockMapInaCbgs = async (recs: RecommendedCode[]): Promise<InaCbgsMapping | null> => {
|
||||
// Mapping sederhana contoh saja
|
||||
const hasPneumonia = recs.some((r) => r.code.startsWith("J18"));
|
||||
const hasDM = recs.some((r) => r.code.startsWith("E11"));
|
||||
const mapping: InaCbgsMapping | null = hasPneumonia
|
||||
? { group: "Respiratory", code: "E-4-13-II", description: "Pneumonia", estTariff: 3500000 }
|
||||
: hasDM
|
||||
? { group: "Endocrine", code: "E-1-10-I", description: "Diabetes Mellitus", estTariff: 2100000 }
|
||||
: null;
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
return mapping;
|
||||
};
|
||||
|
||||
const runAssistPipeline = async () => {
|
||||
setAssistLoading(true);
|
||||
setAssistError(null);
|
||||
setAssistResult(null);
|
||||
try {
|
||||
const pre = preprocessInput(assistInput);
|
||||
const llm = await mockLLMRecommend(pre);
|
||||
const rag = await mockRAGValidate(llm);
|
||||
const ina = await mockMapInaCbgs(rag.validated);
|
||||
setAssistResult({ preprocessed: pre, recommendedCodes: rag.validated, inaCbgs: ina, references: rag.references });
|
||||
} catch (e) {
|
||||
setAssistError("Gagal menjalankan Assist Coding. Coba lagi.");
|
||||
} finally {
|
||||
setAssistLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getSeverityColor = (severity: string) => {
|
||||
switch (severity) {
|
||||
case "ringan":
|
||||
@@ -87,24 +213,14 @@ export default function BPJSCode() {
|
||||
}
|
||||
};
|
||||
|
||||
const getComplexityColor = (complexity: string) => {
|
||||
switch (complexity) {
|
||||
case "sederhana":
|
||||
return "bg-green-100 text-green-800";
|
||||
case "kompleks":
|
||||
return "bg-yellow-100 text-yellow-800";
|
||||
case "sangat_kompleks":
|
||||
return "bg-red-100 text-red-800";
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800";
|
||||
}
|
||||
};
|
||||
// removed legacy complexity color helper (ICP-9 procedures)
|
||||
|
||||
// Sample diagnose codes data
|
||||
const [diagnoseCodes] = useState<DiagnoseCode[]>([
|
||||
{
|
||||
id: "1",
|
||||
icdCode: "I10",
|
||||
bpjsCode: "BPJS-I10-01",
|
||||
description: "Hipertensi Esensial",
|
||||
category: "Penyakit Kardiovaskular",
|
||||
severity: "sedang",
|
||||
@@ -116,6 +232,7 @@ export default function BPJSCode() {
|
||||
{
|
||||
id: "2",
|
||||
icdCode: "E11",
|
||||
bpjsCode: "BPJS-E11-02",
|
||||
description: "Diabetes Mellitus Tipe 2",
|
||||
category: "Penyakit Endokrin",
|
||||
severity: "sedang",
|
||||
@@ -127,6 +244,7 @@ export default function BPJSCode() {
|
||||
{
|
||||
id: "3",
|
||||
icdCode: "J18.9",
|
||||
bpjsCode: "BPJS-J18-03",
|
||||
description: "Pneumonia",
|
||||
category: "Penyakit Pernafasan",
|
||||
severity: "berat",
|
||||
@@ -138,6 +256,7 @@ export default function BPJSCode() {
|
||||
{
|
||||
id: "4",
|
||||
icdCode: "K29.1",
|
||||
bpjsCode: "BPJS-K29-04",
|
||||
description: "Gastritis Akut",
|
||||
category: "Penyakit Pencernaan",
|
||||
severity: "ringan",
|
||||
@@ -149,6 +268,7 @@ export default function BPJSCode() {
|
||||
{
|
||||
id: "5",
|
||||
icdCode: "S52.5",
|
||||
bpjsCode: "BPJS-S52-05",
|
||||
description: "Fraktur Radius",
|
||||
category: "Cedera dan Keracunan",
|
||||
severity: "berat",
|
||||
@@ -160,86 +280,86 @@ export default function BPJSCode() {
|
||||
]);
|
||||
|
||||
// Sample procedure codes data
|
||||
const [procedureCodes] = useState<ProcedureCode[]>([
|
||||
const [icd9DiagnoseCodes] = useState<ICD9DiagnoseCode[]>([
|
||||
{
|
||||
id: "1",
|
||||
icp9Code: "99.04",
|
||||
description: "Transfusi Darah",
|
||||
category: "Prosedur Hematologi",
|
||||
complexity: "sederhana",
|
||||
bpjsRate: 450000,
|
||||
duration: 120,
|
||||
usageCount: 89,
|
||||
icd9Code: "250.00",
|
||||
bpjsCode: "BPJS-250-00",
|
||||
description: "Diabetes mellitus tanpa komplikasi",
|
||||
category: "Penyakit Endokrin",
|
||||
severity: "sedang",
|
||||
bpjsRate: 950000,
|
||||
usageCount: 132,
|
||||
lastUsed: "2024-01-15T13:20:00Z",
|
||||
department: "IGD",
|
||||
department: "Poli Endokrin",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
icp9Code: "36.10",
|
||||
description: "Kateterisasi Jantung",
|
||||
category: "Prosedur Kardiovaskular",
|
||||
complexity: "sangat_kompleks",
|
||||
bpjsRate: 8500000,
|
||||
duration: 180,
|
||||
usageCount: 23,
|
||||
icd9Code: "486",
|
||||
bpjsCode: "BPJS-486-00",
|
||||
description: "Pneumonia, organisme tidak spesifik",
|
||||
category: "Penyakit Pernafasan",
|
||||
severity: "berat",
|
||||
bpjsRate: 2700000,
|
||||
usageCount: 58,
|
||||
lastUsed: "2024-01-14T08:45:00Z",
|
||||
department: "Kardiologi",
|
||||
department: "Poli Paru",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
icp9Code: "79.35",
|
||||
description: "Open Reduction Fraktur",
|
||||
category: "Prosedur Ortopedi",
|
||||
complexity: "kompleks",
|
||||
bpjsRate: 5200000,
|
||||
duration: 240,
|
||||
usageCount: 45,
|
||||
icd9Code: "401.9",
|
||||
bpjsCode: "BPJS-401-90",
|
||||
description: "Hipertensi esensial, tidak spesifik",
|
||||
category: "Penyakit Kardiovaskular",
|
||||
severity: "sedang",
|
||||
bpjsRate: 780000,
|
||||
usageCount: 120,
|
||||
lastUsed: "2024-01-13T14:15:00Z",
|
||||
department: "Ortopedi",
|
||||
department: "Poli Dalam",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
icp9Code: "45.13",
|
||||
description: "Endoskopi Lambung",
|
||||
category: "Prosedur Pencernaan",
|
||||
complexity: "kompleks",
|
||||
bpjsRate: 1800000,
|
||||
duration: 45,
|
||||
usageCount: 67,
|
||||
icd9Code: "530.81",
|
||||
bpjsCode: "BPJS-530-81",
|
||||
description: "Penyakit refluks gastroesofageal",
|
||||
category: "Penyakit Pencernaan",
|
||||
severity: "ringan",
|
||||
bpjsRate: 620000,
|
||||
usageCount: 93,
|
||||
lastUsed: "2024-01-12T10:30:00Z",
|
||||
department: "Gastroenterologi",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
icp9Code: "87.44",
|
||||
description: "CT Scan Thorax",
|
||||
category: "Prosedur Radiologi",
|
||||
complexity: "sederhana",
|
||||
bpjsRate: 750000,
|
||||
duration: 30,
|
||||
usageCount: 156,
|
||||
icd9Code: "813.42",
|
||||
bpjsCode: "BPJS-813-42",
|
||||
description: "Fraktur tertutup radius distal",
|
||||
category: "Cedera dan Keracunan",
|
||||
severity: "berat",
|
||||
bpjsRate: 3400000,
|
||||
usageCount: 27,
|
||||
lastUsed: "2024-01-15T16:45:00Z",
|
||||
department: "Radiologi",
|
||||
department: "Ortopedi",
|
||||
},
|
||||
]);
|
||||
|
||||
// Calculate statistics
|
||||
const stats: CodeUsageStats = {
|
||||
totalDiagnoses: diagnoseCodes.length,
|
||||
totalProcedures: procedureCodes.length,
|
||||
totalProcedures: icd9DiagnoseCodes.length,
|
||||
totalRevenue:
|
||||
diagnoseCodes.reduce((sum, d) => sum + d.bpjsRate * d.usageCount, 0) +
|
||||
procedureCodes.reduce((sum, p) => sum + p.bpjsRate * p.usageCount, 0),
|
||||
icd9DiagnoseCodes.reduce((sum, p) => sum + p.bpjsRate * p.usageCount, 0),
|
||||
mostUsedDiagnose:
|
||||
diagnoseCodes.sort((a, b) => b.usageCount - a.usageCount)[0]?.icdCode ||
|
||||
"",
|
||||
mostUsedProcedure:
|
||||
procedureCodes.sort((a, b) => b.usageCount - a.usageCount)[0]?.icp9Code ||
|
||||
icd9DiagnoseCodes.sort((a, b) => b.usageCount - a.usageCount)[0]?.icd9Code ||
|
||||
"",
|
||||
averageClaimValue:
|
||||
(diagnoseCodes.reduce((sum, d) => sum + d.bpjsRate, 0) +
|
||||
procedureCodes.reduce((sum, p) => sum + p.bpjsRate, 0)) /
|
||||
(diagnoseCodes.length + procedureCodes.length),
|
||||
icd9DiagnoseCodes.reduce((sum, p) => sum + p.bpjsRate, 0)) /
|
||||
(diagnoseCodes.length + icd9DiagnoseCodes.length),
|
||||
};
|
||||
|
||||
// Filter data based on search and category
|
||||
@@ -255,9 +375,9 @@ export default function BPJSCode() {
|
||||
return matchesSearch && matchesCategory;
|
||||
});
|
||||
|
||||
const filteredProcedureCodes = procedureCodes.filter((code) => {
|
||||
const filteredICD9DiagnoseCodes = icd9DiagnoseCodes.filter((code) => {
|
||||
const matchesSearch =
|
||||
code.icp9Code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
code.icd9Code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
code.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
code.category.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
|
||||
@@ -389,78 +509,123 @@ export default function BPJSCode() {
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Code className="h-4 w-4" />
|
||||
<span>Kode Prosedur (ICP-9)</span>
|
||||
<span>Kode Diagnosis (ICD-9)</span>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("assist")}
|
||||
className={`py-4 px-1 border-b-2 font-medium text-sm ${
|
||||
activeTab === "assist"
|
||||
? "border-purple-500 text-purple-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Wand2 className="h-4 w-4" />
|
||||
<span>Assist Coding</span>
|
||||
</div>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Filters and Search */}
|
||||
<div className="p-6 border-b border-gray-200">
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between space-y-4 md:space-y-0">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-3 h-4 w-4 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Cari kode, deskripsi, atau kategori..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-9 pr-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-purple-500 focus:border-transparent w-80"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Filter className="h-4 w-4 text-gray-400" />
|
||||
<select
|
||||
value={categoryFilter}
|
||||
onChange={(e) => setCategoryFilter(e.target.value)}
|
||||
className="border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
<option value="all">Semua Kategori</option>
|
||||
{activeTab === "diagnose" ? (
|
||||
<>
|
||||
<option value="Penyakit Kardiovaskular">
|
||||
Penyakit Kardiovaskular
|
||||
</option>
|
||||
<option value="Penyakit Endokrin">
|
||||
Penyakit Endokrin
|
||||
</option>
|
||||
<option value="Penyakit Pernafasan">
|
||||
Penyakit Pernafasan
|
||||
</option>
|
||||
<option value="Penyakit Pencernaan">
|
||||
Penyakit Pencernaan
|
||||
</option>
|
||||
<option value="Cedera dan Keracunan">
|
||||
Cedera dan Keracunan
|
||||
</option>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<option value="Prosedur Hematologi">
|
||||
Prosedur Hematologi
|
||||
</option>
|
||||
<option value="Prosedur Kardiovaskular">
|
||||
Prosedur Kardiovaskular
|
||||
</option>
|
||||
<option value="Prosedur Ortopedi">
|
||||
Prosedur Ortopedi
|
||||
</option>
|
||||
<option value="Prosedur Pencernaan">
|
||||
Prosedur Pencernaan
|
||||
</option>
|
||||
<option value="Prosedur Radiologi">
|
||||
Prosedur Radiologi
|
||||
</option>
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
{/* Filters and Search or Assist Input */}
|
||||
{activeTab !== "assist" ? (
|
||||
<div className="p-6 border-b border-gray-200">
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between space-y-4 md:space-y-0">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-3 h-4 w-4 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Cari kode, deskripsi, atau kategori..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-9 pr-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-purple-500 focus:border-transparent w-80"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Filter className="h-4 w-4 text-gray-400" />
|
||||
<select
|
||||
value={categoryFilter}
|
||||
onChange={(e) => setCategoryFilter(e.target.value)}
|
||||
className="border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
<option value="all">Semua Kategori</option>
|
||||
{activeTab === "diagnose" ? (
|
||||
<>
|
||||
<option value="Penyakit Kardiovaskular">Penyakit Kardiovaskular</option>
|
||||
<option value="Penyakit Endokrin">Penyakit Endokrin</option>
|
||||
<option value="Penyakit Pernafasan">Penyakit Pernafasan</option>
|
||||
<option value="Penyakit Pencernaan">Penyakit Pencernaan</option>
|
||||
<option value="Cedera dan Keracunan">Cedera dan Keracunan</option>
|
||||
</>
|
||||
) : activeTab === "procedure" ? (
|
||||
<>
|
||||
<option value="Penyakit Kardiovaskular">Penyakit Kardiovaskular</option>
|
||||
<option value="Penyakit Endokrin">Penyakit Endokrin</option>
|
||||
<option value="Penyakit Pernafasan">Penyakit Pernafasan</option>
|
||||
<option value="Penyakit Pencernaan">Penyakit Pencernaan</option>
|
||||
<option value="Cedera dan Keracunan">Cedera dan Keracunan</option>
|
||||
</>
|
||||
) : null}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-6 border-b border-gray-200">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1 flex items-center">
|
||||
<ClipboardList className="h-4 w-4 mr-2 text-purple-600" /> Catatan Medis
|
||||
</label>
|
||||
<textarea
|
||||
rows={4}
|
||||
value={assistInput.clinicalNotes}
|
||||
onChange={(e) => setAssistInput((s) => ({ ...s, clinicalNotes: e.target.value }))}
|
||||
placeholder="Riwayat penyakit, gejala, temuan fisik..."
|
||||
className="w-full rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent p-3"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Hasil Lab</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
value={assistInput.labResults}
|
||||
onChange={(e) => setAssistInput((s) => ({ ...s, labResults: e.target.value }))}
|
||||
placeholder="HbA1c 8.2%, CRP 15 mg/L, Leukosit 12.000/µL, ..."
|
||||
className="w-full rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent p-3"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Tindakan/Prosedur</label>
|
||||
<input
|
||||
type="text"
|
||||
value={assistInput.procedures}
|
||||
onChange={(e) => setAssistInput((s) => ({ ...s, procedures: e.target.value }))}
|
||||
placeholder="Endoskopi lambung, kateterisasi jantung, transfusi, ..."
|
||||
className="w-full rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent p-3"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<button
|
||||
onClick={runAssistPipeline}
|
||||
disabled={assistLoading || (!assistInput.clinicalNotes && !assistInput.labResults && !assistInput.procedures)}
|
||||
className="btn-primary flex items-center space-x-2 disabled:opacity-60"
|
||||
>
|
||||
<Wand2 className="h-4 w-4" />
|
||||
<span>{assistLoading ? "Memproses..." : "Jalankan Assist Coding"}</span>
|
||||
</button>
|
||||
</div>
|
||||
{assistError && (
|
||||
<div className="text-sm text-red-600">{assistError}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tables */}
|
||||
{/* Tables / Assist Results */}
|
||||
<div className="overflow-x-auto">
|
||||
{activeTab === "diagnose" ? (
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
@@ -469,6 +634,9 @@ export default function BPJSCode() {
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Kode ICD-10
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Kode BPJS
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Deskripsi & Kategori
|
||||
</th>
|
||||
@@ -500,6 +668,11 @@ export default function BPJSCode() {
|
||||
{code.icdCode}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">
|
||||
{code.bpjsCode}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
@@ -533,9 +706,15 @@ export default function BPJSCode() {
|
||||
{code.usageCount}x
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<Calendar className="h-4 w-4 mr-1" />
|
||||
{formatDate(code.lastUsed)}
|
||||
<div className="flex flex-col space-y-1 text-sm text-gray-500">
|
||||
<div className="flex items-center">
|
||||
<Calendar className="h-4 w-4 mr-1" />
|
||||
{formatDateOnly(code.lastUsed)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Clock className="h-4 w-4 mr-1" />
|
||||
{formatTimeOnly(code.lastUsed)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
@@ -552,13 +731,12 @@ export default function BPJSCode() {
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
) : activeTab === "procedure" ? (
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Kode ICP-9
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kode ICD-9</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kode BPJS</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Deskripsi & Kategori
|
||||
</th>
|
||||
@@ -566,14 +744,11 @@ export default function BPJSCode() {
|
||||
Departemen
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Kompleksitas
|
||||
Tingkat Keparahan
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Tarif BPJS
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Durasi
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Usage
|
||||
</th>
|
||||
@@ -586,11 +761,20 @@ export default function BPJSCode() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{filteredProcedureCodes.map((code) => (
|
||||
{filteredICD9DiagnoseCodes
|
||||
.filter((code) => {
|
||||
return true;
|
||||
})
|
||||
.map((code) => (
|
||||
<tr key={code.id} className="hover:bg-gray-50">
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{code.icp9Code}
|
||||
{code.icd9Code}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">
|
||||
{code.bpjsCode}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
@@ -610,31 +794,26 @@ export default function BPJSCode() {
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span
|
||||
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getComplexityColor(
|
||||
code.complexity
|
||||
)}`}
|
||||
>
|
||||
{code.complexity
|
||||
.replace("_", " ")
|
||||
.charAt(0)
|
||||
.toUpperCase() +
|
||||
code.complexity.replace("_", " ").slice(1)}
|
||||
</span>
|
||||
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSeverityColor(code.severity)}`}>
|
||||
{code.severity.charAt(0).toUpperCase() + code.severity.slice(1)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{formatCurrency(code.bpjsRate)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{code.duration} menit
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{code.usageCount}x
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<Calendar className="h-4 w-4 mr-1" />
|
||||
{formatDate(code.lastUsed)}
|
||||
<div className="flex flex-col space-y-1 text-sm text-gray-500">
|
||||
<div className="flex items-center">
|
||||
<Calendar className="h-4 w-4 mr-1" />
|
||||
{formatDateOnly(code.lastUsed)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Clock className="h-4 w-4 mr-1" />
|
||||
{formatTimeOnly(code.lastUsed)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
@@ -651,14 +830,109 @@ export default function BPJSCode() {
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div>
|
||||
{!assistResult ? (
|
||||
<div className="text-center py-12">
|
||||
<AlertCircle className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">Belum ada hasil</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">Isi input lalu klik "Jalankan Assist Coding".</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="bg-white border rounded-lg p-4">
|
||||
<div className="text-sm font-medium text-gray-900 mb-2">Hasil Preprocess</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm text-gray-700">
|
||||
<div>
|
||||
<div className="font-medium">Catatan Medis</div>
|
||||
<div className="text-gray-600 whitespace-pre-wrap">{assistResult.preprocessed.clinicalNotes || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">Hasil Lab</div>
|
||||
<div className="text-gray-600 whitespace-pre-wrap">{assistResult.preprocessed.labResults || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">Tindakan</div>
|
||||
<div className="text-gray-600 whitespace-pre-wrap">{assistResult.preprocessed.procedures || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border rounded-lg p-4">
|
||||
<div className="text-sm font-medium text-gray-900 mb-3 flex items-center">
|
||||
<ShieldCheck className="h-4 w-4 mr-2 text-green-600" /> Rekomendasi Kode & Alasan (LLM + RAG)
|
||||
</div>
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Jenis</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kode</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Deskripsi</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Confidence</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Alasan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{assistResult.recommendedCodes.map((c) => (
|
||||
<tr key={`${c.type}-${c.code}`} className="hover:bg-gray-50">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">{c.type}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{c.code}</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-900">{c.description}</td>
|
||||
<td className="px-6 py-4 w-48">
|
||||
<div className="text-sm text-gray-700 mb-1">{c.confidence}%</div>
|
||||
<div className="w-full bg-gray-100 rounded-full h-2">
|
||||
<div className="bg-green-500 h-2 rounded-full" style={{ width: `${Math.max(0, Math.min(100, c.confidence))}%` }} />
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">{c.rationale}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="text-xs text-gray-500 mt-2">Referensi: {assistResult.references.join(", ") || '-'}</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border rounded-lg p-4">
|
||||
<div className="text-sm font-medium text-gray-900 mb-2">Mapping INA-CBGs</div>
|
||||
{assistResult.inaCbgs ? (
|
||||
<div className="text-sm text-gray-700">
|
||||
<div>Kode: <span className="font-medium">{assistResult.inaCbgs.code}</span> ({assistResult.inaCbgs.group})</div>
|
||||
<div>Deskripsi: {assistResult.inaCbgs.description}</div>
|
||||
<div>Estimasi Tarif: <span className="font-medium">{formatCurrency(assistResult.inaCbgs.estTariff)}</span></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-gray-500">Belum ada mapping yang relevan.</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<button className="btn-primary flex items-center space-x-2">
|
||||
<CheckCircle2 className="h-4 w-4" />
|
||||
<span>Verifikasi & Approve</span>
|
||||
</button>
|
||||
<button className="btn-secondary flex items-center space-x-2" onClick={() => {
|
||||
const blob = new Blob([JSON.stringify(assistResult, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'klaim_siap_kirim.json';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}}>
|
||||
<Download className="h-4 w-4" />
|
||||
<span>Export Klaim</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Empty State */}
|
||||
{((activeTab === "diagnose" && filteredDiagnoseCodes.length === 0) ||
|
||||
(activeTab === "procedure" &&
|
||||
filteredProcedureCodes.length === 0)) && (
|
||||
{/* Empty State */}
|
||||
{((activeTab === "diagnose" && filteredDiagnoseCodes.length === 0) ||
|
||||
(activeTab === "procedure" && filteredICD9DiagnoseCodes.length === 0)) && (
|
||||
<div className="text-center py-12">
|
||||
<AlertCircle className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
||||
|
||||
Reference in New Issue
Block a user