12-operations-runbookswave: W6filled7 citations

Tomedo Integration — PMS Data Bridge

Audiences: clinical-buyer, developer, partner

Tomedo Integration — PMS Data Bridge

Dudoxx HMS bridges bidirectionally with Tomedo — the dominant German-market practice management system (PMS) — allowing clinics to read patient records, KarteiEinträge (chart entries), and drug catalogues from their existing Tomedo installation while progressively moving workflows into Dudoxx.

Business Purpose

German clinics that already run Tomedo cannot migrate overnight. Dudoxx HMS must co-exist with Tomedo during the transition: read patient demographics, medical record entries (KarteiEinträge), and the drug catalogue from Tomedo, and optionally sync patients into the Dudoxx FHIR repository. This bridge eliminates duplicate data entry during the transition period and allows clinical staff to work in Dudoxx's AI-augmented interface while their legacy Tomedo data remains the source of truth for billing and statutory reporting.

The integration is feature-flagged (ENABLE_TOMEDO_INTEGRATION=true) and is deployed only for clinics that have a Tomedo server accessible over the network (typically via an SSH/ngrok tunnel to the on-premise server). It is an operational opt-in, not a default feature.

Audiences

  • Investor: Tomedo holds 40%+ of the German outpatient clinic market. A certified bridge makes Dudoxx HMS the only AI-first platform that DACH clinics can adopt without abandoning Tomedo. This is a major adoption accelerator.
  • Clinical buyer (doctor/nurse/receptionist): Doctors see Tomedo patient records in the Dudoxx interface. Drug lookup queries the Tomedo drug catalogue. Once a patient is synced, appointments and clinical notes created in Dudoxx can reference the canonical Tomedo patient identity.
  • Developer/partner: TomedoModule wraps a TypeScript SDK (TomedoSDK, imported from ./sdk) originally ported from dudoxx-saas. All Tomedo API calls go through the SDK which handles retry (5 attempts, 1.5s delay), authentication, and facility filtering. TomedoService exposes methods for patients, KarteiEinträge, and drugs. FHIR sync (FhirClientService) is used for optional patient import.
  • Internal (ops/support): The integration is disabled by default (ENABLE_TOMEDO_INTEGRATION unset / 'false'). When enabled, onModuleInit() initializes the SDK. A drug cache (24-hour TTL, in-memory) reduces Tomedo API load. Facility filtering (TOMEDO_ENABLE_FACILITY_FILTERING) restricts data to a single facility.

Architecture

TomedoModule owns:

MemberResponsibility
TomedoControllerREST endpoints (patients, medical records, drugs, health)
TomedoServiceNestJS wrapper around TomedoSDK

TomedoService dependencies:

DependencyRole
TomedoSDK (./sdk)HTTP client; retry logic; KarteiEintrag manager; drug manager
IFhirClientInjected via FHIR_CLIENT DI token for patient FHIR sync
PrismaServiceInjected for Dudoxx patient lookup by Tomedo ID

Feature-flag flow:

  1. ENABLE_TOMEDO_INTEGRATION=trueonModuleInit() calls initializeSDK().
  2. initializeSDK() validates TOMEDO_API_BASE_URL is non-empty, builds TomedoSDKConfig from env vars, calls createTomedoSDK(config).
  3. If TOMEDO_API_BASE_URL is empty, sdk = null — all service methods check sdk !== null and throw HttpException(503) when the SDK is not initialized.

Drug caching:

  • drugCache: { data: unknown[]; fetchedAt: number } | null — in-memory cache, 24-hour TTL.
  • Drug catalogue is large and rarely changes; caching avoids repeated expensive Tomedo API calls during medication lookup workflows.

Patient sync (optional):

  • ENABLE_TOMEDO_PATIENT_SYNC=true enables copying Tomedo patient records into HAPI FHIR as Patient resources.
  • Sync preserves the Tomedo patient ID as a FHIR identifier for cross-reference.

Facility filtering:

  • TOMEDO_ENABLE_FACILITY_FILTERING=true + TOMEDO_MEDICAL_FACILITY_ID restrict all queries to a single facility partition within the Tomedo server, preventing multi-facility data leakage.

Tech Stack & Choices

ConcernChoiceRationale
API clientTomedoSDK (custom TypeScript, ./sdk/)Ported from dudoxx-saas; battle-tested against Tomedo's REST API; SDK encapsulates auth header, retry, BigInt ID handling
Retry5 attempts, 1.5s delayTomedo servers close sockets on large payloads (tunnel instability); aggressive retry essential in clinic network conditions
Drug cacheIn-memory, 24-hour TTLDrug catalogue is static per release; caching avoids Tomedo server rate limits
Patient syncIFhirClient.executeBatch(bundle) with ifNoneExist conditional-createIdempotent re-import: a FHIR transaction Bundle keyed on the Tomedo identifier avoids duplicating already-synced patients (not a bare create())
AuthTOMEDO_API_KEY + TOMEDO_CLIENT_IDStandard Tomedo REST API authentication
ID handlingAll Tomedo IDs as string (int64 BigInt)Tomedo uses int64 patient IDs that overflow JavaScript number; always serialized as strings
Feature flagENABLE_TOMEDO_INTEGRATION env varPer-clinic opt-in; disabled by default so non-Tomedo deployments are unaffected

Data Flow

Patient list read (Tomedo → Dudoxx UI)

  1. Doctor opens patient search in Dudoxx → frontend calls GET /api/v1/tomedo/patients?search=....
  2. TomedoController.listPatients()TomedoService.listPatients(queryDto) (tomedo.service.ts:232).
  3. TomedoService checks sdk !== null; calls the SDK patient manager with { search, facilityId, page, pageSize }.
  4. SDK sends authenticated HTTP request to TOMEDO_API_BASE_URL/patients; handles retry on socket close.
  5. Response mapped to TomedoPatientListResponse; returned to frontend.

Patient sync (Tomedo → FHIR)

  1. Admin triggers POST /api/v1/tomedo/sync/patients (batch/body-driven sync; a single patient is passed in the request body, not the path).
  2. TomedoService sync handler:
    • Fetches patient(s) from the Tomedo SDK.
    • Maps each to a FHIR Patient resource carrying a Tomedo identifier for cross-reference.
    • Writes via fhirClient.executeBatch(...) (tomedo.service.ts:682) — a FHIR transaction Bundle with ifNoneExist conditional-create so re-imports are idempotent (an existing patient with the same Tomedo identifier is not duplicated). Not a single create('Patient', ...) call.
    • HAPI FHIR stores the resource in the clinic's partition; the Tomedo→FHIR link is the identifier.
  3. Returns the batch outcome (created/matched FHIR ids keyed to Tomedo ids).

Drug lookup (Tomedo → Dudoxx prescription flow)

  1. Doctor types drug name in prescription module → GET /api/v1/tomedo/drugs?search=....
  2. TomedoService.getDrugs(query):
    • Checks in-memory drugCache (24h TTL); if valid, filters locally.
    • On cache miss: fetches full catalogue from Tomedo SDK, stores in drugCache.
  3. Returns TomedoDrugListResponse to prescription UI.

Health check

  • GET /api/v1/tomedo/healthTomedoService.checkHealth() (tomedo.service.ts:183) pings the Tomedo API and returns a TomedoHealthResponse (connection + facility info).

Implicated Code

  • ddx-api/src/integrations/tomedo/tomedo.module.ts:1TomedoModule; imports ConfigModule, AuthModule, PatientsModule, PrismaModule; injects global FHIR_CLIENT (no local FHIR module import needed)
  • ddx-api/src/integrations/tomedo/tomedo.service.ts:56TomedoService; feature flag check at :100; onModuleInit() at :118; drug cache TTL config at :68
  • ddx-api/src/integrations/tomedo/tomedo.service.ts:132initializeSDK(); SDK config builder; retry params delayMs: 1500 at :152 (5 attempts, 1.5s)
  • ddx-api/src/integrations/tomedo/tomedo.controller.ts:64@Controller('tomedo') (all routes under the global api/v1 prefix, i.e. /api/v1/tomedo/*); routes: GET health (:76), GET config (:116), GET patients (:153), GET patients/stream (:203), GET patients/:id (:245), POST sync/patients (:296), POST sync/drugs (:437), GET patients/:id/medical-records (:500), GET medical-records (:603), GET drugs (:717)
  • ddx-api/src/integrations/tomedo/sdk/TomedoSDK; createTomedoSDK(config); patient manager, KarteiEintrag manager, drug manager; HTTP auth headers; BigInt ID serialization
  • ddx-api/src/integrations/tomedo/types/TomedoPatient, TomedoKarteiEintrag, TomedoDrug, TomedoConfig, TomedoRecordType — typed response shapes
  • ddx-api/src/integrations/tomedo/interceptors/ — request interceptors (auth header injection, request logging)
  • ddx-api/src/integrations/tomedo/trace/ — trace/debugging utilities for Tomedo API calls

Operational Notes

  • Feature flag required: ENABLE_TOMEDO_INTEGRATION=true must be set; without it the SDK never initializes and all endpoints return HTTP 503.
  • Network access requirement: Tomedo servers are typically on-premise. A reverse proxy or SSH tunnel (e.g. ngrok, FRP) is required to expose TOMEDO_API_BASE_URL to the Dudoxx API server. Socket instability is the primary failure mode — the 5-retry config handles most transient errors.
  • PHI logging: TOMEDO_LOG_PHI=false by default — never enable in production. TOMEDO_DEBUG_LOG_RAW=true logs raw Tomedo API responses (also PHI-sensitive; dev only).
  • BigInt IDs: All Tomedo patient IDs are string throughout the codebase. Never cast to number — JavaScript Number cannot represent int64 values above 2^53.
  • Drug cache invalidation: The 24-hour in-memory cache is per-process. In a multi-instance deployment, each instance builds its own cache. To force refresh, restart the API process. A future improvement would move this to Redis.
  • Env vars summary: TOMEDO_API_BASE_URL, TOMEDO_API_KEY, TOMEDO_CLIENT_ID, TOMEDO_CHANGING_USER (default ddx-api), TOMEDO_API_TIMEOUT (default 30000ms), TOMEDO_MEDICAL_FACILITY_ID, TOMEDO_MEDICAL_FACILITY_NAME, TOMEDO_MEDICAL_FACILITY_CODE, ENABLE_TOMEDO_INTEGRATION, ENABLE_TOMEDO_PATIENT_SYNC, TOMEDO_ENABLE_FACILITY_FILTERING, TOMEDO_ENABLE_LOGGING, TOMEDO_DEBUG_LOG_RAW, TOMEDO_LOG_PHI.