OZO FHIR implementation guide
0.1.0 - ci-build
OZO FHIR implementation guide - Local Development build (v0.1.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
requester fieldinput/fsh/profiles/ozo-communicationrequest.fshinput/fsh/profiles/ozo-communication.fshinput/pagecontent/interaction-messaging.mdinput/pagecontent/overview.mdinput/fsh/instances/communicationrequest-org-example.fsh (NEW)input/fsh/instances/communication-org-reply-example.fsh (NEW)This document analyzes the addressing problem in the FHIR proposal for organization-level messaging and proposes a solution to enable organization-to-organization communication while maintaining individual auditability.
Related: See the Organization Addressing Proposal for the original business requirements and high-level solution overview.
basedOninResponseTorequester fieldrequester can be different from sendersender allows: Device, Organization, Patient, Practitioner, PractitionerRole, RelatedPerson, HealthcareServicesender to: Practitioner, RelatedPerson (excludes Organization!)recipient allows: Device, Organization, Patient, Practitioner, PractitionerRole, RelatedPerson, HealthcareService, Group, CareTeam, Endpointrecipient to: Practitioner, RelatedPerson, CareTeam (excludes Organization!)Step 1: Pharmacy sends request
CommunicationRequest:
requester = Practitioner/A1 ← Individual who initiated (auditability)
sender = Organization/Pharmacy-A ← Reply-to address
recipient = Organization/Clinic-B ← Addressed to organization
payload = "Can you review patient's medication?"
Step 2: Clinic replies
Communication:
sender = Practitioner/B1 ← Individual responds (auditability)
recipient = Organization/Pharmacy-A ← Read from CommunicationRequest.sender
basedOn = CommunicationRequest/...
payload = "Yes, I'll review it today"
Why this works:
requester tracks the individual who initiated (auditability)sender in CommunicationRequest provides the Organization reply-to addresssender in Communication tracks the individual who responds (auditability)Step 1: Pharmacy sends request
CommunicationRequest:
requester = Practitioner/A1 ← Must be Practitioner (OZO constraint)
sender = Practitioner/A1 ← Must be Practitioner (OZO constraint)
recipient = CareTeam/B ← Using CareTeam as proxy
Step 2: Clinic needs to reply
Communication:
sender = Practitioner/B1
recipient = ??? ← No Organization in original request to reply to!
The issue:
requester fieldNote: This is the recommended solution. See Detailed Analysis below for complete specification.
requester: **Keep as Practitioner |
RelatedPerson** (to track individual who initiated the conversation) |
sender: Allow Organization (in addition to Practitioner, RelatedPerson)recipient: Allow Organization (in addition to Practitioner, RelatedPerson, CareTeam)sender: **Keep as Practitioner |
RelatedPerson** (must be an individual for auditability) |
recipient: Allow Organization (in addition to Practitioner, RelatedPerson, CareTeam)CommunicationRequest.requester tracks who initiated the conversationCommunication.sender tracks who sent each messageCommunicationRequest.sender can be Organization (reply-to address)CommunicationRequest.recipient and Communication.recipient can be OrganizationStep 1: Pharmacy A initiates conversation with first message
CommunicationRequest:
requester = Practitioner/A1 (individual who initiated - auditability)
sender = Organization/Pharmacy-A (organization is thread owner - reply-to address)
recipient = Organization/Clinic-B (addressed to organization)
payload = "Can you review patient's medication?" (initial message)
subject = Patient/123
Step 2: Practitioner B1 from Clinic B replies
Communication:
sender = Practitioner/B1 (individual responds)
recipient = Organization/Pharmacy-A (read from CommunicationRequest.sender)
basedOn = CommunicationRequest/... (links to thread)
payload = "Yes, I'll review and respond by tomorrow"
Step 3: Another practitioner A2 from Pharmacy A follows up
Communication:
sender = Practitioner/A2 (different person from same organization)
recipient = Organization/Clinic-B (continue to same organization)
basedOn = CommunicationRequest/... (links to thread)
inResponseTo = Communication/step2 (reply to previous message)
payload = "Thank you for the quick response"
| Resource | Field | Allowed Types | Note |
|---|---|---|---|
| CommunicationRequest | requester | Practitioner, RelatedPerson | No change - tracks individual who initiated |
| CommunicationRequest | sender | Practitioner, RelatedPerson, Organization | Organization = reply-to address |
| CommunicationRequest | recipient | Practitioner, RelatedPerson, CareTeam, Organization | - |
| Communication | sender | Practitioner, RelatedPerson | No change - must be individual |
| Communication | recipient | Practitioner, RelatedPerson, CareTeam, Organization | - |
Change:
Current OZO:
CommunicationRequest.sender: Practitioner | RelatedPerson
CommunicationRequest.requester: Practitioner | RelatedPerson
Proposed OZO:
CommunicationRequest.sender: Practitioner | RelatedPerson | CareTeam
CommunicationRequest.requester: Practitioner | RelatedPerson
Message Flow:
CommunicationRequest:
requester = Practitioner/A1 (audit: who initiated)
sender = CareTeam/A (reply-to address)
recipient = CareTeam/B (who should respond)
Communication (response):
sender = Practitioner/B1 (audit: who responded)
recipient = CareTeam/A (from CommunicationRequest.sender)
basedOn = CommunicationRequest/...
Communication (follow-up):
sender = CareTeam/A (logical sender is the team)
recipient = CareTeam/B (from previous Communication.sender's CareTeam)
inResponseTo = Communication/...
OR for follow-up maintain individual:
Communication (follow-up):
sender = Practitioner/A2 (different person responds)
recipient = CareTeam/B (keep replying to same team)
inResponseTo = Communication/...
Problem: Still need to derive sender's CareTeam for follow-ups from individuals
Add representedCareTeam extension:
CommunicationRequest:
requester = Practitioner/A1
recipient = CareTeam/B
extension[representedCareTeam] = CareTeam/A
Communication:
sender = Practitioner/B1
recipient = CareTeam/A (from extension)
basedOn = CommunicationRequest/...
extension[representedCareTeam] = CareTeam/B
Note: These are early exploratory options. The recommended solution (Solution B: Organization + Payload Rules) is described in the next section.
| Approach | Pros | Cons | Requires Profile Change |
|---|---|---|---|
| Allow Organization in requester | Standard FHIR pattern | Still need Organization→CareTeam mapping | Yes - requester field |
| Allow CareTeam in sender | Clean reply-to pattern, sender field provides address | Semantic confusion: CareTeam for org messaging | Yes - sender field |
| Extension for reply-to | No profile constraint changes, explicit | Custom extension, applications must support | No - only extension |
Best approach: Allow Organization Addressing
The proposed solution uses Organization directly for organizational messaging, while maintaining individual auditability through the requester and sender fields.
Core Principle: Allow Organization as a participant type, while maintaining individual auditability through proper use of requester and sender fields.
The original OZO constraints created an impossible situation:
Relax constraints to allow Organization:
CommunicationRequest.requester: Practitioner | RelatedPerson (NO CHANGE - must be individual)
CommunicationRequest.sender: Practitioner | RelatedPerson | Organization
CommunicationRequest.recipient: Practitioner | RelatedPerson | CareTeam | Organization
Purpose:
requester identifies the individual who initiated (auditability)sender can be Organization (reply-to address for the conversation)recipient can be Organization (shared inbox for receiving organization)Relax recipient constraint, keep sender as individual:
Communication.sender: Practitioner | RelatedPerson (NO CHANGE - must be individual)
Communication.recipient: Practitioner | RelatedPerson | CareTeam | Organization
Purpose:
Step 1: Thread Initiation
Instance: thread-pharmacy-to-clinic
InstanceOf: CommunicationRequest
* status = #active
* subject = Reference(Patient/123)
* requester = Reference(Practitioner/A1) // ← Individual who initiated
* sender = Reference(Organization/Pharmacy-A) // ← Reply-to address
* recipient = Reference(Organization/Clinic-B)
* payload[0].contentString = "Can you review the patient's medication list?"
Step 2: Reply from Clinic (Practitioner B1)
Instance: msg-1-pharmacy-question
InstanceOf: Communication
* status = #completed
* basedOn = Reference(CommunicationRequest/thread-pharmacy-to-clinic)
* sender = Reference(Practitioner/A1) // ← Individual auditability
* recipient = Reference(Organization/Clinic-B) // ← From CommunicationRequest.recipient
* payload[0].contentString = "Can you review the patient's medication list?"
Application Logic for Reply-To Discovery:
basedOn referenceCommunicationRequest.sender is Organization → use that as reply recipientStep 3: Reply (Practitioner B1)
Instance: msg-2-clinic-response
InstanceOf: Communication
* status = #completed
* basedOn = Reference(CommunicationRequest/thread-pharmacy-to-clinic)
* inResponseTo = Reference(Communication/msg-1-pharmacy-question)
* sender = Reference(Practitioner/B1) // ← Individual auditability
* recipient = Reference(Organization/Pharmacy-A) // ← From CommunicationRequest.sender
* payload[0].contentString = "Yes, I'll review it today and follow up by EOD"
Step 4: Follow-up (Different Practitioner A2)
Instance: msg-3-pharmacy-followup
InstanceOf: Communication
* status = #completed
* basedOn = Reference(CommunicationRequest/thread-pharmacy-to-clinic)
* inResponseTo = Reference(Communication/msg-2-clinic-response)
* sender = Reference(Practitioner/A2) // ← Different person, same org
* recipient = Reference(Organization/Clinic-B) // ← Continue to same org
* payload[0].contentString = "Thank you for the quick response!"
GET /Communication?recipient=Organization/Pharmacy-A&_include=Communication:based-on
Returns all Communications where recipient is the organization, plus their parent CommunicationRequests.
GET /Communication?sender=Practitioner/A1
Returns all Communications sent by this individual.
GET /Communication?based-on=CommunicationRequest/thread-pharmacy-to-clinic
&_sort=sent
Returns chronologically sorted thread.
input/fsh/profiles/ozo-communicationrequest.fshCurrent constraints to relax:
// CURRENT (remove these restrictions):
* sender only Reference(OZOPractitioner or OZORelatedPerson)
* recipient only Reference(OZOPractitioner or OZORelatedPerson or OZOCareTeam)
// CHANGE TO:
* sender only Reference(OZOPractitioner or OZORelatedPerson or OZOOrganization)
* recipient only Reference(OZOPractitioner or OZORelatedPerson or OZOCareTeam or OZOOrganization)
// KEEP AS IS (no change):
* requester only Reference(OZOPractitioner or OZORelatedPerson)
Rationale:
requester stays restricted to individuals (auditability - tracks who initiated)sender now allows Organization (provides reply-to address for organizational messaging)recipient now allows Organization (enables shared inbox pattern)input/fsh/profiles/ozo-communication.fshCurrent constraints to relax:
// CURRENT (remove this restriction):
* recipient only Reference(OZOPractitioner or OZORelatedPerson or OZOCareTeam)
// CHANGE TO:
* recipient only Reference(OZOPractitioner or OZORelatedPerson or OZOCareTeam or OZOOrganization)
// KEEP AS IS (no change):
* sender only Reference(OZOPractitioner or OZORelatedPerson)
Rationale:
sender stays restricted to individuals (auditability - tracks who sent each message)recipient now allows Organization (messages can be addressed to organizations)input/pagecontent/interaction-messaging.mdAdd new section:
CommunicationRequest.senderUpdate existing examples:
CommunicationRequest with:
requester = Practitioner (individual who initiated)sender = Organization (reply-to address)recipient = Organization (addressed to organization)Communication replies addressed to Organizationinput/pagecontent/overview.mdUpdate CommunicationRequest description:
The `CommunicationRequest` can now address Organizations directly:
- `requester`: Individual who initiated (Practitioner or RelatedPerson) - for auditability
- `sender`: Can be Organization to provide reply-to address for organizational conversations
- `recipient`: Can be Organization to enable shared inbox pattern for pharmacies, clinics, etc.
Update Communication description:
The `Communication` sender must remain an individual, but can be addressed to Organizations:
- `sender`: Individual who sent the message (Practitioner or RelatedPerson) - for auditability
- `recipient`: Can be Organization to address messages to all members of an organization
Add clarification:
input/fsh/instances/communicationrequest-org-example.fsh (NEW)Instance: CommunicationRequest-Pharmacy-to-Clinic
InstanceOf: OZOCommunicationRequest
Title: "Example: Pharmacy to Clinic Organization-Level Communication Request"
Description: "Shows a CommunicationRequest from a pharmacy to a clinic using Organization addressing"
* status = #active
* subject = Reference(Patient/example-patient)
* requester = Reference(Practitioner/pharmacy-practitioner-a1)
* sender = Reference(Organization/pharmacy-a)
* recipient = Reference(Organization/clinic-b)
* payload[0].contentString = "Can you review this patient's medication list for potential interactions?"
input/fsh/instances/communication-org-reply-example.fsh (NEW)Instance: Communication-Clinic-Reply
InstanceOf: OZOCommunication
Title: "Example: Clinic Reply to Organization-Level Message"
Description: "Shows a Communication reply from clinic practitioner to pharmacy organization"
* status = #completed
* basedOn = Reference(CommunicationRequest/CommunicationRequest-Pharmacy-to-Clinic)
* sender = Reference(Practitioner/clinic-practitioner-b1)
* recipient = Reference(Organization/pharmacy-a)
* payload[0].contentString = "I've reviewed the medications and will send my recommendations today"
| Aspect | Original Proposal (CareTeam Proxy) | Recommended Solution (Organization Addressing) |
|---|---|---|
| Addressing mechanism | CareTeam as proxy for Organization | Organization directly |
| Sender auditability | Extension or discovery needed | Built-in: requester and sender fields track individuals |
| Reply-to discovery | Complex (CareTeam lookup) | Simple (read from CommunicationRequest.sender) |
| Custom extensions | Yes (for tracking sender org) | No |
| FHIR alignment | Workaround | Direct alignment with FHIR R4 |
| CareTeam confusion | Yes (patient care vs org messaging) | No (clear separation) |
This solution is cleaner, more maintainable, and better aligned with FHIR semantics while solving the same business problem.