Appearance
Honeyframe Data Integration
Read-only BI data source for Hermina 360 dashboards.
Official API contract: https://docs.honeyframe.io/webapp-data-apiBase URL: https://hospital.hubstudio.idMinimum version: Platform v0.2.62+ Token: set in .env.local as VITE_HF_TOKEN — never commit to source control
Quick Start
bash
# .env.local (not committed) — DEV ONLY, see Authentication below
VITE_HF_TOKEN=hf_xxxx...ts
import { useWebappConfig, usePageBatch } from '@/hooks';
import { useHoneyframeStore } from '@/stores';
// 1. Load config (cached)
useWebappConfig('hermina360');
const app = useHoneyframeStore((s) => s.apps['hermina360']);
// 2. Find page and card refs
const page = app?.config.pages.find((p) => p.key === 'executive');
const cardRefs = (page?.card_refs ?? []).filter(
(b): b is HFCardRef => b.kind === undefined || b.kind === 'card',
);
// 3. Fetch batch with optional params
const { data: batch } = usePageBatch('executive', cardRefs, app?.asset_id, {
period_start: '2025-06-01',
period_end: '2025-06-30',
});
// 4. Read results — ALWAYS check error first
const cardResult = batch?.[String(cardId)];
if (cardResult?.error) console.error(cardResult.error);
const rows = cardResult?.rows ?? [];Webapps
| App Key | asset_id | Description | Dashboards |
|---|---|---|---|
hermina360 | 1200 | Main executive + operational | 244, 280, 291–299 |
hermina360_financial | 1207 | CFO / financial (richer data) | 284, 285, 286, 287 |
Authentication
The app logs the user into Honeyframe and uses the returned JWT for every call.
Login
POST {HF_BASE}/api/auth/login
Body: { "username": "...", "password": "..." }
200 → { "access_token": "...", "token_type": "bearer", "must_reset_password": false }
401 → bad credentialsHF_BASE is the hfApi base — /hf-api in dev (Vite proxy) and in production (nginx proxy). useAuthStore.login() (src/stores/useAuthStore.ts) calls it, persists access_token to localStorage under hermina360-auth, and exposes logout() to clear it. must_reset_password: true raises a sticky notice after login and is kept on the store as mustResetPassword.
All subsequent requests carry that JWT:
Authorization: Bearer <access_token>A hfApi request interceptor injects it; a response interceptor clears auth and redirects to /login on any 401 except the login call itself. PrivateRoute gates on the presence of that token.
VITE_HF_TOKEN is a dev-only fallback
When no user is logged in, the interceptor falls back to VITE_HF_TOKEN only if import.meta.env.DEV. That branch is statically false in npm run build, so the PAT is dropped from the production bundle. Never set VITE_HF_TOKEN in a production build — a token baked into the bundle is handed to every visitor.
Token Permissions
project.view— config, block execute, filter optionsdashboard.view— card execute- Recommended scope ceiling:
["project.view", "dashboard.view"]
Failure Modes
| Status | Meaning | Action |
|---|---|---|
401 | Bad, revoked, or expired token | Terminal — do not retry |
403 | Permission denied or token scoped to different org/project | Terminal — do not retry |
404 | Not found or not visible (deliberately indistinguishable) | Check key/ID |
429 | Rate limited | Back off and retry |
Platform Tier Note: Unlicensed orgs receive 403 with license_required body. App tier does not enforce this.
Endpoints
| # | Method | Path | Purpose | Limits |
|---|---|---|---|---|
| 1 | GET | /api/webapps/{key} | Fetch config (pages, nav, card_refs) | Cache — changes only on republish |
| 2 | POST | /api/dashboards/{id}/cards/{card_id}/execute?asset_id=N | Execute single card | 1000 rows, 30s timeout |
| 3 | POST | /api/dashboards/{id}/cards/execute-batch?asset_id=N | Run multiple cards per dashboard | 1000 rows per card, 30s timeout |
| 4 | POST | /api/webapps/{key}/pages/{pageKey}/blocks/{idx}/execute | Run HTML/code blocks with data_sql | 5000 rows, 8s timeout |
| 5 | POST | /api/webapps/preview-filter-options | Resolve options_sql to dropdown values | 5000 rows, 8s timeout |
Webapp Config (Endpoint 1)
Response contains pages, nav, card_refs, filters, parameters.
Block shapes in card_refs:
| Kind | Description | Execute via |
|---|---|---|
"card" or absent | SQL-backed card | Endpoint 2/3 |
"code" | Code block | Endpoint 4 |
"html" | Sandboxed markup; if has data_sql | Endpoint 4 |
"html" (no data_sql) | Static markup | No data call needed |
Card block fields (server-hydrated, usable before execution):
card_type:bar | line | pie | donut | kpi | textcard_title: Display titlecard_config: Chart config (xField,yField,nameField,valueField, etc.)card_config_overrides: Apply on top ofcard_configwhen present
Block indexing: block_idx = position in card_refs array. Public redactor blanks hidden blocks in place (indexes stable). Never filter array before deriving index.
Execute Single Card (Endpoint 2)
POST /api/dashboards/{dashboard_id}/cards/{card_id}/execute?asset_id={asset_id}
Body: { "params": { "key": "value" } }Execute Batch (Endpoint 3 — main data path)
POST /api/dashboards/{dashboardId}/cards/execute-batch?asset_id={assetId}
Body: { "card_ids": [number], "params": { "key": "value" } }Response: Record<string, CardResult> — keyed by card_id (string, not integer).
ts
type CardResult = {
card_id: number;
columns: string[];
rows: Record<string, unknown>[];
row_count: number;
execution_ms: number;
error: string | null; // ← ALWAYS CHECK THIS
bound_params?: string[]; // confirms which params each card responds to
};Row limit: 1000 rows appended as LIMIT on connector-backed and parameterless warehouse paths. Parameterized warehouse queries (common for card execution with params) run without appended LIMIT — only 30s timeout bounds them. Size clients accordingly.
Critical: Failed queries return HTTP 200 with error set and rows: []. Always check error before reading rows.
Block Execute (Endpoint 4)
POST /api/webapps/{key}/pages/{pageKey}/blocks/{blockIdx}/execute
Body: { "filters": { "filter_name": "value" } }ts
type BlockResult = {
columns: string[];
rows: Record<string, unknown>[];
row_count: number;
execution_ms: number;
error: string | null;
truncated: boolean; // true if exceeded 5000-row limit — surface to user
};blockIdx = index in card_refs array (before any filtering). Max 5000 rows, 8s timeout. Honor truncated flag.
Filter Options (Endpoint 5)
POST /api/webapps/preview-filter-options
Body: { "sql": "SELECT code AS value, name AS label FROM ..." }ts
type FilterOption = {
value: string | number;
label: string;
code?: string; // present only when query returns it
};Send options_sql from config verbatim — modified SQL returns 403. Read-only tokens cannot run ad-hoc SQL.
Anonymous/Public Endpoints
| Method | Path | Notes |
|---|---|---|
| GET | /api/public/webapps/{key}?token= | SQL redacted, PII masked, no filter options |
| POST | /api/public/webapps/{key}/cards/{dashboard_id}/{card_id}/execute?token= | No auth header needed |
Public paths redact data_sql, mask PII columns, and blank public_hidden blocks in place (indexes stable).
Filter Parameter Contract
Filter state travels as ONE flat dict in the params field of execute-batch. Build it with buildFilterParams() (src/lib/filterParams.ts) — do not assemble the keys by hand.
1. Period (filters.period, type preset_date_range)
The config's params object names the bind keys; on both hermina360 apps they are:
jsonc
{ "period_start": "2026-07-01", "period_end": "2026-07-31", "period_label": "Bln Lalu" }period_label is the preset key, or 'custom' for a hand-picked range.
2. Select / pill filters (branch_select, pill_multi)
The config declares param (e.g. branch_id, kelas, regional). The card SQL binds the COMPANION key <param>_codes — a CSV of the selected options' code values (pill options with no code use their value):
sql
(COALESCE(:branch_id_codes,'ALL') = 'ALL'
OR afya_code = ANY(string_to_array(:branch_id_codes, ',')))| Selection | <param> | <param>_codes |
|---|---|---|
| nothing | emptyValue ('ALL') | emptyValue ('ALL') |
| one | the raw value | that option's code |
| many | array of raw values | codes joined with , |
kelas_codes matches criteria_band (A/B/C/D); regional_codes matches regional. Never send branch_keys — server-side cards COALESCE it away.
ts
const params = buildFilterParams({
period: { start: '2026-07-01', end: '2026-07-31', label: 'Bln Lalu', keys: periodKeys },
selects: [
{ param: 'branch_id', options: branchOptions, selected: ['12', '31'] },
{ param: 'kelas', options: kelasOptions, selected: kelas === 'Semua' ? [] : [kelas], mode: 'single' },
],
});
// → { period_start, period_end, period_label,
// branch_id: ['12','31'], branch_id_codes: 'RSHB,RSHK',
// kelas: 'ALL', kelas_codes: 'ALL' }Bind names and option codes are read from the app config at runtime (resolvePeriodKeys, resolveSelectFilter); the page's hardcoded pill values are only a fallback for when the config does not declare that filter.
3. Card badges (bound_params)
Each batch result carries bound_params — the bind names that card's SQL actually references. deriveCardBadge() (src/lib/cardBadges.ts) turns that into a chip:
- a branch-scoping filter is set AND neither
<param>nor<param>_codesis inbound_params→ "Tidak per cabang" - else the period moved off the default AND none of
period_start/period_end/period_labelis inbound_params→ "Tidak per periode"
Branch wins; the two never stack. The chip only renders once the card has loaded cleanly (no error, not loading) and the server reported bound_params — an absent bound_params badges nothing rather than guessing.
Integration Pattern
Standard page pattern used across all dashboard pages:
useWebappConfig → find page by key → filter card_refs (kind=card) → usePageBatch(pageKey, cardRefs, asset_id, params?)executePageBatch groups cards by dashboard_id and fires parallel batches.
Per-page block loop:
ts
for (const [idx, blk] of page.card_refs.entries()) {
if (blk.kind === 'code' || (blk.kind === 'html' && blk.data_sql)) {
await executeBlock(key, page.key, idx, filters);
} else if (blk.kind === undefined || blk.kind === 'card') {
// Collect by dashboard_id, batch per dashboard
} else if (blk.kind === 'html') {
// Static markup—render blk.html, no data call
}
}hermina360 — Card Inventory (v132, 34 pages)
executive (dashboard 244)
| Card ID | Title | Notes |
|---|---|---|
| 2016 | Pasien Unik | |
| 2017 | Dokter Aktif | |
| 2026 | Tingkat Pembatalan | |
| 2010 | Rawat jalan total | Kunjungan, per hari |
| 2014 | Admisi Rawat Inap (Jaringan) | Rawat Inap (Episode), NO Hari Rawat field |
| 2039 | Operasi · Jaringan | NOT period-bound (YTD always) |
| 2037 | Lahir · Jaringan | NOT period-bound (YTD always) |
| 2225 | Tren Kinerja Jaringan — 6 Bulan | |
| 2226 | Prioritas Tindakan Direksi | |
| 2246 | Bulan Berjalan vs Bulan Lalu | Has Δ MoM (%) per metric |
| 2002 | Padma vs Kamala — Bauran Layanan |
operational (dashboard 244) — superset of executive
All executive cards plus:
| Card ID | Title | Notes |
|---|---|---|
| 2312 | Fisioterapi | |
| 2313 | KTK — Tumbuh Kembang | |
| 2363 | Tren Operasional Jaringan — 12 Bulan | |
| 2362 | Pembatalan per Cabang | |
| 2364 | Ringkasan Operasional per Cabang | |
| 2365 | BOR Bulanan | BOR % trend, last value = current BOR |
| 2366 | Volume Operasi Bulanan — Jaringan |
financial (dashboard 244)
| Card ID | Title | Notes |
|---|---|---|
| 2669 | Pendapatan (Bruto) — Jaringan | Gross revenue (≠ net from 2598) |
| 2670 | Revenue Rawat Inap | Gross, period-bound |
| 2671 | Revenue Rawat Jalan | Gross, period-bound |
| 2672 | Pendapatan IGD | Gross, period-bound |
| 2675 | Pendapatan Bruto per Regional (I–VI) | Regional bar chart data |
| 2676 | Yield per Episode | ARPIP/ARPOP per episode type |
| 2227 | Penerimaan / Collections | |
| 2228 | Klaim (Settlement) | |
| 2229 | Bayar Langsung (Payment) | |
| 2230 | Deposit Diterima | |
| 2231 | Komposisi Penerimaan (Metode) | |
| 2357 | Akrual vs Collections per Cabang | |
| 2358 | Catatan Data Keuangan | |
| 2359 | Tren Penerimaan & Deposit — 12 Bulan | |
| 2360 | Penerimaan per Cabang (Top 15) | |
| 2361 | Komposisi Kunjungan BPJS vs Non-BPJS | |
| 2387 | Tren Pendapatan Bruto (GL) — Padma vs Kamala | |
| 2388 | Pendapatan Akrual per Penjamin (GL) | |
| 2389 | Pendapatan Akrual per Cabang (GL) — Top 15 |
Cards 4425–4442 (18 new cards added in v132, dashboard 280).
financial-gl (dashboard 280)
GL-based P&L data. Cards 3130–3176 covering:
- P&L Summary (3130–3137): Revenue RS, COGS, GP, Margin, Opex, EBIT
- Revenue breakdown (3138–3149): IP/OP/IGD, BPJS vs Non-BPJS, per cabang
- COGS structure (3151–3161): drug, salary, per line item, ratio trends
- Opex (3163–3172): salary BUA, insurance, per line item
- Profitability (3174–3176): drug revenue
ringkasan-eksekutif (dashboard 244)
| Card ID | Title | Notes |
|---|---|---|
| 2598 | Kinerja Keuangan HEAL (Metrik × Periode) | Revenue, EBITDA, NP, NPM, Padma Mix — NET revenue |
| 2581 | Laporan Laba Rugi (P&L Cascade) | Full P&L table |
| 2574 | Prioritas Tindakan Direksi | Cabang, Kls, EBITDA Mgn %, Net Profit, NPM %, % Padma |
| 2572 | Komposisi Revenue — Padma vs Kamala | |
| 2599 | Pendapatan Net per Segmen (GL Live) | |
| 2665–2668 | Metric heroes (Revenue, EBITDA, NP, GP) | Trend data, 12-month series |
tren-periodik (dashboard 244)
| Card ID | Title | Notes |
|---|---|---|
| 2665 | Revenue Total — metric_hero | Monthly series with Δ% |
| 2666 | EBITDA — metric_hero | |
| 2667 | Net Profit — metric_hero | |
| 2668 | Gross Profit — metric_hero | |
| 2582 | Tren — Revenue Total · RS · Non-RS | |
| 2583 | Tren — EBITDA · NP · GP | |
| 2584 | Tren — Margin: EBITDA · NPM · GPM (%) | |
| 2585 | Tren — Revenue Padma vs Kamala | |
| 2586 | Tren — Biaya: Obat · TK · Pemeliharaan · Utilitas | |
| 2587 | Tren — Rasio Biaya / Revenue (%) |
clinical (dashboard 244)
| Card ID | Title |
|---|---|
| 2376 | LOS Rata-rata Rawat Inap |
| 2025 | Hari rawat ICU |
| 2028 | Pemakaian ventilator |
| 2377 | Komposisi Encounter (RJ/RI/Darurat) |
| 2378 | Top 12 Diagnosa (ICD-10) |
| 2365 | BOR Bulanan |
| 2441 | Top 10 Diagnosa — Jaringan AFYA |
| 2442 | ALOS per RS — Jaringan AFYA |
pharmacy (dashboard 244)
| Card ID | Title |
|---|---|
| 2328 | Belanja Farmasi & Supply |
| 2420 | Pendapatan Obat · Jaringan |
| 2329 | SKU Diterima |
| 2330 | Vendor Aktif |
| 2331 | Batch ≤90 Hari Kadaluarsa |
| 2419 | Analisis ABC — Pareto Pengadaan |
| 2332 | Top Item — Belanja Procurement |
| 2334 | Belanja Procurement per Bulan |
| 2335 | Batch Mendekati Kadaluarsa |
branches (dashboard 244)
| Card ID | Title |
|---|---|
| 2421 | Statistik Cabang — Cabang vs Region vs Jaringan |
| 2340 | Ranking Cabang — Volume Kunjungan |
| 2422 | Ranking — Penerimaan (Rp juta) |
| 2424 | Semua Metrik — 54 Cabang |
| 2336 | Branch Explorer — Semua Cabang |
branch-focus (dashboard 244)
| Card ID | Title |
|---|---|
| 2606 | Perbandingan Cabang header |
| 2603 | Perbandingan Revenue per Cabang — Bulanan |
| 2605 | Profitabilitas per Cabang — Bulan Terkini |
| 2607 | Struktur Biaya per Cabang — Bulan Terkini |
likuiditas (dashboard 291)
| Card ID | Title |
|---|---|
| 4188 | Kas & Setara Kas |
| 4189 | Piutang Usaha (AR) |
| 4190 | Persediaan |
| 4191 | Utang Usaha (Trade AP) |
| 4192 | CCC (Hari) |
| 4193 | CCC Komposit — DSO + DIO − DPO |
| 4194 | Piutang per Penjamin |
| 4195 | Tren Modal Kerja — 6 Bulan |
| 4196 | AR Aging — Piutang Pasien Pribadi |
| 4197 | Umur Utang Usaha (AP Aging) |
| 4198 | AR Days (DSO) — Top 15 Cabang |
| 4386 | Likuiditas — additional metric |
heatmap-kpi (dashboard 297)
| Card ID | Title |
|---|---|
| 4182 | Heatmap KPI Cabang — 8 Indikator |
| 4183 | Ranking Worst-first — Komposit KPI |
lini-layanan (dashboard 293)
Cards 4339–4364 covering Poliklinik, IGD, Rawat Inap, Kelahiran metrics with trends and worst-first rankings.
kinerja-penjamin (dashboard 295)
Cards 4397–4405: Asuransi, Perusahaan, BPJS TK, OOP, target realization, payer mix.
pelayanan-jkn (dashboard 296)
Cards 4406–4415: BPJS revenue/volume, ARPIP/ARPOP BPJS, selisih koding.
vaksinasi (dashboard 298)
Cards 4170–4180: Vaccine doses, antigen trends, funnel, projection.
varians-biaya (dashboard 294)
Cards 4365–4385: Cost ratios, worst-first rankings, honor dokter.
retensi-pasien (dashboard 299)
Cards 4416–4424: Retention cohorts, new vs returning patients.
wasdal (dashboards 244, 292)
| Card ID | Title |
|---|---|
| 2236 | Wasdal Prioritas — Scorecard 54 Cabang |
| 4199 | Wasdal Prioritas — Heatmap 8 Indikator |
| 4200 | Ranking Worst-first — Komposit Wasdal |
review-heal (dashboard 244)
HEAL network review. 12 cards, shares many with ringkasan-eksekutif and tren-periodik.
| Card ID | Title |
|---|---|
| 2562 | Review HEAL — Summary |
| 2564 | Review HEAL — Detail |
| 2572 | Komposisi Revenue — Padma vs Kamala |
| 2574 | Prioritas Tindakan Direksi |
| 2581 | Laporan Laba Rugi (P&L Cascade) |
| 2599 | Pendapatan Net per Segmen (GL Live) |
| 2656 | Metric Hero — Revenue |
| 2657 | Metric Hero — EBITDA |
| 2665 | Revenue Total — metric_hero |
| 2666 | EBITDA — metric_hero |
| 2667 | Net Profit — metric_hero |
| 2668 | Gross Profit — metric_hero |
review-rs (dashboard 244)
Per-hospital review. 8 cards.
| Card ID | Title |
|---|---|
| 2563 | Review per RS — Detail (names NOT masked) |
| 2564 | Review HEAL — Detail |
| 2597 | Revenue per RS |
| 2656 | Metric Hero — Revenue |
| 2665 | Revenue Total — metric_hero |
| 2666 | EBITDA — metric_hero |
| 2667 | Net Profit — metric_hero |
| 2668 | Gross Profit — metric_hero |
dwh-gross (dashboard 244)
DWH gross revenue analysis. 9 cards (2589–2597).
| Card ID | Title |
|---|---|
| 2589–2596 | DWH Gross Revenue breakdowns |
| 2597 | Revenue per RS |
patients (dashboard 244)
Patient analytics. 13 cards (7 new: 4443–4450).
| Card ID | Title |
|---|---|
| 2002 | Padma vs Kamala — Bauran Layanan |
| 2010 | Rawat jalan total |
| 2011 | Rawat Jalan — Detail |
| 2014 | Admisi Rawat Inap (Jaringan) |
| 2015 | Rawat Inap — Detail |
| 4443–4450 | Patient analytics (new in v132) |
Other pages
- doctors (244): 2234 — Doctor listing
- specialists (244): 2235 — Specialist listing
- catalog (244): 2239 — Catalog/reference data
- builder (244): 2240 — Dashboard builder data
- wiki (244): 2241 — Wiki content
- episode (no dashboard): 1 code block — Episode analysis
- sdm (244): 2234, 2380–2383 + 2 code blocks — Doctor headcount, specialization distribution
- diagnostic (244): 2031–2035, 2232, 2417 — Imaging modality volumes
- staffing (244): 2042–2043 — SP1/SP2 doctor counts
- assets (244): 2004–2006, 2386, 2431–2433 — Bed inventory
- data-trust (244): 2485–2497 — Patient identity quality metrics
hermina360_financial — Card & Block Inventory
ringkasan (dashboard 284)
HTML Blocks (block-execute):
| Block | Content | Key Columns |
|---|---|---|
| block[0] | Data freshness | kind, a (timestamp), b (business date), n (count) |
| block[1] | P&L monthly trend | kind=m: label (YYYY-MM), a (revenue), b, c, d |
| block[2] | Revenue breakdown (exact) | rev_ip, ip_p, ip_k, rev_op, op_p, op_k, rev_igd, d (IP days), v (OP visits) — all in Rp |
| block[3] | P&L totals | rev, ebitda, laba, gp, padma, kamala, p_* (previous period) |
| block[4] | Volume & BOR base | d, v, bed_days, beds — BOR = d/bed_days×100 |
| block[8] | Intra-month preview | Daily billing, NOT comparable with GL data |
Cards:
| Card ID | Title | Key Fields |
|---|---|---|
| 3786 | Rincian Lini Layanan × Kelas | Metrik, Total/Blended, Padma, Kamala |
| 3787 | Revenue per Regional | Regional, Revenue (Rp M) |
| 3788 | Prioritas Direksi (richer than 2574) | Cabang, Reg, Kelas, Revenue (Rp M), EBITDA Mgn %, NPM %, BOR* %, % Padma, Quick Assessment |
review-konsolidasi (dashboard 285)
| Card ID | Title |
|---|---|
| 3790 | Revenue Padma |
| 3791 | Revenue Kamala |
| 3792 | Rasio Obat |
| 3793 | Rasio Tenaga Kerja |
| 3794 | Tabel Deret Waktu — YTD · Avg · M-2 · M-1 · M-0 |
| 3795 | Revenue Total — 12 Bulan (anotasi musiman) |
| 3796 | EBITDA & Margin — 12 Bulan |
| 3797 | Revenue Padma vs Kamala — 12 Bulan |
| 3798 | Rasio 4 Penggerak Biaya — 12 Bulan |
| 3799 | Ranking Biaya — bulan berjalan |
review-rs (dashboard 286)
| Card ID | Title |
|---|---|
| 3801 | Peta Panas — seluruh RS × indikator |
| 3802 | EBITDA Margin — 15 RS terburuk |
| 3803 | Revenue per RS — Top 15 |
| 3804 | Waterfall Δ EBITDA MoM |
| 3805 | Rollup per Regional |
| 3806 | Distribusi Quick Assessment |
tren-bulanan (dashboard 287)
| Card ID | Title |
|---|---|
| 3808 | Revenue Bulanan — Total · Padma · Kamala |
| 3809 | Revenue Bulanan — Rawat Inap vs Rawat Jalan |
| 3810 | Hari Rawat Inap Bulanan — Padma vs Kamala |
| 3811 | ARPIP Bulanan — Padma vs Kamala |
| 3812 | Growth MoM Revenue (%) |
| 3813 | Penguraian Pertumbuhan IP — Efek Volume vs Efek Tarif |
| 3814 | CMGR YTD — Revenue & Volume per RS |
Data Gotchas
- HTTP 200 with error: Failed queries return 200 with
errorset + emptyrows. Always checkerrorbefore consuming data. - Gross vs Net revenue: Cards 2669–2672 return gross revenue; card 2598 returns net revenue. Different legitimate metrics.
- Card 2014 has no
Hari Rawat: OnlyRawat Inap (Episode). Usehermina360_financialblock[2] for real IP days. - Cards 2039/2037 not period-bound: Surgery and birth cards return YTD data regardless of period params.
- Hospital names masked in dashboards 293/294/297 —
branch_codeavailable in 297. - Card 2563 (review-rs): Names NOT masked,
Thn= founding year. bound_paramsin execute-batch response confirms which params each card responds to.block_idxis array index: Use the index incard_refsarray BEFORE filtering by kind.options_sqlis read-only: Send verbatim from config — modified SQL returns 403. Read-only tokens cannot run ad-hoc SQL.- Padma/Kamala split:
hermina360card 2598 only has overallPadma Mix %(approximation).hermina360_financialblock[2] has exact per-service-line split. - Row cap not guaranteed: 1000-row
LIMITis appended on connector-backed paths, but parameterized warehouse queries run without it — only 30s timeout bounds them. card_config_overrides: Apply on top ofcard_configwhen present in card_refs.
Integration Checklist
- Fetch config once per page load; cache it (changes only on republish)
- Lay grid from
layoutand hydratedcard_type; do not wait for execution - Render static
optionsimmediately; call endpoint 5 foroptions_sql(send verbatim) - Group cards by
dashboard_id; one batch per dashboard (endpoint 3) - Execute code and data-bearing HTML blocks (endpoint 4)
- Check
erroron every result before readingrows - Honor
truncatedon block path; surface rather than hide - Pass
asset_idon card executes - Treat
401/403as non-retryable stop - Back off on
429(rate limited)
Pages Using HF Data
| Page Component | HF Page Key | Dashboard | Status |
|---|---|---|---|
| ExecSummaryPage | ringkasan-eksekutif + financial + operational | 244 | Live |
| CfoCockpitPage | financial | 244 | Live |
| OperationalPage | operational | 244 | Live |
| PeriodicTrendsPage | tren-periodik | 244 | Live |
| BranchStatisticsPage | branches | 244 | Live |
| BranchComparisonPage | branch-focus | 244 | Live |
| HeatmapPage | heatmap-kpi | 297 | Live |
| ServiceLinesPage | lini-layanan | 293 | Live |
| PayerPerformancePage | kinerja-penjamin | 295 | Live |
| VaccinationPage | vaksinasi | 298 | Live |
| LiquidityPage | likuiditas | 291 | Live |
| SPIPage | varians-biaya | 294 | Live |
| PatientRetentionPage | retensi-pasien | 299 | Live |
| JKNPage | pelayanan-jkn | 296 | Live |
| ClinicalFlowPage | clinical | 244 | Live |
| PharmacyPage | pharmacy | 244 | Live |
| ReviewPerRSPage | review-rs | 244 | Live |
| WasdalPage | wasdal | 244 | Live |
| ConsolidationPage | review-konsolidasi (hermina360_financial) | 285 | Live |
Pages with no backing cards
MCUPage, MaternalPage and AccessManagementPage have no cards in either webapp. They render <DemoDataBanner /> ("Data demo — belum terhubung ke sumber data") over placeholder figures. Wire them only once real cards exist — do not invent an endpoint for them.