AdvancedRx Project Plan · Updated 2026-05-09

Azure structured intake writes

One Apex service shared by Azure and the data-entry screen flow, replacing today's 7,904-line transcription monolith. Phase 3 of the Azure roadmap — the project that lets Azure write the full intake graph (patient + order + prescriptions + notes), instead of staging an image for a human to transcribe.

Active · Phase A authoring next Owner: Kyle Last verified 2026-05-06
4
phases of automation
P0 live · P1 in progress · P2 next · P3 = this plan
3 → 11
SF objects Azure writes
today: ContentVersion + Rx_Image + Case · target: full intake graph
~150 → ~30
screen-flow nodes after refactor
load-bearing transcription flow → calls Apex once
5 / A→E
phases of build
A0 discovery complete · A authoring next

§1The big picture

Same pipeline, four states. Toggle the phase to watch what Azure writes today (Phase 0), what's about to flip on (Phases 1 + 2), and what this plan adds (Phase 3). Every other phase-aware section on the page reacts to the same toggle.

Show pipeline at:
Upstream Azure Salesforce Human LifeFile (web UI) no API — passive UI PAD bot 8 AM daily · UI scrape → JSON Logic Apps · wf-sf-writer 5 managed API connections AI Search · matching patient (P1) · prescriber (P2) Data Factory · ADF Contact roster sync Azure SQL PrescriptionDocuments + roster ContentVersion PDF / PNG bytes Rx_Image__c External_Id_c__c · Image__c Staging Case RT=Data_Input ContactId blank · transcribed by hand + Contact pre-linked when match high + Contact + Prescriber pre-linked closed · ParentId → new-order Case PatientOrderIntakeRest @RestResource · /intake/v1/escript Account → Contact → EhrPatient + ContactPointAddress HC auto-creates Contact Order + Rx records + Notes + Fill + OrderItem (downstream flow) + new-order Case Data-entry transcription ~all e-scripts · screen flow most e-scripts · pre-link reduces lookup most e-scripts · less lookup · less linking fallback path only · low-confidence + parser-failed Pharmacist · PV1 checkpoint regardless of source parsed JSON match scores sync structured POST transcribes low-confidence only verifies
External system Azure Salesforce SF record / object Human action Dashed + dimmed = not active in this phase
Phase 0 · live today
What's lit up: LifeFile → PAD → Logic Apps writes ContentVersion + Rx_Image__c + Case (RT=Data_Input). Staging Case lands with ContactId=null. Everything past the staging Case is human transcription. The DE team carries every e-script.
Phase 1 · in progress
What's new: ADF syncs Contact roster into Azure SQL; AI Search scores incoming patients; high-confidence matches arrive with Case.ContactId pre-populated. DE workload shrinks for matched patients (review, don't lookup). Flag: Escript.Evaluator.RequirePatientMatching false → true. SF-side perm-set expansion lands with the Azure flag flip — neither alone is functional.
Phase 2 · next
What's new: same AI-match approach, applied to prescribers. Case.EHR_Practitioner__c already exists and wf-sf-writer already writes to it — currently resolves to null because the flag is off. Phase 2 is a feature-flag flip on the Azure side, not a new SF field addition.
Phase 3 · this plan
What's new: Azure stops posting an image and starts posting a domain JSON to a new Apex @RestResource (PatientOrderIntakeRest). The endpoint owns the full intake graph creation: Account → HC-auto-Contact → EhrPatient → ContactPointAddress → Order → Rx[] → Pharmacy_Notes → new-order Case. The data-entry screen flow gets refactored to call the same service via an @InvocableMethod. One Apex layer, two surfaces. DE team becomes the fallback path for low-confidence + parser-failed payloads.

§2Phases in depth

Each phase is additive — earlier phases stay live as later phases land. The panel matching your current phase toggle gets the strong border + shadow.

Phase 0

Live today

Active in production

Today's state. Azure scrapes LifeFile and creates the 3-record staging set per e-script. The data-entry team transcribes everything else by hand.

What's running

  • Daily 8 AM wf-start-lifefile-automation kicks the PAD bot
  • PAD bot scrapes LifeFile UI and parses to JSON
  • wf-sf-writer creates ContentVersion + Rx_Image__c + Case (RT=Data_Input) + 2 ContentDocumentLinks
  • Idempotent on External_Id_c__c (Rx_Image) and External_ID__c (Case)
  • DE team transcribes via the 7,904-line screen flow

Feature flags

  • RequirePatientMatching = false
  • RequirePrescriberMatching = false
  • Every Case lands with ContactId=null and EHR_Practitioner__c=null

What's de-risked

  • Image-based ingestion has been running stable; idempotency proven
  • Staging schema is mature
  • Auth chain (Connected App + Run-As user + perm set) operational

Living-with-it hazards

  • Every e-script becomes a DE work item — no automation past staging
  • 12-flow Case collision fires on every Azure-created Case
  • DE workload scales linearly with intake volume
Phase 1

Patient matching

In progress

ADF syncs the SF Contact roster to Azure SQL; AI Search scores incoming patients against the roster; high-confidence matches arrive with Case.ContactId pre-populated. Pure flag flip on the Azure side — paired with a SF perm-set expansion.

What's new

  • ADF outbound Contact sync to Azure SQL
  • AI Search scoring (phone, DOB, name + 1,100+ nicknames)
  • wf-sf-writer calls AI Search per e-script
  • High-confidence matches → Case.ContactId pre-populated on insert
  • Low-confidence → unchanged (Case lands unlinked)

Feature flag flip

  • RequirePatientMatching false → true
  • Confidence threshold tunable Azure-side without an SF deploy

What's de-risked

  • DE keeps human review (advisory match, not authoritative)
  • Threshold safety net via Azure-side tuning
  • Backward compatible: low-confidence stays on the existing path

Cutover gates & hazards

  • Interlock: SF perm-set expansion (Contact Read + View All + FLS) MUST land WITH the flag flip
  • DE workflow change: some Cases pre-linked, some not
  • No SF-side audit trail today — confidence stays in Azure SQL (recommend adding Match_Confidence_Score__c)
Phase 2

Prescriber matching

Next · pure flag flip

Same AI-match approach applied to prescribers. The Case.EHR_Practitioner__c field already exists, wf-sf-writer already writes to it, and it currently resolves to null because the flag is off. Phase 2 is purely a flag flip — not a SF schema change.

What's new

  • AI Search scoring extended to prescriber identifiers
  • High-confidence prescriber matches → Case.EHR_Practitioner__c pre-populated

Feature flag flip

  • RequirePrescriberMatching false → true

What's de-risked

  • SF write surface already in place (verified 2026-04-21 via FieldDefinition)
  • No new SF field additions
  • No new perm-set expansion required for this phase

Cutover gates & hazards

  • Same DE-workflow-change concern as Phase 1
  • Same audit-trail gap (confidence stays in Azure SQL)
Phase 3

Structured intake writes — this plan

Authoring next (Phase A)

Azure stops POSTing an image and starts POSTing structured JSON to a new Apex @RestResource. The endpoint creates the entire intake graph in one transaction. The data-entry screen flow gets refactored to call the same Apex service via an @InvocableMethod wrapper — one source of truth for the create-graph, two surfaces.

What ships

  • PatientOrderIntakeRest at /services/apexrest/intake/v1/escript
  • PatientOrderIntakeService.intake(IntakeRequest) — savepoint-bounded transaction
  • PatientOrderIntakeInvocable wrapper for the refactored screen flow
  • Order.Intake_Idempotency_Key__c (new field, External ID, Unique)
  • HTTP status codes: 200 / 400 / 409 / 500 with structured errors[] + warnings[]

Phase decomposition

  • A0 · Discovery — ✓ complete (8+ contract corrections, 38 VR audit, OrderItem + Fill contracts)
  • A · Author the service — next; tests only, no callers wired
  • B · Refactor screen flow — ~150 nodes → ~30–40, calls invocable
  • C · Build REST endpoint — independent of B, can run concurrent
  • D · Per-channel cutover — CMDT-gated, e-scripts first, faxes later
  • E · Cleanup — only after D stable for one release cycle

What's de-risked

  • A0 validated 8+ contract corrections against live schema and screen flow
  • OrderItem VR-bypass requirement, auto-quarantine override, 3-FK integrity all documented
  • Idempotency design + structured error vocabulary defined
  • Sandbox test gate before A ships (HC auto-Contact-create under with sharing)
  • Per-channel CMDT routing means rollback is a row flip — no deploy

Risk register highlights

  • Sharing keyword decision — HC auto-Contact-create may rely on system mode (Phase A test gate)
  • OrderItem Prevent_Manual_Edits VR — every update needs Validation_Bypass_Date_Time__c
  • Auto-quarantine override on new-order Case insert — silent RT/Owner rewrite
  • 3 patient FKs on Rx must agree — no managed integrity
  • Phase 3 perm-set blast radius — open decision before Phase D
  • HC upgrade fragility — pin version in test plan; rerun service tests after each upgrade
  • Logic Apps Scope_Catch_Failure only triggers on HTTP error codes — 200-with-errors body silently lost

§3Pipeline anatomy

Every moving part in the pipeline, in roughly upstream-to-downstream order. Each card names what the component does, where its credentials live, and how the integration breaks when the component does. Color stripe matches the §1 diagram (slate=external · blue=Azure · indigo=Salesforce · amber=auth surface · dashed blue=Phase 3 future).

LifeFile

Upstream · external

The web app where prescribers' e-scripts arrive. No API, no webhooks, no push. AdvancedRx's only integration with LifeFile is browser automation against the UI as a logged-in human user.

Auth
lifefile-username + lifefile-password, loaded into the PAD bot from Azure Key Vault
Fails when
UI selectors change upstream → PAD scrape breaks (no API contract to lean on)

Reference: azure-integration.md "Upstream source"

PAD bot

Power Automate Desktop

Daily 8 AM browser-automation bot. Logs into LifeFile, captures each new e-script's image as PNG, and parses UI text into structured JSON for downstream Logic Apps to consume.

Auth
Azure Key Vault → LifeFile creds at run time
Fails when
LifeFile UI changes; bot host machine offline; cred expiry not refreshed

Reference: azure-integration.md "Upstream source"

Azure Logic Apps

Orchestration · 5 SF connectors

Three workflows compose the pipeline: wf-start-lifefile-automation (8 AM cron → triggers PAD) → wf-sf-dispatch (per-document fan-out via Service Bus) → wf-sf-writer (the Salesforce writer with Scope_Catch_Failure). API version pinned in the SF connection config to insulate against vendor drift.

Auth
Managed Identity (Azure side) → Connected App OAuth (SF side)
Fails when
SF auth expires / scope reverted; SF returns 4xx → Scope_Catch_Failure → DLQ + SQL SalesforceStatus='Failed'

Reference: azure-integration.md "What Azure creates" + "Failure handling"

Azure SQL · PrescriptionDocuments

State + audit

One row per inbound e-script. Tracks DispatchStatus, SalesforceStatus, AI confidence (PatientMatchScore + PatientMatchReasons + MatchedPatientId), and any partially-written SF record IDs. Stored proc usp_UpdateSalesforceStatus uses COALESCE so partial writes never overwrite real IDs on retry.

Auth
Logic Apps managed connection
Fails when
Row stuck at Failed + Publishedwf-sf-dispatch won't auto-retry; manual replay only

Reference: azure-integration.md "Failure handling"

Azure AI Search

Phase 1 + 2 matching engine

Scores incoming patient identifiers (Phase 1) and prescriber identifiers (Phase 2) against the Contact roster ADF synced into Azure SQL. Signals: phone, DOB, name similarity (phonetic + 1,100+ nickname pairs). Threshold tunable Azure-side without an SF deploy.

Auth
Internal Azure managed connection
Fails when
Roster stale (ADF lag); threshold tuned permissive → false-positive matches reach PV1; threshold tuned strict → DE workload doesn't drop

Reference: azure-integration.md "Outbound · Data Factory Contact sync"

Azure Data Factory · ADF

Outbound Contact sync

Pulls SF Contact roster → Azure SQL on a scheduled cadence. Feeds AI Search. Authenticates through the same Azure Logic Apps Connected App as inbound — the legacy Data Factory Connected App (0H44W000000M5hgSAC) is a relic; SF no longer allows creating that type, so both Azure workloads consolidate on one.

Auth
Linked service LS_Salesforce_ProductionAzure Logic Apps Connected App, OAuth Client Credentials, scope api
Fails when
Connected App secret rotated without ADF refresh; perm set narrowed below ADF's SELECT field list

Reference: azure-integration.md "Outbound" + "Why one Connected App"

Connected App · Azure Logic Apps

SF auth surface

The SF-side OAuth surface for both Azure workloads. Client Credentials flow. Scope = api ("Manage user data via APIs"). Do not revert to full — Salesforce deprecated full for Client Credentials grants in Winter '26; reverting breaks both pipelines silently.

Creds
Consumer key + secret stored Azure-side; Key Vault wiring + expiry alerts need verification (open question)
Blast radius
One secret rotation refreshes BOTH inbound writes AND outbound ADF reads — coordinate during a maintenance window

Reference: azure-integration.md "Auth chain" + "Secret rotation"

User · azure-integration@advancedrx.net

Run-As identity

Single Run-As user covers both inbound + outbound. Salesforce Integration User License (no UI login). The CreatedById on every Azure-written record is this user — the strongest writer-origin signal when debugging "where did this record come from?"

Auth
Passwordless · OAuth-token-bound only
Fails when
Deactivation; license drop; profile reassignment; user-Id-based VR bypasses authored against this Id

Reference: azure-integration.md "Auth chain"

Profile + Perm Set · least-privilege scope

Capability layer

Profile Minimum Access - Azure Integration is the least-privilege base (CLAUDE.md §3.14 exemplar — the one AdvancedRx-controlled integration that follows the convention cleanly). Perm set Azure_Integration layers capabilities. Each phase expands what a compromised credential can reach — Phase 1 added Contact Read; Phase 3 will add full intake-graph CRUD.

Maintenance
Layer new caps via the perm set; do not bulk-edit the profile
Open question
Phase 3 perm-set blast radius: keep one perm set, or split a Phase-3-only one? (Decide before Phase D.)

Reference: plan.md §6 risk #6 + azure-integration.md "Why one user"

PatientOrderIntakeRest

Phase 3 · planned

The new Apex @RestResource Azure will POST to in Phase 3. URL: /services/apexrest/intake/v1/escript. Maps Azure JSON → IntakeRequest → calls PatientOrderIntakeService.intake() → returns IntakeResponse as HTTP body. Status codes drive Logic Apps' Scope_Catch_Failure retry / DLQ logic (200-with-errors won't work — see §9 hazards).

Status
200 success · 200 idempotency replay (warnings allowed) · 400 validation reject · 409 idempotency conflict · 500 internal
Auth
Same Connected App + Run-As user; perm set extended for full intake-graph CRUD

Reference: plan.md §3 (target architecture) + §3 vocabulary

§4What gets written where

Twelve cards. Each names an SObject (or group) the pipeline touches, who creates it, what fields are populated, and the per-record hazard worth remembering. Phase 3 cards dim until you flip the toggle to P3. Hover a dimmed card to read it without flipping.

Created by Azure today P0+ · every phase

ContentVersion + ContentDocumentLink ×2

standard SF

Created by: wf-sf-writer · Trigger: every inbound e-script

  • ContentVersionVersionData (the PDF / PNG bytes), PathOnClient, Title
  • ContentDocumentLink #1 — links the file to Rx_Image__c
  • ContentDocumentLink #2 — links the same file to Case
Note: both links point at the same ContentVersion — that's how SOQL "Cases linked to a specific image" triangulates through ContentDocumentLink.

Rx_Image__c

staging record

Created by: wf-sf-writer · Idempotent on: External ID (upsert)

  • External_Id_c__c — Rx External ID; API name is typo'd (_c__c suffix). Idempotent upsert key.
  • Image__c — HTML rich-text (32k limit) with embedded <img> reference
  • Phase 3 finalize: service writes Contact__c + EHR_Practitioner__c after the intake graph lands
Don't autocorrect the External_Id_c__c API name — the typo is preserved deliberately. Renaming breaks every reference.

Staging Case

RT=Data_Input

Created by: wf-sf-writer · Idempotent on: External_ID__c (clean API name)

  • Subject = "LastName, FirstName (DOB: MM/DD/YY)" · same-day repeats prefix "Multiple RX"
  • External_ID__c — Rx External ID, case-insensitive unique
  • RecordType.DeveloperName = Data_Input
  • P0: ContactId = null · EHR_Practitioner__c = null
  • P1: + ContactId pre-populated when patient match high
  • P2: + ContactId + EHR_Practitioner__c both pre-populated when match high
  • P3 finalize: ParentId → new-order Case · Status='Closed' · Order__c set · OwnerId untouched
12-flow Case collision fires on every insert. Worst object-trigger collision in the org. Real per-update tax is 2 flows (the canonical Same/Related pair); the other 10 are gated.

Phase 3 — created by the Apex service P3 · PatientOrderIntakeService

Account

RT=Individual

Created by: resolvePatient() · Skipped: existing-patient reuse path

  • RecordType.DeveloperName = Individual (DeveloperName lookup, never hardcoded ID)
  • Name = "<lastName>, <firstName>"
  • HealthCloudGA__IndividualType__c = 'Group'
  • HealthCloudGA__EnrollmentType__c = 'NonDual'
  • SF_Manually_Entered__c = true
Inserting this triggers HC managed-package code that creates a paired Contact automatically. The HC picklist values are load-bearing for that auto-create — don't drift them.

Contact

RT=Patient · auto-created

Created by: Health Cloud package (server-side, on Account insert) · Updated by: service post-create

  • Service writes after the auto-create:
  • FirstName, MiddleName, LastName, Patient_Memo__c
  • Phone fields (Phone, MobilePhone, HomePhone, OtherPhone, SecondaryPhone__c) — formatted to exactly 10 digits or rejected
5 active phone-format VRs require exactly 10 digits. Service uses a PhoneFormatter helper. Don't insert Contact yourself — Health Cloud does it.

HealthCloudGA__EhrPatient__c

third leg of patient triangle

Created by: service · Anchors: Account + Contact + EhrPatient

  • HealthCloudGA__Account__c = Account.Id
  • Contact__c = the auto-created Contact.Id (back-link)
  • HealthCloudGA__BirthDate__c, HealthCloudGA__GenderLabel__c
  • HealthCloudGA__GivenName1__c = First name
  • HealthCloudGA__GivenName2__c = Middle name
  • HealthCloudGA__GivenName3__c = Last name
Non-obvious mapping: GivenName1 = First, 2 = Middle, 3 = Last. Not sequential by position — by role.

ContactPointAddress

standard SF

Created by: service · Verified by: ProvenWorks (post-insert)

  • Name = 'Default' · Primary__c = true · Status__c = 'Active'
  • Street, City, State, Country, PostalCode — from payload
  • ParentId = Account.Id (Master-Detail to Account, NOT Contact)
  • Contact__c = Contact.Id (custom Lookup)
  • Verification_Status__c / Unverified_Reason__c — when address-validation gating produced a result
Master-Detail is to Account, not Contact — common assumption error. The Contact__c custom Lookup tracks which Contact the address belongs to, but the parent record is the Account.

Order

Status=Draft

Created by: service · Reused: existing patient Draft Order if any

  • AccountId, BillToContactId, ShipToContactId, Customer_Contact__c — all → resolved Contact
  • EffectiveDate, Transaction_Date__c = today
  • Rx_Image__c — lookup to staging image
  • SF_Direct_Rx__c = true · Skip_Quarantine_Check__c = true
  • SalesChannelId = lookup WHERE SalesChannelName='AdvancedRx'
  • Intake_Idempotency_Key__cNEW field added in Phase A (Text(64), External ID, Unique, Case-Insensitive)
Multi-Rx-same-day: if a same-day New-Order Case already exists for the patient, attach to it instead of branching a new Order. The service handles this lookup.

HealthCloudGA__EhrMedicationPrescription__c · Master Rx

RT=Prescription · up to 5

Created by: service · 17 fields per Rx · SF_Manually_Entered__c required

  • Three patient FKs (must match): HealthCloudGA__Patient__c · HealthCloudGA__Account__c · Contact__c
  • Prescriber: HealthCloudGA__Prescriber__c + denormalized Prescriber_ID__c (Text 255)
  • Order: Order__c
  • Image: Rx_Image__c
  • Drug data: Product__c, Sig__c, Quantity__c, RefillsAuthorized__c, HealthCloudGA__DateWritten__c
  • Date_filled__cnote typo'd lowercase 'd'
  • Price__c = 0.0 (initialized; updated by applyPricing after Fills + OrderItems exist)
  • SF_Manually_Entered__c = true (required) — triggers downstream New_Prescriptions_Manually_Entered flow that creates Fill + OrderItem
3-FK integrity hazard: the three patient-side relationships MUST point at the same patient resolution result. No managed integrity helps you here — drift is silently inconsistent.

New-order Case

RT may auto-quarantine

Created by: service · OwnerId: CS_Case queue (or admin if quarantined)

  • RecordType.DeveloperName = Customer_Service_New_Order (typically) OR Quarantined_Order (auto-override)
  • ContactId, AccountId, Order__c, EHR_Practitioner__c
  • Status='New' · Subject='New Order' · HealthCloudGA__DueDate__c=today
  • New_Patient__c · New_Prescription_Order_Status__c='Contact Patient'
  • OwnerId = lookup Group WHERE DeveloperName='CS_Case' AND Type='Queue'
Auto-quarantine override: if patient has any open Case with RT=Quarantined_Order or Refill_Request_Case, the canonical Case_Insert_Update_Same_Record BeforeSave silently rewrites RT and OwnerId. Gated on ISNEW=true. Service post-queries the Case and returns actualCaseRecordType + wasQuarantined in the response.

Pharmacy_Note__c ×3

RT=Customer_Service

Created by: service · up to 3 per intake

  • 'Active Portal Cart' on new-order Case — when patient has an active WebCart with Rx CartItems
  • 'Note from Data Input' on new-order Case — free-form note from payload (or DE-team input via refactored screen flow)
  • 'Duplicate Order' on staging Case — when intake detects this is a duplicate of an existing Order
All three use RecordType.DeveloperName='Customer_Service' looked up at runtime — never hardcoded. The Pharmacy_Note__c object has only 2 RTs (Customer_Service + Pharmacist); these CS-bound notes correctly route to the CS RT.

Phase 3 — created by downstream automation P3 · fires from SF_Manually_Entered__c=true

Fill (HealthCloudGA__EhrMedicationPrescription__c RT=Fill)

downstream flow

Created by: New_Prescriptions_Manually_Entered flow · Service does NOT create Fills directly

  • Fields cached from Master Rx: patient FKs · prescriber · product · sig · quantity · strength · dosage form · days supply
  • NL_Prescription__cFill → Master Rx parent FK
  • Patient_Fill_Id__c — populated by HashUtils (MD5) for cross-system correlation
  • Date_filled__c (typo'd) · Order__c · Prescription_Number__c
NL_Prescription__c name is misleading. Sounds like legacy NewLeaf, but it's the active Fill→Master FK in live use. Don't drop based on naming.

OrderItem + PricebookEntry

downstream flow + service pricing pass

Created by: New_Prescriptions_Manually_Entered flow · Updated by: service's applyPricing()

  • OrderId · Product2Id · PricebookEntryId · Quantity
  • UnitPrice = 1.0 always — actual price lives in TotalLineAmount + ListPrice
  • Prescription_Link__c = Fill record (custom FK)
  • Auto-created PricebookEntry uses Standard Price Book — service should look up by Name='Standard Price Book', NOT hardcoded ID
Validation_Bypass_Date_Time__c is mandatory. Every OrderItem update by applyPricing() must set Validation_Bypass_Date_Time__c = Datetime.now() or the Prevent_Manual_Edits VR fails the update. The single most consequential technical fact for Phase A authoring.

§5Auth chain

Both Azure workloads — inbound Logic Apps writes and outbound ADF Contact reads — consolidate on one Connected App, one Run-As user, one profile, one perm set. Single point of operational truth, single point of compromise. The chain below is the inbound path; the outbound delta is called out at the bottom.

Step 1 · Upstream 01

LifeFile (web UI)

No auth surface for AdvancedRx — passive UI we scrape, not an integration partner.

Carries
The e-script document itself — viewed in a browser, no API surface.
Creds
None on this surface. Auth happens at the next step (PAD logs in as a human user).
Rotation
n/a — not a system AdvancedRx operates.
Compromise
If LifeFile creds leak: attacker can read prescriptions but can't write to SF (different auth domain).
Step 2 · UI scrape 02

PAD bot

Logs into LifeFile each morning as a human user; captures images, parses JSON.

Carries
LifeFile session cookie for the duration of the scrape.
Creds
lifefile-username + lifefile-password in Azure Key Vault, fetched at run time.
Rotation
Rotate the LifeFile credentials in Key Vault; PAD picks up the new values on the next 8 AM run.
Compromise
Equivalent to LifeFile creds compromise — read-only e-scripts, no SF write capability.
Step 3 · Orchestration 03

Azure Logic Apps

The orchestrator. Holds nothing sensitive locally — auth happens at the next hop.

Carries
Parsed JSON + image bytes for each e-script.
Creds
Managed Identity on the Azure side (no stored creds for Azure resources). Outbound to SF uses the Connected App.
Rotation
Managed Identity is auto-rotated by Azure. Connected App secret rotation handled at step 4.
Compromise
Managed Identity scoped to the Logic App's resource group; not directly exfiltrable. SF-bound auth gated by step 4.
Step 4 · OAuth bridge 04

Connected App · Azure Logic Apps

The single trust contract between Azure and Salesforce. Inbound + outbound share it.

Carries
Issues an OAuth access token bound to the Run-As user.
Flow
OAuth 2.0 Client Credentials. Scope = api (NOT full — deprecated for CC in Winter '26).
Creds
Consumer key + secret stored Azure-side. Key Vault expiry-alert wiring needs verification before Phase 1 prod cutover.
Rotation
Single rotation refreshes BOTH inbound + outbound auth — coordinate during a maintenance window. Runbook: .claude/runbooks/task-13-secret-rotation.md.
Compromise
Anyone with the consumer secret can mint tokens as the Run-As user → step 5 capability set.
Step 5 · Run-As identity 05

azure-integration@advancedrx.net

One user account fronts every Azure workload — the strongest writer-origin signal.

Carries
The user identity tokens are issued for. CreatedById on every Azure-written record.
License
Salesforce Integration User License (no UI login).
Rotation
n/a (passwordless). Deactivation = full integration outage; treat as a load-bearing user.
Compromise
If the consumer secret leaks (step 4), an attacker can act as this user — capability scoped at steps 6 + 7.
Step 6 · Profile 06

Minimum Access - Azure Integration

The least-privilege base. Locked-down by default; capabilities layered on top via the perm set.

Carries
Least-privilege base. Cloned from standard Minimum Access; permissions added back deliberately.
Reference
The CLAUDE.md §3.14 exemplar — the one AdvancedRx-controlled integration that follows least-privilege convention cleanly.
Maintenance
Do NOT bulk-edit the profile to grant new capabilities — layer them onto the perm set instead (step 7).
Compromise
Profile-level perms are intentionally narrow; the real capability surface lives in the perm set.
Step 7 · Permission set 07

Azure_Integration

Where capability actually lives. Each phase expands the scope of what gets reached.

Today
ContentVersion + Rx_Image__c + Case CRUD (inbound writes).
Phase 1
+ Contact Read + View All (ADF outbound). Lands together with the Azure-side RequirePatientMatching flag flip — neither is functional alone.
Phase 3
+ full intake-graph CRUD (Account, Contact, EhrPatient, ContactPointAddress, Order, OrderItem, Rx, Pharmacy_Note__c, Case + Rx_Image updates). Open: keep one perm set or split a Phase-3 one?
Compromise
Each phase widens what a compromised credential reaches. Document scope at each rollout.
Step 8 · API surface 08

Salesforce REST API

What the integration user can actually do. Phase 3 adds the new @RestResource; auth chain unchanged.

Today
Standard REST: insert ContentVersion + Rx_Image__c + Case + ContentDocumentLink. Idempotent upserts on External ID fields (External_Id_c__c on Rx_Image, External_ID__c on Case).
Phase 3
+ POST /services/apexrest/intake/v1/escriptPatientOrderIntakeRest. Same auth chain; new endpoint.
Errors
4xx + 5xx surface to Logic Apps' Scope_Catch_Failure → DLQ + SQL Failed. 200-with-errors looks like success to Scope_Catch_Failure — see §9 hazards.
Outbound delta (ADF Contact sync): identical chain from step 4 down. The legacy Data Factory Connected App (0H44W000000M5hgSAC) exists in the org but is not in use — Salesforce no longer allows creating Connected Apps of that type, so ADF authenticates through the same Azure Logic Apps Connected App, runs as the same user, uses the same perm set. The trade-off: rotating the consumer secret touches both inbound and outbound at once.

§6Feature flags

Three flags govern the rollout — two on the Azure side, one new on the Salesforce side that lands in Phase D. Phases 1 and 2 are flag flips, not feature builds; the SF write surface for both is already in place.

Flag Lives in Today (P0) After flip What flipping it does
Escript.Evaluator.RequirePatientMatching
Phase 1 trigger
Azure (wf-sf-writer evaluator config) false — every Case lands with ContactId=null, DE matches by hand true — high-confidence matches arrive with ContactId pre-populated Azure AI Search starts scoring incoming patients against the ADF-synced Contact roster. Threshold tunable Azure-side without an SF deploy.
Escript.Evaluator.RequirePrescriberMatching
Phase 2 trigger
Azure (wf-sf-writer evaluator config) false — outcome = "Ignored" → EHR_Practitioner__c resolves null even though wf-sf-writer writes the field true — high-confidence matches set Case.EHR_Practitioner__c Same AI-match approach, applied to prescribers. Field already exists; wf-sf-writer already writes it. Phase 2 is a flag flip, not a SF field addition.
Intake_Pipeline_Routing__mdt
Phase D channel cutover
Salesforce (CMDT) — added in Phase D n/a — doesn't exist yet per-channel boolean — e.g. eScript=true, fax=false Per-channel + per-confidence-band routing. true = Azure POSTs structured JSON to PatientOrderIntakeRest; false = falls back to today's image-staging path. Rollback = flip a row, no deploy.
Phase 1 SF + Azure interlock: the RequirePatientMatching flip on the Azure side AND the Azure_Integration perm-set expansion (Contact Read + View All + FLS on the synced fields) on the SF side must land together. Either alone leaves the pipeline broken intermediate — Azure asking for Contact data the integration user can't read, or SF granting access nothing's reading. The azure-integration@advancedrx.net user is already on the perm set for inbound writes; Phase 1 widens its scope, no new perm-set assignment needed.

§7The transcription handoff

14 flows reference Rx_Image__c in some way. The load-bearing one is the 7,904-line screen flow the data-entry team launches off the staging Case to produce structured records — Phase B refactors that flow to call PatientOrderIntakeService via an @InvocableMethod wrapper, preserving all the screens but moving orchestration into Apex. Filter to scope the table.

Flow Type Status Role in the cluster
New_Patient_New_Order_New_Rx_New_Case_Launch_from_data_case Screen Active The canonical transcription path. DE team launches from the staging Case; produces Account → HC-auto-Contact → EhrPatient → Order → Rx records → Pharmacy_Notes → new-order Case. ~7,904 lines / ~150 nodes today; Phase B refactors to ~30–40 nodes calling PatientOrderIntakeInvocable.
New_Prescriptions_and_New_Order_Case Screen Active Alternative hand-entry path (when there's no staging Case to launch from). Phase B refactors this to call the same invocable wrapper.
E_Script_Case_Creation Record-triggered Active Creates a paired Case for a newly-staged Rx_Image. May complement or supersede part of Azure's case-creation behavior — verify before extending.
Image_Uploaded_to_Existing_Order Record-triggered Active Manual-upload path — when an image is uploaded against an existing Order (rather than via Azure ingest), this flow links the image into the right Order context.
Rx_Image_Annotation_Button Screen Active Pharmacist / DE-team rich-text annotation of the image. Mutates Image__c; preserve the inline <img> reference.
Pharmacist_PV1_Rx_Image_Annotation_Button Screen Active PV1-time pharmacist annotation. Same surface as above; pharmacist-action context.
Fax_Clarification_Flow Screen Active Fax-path intake (distinct from e-script) — reads Rx_Image__c for fax-clarification context.
New_Order_Flow Screen / Autolaunched Active Order-side automation that reads Rx_Image__c for context.
Order_Insert_Update_Same_Record Record-triggered (Order) Active Order canonical BeforeSave — reads Rx_Image__c for source-image lookup. Doesn't mutate Rx_Image directly.
Order_Insert_Update_Related_Records Record-triggered (Order) Active Order canonical AfterSave — also reads Rx_Image__c for downstream linkage.
Automation_on_New_Refills Record-triggered (Rx) Active Refill automation — reads Rx_Image for source-image audit trail.
Advanced_Rx_Prescription_Insert_Update_Same_Record Record-triggered (Rx) Active Rx canonical BeforeSave — reads Rx_Image for cross-record validation.
FL_Loop_in_an_order_Final_Verification_view Screen Active Final-verification screen flow — surfaces Rx_Image for pharmacist review at PV checkpoint.
Data_New_Formula_or_Sig Screen Active Misc data-entry helper that reads Rx_Image when the user is creating a new Formula or Sig in context.
Update_Prescription_Image_via_Rx_Image Record-triggered Inactive Predecessor of Image_Uploaded_to_Existing_Order. Inactive but on disk — don't extend.
Update_Prescription_image_via_Rx_Image_preocess Process Builder (typo'd preocess) Inactive Process-Builder sibling of the row above. Inactive. Migration candidate; don't extend.
No flows match the current filter.

No canonical Same/Related pair exists on Rx_Image__c. Both record-triggered flows are functionally-named, not Rx_Image_Insert_Update_*. New record-level automation should consider introducing the canonical pair shape per .claude/rules/flow.md.

§8One e-script's lifecycle

Walk one inbound e-script through the pipeline, step by step. The stepper is phase-aware — switch the toggle (top right) to see how each step changes across phases. Steps marked "P3" are introduced in Phase 3 and dim out in earlier phases.

Step 1 of 9

An e-script arrives at LifeFile

Upstream LifeFile

A doctor sends a prescription to their pharmacy via LifeFile. The document arrives in the doctor's LifeFile UI for the pharmacy to retrieve.

AdvancedRx has no integration with LifeFile — no API, no webhooks, no email or fax bridge. The only way to discover documents is to log in as a human user and read them off the screen.

Upstream UI dependency: if LifeFile changes the e-script page layout, the scrape breaks. There's no API contract to fall back on.
Step 2 of 9

The PAD bot scrapes LifeFile

Azure wf-start-lifefile-automation PAD bot

At 8 AM daily, Logic App wf-start-lifefile-automation kicks off the Power Automate Desktop bot.

The bot pulls lifefile-username + lifefile-password from Azure Key Vault, logs in as a human user, navigates the UI to the new e-scripts queue, and for each document: captures the image as PNG, parses the surrounding UI text into structured JSON.

The bot hands the parsed document set off to wf-sf-dispatch for fan-out to the writer.

Step 3 of 9

Logic Apps writes the 5-record staging set

Azure wf-sf-dispatch wf-sf-writer Salesforce ContentVersion Rx_Image__c ContentDocumentLink ×2 Case

wf-sf-dispatch queues one Service Bus message per document. wf-sf-writer consumes each and writes the staging set to Salesforce in one logical sequence:

  • Insert ContentVersion — the file bytes (PDF or PNG)
  • Insert Rx_Image__c — staging record with rich-text Image__c + idempotent External_Id_c__c
  • Insert ContentDocumentLink #1 — links the file to the staging record
  • Insert Case with Subject = "LastName, FirstName (DOB: MM/DD/YY)" · same-day repeats prefix "Multiple RX"
  • Insert ContentDocumentLink #2 — links the same file to the Case (so it shows on the Case Files panel)

Idempotent on the External ID — if Azure replays the same document later, both Rx_Image__c and Case upsert instead of duplicating.

12-flow Case collision: the moment the Case inserts, all 12 active Case-triggered flows evaluate. Real per-update cost is the 2 canonical flows (Same / Related); the other 10 are gated.
Step 4 of 9

Azure AI matches incoming patient (and prescriber)

P1 in progress · P2 next
Azure AI Search Azure SQL · Contact roster Data Factory
Phase 0: this step doesn't run. Escript.Evaluator.RequirePatientMatching = false. Every Case lands with ContactId = null.
Phase 1+ (patient match): RequirePatientMatching = true. wf-sf-writer calls Azure AI Search before the Case insert. AI Search scores the incoming patient identifiers against the ADF-synced Contact roster — signals: phone, DOB, name similarity (phonetic + 1,100+ nickname pairs). If confidence ≥ threshold, wf-sf-writer includes ContactId in the Case insert from step 3.
Phase 2+ (prescriber match): RequirePrescriberMatching = true. Same approach applied to prescribers. Case.EHR_Practitioner__c pre-populated when match high. The field already exists and wf-sf-writer already writes to it — Phase 2 is purely a flag flip.
The match is advisory, not authoritative. Pharmacy practice still requires DE / pharmacist review. Goal: reduce lookup time, not remove human review.
Step 5 of 9

Logic Apps POSTs structured JSON to the Apex endpoint

P3 only
Azure wf-sf-writer (extension) Salesforce PatientOrderIntakeRest
Pre-Phase-3: this step doesn't run. wf-sf-writer only writes the staging set; the data-entry team produces structured records by hand from step 7 onward.
Phase 3: for high-confidence + parseable payloads (gated by Intake_Pipeline_Routing__mdt per channel), wf-sf-writer POSTs to /services/apexrest/intake/v1/escript. Body: the parsed domain JSON — idempotency key, patient block, prescriber block, prescriptions array, notes, address, optional staging Case + Rx_Image IDs.

The PatientOrderIntakeRest endpoint deserializes into IntakeRequest, calls PatientOrderIntakeService.intake(), returns IntakeResponse as the HTTP body. HTTP status code drives Logic Apps' retry / DLQ logic — 200 = success (warnings allowed) · 400 = validation reject · 409 = idempotency conflict · 500 = internal failure.

200-with-errors won't work. Logic Apps' Scope_Catch_Failure triggers on HTTP error status only. A pure-200 body with an errors[] list would be silently treated as success — and every validation failure would be lost.
Step 6 of 9

The Case lands in the work queue

Salesforce Staging Case Human DE team
Primary path (P0–P2): the Case appears in the data-entry queue with subject "LastName, FirstName (DOB)". DE picks it up, opens it, launches the screen flow. Every inbound e-script flows through here.
Fallback path only (P3): only low-confidence matches and parser-failures land here. The bulk of intake goes through the structured POST in step 5 and never reaches the queue.

Either way, the staging Case + image are the audit trail. Even when the Apex service does the work, the staging Case is finalized later (step 8) so the source-image lineage remains intact.

Step 7 of 9

The structured graph appears

Salesforce Account Contact (HC auto) EhrPatient CPA Order Master Rx ×N Fill + OrderItem Pharmacy_Note ×3 new-order Case
P0–P2 (screen flow path): the 7,904-line New_Patient_New_Order_New_Rx_New_Case_Launch_from_data_case walks DE through screens — patient demographics, prescriber, up to 5 prescriptions, address, notes — then creates everything in roughly the order shown above. ~150 nodes.
P3 (Apex service path): all of it runs in one Apex transaction with a SAVEPOINT. PatientOrderIntakeService.intake() does: resolvePatient()resolvePrescriber()resolveOrAttachOrder()createPrescriptions()applyPricing()createPharmacyNotes()linkBackToStagingCase() + Rx_Image. On any failure: ROLLBACK + Error_Log__c + structured error in body.

Either way, the sequence is: insert Account → Health Cloud managed package auto-creates the paired Contact → service / flow updates the Contact → insert EhrPatient + ContactPointAddress → insert Order → insert up to 5 Master Rx records (each with SF_Manually_Entered__c=true) → downstream New_Prescriptions_Manually_Entered flow creates Fill + OrderItem + PricebookEntry → service applies prices (sets Validation_Bypass_Date_Time__c to skirt Prevent_Manual_Edits) → up to 3 Pharmacy_Notes → new-order Case.

Three patient FKs on Rx must agree. HealthCloudGA__Patient__c, HealthCloudGA__Account__c, Contact__c all point at the same human — no managed integrity helps here. Drift = silent inconsistency.
Step 8 of 9

Auto-quarantine check + staging Case finalize

Salesforce Case_Insert_Update_Same_Record new-order Case staging Case

On the new-order Case insert, the canonical Case BeforeSave flow runs an auto-quarantine check: does this patient have any open Case with RT=Quarantined_Order or Refill_Request_Case? If yes, silently rewrite RT to Quarantined_Order and OwnerId to Advanced Rx Admin user. Gated on ISNEW=true, so only Inserts trigger the rewrite.

This is by design — patient with quarantine concerns gets routed to admin, not CS. The service post-queries the Case and returns the actual landed values (actualCaseRecordType, actualCaseOwnerType, wasQuarantined) plus an AUTO_QUARANTINE_APPLIED warning so consumers don't have to re-query.

Then the staging Case (the original Azure-created Data_Input Case) is finalized: ContactId + EHR_Practitioner__c set, ParentId → new-order Case, Status='Closed', Order__c set. OwnerId deliberately left unchanged — avoids tripping Restrict_Case_Ownership_Changes, the one active VR on Case.

Two quarantine systems exist. Order.Skip_Quarantine_Check__c=true bypasses an Order-side mechanism. It does NOT bypass this Case-side auto-quarantine.
Step 9 of 9

PV1 pharmacist verifies

Human Pharmacist Salesforce Pv1_Screen_Flow

A pharmacist verifies the prescription before any fill ships. PV1 (Pharmacist Verification 1) is the regulatory checkpoint — the actual gate between "data exists in Salesforce" and "patient receives a fill."

PV1 is the same checkpoint regardless of who or what produced the records. Whether DE transcribed by hand, Phase 1+ AI pre-linked an existing patient, or Phase 3's Apex service auto-wrote the entire graph, every prescription passes through this verification before it ships.

Out of scope for this plan — but the path leads here. The whole upstream automation effort exists to make PV1 faster, not to replace it.
Step 1 of 9

§9Hazards & gotchas

The subtle stuff: typo'd API names that mustn't be autocorrected, hardcoded IDs to retrofit, silent-failure surfaces, multi-relationship integrity, Health Cloud upgrade fragility, and a worth-keeping teaching moment about inference vs. ground truth.

These API names are typo'd in the live org. The typos are load-bearing — deployments referencing the "corrected" spelling will fail with INVALID_FIELD. Don't autocorrect.

  • External_Id_c__c on Rx_Image__c_c__c suffix. UI label is External_Id__c (clean). Phase 3 idempotent-upsert path depends on this.
  • Date_filled__c on Master Rx — lowercase d. Service writes this on every Rx insert.
  • Update_Prescription_image_via_Rx_Image_preocesspreocess. It's an inactive Process Builder.
  • Overrided__c on Refill — should be Overrode or Overridden.
  • Special_counseling_requirents_* on Compound — requirents.
  • B2BAuthroize* Apex class family — Authroize.
  • External_Id_c__c consequence: SOQL searches for External_Id__c on Rx_Image__c miss every Azure-staged record.

The screen flow + downstream New_Prescriptions_Manually_Entered flow together hardcode 5 RT IDs, 1 Group ID, and 1 Pricebook ID. ~175 locations across the org carry the same anti-pattern. Phase A and B remove these from new code; Phase E may opportunistically clean up untouched paths.

  • 5 hardcoded RT IDs — Account-Individual · Case-CustomerServiceNewOrder · Rx-Prescription · Rx-Fill · Pharmacy_Note-CustomerService
  • 1 hardcoded Group IDCS_Case queue. Worse: stored as a flow constant and pasted inline (the missed-dedup anti-pattern in flow.md)
  • 1 hardcoded Pricebook2 ID01s4W000005JCZUQA4 (Standard Price Book). Survives same-parent sandbox refresh; breaks on cross-org migration.
  • The new service uses RecordType.DeveloperName / Group.DeveloperName / Pricebook2 WHERE Name='Standard Price Book' exclusively.
  • Severity nuance (per .claude/rules/apex.md): User IDs > Profile IDs > RT IDs. User IDs break immediately on deactivation; RT IDs survive same-parent refresh.

Five places in the pipeline can fail without surfacing a visible error. Knowing where they are is half the battle when debugging.

  • Flow fault connectors org-wide: 18% conformance per flow.md §3.12. Flows fired by Azure-written Cases may fail silently.
  • Logic Apps Scope_Catch_Failure: only triggers on HTTP error status codes. A 200-with-errors response body is silently treated as success — every validation failure would be lost. (This is why the Phase 3 endpoint must use real HTTP status codes.)
  • No SF-side audit of Azure failures: the Error_Log__c convention does not extend to Azure-side failures. Debugging needs Azure run history + SQL + Service Bus DLQ.
  • External_Id_c__c typo: SOQL on External_Id__c for Rx_Image__c returns zero Azure-staged records.
  • Azure connector field filtering: some managed Salesforce connectors in Azure do client-side field filtering and silently drop unknowns before the API call. Verify error surfacing in sandbox before relying on it.
  • 75% Apex coverage gate: measures executed lines, not assertion density. increaseCoverage() no-ops + zero-assertion tests inflate reported coverage. Treat as a floor, not a quality signal.

Master Rx carries three parallel patient-side relationships that all must point at the same human. The Health Cloud package's auto-pairing only protects one side of the triangle — the other two are AdvancedRx-added custom Lookups with no managed integrity guarantee.

  • HealthCloudGA__Patient__cHealthCloudGA__EhrPatient__c (managed)
  • HealthCloudGA__Account__cAccount (custom, AdvancedRx-added)
  • Contact__cContact (custom, AdvancedRx-added)

If a future code path sets HealthCloudGA__Patient__c to one patient's EhrPatient but Contact__c to a different patient's Contact, the data is silently inconsistent — none of the Health-Cloud-managed integrity helps. The service must resolve the Account+Contact+EhrPatient triple as a single unit and populate all three FKs from that single resolution result. (Phase A0 §1 documents the screen flow's correct multi-FK population.)

The service writes to managed HealthCloudGA__* objects (Account-side via auto-Contact-create, plus EhrPatient, Master Rx, Fill). Future package upgrades may rename managed fields and silently break the service.

  • Pin Health Cloud version in the test plan. Rerun service tests after every HC managed-package upgrade.
  • 7 VRs on managed HealthCloudGA__* objects (Rx, Practitioner) carry custom AdvancedRx logic — upgrade-fragile too. Any package upgrade may invalidate the bypass logic.
  • The auto-Contact-create dependency is load-bearing. Inserting an Account (Individual RT, Group + NonDual picklist values) triggers HC-managed-package code that creates the paired Contact server-side. The whole 5-step patient-creation chain depends on this. If HC behavior changes, the service breaks silently.
  • Phase A test gate: verify auto-Contact-create works under with sharing Apex running as the integration user before Phase A ships. If it fails, three options — widen the integration user's perms, flip the relevant Account-insert step to without sharing, or abandon the auto-create path entirely.

The Azure pipeline story has been corrected twice during this project's research. Both corrections illustrate the same hazard: structural inference produces internally consistent but externally wrong attributions with unsettling ease.

2026-04-17
Inference: the 5 *_to_Salesforce_*__mdt mapping CMDTs target the LifeFile pipeline. Wrong
Reasoning shape: CMDT naming pattern + LifeFile is real → must be the target. But LifeFile has zero SF-side integration.
2026-04-19
Replacement inference: the same 5 CMDTs feed the Azure inbound flow. Also wrong
Reasoning shape: Azure is real + CMDTs must feed something → must be the target. Azure uses none of these CMDTs; grep found references only in dead NewLeaf batch jobs.
2026-04-21
Confirmed: the 5 CMDTs are NewLeaf-era legacy artifacts — die with the NewLeaf cleanup. Independent of Azure entirely. Current
Lesson worth keeping: internal-shape inference (object models, naming conventions, trigger frameworks, junction patterns, audit-field shapes) is reliable. External-system identity and data-flow topology from metadata alone are not. "Not A, therefore B" isn't confirmation of B — it's a narrower inference with the same shape, and the same failure mode. When a CMDT, code class, or field name suggests "this connects to System X," treat it as a hypothesis until human-confirmed.

§10Open questions

What still needs a decision before each phase ships. Border colors: Kyle decision Marissa needed Sandbox-verifiable Recommended (not blocking).

Q1 · Phase 3 perm-set blast radius

Kyle decision

Accept consolidation on the existing Azure Logic Apps Connected App + Azure_Integration perm set (continues the Phase 0/1/2 trajectory)? Or split a separate Connected App + perm set for Phase 3 writes only (isolates the write blast radius at rotation-coordination cost)?

Blocks: Phase D cutover Owner: Kyle Reference: plan.md §6 risk #6

Q2 · Fax intake JSON contract

Kyle + Marissa

Does Azure parse faxes into the same domain JSON contract as e-scripts (one endpoint, one schema), or do faxes use a different contract / a different channel? Initial Phase D cutover keeps faxes on the existing image-staging path, so this can be deferred — but must be answered before the fax row of Intake_Pipeline_Routing__mdt flips to true.

Blocks: Phase D fax row Owner: Kyle + Marissa Reference: plan.md §9 #2

Q3 · Confidence-threshold tuning

Kyle (post-launch)

At what Azure-AI confidence floor does the structured-write path stop being worth it (records bouncing at PV1 too often, parser ambiguity outweighing DE-team labor savings)? Tunable Azure-side via the AI Search threshold + the per-channel CMDT — not a Phase D blocker; tune from real-world match quality after launch.

Blocks: nothing (post-launch tuning) Owner: Kyle Reference: plan.md §9 #3

Q4 · Manual_Order__c semantics on Rx_Image__c

Marissa needed

Azure never sets this checkbox (verified via wf-sf-writer). The DE-team-sets-it-true-for-manual-uploads interpretation is supported by code + workflow history, but Marissa's explicit confirmation is still pending. Affects nothing in this plan directly — flagged here so future code touching the field knows the semantics aren't fully confirmed.

Blocks: nothing in this plan Owner: Marissa Reference: azure-integration.md open question #2

Q5 · Phase 1 DE workflow redesign

Marissa decision

When RequirePatientMatching flips, DE's workflow changes — some Cases arrive pre-linked, some don't. Kyle's leading idea: encode confidence + match status in the Case subject so DE can quickly assess whether to trust the auto-link. DE keeps human review either way; the goal is reduced lookup time, not removed review. Marissa decides the Case-subject format convention.

Blocks: Phase 1 prod cutover Owner: Marissa Reference: azure-integration.md open question #3

Q6 · SF-side match audit trail

Recommended

Today the AI confidence score lives in Azure SQL only (dbo.PrescriptionDocuments.PatientMatchScore + PatientMatchReasons). A pharmacist debugging "why did this Case link to the wrong patient?" needs Azure SQL access. Recommended before Phase 1 prod cutover: add Case.Match_Confidence_Score__c + Case.Match_Reasons__c; wire wf-sf-writer + usp_UpdateSalesforceStatus to populate. One-line Azure change; SF additions carry the 12-flow + 21-RT blast radius — coordinate the deploy.

Blocks: nothing (deferral OK) Owner: Kyle (decision) + Marissa (UX) Reference: azure-integration.md open question #4

Q7 · Connected App secret · Key Vault coverage

Sandbox-verifiable

Confirm the Azure Logic Apps Connected App consumer secret is tracked in Azure Key Vault with expiry-alert wiring (analogous to MICROSOFT_PROVIDER_AUTHENTICATION_SECRET). Extend task-13-secret-rotation.md to cover the bidirectional refresh (ADF LS_Salesforce_Production + inbound Logic Apps auth). Close before Phase 1 prod cutover.

Blocks: Phase 1 prod cutover Owner: Kyle (verifiable in Azure Portal) Reference: azure-integration.md open question #5

Q8 · Health Cloud auto-Contact-create under with sharing

Sandbox-verifiable

Phase A authoring depends on Health Cloud's managed-package code creating the paired Contact when the integration user inserts an Account (Individual RT, Group + NonDual). The screen flow runs SystemModeWithoutSharing. Whether the auto-create works the same when invoked from with sharing Apex is unverified. Phase A gate — if the test fails, the team picks between widening the integration user's perm set, flipping the Account-insert step to without sharing, or abandoning the auto-create path (changes the contract).

Blocks: Phase A ship Owner: Kyle (sandbox test) Reference: plan.md §6 risk #1 + Phase A0 §5.B

§11Glossary

Acronyms and the API names that recur. Includes the External_Id_c__c typo — preserved deliberately, not autocorrected.

Acronyms
ADF
Azure Data Factory — pulls SF Contact roster into Azure SQL on a schedule.
AI Search
Azure AI Search (formerly Azure Cognitive Search) — scores incoming patients/prescribers against the synced roster.
CMDT
Custom Metadata Type — Salesforce schema for non-secret config that survives sandbox refresh.
CPA
ContactPointAddress — standard SF object; Master-Detail to Account, custom Lookup to Contact.
CS
Customer Service — the team that does patient outreach. CS_Case is their queue's DeveloperName.
DE
Data Entry — the team that transcribes inbound e-scripts via the screen flow.
DLQ
Dead Letter Queue — Azure Service Bus messages that fail are dead-lettered with a descriptive reason.
DLRS
Declarative Lookup Rollup Summaries — managed package that generates rollup triggers (dlrs_*).
FK
Foreign Key — Salesforce Lookup or Master-Detail relationship.
FLS
Field-Level Security — per-field read/edit permission, layered on the perm set.
HC
Health Cloud — Salesforce managed package providing HealthCloudGA__* objects.
LWC
Lightning Web Component.
NC
Named Credential — SF surface for secure outbound callout configuration.
NPI
National Provider Identifier — 10-digit prescriber ID (3 active VRs enforce format).
OWD
Organization-Wide Default — SF base sharing model (Public Read/Write, Read Only, or Private).
PAD
Power Automate Desktop — Microsoft's desktop UI-automation tool. Used to scrape LifeFile.
PB
Process Builder — legacy SF automation tool, superseded by Flow.
PHI
Protected Health Information — under USP 795 + HIPAA scope at AdvancedRx.
PV1
Pharmacist Verification 1 — regulatory checkpoint before any fill ships. Same checkpoint regardless of who produced the records.
RT
Record Type — drives layouts, picklist values, and automation routing.
SCV
Service Cloud Voice — Salesforce voice-call lifecycle product (30 active seats, Amazon Connect routing).
SOQL
Salesforce Object Query Language — SF's read-only query language.
USP 795
United States Pharmacopeia chapter 795 — governs nonsterile compounding. AdvancedRx is licensed in 48 continental states.
VR
Validation Rule — record-level field validation; fails the DML if the formula returns true.
API names that recur
azure-integration@advancedrx.net
The Run-As user for both inbound Logic Apps writes and outbound ADF reads. CreatedById on every Azure-written record.
Azure Logic Apps
The Connected App fronting both Azure workloads. Note: same name as the Azure service — confusing. Connected App ID in ClaudeTest: 0H4aZ00000013OrSAI.
Azure_Integration
The perm set layered on the integration user. Each phase expands its scope.
Case_Insert_Update_Same_Record
Canonical Case BeforeSave flow. The auto-quarantine override (RT + Owner rewrite when patient has open quarantined or refill-request Case) lives here, gated on ISNEW=true.
Escript.Evaluator.RequirePatientMatching
Phase 1 feature flag (Azure-side). False today; flipping to true triggers AI patient-matching and pre-populates Case.ContactId on high-confidence matches.
Escript.Evaluator.RequirePrescriberMatching
Phase 2 feature flag (Azure-side). False today; flipping triggers prescriber matching, pre-populates Case.EHR_Practitioner__c.
External_Id_c__c
Typo'd API name on Rx_Image__c. The _c__c suffix is preserved deliberately — autocorrecting to External_Id__c breaks every reference.
External_ID__c
Clean API name on Case. Different object than above; both carry the same Rx External ID value.
Intake_Pipeline_Routing__mdt
New CMDT in Phase D. Per-channel + per-confidence-band routing — true = use Phase 3 endpoint, false = staging path.
LS_Salesforce_Production
The ADF linked service that authenticates to Salesforce.
Minimum Access - Azure Integration
Least-privilege custom profile (CLAUDE.md §3.14 exemplar) — least-perms base; capabilities layered via the perm set.
New_Patient_New_Order_New_Rx_New_Case_Launch_from_data_case
The 7,904-line canonical screen flow. Produces the structured patient + order + Rx graph from a staging Case. Phase B refactor target.
New_Prescriptions_Manually_Entered
Downstream flow that fires when SF_Manually_Entered__c=true on Master Rx insert. Creates Fill + OrderItem + PricebookEntry.
PatientOrderIntakeRest
New Apex @RestResource at /services/apexrest/intake/v1/escript (Phase 3). Maps Azure JSON → service.intake() → HTTP response.
PatientOrderIntakeService
New Apex service class (Phase 3). Single canonical intake(IntakeRequest) method shared by the REST surface and the refactored screen flow.
Validation_Bypass_Date_Time__c
OrderItem field that bypasses the Prevent_Manual_Edits VR. Service must set this on every OrderItem update or every update fails.
wf-sf-dispatch
Logic App that fans out per-document writes via Service Bus.
wf-sf-writer
Logic App that writes to Salesforce. Carries Scope_Catch_Failure for HTTP-error-driven DLQ.
wf-start-lifefile-automation
Daily 8 AM Logic App that triggers the PAD bot.