MONAILabel
MONAI Label은 지능형 오픈소스 이미지 라벨링 및 학습 도구이다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기PERISAI: claim risk intelligence for JKN hospital audits (BPJS Healthkathon 2026)
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Which hospital claims to check first, why, and who decides.
BPJS Kesehatan Healthkathon 2026, "Detect Smarter, Protect JKN". Category: Facility Risk Efficiency. Team RAEVA.
Deck (PDF), Signature features, Tour, How it works, Architecture, Technical pillars, Run it, Limits
For Healthkathon 2026 judges and organisers. The app runs on the official BPJS Kesehatan sample data, which is licensed for the competition only. Please keep the app link within the judging panel. Search engines are told not to index it, and the demo will be switched off after the competition.
BPJS Kesehatan flags thousands of hospital claims as possible fraud every day, and its verifiers cannot check them all. PERISAI decides the order. It compares each inpatient claim with similar patients at similar hospitals, estimates how much money could be recovered, and puts the largest amounts at the top of the queue. Every flag comes with numbered facts and a plain-language reason.
An AI model drafts summaries and quotes the relevant regulation. It does not decide. A verifier makes the first call, a supervisor confirms the serious cases, and every decision is written to a log that cannot be edited afterwards.
On 238 claims that BPJS verifiers had actually corrected, PERISAI found five times more problem claims than random checks, inside the same daily workload.
| 195,544 | 5.0× | 8 | 3 + 1 | 137 | 0 |
|---|---|---|---|---|---|
| real inpatient claims, not synthetic | more problem claims found than random checks, on real verifier corrections | risk detectors | roles (verifier, supervisor, branch head) plus admin | automated tests passing (101 unit, 36 integration) | member identities sent to the AI |
| Instead of | PERISAI offers |
|---|---|
| An anomaly dashboard on made-up data | A verification workspace on 195,544 real claims, tested on 238 real verifier corrections |
| A 0 to 100 score with no reason | Numbered facts and per-feature model reasons. The model never sees the hospital's name |
| A chatbot that can invent numbers | An AI that reads numbered facts only, quotes Permenkes through retrieval, and drops any number it cannot source |
| One "fraud / not fraud" button | Two-step decisions: verifier, supervisor, facility field audits, a 72-hour correction window, a sealed PDF |
| An application log | A SHA-256 hash chain inside PostgreSQL. Updates and deletes are rejected by triggers, and anyone can re-verify it on the Governance page |
| Savings claims that were never measured | An Impact Simulator built on the measured lift curve (at 1% and 5% audit capacity) |
Most fraud tools stop at a score. These three go further, and each runs on real computation rather than scripted answers. The Impact Simulator sits beside them to show what the gain is worth.
| Feature | The question it answers | How it works | |
|---|---|---|---|
| 1 | Document Authenticity Scan (/pindai) | "Is this supporting file genuine, or a duplicate, a paste-up or an edit?" | Forensics in the browser; files are never uploaded. It reads PDF, JPEG and PNG metadata (editor, modified date, C2PA and AI-generator marks), finds duplicate files through a 24×34 tile fingerprint checked against an archive, finds pasted ink through 32×16 density matching of connected strokes, finds edits through breaks in sensor noise, and counts filled table rows (after deskewing) against the days or sessions billed. Blind test on another team's files: 17/17 PDFs, 15/17 JPGs, and no genuine file was ever flagged |
| 2 | Pre-payment Check (/prabayar) | "Should this claim be paid now, or held for review?" | The production model and formula, without writing anything. About 250 ms per claim. Paste up to 50 claim IDs at once, or simulate one VClaim day (100 claims in about 4.6 seconds, 10 in parallel). BPJS sets the hold threshold. Held claims link straight to the document scan |
| 3 | Dispute Time Machine (/sanggahan) | "When a hospital disputes, what did the system know at the moment of decision?" | Rebuilds a case at any point in time from bitemporal scores (risk.score_as_of) and the ledger, recomputes the SHA-256 chain, and rescores the claim today with the same model. Differences come only from float rounding (case 51323: Rp 135) |
| + | Impact Simulator (/dampak) | "With a team this size, how many more problem claims a year?" | The measured lift curve (5.04× at 1% capacity, 2.44× at 5%) from 238 real verifier corrections, interpolated on a log scale. With 1,000 verifiers it projects about 47,600 more problem claims a year |
Each file goes through eight checks on the user's own device, in about 250 ms a page. The core is a pure function
(inspect in web/src/lib/forensik.ts); the browser only decodes images into pixels.
| # | Check | Method | Outcome |
|---|---|---|---|
| 1 | Intake | PDF (first DCTDecode image), JPEG, PNG | up to 20 files at once |
| 2 | Scan quality | Laplacian variance, contrast, paper level, ink in a 1.5% band at each edge, phone-photo detection | blurred, small, faint, dark or cropped files get Rescan, not a fraud label |
| 3 | Metadata | PDF Info dictionary (UTF-16, hex, nested brackets), JPEG APP segments and XMP, PNG tEXt | image editor or design app, or modified two or more days after creation: medium |
| 4 | Duplicate file | 24×34 tile fingerprint, masked 8×8 dHash, one-tile shift tolerance | 92% or more tiles identical: strong; the differing area (usually the identity block): medium |
| 5 | Pasted ink | pen-ink seeds (blue minus red above 18), grown into dark pixels; connected strokes only; 32×16 density | correlation of 0.93 or more and size within 4%; three identical strokes, four for strong |
| 6 | Edits | sensor-noise map per 16 px block on flat paper; areas of two or more blocks below 40% of the page median | medium; not judged on phone photos or noise-free files |
| 7 | Filled rows | page skew by projection (±2.5°), table rules by modal spacing, inked rows | compared with days or sessions billed; a mismatch is strong |
| 8 | AI traces | C2PA marks, trainedAlgorithmicMedia, generator names; born-digital signs (noise below 0.4, even light, no scanner) | never the only reason for Priority |
Labels follow verification practice. Priority needs two strong signals, or one strong signal plus another. Needs a check means at least one signal. Rescan means the file could not be read whole. Passed means clean. A member's reply that the service "never happened" is added as a strong signal, never as a verdict.
Blind test. We used another team's test set (17 physiotherapy files with an answer key) because we did not make it. Thresholds were tuned on PERISAI's own samples and the files ending in -01, then frozen for the rest.
| Set | Result |
|---|---|
| PDF, calibration (5 files) | 5 / 5 |
| PDF, blind (12 files) | 12 / 12 |
| JPG without PDF metadata (17 files) | 15 / 17; missed two patches the size of one digit |
The archive stores tile fingerprints (numbers), never images. The fictional samples come from
web/scripts/pindai_samples.py, with realistic scan effects added before the edits, and are tested in
web/src/lib/__tests__/forensik.test.ts.
Sign in through Try the demo (one click, no password, consent required). Switch roles from the role chip at the top right.
| # | Open | What you see |
|---|---|---|
| 1 | Home (West Java verifier) | today's progress, an AI brief with source codes, red, blue and green priority cards |
| 2 | Case Queue | cases sorted by recoverable rupiah, the 40-case daily line, CSV export |
| 3 | A case | a three-line reason per finding, the bill on one rupiah axis, model reasons, the patient's timeline and links |
| 4 | Ask the AI for help | a summary, documents to request, the Permenkes article it relies on |
| 5 | Send to supervisor | the note travels with the case; switch to Supervisor and find it under "Waiting for you" |
| 6 | Confirmed, then the PDF report | two decisions in one chain; the PDF rechecks the chain when it opens |
| 7 | Intake Check | many claims checked before payment, then their supporting files scanned |
| 8 | Facility Audit, Risk Analysis, Ask AI, Proof and Impact | field audits, the risk map and trends, plain-language questions, measured accuracy |
PERISAI is a workspace for BPJS Kesehatan's verification and anti-fraud teams. It does not replace VClaim or VIDI, which still check every claim. It answers the question those systems leave open: out of thousands of flagged claims, which deserve a look today, and why?
| User | Daily question | What PERISAI gives them |
|---|---|---|
| Verifier | "Which case do I work on first?" | an ordered queue, a risk score, reasons per finding, an AI summary, decision buttons |
| Anti-fraud supervisor | "Which cases need my decision?" | cases sent up by verifiers, team decisions, a national map |
| Branch head | "How risky is my area?" | the most unusual facilities, the map, review progress (read only) |
| Analyst | "How many findings of each type in Central Java?" | Ask AI in plain language, no code needed |
BPJS receives about 650,000 claim files a day and flags around 5% as possible fraud. Shared among roughly 1,000 verifiers, that is about 32 flags each, every day, in no order of value and with no reason attached. A verifier can review about 40 cases a day, and most alarms turn out to be fine. Large claims slip through while time goes on small ones.
PERISAI fixes three things:
K1.F3).Official Healthkathon sub-categories covered:
| Sub-category | PERISAI detector |
|---|---|
| Upcoding and unbundling | severity too high (upcoding), split episode (outpatient billing during an inpatient stay) |
| Prolonged stay and readmission | readmission within 30 days, back-to-back stays (gap of one day or less, possibly one episode billed twice) |
| Referral and self-referral | referral concentration on one hospital, too many referrals, referrals older than 90 days |
| Phantom and repeat billing | primary-care contacts that are hard to verify (online, home visits, counselling) |
| Inflated bills, cloning, drugs and devices | not covered: the sample has no drug, device or medical-record detail |
flowchart LR
A["1. Claim arrives"] --> B["2. Compared with<br/>similar patients"]
B --> C["3. Scored and<br/>valued in rupiah"]
C --> D["4. Reviewed by a verifier<br/>with AI help"]
D --> E["5. Decided by<br/>verifier / supervisor"]
E -. "false alarms retrain the model" .-> B
POST /api/v1/claims/score) or the bulk queue in the database.Every number on screen is computed from claim data. The AI writes no number without a source.
| On screen | Source | How to read it |
|---|---|---|
| Risk score (0 to 100 ring) | the strongest signal in the case. Severity: probability of a problem from the tree model. Readmission: probability from the readmission model. Facility: distance above similar facilities (1 − 1/(O/E)) | higher means further from its peers. Not a verdict |
| Can be recovered (rupiah) | probability of a problem × tariff gap. For upcoding: probability × (bill − tariff one severity level lower) | money that could return if the claim proves wrong. It sets the queue order |
| Priority (red, blue, green) | rank by rupiah in the user's queue. Red: top 40 (one verifier-day). Blue: up to 200 (one week). Green: the rest | the score ring takes this colour |
| Reason per finding | written automatically from the same numbered facts, without AI or cost (web/src/lib/insight.ts) | three sentences: what happened, why it is odd, what it means |
| AI summary | an LLM reads numbered facts and official regulation text (retrieval). Sentences with numbers not in the facts are dropped | a suggestion. If the AI is down or out of quota, a non-AI summary still appears |
| Decision | chosen by a person. Saved by the database function ops.decide, which checks role, area and case stage | permanent, hash-chained, unchangeable even by an admin |
flowchart LR
N["Not reviewed"] --> V{"Verifier"}
V -- "Valid" --> W["Closed: valid"]
V -- "False alarm" --> F["Closed: false alarm<br/>model feedback"]
V -- "Request documents" --> M["Waiting for hospital"] --> V
V -- "Send to supervisor" --> S{"Supervisor"}
S -- "Confirmed" --> T["Closed: confirmed<br/>money recovered"]
S -- "Valid / False alarm" --> W
S -- "Send back with a note" --> V
20261028000200_review_chain.sql, 20261029000100_audit_faskes_koreksi.sql) and is
covered by integration tests.All numbers come from the real sample data. Details and confidence intervals: docs/EVIDENCE.md.
| Measurement | Result |
|---|---|
| Expected-severity model, AUC on hospitals never seen in training | 0.811 (195,544 claims, 2,830 hospitals, GroupKFold by hospital) |
| Hospitals billing high severity above expectation (Poisson test, Benjamini-Hochberg FDR 5%) | 88 of 1,906 |
| Official workshop evaluation, 1% audit capacity, real verifier corrections | 5.0× lift against 1.3× for a simple rule (p = 0.022, bootstrap by hospital) |
| Rupiah caught at the same capacity | 12.7% against 4.7% for the simple rule (2.7×) |
| Online scoring (TypeScript) against training (Python) | difference below 10⁻⁹ on 500 claims |
| Per-claim explanation (tree path decomposition) | contributions sum to the model score, difference below 10⁻⁹ |
| Resilience: worker killed mid-batch | 100/100 claims rescored, exactly one score in force per claim |
Honesty is part of the result. A model that predicted verifier corrections reached an AUC of 0.993, but it had learned a shortcut: 85% of the labels came from a single administrative cohort. We rejected it and used the labels only for external validation. See Limits and what we rejected.
Each detector compares a claim or facility with a fair peer group and stores numbered facts
(risk.finding.facts, F1 to Fn). The screen, the PDF and the AI all cite those facts. Counts are from production.
| Detector | Subject | Method | Findings | Rupiah |
|---|---|---|---|---|
| Severity too high (upcoding) | claim | HistGradientBoosting, 300 trees, no hospital identity, predicts the chance of severity II or higher; O/E per hospital with gamma-Poisson shrinkage; p_issue × (bill − tariff one level lower) | 4,450 | Rp 3.84 B |
| Readmission within 30 days | claim | readmission probability model plus O/E per hospital (as in CMS HRRP); dialysis and chemotherapy excluded | 124 | Rp 610 M |
| Back-to-back stays (repeat billing) | claim | consecutive inpatient stays with a gap of one day or less, same patient and hospital | 68 | Rp 237 M |
| Split episode (unbundling) | episode | outpatient claims billed while the patient is still an inpatient | 27 | Rp 10 M |
| Referral concentration | clinic to hospital pair | TF-IDF, SVD and kNN as latent geography; flagged only when the share is far above both latent neighbours and the district | 1,187 | Rp 18.7 B |
| Expired referrals | facility | visits using referrals older than 90 days, O/E against similar hospitals | 202 | Rp 7.0 B |
| Hard-to-verify contacts | primary care | share of online, home-visit and counselling contacts; robust z (median/MAD) per province, z of 3.5 or more | 688 | watched |
| Too many referrals | primary care | referrals per sick visit; robust z per province | 110 | watched |
Patterns are also tested over time. The Risk Patterns page compares rates per 10,000 claims (October to December against July to September 2024) with a conditional Poisson rate test, and calls a rise only when the chance of luck is below 5%.
Privacy and consent. The public Privacy Policy and
Terms of Service follow Indonesia's Personal Data Protection Law (UU 27/2022): data
types, purpose, legal basis, retention, rights, security, prohibited uses and prototype status, with a dated version.
Both sign-in forms require a consent tick; without it the server refuses. Consent is written to the hash-chained audit
trail (log_event('masuk', details.setuju_ketentuan)). A secret scan on 1 November 2026 found no keys in the
repository, its git history or the live JavaScript bundles. The only key in the browser is Supabase's publishable key,
which row-level security constrains.
| Rule | Content | Enforced in |
|---|---|---|
| Access by area | verifiers and branch heads see only their province | PostgreSQL RLS (ops.can_see_prov) |
| Who decides what | verifiers at step one, supervisors only on cases sent up, branch heads read only | ops.decide (security definer) |
| Required notes | Valid, False alarm, Confirmed and Send back need a note of at least 5 characters | ops.decide |
| 72-hour correction | the last decider may decide again; the correction is a new row | ops.decide |
| Records cannot change | UPDATE, DELETE and TRUNCATE on the ledger are rejected, admins included | triggers ops.chain_hash, forbid_mutation |
| The AI does not decide | the AI writes drafts; decision buttons belong to people | application (lib/agent.ts) |
| No identities to the AI | HMAC tokens, PII redaction, numbered facts only | gateway lib/llm.ts |
| AI numbers are checked | sentences with numbers absent from the source are dropped | grounded() |
| AI quota | per user and 300 a day overall, with a cache | ai.take_token |
The Governance page, open to every role, recomputes the SHA-256 of every row in ops.decision and ops.audit_log
on each visit. It shows intact or broken, the time of the check, the policies above, the role matrix and the latest
links in the chain. The check writes nothing.
User testing on 1 November 2026 cut the sidebar from eleven menus to eight. Follow-on pages became tabs, and their routes did not change.
| Menu | Tabs and follow-on features |
|---|---|
| Home | today's tasks, AI brief, priority cards |
| Case Queue | Queue, Closed and reports, Dispute Time Machine. Each case ends with Continue with this case: supporting files, trail and disputes, PDF report |
| Facility Audit | Facility Audit, Referral Network |
| Intake Check (before payment) | Check claims (one, or many at once pasted from Excel), Claims data file (scores and episode patterns), Supporting files (Document Authenticity Scan). Results connect: a held claim opens its files at /pindai?claim=, and a claims file dropped into the scan fills in the billed days automatically |
| Risk Analysis | Risk Map, Risk Patterns, National Reference |
| Ask AI | data, rules, how-to, automatic maps |
| Proof and Impact | Accuracy Proof (per detector, blind scan test), Impact Simulator |
| Governance | chains, policies, detector thresholds; an Admin tab for administrators |
How It Works and the Glossary sit as small links at the foot of the sidebar. How It Works follows one claim through five stages (before payment, daily work, facilities, the bigger picture, trust) and covers every feature with a real screenshot.
Design rules, from five rounds of user testing:
pnpm copy checks the code and
scripts/uat-text.mjs checks the rendered text, contrast, font size and card alignment.Sign in. Open the demo link, choose Try the demo, pick a role, tick the consent box and enter. A six-step welcome tour covers your role and rights, the menu, the colours, where to start, the data source and the Guide button.
As a verifier (West Java):
As a supervisor (national): Home shows the cases waiting for you. Open one, read the verifier's note, choose Confirmed, Send back, Valid or False alarm, and save. Approve or reject field-audit proposals. Made a mistake? Reopen the case within 72 hours and Save correction.
As a branch head (Central Java, read only): open Risk Analysis and Facility Audit, click See evidence on the most unusual facility, then open its cases. Decision buttons are not available for this role.
Everyone: Ask AI takes plain questions such as "top 10 hospitals by findings", then follow-ups like "what about Central Java?".
flowchart LR
A[VClaim claim / batch API<br/>or claims data file] --> B[HMAC token<br/>identity removed]
B --> C[Case-mix features<br/>PostgreSQL]
C --> D[Severity model + 8 detectors<br/>O/E, FDR 5%]
D --> E[Score, rupiah, numbered facts]
E --> F[Money-first queue<br/>40 cases a day]
F --> G[Verifier decides<br/>or sends up]
G --> H[Supervisor<br/>Confirmed / Send back]
H --> I[(SHA-256 hash ledger)]
I --> J[Sealed PDF report]
D --> K[Facility Audit<br/>propose, approve, outcome]
E -.-> L[AI: summary + Permenkes article<br/>numbered facts only]
L -.-> G
flowchart LR
A["Claim or batch<br/>API v1"] --> Q["pgmq queue"]
Q --> W["Scoring worker<br/>called by pg_cron every minute"]
W --> P[("PostgreSQL<br/>bitemporal scores")]
P --> U["Verifier UI<br/>RLS by area"]
U --> L["Decision ledger<br/>hash chain"]
W -. "fails more than 3 times" .-> D["Dead-letter queue"]
| Component | Where | Role |
|---|---|---|
ml/ (Python) | team laptop | ingest the sample, HMAC tokens, risk engines, evaluation, publish to the database |
| PostgreSQL 17 | Supabase, Singapore | 5 schemas, RLS by area, monthly partitions, ledger, AI history |
| pgmq, pg_cron, pg_net, Vault | Supabase, Singapore | scoring queue and DLQ, worker trigger every minute, secret storage |
| Private Storage | Supabase, Singapore | model artefact registry |
| Next.js 16 | Vercel, Singapore | verifier UI, API v1 with OpenAPI 3.1, scoring worker (TypeScript tree evaluator) |
| LLM gateway | Vercel, Singapore | AI summaries and Ask AI; only computed facts are sent; the model can move on-premises |
Full description: docs/ARCHITECTURE.md.
Problem. A hospital can dispute a decision years later. BPJS must then show what the system knew, who decided, and that nothing changed since.
Design. One PostgreSQL 17 database with five schemas (ref, core, risk, ops, ai), 28 tables and 2 views,
all under RLS. core.claim_ritl is range-partitioned by admission month into 19 partitions. Each score row has
valid_from and valid_to, and risk.score_as_of(claim, time) rebuilds the score in force at the second of a
decision. ops.decision and ops.audit_log are guarded by triggers that hash each row with the previous one and
reject updates and deletes. Users have no direct INSERT or UPDATE rights; writes go only through security definer
functions.
Evidence. 17 versioned migrations in supabase/migrations/, 50 RLS policies, 6 ledger triggers.
Problem. PERISAI is most useful before a claim is paid, so BPJS systems must be able to call it.
Design. A versioned REST API documented with OpenAPI 3.1 at /api/v1/openapi. POST /api/v1/claims/score is
idempotent: the score is computed first and written only when it differs from the one in force. Systems authenticate
with x-api-key (bursts of 600, refilled at 50 a second); users with a Supabase session (120, refilled at 2 a second).
All input is validated with zod, and errors follow RFC 9457. Pages render on the server, and actions use Server Actions.
Evidence. Integration tests cover online and batch score parity, idempotency, validation and authorisation (400, 404, 401).
Problem. Beyond pre-payment checks, BPJS needs to rescore large volumes, for example when a model is released.
Design. Jobs go into a pgmq queue inside PostgreSQL. pg_cron calls the worker every minute; a visibility timeout returns unfinished jobs to the queue, and jobs that fail three times move to a dead-letter queue. Keeping the queue next to the data avoids running Kafka or a separate broker.
Evidence. A worker killed mid-batch: 100 of 100 claims rescored, exactly one score in force per claim, about 10 claims a second after recovery. The Admin page can rerun this test.
Problem. A model that only asks "is this claim odd?" floods verifiers with alarms they cannot clear.
Design. The severity model predicts what a claim should look like from case mix alone (age, class, CMG, diagnoses, history), never the hospital. The gap between billed and expected, priced in rupiah, sets the order. Explanations decompose each tree path (Saabas), so contributions add up to the score. Generative AI is fenced in: it sees numbered facts and official regulation text found by retrieval (768-dimension embeddings over four regulations), every point must cite a fact or article, and unsourced numbers are removed. Ask AI writes one read-only SELECT over an allowlist of 11 tables, checked again in the database under the user's RLS, and answers only from aggregate results. Every call is logged with a payload hash and the fact IDs used.
Evidence. Lift of 5.0× on real corrections; AUC 0.811 on unseen hospitals; parity with training below 10⁻⁹.
Problem. Judges and BPJS need to know the running version is the code under review, and the design must move to BPJS servers.
Design. Each push to main deploys to Vercel in Singapore, next to the Supabase database. /api/health reports the
running commit. web/Dockerfile builds a three-stage image for on-premises use. AI cost is capped by a cache, a token
bucket per user and overall, and max_tokens of 160 to 900 per feature with a 25-second timeout.
Evidence. Integration tests run against production after deploys; the live commit is visible at /api/health.
Problem. Claim data is health data, a specific category of personal data under UU 27/2022.
Design. Defence in layers, each able to hold if another fails: minimal data (tokens, no names), authorisation in
the database rather than the UI, an AI gateway that redacts and checks, a ledger that cannot be rewritten, and browser
headers that block framing and indexing. A STRIDE threat model is in docs/SECURITY.md.
Evidence. Integration tests confirm that anonymous requests read nothing (401), that a West Java verifier cannot
see other provinces even through partitions, that Ask AI is refused writes and pg_sleep inside the database, that AI
history stays private to each user, that forged user IDs are rejected, and that even the service role cannot alter
the ledger.
| Layer | Tool | Coverage |
|---|---|---|
| Unit (web) | Vitest, 101 tests | file forensics (PDF metadata, noise, table rows, skew, pasted ink, labels), upload episode patterns, detector thresholds, XLSX reader, CSV/TSV/JSON parsing and column mapping, upload validation, SQL validator, PII redaction, number grounding, model parity and explanations, priorities, the Poisson rate test, the lift curve, decision rules per role, reasons per finding, page tours |
| Unit (ML) | pytest | facility ID reconstruction, tokenisation |
| System integration | web/scripts/sit.mjs, 36 tests | sign-in and consent, RLS, API, distributed queue, ledger, Ask AI and streaming, AI history, rate limits, AI summaries, regulation retrieval, PDF reports, field audits; run against production |
| User acceptance | headless Chrome, three roles | workflows, two languages, light and dark themes, 390 px phone and 1440 px laptop |
| On-screen text | scripts/uat-text.mjs | three roles, two languages, 390 and 1440 px: at most 15 words, font of at least 12 px, WCAG AA contrast, no overflow |
Integration-test cases are marked is_test and never appear in queues or statistics. Full results:
docs/SIT.md.
Requirements: Node.js 24 with pnpm, Python 3.12 with uv, the Supabase CLI, and a Supabase project with the pgmq, pg_cron, pg_net and Vault extensions.
# 1. Database
supabase link --project-ref <ref> && supabase db push
# 2. Data and model (team laptop; needs the extracted "Data sampel 2015-2024.zip")
cd ml && uv sync
uv run python -m perisai_ml.ingest --edition reguler # .dta to parquet
uv run python -m perisai_ml.serving # serving tables: tokens, facilities, aggregates
uv run python -m perisai_ml.publish # to Supabase (COPY, one transaction)
uv run python -m perisai_ml.pipeline --version 2026.10.23 # risk engines, cases, external validation
uv run python -m perisai_ml.publish --risk # to Supabase (refuses if the ledger has decisions)
uv run python -m perisai_ml.artifacts # model artefacts to private Storage
# 3. App
cd web && pnpm install
cp ../.env.example .env.local # fill in the environment variables
pnpm seed:users # invited accounts per role
pnpm seed:regulasi # 768-dimension embeddings into ai.reg_chunk
pnpm dev # http://localhost:3000
pnpm test && pnpm typecheck && pnpm lint
BASE=http://localhost:3000 node --env-file=.env.local scripts/sit.mjs
Environment variables are described in .env.example. The demo opens with one click from a link shared
only with judges, and is served with noindex. An optional access code (PERISAI_DEMO_CODE) can be switched back on,
and PERISAI_DEMO_LOGIN=0 turns the demo off.
Choosing a language model. Every call goes through an OpenAI-compatible endpoint set by LLM_BASE_URL,
LLM_API_KEY and LLM_MODEL. Gemini is used today; vLLM, Ollama, Azure OpenAI or a BPJS in-house model needs only
those three variables. Without credentials, AI summaries and Ask AI fall back to deterministic paths that still work.
ml/ Python pipeline: ingest, tokenisation, risk engines, evaluation, publish
perisai_ml/engines/ severity, episodes, referral, fktp, tariff
supabase/migrations/ schema, RLS, RPC, queue, cron, ledger, AI history (versioned)
web/ Next.js 16 app
src/app/ pages and API route handlers
src/lib/ scoring, model evaluator, AI agents, Ask AI, i18n, forensics
src/components/ shared UI, maps, guided tours
scripts/sit.mjs system integration tests
scripts/regulasi.jsonl retrieval corpus: official text of four regulations, by article
docs/ architecture, security, evidence, integration results, roadmap, Indonesian README
| Approach | Result | Decision |
|---|---|---|
| Model predicting verifier corrections | AUC 0.993 from an administrative-cohort leak | rejected; labels used only for validation |
| Rule: bill above the empirical tariff | 9,537 claims, precision 0.08% | rejected |
| A single referral benchmark (district or latent neighbours) | too many false alarms | flagged only when both benchmarks fail: 1,187 of 6,388 clinics |
| Referral-back patients kept by hospital | spread evenly, no outlier hospital | reported as a policy finding |
Known limits:
web/Dockerfile on BPJS servers with an internal PostgreSQL and an internal LLM through
LLM_BASE_URL, in line with the guidance that member data stays with in-house AI.Presentation deck: docs/PERISAI_BPJS_Healthkathon.pdf (18 slides).
Further reading: Architecture, Security and privacy, Evidence and validation, Integration results, Build plan.
Team RAEVA, BPJS Kesehatan Healthkathon 2026.
| Member | Role | Built |
|---|---|---|
| Muhammad Rafi Dhiyaulhaq | Team lead, AI engineer, full-stack developer | risk model and eight detectors, PostgreSQL schema with RLS and the ledger, scoring API, AI assistant with retrieval, the app |
| Belva Charissa Putri Have | Data scientist, data analyst | analysis of the claim sample, five rounds of user testing (priority colours, page flow), the business case |