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-ukssubscription (6143e663-2543-4fea-b807-11cf16f15831) - Resource Group:
how-prod-integration-rg - Custom domain:
results.houseofwellnessuk.com - Dev deployed under
dev-spoke-ukssubscription (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)