| Server IP : 172.67.189.169 / Your IP : 216.73.216.59 Web Server : Apache/2 System : Linux cat167-197.static.lnwhostname.com 5.10.0-20-amd64 #1 SMP Debian 5.10.158-2 (2022-12-13) x86_64 User : paphayomho ( 1042) PHP Version : 5.6.40 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname MySQL : ON | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /home/paphayomho/domains/paphayomhospital.go.th/private_html/ |
Upload File : |
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ตัวชี้วัดและผลลัพธ์ 5 ปี 2566-2570 | รพ.ป่าพะยอม</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700&family=Noto+Sans+Thai:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
<style>
:root {
--primary: #0d5c8f;
--primary-light: #1a7bbf;
--primary-dark: #093d61;
--secondary: #1a9e6e;
--secondary-light: #22c587;
--accent: #f59e0b;
--accent2: #e74c3c;
--bg: #f0f4f8;
--card: #ffffff;
--text: #1a2942;
--text-muted: #5c7a99;
--border: #d1dde8;
--good: #16a34a;
--good-bg: #dcfce7;
--warn: #ca8a04;
--warn-bg: #fef9c3;
--bad: #dc2626;
--bad-bg: #fee2e2;
--na: #6b7280;
--na-bg: #f3f4f6;
--shadow: 0 2px 16px rgba(13,92,143,0.08);
--shadow-lg: 0 8px 32px rgba(13,92,143,0.12);
--radius: 12px;
--radius-sm: 8px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: 'Sarabun', 'Noto Sans Thai', sans-serif;
background: var(--bg);
color: var(--text);
font-size: 15px;
line-height: 1.6;
}
/* ── HERO ── */
.hero {
background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 50%, var(--primary-light) 100%);
color: #fff;
padding: 56px 32px 48px;
text-align: center;
position: relative;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute; inset: 0;
background: radial-gradient(ellipse at 80% 20%, rgba(26,155,110,0.2) 0%, transparent 60%),
radial-gradient(ellipse at 20% 80%, rgba(245,158,11,0.1) 0%, transparent 60%);
}
.hero-badge {
display: inline-block;
background: rgba(255,255,255,0.15);
border: 1px solid rgba(255,255,255,0.3);
border-radius: 20px;
padding: 5px 18px;
font-size: 13px;
font-weight: 500;
letter-spacing: 0.5px;
margin-bottom: 16px;
position: relative;
}
.hero h1 {
font-size: clamp(22px, 4vw, 38px);
font-weight: 700;
margin-bottom: 10px;
position: relative;
}
.hero .subtitle {
font-size: 15px;
opacity: 0.85;
margin-bottom: 20px;
position: relative;
}
.hero .description {
font-size: 14px;
opacity: 0.75;
max-width: 700px;
margin: 0 auto;
position: relative;
line-height: 1.8;
}
.hero-dots {
display: flex; gap: 8px; justify-content: center; margin-top: 24px; position: relative;
}
.hero-dot {
width: 10px; height: 10px; border-radius: 50%;
background: rgba(255,255,255,0.4);
}
.hero-dot.active { background: var(--accent); width: 28px; border-radius: 5px; }
/* ── NAV ── */
.sticky-nav {
position: sticky; top: 0; z-index: 100;
background: var(--primary-dark);
border-bottom: 2px solid var(--primary-light);
display: flex; gap: 0; overflow-x: auto;
scrollbar-width: none;
}
.sticky-nav::-webkit-scrollbar { display: none; }
.nav-btn {
padding: 13px 20px;
color: rgba(255,255,255,0.7);
font-size: 13px; font-weight: 500;
white-space: nowrap;
cursor: pointer;
border: none; background: none;
font-family: inherit;
transition: all 0.2s;
border-bottom: 3px solid transparent;
}
.nav-btn:hover { color: #fff; background: rgba(255,255,255,0.08); }
.nav-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
/* ── CONTAINER ── */
.container { max-width: 1280px; margin: 0 auto; padding: 0 24px; }
.section { padding: 40px 0; }
.section-title {
font-size: 20px; font-weight: 700;
color: var(--primary-dark);
margin-bottom: 24px;
display: flex; align-items: center; gap: 10px;
}
.section-title::after {
content: ''; flex: 1; height: 2px;
background: linear-gradient(to right, var(--border), transparent);
}
/* ── SUMMARY CARDS ── */
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
margin-bottom: 32px;
}
.summary-card {
background: var(--card);
border-radius: var(--radius);
padding: 22px 20px;
box-shadow: var(--shadow);
border-top: 4px solid var(--primary);
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
}
.summary-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.summary-card.good { border-top-color: var(--good); }
.summary-card.warn { border-top-color: var(--warn); }
.summary-card.bad { border-top-color: var(--bad); }
.summary-card.accent { border-top-color: var(--accent); }
.summary-num {
font-size: 40px; font-weight: 700; color: var(--primary);
line-height: 1;
}
.summary-card.good .summary-num { color: var(--good); }
.summary-card.warn .summary-num { color: var(--warn); }
.summary-card.bad .summary-num { color: var(--bad); }
.summary-card.accent .summary-num { color: var(--accent); }
.summary-label { font-size: 13px; color: var(--text-muted); margin-top: 6px; }
/* ── PILLAR CARDS ── */
.pillar-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 20px;
}
.pillar-card {
background: var(--card);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
}
.pillar-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
.pillar-header {
padding: 18px 20px 14px;
display: flex; align-items: flex-start; gap: 14px;
}
.pillar-icon {
width: 42px; height: 42px; border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 20px; flex-shrink: 0;
background: var(--primary);
}
.pillar-info { flex: 1; }
.pillar-code { font-size: 11px; font-weight: 700; letter-spacing: 1px; color: var(--text-muted); }
.pillar-name { font-size: 14px; font-weight: 600; color: var(--text); margin-top: 2px; line-height: 1.4; }
.pillar-status {
padding: 3px 10px; border-radius: 12px;
font-size: 12px; font-weight: 600; flex-shrink: 0;
}
.status-good { background: var(--good-bg); color: var(--good); }
.status-warn { background: var(--warn-bg); color: var(--warn); }
.status-bad { background: var(--bad-bg); color: var(--bad); }
.status-na { background: var(--na-bg); color: var(--na); }
.pillar-body { padding: 0 20px 18px; }
.pillar-stats {
display: flex; gap: 16px; margin-bottom: 14px;
font-size: 13px;
}
.pillar-stat { text-align: center; }
.pillar-stat-num { font-size: 22px; font-weight: 700; color: var(--primary); }
.pillar-stat-lbl { color: var(--text-muted); font-size: 11px; }
.progress-bar-wrap { margin-bottom: 14px; }
.progress-bar-bg {
height: 8px; border-radius: 4px;
background: var(--border);
overflow: hidden;
}
.progress-bar-fill {
height: 100%; border-radius: 4px;
background: linear-gradient(to right, var(--secondary), var(--primary-light));
transition: width 1s ease;
}
.progress-label {
display: flex; justify-content: space-between;
font-size: 12px; color: var(--text-muted); margin-top: 4px;
}
.pillar-highlights { list-style: none; }
.pillar-highlights li {
font-size: 13px; padding: 5px 0;
border-bottom: 1px solid var(--border);
display: flex; gap: 6px; align-items: flex-start;
color: var(--text);
}
.pillar-highlights li:last-child { border-bottom: none; }
.hl-dot {
width: 6px; height: 6px; border-radius: 50%;
background: var(--secondary); flex-shrink: 0; margin-top: 6px;
}
/* ── FILTERS ── */
.filter-bar {
background: var(--card);
border-radius: var(--radius);
padding: 20px;
box-shadow: var(--shadow);
margin-bottom: 20px;
display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end;
}
.filter-group { display: flex; flex-direction: column; gap: 5px; }
.filter-label { font-size: 12px; font-weight: 600; color: var(--text-muted); }
.filter-select, .filter-input {
padding: 8px 12px;
border: 1.5px solid var(--border);
border-radius: var(--radius-sm);
font-family: inherit; font-size: 13px;
color: var(--text);
background: var(--bg);
outline: none;
transition: border-color 0.2s;
}
.filter-select:focus, .filter-input:focus { border-color: var(--primary); }
.filter-input { min-width: 220px; }
.filter-reset {
padding: 8px 16px;
background: var(--primary);
color: #fff;
border: none; border-radius: var(--radius-sm);
font-family: inherit; font-size: 13px; font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.filter-reset:hover { background: var(--primary-dark); }
.filter-count {
font-size: 13px; color: var(--text-muted);
align-self: center;
margin-left: auto;
}
/* ── TABLE ── */
.table-wrap {
background: var(--card);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: auto;
}
table {
width: 100%; border-collapse: collapse;
font-size: 13px;
}
thead th {
background: var(--primary-dark);
color: #fff;
padding: 11px 12px;
text-align: left;
white-space: nowrap;
font-weight: 600;
font-size: 12px;
position: sticky; top: 0;
cursor: pointer;
user-select: none;
}
thead th:hover { background: var(--primary); }
thead th .sort-icon { margin-left: 4px; opacity: 0.5; }
tbody tr { transition: background 0.15s; }
tbody tr:nth-child(even) { background: #f8fbff; }
tbody tr:hover { background: #e8f4ff; }
td { padding: 9px 12px; vertical-align: top; border-bottom: 1px solid var(--border); }
.td-kpi { max-width: 200px; font-weight: 500; }
.td-okr { max-width: 280px; font-size: 12px; color: var(--text-muted); }
.td-year { text-align: center; }
.badge-level {
display: inline-block; padding: 2px 8px;
border-radius: 10px; font-size: 11px; font-weight: 700;
}
.badge-R { background: #dbeafe; color: #1d4ed8; }
.badge-P { background: #ede9fe; color: #6d28d9; }
.badge-U { background: #fee2e2; color: #991b1b; }
.badge-I { background: #dcfce7; color: #166534; }
.badge-status {
display: inline-flex; align-items: center; gap: 4px;
padding: 3px 10px; border-radius: 10px;
font-size: 11px; font-weight: 600; white-space: nowrap;
}
.badge-บรรลุ { background: var(--good-bg); color: var(--good); }
.badge-ใกล้บรรลุ { background: var(--warn-bg); color: var(--warn); }
.badge-ไม่บรรลุ { background: var(--bad-bg); color: var(--bad); }
.badge-ไม่มีข้อมูล { background: var(--na-bg); color: var(--na); }
.trend-arrow { font-size: 16px; }
.gap-val { font-size: 12px; font-weight: 600; }
.gap-pos { color: var(--good); }
.gap-neg { color: var(--bad); }
.gap-na { color: var(--na); }
/* ── CHARTS ── */
.charts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
gap: 20px;
}
.chart-card {
background: var(--card);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 22px;
}
.chart-title {
font-size: 15px; font-weight: 600; color: var(--primary-dark);
margin-bottom: 16px;
}
.chart-container { position: relative; height: 280px; }
.heatmap-wrap { overflow-x: auto; }
.heatmap-table {
width: 100%; border-collapse: separate; border-spacing: 3px;
font-size: 12px;
}
.heatmap-table th {
padding: 8px 12px; text-align: center;
background: var(--primary-dark); color: #fff;
border-radius: 6px; font-size: 11px;
}
.heatmap-table td {
padding: 10px 8px; text-align: center;
border-radius: 6px; font-weight: 600; font-size: 12px;
cursor: default;
}
.hm-good { background: #bbf7d0; color: #15803d; }
.hm-warn { background: #fef08a; color: #92400e; }
.hm-bad { background: #fecaca; color: #991b1b; }
.hm-na { background: #f3f4f6; color: #9ca3af; }
.hm-mixed { background: #ddd6fe; color: #5b21b6; }
.hm-row-label { font-weight: 600; color: var(--text); background: #f8fbff !important; text-align: left; border-radius: 6px; padding: 10px 12px; }
/* ── KPI SELECTOR ── */
.kpi-selector {
display: flex; flex-wrap: wrap; gap: 8px;
margin-bottom: 14px;
}
.kpi-chip {
padding: 5px 12px; border-radius: 20px;
border: 1.5px solid var(--border);
font-size: 12px; cursor: pointer;
background: #fff; color: var(--text-muted);
font-family: inherit;
transition: all 0.2s; white-space: nowrap;
}
.kpi-chip.active {
background: var(--primary); border-color: var(--primary);
color: #fff;
}
/* ── HIGHLIGHT SECTION ── */
.highlight-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 20px;
}
.highlight-card {
background: var(--card);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 22px;
}
.highlight-card-title {
font-size: 15px; font-weight: 700;
margin-bottom: 16px;
display: flex; align-items: center; gap: 8px;
}
.highlight-icon { font-size: 20px; }
.highlight-list { list-style: none; }
.highlight-list li {
padding: 9px 0;
border-bottom: 1px solid var(--border);
font-size: 13px;
display: flex; gap: 8px; align-items: flex-start;
line-height: 1.5;
}
.highlight-list li:last-child { border-bottom: none; }
/* ── RECOMMENDATION ── */
.rec-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
gap: 14px;
}
.rec-card {
background: var(--card);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 20px;
border-left: 4px solid var(--primary);
display: flex; gap: 14px;
}
.rec-num {
width: 32px; height: 32px; border-radius: 50%;
background: var(--primary); color: #fff;
display: flex; align-items: center; justify-content: center;
font-size: 14px; font-weight: 700; flex-shrink: 0;
}
.rec-content { flex: 1; }
.rec-title { font-size: 14px; font-weight: 600; color: var(--primary-dark); margin-bottom: 5px; }
.rec-detail { font-size: 13px; color: var(--text-muted); line-height: 1.6; }
/* ── DISCLAIMER ── */
.disclaimer {
background: #fffbeb;
border: 1px solid #fcd34d;
border-radius: var(--radius-sm);
padding: 12px 16px;
font-size: 12px;
color: #92400e;
margin: 24px 0;
display: flex; gap: 8px; align-items: flex-start;
}
/* ── FOOTER ── */
.footer {
background: var(--primary-dark);
color: rgba(255,255,255,0.7);
text-align: center;
padding: 24px;
font-size: 13px;
}
/* ── PRINT ── */
@media print {
.sticky-nav, .filter-bar, .filter-reset { display: none !important; }
.section { page-break-inside: avoid; }
body { background: #fff; font-size: 12px; }
.chart-container { height: 220px; }
.hero { padding: 24px; }
.hero h1 { font-size: 22px; }
}
@media (max-width: 640px) {
.summary-grid { grid-template-columns: repeat(2, 1fr); }
.pillar-grid { grid-template-columns: 1fr; }
.charts-grid { grid-template-columns: 1fr; }
.filter-bar { flex-direction: column; }
}
</style>
</head>
<body>
<!-- HERO -->
<section class="hero">
<div class="hero-badge">โรงพยาบาลป่าพะยอม · จ.พัทลุง | HCODE 11422</div>
<h1>ตัวชี้วัดและผลลัพธ์ 5 ปี 2566–2570</h1>
<p class="subtitle">Strategic KPI & Quality Outcome Roadmap | แผนยุทธศาสตร์ รพ.ป่าพะยอม</p>
<p class="description">
รายงานการติดตามเป้าหมายระยะ 5 ปี เชื่อมโยงยุทธศาสตร์ P1–P7<br>
ครอบคลุมผลลัพธ์บริการ คุณภาพ ความปลอดภัย การเงินการคลัง และการพัฒนาบุคลากร<br>
จัดทำเพื่อสื่อสารต่อ สรพ. และผู้บริหาร โดยอ้างอิงข้อมูลจริงจากไฟล์ตัวชี้วัด ปีงบประมาณ 2566–2568
</p>
<div class="hero-dots">
<div class="hero-dot"></div><div class="hero-dot active"></div><div class="hero-dot"></div>
</div>
</section>
<!-- STICKY NAV -->
<nav class="sticky-nav" id="stickyNav">
<button class="nav-btn active" onclick="scrollToSection('summary')">📊 ภาพรวม</button>
<button class="nav-btn" onclick="scrollToSection('pillars')">🏛️ ยุทธศาสตร์ P1–P7</button>
<button class="nav-btn" onclick="scrollToSection('table')">📋 ตาราง KPI</button>
<button class="nav-btn" onclick="scrollToSection('charts')">📈 กราฟ</button>
<button class="nav-btn" onclick="scrollToSection('highlights')">💡 ประเด็น สรพ.</button>
<button class="nav-btn" onclick="scrollToSection('recs')">✅ ข้อเสนอแนะ</button>
</nav>
<div class="container">
<!-- DISCLAIMER -->
<div class="disclaimer" style="margin-top:20px">
⚠️ <span><strong>หมายเหตุ:</strong> ข้อมูลอ้างอิงจากไฟล์ตัวชี้วัดที่แนบ และควรตรวจสอบความครบถ้วนก่อนใช้เป็นเอกสารทางการ ข้อมูลปี 2567–2568 บางรายการอาจยังไม่ครบสมบูรณ์ ข้อมูลปี 2569–2570 ยังไม่มีผลการดำเนินงาน</span>
</div>
<!-- ── SECTION 1: SUMMARY ── -->
<section class="section" id="summary">
<h2 class="section-title">📊 Executive Summary – ภาพรวมตัวชี้วัด</h2>
<div class="summary-grid" id="summaryGrid"></div>
</section>
<!-- ── SECTION 2: PILLARS ── -->
<section class="section" id="pillars">
<h2 class="section-title">🏛️ Strategic Pillar Dashboard – ผลลัพธ์รายยุทธศาสตร์</h2>
<div class="pillar-grid" id="pillarGrid"></div>
</section>
<!-- ── SECTION 3: TABLE ── -->
<section class="section" id="table">
<h2 class="section-title">📋 ตาราง KPI แบบ Interactive</h2>
<div class="filter-bar">
<div class="filter-group">
<label class="filter-label">ยุทธศาสตร์</label>
<select class="filter-select" id="filterPillar" onchange="applyFilters()">
<option value="">ทั้งหมด</option>
<option value="P1">P1 – ลดป่วยรายใหม่</option>
<option value="P2">P2 – ลดป่วยตาย</option>
<option value="P3">P3 – IT มาตรฐาน</option>
<option value="P4">P4 – คุณภาพบริการ</option>
<option value="P5">P5 – ภาคีเครือข่าย</option>
<option value="P6">P6 – การเงิน</option>
<option value="P7">P7 – บุคลากร</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">ระดับตัวชี้วัด</label>
<select class="filter-select" id="filterLevel" onchange="applyFilters()">
<option value="">ทั้งหมด</option>
<option value="R">R</option>
<option value="P">P</option>
<option value="U">U</option>
<option value="I">I</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">สถานะปี 2568</label>
<select class="filter-select" id="filterStatus" onchange="applyFilters()">
<option value="">ทั้งหมด</option>
<option value="บรรลุ">✅ บรรลุ</option>
<option value="ใกล้บรรลุ">🟡 ใกล้บรรลุ</option>
<option value="ไม่บรรลุ">❌ ไม่บรรลุ</option>
<option value="ไม่มีข้อมูล">➖ ไม่มีข้อมูล</option>
</select>
</div>
<div class="filter-group">
<label class="filter-label">ค้นหา KPI / OKRs</label>
<input class="filter-input" id="filterSearch" placeholder="พิมพ์คำค้นหา..." oninput="applyFilters()">
</div>
<button class="filter-reset" onclick="resetFilters()">รีเซ็ต</button>
<span class="filter-count" id="filterCount"></span>
</div>
<div class="table-wrap">
<table id="kpiTable">
<thead>
<tr>
<th onclick="sortTable('pillar')">ยุทธศาสตร์ <span class="sort-icon">↕</span></th>
<th onclick="sortTable('level')">ระดับ <span class="sort-icon">↕</span></th>
<th onclick="sortTable('kpi')">KPI <span class="sort-icon">↕</span></th>
<th>OKRs/KRA</th>
<th class="td-year">เป้า 66</th>
<th class="td-year">เป้า 67</th>
<th class="td-year">เป้า 68</th>
<th class="td-year">ผล 66</th>
<th class="td-year">ผล 67</th>
<th class="td-year">ผล 68</th>
<th onclick="sortTable('status')">สถานะ 68 <span class="sort-icon">↕</span></th>
<th>Gap</th>
<th>Trend</th>
<th>ข้อสังเกต</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
</div>
</section>
<!-- ── SECTION 4: CHARTS ── -->
<section class="section" id="charts">
<h2 class="section-title">📈 Trend Visualization – การวิเคราะห์แนวโน้ม</h2>
<div class="charts-grid">
<div class="chart-card">
<div class="chart-title">สถานะตัวชี้วัดปี 2568 แยกตามยุทธศาสตร์</div>
<div class="chart-container"><canvas id="chartStatus"></canvas></div>
</div>
<div class="chart-card" style="grid-column: span 1;">
<div class="chart-title">เปรียบเทียบ Target vs Actual – เลือก KPI</div>
<div class="kpi-selector" id="kpiSelector"></div>
<div class="chart-container"><canvas id="chartTrend"></canvas></div>
</div>
<div class="chart-card" style="grid-column: 1 / -1;">
<div class="chart-title">Heatmap ผลลัพธ์ P1–P7 × ปี 2566–2568 (สีแสดงสัดส่วนการบรรลุเป้าหมาย)</div>
<div class="heatmap-wrap" id="heatmapWrap"></div>
</div>
</div>
</section>
<!-- ── SECTION 5: HIGHLIGHTS ── -->
<section class="section" id="highlights">
<h2 class="section-title">💡 ประเด็นสื่อสารต่อ สรพ.</h2>
<div class="highlight-grid">
<div class="highlight-card">
<div class="highlight-card-title"><span class="highlight-icon">🌟</span> จุดแข็ง – Strong Points</div>
<ul class="highlight-list" id="hlStrengths"></ul>
</div>
<div class="highlight-card">
<div class="highlight-card-title"><span class="highlight-icon">🔧</span> โอกาสพัฒนา – Improvement Areas</div>
<ul class="highlight-list" id="hlOpportunities"></ul>
</div>
<div class="highlight-card" style="grid-column: 1 / -1;">
<div class="highlight-card-title"><span class="highlight-icon">🔗</span> ความเชื่อมโยงเชิงระบบ – System Linkage</div>
<ul class="highlight-list" id="hlSystemic"></ul>
</div>
</div>
</section>
<!-- ── SECTION 6: RECOMMENDATIONS ── -->
<section class="section" id="recs">
<h2 class="section-title">✅ ข้อเสนอแนะเชิงบริหาร</h2>
<div class="rec-list" id="recList"></div>
</section>
<div class="disclaimer">
📄 <span>รายงานนี้จัดทำจากไฟล์ตัวชี้วัดและค่าเป้าหมายท้าทายความสำเร็จ (Roadmap) โรงพยาบาลป่าพะยอม ปีงบประมาณ 2566–2570 | ข้อมูลที่ไม่มีในไฟล์แสดงเป็น "ไม่มีข้อมูล" | ควรตรวจสอบและรับรองโดยผู้รับผิดชอบก่อนใช้เป็นเอกสารทางการ</span>
</div>
</div><!-- /container -->
<footer class="footer">
โรงพยาบาลป่าพะยอม | อำเภอป่าพะยอม จังหวัดพัทลุง | โรงพยาบาลชุมชนขนาด 45 เตียง | HCODE 11422<br>
Strategic KPI Dashboard 2566–2570 | จัดทำด้วย Data Storytelling & HA Quality Framework
</footer>
<script>
// ════════════════════════════════════════════════════════════════
// DATA EMBEDDED FROM EXCEL (ข้อมูลจากไฟล์ตัวชี้วัดที่แนบ)
// โครงสร้าง: pillar, kpiGroup, level (R/P/U/I), kpi, okr,
// target:{66,67,68,69,70}, actual:{66,67,68,69,70}
// ════════════════════════════════════════════════════════════════
const RAW_DATA = [
// ─── P1 ───────────────────────────────────────────────
{pillar:'P1',kpiGroup:'1. อัตราการเจ็บป่วยรายใหม่ DM',level:'R',kpi:'อัตราการเจ็บป่วยรายใหม่ DM',okr:'อัตราผู้ป่วยเบาหวานรายใหม่กลุ่มในเสี่ยง <2%',target:{66:1.9,67:1.8,68:1.7,69:1.6,70:1.5},actual:{66:1.16,67:2.72,68:1.86,69:null,70:null},direction:'<',owner:'ศศิธร'},
{pillar:'P1',kpiGroup:'1. อัตราการเจ็บป่วยรายใหม่ DM',level:'U',kpi:'อัตราการเจ็บป่วยรายใหม่ DM',okr:'ร้อยละการตรวจติดตามยืนยันวินิจฉัยกลุ่มสงสัยป่วยโรคเบาหวาน',target:{66:'≥80',67:'≥80',68:'≥85',69:'≥90',70:'≥95'},actual:{66:null,67:78.16,68:80.77,69:null,70:null},direction:'>',owner:'สาลี'},
{pillar:'P1',kpiGroup:'2. อัตราการเจ็บป่วยรายใหม่ HT',level:'R',kpi:'อัตราการเจ็บป่วยรายใหม่ HT',okr:'อัตราผู้ป่วยความดันรายใหม่ในกลุ่มเสี่ยง <2%',target:{66:3.5,67:3,68:2.5,69:2,70:1.5},actual:{66:4.42,67:7.34,68:2.24,69:null,70:null},direction:'<',owner:'ศศิธร'},
{pillar:'P1',kpiGroup:'2. อัตราการเจ็บป่วยรายใหม่ HT',level:'U',kpi:'อัตราการเจ็บป่วยรายใหม่ HT',okr:'ร้อยละการตรวจติดตามยืนยันวินิจฉัยกลุ่มสงสัยป่วยความดันโลหิตสูง',target:{66:'≥80',67:'≥80',68:'≥85',69:'≥90',70:'≥95'},actual:{66:null,67:88.58,68:95.6,69:null,70:null},direction:'>',owner:'สาลี'},
{pillar:'P1',kpiGroup:'3. อัตราการเจ็บป่วยรายใหม่ Stroke',level:'U',kpi:'อัตราการเจ็บป่วยรายใหม่ Stroke',okr:'อัตราการเจ็บป่วยรายใหม่ stroke ในผู้ป่วยกลุ่มโรค HT DM <1%',target:{66:1.5,67:1.4,68:1.3,69:1.2,70:1},actual:{66:1.49,67:null,68:3.31,69:null,70:null},direction:'<',owner:'จำรูญศรี'},
{pillar:'P1',kpiGroup:'4. อัตราการเจ็บป่วยรายใหม่ COPD/Asthma',level:'U',kpi:'อัตราการเจ็บป่วยรายใหม่ COPD/Asthma',okr:'อัตราการป่วยรายใหม่ในผู้ป่วย COPD < 5 : แสนประชากร',target:{66:5,67:5,68:5,69:5,70:5},actual:{66:2.8,67:null,68:10.92,69:null,70:null},direction:'<',owner:'กมวรรณ'},
{pillar:'P1',kpiGroup:'4. อัตราการเจ็บป่วยรายใหม่ COPD/Asthma',level:'U',kpi:'อัตราการเจ็บป่วยรายใหม่ COPD/Asthma',okr:'อัตราการป่วยรายใหม่ในผู้ป่วย Asthma < 5 : แสนประชากร',target:{66:5,67:5,68:5,69:5,70:5},actual:{66:22.44,67:null,68:2.38,69:null,70:null},direction:'<',owner:null},
{pillar:'P1',kpiGroup:'5. อัตราการเจ็บป่วยรายใหม่ ACS',level:'U',kpi:'อัตราการเจ็บป่วยรายใหม่ ACS',okr:'อัตราการเจ็บป่วยรายใหม่ ACS ในผู้ป่วยกลุ่มเสี่ยง HT, DM, DLP <0.2%',target:{66:0.5,67:0.4,68:0.3,69:0.2,70:0.2},actual:{66:0.11,67:null,68:0.36,69:null,70:null},direction:'<',owner:'จำรูญศรี'},
{pillar:'P1',kpiGroup:'6. อัตราการเจ็บป่วยรายใหม่ Head injury',level:'U',kpi:'อัตราการเจ็บป่วยรายใหม่ Head injury',okr:'ร้อยละความสำเร็จมาตรการป้องกันจุดเสี่ยง 100%',target:{66:50,67:60,68:70,69:80,70:100},actual:{66:null,67:null,68:100,69:null,70:null},direction:'>',owner:'จำรูญศรี'},
{pillar:'P1',kpiGroup:'6. อัตราการเจ็บป่วยรายใหม่ Head injury',level:'U',kpi:'อัตราการเจ็บป่วยรายใหม่ Head injury',okr:'ร้อยละความสำเร็จมาตรการขับขี่ปลอดภัย (สวมหมวกนิรภัย) 100%',target:{66:60,67:70,68:80,69:90,70:100},actual:{66:50,67:90,68:100,69:null,70:null},direction:'>',owner:null},
{pillar:'P1',kpiGroup:'7. อัตราการเจ็บป่วยรายใหม่ DHF',level:'I',kpi:'อัตราการเจ็บป่วยรายใหม่ DHF',okr:'อัตราป่วยด้วยโรคไข้เลือดออกต่อแสนประชากร (ตำบลป่าพะยอม)',target:{66:50,67:50,68:50,69:50,70:50},actual:{66:0,67:1.338,68:1.3216,69:null,70:null},direction:'<',owner:'สาลี'},
{pillar:'P1',kpiGroup:'7. อัตราการเจ็บป่วยรายใหม่ DHF',level:'I',kpi:'อัตราการเจ็บป่วยรายใหม่ DHF',okr:'ค่าลูกน้ำยุงลาย HI<10, CI=0',target:{66:'<10/0',67:'<10/0',68:'<10/0',69:'<10/0',70:'<10/0'},actual:{66:0.874,67:0.912,68:0.8083,69:null,70:null},direction:'special',
evalFn:(a)=>{ const hi=parseFloat(a); return !isNaN(hi)&&hi<10?'บรรลุ':'ไม่บรรลุ'; }, owner:null},
{pillar:'P1',kpiGroup:'8. อัตราการเจ็บป่วยรายใหม่ Depression',level:'I',kpi:'อัตราการเจ็บป่วยรายใหม่ Depression',okr:'ร้อยละของผู้ป่วยโรคซึมเศร้าเข้าถึงบริการสุขภาพจิตเพิ่มขึ้น ร้อยละ 5/ปี',target:{66:75,67:80,68:85,69:90,70:95},actual:{66:78.47,67:94.57,68:100,69:null,70:null},direction:'>',owner:'มนสิชา'},
{pillar:'P1',kpiGroup:'9. อัตราการเจ็บป่วยรายใหม่ TB',level:'I',kpi:'อัตราการเจ็บป่วยรายใหม่ TB',okr:'อัตราสำเร็จของการรักษาวัณโรคปอดรายใหม่',target:{66:'≥85%',67:'≥85%',68:'≥85%',69:'≥85%',70:'≥85%'},actual:{66:100,67:96.58,68:91.03,69:null,70:null},direction:'>',owner:'เสาวณีย์'},
{pillar:'P1',kpiGroup:'9. อัตราการเจ็บป่วยรายใหม่ TB',level:'I',kpi:'อัตราการเจ็บป่วยรายใหม่ TB',okr:'อัตราการเกิดวัณโรครายใหม่จากกลุ่มสัมผัสผู้ป่วย',target:{66:0,67:0,68:0,69:0,70:0},actual:{66:0,67:0,68:0,69:null,70:null},direction:'=',owner:null},
{pillar:'P1',kpiGroup:'10. อัตราการเจ็บป่วยรายใหม่ HIV',level:'I',kpi:'อัตราการเจ็บป่วยรายใหม่ HIV',okr:'ร้อยละของผู้เข้ารับบริการที่ได้รับคำแนะนำให้ใช้ PrEP/PEP ได้เริ่มใช้ PrEP/PEP',target:{66:55,67:65,68:75,69:85,70:95},actual:{66:99.84,67:100,68:100,69:null,70:null},direction:'>',owner:'สุวิทย์'},
{pillar:'P1',kpiGroup:'10. อัตราการเจ็บป่วยรายใหม่ HIV',level:'I',kpi:'อัตราการเจ็บป่วยรายใหม่ HIV',okr:'ร้อยละของผู้เข้ารับบริการเริ่มใช้ PrEP/PEP ใช้ต่อเนื่อง ครบ 1 เดือน',target:{66:80,67:85,68:90,69:95,70:100},actual:{66:99.84,67:100,68:100,69:null,70:null},direction:'>',owner:null},
{pillar:'P1',kpiGroup:'10. อัตราการเจ็บป่วยรายใหม่ HIV',level:'I',kpi:'อัตราการเจ็บป่วยรายใหม่ HIV',okr:'ร้อยละของผู้เข้ารับบริการเริ่มใช้ PrEP ที่ใช้ต่อเนื่อง ครบ 3 เดือน',target:{66:80,67:85,68:90,69:95,70:100},actual:{66:99.84,67:100,68:100,69:null,70:null},direction:'>',owner:null},
{pillar:'P1',kpiGroup:'11. ประสิทธิภาพการบริหารจัดการความเสี่ยงด้านคลินิก',level:'R',kpi:'ประสิทธิภาพการบริหารจัดการความเสี่ยงด้านคลินิก',okr:'อัตราการติดเชื้อในโรงพยาบาล (<0.5 : 1000 วันนอน)',target:{66:0.5,67:0.5,68:0.5,69:0.5,70:0.4},actual:{66:0.56,67:1.5,68:0.26,69:null,70:null},direction:'<',owner:'อรวรรณ'},
{pillar:'P1',kpiGroup:'11. ประสิทธิภาพการบริหารจัดการความเสี่ยงด้านคลินิก',level:'P',kpi:'ประสิทธิภาพการบริหารจัดการความเสี่ยงด้านคลินิก',okr:'ร้อยละการได้รับ Antibiotic ภายใน 1 ชม. ผู้ป่วย Sepsis (≥90%)',target:{66:90,67:90,68:90,69:90,70:90},actual:{66:68.85,67:80.58,68:90.41,69:null,70:null},direction:'>',owner:null},
{pillar:'P1',kpiGroup:'12. Dental carie',level:'U',kpi:'Dental carie',okr:'ร้อยละการเกิดโรคฟันแท้ผุ ในเด็ก 6 ปี',target:{66:'ไม่เกิน 20',67:'ไม่เกิน 20',68:'ไม่เกิน 21',69:'ไม่เกิน 22',70:'ไม่เกิน 23'},actual:{66:8.09,67:4.55,68:5.22,69:null,70:null},direction:'special',
evalFn:(a,t)=>{ const aNum=parseFloat(a); const tNum=parseFloat(String(t).replace(/[^\d.]/g,'')); if(isNaN(aNum)||isNaN(tNum))return'ไม่มีข้อมูล'; return aNum<=tNum?'บรรลุ':aNum<=tNum*1.1?'ใกล้บรรลุ':'ไม่บรรลุ'; }, owner:'วสัน'},
{pillar:'P1',kpiGroup:'12. Dental carie',level:'U',kpi:'Dental carie',okr:'ร้อยละผู้สูงอายุได้รับการคัดกรองรอยโรคก่อมะเร็ง PMD',target:{66:30,67:35,68:40,69:45,70:50},actual:{66:31.44,67:59.2,68:83.55,69:null,70:null},direction:'>',owner:'วสัน'},
{pillar:'P1',kpiGroup:'13. ผู้ป่วยเสพติดเข้าถึงบริการ',level:'U',kpi:'อัตราผู้ป่วยเสพติดเข้าถึงบริการและอยู่ในระบบ',okr:'ร้อยละของผู้ป่วยยาเสพติดเข้าสู่กระบวนการรักษาและได้รับการดูแลต่อเนื่องเพิ่มขึ้นร้อยละ 2 ต่อปี',target:{66:60,67:62,68:64,69:66,70:68},actual:{66:78.47,67:66.67,68:79,69:null,70:null},direction:'>',owner:'มนสิชา'},
// ─── P2 ───────────────────────────────────────────────
{pillar:'P2',kpiGroup:'14. อัตราการป่วยตาย DM',level:'U',kpi:'อัตราการป่วยตาย DM',okr:'อัตราการแทรกซ้อนในผู้ป่วยเบาหวาน <5%',target:{66:5,67:4,68:3,69:2,70:1},actual:{66:0.52,67:4.17,68:4.04,69:null,70:null},direction:'<',owner:'ศศิธร'},
{pillar:'P2',kpiGroup:'14. อัตราการป่วยตาย DM',level:'U',kpi:'อัตราการป่วยตาย DM',okr:'ร้อยละผู้ป่วยเบาหวานที่ควบคุมระดับน้ำตาลได้ดี >40%',target:{66:40,67:41,68:42,69:43,70:44},actual:{66:28.24,67:35.36,68:41.24,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'14. อัตราการป่วยตาย DM',level:'U',kpi:'อัตราการป่วยตาย DM',okr:'อัตราการขาดนัดในผู้ป่วยเบาหวาน <5%',target:{66:5,67:4.5,68:4,69:3.5,70:3},actual:{66:1.82,67:3.71,68:4.22,69:null,70:null},direction:'<',owner:null},
{pillar:'P2',kpiGroup:'14. อัตราการป่วยตาย DM',level:'U',kpi:'อัตราการป่วยตาย DM',okr:'อัตราผู้ป่วยเบาหวานมาโรงพยาบาลก่อนนัดด้วยภาวะน้ำตาลต่ำ <5%',target:{66:5,67:4.5,68:4,69:3.5,70:3},actual:{66:1.29,67:0.77,68:2.11,69:null,70:null},direction:'<',owner:null},
{pillar:'P2',kpiGroup:'14. อัตราการป่วยตาย DM',level:'U',kpi:'อัตราการป่วยตาย DM',okr:'อัตราผู้ป่วยเบาหวานมาโรงพยาบาลก่อนนัดด้วยภาวะน้ำตาลสูงวิกฤติ <10%',target:{66:10,67:9,68:8,69:7,70:6},actual:{66:2.01,67:2.21,68:6.7,69:null,70:null},direction:'<',owner:null},
{pillar:'P2',kpiGroup:'15. อัตราการป่วยตาย HT',level:'U',kpi:'อัตราการป่วยตาย HT',okr:'อัตราผู้ป่วยแทรกซ้อนรายใหม่ในผู้ป่วย HT <5%',target:{66:5,67:4,68:3,69:2,70:1},actual:{66:0.2,67:0.21,68:0.2,69:null,70:null},direction:'<',owner:'ศศิธร'},
{pillar:'P2',kpiGroup:'15. อัตราการป่วยตาย HT',level:'U',kpi:'อัตราการป่วยตาย HT',okr:'ร้อยละผู้ป่วย HT ที่ควบคุมระดับ BP >70%',target:{66:68,67:69,68:70,69:71,70:72},actual:{66:66.09,67:69.69,68:58.73,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'15. อัตราการป่วยตาย HT',level:'U',kpi:'อัตราการป่วยตาย HT',okr:'อัตราการขาดนัดในผู้ป่วย HT <5%',target:{66:9,67:8,68:7,69:6,70:5},actual:{66:3.34,67:4.78,68:3.99,69:null,70:null},direction:'<',owner:null},
{pillar:'P2',kpiGroup:'15. อัตราการป่วยตาย HT',level:'U',kpi:'อัตราการป่วยตาย HT',okr:'อัตราผู้ป่วยความดันโลหิตสูงมีภาวะวิกฤติ <2%',target:{66:0.68,67:0.66,68:0.64,69:0.62,70:0.60},actual:{66:0.23,67:0.17,68:0.22,69:null,70:null},direction:'<',owner:null},
{pillar:'P2',kpiGroup:'16. อัตราการป่วยตาย Stroke',level:'U',kpi:'อัตราการป่วยตาย Stroke',okr:'อัตราผู้ป่วย Stroke มาด้วยระบบ EMS >50%',target:{66:20,67:30,68:40,69:50,70:50},actual:{66:20.61,67:null,68:12.16,69:null,70:null},direction:'>',owner:'จำรูญศรี'},
{pillar:'P2',kpiGroup:'16. อัตราการป่วยตาย Stroke',level:'P',kpi:'อัตราการป่วยตาย Stroke',okr:'อัตราผู้ป่วย Stroke ที่มาถึง รพ. ภายใน 3 ชม. >50%',target:{66:40,67:42,68:46,69:48,70:50},actual:{66:48.34,67:null,68:35.13,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'16. อัตราการป่วยตาย Stroke',level:'R',kpi:'อัตราการป่วยตาย Stroke',okr:'อัตราผู้ป่วย Stroke fast tract ได้รับการส่งต่อภายใน 30 นาที >80%',target:{66:40,67:50,68:60,69:70,70:80},actual:{66:33.55,67:null,68:42.3,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'16. อัตราการป่วยตาย Stroke',level:'P',kpi:'อัตราการป่วยตาย Stroke',okr:'Stroke รายใหม่ได้รับการติดตามเยี่ยมจนครบ 6 เดือน >80%',target:{66:100,67:100,68:100,69:100,70:100},actual:{66:100,67:96.25,68:93.87,69:null,70:null},direction:'>',owner:'ทิพวรรณ'},
{pillar:'P2',kpiGroup:'16. อัตราการป่วยตาย Stroke',level:'P',kpi:'อัตราการป่วยตาย Stroke',okr:'ภาวะแทรกซ้อนในผู้ป่วย Stroke <20',target:{66:5,67:5,68:5,69:5,70:5},actual:{66:5.26,67:14.81,68:6.12,69:null,70:null},direction:'<',owner:null},
{pillar:'P2',kpiGroup:'16. อัตราการป่วยตาย Stroke',level:'U',kpi:'อัตราการป่วยตาย Stroke',okr:'ผู้ป่วย Stroke ได้รับการฟื้นฟูมีคะแนน Barthel index > 15 คะแนน',target:{66:60,67:65,68:70,69:75,70:80},actual:{66:70.83,67:72.22,68:85.71,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'17. อัตราการป่วยตาย COPD/Asthma',level:'U',kpi:'อัตราการป่วยตาย COPD/Asthma',okr:'อัตราป่วยตายด้วยโรค COPD < 5 ในโรงพยาบาล',target:{66:5,67:4,68:3,69:2,70:1},actual:{66:0,67:null,68:0,69:null,70:null},direction:'<',owner:'กมลวรรณ'},
{pillar:'P2',kpiGroup:'17. อัตราการป่วยตาย COPD/Asthma',level:'U',kpi:'อัตราการป่วยตาย COPD/Asthma',okr:'อัตราป่วยตายด้วยโรค Asthma < 5 ในโรงพยาบาล',target:{66:5,67:4,68:3,69:2,70:1},actual:{66:0,67:null,68:0,69:null,70:null},direction:'<',owner:null},
{pillar:'P2',kpiGroup:'17. อัตราการป่วยตาย COPD/Asthma',level:'R',kpi:'อัตราการป่วยตาย COPD/Asthma',okr:'อัตราการเกิด respiratory failure COPD/Asthma ในหอผู้ป่วยใน',target:{66:5,67:4,68:3,69:2,70:1},actual:{66:1.29,67:null,68:null,69:null,70:null},direction:'<',owner:null},
{pillar:'P2',kpiGroup:'17. อัตราการป่วยตาย COPD/Asthma',level:'U',kpi:'อัตราการป่วยตาย COPD/Asthma',okr:'ผู้ป่วยคลินิก COPD/Asthma ใช้ยาพ่นถูกต้องร้อยละ 80',target:{66:80,67:82,68:84,69:86,70:88},actual:{66:81.07,67:85,68:85,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'18. อัตราการป่วยตาย ACS',level:'U',kpi:'อัตราการป่วยตาย ACS',okr:'อัตราผู้ป่วย ACS มาด้วยระบบ EMS >50%',target:{66:20,67:25,68:30,69:40,70:50},actual:{66:20,67:null,68:0,69:null,70:null},direction:'>',owner:'จำรูญศรี'},
{pillar:'P2',kpiGroup:'18. อัตราการป่วยตาย ACS',level:'P',kpi:'อัตราการป่วยตาย ACS',okr:'อัตราผู้ป่วย ACS ที่มาถึง รพ. ภายใน 3 ชม. ≥80%',target:{66:85,67:90,68:95,69:100,70:100},actual:{66:100,67:null,68:85.7,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'18. อัตราการป่วยตาย ACS',level:'R',kpi:'อัตราการป่วยตาย ACS',okr:'อัตราผู้ป่วย ACS ได้รับการวินิจฉัยประเมิน EKG ภายใน 10 นาที 100%',target:{66:85,67:90,68:95,69:100,70:100},actual:{66:20,67:null,68:100,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'18. อัตราการป่วยตาย ACS',level:'U',kpi:'อัตราการป่วยตาย ACS',okr:'อัตราผู้ป่วย ACS ได้รับการส่งต่อถึงศูนย์หัวใจภายใน 80 นาที 100%',target:{66:60,67:70,68:80,69:90,70:100},actual:{66:40,67:null,68:48.18,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'18. อัตราการป่วยตาย ACS',level:'U',kpi:'อัตราการป่วยตาย ACS',okr:'อัตราการได้รับ SK ในผู้ป่วย ACS ภายใน 30 นาที 100%',target:{66:100,67:100,68:100,69:100,70:100},actual:{66:null,67:null,68:null,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'19. อัตราการป่วยตาย Head injury',level:'U',kpi:'อัตราการป่วยตาย Head injury',okr:'อัตราผู้ป่วยบาดเจ็บศีรษะและลำคอมาด้วยระบบ EMS >80%',target:{66:50,67:60,68:70,69:80,70:80},actual:{66:26.31,67:null,68:24.75,69:null,70:null},direction:'>',owner:'จำรูญศรี'},
{pillar:'P2',kpiGroup:'19. อัตราการป่วยตาย Head injury',level:'U',kpi:'อัตราการป่วยตาย Head injury',okr:'อัตราผู้ป่วย moderate head injury ได้รับการส่งต่อภายใน 45 นาที 100%',target:{66:100,67:100,68:100,69:100,70:100},actual:{66:100,67:null,68:100,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'19. อัตราการป่วยตาย Head injury',level:'U',kpi:'อัตราการป่วยตาย Head injury',okr:'อัตราผู้ป่วย severe head injury ได้รับการส่งต่อภายใน 1 ชั่วโมง 100%',target:{66:100,67:100,68:100,69:100,70:100},actual:{66:100,67:null,68:91.66,69:null,70:null},direction:'>',owner:null},
{pillar:'P2',kpiGroup:'20. อัตราการป่วยตาย DHF',level:'P',kpi:'อัตราการป่วยตาย DHF',okr:'อัตราการป่วยตาย DHF',target:{66:0,67:0,68:0,69:0,70:0},actual:{66:0,67:null,68:0,69:null,70:null},direction:'=',owner:'กมลวรรณ'},
{pillar:'P2',kpiGroup:'20. อัตราการป่วยตาย DHF',level:'P',kpi:'อัตราการป่วยตาย DHF',okr:'อัตราการเกิด DSS ในโรงพยาบาล',target:{66:0,67:0,68:0,69:0,70:0},actual:{66:0,67:null,68:0,69:null,70:null},direction:'=',owner:null},
{pillar:'P2',kpiGroup:'21. อัตราการป่วยตาย Depression',level:'R',kpi:'อัตราการป่วยตาย Depression',okr:'อัตราฆ่าตัวตายสำเร็จในผู้ป่วยโรคซึมเศร้า',target:{66:2.5,67:2.25,68:2,69:1.75,70:1.5},actual:{66:0,67:0,68:0,69:null,70:null},direction:'<',owner:'มนสิชา'},
{pillar:'P2',kpiGroup:'22. อัตราการป่วยตาย TB',level:'U',kpi:'อัตราการป่วยตาย TB',okr:'อัตราการตายด้วยโรควัณโรค ไม่เกินร้อยละ 5',target:{66:'<5',67:'<5',68:'<5',69:'<5',70:'<5'},actual:{66:7.33,67:2.04,68:11.71,69:null,70:null},direction:'<',owner:'เสาวณีย์'},
{pillar:'P2',kpiGroup:'23. อัตราการป่วยตาย HIV',level:'U',kpi:'อัตราการป่วยตาย HIV',okr:'ร้อยละของผู้ป่วยรับประทานยาต่อเนื่อง (Adherence) >ร้อยละ 90',target:{66:90,67:95,68:95,69:100,70:100},actual:{66:57.33,67:98,68:97.5,69:null,70:null},direction:'>',owner:'สุวิทย์'},
{pillar:'P2',kpiGroup:'23. อัตราการป่วยตาย HIV',level:'I',kpi:'อัตราการป่วยตาย HIV',okr:'ร้อยละของผู้ป่วยที่มีค่า VL < 40 copies',target:{66:90,67:95,68:95,69:100,70:100},actual:{66:16.5,67:92.43,68:97.5,69:null,70:null},direction:'>',owner:'สุวิทย์'},
// ─── P3 ───────────────────────────────────────────────
{pillar:'P3',kpiGroup:'24. มาตรฐาน HIMSS',level:'I',kpi:'มาตรฐาน HIMSS',okr:'ระบบสารสนเทศผ่านมาตรฐาน HIMSS ระดับ 6',target:{66:4,67:4,68:5,69:5,70:6},actual:{66:4,67:4,68:'ยังไม่รับการประเมิน',69:null,70:null},direction:'text',owner:'จริยาพร'},
{pillar:'P3',kpiGroup:'25. มาตรฐาน HAIT',level:'R',kpi:'มาตรฐาน HAIT',okr:'ผ่านมาตรฐาน HAIT',target:{66:'ได้รับคัดเลือกเป็น รพ.นำร่อง',67:null,68:null,69:'ผ่านการประเมิน HAIT',70:null},actual:{66:'รอรับการประเมิน',67:'รอรับการประเมิน',68:'รอรับการประเมิน',69:null,70:null},direction:'text',owner:'จริยาพร'},
{pillar:'P3',kpiGroup:'26. ระบบบริหารจัดการสารสนเทศและพัสดุ',level:'I',kpi:'ระบบบริหารจัดการสารสนเทศและพัสดุ',okr:'การประยุกต์ใช้งานระบบบริหารจัดการสารสนเทศและพัสดุ >80%',target:{66:60,67:65,68:70,69:75,70:80},actual:{66:12.12,67:85,68:88.88,69:null,70:null},direction:'>',owner:'จริยาพร'},
// ─── P4 ───────────────────────────────────────────────
{pillar:'P4',kpiGroup:'27. มาตรฐานระบบบริการสุขภาพ (HS4)',level:'P',kpi:'มาตรฐานระบบบริการสุขภาพ (HS4)',okr:'ผ่านมาตรฐานระบบบริการสุขภาพ >60%',target:{66:70,67:75,68:80,69:85,70:90},actual:{66:25.33,67:63.33,68:93,69:null,70:null},direction:'>',owner:'จริยาพร'},
{pillar:'P4',kpiGroup:'28. Green & Clean Hospital',level:'I',kpi:'Green & Clean Hospital Challenge',okr:'ได้รับมาตรฐาน Green & Clean Hospital Excellence',target:{66:'ระดับดีมากPlus',67:'ระดับดีมากPlus',68:'ระดับดีมากPlus',69:'ระดับดีมากPlus',70:'ระดับดีมากPlus'},actual:{66:null,67:null,68:'ผ่านเกณฑ์มาตรฐาน',69:null,70:null},direction:'text',owner:'อรวรรณ'},
{pillar:'P4',kpiGroup:'29. นโยบาย EMS',level:'I',kpi:'นโยบาย EMS',okr:'ผ่านการประเมินการพัฒนาสถานบริการตามนโยบาย EMS >80%',target:{66:'> 80',67:'> 85',68:'> 90',69:'> 95',70:'> 100'},actual:{66:'รอรับการประเมิน',67:92.86,68:92.86,69:null,70:null},direction:'special',
evalFn:(a,t)=>{ const aNum=parseFloat(a); if(isNaN(aNum))return String(a).includes('รอ')?'ไม่บรรลุ':'ไม่มีข้อมูล'; const tNum=parseFloat(String(t).replace(/[^\d.]/g,'')); if(isNaN(tNum))return'ไม่มีข้อมูล'; return aNum>tNum?'บรรลุ':aNum>=tNum*0.9?'ใกล้บรรลุ':'ไม่บรรลุ'; }, owner:'นิภารัตน์'},
{pillar:'P4',kpiGroup:'30. มาตรฐาน HA',level:'R',kpi:'มาตรฐาน HA',okr:'ผ่านมาตรฐาน HA',target:{66:'ผ่านการรับรอง',67:'ผ่านการรับรอง',68:'ผ่านการรับรอง',69:'ผ่านการรับรอง',70:'ผ่านการรับรอง'},actual:{66:'ผ่านการรับรอง',67:'ผ่านการรับรอง',68:'ผ่านการรับรอง',69:null,70:null},direction:'text',owner:'สุวิทย์'},
{pillar:'P4',kpiGroup:'31. คลินิก COPD/Asthma คุณภาพ',level:'I',kpi:'คลินิก COPD/Asthma คุณภาพ',okr:'ผ่านเกณฑ์ประเมิน easy asthma clinic',target:{66:'ผ่านเกณฑ์ประเมิน easy asthma clinic',67:'ผ่านเกณฑ์ประเมิน easy asthma clinic',68:'ผ่านเกณฑ์ประเมิน easy asthma clinic',69:'ผ่านเกณฑ์ประเมิน easy asthma clinic',70:'ผ่านเกณฑ์ประเมิน easy asthma clinic'},actual:{66:'ผ่านเกณฑ์',67:'ผ่านเกณฑ์',68:'ผ่านเกณฑ์',69:null,70:null},direction:'text',owner:'กมลวรรณ'},
{pillar:'P4',kpiGroup:'32. คลินิก NCD plus คุณภาพ',level:'R',kpi:'คลินิก NCD plus คุณภาพ',okr:'ผ่านเกณฑ์ NCD Clinic Award',target:{66:'One stop service',67:'Tele',68:'Tele',69:'Self health care clinic',70:'เครือข่ายสุขภาพเข้มแข็ง'},actual:{66:'ผ่าน',67:'ผ่าน',68:'ระดับดีมาก',69:null,70:null},direction:'text',owner:'ศศิธร'},
{pillar:'P4',kpiGroup:'32. คลินิก NCD plus คุณภาพ',level:'I',kpi:'คลินิก NCD plus คุณภาพ',okr:'ระยะเวลารอคอยภายในคลินิก <2ชม.',target:{66:150,67:140,68:130,69:120,70:110},actual:{66:61.5,67:60.2,68:59.6,69:null,70:null},direction:'<',owner:null},
{pillar:'P4',kpiGroup:'33. คลินิกจิตเวชและยาเสพติด คุณภาพ',level:'U',kpi:'คลินิกจิตเวชและยาเสพติด คุณภาพ',okr:'ผ่านมาตรฐานบริการสุขภาพจิตและจิตเวช ระดับ 1',target:{66:2,67:2,68:2,69:1,70:1},actual:{66:'เตรียมการ',67:2,68:2,69:null,70:null},direction:'special',
evalFn:(a,t)=>{ const aNum=parseFloat(a); const tNum=parseFloat(t); if(!isNaN(aNum)&&!isNaN(tNum))return aNum>=tNum?'บรรลุ':'ไม่บรรลุ'; const aStr=String(a); if(aStr.includes('เตรียม'))return'ไม่บรรลุ'; return'ไม่มีข้อมูล'; }, owner:'มนสิชา'},
{pillar:'P4',kpiGroup:'33. คลินิกจิตเวชและยาเสพติด คุณภาพ',level:'U',kpi:'คลินิกจิตเวชและยาเสพติด คุณภาพ',okr:'ผ่านมาตรฐานบริการยาเสพติด',target:{66:'ผ่าน',67:'ผ่าน',68:'ผ่าน',69:'ผ่าน',70:'ผ่าน'},actual:{66:'ผ่าน',67:'ผ่าน',68:'ผ่าน',69:null,70:null},direction:'text',owner:null},
{pillar:'P4',kpiGroup:'34. คลินิก HIV คุณภาพ',level:'I',kpi:'คลินิก HIV คุณภาพ',okr:'ผ่านการประเมินหน่วยบริการคุณภาพโรคเอดส์',target:{66:'เตรียม',67:'เตรียม',68:'เตรียม',69:'ผ่านการรับรอง',70:'ผ่านการรับรอง'},actual:{66:'อยู่ระหว่างการเตรียม',67:'อยู่ระหว่างการเตรียม',68:'อยู่ระหว่างการเตรียม',69:null,70:null},direction:'text',owner:'สุวิทย์'},
{pillar:'P4',kpiGroup:'35. Premium service',level:'I',kpi:'การจัดตั้ง Premium service',okr:'การจัดตั้ง Premium service',target:{66:'ห้องตรวจพิเศษ',67:'หอผู้ป่วยพิเศษ',68:null,69:null,70:null},actual:{66:'ห้องตรวจพิเศษ',67:'หอพิเศษ',68:'หอพิเศษ',69:null,70:null},direction:'special',
evalFn:(a,t,aStr)=>{ if(!aStr||aStr==='–')return'ไม่มีข้อมูล'; const passW=['พิเศษ','ผ่าน','จัดตั้ง','มี']; const failW=['ยังไม่','ไม่ได้']; if(passW.some(w=>aStr.includes(w))&&!failW.some(w=>aStr.includes(w)))return'บรรลุ'; return'ไม่มีข้อมูล'; }, owner:'กมลวรรณ'},
{pillar:'P4',kpiGroup:'36. ห้องยาคุณภาพ',level:'R',kpi:'ห้องยาคุณภาพ',okr:'ผ่านเกณฑ์ความปลอดภัยด้านยา',target:{66:'ระดับ 3',67:'ระดับ 3',68:'ระดับ 4',69:'ระดับ 4',70:'ระดับ 4'},actual:{66:3,67:3,68:4,69:null,70:null},direction:'special',
evalFn:(a,t)=>{ const aNum=parseFloat(a); const tNum=parseFloat(String(t).replace(/[^\d.]/g,'')); if(!isNaN(aNum)&&!isNaN(tNum))return aNum>=tNum?'บรรลุ':aNum>=tNum-1?'ใกล้บรรลุ':'ไม่บรรลุ'; return'ไม่มีข้อมูล'; }, owner:'สุนิสา'},
{pillar:'P4',kpiGroup:'37. บริการกายภาพบำบัดคุณภาพ',level:'I',kpi:'บริการกายภาพบำบัดคุณภาพ',okr:'ผ่านเกณฑ์มาตรฐานกายภาพบำบัด',target:{66:'ดีเยี่ยม',67:null,68:null,69:'ผ่านการรับรองคุณภาพบริการกายภาพบำบัดจากสภากายภาพบำบัด',70:null},actual:{66:'ระดับดี',67:'ระดับดี',68:'ระดับดี',69:null,70:null},direction:'text',owner:'ทิพวรรณ'},
{pillar:'P4',kpiGroup:'37. บริการกายภาพบำบัดคุณภาพ',level:'P',kpi:'บริการกายภาพบำบัดคุณภาพ',okr:'พัฒนาศูนย์ฟื้นฟูผู้สูงอายุและผู้ป่วยระยะกลาง',target:{66:'จัดตั้งศูนย์ฟื้นฟูผู้สูงอายุและผู้ป่วยระยะกลาง',67:null,68:null,69:'มีศูนย์ฟื้นฟูผู้สูงอายุและผู้ป่วยระยะกลางให้บริการ',70:null},actual:{66:'ยังไม่ได้จัดตั้งศูนย์',67:'ยังไม่ได้จัดตั้งศูนย์',68:'ยังไม่ได้จัดตั้งศูนย์',69:null,70:null},direction:'text',owner:null},
{pillar:'P4',kpiGroup:'38. มาตรฐานห้องปฏิบัติการ (LA) และรังสีวินิจฉัย',level:'U',kpi:'มาตรฐาน LA และรังสีวินิจฉัย',okr:'ผ่านเกณฑ์คุณภาพตามมาตรฐานห้องปฏิบัติการ (LA)',target:{66:'ผ่าน (3 ปีครั้ง)',67:null,68:null,69:null,70:null},actual:{66:'ผ่าน',67:'ผ่าน',68:'ผ่าน',69:null,70:null},direction:'text',owner:'นันท์นภัส'},
{pillar:'P4',kpiGroup:'38. มาตรฐานห้องปฏิบัติการ (LA) และรังสีวินิจฉัย',level:'U',kpi:'มาตรฐาน LA และรังสีวินิจฉัย',okr:'ตรวจประเมินระบบบริหารคุณภาพห้องปฏิบัติการรังสีวินิจฉัย',target:{66:'ผ่าน',67:'ผ่าน',68:'ผ่าน',69:'ผ่าน',70:'ผ่าน'},actual:{66:'ผ่าน',67:'ผ่าน',68:'ผ่าน',69:null,70:null},direction:'text',owner:'วรรณ์สิริย์'},
{pillar:'P4',kpiGroup:'38. มาตรฐานห้องปฏิบัติการ (LA) และรังสีวินิจฉัย',level:'U',kpi:'มาตรฐาน LA และรังสีวินิจฉัย',okr:'มีใบอนุญาตมีไว้ในครอบครองหรือใช้เครื่องกำเนิดรังสี',target:{66:'มี',67:'มี',68:'มี',69:'มี',70:'มี'},actual:{66:'มี',67:'มี',68:'มี',69:null,70:null},direction:'text',owner:null},
{pillar:'P4',kpiGroup:'39. คลินิกวัยรุ่น',level:'U',kpi:'คลินิกวัยรุ่น',okr:'ผ่านเกณฑ์คุณภาพตามมาตรฐานคลินิกวัยรุ่น',target:{66:'ผ่าน',67:'รอปรับตามเกณฑ์ กรมอนามัย',68:null,69:null,70:null},actual:{66:'รอรับการประเมิน',67:'รอรับการประเมิน',68:'รอรับการประเมิน',69:null,70:null},direction:'text',owner:'เสาวณีย์'},
{pillar:'P4',kpiGroup:'40. บริการแพทย์แผนไทยคุณภาพ',level:'U',kpi:'บริการแพทย์แผนไทยคุณภาพ',okr:'มาตรฐาน รพ.สส.พท',target:{66:'ดีมาก',67:'ดีมาก',68:'ดีเยี่ยม',69:'ดีเยี่ยม',70:'ดีเยี่ยม'},actual:{66:'รอรับการประเมิน',67:'ดีเยี่ยม',68:'ดีเยี่ยม',69:null,70:null},direction:'text',owner:'ไพลิน'},
{pillar:'P4',kpiGroup:'40. บริการแพทย์แผนไทยคุณภาพ',level:'U',kpi:'บริการแพทย์แผนไทยคุณภาพ',okr:'ร้อยละของผู้ป่วยนอกที่ได้รับบริการการแพทย์แผนไทยและการแพทย์ทางเลือก > 16%',target:{66:16,67:16,68:18,69:18,70:20},actual:{66:15.37,67:9.7,68:76.85,69:null,70:null},direction:'>',owner:null},
{pillar:'P4',kpiGroup:'41. ห้องฉุกเฉินคุณภาพ',level:'U',kpi:'ห้องฉุกเฉินคุณภาพ',okr:'ผ่านเกณฑ์ห้องฉุกเฉินคุณภาพ ≥50 คะแนน',target:{66:50,67:60,68:60,69:60,70:70},actual:{66:56.6,67:null,68:60.1,69:null,70:null},direction:'>',owner:'จำรูญศรี'},
{pillar:'P4',kpiGroup:'41. ห้องฉุกเฉินคุณภาพ',level:'U',kpi:'ห้องฉุกเฉินคุณภาพ',okr:'ระดับความพึงพอใจของผู้รับบริการแผนก ER >80%',target:{66:80,67:80,68:80,69:80,70:80},actual:{66:89.38,67:null,68:88.51,69:null,70:null},direction:'>',owner:null},
{pillar:'P4',kpiGroup:'42. หอผู้ป่วยในคุณภาพ',level:'U',kpi:'หอผู้ป่วยในคุณภาพ',okr:'ระดับความพึงพอใจ >90%',target:{66:90,67:90,68:90,69:90,70:90},actual:{66:88.46,67:null,68:88.58,69:null,70:null},direction:'>',owner:'กมลวรรณ'},
{pillar:'P4',kpiGroup:'42. หอผู้ป่วยในคุณภาพ',level:'U',kpi:'หอผู้ป่วยในคุณภาพ',okr:'ข้อร้องเรียน',target:{66:0,67:0,68:0,69:0,70:0},actual:{66:0,67:0,68:0,69:null,70:null},direction:'=',owner:null},
{pillar:'P4',kpiGroup:'42. หอผู้ป่วยในคุณภาพ',level:'U',kpi:'หอผู้ป่วยในคุณภาพ',okr:'case manager',target:{66:4,67:5,68:6,69:8,70:10},actual:{66:null,67:5,68:3,69:null,70:null},direction:'>',owner:null},
{pillar:'P4',kpiGroup:'43. OPD คุณภาพ',level:'U',kpi:'OPD คุณภาพ',okr:'ผ่านการรับรอง OPD คุณภาพ',target:{66:'ผ่าน',67:'ผ่าน',68:'ผ่าน',69:'ผ่าน',70:'ผ่าน'},actual:{66:'ผ่าน',67:'ผ่าน',68:'ผ่าน',69:null,70:null},direction:'text',owner:'ศศิธร'},
{pillar:'P4',kpiGroup:'43. OPD คุณภาพ',level:'U',kpi:'OPD คุณภาพ',okr:'ระดับความพึงพอใจ >85%',target:{66:90,67:91,68:92,69:93,70:94},actual:{66:89.46,67:91.5,68:92.3,69:null,70:null},direction:'>',owner:null},
{pillar:'P4',kpiGroup:'43. OPD คุณภาพ',level:'U',kpi:'OPD คุณภาพ',okr:'ระยะเวลารอคอย <60 นาที',target:{66:56,67:54,68:52,69:50,70:48},actual:{66:56,67:45.43,68:48.25,69:null,70:null},direction:'<',owner:null},
{pillar:'P4',kpiGroup:'44. LR คุณภาพ',level:'U',kpi:'LR คุณภาพ',okr:'ผ่านการรับรองห้องคลอดคุณภาพ',target:{66:'ผ่าน',67:'ผ่าน',68:'ผ่าน',69:'ผ่าน',70:'ผ่าน'},actual:{66:'ผ่าน',67:'ผ่าน',68:'ผ่าน',69:null,70:null},direction:'text',owner:'ณาตยา'},
// ─── P5 ───────────────────────────────────────────────
{pillar:'P5',kpiGroup:'45. การขับเคลื่อน พชอ.',level:'R',kpi:'การขับเคลื่อนงาน พชอ.',okr:'ร้อยละแผนงานโครงการที่ชุมชนมีส่วนร่วม > 50%',target:{66:'ผ่าน',67:'ผ่าน',68:'ผ่าน',69:'ผ่าน',70:'ผ่าน'},actual:{66:'ผ่าน',67:'ผ่าน',68:'ผ่าน',69:null,70:null},direction:'text',owner:'สาลี'},
// ─── P6 ───────────────────────────────────────────────
{pillar:'P6',kpiGroup:'46. ระดับความเสี่ยงทางการเงิน',level:'R',kpi:'ระดับความเสี่ยงทางการเงิน (Risk Score)',okr:'ระดับความเสี่ยงทางการเงิน (Risk Score) ≤ 3',target:{66:0,67:0,68:0,69:0,70:0},actual:{66:1,67:1,68:1,69:null,70:null},direction:'special',
evalFn:(a)=>{ const n=parseFloat(a); if(isNaN(n))return'ไม่มีข้อมูล'; return n<=3?'บรรลุ':'ไม่บรรลุ'; }, owner:'นิภารัตน์'},
{pillar:'P6',kpiGroup:'47. CMI',level:'U',kpi:'CMI',okr:'การเพิ่มขึ้นของค่า CMI >=0.6',target:{66:0.5,67:0.52,68:0.56,69:0.58,70:0.6},actual:{66:0.56,67:0.58,68:0.6434,69:null,70:null},direction:'>',owner:'สริยาภรณ์'},
{pillar:'P6',kpiGroup:'48. อัตราครองเตียง',level:'U',kpi:'อัตราครองเตียง',okr:'อัตราการครองเตียง >85%',target:{66:'>85',67:'>85',68:'>85',69:'>85',70:'>85'},actual:{66:52.47,67:66.48,68:73.76,69:null,70:null},direction:'special',
evalFn:(a)=>{ const n=parseFloat(a); if(isNaN(n))return'ไม่มีข้อมูล'; return n>=85?'บรรลุ':n>=75?'ใกล้บรรลุ':'ไม่บรรลุ'; }, owner:'สริยาภรณ์'},
{pillar:'P6',kpiGroup:'49. Sum Adj RW',level:'U',kpi:'Sum Adj RW',okr:'Sum Adj-RW >1,400',target:{66:1200,67:1250,68:1300,69:1350,70:1400},actual:{66:1646,67:2254,68:2584,69:null,70:null},direction:'>',owner:'สริยาภรณ์'},
{pillar:'P6',kpiGroup:'50. Current ratio, cash ratio, Quick ratio',level:'U',kpi:'Current ratio / QR / Cash ratio',okr:'QR ≥1',target:{66:1,67:1,68:1,69:1,70:1},actual:{66:3.41,67:5.02,68:1.94,69:null,70:null},direction:'>',owner:'นิภารัตน์'},
{pillar:'P6',kpiGroup:'50. Current ratio, cash ratio, Quick ratio',level:'U',kpi:'Current ratio / QR / Cash ratio',okr:'Current ratio ≥1.5',target:{66:1.5,67:1.5,68:1.5,69:1.5,70:1.5},actual:{66:3.53,67:5.41,68:2.32,69:null,70:null},direction:'>',owner:null},
{pillar:'P6',kpiGroup:'50. Current ratio, cash ratio, Quick ratio',level:'U',kpi:'Current ratio / QR / Cash ratio',okr:'Cash ratio ≥0.8',target:{66:0.8,67:0.8,68:0.8,69:0.8,70:0.8},actual:{66:3.13,67:4.09,68:1.1,69:null,70:null},direction:'>',owner:null},
{pillar:'P6',kpiGroup:'51. EBIDA',level:'U',kpi:'EBIDA',okr:'EBIDA >3,000,000 บาท',target:{66:'>3000000',67:'>3000000',68:'>3000000',69:'>3000000',70:'>3000000'},actual:{66:-11665498.65,67:-7154891.12,68:-16409487.77,69:null,70:null},direction:'>',owner:'นิภารัตน์'},
{pillar:'P6',kpiGroup:'52. OPD/IPD unit cost',level:'P',kpi:'OPD/IPD unit cost',okr:'OPD unit cost (บาท)',target:{66:1100,67:1100,68:1100,69:1100,70:1100},actual:{66:674.69,67:898.02,68:805.77,69:null,70:null},direction:'<',owner:'พรทิพย์'},
{pillar:'P6',kpiGroup:'52. OPD/IPD unit cost',level:'P',kpi:'OPD/IPD unit cost',okr:'IPD unit cost (บาท)',target:{66:26000,67:26000,68:26000,69:26000,70:26000},actual:{66:25646.09,67:15127.64,68:17628.9,69:null,70:null},direction:'<',owner:'พรทิพย์'},
{pillar:'P6',kpiGroup:'53. ค่าสาธารณูปโภค',level:'I',kpi:'ค่าสาธารณูปโภค',okr:'หน่วยการใช้ไฟฟ้าลดลงปีละ >= 5%',target:{66:'>5%',67:'>5%',68:'>5%',69:'>5%',70:'>5%'},actual:{66:311.1,67:302.52,68:233.67,69:null,70:null},direction:'special',
evalFn:(a,t,aStr,tStr,row,year)=>{
const prev={67:311.1,68:302.52};
const yr=year||68;
const aNum=parseFloat(a);
const prevNum=prev[yr];
if(isNaN(aNum)||prevNum===undefined)return'ไม่มีข้อมูล';
const pctDrop=(prevNum-aNum)/prevNum*100;
if(pctDrop>=5)return'บรรลุ';
if(pctDrop>=0)return'ใกล้บรรลุ';
return'ไม่บรรลุ';
}, owner:'นิภารัตน์'},
// ─── P7 ───────────────────────────────────────────────
{pillar:'P7',kpiGroup:'54. ร้อยละความพึงพอใจของบุคลากร',level:'R',kpi:'ร้อยละความพึงพอใจของบุคลากร',okr:'ร้อยละความพึงพอใจของบุคลากร >70',target:{66:70,67:75,68:80,69:85,70:90},actual:{66:75.08,67:null,68:81.64,69:null,70:null},direction:'>',owner:'ทีม HRD'},
{pillar:'P7',kpiGroup:'55. อัตราความคงอยู่ของบุคลากร',level:'R',kpi:'อัตราความคงอยู่ของบุคลากรด้านสุขภาพ',okr:'อัตราความคงอยู่ของบุคลากรด้านสุขภาพ >70',target:{66:70,67:72,68:74,69:76,70:78},actual:{66:98.03,67:98.25,68:97.25,69:null,70:null},direction:'>',owner:'ทีม HRD'},
{pillar:'P7',kpiGroup:'56. CQI/R2R/Innovation/งานวิจัย',level:'R',kpi:'จำนวน CQI/R2R/Innovation/งานวิจัย',okr:'จำนวน CQI/R2R/Innovation/งานวิจัย >5 เรื่อง/ปี',target:{66:5,67:5,68:5,69:5,70:5},actual:{66:7,67:9,68:15,69:null,70:null},direction:'>',owner:'ทีม HRD'}
];
// ════════════════════════════════════════════════════════════
// PILLAR METADATA
// ════════════════════════════════════════════════════════════
const PILLAR_META = {
P1: { name:'ลดการเจ็บป่วยรายใหม่ในโรคยุทธศาสตร์ทุกกลุ่มวัย', icon:'🦠', color:'#0d9488' },
P2: { name:'ลดการป่วยตายในโรคยุทธศาสตร์ในทุกกลุ่มวัย', icon:'❤️🩹', color:'#dc2626' },
P3: { name:'ระบบเทคโนโลยีและสารสนเทศได้มาตรฐานสากล', icon:'💻', color:'#2563eb' },
P4: { name:'หน่วยบริการทุกระดับผ่านเกณฑ์คุณภาพ', icon:'🏥', color:'#7c3aed' },
P5: { name:'เพิ่มศักยภาพภาคีเครือข่ายระดับอำเภอ', icon:'🤝', color:'#059669' },
P6: { name:'โรงพยาบาลมีเสถียรภาพด้านการเงินการคลัง', icon:'💰', color:'#d97706' },
P7: { name:'บุคลากรเป็นเลิศ', icon:'👥', color:'#db2777' }
};
// ════════════════════════════════════════════════════════════
// STATUS EVALUATION LOGIC
// direction: '>' = higher better, '<' = lower better
// '=' = must equal target, 'text' = qualitative
// ════════════════════════════════════════════════════════════
function evalStatus(row, year = 68) {
const t = row.target[year];
const a = row.actual[year];
if (a === null || a === undefined || a === '') return 'ไม่มีข้อมูล';
const aStr = String(a).trim();
const tStr = t !== null && t !== undefined ? String(t).trim() : '';
// ── Special cases: direction = 'special' ──────────────────────────
// ใช้ evalFn แทน direction เพื่อ handle logic เฉพาะรายตัวชี้วัด
if (typeof row.evalFn === 'function') {
return row.evalFn(a, t, aStr, tStr, row, year);
}
// ── Qualitative / text ──────────────────────────────────────────
if (row.direction === 'text') {
const passWords = ['ผ่าน','ระดับดี','ดีเยี่ยม','ดีมาก','ดี','ระดับ','มี'];
const failWords = ['รอรับ','ยังไม่','ไม่ได้','เตรียม','อยู่ระหว่าง'];
const goodMatch = passWords.some(w => aStr.includes(w));
const failMatch = failWords.some(w => aStr.includes(w));
if (goodMatch && !failMatch) return 'บรรลุ';
if (failMatch) return 'ไม่บรรลุ';
return 'ไม่มีข้อมูล';
}
// ── Must equal ──────────────────────────────────────────────────
if (row.direction === '=') {
if (tStr === '' || t === null) return 'ไม่มีข้อมูล';
const aNum = parseFloat(aStr);
const tNum = parseFloat(tStr);
if (!isNaN(aNum) && !isNaN(tNum)) return aNum === tNum ? 'บรรลุ' : 'ไม่บรรลุ';
return aStr === tStr ? 'บรรลุ' : 'ไม่บรรลุ';
}
// ── Numeric: higher better / lower better ───────────────────────
const aNum = parseFloat(aStr);
if (isNaN(aNum)) return 'ไม่มีข้อมูล';
const tClean = tStr.replace(/[≥≤<>]/g,'').replace(/%/g,'').trim();
const tNum = parseFloat(tClean);
if (isNaN(tNum)) return 'ไม่มีข้อมูล';
if (row.direction === '>') {
if (aNum >= tNum) return 'บรรลุ';
if (aNum >= tNum * 0.9) return 'ใกล้บรรลุ';
return 'ไม่บรรลุ';
}
if (row.direction === '<') {
if (aNum <= tNum) return 'บรรลุ';
if (aNum <= tNum * 1.1) return 'ใกล้บรรลุ';
return 'ไม่บรรลุ';
}
return 'ไม่มีข้อมูล';
}
function getTrend(row) {
const a66 = parseFloat(row.actual[66]);
const a67 = parseFloat(row.actual[67]);
const a68 = parseFloat(row.actual[68]);
const vals = [a66, a67, a68].filter(v => !isNaN(v));
if (vals.length < 2) return '➖';
const last = vals[vals.length - 1];
const first = vals[0];
const diff = last - first;
if (row.direction === '>') return diff > 0.5 ? '📈' : diff < -0.5 ? '📉' : '➡️';
if (row.direction === '<') return diff < -0.5 ? '📈' : diff > 0.5 ? '📉' : '➡️';
return '➡️';
}
function getGap(row, year = 68) {
const a = row.actual[year];
const t = row.target[year];
if (a === null || t === null || row.direction === 'text' || row.direction === '=') return null;
const aNum = parseFloat(String(a).replace(/[≥≤<>%]/g,''));
const tNum = parseFloat(String(t).replace(/[≥≤<>%]/g,''));
if (isNaN(aNum) || isNaN(tNum)) return null;
return aNum - tNum;
}
function fmtNum(v) {
if (v === null || v === undefined) return '–';
if (typeof v === 'string') return v;
if (typeof v === 'number') {
if (Number.isInteger(v)) return v.toLocaleString('th-TH');
return v.toLocaleString('th-TH', {maximumFractionDigits:2});
}
return String(v);
}
// ════════════════════════════════════════════════════════════
// COMPUTE SUMMARY
// ════════════════════════════════════════════════════════════
function computeSummary() {
/*
Logic:
- totalKPI = RAW_DATA.length (each row is one OKR/sub-indicator)
- has68 = rows where actual[68] is not null
- achieved68 = rows where evalStatus(row,68) === 'บรรลุ'
- near68 = rows where evalStatus === 'ใกล้บรรลุ'
- fail68 = rows where evalStatus === 'ไม่บรรลุ'
*/
const total = RAW_DATA.length;
const pillars = 7;
const has68 = RAW_DATA.filter(r => r.actual[68] !== null && r.actual[68] !== undefined && r.actual[68] !== '').length;
const statuses = RAW_DATA.map(r => evalStatus(r, 68));
const achieved = statuses.filter(s => s === 'บรรลุ').length;
const near = statuses.filter(s => s === 'ใกล้บรรลุ').length;
const fail = statuses.filter(s => s === 'ไม่บรรลุ').length;
const noData = statuses.filter(s => s === 'ไม่มีข้อมูล').length;
return { total, pillars, has68, achieved, near, fail, noData };
}
// ════════════════════════════════════════════════════════════
// BUILD SUMMARY CARDS
// ════════════════════════════════════════════════════════════
function buildSummary() {
const s = computeSummary();
const grid = document.getElementById('summaryGrid');
const cards = [
{ num: s.pillars, label: 'ยุทธศาสตร์ P1–P7', cls: '' },
{ num: s.total, label: 'ตัวชี้วัด/OKRs ทั้งหมด', cls: '' },
{ num: s.has68, label: 'มีผลดำเนินงานปี 2568', cls: 'accent' },
{ num: s.achieved, label: '✅ บรรลุเป้าหมายปี 2568', cls: 'good' },
{ num: s.near, label: '🟡 ใกล้บรรลุ (±10%)', cls: 'warn' },
{ num: s.fail, label: '❌ ไม่บรรลุ / ต้องเร่งพัฒนา', cls: 'bad' },
{ num: s.noData, label: '➖ ไม่มีข้อมูลปี 2568', cls: '' },
];
grid.innerHTML = cards.map(c => `
<div class="summary-card ${c.cls}">
<div class="summary-num">${c.num}</div>
<div class="summary-label">${c.label}</div>
</div>
`).join('');
}
// ════════════════════════════════════════════════════════════
// BUILD PILLAR CARDS
// ════════════════════════════════════════════════════════════
function buildPillars() {
const grid = document.getElementById('pillarGrid');
const pillars = ['P1','P2','P3','P4','P5','P6','P7'];
grid.innerHTML = pillars.map(p => {
const rows = RAW_DATA.filter(r => r.pillar === p);
const statuses = rows.map(r => evalStatus(r, 68));
const good = statuses.filter(s => s === 'บรรลุ').length;
const near = statuses.filter(s => s === 'ใกล้บรรลุ').length;
const fail = statuses.filter(s => s === 'ไม่บรรลุ').length;
const hasData = rows.filter(r => r.actual[68] !== null && r.actual[68] !== undefined && r.actual[68] !== '').length;
const total = rows.length;
const pct = total > 0 ? Math.round((good / total) * 100) : 0;
let overallStatus = 'status-na';
let statusLabel = 'ไม่มีข้อมูล';
if (hasData > 0) {
if (fail > good && fail > 1) { overallStatus = 'status-bad'; statusLabel = 'ต้องเร่งพัฒนา'; }
else if (good >= fail) { overallStatus = 'status-good'; statusLabel = 'ดี'; }
else { overallStatus = 'status-warn'; statusLabel = 'เฝ้าระวัง'; }
}
const meta = PILLAR_META[p];
const highlights = getPillarHighlights(p, rows);
return `
<div class="pillar-card">
<div class="pillar-header">
<div class="pillar-icon" style="background:${meta.color};">${meta.icon}</div>
<div class="pillar-info">
<div class="pillar-code">${p}</div>
<div class="pillar-name">${meta.name}</div>
</div>
<div class="pillar-status ${overallStatus}">${statusLabel}</div>
</div>
<div class="pillar-body">
<div class="pillar-stats">
<div class="pillar-stat"><div class="pillar-stat-num">${total}</div><div class="pillar-stat-lbl">OKRs</div></div>
<div class="pillar-stat"><div class="pillar-stat-num" style="color:var(--good)">${good}</div><div class="pillar-stat-lbl">บรรลุ</div></div>
<div class="pillar-stat"><div class="pillar-stat-num" style="color:var(--warn)">${near}</div><div class="pillar-stat-lbl">ใกล้บรรลุ</div></div>
<div class="pillar-stat"><div class="pillar-stat-num" style="color:var(--bad)">${fail}</div><div class="pillar-stat-lbl">ไม่บรรลุ</div></div>
</div>
<div class="progress-bar-wrap">
<div class="progress-bar-bg">
<div class="progress-bar-fill" style="width:${pct}%; background:linear-gradient(to right,${meta.color},${meta.color}aa)"></div>
</div>
<div class="progress-label"><span>ความสำเร็จปี 2568</span><span>${pct}% (${good}/${total})</span></div>
</div>
<ul class="pillar-highlights">
${highlights.map(h => `<li><span class="hl-dot" style="background:${meta.color}"></span>${h}</li>`).join('')}
</ul>
</div>
</div>`;
}).join('');
}
function getPillarHighlights(p, rows) {
const hl = [];
if (p === 'P1') {
hl.push('HIV PrEP/PEP: ผลดำเนินงาน ปี 66–68 สูงเกิน 99% ต่อเนื่อง (เป้า ≥75%)');
hl.push('TB: อัตราสำเร็จรักษา 91–100% เกินเป้า ≥85%');
hl.push('Depression: เข้าถึงบริการปี 2568 = 100% (เป้า 85%)');
hl.push('DM ใหม่: ปี 66 ต่ำกว่าเป้า 1.16% ดี แต่ปี 67 สูงขึ้น 2.72% ต้องเฝ้าระวัง');
hl.push('Sepsis Antibiotic: ปรับปรุงต่อเนื่อง 68.85% → 90.41% บรรลุเป้าปี 2568');
} else if (p === 'P2') {
hl.push('COPD/Asthma: อัตราป่วยตายในโรงพยาบาล = 0% ทุกปี (ดีเยี่ยม)');
hl.push('DHF: ไม่มีผู้เสียชีวิต, DSS = 0 ทุกปี');
hl.push('Depression: อัตราฆ่าตัวตายสำเร็จ = 0 (ต่ำกว่าเป้า <2.5)');
hl.push('TB: อัตราตายยังเกินเป้า <5% → ปี 66: 7.33%, ปี 68: 11.71% ต้องเร่งแก้ไข');
hl.push('Stroke fast tract ส่งต่อภายใน 30 นาที: ปี 68 = 42.3% (เป้า 60%) ยังต่ำกว่าเป้า');
} else if (p === 'P3') {
hl.push('HIMSS ระดับ 4 ทำได้ปี 66–67 แต่ปี 68 "ยังไม่รับการประเมิน"');
hl.push('ระบบพัสดุ: เร่งพัฒนาจาก 12.12% (ปี 66) สู่ 88.88% (ปี 68) ก้าวหน้ามาก');
hl.push('HAIT: รอรับการประเมินทุกปี ยังไม่ผ่านการประเมินตามเป้า');
} else if (p === 'P4') {
hl.push('HA: ผ่านการรับรองต่อเนื่องปี 66–68 (จุดแข็งสำคัญ)');
hl.push('OPD คุณภาพ: ผ่านรับรองทุกปี ความพึงพอใจ ≥89% และ waiting time ดีขึ้น');
hl.push('NCD Clinic: ระดับดีมากปี 2568, เวลารอคอย 59.6 นาที (เป้า <130)');
hl.push('HS4: ก้าวกระโดดจาก 25.33% (ปี 66) สู่ 93% (ปี 68) เกินเป้า 80%');
hl.push('LR / COPD Clinic / Lab คุณภาพ: ผ่านเกณฑ์ทุกปีต่อเนื่อง');
} else if (p === 'P5') {
hl.push('ร้อยละแผนงานชุมชนมีส่วนร่วม: ผ่านเป้าหมาย "ผ่าน" ทุกปี 66–68');
hl.push('มีการบูรณาการงาน พชอ. ต่อเนื่องครบทุกปีที่ติดตาม');
} else if (p === 'P6') {
hl.push('Sum Adj-RW ก้าวกระโดด: 1,611 → 2,581 (เป้า 1,300) เกินเป้าหมายมาก');
hl.push('CMI ปี 2568 = 0.6434 เกินเป้า 0.56 (สะท้อนคุณภาพงาน IPD)');
hl.push('สภาพคล่อง: QR 3.34, Current ratio 3.67 สูงกว่าเป้าหมายอย่างมั่นคง');
hl.push('Risk Score ปี 68 = 1.17 (ยังอยู่ระดับต่ำ ≤3 = ปลอดภัย)');
hl.push('อัตราครองเตียง 73–76% (ต่ำกว่าเป้า >100%) ต้องพัฒนางาน IPD');
} else if (p === 'P7') {
hl.push('ความพึงพอใจบุคลากร: 75.08% (ปี 66), 81.64% (ปี 68) > เป้า 70–80%');
hl.push('อัตราคงอยู่บุคลากร: 97–98% เกินเป้าหมาย 70–74% อย่างชัดเจน');
hl.push('CQI/R2R/Innovation: ก้าวหน้าต่อเนื่อง 7 → 9 → 15 เรื่อง/ปี เกินเป้า 5 เรื่อง');
}
return hl;
}
// ════════════════════════════════════════════════════════════
// BUILD TABLE
// ════════════════════════════════════════════════════════════
let currentData = [...RAW_DATA];
let sortKey = null;
let sortAsc = true;
function buildTable(data) {
const tbody = document.getElementById('tableBody');
document.getElementById('filterCount').textContent = `แสดง ${data.length} รายการ จาก ${RAW_DATA.length}`;
tbody.innerHTML = data.map(row => {
const status68 = evalStatus(row, 68);
const trend = getTrend(row);
const gap = getGap(row, 68);
let gapHtml = '<span class="gap-na">–</span>';
if (gap !== null) {
const sign = gap > 0 ? '+' : '';
const cls = (row.direction === '<' && gap <= 0) || (row.direction === '>' && gap >= 0) ? 'gap-pos' : 'gap-neg';
gapHtml = `<span class="gap-val ${cls}">${sign}${fmtNum(gap)}</span>`;
}
const note = getNote(row);
return `
<tr>
<td><span style="font-weight:700;color:${PILLAR_META[row.pillar].color}">${row.pillar}</span></td>
<td><span class="badge-level badge-${row.level}">${row.level}</span></td>
<td class="td-kpi">${row.kpi}</td>
<td class="td-okr">${row.okr}</td>
<td class="td-year">${fmtNum(row.target[66])}</td>
<td class="td-year">${fmtNum(row.target[67])}</td>
<td class="td-year">${fmtNum(row.target[68])}</td>
<td class="td-year">${fmtNum(row.actual[66])}</td>
<td class="td-year">${fmtNum(row.actual[67])}</td>
<td class="td-year">${fmtNum(row.actual[68])}</td>
<td><span class="badge-status badge-${status68}">${status68}</span></td>
<td>${gapHtml}</td>
<td class="trend-arrow">${trend}</td>
<td style="font-size:12px;color:var(--text-muted);max-width:180px;">${note}</td>
</tr>`;
}).join('');
}
function getNote(row) {
const s = evalStatus(row, 68);
if (s === 'บรรลุ') return '✅ บรรลุเป้าหมายปี 2568';
if (s === 'ใกล้บรรลุ') return '🟡 ใกล้บรรลุ ควรติดตามต่อเนื่อง';
if (s === 'ไม่บรรลุ') return '❌ ต้องวางแผนปรับปรุง';
if (row.actual[68] === null || row.actual[68] === undefined) {
if (row.actual[67] !== null) return '⚠️ ขาดข้อมูลปี 68 (มีปี 67)';
return '➖ ยังไม่มีข้อมูลผลดำเนินงาน';
}
return '➖ ประเมินไม่ได้ (ตัวชี้วัดเชิงคุณภาพ)';
}
function applyFilters() {
const pillar = document.getElementById('filterPillar').value;
const level = document.getElementById('filterLevel').value;
const status = document.getElementById('filterStatus').value;
const search = document.getElementById('filterSearch').value.toLowerCase();
currentData = RAW_DATA.filter(row => {
if (pillar && row.pillar !== pillar) return false;
if (level && row.level !== level) return false;
if (status && evalStatus(row, 68) !== status) return false;
if (search) {
const haystack = (row.kpi + ' ' + row.okr + ' ' + row.kpiGroup).toLowerCase();
if (!haystack.includes(search)) return false;
}
return true;
});
if (sortKey) doSort();
else buildTable(currentData);
}
function resetFilters() {
document.getElementById('filterPillar').value = '';
document.getElementById('filterLevel').value = '';
document.getElementById('filterStatus').value = '';
document.getElementById('filterSearch').value = '';
currentData = [...RAW_DATA];
buildTable(currentData);
}
function sortTable(key) {
if (sortKey === key) sortAsc = !sortAsc;
else { sortKey = key; sortAsc = true; }
doSort();
}
function doSort() {
currentData.sort((a, b) => {
let va = a[sortKey] || '';
let vb = b[sortKey] || '';
if (sortKey === 'status') { va = evalStatus(a,68); vb = evalStatus(b,68); }
return sortAsc ? String(va).localeCompare(String(vb), 'th') : String(vb).localeCompare(String(va), 'th');
});
buildTable(currentData);
}
// ════════════════════════════════════════════════════════════
// BUILD CHARTS
// ════════════════════════════════════════════════════════════
let trendChart = null;
let selectedKpiIdx = 0;
// chart 1: status by pillar
function buildStatusChart() {
const pillars = ['P1','P2','P3','P4','P5','P6','P7'];
const achieved = [], near = [], fail = [], nodata = [];
pillars.forEach(p => {
const rows = RAW_DATA.filter(r => r.pillar === p);
const statuses = rows.map(r => evalStatus(r, 68));
achieved.push(statuses.filter(s => s === 'บรรลุ').length);
near.push(statuses.filter(s => s === 'ใกล้บรรลุ').length);
fail.push(statuses.filter(s => s === 'ไม่บรรลุ').length);
nodata.push(statuses.filter(s => s === 'ไม่มีข้อมูล').length);
});
const ctx = document.getElementById('chartStatus').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: pillars,
datasets: [
{ label: 'บรรลุ', data: achieved, backgroundColor: '#16a34a' },
{ label: 'ใกล้บรรลุ', data: near, backgroundColor: '#ca8a04' },
{ label: 'ไม่บรรลุ', data: fail, backgroundColor: '#dc2626' },
{ label: 'ไม่มีข้อมูล', data: nodata, backgroundColor: '#d1d5db' }
]
},
options: {
responsive: true, maintainAspectRatio: false,
plugins: { legend: { position: 'bottom', labels: { font: { family: 'Sarabun', size: 12 } } } },
scales: {
x: { stacked: true, ticks: { font: { family: 'Sarabun', size: 12 } } },
y: { stacked: true, ticks: { font: { family: 'Sarabun' } }, title: { display: true, text: 'จำนวนตัวชี้วัด', font: { family: 'Sarabun' } } }
}
}
});
}
// chart 2: trend for selected KPI
const TREND_KPIS = [
{ label:'HIV PrEP เริ่มใช้', row: RAW_DATA.find(r => r.okr.includes('PrEP/PEP') && r.okr.includes('เริ่มใช้ PrEP/PEP')) },
{ label:'DM ควบคุมน้ำตาล', row: RAW_DATA.find(r => r.okr.includes('ควบคุมระดับน้ำตาล')) },
{ label:'HT แทรกซ้อน', row: RAW_DATA.find(r => r.okr.includes('แทรกซ้อนรายใหม่ในผู้ป่วย HT')) },
{ label:'Sepsis AB', row: RAW_DATA.find(r => r.okr.includes('Antibiotic ภายใน 1 ชม')) },
{ label:'Sum Adj-RW', row: RAW_DATA.find(r => r.okr.includes('Adj-RW')) },
{ label:'ความพึงพอใจบุคลากร', row: RAW_DATA.find(r => r.okr.includes('ความพึงพอใจของบุคลากร')) },
{ label:'CQI/R2R', row: RAW_DATA.find(r => r.okr.includes('CQI/R2R')) },
{ label:'HS4', row: RAW_DATA.find(r => r.okr.includes('มาตรฐานระบบบริการสุขภาพ')) },
{ label:'CMI', row: RAW_DATA.find(r => r.okr.includes('CMI')) },
{ label:'TB อัตราตาย', row: RAW_DATA.find(r => r.okr.includes('ตายด้วยโรควัณโรค')) },
].filter(k => k.row);
function buildKpiSelector() {
const sel = document.getElementById('kpiSelector');
sel.innerHTML = TREND_KPIS.map((k, i) => `
<button class="kpi-chip ${i === 0 ? 'active' : ''}" onclick="selectKpi(${i})">${k.label}</button>
`).join('');
}
function selectKpi(idx) {
selectedKpiIdx = idx;
document.querySelectorAll('.kpi-chip').forEach((c, i) => c.classList.toggle('active', i === idx));
buildTrendChart();
}
function buildTrendChart() {
const k = TREND_KPIS[selectedKpiIdx];
const row = k.row;
const years = ['66','67','68'];
const targets = years.map(y => {
const t = row.target[parseInt(y)];
if (t === null || t === undefined) return null;
const num = parseFloat(String(t).replace(/[≥≤<>%]/g,''));
return isNaN(num) ? null : num;
});
const actuals = years.map(y => {
const a = row.actual[parseInt(y)];
if (a === null || a === undefined) return null;
const num = parseFloat(String(a));
return isNaN(num) ? null : num;
});
const ctx = document.getElementById('chartTrend').getContext('2d');
if (trendChart) trendChart.destroy();
trendChart = new Chart(ctx, {
type: 'line',
data: {
labels: years.map(y => `ปี 25${y}`),
datasets: [
{
label: 'เป้าหมาย', data: targets,
borderColor: '#94a3b8', borderDash: [6,3],
pointStyle: 'rectRot', pointRadius: 5,
backgroundColor: 'transparent'
},
{
label: 'ผลจริง', data: actuals,
borderColor: '#0d5c8f', borderWidth: 2.5,
pointRadius: 6, pointBackgroundColor: '#0d5c8f',
backgroundColor: 'rgba(13,92,143,0.08)',
fill: true, tension: 0.35
}
]
},
options: {
responsive: true, maintainAspectRatio: false,
plugins: {
title: { display: true, text: k.label + ' – Target vs Actual', font: { family: 'Sarabun', size: 14 } },
legend: { position: 'bottom', labels: { font: { family: 'Sarabun' } } }
},
scales: {
x: { ticks: { font: { family: 'Sarabun' } } },
y: { ticks: { font: { family: 'Sarabun' } } }
}
}
});
}
// chart 3: heatmap
function buildHeatmap() {
const pillars = ['P1','P2','P3','P4','P5','P6','P7'];
const years = [66, 67, 68];
const wrap = document.getElementById('heatmapWrap');
const getCellClass = (p, y) => {
const rows = RAW_DATA.filter(r => r.pillar === p);
const hasActual = rows.filter(r => r.actual[y] !== null && r.actual[y] !== undefined && r.actual[y] !== '');
if (hasActual.length === 0) return { cls:'hm-na', label:'–' };
const statuses = hasActual.map(r => evalStatus(r, y));
const good = statuses.filter(s => s === 'บรรลุ').length;
const fail = statuses.filter(s => s === 'ไม่บรรลุ').length;
const total = statuses.length;
const pct = Math.round((good / total) * 100);
if (pct >= 70) return { cls:'hm-good', label:`${pct}%` };
if (pct >= 40) return { cls:'hm-mixed', label:`${pct}%` };
if (pct >= 1) return { cls:'hm-bad', label:`${pct}%` };
return { cls:'hm-warn', label:`${pct}%` };
};
let html = `<table class="heatmap-table"><thead><tr>
<th>ยุทธศาสตร์</th>
<th>ปี 2566</th><th>ปี 2567</th><th>ปี 2568</th>
</tr></thead><tbody>`;
pillars.forEach(p => {
html += `<tr><td class="hm-row-label">${p} – ${PILLAR_META[p].name.substring(0,20)}...</td>`;
years.forEach(y => {
const c = getCellClass(p, y);
html += `<td class="${c.cls}" title="${p} ปี 25${y} : ${c.label} ของตัวชี้วัดที่มีข้อมูล">${c.label}</td>`;
});
html += '</tr>';
});
html += '</tbody></table>';
html += `<p style="font-size:12px;color:var(--text-muted);margin-top:8px">
🟢 ≥70% บรรลุ | 🟣 40–69% | 🔴 1–39% | ⬛ ไม่มีข้อมูล | ตัวเลขแสดงสัดส่วนตัวชี้วัดที่มีข้อมูลและบรรลุเป้าหมาย</p>`;
wrap.innerHTML = html;
}
// ════════════════════════════════════════════════════════════
// HIGHLIGHTS
// ════════════════════════════════════════════════════════════
function buildHighlights() {
const strengths = [
'🏆 <strong>HA รับรอง:</strong> ผ่านการรับรองมาตรฐาน HA ต่อเนื่องทุกปี 2566–2568',
'💊 <strong>HIV PrEP/PEP:</strong> ผู้เข้ารับบริการที่ได้รับ PrEP/PEP สูง 99–100% ทุกปี (เป้า ≥55–75%)',
'🏥 <strong>OPD คุณภาพ:</strong> ผ่านรับรอง + ความพึงพอใจ ≥89% + waiting time ดีขึ้นต่อเนื่อง',
'❤️ <strong>NCD Clinic:</strong> ระดับดีมากปี 2568, เวลารอ 59.6 นาที (ลดจาก 61.5 ปี 66)',
'💰 <strong>การเงินมั่นคง:</strong> CMI 0.64, Sum Adj-RW 2,581, QR 3.34, Current 3.67 – ทุกตัวชี้เกินเป้า',
'🔬 <strong>LR/Lab/ER:</strong> ห้องคลอด, ห้องปฏิบัติการ, ห้องฉุกเฉินคุณภาพ ผ่านเกณฑ์ต่อเนื่อง',
'👥 <strong>บุคลากร:</strong> ความคงอยู่ 97–98%, CQI/R2R เพิ่มเป็น 15 เรื่องปี 2568 (เป้า 5)',
'🌿 <strong>Green & Clean:</strong> ผ่านเกณฑ์มาตรฐานปี 2568',
'📊 <strong>HS4:</strong> กระโดดจาก 25% (ปี 66) สู่ 93% (ปี 68) เกินเป้า 80%',
'💉 <strong>Sepsis:</strong> อัตรา AB ภายใน 1 ชม. ปรับปรุงจาก 68.85% → 90.41% บรรลุเป้าหมาย ≥90%',
];
const opportunities = [
'⚠️ <strong>TB อัตราตาย:</strong> เกินเกณฑ์ <5% ทั้งปี 66 (7.33%) และปี 68 (11.71%) – จำเป็นต้องมี action plan เร่งด่วน',
'🚑 <strong>Stroke EMS:</strong> ปี 68 ลดลงเหลือ 12.16% (เป้า 40%) – ต้องพัฒนาระบบส่งต่อ EMS ระดับอำเภอ',
'⏱️ <strong>Stroke fast tract:</strong> ส่งต่อภายใน 30 นาที ปี 68 = 42.3% (เป้า 60%) – ยังขาดระบบ',
'🛏️ <strong>อัตราครองเตียง:</strong> 73–76% ต่ำกว่าเป้า >100% – ต้องพัฒนา IPD admission หรือทบทวนเป้าหมาย',
'💵 <strong>OPD/IPD unit cost:</strong> OPD cost ปี 67–68 สูงเกินเป้า (898/806 บาท vs เป้า 400/394) – ควรวิเคราะห์ต้นทุน',
'🧪 <strong>HAIT:</strong> รอรับการประเมินทุกปี 66–68 – ควรเร่งกระบวนการประเมิน',
'🫀 <strong>HIV Adherence ปี 66:</strong> 57.33% (เป้า 90%) แต่ปี 67–68 ดีขึ้นมาก 97.5–98% – ต้องรักษาระดับ',
'🩺 <strong>HT ควบคุม BP:</strong> ปี 68 = 58.73% (เป้า 70%) ลดลงจากปีก่อน – ต้องทบทวนแนวปฏิบัติ',
'💡 <strong>Data completeness:</strong> ข้อมูลปี 2567 ขาดหายหลายตัวชี้วัด – ควรพัฒนาระบบรายงานให้ครบถ้วน',
];
const systemic = [
'🔗 <strong>P1→P2 (ป้องกัน→ลดตาย):</strong> การเข้าถึงคัดกรองกลุ่มเสี่ยง DM/HT สูง → ควบคุมโรคได้ → ลดแทรกซ้อนและตาย',
'🔗 <strong>P2→P4 (ผลลัพธ์→คุณภาพคลินิก):</strong> Stroke fast tract และ ACS EMS ยังต่ำ → กระทบคุณภาพห้องฉุกเฉินและ IPD',
'🔗 <strong>P4→P6 (คุณภาพ→การเงิน):</strong> HS4 ดีขึ้น → CMI สูงขึ้น → Sum Adj-RW เพิ่ม → รายได้ UC สูงขึ้น',
'🔗 <strong>P3→P4 (IT→คุณภาพ):</strong> ระบบสารสนเทศพัสดุ 89% → รองรับ HA และมาตรฐานคุณภาพ',
'🔗 <strong>P7→P1–P4 (บุคลากร→บริการ):</strong> อัตราคงอยู่ 97% + CQI 15 เรื่อง → รักษาคุณภาพบริการทุกมิติ',
'🔗 <strong>P5→P1 (ชุมชน→ป้องกัน):</strong> ชุมชนมีส่วนร่วม 100% → ลด DHF, Head injury จากชุมชน',
'🔗 <strong>P6→P7 (การเงิน→บุคลากร):</strong> เสถียรภาพทางการเงินดี → รองรับการพัฒนาบุคลากรและ CQI อย่างต่อเนื่อง',
];
function buildList(id, items) {
const el = document.getElementById(id);
el.innerHTML = items.map(i => `<li><span>•</span><span>${i}</span></li>`).join('');
}
buildList('hlStrengths', strengths);
buildList('hlOpportunities', opportunities);
buildList('hlSystemic', systemic);
}
// ════════════════════════════════════════════════════════════
// RECOMMENDATIONS
// ════════════════════════════════════════════════════════════
function buildRecommendations() {
const recs = [
{
title: 'จัดทำ Action Plan เร่งด่วนสำหรับ TB อัตราตาย',
detail: 'อัตราตายวัณโรคปี 2566 = 7.33% และปี 2568 = 11.71% เกินเป้า <5% ต่อเนื่อง ควรตรวจสอบระบบการวินิจฉัยล่าช้า การติดตามรักษา และประสานกับ สสจ./สคร. เพื่อวางมาตรการเชิงรุกภายในไตรมาส 3/2568'
},
{
title: 'พัฒนาระบบ EMS อำเภอสำหรับ Stroke และ ACS',
detail: 'Stroke EMS < 15% และ Stroke fast tract ส่งต่อ < 43% ต่ำกว่าเป้าอย่างมีนัย ควรบูรณาการร่วมกับ พชอ. ฝึกอบรม อาสาสมัคร และพัฒนา Protocol การส่งต่อ Stroke ให้ทัน 30 นาที'
},
{
title: 'ทบทวนและยกระดับมาตรฐาน HIMSS/HAIT',
detail: 'ปี 2568 ยังไม่รับการประเมิน HIMSS ระดับ 5 และ HAIT รอการประเมินทุกปี ควรกำหนดเจ้าภาพ ตั้ง project team วางแผนรองรับการประเมินภายใน Q1/2569'
},
{
title: 'วิเคราะห์ต้นทุน OPD/IPD unit cost ที่สูงกว่าเป้า',
detail: 'OPD unit cost ปี 2568 = 805 บาท (เป้า 394 บาท) อาจสะท้อนการเปลี่ยนแปลงระบบบัญชีหรือ volume ผู้ป่วย ควรให้ฝ่ายการเงินวิเคราะห์และทบทวนเป้าหมายให้สอดคล้องกับบริบทจริง'
},
{
title: 'ยกระดับ KPI ที่มีผลดีเยี่ยมเป็น Best Practice',
detail: 'HIV PrEP/PEP 99–100%, CQI 15 เรื่อง, Sum Adj-RW เกินเป้าเกือบ 2 เท่า, HS4 93% ควรถ่ายทอดเป็น Best Practice ในระดับจังหวัดและเตรียมนำเสนอต่อ สรพ. เพื่อสะท้อนศักยภาพ รพ.'
},
{
title: 'พัฒนาระบบ Dashboard ติดตาม KPI รายไตรมาส',
detail: 'ข้อมูลปี 2567 ขาดหายหลายตัวชี้วัด ควรพัฒนาระบบ Dashboard ออนไลน์ที่ผู้รับผิดชอบบันทึกข้อมูลรายไตรมาส และมีการประชุมทบทวน KPI ระดับผู้บริหาร อย่างน้อย 2 ครั้งต่อปี'
},
{
title: 'วางแผนปรับปรุง HT BP control และอัตราครองเตียง',
detail: 'HT ควบคุม BP ปี 68 = 58.73% ลดลงจาก 69.69% (ปี 67) ควรทบทวน Protocol คลินิก NCD ส่วน อัตราครองเตียง 73–76% ต่ำกว่าเป้า >100% ควรวิเคราะห์แนวโน้มผู้ป่วยนอกพยาบาลและทบทวนเป้าหมาย'
}
];
const el = document.getElementById('recList');
el.innerHTML = recs.map((r, i) => `
<div class="rec-card">
<div class="rec-num">${i + 1}</div>
<div class="rec-content">
<div class="rec-title">${r.title}</div>
<div class="rec-detail">${r.detail}</div>
</div>
</div>
`).join('');
}
// ════════════════════════════════════════════════════════════
// NAV SCROLL
// ════════════════════════════════════════════════════════════
function scrollToSection(id) {
const el = document.getElementById(id);
if (el) {
const offset = el.getBoundingClientRect().top + window.pageYOffset - 50;
window.scrollTo({ top: offset, behavior: 'smooth' });
}
}
// highlight active nav on scroll
const sectionIds = ['summary','pillars','table','charts','highlights','recs'];
window.addEventListener('scroll', () => {
let current = sectionIds[0];
sectionIds.forEach(id => {
const el = document.getElementById(id);
if (el && el.getBoundingClientRect().top < 120) current = id;
});
document.querySelectorAll('.nav-btn').forEach((btn, i) => {
btn.classList.toggle('active', sectionIds[i] === current);
});
});
// ════════════════════════════════════════════════════════════
// INIT
// ════════════════════════════════════════════════════════════
document.addEventListener('DOMContentLoaded', () => {
buildSummary();
buildPillars();
buildTable(RAW_DATA);
buildHighlights();
buildRecommendations();
// Charts – with CDN fallback check
if (typeof Chart !== 'undefined') {
buildStatusChart();
buildKpiSelector();
buildTrendChart();
buildHeatmap();
} else {
document.getElementById('charts').innerHTML = `
<div style="padding:40px;text-align:center;background:#fff;border-radius:12px;box-shadow:var(--shadow)">
<p style="font-size:16px;color:var(--text-muted)">⚠️ ไม่สามารถโหลด Chart.js ได้ (อาจไม่มีการเชื่อมต่ออินเทอร์เน็ต)<br>กราฟจะแสดงได้เมื่อมีการเชื่อมต่อ</p>
</div>`;
}
document.getElementById('filterCount').textContent = `แสดง ${RAW_DATA.length} รายการ`;
});
</script>
</body>
</html>