Initial Release
TL;DR:
bash tools/setup.sh(one command to bring up the whole stack) βpython3 tools/e2e_ui_flow.py
(one command to reproduce the full demo). Prerequisites: Docker + Compose, a free InterSystems container
registry account (to pull the IRIS image) and your own LLM key (required by the AI features); on Windows use
WSL2 + Docker Desktop.
An AI-driven data pipeline automation demo built on InterSystems IRIS for Health.
Sources (2) = FHIR endpoints / SQL tables Β· Targets (3) = DB tables / SOAP services / FHIR repositories β
any combination (several pipelines can coexist inside one Production). The platform analyzes source/target
interfaces (FHIR Profile / SQL columns / WSDL operation semantics) and registers data assets; an AI
(OpenAI-compatible LLM) recommends "asset β target" matching and field mappings; after confirmation it
generates an IRIS interoperability Production and delivers the data. Chinese diagnoses/drugs are converted
through the terminology server (the source of truth for terminology mappings).
FHIR / SQL source β interface analysis (Profile / columns) β register assets
β β
AI Matching (asset β target + field mappings) β user confirms
β β
Transformation Plan β IRIS Production β DB table / SOAP service / FHIR repository
The demo ships a bilingual UI: Chinese (default) and English at
/en(language switch in the top bar).
All decisions & generation run through the LLM at runtime. Code only reads facts, parameterizes, validates and
backfills β there are no hard-coded mapping / topology / conclusion templates:
| Capability | AI (LLM) decides | Code only does |
|---|---|---|
| Interface / source analysis | Asset semantics, polling-key hints, target write/read direction, runtime-contract interpretation | Read facts (Capability/columns/WSDL), persist |
| Data mapping | Assetβtarget matching & field mappings (incl. concat() expressions) |
Structure normalization, integrity checks |
| Data pipeline | Component set & order, naming (per group in multi-pipeline) | Registry fills className/settings; backfills essentials |
| Validation & fix | Judges whether issues are real + chooses fix actions | Fact-check tools, mechanical pruning, explicit fallback |
AI red line: an LLM failure is an explicit failure surfaced to the user (missing key / timeout / malformed
output, with the Agent name) β never a silent rule result. Rules/registry only do β parameterization
β‘ completeness validation β’ backfilling essential pieces (flagged ai_supplemented). Every generation carries an
auditable ai payload (driven/components/supplemented/c2_rule_rebuilt) and token usage is logged.
^demo.ValidationIssue)export_validation_issues.py).Generation is no longer a full recompute every time β it is incremental at the pipeline level:
(source datasource, target) is always the same pipeline β no matter how manyPIPE_<src>_<tgt> record, updated in place).unchanged=true / render_skipped=true; the UI reports "all pipelines already exist and are unchanged").(source, target) groups inside one payload are merged and reporteddup_merged (auditable).force=true) when you really want a redesign.The AI Agents page classifies platform capabilities per the industry reference (Anthropic, Building effective
agents) and shows the reasoning for every entry:
| Class | Meaning | Items in this project |
|---|---|---|
| Tool | Deterministic callable, no LLM | Connection profiler / connectivity gate / fact checks (pipeline_validator.check_*) / rule checks / BP static admission / terminology gap precheck / target column & key facts / WSDL entity analysis / terminology lookup BO (demo.TerminologyOperation) / type & FHIR model registries |
| Workflow | LLM involved, but the path is predefined in code | Interface analysis (tool-driven facts + one LLM pass); Pipeline Design Agent B (decides once β the platform renders along a fixed path) |
| Skill | Packaged instructions/knowledge, single step, no tool loop | Transformation generation (A), knowledge polish, terminology judgement (C3 / C3-Dx: single-shot judgement + lookup tool) |
| Agent | Tools + multi-round autonomous loop + goal | Transformation validation-fix (C1); Pipeline validation-fix (C2) |
The page also shows the Skill catalog (the controlled list used for AI decisions, GET /api/agents/skills):
6 pipeline-design Skills (sql2fhir-patient-tx / sql2db / fhir2db / sql2soap / fhir2soap / fhir2fhir,
with applies-to sourceβtarget, status and topology roles) and 2 terminology Skills (cn2snomed / cn2rx, with
source/target systems and the judging agent) β each with a usage counter for the current environment.
The catalog is only used for parameterization; choosing which Skill to use remains an AI decision.
The platform separates conversion into three layers instead of assuming a 1:1 "source β target" mapping:
Plans are managed through /api/source-assets, /api/target-interfaces and /api/transformation-plans, and can be
fact-verified via /api/ai/verify. Only when you click Generate / Rebuild Pipeline does the system call AI to
design the Production topology and hand it to IRIS to compile and start.
ai_supplemented), Knowledge Polish (dedupe + restructure validation experience β Obsidian vault).allow_rule_fallback=true and is flagged rule_fallback:true).ai audit object (driven/components/supplemented/c2_rule_rebuilt), and the_lastUpdated cursor.EnsLib.HTTP.GenericOperation) with schema-driven resource assemblyPatient / Encounter / Condition / MedicationRequest / β¦ from the column metadata in^demo.Config("fhir","schema",<type>), then PUT + Basic Auth to the target FHIR repository).backend/services/mock_soap.py, persists to PatientEntity and returns an acknowledgment).POST /api/pipelines/generate body pipelines: [group1, group2]).demo.TransformProcess is reused β e.g. TransformProcess__sql2soap). Each pipeline's source BS pointsTargetConfigNames at its own BP, and the BP reads its own parameters from ^demo.Config("bp", <BP name>)JavaGateway (one JDBC gateway),FHIRSyncService (cursor) β FHIRQueue β FHIRService (one-by-one, independent sessions).EnsLib.SQL.Service.GenericService (Query + KeyFieldName).check_connection gate β C1 transformation validation β C2 pipeline validation (topology/compile/start/messages)^demo.ValidationIssue; they feed C1/C2 prompts at runtime and can be read viaGET /api/pipelines/validation-issues, appended via POST, polished by LLM via POST /validation-issues/polish,export_validation_issues.py.docker exec dataflow-backend python /app/generate_mock_data.py --fhir N --obs M --sql KPatientEntity rows).| Component | Stack |
|---|---|
| Database / integration engine | InterSystems IRIS for Health Community (containers.intersystems.com/intersystems/irishealth-community:2026.1) |
| FHIR source | Built-in IRIS FHIR Server (core R4, FHIRSERVER namespace) |
| Backend API | Flask + IRIS Native SDK + OpenAI SDK |
| Frontend | Vue 3 + Element Plus + Vite + nginx (bilingual zh/en) |
| AI | OpenAI-compatible endpoint (base_url / api_key / model) |
| Orchestration | Docker Compose (5 services: iris, backend, frontend, iris-terminology, embedding; 4 started by default β embedding is skipped, see "Demo Environment Initialization") |
IRIS multi-role (single instance):
FHIRSERVER namespace = built-in FHIR Server β default FHIR target repository (conversion results land here)/csp/healthshare/fhirserver/fhir/r4/DEMOFHIR namespace = second, independent FHIR repository (main DB DEMOFHIR + repository data DBsDEMOFHIRX0001R/V) β endpoint /csp/healthshare/demofhir/fhir/r4/; fully isolated from FHIRSERVERFHIRConfig.BASE_URL and the mock-data scripts all default to it).iris/setup.sh step 2b (idempotent); re-runnable on a live instance viapython3 tools/create_fhir_repo.py (with 15 built-in checks).USER namespace = the platform (targets / Production / Mappings / runtime contracts).Demo tables (SQLUser): Patient/Observation (FHIRβDB landing) Β· PatientSource (SQL-source demo table) Β·
PatientEntity (SOAP mock delivery result) Β· FHIRQueue (incremental fetch queue).
Prerequisites: Docker + Docker Compose (Windows: WSL2 + Docker Desktop recommended; the repo enforces LF β see prerequisite 7 below).
New-environment prerequisites (read this when cloning onto another machine)
- The IRIS image comes from the InterSystems container registry (free account required).
irishealth-communityis not hosted on Docker Hub; register (free) at
https://containers.intersystems.com and log in first:Without login,docker login containers.intersystems.com # username/password = the ones you registereddocker compose upfails to pull the image (image distribution is bound by the
InterSystems license; this repo does not redistribute it).
Also allow β 20 GB of disk and some patience: the first build/init takes 20β40 minutes
(the embedding container downloads and installs torch plus the local model on first start; later builds
hit the build cache).- Submodule dependency β the terminology server is a separate project.
termsrvis pulled in as a
git submodule (zlnick/iris-terminology-server,
branchdemo-community), and theiris-terminologycontainer is built from it:
- Clone with submodules:
git clone --recurse-submodules https://github.com/zlnick/DataPipelineDemo.git- Already cloned without it?
git submodule update --init --recursive
β If it fails with Unable to find current revision in submodule path termsrv, the submodule repo
(zlnick/iris-terminology-server, branch demo-community) is either not public yet, or that branch has not
received the commit recorded here. Self-check: git ls-remote refs/heads/demo-community
(without the submodule theiris-terminologybuild fails; the main platform still runs, but
terminology degrades to "keep source coding +meta.tag=urn:cn-nhsa:term-map|unmapped",
and tools such asterm_map_build.pyare unavailable)- How it is built: the compose service uses
build: context: ./termsrv(termsrv/iris/Dockerfile),
sodocker compose up -dbuilds it automatically; to build/rebuild it alone:
docker compose build iris-terminology && docker compose up -d iris-terminology- The terminology server keeps its data directory at
./data/iris-terminology, but the mappings
(/mapping/*) actually live in a DB inside the container: rebuilding the container loses them β
re-import withbash tools/term_map_import.sh(same aspython3 tools/term_map_sync.py import;
setup.shalready does this, idempotently).tools/term_map_seed.pyis the seed generator, not an
importer. To only refresh the platform extension classes without a rebuild, hot-load with
bash tools/termsrv_load.sh(no data loss). Terminology material underdata/terms-inbox/
(GB/T 14396-2016 ICD-10 ~20k entries + NRDL/CBIH Chinese drug catalogues) is shipped in this repo
and loaded into the terminology server concept tables (Terminology_Icd10.Concept/
Terminology_Drug.Code) bybash tools/term_data_load.shβ idempotent, and invoked bytools/setup.sh.
The CLINIC demo-data generator depends on it (diagnosis/drug Chinese names come from the terminology
store); re-run it after a container rebuild. Provenance and terms: seeNOTICE.
The CLINIC demo source tables (Patient/Encounter/Diagnosis/MedicationOrder) are created
idempotently bybash tools/clinic_init.sh(create-only-if-missing, never drops data;
setup.shruns it, and the backend also re-checks on startup) β without them the CLINIC SQL-source
service errors out and the "generate demo data" button fails.
Also: a ready-made mapping seed (data/seeds/term_map_seed.json, 81 mappings) is imported
automatically bytools/setup.sh(tools/term_map_sync.py import); the terminology server platform
extensions (/mapping/*routes +CodeMap) are overlaid fromtermsrv-patches/(idempotent), so
terminology conversion works right after cloning regardless of the submodule remote state.
- JDBC driver jar β one command, no download. The backend's "test connection / pick schema & tables /
analyze columns / DB metadata discovery" uses JayDeBeApi + JPype and needsintersystems-jdbc-*.jar
(a proprietary InterSystems artifact, not committed to this repo). The official IRIS image already
ships it, so a one-shot extraction script is provided:(If everything is already running: extract, thendocker compose up -d iris # start IRIS first (the driver is inside the image) bash tools/fetch_jdbc_jar.sh # extract it into ./jdbc/ (no download; same version as IRIS) docker compose up -d # then start the restdocker compose restart backend; β that restart re-runs
init_data.pyand rebuilds the target tables β don't do it casually on an environment that already holds
demo data. To use a custom driver, simply drop the jar into./jdbc/.).env:cp .env.example .envand fillLLM_BASE_URL / LLM_API_KEY / LLM_MODEL
(without it AI features fail explicitly rather than silently degrading; the platform still starts).data/embedding-model(local embedding model) needs no manual step: the embedding container
downloads it automatically from ModelScope on first start (Qwen/Qwen3-Embedding-0.6B, needs network).- On a flaky network, just re-run
bash tools/setup.shβ it is idempotent (submodule /.env/
data dirs / build / seed import all skip what is already done); if the submodule fetch was interrupted,
re-running recovers it.- Terminology vectorization (optional, off by default).
setup.shneeds no vectors (conversion only
uses the prepared mappings; theembeddingcontainer is skipped by default, saving the ~1.1 GB
first-run model download). To try vectorization / semantic search / AI mapping back-fill yourself, see the
standalone section Terminology Vectorization (optional, standalone test).- Cross-platform line endings (LF everywhere):
.gitattributesat the repo root enforces
* text=auto eol=lf(expliciteol=lffor*.sh/*.bash/*.cls/Dockerfileand*.csv/*.tsv;
*.jar/*.zip/images/fonts/xlsx/pdfare treated as binary). Windows users (Git for Windows defaults to
core.autocrlf=true) can end up with CRLF checkouts, which break things such as:
- scripts inside containers:
sh: 1: /shared/setup.sh: not found(IRIS),
exec /app/entrypoint.sh: no such file or directory(embedding)- host bash:
set: pipefail: invalid option name
Fix:git config --global core.autocrlf falseand re-clone (orgit checkout -- .so.gitattributesapplies).
bash tools/setup.sh && python3 tools/e2e_ui_flow.py
It performs: register the CLINIC SQL source β test connection β pick tables β generate demo data β register a FHIR
target β AI matching β save mappings β generate the pipeline, then prints the result.
Measured: result: OK, validation ok: True, FHIR landing Patient 3 / Encounter 4 / Condition 7 / MedicationRequest 7.
β Fill in
.env'sLLM_*first (required by AI matching/generation); mappings are AI-judged and the generated data
is randomized β the flow and structure are reproducible, the exact mappings/codes/counts are not.
bash tools/setup.sh # ONE command: submodule + .env + data dirs + build & up + JDBC extract + CLINIC source-table init + terminology concept load + mapping-seed import + health check
docker compose up -d # first run builds images & initializes FHIR Server + target tables
http://localhost # Chinese UI (default) | http://localhost/en # English UI
Stop: docker compose down. To wipe data (FHIR data / target tables / registrations):
# Windows / Docker Desktop (/dur = named volume dataflow-iris-dur):
# the volume is declared in the override file, so `down -v` removes it **when the override is passed**
# (without the override, -v does NOT touch it):
docker compose -f docker-compose.yml -f docker-compose.windows.yml down -v
# equivalent (containers already stopped):
docker volume rm dataflow-iris-dur
# then: bash tools/setup.sh (first boot rebuilds the instance data)
# macOS / Linux (/dur = bind mount ./data/iris): `-v` does nothing for bind mounts β move the dir away:
mv data/iris data/iris.bak-$(date +%Y%m%d) # then: docker compose up -d
bash tools/setup.sh does)Goal: turn a fresh clone into a runnable demo with one command. No vectorization is done by default
(the demo does not need it, and initialization stays fast).
| Step | Action | Result / notes |
|---|---|---|
| 1 | Fetch the termsrv submodule + apply the termsrv-patches/ overlay |
Build source for the terminology server; the overlay is idempotent (applied=0 = already current) |
| 2 | Prepare .env (copied from .env.example if missing) |
AI features need LLM_*; without it AI endpoints fail explicitly while the platform still runs |
| 3 | Create data/ subdirs |
data/iris, data/iris-terminology, data/terms-inbox, data/embedding-model |
| 4 | Build & start containers | 4 by default: iris, backend, frontend, iris-terminology (add --with-embedding to include embedding) |
| 4b | Windows: use a named volume for the IRIS data dir (docker-compose.windows.yml, enabled automatically by setup.sh) |
On Windows/WSL /dur moves from the ./data/iris bind mount to the named volume dataflow-iris-dur (created + chowned to irisowner). Reason: bind mounts through Docker Desktop's file-sharing layer make chown/rename unreliable, so IRIS's first-boot "move data directory" fails with EPERM β container exits (1); a named volume is real ext4 inside the VM and removes this whole class of problems |
| 4c | Wait for core services (bounded) | Wait for IRIS/backend first (--wait 300/180) before the table/terminology loads below β a fresh instance's first FHIR initialization often exceeds 180s, which previously made clinic_init / term_data_load report a false Access Denied |
| 5 | Extract the JDBC driver | One command copies intersystems-jdbc-*.jar out of the IRIS image into ./jdbc/ (no downloads) |
| 6 | Create CLINIC source tables | Patient/Encounter/Diagnosis/MedicationOrder β idempotent (create-only-if-missing, never drops data; step 4c already waited for IRIS) |
| 7 | Load terminology concepts | ICD-10 20,484 + drugs NRDL 3,919 / CBIH 19 β Terminology_Icd10.Concept / Terminology_Drug.Code |
| 8 | Load the mapping seed | data/seeds/term_map_seed.json 81 entries β the terminology server's mapping table (source of truth) |
| 9 | Health check | Prints backend / frontend / terminology (best-effort probing, never a hard failure) |
| 10 | Private web server self-healing (iris/setup.sh step 7) |
Points httpd's PidFile at /tmp/httpd.pid inside the container (avoiding rename being rejected on Windows bind mounts), starts httpd when needed, and verifies the portal on 52773 |
State after initialization (fresh environment): 4 containers up; http://localhost shows a blank demo state
(no data sources / targets / mappings / pipelines); the terminology server already holds concepts + mappings;
/api/pipelines/status reports running:false (no pipeline generated yet).
Options
| Command | Effect |
|---|---|
bash tools/setup.sh |
Full flow (skips embedding by default) |
bash tools/setup.sh --with-embedding |
Also build/start embedding (only needed for vectorization experiments) |
bash tools/setup.sh --check |
Read-only check: submodule / .env / JDBC / docker |
bash tools/setup.sh --help |
Usage |
β After a container rebuild you must reload: terminology concepts and mappings live in a DB inside
the iris-terminology container, so a rebuild loses them β re-run bash tools/term_data_load.sh (concepts) +
bash tools/term_map_import.sh (mappings), or simply re-run bash tools/setup.sh (idempotent; both steps included).
The CLINIC tables are re-checked automatically when the backend starts (create-only-if-missing).
iris-terminology) β what it does, how the platform uses itRole: an independent container β a trimmed fork of the open-source
intersystems-ib/iris-terminology-server (MIT,
by Luis Angel PΓ©rez Ramos; the fork used
here: zlnick/iris-terminology-server). All credit for the
server itself goes to him β multi-terminology ingestion and release management, persistent storage with SQL /
iFind-backed search, native and FHIR R4 terminology APIs, production-based processing. Host ports 52774β52773,
51774β1972; credentials superuser / SYS.
It does two things: β terminology storage + search/validation
(CodeSystem / ValueSet); β‘ source of truth for conversion mappings (/mapping/*). The actual mapping
decisions are made by the platform's AI skills (C3 for drugs, C3-Dx for diagnoses) β not here.
Web entry points: http://localhost:52774/terminology/ (catalog page: concepts / vectors / endpoints per code system)
and http://localhost:52774/csp/sys/UtilHome.csp (management portal).
Built-in code systems (GET /terminology/systems)
| id | Content | Used by the platform for |
|---|---|---|
chinese-icd10 |
GB/T 14396-2016 ICD-10 (Chinese) | Chinese diagnosis names in CLINIC seeding |
chinese-drugs |
Chinese drug catalogue (NRDL + CBIH) | Chinese drug names in CLINIC seeding |
rxnorm |
RxNorm (IN/SCD/BN) | target system for drug mappings |
snomed-uscore |
SNOMED CT (US Core Condition sample) | target system for diagnosis mappings |
REST capabilities (selection)
| Area | Endpoints |
|---|---|
| Search / validate | /terminology/icd10/{search,lookup,validate-code}, /terminology/icd/{β¦}, /terminology/drug/{search,lookup,validate-code,codesystems}, /terminology/rxnorm/{search,lookup,validate-code,chinese-map}, /terminology/snomed/*, /terminology/loinc/*, /terminology/uscore-condition/* |
| Mappings (used at runtime) | GET /terminology/mapping/lookup?sourceSystem=&targetSystem=&sourceCode=, GET /terminology/mapping/systems, POST /terminology/mapping/entries (idempotent bulk upsert) |
| Vectors | GET /terminology/vector/search?q=&systemUri=&limit=, /terminology/vector/crosswalk (next section) |
| FHIR terminology | /terminology/fhir/r4 (CodeSystem/$lookup, $validate-code, $subsumes, ValueSet/$expand) |
Where the platform uses it
demo.TerminologyOperation queries /mapping/lookup live βmeta.tag=β¦|unmapped): never silent, never blocking β back-fill below.TermLookupRequest/TermLookupResponse are part of iris/setup.sh's compile<CLASS DOES NOT EXIST> β¦ demo.TerminologyOperation.)Data (what is automatic vs. what you generate yourself)
| Table | Content | Loaded by |
|---|---|---|
Terminology_Icd10.Concept / Terminology_Drug.Code |
terminology concepts | setup.sh (automatic) via tools/term_data_load.sh |
Terminology_Mapping.CodeMap |
conversion mappings (source of truth) | setup.sh (automatic) via tools/term_map_import.sh (81-entry seed) |
Terminology_Vector.TermEmbedding |
term vectors | generate yourself (next section; not needed by the demo) |
Operations cheat-sheet
| Command | Effect |
|---|---|
bash tools/term_data_load.sh |
idempotent concept load (the material ships with the repo) |
bash tools/term_map_import.sh |
idempotent mapping-seed load (81 entries) |
bash tools/termsrv_vector_init.sh [--check] |
vector-capability status (auto-creates the table if missing) + steps |
bash tools/termsrv_apply_patches.sh |
overlay the platform extensions (/mapping/* + CodeMap) into the submodule (idempotent) |
bash tools/termsrv_load.sh |
hot-load the extension classes into the running container (no rebuild) |
python3 tools/term_map_build.py [--dry] [--limit N] |
AI back-fill of missing mappings (vector recall β LLM verdict β write back) |
What it is for: vectorization serves the production of terminology mappings, not the demo runtime.
Chinese diagnosis/drug names have no direct code mapping to English SNOMED/RxNorm, so the AI code-mapping
skills (C3 / C3-Dx) first need a semantic recall of Top-5~6 candidates, which the LLM then judges β written
back to the terminology server. This repo ships the prepared mappings (81 entries), so the demo works
out of the box without running any vectorization.
Prerequisites (all three are ready)
| Requirement | Note |
|---|---|
| Terminology server vector table | Terminology_Vector.TermEmbedding β exists out of the box (columns ID/Code/Embedding/Lang/Model/ReleaseId/SystemUri/Text), 0 rows by default; if it is missing, termsrv_vector_init.sh compiles the class to create it |
| Local embedding service | docker compose up -d embedding (Qwen3-Embedding-0.6B, ~1.1 GB model auto-downloaded on first start); the terminology server reaches it via ^Config("Vector","EmbeddingHost") (default embedding:8000) |
| Concept data (the input) | Chinese ICD-10 / drug concepts, loaded automatically by setup.sh β |
Standalone test procedure (verified in this project)
# 1) start the local embedding service (first run downloads the model; watch with
# docker logs -f dataflow-embedding)
docker compose up -d embedding
# 2) inspect the vector capability: table / row count / per-system counts
# (auto-creates the table if missing)
bash tools/termsrv_vector_init.sh
# 3) generate vectors (pick one)
python3 tools/dx_vectorize.py --zh # full Chinese ICD-10 (~20k rows; ~10-20 rows/s)
python3 tools/term_embed.py --system <uri> --tsv <file.tsv> # any code system from a TSV
bash run_rxnorm_vec.sh # full RxNorm (26k rows; needs YOUR OWN RxNorm data;
# built-in OOM/overheat self-healing + resume)
# 4) semantic search check (returns candidates with scores)
curl -u superuser:SYS 'http://localhost:52774/terminology/vector/search?q=ιΏεΈεΉζ&limit=3'
# 5) full chain: vector recall -> LLM verdict -> write back the mapping (needs LLM_* in .env)
python3 tools/term_map_build.py --dry # list missing mappings (no LLM calls, no writes)
python3 tools/term_map_build.py --limit 5 # back-fill 5 entries
Measured reference (2026-09-27): embedding dimension 1024; after inserting 2 rows,
vector/search?q=ιΏεΈεΉζ returned ιΏεΈεΉζ score 1.0024 and ε€ζΉη‘Όη score 0.5708 β
Notes
run_rxnorm_vec.sh already does resume + self-healing).docker compose stop embedding β the platform and the demo are unaffecteddata/seeds/term_map_seed.json (imported by setup.sh).| Service | URL | Notes |
|---|---|---|
| Frontend | http://localhost ο½ http://localhost/en | Vue3 + Element Plus, bilingual (Chinese at / by default, English at /en), switchable in the top bar |
| Backend API | http://localhost:5001 | REST (proxied at /api/*) |
| IRIS Portal | http://localhost:52773/csp/sys/UtilHome.csp | superuser / SYS |
| FHIR endpoint (built-in = default target) | http://localhost:52773/csp/healthshare/fhirserver/fhir/r4/ | /metadata anonymous; data needs Basic Auth; conversion results land here |
| FHIR endpoint (second repository = default source) | http://localhost:52773/csp/healthshare/demofhir/fhir/r4/ | Namespace DEMOFHIR; the data-source form / FHIRConfig.BASE_URL default to it; data invisible to the repository above; check via python3 tools/create_fhir_repo.py --check |
| Superserver | localhost:1972 | Native SDK / DB-API |
| Terminology server: terminology sets page (built-in web page) | http://localhost:52774/terminology/ | Title βζ―θ―ζε‘ε¨ Β· ζ―θ―ιβ; lists 4 terminology sets (Chinese drugs / RxNorm / national ICD-10 / SNOMED US Core sample) with their endpoints; JSON: http://localhost:52774/terminology/systems; superuser / SYS |
| Terminology server: native REST (browsable) | http://localhost:52774/terminology/β¦ | e.g. /terminology/icd10/search?q=η³ε°Ώη
, /terminology/drug/search?q=ιΏεΈεΉζ, /terminology/uscore-condition/zh-map?q=η³ε°Ώη
, /terminology/vector/search?q=diabetes; full route map in termsrv/iris/src/Terminology/Production/API.cls |
| Terminology server: portal / Production config | http://localhost:52774/csp/sys/UtilHome.csp ο½ http://localhost:52774/csp/user/EnsPortal.ProductionConfig.zen?$NAMESPACE=TERMINOLOGY | Production = Terminology.Production; β the Ensemble portal lives under /csp/user/ (the /csp/sys/ variant returns 404) |
| Terminology server: upstream React demo UI (Terminology Explorer) | http://localhost:5173 (not deployed here) | Requires Node on the host: cd termsrv/ui && npm install && VITE_API_BASE_URL=http://localhost:52774 npm run dev; upstream also ships a webgateway container (8080) |
The frontend is for business configuration & monitoring. Technical management of the generated
Ens.Production
(component config, start/stop, message details) happens in the IRIS Management Portal β Interoperability.
Unified response: {"code": 0, "data": ..., "message": "success"} (code != 0 = error).
| Method | Path | Description |
|---|---|---|
| GET | /api/health |
Health check (IRIS connectivity) |
| POST | /api/datasources |
Register data source |
| POST | /api/datasources/<id>/analyze |
Analyze endpoint β assets + AI semantics + runtime contract |
| GET | /api/datasources/<id>/assets |
Assets of a datasource |
| POST | /api/ai/recommend |
AI recommend (asset β target + field mappings) |
| POST | /api/mappings |
Save transformation relations |
| POST | /api/pipelines/generate |
Generate & start pipelines (single or multi) |
| GET | /api/pipelines/status Β· /logs |
Status / message log |
| GET | /api/pipelines/target-data |
Target table landing data |
| GET | /api/agents |
Wrapped AI capabilities (Skills / Agents) |
| GET | /api/agents/skills |
Skill catalog (pipeline-design + terminology Skills, with applies-to / status / usage count) |
| GET Β· POST | /api/pipelines/validation-issues |
Read / append validation experience |
| POST | /api/pipelines/validation-issues/polish |
LLM dedupe + restructure for knowledge export |
| GET | /api/agents |
AI capability catalog (Skills / Agents, kind field) |
| GET Β· POST | /api/targets |
List / add data targets (DB / SOAP) |
# .env
LLM_BASE_URL=https://api.deepseek.com/v1 # any OpenAI-compatible service
LLM_API_KEY=sk-xxxxxxxx # required for AI features
LLM_MODEL=deepseek-chat # a fast (-flash) model is recommended for demos
Without
LLM_API_KEYthe AI endpoints return explicit errors; the rest keeps working.
.
βββ docker-compose.yml # IRIS + backend + frontend
βββ .env.example # env template (IRIS/FHIR/LLM)
βββ init_data.py # init target tables (DROP+CREATE, idempotent)
βββ generate_mock_data.py # demo data (--fhir/--obs/--sql)
βββ export_validation_issues.py # validation experience β Obsidian knowledge
βββ README.en.md # this file
βββ LICENSE / NOTICE # license + trademark attribution (components not distributed with the repo, e.g. the termsrv submodule)
βββ backend/
β βββ services/ # repository / interface_analyzer / llm_client / connection_profiler /
β β # mock_soap / validate_agent / transformation_validator / pipeline_validator /
β β # type_registry / wsdl_importer / ...
β βββ routes/ # datasources / targets / ai / mappings / pipelines
βββ frontend/src/
β βββ api/ # axios + dataflow.js + constants.js
β βββ views/ # Home/Datasources/Assets/Recommend/Mappings/Pipelines/Targets/Agents
β βββ i18n/locales/ # zh.js + en.js
βββ iris/
β βββ setup.sh # container init
β βββ src/demo/ # Production components + PipelineGenerator + PipelineQuery
β βββ python/ # transform_handler.py (Embedded Python)
βββ tools/ # datakit toolbox (`run.sh list`) + check_component_fidelity.py
βββ data/ # IRIS persistence
CLINIC demo data (the UI "generate demo data" button /
tools/seed_clinic.py): you must register a SQL
data source first (the demo default is theCLINICnamespace β see Β§B step 1). Without one the script now
prints a clear hint instead of failing with anIndexError.
Want it in one command?
bash tools/setup.sh && python3 tools/e2e_ui_flow.py(does the steps below plus AI matching automatically β see "Quick Start Β· Reproduce the whole demo with one command")
Shortest end-to-end path (~10 minutes): β register the SQL source (demo default = the CLINIC namespace) β
β‘ click "generate demo data" β β’ register the DB target (jdbc:IRIS://iris:1972/USER, tables
Patient/Observation) β β£ confirm field mappings via "AI matching" β β€ generate the pipeline in
"Pipeline Monitor" β β₯ inspect landed rows / Completed messages in the target-data dropdown.
http://iris:52773/csp/healthshare/demofhir/fhir/r4/ (DemoFHIR = default FHIR source repository),superuser/SYS β Register β Analyze (produces runtime contract + AI asset semantics).jdbc:IRIS://iris:1972/CLINICPatient/Observation, which live in the USER namespace, so change the URL tojdbc:IRIS://iris:1972/USER, auth superuser/SYS β Add β Test β schema SQLUserPatient/Observation β Analyze columns β Save.Patient asset β AI recommends field mappings β Confirm & save.PatientPatientSource (Patient works too β the seeding script supports both source tables) β analyze columns (polling Query generated)./tmp/patient.wsdl (write-type AddPatient) β import (BO + entities);AddPatient β write.PatientSource β confirm (mapping carries target_type=SOAP).SQLService polls PatientSource β converts β SOAPOp_PatientService calls the mock βPatientEntity and replies.PatientSource β polling delivers β Completed + PatientEntity rows appear.
tools/datakit/README.md Β§6.1):# source table PatientSource (as described above)
bash tools/datakit/run.sh gen_test_patient.py --source user --table PatientSource --count 3
# source table Patient (same scenario, other source table; also the script default)
bash tools/datakit/run.sh gen_test_patient.py --source user --count 3
# independent check: SOAP landing (PatientEntity) + recent business messages
bash tools/datakit/run.sh check_pair_sink.py
diag_msgs.py (message headers / scan credentials) β diag_errors.py (Ens event log) βrescan_sql_source.py (force full rescan); or add --force to the seeding run.pipelines: [group1, group2]; TransformProcess routes eachPatientSource/api/pipelines/items) and each instance's generation count stay the samedup_merged.force=true) β that group'spython3 tools/check_component_fidelity.py auditsThe flow that best shows "AI decides + terminology converts": the HIS-style patient master table plus
encounter / diagnosis / medication are mapped by AI onto Patient / Encounter / Condition / MedicationRequest,
with Chinese diagnoses converted to SNOMED dual coding by the terminology server.
jdbc:IRIS://iris:1972/CLINIC β pickPatient/Encounter/Diagnosis/MedicationOrder (all four) β analyze columns (polling Query generated).bash tools/datakit/run.sh seed_clinic.py): seedsβ¦/csp/healthshare/fhirserver/fhir/r4/FHIRSERVER = default target repository) β register β refresh candidate resources.PatientβPatient, EncounterβEncounter,DiagnosisβCondition, MedicationOrderβMedicationRequest; the AI judges the diagnosis column asterm_map:cn2snomed) β save the mappings.FHIRQueue fills up and the FHIR repository counts Patient 3 / Encounter 4 /Condition.code carries dual codings (sourceurn:cn-nhsa:icd10-gbt2016 + SNOMED http://snomed.info/sct; if the terminology server has no mapping,Condition.subject / .encounter referenceurn:uuid:.
bash tools/datakit/run.sh gen_test_patient.py --count 2 --family θ΅΅ --given ζ --diagnosis η³ε°Ώη
--drug ιΏεΈεΉζbash tools/datakit/run.sh check_fhir.py (or the Pipelines target-data dropdown)This repository was hit by an incident where an AI assistant deleted containers belonging to other
projects (7 containers + their networks; one IRIS database was unrecoverable).
dataflow-* / iris-terminology) and the knowledge vault only; everything else is read-only andtools/guard/README.md (the session-level rule files themselves are local and are notsource tools/guard/docker_guard.sh before running docker β destructive operations onpython3 tools/guard/scope_guard.py check <path>...../tools/guard/ai-session.shsandbox-exec) may only write to the repo, the knowledge vault, /tmp and ~/Library/Caches.See tools/guard/README.md for the full design, verification runs and known pitfalls.
β 2026-09-14: an earlier "restricted Docker API proxy" layer (
DOCKER_HOSTpointing at an in-between
proxy that ruled on daemon facts) was rolled back: too complex, and it fail-closed ondocker cp
streaming bodies (ownership could not be resolved from the streamed tar), which broke everyday work.
There is no proxy layer now β docker talks to the real local socket.
| # | Symptom | Fix |
|---|---|---|
| 1 | Cannot pull the IRIS image / unauthenticated | The image lives in the InterSystems private registry: register (free) then docker login containers.intersystems.com (see prerequisite 0) |
| 2 | Inside containers sh: 1: /shared/setup.sh: not found; on host set: pipefail: invalid option name |
Windows checked the scripts out as CRLF: git config --global core.autocrlf false and re-clone (prerequisite 7) |
| 3 | First start looks slow / stuck at "wait for core services" | Normal: a fresh IRIS builds two FHIR repositories and the target tables (about 1β5 min); setup.sh waits with a bound before loading data |
| 4 | Terminology conversion stopped working after a rebuild, or CLINIC seeding finds no Chinese names | Concepts and mappings live in a DB inside the terminology container and are lost on rebuild β re-run bash tools/term_data_load.sh + bash tools/term_map_import.sh (or just bash tools/setup.sh, idempotent) |
| 5 | Pipeline generation 500: <CLASS DOES NOT EXIST> β¦ demo.TerminologyOperation |
iris/setup.sh must compile demo.TerminologyOperation/TermLookupRequest/TermLookupResponse (fixed) |
| 6 | Pipeline generation 500: ERROR #5007: Directory name '/dur/generated/' is invalid |
The generated-artifacts directory must be /dur/generated (not $ISC_DATA_DIRECTORY/generated); iris/setup.sh step 7b creates it and chowns it to irisowner (fixed) |
| 7 | Only 4 containers, no dataflow-embedding |
The demo needs no vectors, so setup.sh skips it by default; run docker compose up -d embedding for vectorization experiments (see "Terminology Vectorization") |
| 8 | Reset to a blank state / wipe data | Reset registrations: bash tools/datakit/run.sh reset_ui_env.py (26 self-checks; --check-only to only inspect). Wipe data: Windows named volume docker volume rm dataflow-iris-dur; macOS/Linux delete/rename data/iris |
| 9 | How many pipelines can run at once? | Community license = 8 business-host units (β€7 components resident + 1 backend connection); extra pipelines are still generated but start disabled β flip them on the "Data Pipelines" card (it evicts others automatically) |
| 10 | AI endpoints error out / no recommendations | .env's LLM_* is missing or unreachable β the platform fails explicitly (no silent fallback); self-check with docker exec dataflow-backend python /tmp/test_llm.py (see "LLM Configuration") |
superuser / SYS; FHIR reads/writes require Basic Auth.WebServiceURL overrides the WSDL address).init_data.py rebuilds the target tables on each backend start (DROP+CREATE; fine for demos, do not auto-clear intools/gen_test_patient.py, generate_mock_data.py --fhir N), or runbash tools/datakit/run.sh seed_fhir_demo.py when you explicitly need pre-existing history.runtime.note.fields.provenance and in the UI "Runtime Contract" column; once real data exists, the nextbash tools/datakit/run.sh seed_fhir_demo.py./en, or use the top-bar language switch β the language follows the route prefix) is anzh.js / en.js); dynamic content produced bydemo.TerminologyOperation): active β the target-system coding is appended (dual coding, e.g. E11.900 +44054006); negative (judged "no equivalent", e.g. a compound drug) β only the source coding is kept,unmapped tag; missing / call failure β source coding kept plusmeta.tag=urn:cn-nhsa:term-map|unmapped (non-blocking; after curation no regeneration is needed).suspended; use the enable / disabledisabled_others).irisowner and on first boot relocates the instance data into$ISC_DATA_DIRECTORY; on a Windows bind mount the file-sharing layer makes chown/rename unreliableroot / ubuntu(1000) / the right owner depending on how the mount waschown always ended in EPERM) βError while moving data directories ERROR #5001: Error executing chown irisowner:irisowner /dur/irissys/: Error:1:Exited (1);open(tmp) β write β rename, the sameEPERM (httpd/logs/error.log: AH10231 Failed creating pid file) β httpd exits silently andcurl empty reply / exit 52) while the container health checkdocker-compose.windows.yml: /dur switches from the ./data/iris bind mount to the named volumedataflow-iris-dur (real ext4 inside the Docker VM β chown/rename work) β removes β and β‘ at once;tools/setup.sh enables it automatically on Windows/WSL (sets COMPOSE_FILE), creates the volume and chownsirisowner;iris/setup.sh step 7: points the private web server's PidFile at /tmp/httpd.pid inside the container anddocker compose setups that still use adocker compose -f docker-compose.yml -f docker-compose.windows.yml ps;docker volume rm dataflow-iris-dur.curl can briefly return 000, just retry (inside the container it is ready immediately).python3 tools/check_component_fidelity.py; toolbox index:bash tools/datakit/run.sh list (includes test_incremental_pipeline.py and data-generation scripts).Apache-2.0 - see LICENSE; NOTICE lists trademarks and the components that are not distributed (JDBC driver, raw terminology data, termsrv submodule).