02-data-and-fhirwave: W6filled4 citations

HMS Seeder — Idempotent Data Seeding

Audiences: developer, internal

HMS Seeder — Idempotent Data Seeding

ddx-seeder-ts is a TypeScript CLI tool that populates a Dudoxx HMS environment through REST API calls alone — no direct database, FHIR, or MinIO access — using the SMART idempotency pattern so every phase can be re-run safely without creating duplicates.

Business Purpose

Every new environment (dev, staging, demo, CI) needs a consistent, realistic dataset before clinical workflows can be demonstrated or tested. Manual bootstrapping is error-prone and slow. The seeder provides: (1) a 19-phase pipeline that bootstraps a complete platform from zero to a fully-populated clinic with patients, practitioners, appointments, lab results, and AI templates in under 15 minutes; (2) an idempotency guarantee — re-running any phase is always a no-op for unchanged data and an update for changed data; (3) a state file on disk so a failed seed mid-phase resumes from where it stopped, not from the beginning. This capability is critical for demo environments (Mesalvo, partner POCs) that need to be torn down and rebuilt quickly.

Audiences

  • Investor: Demo environments can be provisioned end-to-end in ~15 minutes for sales engagements. Clinical narrative data (ACME Hamburg) is rich enough to demonstrate TUCAN AI, document generation, and FHIR integration in live demos.
  • Clinical buyer (doctor/nurse/receptionist): Invisible at runtime — the seeder is a dev/ops tool. Its output is the realistic test data clinical staff see in demo environments.
  • Developer/partner: All seeding goes through REST API calls to ddx-api at http://localhost:6100/api/v1. Authentication uses X-API-Key (GATEWAY_API_KEY_SEEDER) + X-Clinic-ID (clinic slug). No direct DB/FHIR/MinIO access. All entities use the SMART pattern.
  • Internal (ops/support): pnpm seed:platform bootstraps a fresh environment end-to-end. pnpm seed:org:acme-hamburg populates the ACME Hamburg demo clinic with full clinical data. State file at .ddx-seeder-state.json tracks what has been seeded.

Architecture

The seeder is a Commander.js CLI (ddx-seeder-ts/src/index.ts) with 24 seeder classes, each covering one phase. All seeders extend BaseSeeder which provides the SMART upsert pattern, state management, spinner UI, and dry-run support.

SMART Pattern (mandatory on every seeder):

1. CHECK  → Does entity exist? (GET by email / slug / name via API)
2. FETCH  → If yes, get existing entity data
3. READ   → Compare existing vs. desired state
4. UPDATE → PATCH if different
5. LINK   → Record IDs in .ddx-seeder-state.json for downstream phases

Result actions: created | updated | unchanged | failed

Phase pipeline (recommended order):

#PhaseSeeder classWhat it creates
0initInitSeederSystem roles + RBAC permission catalog + role-permission mappings + platform settings + feature flags
1essentialsEssentialsSeederOrganizations (tenants via POST /bootstrap/tenants) + super admins + clinic admins
2vaccine-schedulesVaccineSchedulesSeederVaccine schedule definitions (platform-scoped, no clinic required)
3medication-catalogMedicationCatalogSeeder~1000+ medication catalog entries
4assessmentAssessmentSeederAssessment form definitions (Zod-validated against form schema)
5document-templatesDocumentTemplatesSeederDocument generation templates + section definitions
6document-summarization-templatesDocumentSummarizationTemplatesSeederAI summarization prompt templates
7treatment-plan-templatesTreatmentPlanTemplatesSeederTreatment plan template library
8help-cmsHelpCmsSeederIn-app help content (~5 seconds)
9flowagentFlowAgentSeederFlowAgent scenario definitions
10diagnosis-engineDiagnosisEngineSeederDiagnosis engine rules (1032 rows, ~8 minutes — the longest phase)
11staffStaffSeederDoctors, nurses, receptionists (FHIR Practitioner created on role assignment)
12demoDemoSeederDemo patients + login helper accounts
13clinicalClinicalSeederMedications, prescriptions (Faker-generated)
14uploadsUploadsSeederPatient attachments → MinIO via POST /patients/:id/attachments
15clinical-historyClinicalHistorySeederCurated clinical history from data/08-clinical-history/*.json
16visit-transcriptionsVisitTranscriptionsSeederVisit audio transcription data
17lab-dataLabDataSeederLab result data
(org-level)acme-clinical-fullAcmeClinicalFullSeederFull ACME Hamburg clinical dataset (13 patients + rich history)
(org-level)acme-enrichAcmeEnrichSeederACME data enrichment pass
(org-level)acme-labsAcmeLabsSeederACME lab result set
(org-level)acme-org-settingsAcmeOrgSettingsSeederACME org-specific settings

State management (ddx-seeder-ts/src/utils/state.ts):

  • State is serialized to .ddx-seeder-state.json (path from STATE_FILE env var).
  • On load, the file is parsed and validated through SeederStateSchema (Zod). Invalid or missing files start fresh.
  • Each phase records completedPhases[] and entities[] with their API-returned IDs.
  • Downstream phases look up IDs from state (e.g. StaffSeeder looks up organizationId written by EssentialsSeeder).

Tech Stack & Choices

ConcernChoiceRationale
CLI frameworkCommander.jsStandard Node.js CLI library; supports sub-commands per phase
HTTP clientAxiosConsistent with ddx-seeder-ts (not NestJS HttpService — seeder runs standalone)
Data validationZodSeederStateSchema, RolesFileSchema, OrganizationsFileSchema validate JSON data files before seeding
Data generation@faker-js/fakerFaker-generated clinical data (prescriptions, vitals) for non-ACME demo patients
Terminal outputchalk + ora + cli-table3Rich spinner + table output for phase progress
State persistenceJSON file (.ddx-seeder-state.json)Crash-safe; survives partial runs; Zod-validated on load
Module formatES Modules ("type": "module")Modern Node.js; imports use .js extension per ESM rules
Runtimetsx for dev, compiled JS for prodTypeScript 5.x with strict mode

Data Flow

Full platform bootstrap:

bash
cd ddx-seeder-ts
pnpm seed:platform
# Runs phases: init → vaccine-schedules → essentials → medication-catalog
# → assessment → document-templates → document-summarization-templates
# → treatment-plan-templates → help-cms → flowagent → diagnosis-engine

pnpm seed:org:acme-hamburg
# Runs: staff → demo → uploads → clinical-history → acme-clinical-full
# → acme-enrich → acme-labs → acme-org-settings

SMART upsert detail (roles, from InitSeeder):

  1. InitSeeder.seed() reads data/PLATFORM-DATA/roles.json.
  2. RolesFileSchema.parse() validates the JSON (Zod).
  3. For each role: api.smartUpsertRole(role)GET /roles?name={role.name} (CHECK).
  4. If exists: compare fields → PATCH /roles/:id if different (UPDATE).
  5. If missing: POST /roles (CREATE).
  6. processSmartResult() increments created/updated/unchanged/failed counters.
  7. After all roles: api.seedRbacPermissions()POST /super-admin/permissions/seed (idempotent bulk endpoint).

Organization creation (EssentialsSeeder):

  • Uses POST /bootstrap/tenants (NOT POST /organizations) — this triggers the full tenant provisioner pipeline (FHIR partition + MinIO buckets). See Tenant Provisioner.

Implicated Code

  • ddx-seeder-ts/src/seeders/init.ts:14InitSeeder; SMART role upsert loop at :43; RBAC permissions seed at :60–80; PHASE_ORDER=0
  • ddx-seeder-ts/src/seeders/essentials.ts:18EssentialsSeeder; organization creation at :44 (reads data/00-init/organizations.json); super-admin seeding; clinic-admin seeding; PHASE_ORDER=1
  • ddx-seeder-ts/src/utils/state.ts:18loadState(); SeederStateSchema Zod validation at :32; crash-safe state persistence; completedPhases[] + entities[] tracking
  • ddx-seeder-ts/src/seeders/base.tsBaseSeeder; SMART pattern methods (processSmartResult()), spinner helpers, dry-run support; extended by all 24 seeder classes
  • ddx-seeder-ts/src/seeders/index.ts:1 — seeder registry; exports all 24 seeder classes
  • ddx-seeder-ts/src/seeders/diagnosis-engine.tsDiagnosisEngineSeeder; longest phase (~8 min, 1032 rows across 9 substeps)
  • ddx-seeder-ts/src/types/index.tsSeederStateSchema, RolesFileSchema, OrganizationsFileSchema, SeederResult type definitions
  • ddx-seeder-ts/src/utils/config.ts — env config (API_BASE_URL, API_KEY, STATE_FILE, DRY_RUN, CONTINUE_ON_ERROR)

Operational Notes

  • API must be running: The seeder calls GET /health first. If ddx-api is not reachable at API_BASE_URL (default http://localhost:6100/api/v1), all phases abort.
  • Phase ordering matters: init must complete before essentials (roles must exist before admins), and essentials before staff (org IDs must be in state). Running phases out of order causes LINK step failures.
  • CONTINUE_ON_ERROR=true (default): A failed entity does not abort the phase — it increments result.failed and continues. Set to false in CI to make failures explicit.
  • Dry-run support: DRY_RUN=true (or --dry-run flag) logs what would be created/updated without making any API calls. Useful for pre-flight checks.
  • Diagnosis engine timing: The diagnosis-engine phase takes ~8 minutes due to 1032 row creates across 9 substeps. Do not interrupt mid-phase — it is resumable because of the SMART pattern, but re-running from scratch re-sends all 1032 rows (as unchanged).
  • ACME data: data/ACME-ORG-DATA/ contains 13 patient profiles, multilingual transcripts (de/en/fr), DICOM imaging samples, and full lab results for the ACME Hamburg demo clinic. This dataset is the primary sales demo asset.
  • Forbidden patterns (from ddx-seeder-ts/CLAUDE.md): Direct Prisma/FHIR/MinIO calls are forbidden — all data flows through the REST API. Never use POST /organizations — always POST /bootstrap/tenants.
  • Prisma 7 Schemas — the seeder populates data that lands in all 5 Prisma schemas; prisma-main is the primary target
  • FHIR Partitions & Tenancyessentials phase provisions FHIR partitions via the tenant bootstrap endpoint
  • Tenant ProvisionerPOST /bootstrap/tenants called by EssentialsSeeder triggers the full provisioning pipeline
  • User ManagementStaffSeeder creates practitioners + triggers FHIR Practitioner resource creation