Turn NHI reimbursement rules
into CQL you can verify

Physicians review every condition. A CQL engine verifies every one.

Review screen, in Traditional Chinese: the clause list on the left, the AND / OR decision logic in the middle, and on the right the source quote, evidence contract and accept, reject or not-applicable decision for condition C3.
14 daysLLM prompts and responses kept for audit
3 valuestrue · false · null; missing data is never guessed
4 triesrepair budget per clause, shared by all three stages
292 + 77backend and frontend tests
Pipeline

Seven steps, with a code check between each

Quotes must appear in the source, node definitions must be valid, and the whole IR must pass validateIr. Whatever fails is repaired on its own.

  1. 01ImportPaste clause text or upload a PDF or docx, then pick the clauses to analyze.
  2. 02DecomposeVerbatim quotes → atomic conditions → AND / OR / NOT groups, extracted in three stages.
  3. 03Cross-checkOne small call per condition, carrying only the quotes it cites, to check it against the source and normalize values.
  4. 04ReviewPhysicians accept, reject or mark each condition not applicable, then sign off the clause.
  5. 05GenerateOne Boolean parameter per condition, defines for the logic, compiled to ELM.
  6. 06VerifyOverride parameters one at a time, run the CQL with cql-execution and compare with the IR’s three-valued logic.
  7. 07ExportCQL, ELM, evidence contracts and fixtures, packaged per clause or per document.
For reviewers →

Every condition traces back to the source

Physicians never read CQL. The canvas shows conditions, groups and where each came from. A doubtful condition can be re-run alone, and edited logic invalidates the sign-offs it touches.

01 · Quote check

Each condition points back to the source

The LLM copies verbatim quotes first, then derives conditions from them. A quote with no exact match in the source is flagged for review, never passed automatically.

Read the docs →
How it's enforcedMatching ignores whitespace and full- vs half-width characters. Quotes that can't be repaired are logged as “N quotes pending review”; the analysis still completes.
02 · Re-analyze one condition

Re-run only the condition in doubt

If one condition looks wrong, call the LLM again for that condition alone. The result goes through the normal logic-edit flow; other conditions are untouched.

Read the docs →
How it's enforcedReview decisions on changed conditions are invalidated. Decisions on untouched conditions are kept.
03 · Logic canvas

Edit the logic on a canvas

Edit, add and move conditions, and switch groups between AND, OR and NOT. On save, the CQL is regenerated and the ablation check re-runs.

Read the docs →
How it's enforcedSaves carry a baseHash. If someone else saved first, you get a 409 instead of overwriting their work.
For engineering and data teams →

The IR is the single source of truth. The generated CQL is run to check it.

CQL is generated from the IR, never written by hand. Every stage of LLM output passes a code check first, and any logic change triggers regeneration and re-verification.

04 · renderCql

CQL is generated from the IR

Each atomic condition becomes a Boolean parameter and each group a define. It evaluates without a FHIR retrieve; an external adapter supplies the evidence.

Read the generated CQL →
How it's enforcedSaving the logic regenerates the CQL and re-runs the ablation check, so what you see is what gets exported.
05 · Ablation check

Every scenario is actually run

cql-engine compiles the CQL to ELM, and cql-execution runs it with parameters overridden one at a time. Each result must match what the IR's three-valued logic expects.

Read the generated CQL →
How it's enforcedWhen evidence is missing it returns insufficient-data instead of guessing.
06 · Targeted repair

Repair only the part that failed

Decomposition runs in three stages: quotes, conditions, groups. Each is checked in code first. A failing part gets its own repair step and nothing else re-runs; a clause gets at most 4 repairs, shared by all three stages.

Read the generated CQL →
How it's enforcedTruncated JSON is never auto-closed. A parse failure sends the error position back to the model to ask again.
CQL primer →

Read any CQL file in four parts

Every CQL file the workbench exports has the same shape. Here is the logic for the sample clause, with a note beside each part.

Clause_Sample.cqlrenderCql output · excerpt
library Clause_Sample version '1.0.0'

using FHIR version '4.0.1'
1

Header

library names the logic and its version. using FHIR declares the FHIR R4 data model.

parameter "C1" Boolean default null
parameter "C2" Boolean default null
parameter "C3" Boolean default null
parameter "C4" Boolean default null
2

Inputs: one parameter per condition

C1–C4 are each a Boolean parameter that defaults to null. The evidence adapter fills in true or false after checking the record. If it finds nothing, the value stays null.

context Patient

define "Criterion C1":
  "C1"
// Criterion C2–C4 follow the same pattern

define "Group G1":
  "Criterion C1"
  and "Criterion C2"
  and "Group G2"

define "Group G2":
  "Criterion C3"
  or "Criterion C4"

define "Eligibility":
  "Group G1"
3

Logic: conditions combined

define gives an expression a name. The and / or structure matches groups G1 and G2 on the review screen. context Patient means each define is evaluated for one patient.

define "Eligibility Status":
  if "Eligibility" is true then 'eligible'
  else if "Eligibility" is false then 'ineligible'
  else 'insufficient-data'
4

Result: three outcomes

is true and is false split the result into eligible, ineligible and insufficient-data. Missing data never counts as ineligible.

With nulltrue and null → nullfalse and null → falsetrue or null → trueRead the full CQL primer →
Governance

Still under control after go-live

Each sign-off applies to one version of the logic. Who changed what, and when, is always on record.

  • Three rolesadmin, reviewer and viewer. Viewers see every page but can't analyze, sign off or edit logic.
  • Edits invalidate sign-offChanged conditions and the clause sign-off are invalidated, and the clause status is re-derived from the decisions that remain.
  • Lock and send backReviewers can send an approved clause back for re-review. Only an admin can unlock a locked one.
  • Everything is loggedEdits, send-backs and re-runs go to audit_log. LLM prompts and responses are kept for 14 days.
  • No silent overwritesSaves carry a baseHash. When two people edit at once, the later save gets a 409 instead of overwriting the earlier one.
  • Fidelity checkA round-trip back-translation compares the output with the original clause. Any logic edit marks it stale until someone re-runs it.
Quick start

Running locally in three steps

Full install guide →
  1. 1Replace the defaults firstBefore go-live
    Copy .env.example, set JWT_SECRET, and change every default password.
  2. 2Start the full stack
    $ docker compose up -d
    $ cd frontend && npm ci
    $ npm run dev
  3. 3Open the workbench
    :5173Workbench (frontend dev server)
    :8081Full stack (via nginx)
    Add a model profile under 管理 → LLM 設定 (Admin → LLM settings), then paste your first clause.

Start with one clause

Paste text or upload a PDF or docx. Decompose, review, verify and export, all in one workbench.

  • docker compose brings up the whole stack
  • Works with local Ollama or cloud LLMs
  • Every edit is written to audit_log