Skip to content

House of Wellness – Randox ↔ OptimalDX ↔ CRM Orchestration

Goal

Build a private client-only web portal to orchestrate blood test workflows between: - CRM (GoHighLevel / LeadConnector) as the canonical system of record for patient identity - Randox (upstream lab) for orders/results - OptimalDX (ODX) for interpretation + report/PDF generation

Production URL: https://results.houseofwellnessuk.com

Key constraints & decisions

CRM-first identity

  • CRM is the single source of truth for patient identity.
  • Every "case" is linked to a CRM contact/patient ID (externalPatientId).
  • Our system stores mappings:
  • CRM Contact ID
  • Randox Order Reference (e.g., FML order number)
  • OptimalDX Patient/Record IDs (as needed)

Randox limitation

  • Randox API does NOT provide a way to list/query in-flight orders.
  • You can only retrieve an order/results if you already know the Randox order reference.
  • Therefore our system must track case/order refs in our own datastore.

Delivery milestones (completed)

  • Milestone 1 (Randox-first): Order creation, status polling, results pull — complete and automated.
  • Milestone 2 (ODX integration): HL7 submission, report generation, PDF email — complete and automated.
  • Milestone 3 (CRM integration): Contact management, email/SMS, PDF attachments — complete.
  • Milestone 4 (API optimization): IMemoryCache caching across all external API clients (Randox, ODX, CRM) to reduce call volume. OdxUnitType persisted to eliminate redundant API calls on completed cases.

HbA1c handling (important)

  • OptimalDX has two separate elements for HbA1c:
  • Element ID 781 = mmol/mol (IFCC)
  • Element ID 540 = %
  • Standard: use mmol/mol (Element ID 781).
  • No HbA1c conversion is performed; we standardize on mmol/mol.

Architecture (locked)

  • Frontend: Azure Static Web Apps (SWA) – React portal
  • Backend: Azure Functions (.NET 10 isolated)
  • Resilience: Microsoft.Extensions.Http.Resilience on all HttpClients (retry, circuit breaker, timeout)
  • Caching: IMemoryCache (singleton) for API call reduction across all external clients
  • Data: Azure Storage
  • Table Storage: Cases, ContactNotes, Tags, SandboxClinicalBookings
  • Blob Storage: artifacts (raw JSON payloads, HL7, PDFs)
  • Secrets: Azure Key Vault
  • Observability: Application Insights (enabled)
  • Auth: Entra ID via SWA built-in auth + server-side role enforcement (How_Admin, How_Staff, How_ReadOnly)

Azure environment notes

  • Production deployed under prod-spoke-uks subscription (6143e663-2543-4fea-b807-11cf16f15831)
  • Resource Group: how-prod-integration-rg
  • Custom domain: results.houseofwellnessuk.com
  • Dev deployed under dev-spoke-uks subscription (8475131a-9a01-485c-8126-163d6709856a)
  • Resource Group: how-poc-integration-rg
  • Dev func apps are stopped; SWA backend is unlinked
  • UK South for Function Apps; West Europe for Static Web Apps (UK South SWA quota limitation)

Naming

Production

  • Resource Group: how-prod-integration-rg
  • Function App: how-prod-api-func
  • Static Web App: how-prod-portal
  • Docs Site: how-prod-docs
  • Storage: howprodintegration
  • Key Vault: how-prod-kv
  • Custom Domain: results.houseofwellnessuk.com
  • Docs Domain: docs.houseofwellnessuk.com

Dev (stopped)

  • Resource Group: how-poc-integration-rg
  • Function App: how-poc-api-func (stopped)
  • Static Web App: how-poc-portal (backend unlinked)
  • Storage: howpocintegrationrg9f7a
  • Key Vault: how-poc-kv

Key Vault secret names (do NOT store secret values in repo)

  • Randox-Username
  • Randox-Password
  • Randox-SubscriptionKey
  • OptimalDX-Prod-ApiKey
  • OptimalDX-Prod-PracticeID
  • OptimalDX-Prod-baseURL (optional)
  • CRM (GoHighLevel / LeadConnector)
  • GHL-Location (optional, reference only)
  • GHL-PIT (PIT mode)
  • GHL-OAuth-ClientId (OAuth mode)
  • GHL-OAuth-ClientSecret (OAuth mode)
  • GHL-OAuth-RefreshToken (OAuth mode)

Notes: - Supports both OAuth and PIT for CRM. Use OAuth for production. - Location ID is configured via app setting CrmLocationId (and can be stored in Key Vault as GHL-Location for convenience, but the app does not read it automatically).

Non-goals

  • No multi-tenant billing/tenant separation
  • No public-facing patient portal (staff-only)
  • No Durable Functions orchestration (timer + direct calls are sufficient)