
Branch: main
This release resolves all issues identified during Open Exchange review and subsequent clean-environment validation. It includes fixes for the FHIR installation race condition, restoration of the runtime installation approach, and ObjectScript syntax compatibility improvements for non-interactive execution.
The original installation process executed:
HS.Util.Installer.Foundation.Install("FHIRSERVER")
followed immediately by:
HS.FHIRServer.Installer.InstallInstance(...)
Although Foundation.Install() starts the namespace/database creation process, it completes asynchronously through background jobs. On clean environments, the FHIR installer could execute before the FHIRSERVER namespace became available.
Fix:
Added an explicit polling loop to wait until the namespace exists before proceeding:
for {
quit:##class(%SYS.Namespace).Exists("FHIRSERVER")
hang 2
}
This guarantees that InstallInstance() only runs after the FHIR infrastructure is fully initialized.
An initial attempt to solve the race condition moved FHIR installation to Docker image build time.
During validation it was discovered that IRIS registers parts of the FHIR web application using the hostname present during image creation (buildkitsandbox). When containers later started with a different hostname, the /fhir/r4 endpoint became inaccessible and returned HTTP 404 responses.
Resolution:
The project returned to the runtime installation model while retaining the namespace readiness polling logic.
Changes:
docker-compose.yml
start.sh
README.md
config/iris/Dockerfile
Validation uncovered <SYNTAX> errors in:
config/iris/setup_fhir_post_install.sh
when ObjectScript was executed through:
iris session IRIS << 'EOF'
The original implementation used multi-line block syntax:
if condition {
...
} else {
...
}
This syntax is not reliably supported when commands are executed through stdin piping.
Fix:
Replaced block-style conditionals with equivalent single-line statements:
set fhirAppExists = ##class(Security.Applications).Exists("/fhir")
if 'fhirAppExists do ##class(Security.Applications).Create("/fhir", .props)
if 'fhirAppExists write "CSP application /fhir created",!
if fhirAppExists do ##class(Security.Applications).Modify("/fhir", .props)
if fhirAppExists write "CSP application /fhir already exists — updated.",!
This ensures compatibility across automated installation environments.
config/iris/install_fhir.txtconfig/iris/setup_fhir_post_install.shdocker-compose.ymlstart.shREADME.mdconfig/iris/DockerfileValidated from a completely clean environment:
docker-compose down -v
./start.sh
Results:
<SYNTAX> errors during installationGET /fhir/r4/metadata returns HTTP 200http://localhost:3000No manual migration steps are required.
For existing installations:
docker-compose down
docker-compose up -d
For a completely clean installation:
docker-compose down -v
./start.sh
AI-Powered Clinical Reasoning Agent for Hospital Readmission Risk Assessment
Author: Carlos Eduardo Dias Duarte | Email: kcedd34@gmail.com | GitHub: @kcedd34
Running on a public server — no installation required.
| Service | URL |
|---|---|
| Frontend | http://109.123.244.170:3000 |
| Swagger / API Docs | http://109.123.244.170:8000/docs |
| IRIS Management Portal | http://109.123.244.170:52773/csp/sys/UtilHome.csp (SuperUser / SYS) |
| FHIR R4 Endpoint | http://109.123.244.170:52773/fhir/r4/metadata |
# 1. Clone repository git clone https://github.com/kcedd34/smart-discharge-navigator.git cd smart-discharge-navigator2. (Optional) Enable AI features
echo "OPENAI_API_KEY=sk-your-key-here" > .env echo "AI_MODEL=gpt-4o" >> .env
3. Start application
chmod +x start.sh ./start.sh
⚠️ First run installs FHIR (~3–8 min):
start.shautomatically installs
the FHIR R4 endpoint into the running IRIS container on first run. The script
waits for the FHIRSERVER namespace to be fully ready before proceeding, which
eliminates the race condition from the original release. Subsequent runs skip
the installation entirely (takes seconds).
Windows Users (or if start.sh fails): see Manual Installation below.
Note: Without OPENAI_API_KEY, the system runs in rule-based mode — fully functional, no AI features.
Smart Discharge Navigator tackles hospital readmissions ($17B annual cost in US) through a hybrid AI Agent + rule-based system that:
| Layer | Purpose | When Used |
|---|---|---|
| AI Agent (GPT-4o) | Deep clinical reasoning, identifies non-obvious risks, conversational interface | When OPENAI_API_KEY is configured |
| Rule-Based Engine | Evidence-based deterministic scoring (6 weighted factors) | Always (baseline + fallback) |
Key Differentiator: The AI Agent identifies risks missed by rules (medication interactions, comorbidity patterns, social determinants) while maintaining explainable step-by-step reasoning.
| Feature | Description |
|---|---|
| AI Clinical Chat | Conversational interface for patient queries with FHIR data context |
| AI Risk Analysis | LLM-powered assessment with step-by-step clinical reasoning |
| AI Discharge Plans | Personalized, AI-generated discharge narratives |
| AI Patient Comparison | Multi-patient triage with AI prioritization rationale |
| Graceful Degradation | Auto-fallback to rule-based mode when AI is unavailable |
| Requirement | Version | Notes |
|---|---|---|
| Docker | 20.10+ | Allocate minimum 6 GB RAM to Docker |
| Docker Compose | 2.0+ | |
| Python | 3.8+ | For sample data loading script |
| Git | Latest | |
| OpenAI API Key | — | Optional — enables AI features |
Critical: InterSystems IRIS requires significant memory. Ensure Docker has at least 6 GB RAM allocated (Docker Desktop → Settings → Resources). The
config/iris/merge.cpffile setsglobals=256to allocate 256 MB for IRIS globals buffer.
⚠️
docker-compose up -dalone is NOT sufficient. It only starts the
containers — the FHIR endpoint and sample data are not pre-installed in the
image. Use./start.shfor a fully automatic setup, or follow Steps 5–7
manually (Windows users or ifstart.shfails).
git clone https://github.com/kcedd34/smart-discharge-navigator.git cd smart-discharge-navigatorOptional: enable AI features
echo "OPENAI_API_KEY=sk-your-key-here" > .env echo "AI_MODEL=gpt-4o" >> .env
chmod +x start.sh ./start.sh
What the script does automatically:
docker-compose up -d)Access Points:
| Service | URL | Credentials |
|---|---|---|
| Frontend | http://localhost:3000 | — |
| Backend API | http://localhost:8000 | — |
| Swagger Docs | http://localhost:8000/docs | — |
| IRIS Portal | http://localhost:52773/csp/sys/UtilHome.csp | SuperUser / SYS |
| FHIR Endpoint | http://localhost:52773/fhir/r4/metadata | — (no auth for GET) |
⚠️
docker-compose up -d(Step 3) only starts the containers. You must
also complete Steps 5, 6, and 7 to install the FHIR endpoint and load sample
data — without them the application will show no patients.
git clone https://github.com/kcedd34/smart-discharge-navigator.git
cd smart-discharge-navigator
Create .env in the project root:
OPENAI_API_KEY=sk-your-key-here
AI_MODEL=gpt-4o
AI_ENABLED=true
Important: Use
gpt-4oorgpt-4-turbo. Basegpt-4lacks JSON mode support and will fall back to rule-based mode.
docker-compose up -d
Starts 3 services: IRIS for Health (port 52773), Backend API (port 8000), Frontend (port 3000).
# Wait until the web server responds (2–5 minutes)
curl -s -o /dev/null -w "%{http_code}" http://localhost:52773/csp/sys/UtilHome.csp
# Expected: 200, 302, or 401
Mandatory on first run and after
docker-compose down. The FHIR endpoint
is not pre-configured in the community image.The script below includes a poll loop that waits for the FHIRSERVER
namespace background jobs to complete before callingInstallInstance— this
is the fix for the race condition from the original release.
docker cp config/iris/install_fhir.txt smart-discharge-iris:/tmp/install_fhir.txt
docker exec -i smart-discharge-iris bash -c 'iris session IRIS -U %SYS < /tmp/install_fhir.txt'
Wait 3–8 minutes for package download and class compilation. Success indicator: FHIR Server Installation Complete!
Verify:
curl http://localhost:52773/fhir/r4/metadata
# Expected: 200 with CapabilityStatement JSON
docker cp config/iris/setup_fhir_post_install.sh smart-discharge-iris:/tmp/setup_fhir_post_install.sh
docker exec smart-discharge-iris bash /tmp/setup_fhir_post_install.sh
Idempotent — safe to run multiple times. Registers the /fhir CSP application entry and enables Password auth.
# Direct FHIR resource access (Basic Auth — handled automatically by the backend):
curl -u SuperUser:SYS http://localhost:52773/fhir/r4/Patient?_count=5
docker cp data/load_sample_data.py smart-discharge-backend:/tmp/load_sample_data.py
docker exec -e FHIR_BASE_URL=http://iris:52773/fhir/r4 smart-discharge-backend python /tmp/load_sample_data.py
Creates 8 synthetic patients:
Containers stopped (not destroyed — FHIR data persists in volume):
docker-compose start
Containers destroyed (docker-compose down): repeat Steps 3–7 (or use ./start.sh — it handles Steps 5–7 automatically).
Full reset (remove volumes too):
docker-compose down -v
./start.sh # fresh volume + full reinstall
Access http://localhost:3000 to view all patients sorted by risk score.
Example questions:
Note: General queries (without a patient selected) automatically load full clinical FHIR data — Encounter, Condition, MedicationRequest, Observation, AllergyIntolerance — plus pre-calculated risk scores for every patient, giving the AI Agent complete context to rank and compare patients.
From the Dashboard, click “🤖 AI Analysis” on any patient to view:
Click “🤖 AI Discharge Plan” to generate a personalized plan including:
Base URL: http://localhost:8000/api/v1 | Interactive Docs: http://localhost:8000/docs
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Health check |
GET |
/patients |
List all patients with risk scores |
GET |
/patients/{id} |
Get patient details |
GET |
/patients/{id}/risk-assessment |
Rule-based risk assessment |
POST |
/patients/{id}/discharge-plan |
Generate discharge plan (creates FHIR CarePlan + Tasks) |
GET |
/statistics |
Population statistics |
| Method | Endpoint | Description |
|---|---|---|
GET |
/ai/status |
Check AI availability |
POST |
/ai/chat |
Clinical chat with patient context |
GET |
/patients/{id}/ai-analysis |
AI-powered risk analysis |
POST |
/patients/{id}/ai-discharge-plan |
AI-generated discharge plan |
POST |
/ai/compare-patients |
Multi-patient AI triage |
| Method | Endpoint | Description |
|---|---|---|
GET |
/analytics/sql-stats |
Population analytics via SQL |
GET |
/analytics/readmission-sql |
Readmission candidates (SQL-based) |
GET |
/analytics/sql-builder-info |
FHIR SQL Builder feature info |
InterSystems Programming Contest: AI Agents for FHIR 2026 — Task #9: Hospital Readmission Risk Workbench
| Requirement | Implementation |
|---|---|
| AI Agent for FHIR | ✅ GPT-4o LLM with clinical reasoning over FHIR R4 data |
| FHIR Resources | ✅ Patient, Encounter, Condition, Observation, MedicationRequest, AllergyIntolerance, CarePlan, Task |
| Platform Features | ✅ FHIR API, FHIR SQL Builder, AI Hub pattern (OpenAI-compatible) |
| MVP Criteria | ✅ Rule-based + AI scoring, FHIR Task generation, discharge plans |
| Conversational AI | ✅ Chat interface with patient context switching |
| Explainable AI | ✅ Step-by-step reasoning, confidence scores, risk attribution |
| Resource | Purpose | AI Agent Usage |
|---|---|---|
| Patient | Demographics, age | Context for AI reasoning |
| Encounter | Admission history | Pattern analysis |
| Condition | Active diagnoses | Comorbidity reasoning |
| Observation | Vital signs, labs | Trend detection |
| MedicationRequest | Active medications | Interaction analysis |
| AllergyIntolerance | Drug/food allergies | Allergy-aware planning |
| Resource | Purpose |
|---|---|
| CarePlan | Structured discharge plan (rule-based + AI-enhanced) |
| Task | Discharge checklist items |
The backend uses the FHIR SQL Builder built into IRIS for Health to run standard SQL directly against FHIR resource projections, avoiding JSON bundle parsing in application code.
How it works:
InstallInstance automatically creates SQL projections for every stored FHIR resource. The schema is named HSFHIR_X000x_S (e.g., HSFHIR_X0001_S on a clean first install).fhir_sql_analytics.py) sends queries via POST /api/atelier/v1/{namespace}/_query/sql.Example queries:
-- Readmission candidates SELECT subject AS patient_id, COUNT(*) AS admissions FROM HSFHIR_X0001_S.Encounter WHERE status = 'finished' GROUP BY subject HAVING COUNT(*) > 1-- Polypharmacy analysis (5+ active medications) SELECT subject AS patient_id, COUNT() AS med_count FROM HSFHIR_X0001_S.MedicationRequest WHERE status = 'active' GROUP BY subject HAVING COUNT() >= 5
-- High-risk population (3-table JOIN) SELECT p.Key, c.code, e.status FROM HSFHIR_X0001_S.Patient p JOIN HSFHIR_X0001_S.Condition c ON c.subject = p.Key JOIN HSFHIR_X0001_S.Encounter e ON e.subject = p.Key WHERE c.code IN ('44054006','73211009','38341003')
The AI layer follows the InterSystems AI Hub integration pattern using an OpenAI-compatible API interface:
┌─────────────────────────────────┐
│ Smart Discharge Navigator │
├─────────────────────────────────┤
│ AIAgentService │
│ ├─ OpenAI-compatible API ←─── AI Hub pattern (model gateway)
│ ├─ Structured clinical prompts│
│ ├─ FHIR context injection │
│ └─ Graceful degradation │
├─────────────────────────────────┤
│ InterSystems IRIS for Health │
│ ├─ FHIR Repository (R4) │
│ ├─ FHIR SQL Builder │
│ └─ IntegratedML (production) │
└─────────────────────────────────┘
intersystemsdc/irishealth-community:latestsmart-discharge-navigator/
├── backend/
│ ├── app/
│ │ ├── core/
│ │ │ ├── config.py # Config (incl. AI settings)
│ │ │ └── fhir_client.py # FHIR API wrapper
│ │ ├── models/
│ │ │ └── patient.py # Models (incl. AI models)
│ │ ├── services/
│ │ │ ├── ai_agent_service.py # AI Agent
│ │ │ ├── risk_calculator.py # Rule-based scoring
│ │ │ ├── care_plan_generator.py # Discharge planning
│ │ │ └── fhir_service.py # FHIR orchestration
│ │ └── api/
│ │ └── routes.py # API endpoints
│ ├── main.py
│ ├── requirements.txt
│ └── Dockerfile
├── frontend/
│ ├── src/
│ │ ├── App.js # React app (incl. AI Chat)
│ │ ├── index.js
│ │ └── index.css
│ ├── public/
│ ├── package.json
│ └── Dockerfile
├── data/
│ └── load_sample_data.py
├── config/
│ └── iris/
│ ├── merge.cpf
│ └── setup_fhir_post_install.sh
├── .env.example
├── docker-compose.yml
├── start.sh
├── AI_AGENT_GUIDE.md
├── ARCHITECTURE.md
└── README.md
Backend:
cd backend
pip install -r requirements.txt
export OPENAI_API_KEY=sk-your-key-here
uvicorn main:app --reload --port 8000
Frontend:
cd frontend
npm install
npm start
▶️ Smart Discharge Navigator Demo (YouTube)
Coverage (5–7 minutes):
Key demo points:
AI_ENABLED=falseVariables are set in docker-compose.yml. The only value you need to supply is OPENAI_API_KEY for AI features (via .env file in the project root).
environment:
- FHIR_BASE_URL=http://iris:52773/fhir/r4
- IRIS_USERNAME=SuperUser
- IRIS_PASSWORD=SYS
- CORS_ORIGINS=["http://localhost:3000","http://localhost:3001"]
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- AI_MODEL=${AI_MODEL:-gpt-4o}
- AI_ENABLED=${AI_ENABLED:-true}
CORS format:
CORS_ORIGINSmust be a valid JSON array string. A plain URL string will cause a pydantic-settings v2 parse error and prevent the backend from starting.
| Variable | Required | Default | Description |
|---|---|---|---|
FHIR_BASE_URL |
Yes | http://iris:52773/fhir/r4 |
Internal FHIR endpoint (container-to-container) |
IRIS_USERNAME |
Yes | SuperUser |
IRIS user for authenticated FHIR writes |
IRIS_PASSWORD |
Yes | SYS |
IRIS password |
CORS_ORIGINS |
Yes | ["http://localhost:3000","http://localhost:3001"] |
Allowed CORS origins (JSON array) |
OPENAI_API_KEY |
No | (empty) | OpenAI key — without it, system runs in rule-based fallback mode |
AI_MODEL |
No | gpt-4o |
Use gpt-4o or gpt-4-turbo; base gpt-4 lacks JSON mode |
AI_TEMPERATURE |
No | 0.3 |
Sampling temperature |
AI_MAX_TOKENS |
No | 2000 |
Maximum tokens in AI response |
AI_ENABLED |
No | true |
Master toggle for AI features |
HIGH_RISK_THRESHOLD |
No | 0.7 |
Score threshold for HIGH risk classification |
MODERATE_RISK_THRESHOLD |
No | 0.4 |
Score threshold for MODERATE risk classification |
Most common 404 on a fresh install. The embedded httpd doesn’t route /fhir requests to IRIS unless configured.
docker cp config/iris/setup_fhir_post_install.sh smart-discharge-iris:/tmp/setup_fhir_post_install.sh
docker exec smart-discharge-iris bash /tmp/setup_fhir_post_install.sh
Verify: curl http://localhost:52773/fhir/r4/metadata — expected 200 with CapabilityStatement.
docker exec -it smart-discharge-iris iris session IRIS -U %SYS
# Paste all installation commands from Step 5 above
# Wait 3–8 minutes, then configure CSP gateway:
docker cp config/iris/setup_fhir_post_install.sh smart-discharge-iris:/tmp/setup_fhir_post_install.sh
docker exec smart-discharge-iris bash /tmp/setup_fhir_post_install.sh
FHIR search index classes were not fully generated during installation. Uninstall and reinstall:
// In an IRIS terminal session
set sc = ##class(HS.FHIRServer.Installer).UninstallInstance("/fhir/r4")
write "Uninstall sc=",sc,!
halt
Then re-run Steps 5–6 from the Installation section.
IRIS needs at least 4 GB RAM. Confirm config/iris/merge.cpf contains globals=256,0,0,0,0,0. Check status:
# Check healthcheck status (use conditional template — plain .State.Health.Status fails if healthcheck not yet initialized) docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}' smart-discharge-irisOr probe HTTP directly (more reliable):
curl -s -o /dev/null -w "%{http_code}" http://localhost:52773/csp/sys/UtilHome.csp
Expected: 200, 302, or 401
docker logs smart-discharge-iris --tail=50
Write operations require HTTP Basic Auth (handled automatically by the backend). For manual testing:
curl -u SuperUser:SYS -X POST http://localhost:52773/fhir/r4/Patient \
-H "Content-Type: application/fhir+json" \
-d '{"resourceType":"Patient","name":[{"family":"Test"}]}'
echo 'set props("ChangePassword")=0
do ##class(Security.Users).Modify("SuperUser",.props)
halt' | docker exec -i smart-discharge-iris bash -c 'iris session IRIS -U %SYS'
Ensure CORS_ORIGINS is a JSON array in docker-compose.yml:
- CORS_ORIGINS=["http://localhost:3000","http://localhost:3001"] # Correct
- CORS_ORIGINS=http://localhost:3000 # Wrong
docker-compose ps
docker-compose logs iris
docker-compose logs backend --tail=30
The backend connects using the internal Docker hostname iris (not localhost): FHIR_BASE_URL=http://iris:52773/fhir/r4.
curl http://localhost:8000/api/v1/ai/status
docker-compose logs backend --tail=50
Common causes:
OPENAI_API_KEY not set — create .env file in the project rootAI_MODEL=gpt-4 (base) — change to gpt-4o or gpt-4-turboAI_ENABLED=false — set to true in docker-compose.ymlIRIS returns HTTP 201 with an empty body on resource creation. The create_resource() method must extract the ID from the Location response header instead of calling .json(). Ensure you’re on the latest code:
docker-compose up -d --no-deps --force-recreate backend
CORS errors almost always indicate an underlying backend 500 — the backend does not emit CORS headers on error responses. Fix the backend error first (docker-compose logs backend), and the CORS error will resolve automatically.
IRIS returns an empty body on 201 Created. Ensure you’re using the latest data/load_sample_data.py (extracts resource ID from the Location header).
Verify that the FHIR endpoint accepts writes:
curl -s -o /dev/null -w "%{http_code}" -u SuperUser:SYS \
-X POST http://localhost:52773/fhir/r4/Patient \
-H "Content-Type: application/fhir+json" \
-d '{"resourceType":"Patient","name":[{"family":"Test"}]}'
# Expected: 201
This application was developed with irishealth-community:2026.1.0.235.2. If you encounter issues with a newer latest image, pin the version in docker-compose.yml:
image: intersystemsdc/irishealth-community:2026.1.0.235.2
MIT License — see https://github.com/kcedd34/smart-discharge-navigator/blob/main/LICENSE
Carlos Eduardo Dias Duarte
Built with ❤️ and 🤖 by Carlos Eduardo Dias Duarte for InterSystems Programming Contest: AI Agents for FHIR 2026