Projo FHIR Documentation
0.1.0 - ci-build Switzerland flag

Projo FHIR Documentation - Local Development build (v0.1.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions

Data Model

Data Model

Projo's FHIR data model spans 27 resource types. Together they model the tenancy graph, the conversational identities that can receive WhatsApp messages, the care-plan schedule layer produced by PlanDefinition/$apply, the dispatch (Task) → contract (CommunicationRequest) → audit (Communication) → answer (QuestionnaireResponse) → extraction (Observation/Condition/MedicationStatement) pipeline, and the idempotency/routing anchors (Provenance, Flag) that keep the orchestrator replay-safe.

Resource inventory

# Resource Role
1 Organization Customer/tenant; carries WhatsApp phone (telecom), plan-slots, cached phone-number-id
2 Patient End user; linked to Organization via managingOrganization; carries telecom (phone) and BSUID identifier
3 Practitioner Alternative conversational identity (telemedicine line owner)
4 RelatedPerson Alternative conversational identity
5 Person Walked via Person.link for non-conversational-only reverse-CR reason
6 CarePlan Schedule container; produced by PlanDefinition/$apply; carries subject, status, timezone extension
7 PlanDefinition Care-plan template; canonical URLs pinned via plan-slot extension
8 Library CQL library for clinical-routing $apply evaluation
9 Task Single questionnaire dispatch; carries for, status, businessStatus, output, restriction.period, delivery-time extension
10 Questionnaire Form definition; carries SDC extraction annotations, constraints, itemMedia, translation, ordinalValue
11 QuestionnaireResponse Patient's answers; status, questionnaire, subject, authored, nested item[]
12 CommunicationRequest Contract surface between projo and sidecar/web; medium, category, status, recipient[], subject, about[], payload[], occurrencePeriod, basedOn, identifier[], many extensions
13 Communication Per-message audit + projection state; status, category, medium, partOf, sender, recipient[], payload[].contentString, identifier (wamid), sent/received
14 Provenance Extraction + routing idempotency anchor; meta.tag, target[], entity[0].what=QR, activity=DERIVE
15 Observation Extracted clinical fact (observation-based extraction); code = Questionnaire.item.code, derivedFrom → QR
16 Condition Extracted clinical fact (definition-based extraction); linked only via Provenance
17 MedicationStatement Extracted from treatment questionnaire items (definition-based)
18 Flag Raised by route-to-review bounded action when router cannot enroll
19 Subscription 4 managed Subscriptions wiring the apps together
20 SearchParameter Single custom SearchParameter: web-answer-token on CommunicationRequest
21 Binary Wraps JSON-Patch payload for Patient write-back; also FHIR Binary storage for media attachments
22 Parameters Used for $apply, $subscribe-due-events, $extract operation I/O
23 Bundle Transaction bundles for atomic writes (CR+Task, extraction, routing, inbound+reverseCR, inbound+answer)
24 Basic FHIR-capacity probe resource (measure-fhir-capacity CLI)
25 StructureDefinition Read side-effect during $extract (CQF-FHIR DAO needs it)
26 PractitionerRole Read/created by orchestrator tooling for admin role seeding
27 RequestGroup Returned by $apply (PlanDefinition output)

Reference graph

The resources are not a flat list; they form a directed reference graph rooted at Task (the dispatch) and CommunicationRequest (the contract between projo and the sidecar/web front-end). The links below are the ones the orchestrator writes and reads at runtime. References that are intentionally absent are called out separately in the following sections.

graph
    Task -- "for" --> Pt["Patient / Practitioner / RelatedPerson"]
    Task -- "basedOn" --> CarePlan
    Task -- "output: communication-request" --> CR["CommunicationRequest"]
    Task -- "output: questionnaire-response" --> QR["QuestionnaireResponse"]
    Task -- "input: valueReference" --> Q["Questionnaire"]

    CR -- "recipient" --> Pt
    CR -- "subject" --> Pt
    CR -- "about" --> Task
    CR -- "payload.contentReference (initial)" --> Q
    CR -- "payload.contentReference (appended)" --> QR
    CR -- "basedOn" --> ParentCR["Parent CommunicationRequest"]

    Comm["Communication"] -- "partOf" --> CR
    Comm -- "sender" --> Pt
    Comm -- "identifier: wamid" --> Wamid[("wamid")]

    QR -- "questionnaire" --> Q
    QR -- "subject" --> Pt

    Obs["Observation"] -- "code from Questionnaire.item.code" --> Q
    Obs -- "derivedFrom" --> QR
    Obs -- "identifier: extracted-resource" --> DetId[("deterministic id")]

    Prov["Provenance"] -- "entity.what" --> QR
    Prov -- "target" --> Extracted["Observation / Condition / MedicationStatement"]
    Prov -- "meta.tag" --> Tag[("extraction-provenance / routing-provenance")]

    Cond["Condition"] -.->|"linked only via Provenance"| Prov
    MedStat["MedicationStatement"] -.->|"linked only via Provenance"| Prov

    Flag -- "subject" --> Pt
    Flag -- "identifier: routing-resource" --> RoutingId[("deterministic id")]

    Pt -- "managingOrganization" --> Org["Organization"]
    Org -- "partOf" --> RootOrg["Root Organization"]

    Sub["Subscription"] -- "meta.tag: managed-subscription" --> SubTag[("managed-subscription")]

Reference index

  • Task.for → Patient/Practitioner/RelatedPerson
  • Task.basedOn → CarePlan
  • Task.output[] → CommunicationRequest (code=communication-request), QuestionnaireResponse (code=questionnaire-response)
  • Task.input[] → Questionnaire (valueReference)
  • CommunicationRequest.recipient[] → Patient/Practitioner/RelatedPerson (or identifier-only Reference)
  • CommunicationRequest.subject → same as Task.for
  • CommunicationRequest.about[] → Task
  • CommunicationRequest.payload[].contentReference → Questionnaire (initial), QuestionnaireResponse (appended by sidecar)
  • CommunicationRequest.basedOn → parent CommunicationRequest (for reminder CRs)
  • Communication.partOf[] → CommunicationRequest
  • Communication.sender → resolved Reference or identifier-only Reference
  • Communication.identifier → wamid
  • QuestionnaireResponse.questionnaire → Questionnaire canonical URL
  • QuestionnaireResponse.subject → Patient/Practitioner/RelatedPerson
  • QuestionnaireResponse.basedOnintentionally NOT set (HAPI-0931; link is on CR.payload)
  • Observation.codeQuestionnaire.item.code
  • Observation.derivedFrom → QuestionnaireResponse
  • Observation.identifierextracted-resource (deterministic)
  • Provenance.entity[0].what → QuestionnaireResponse (source)
  • Provenance.target[] → extracted resources or routing outputs
  • Provenance.meta.tagextraction-provenance|sdc-extraction or routing-provenance|clinical-routing
  • Flag.subject → Patient
  • Flag.identifierrouting-resource
  • Patient.managingOrganization → Organization
  • Organization.partOf → root Organization
  • Subscription.meta.tagmanaged-subscription code

CRUCIAL: QuestionnaireResponse.basedOn is NOT used

QuestionnaireResponse.basedOn looks like the obvious place to link a response back to the Task or CommunicationRequest that triggered it. It is not used in Projo, for two compounding reasons:

  1. FHIR R4 cardinality of QuestionnaireResponse.basedOn is Reference(CarePlan | ServiceRequest). A CommunicationRequest (or Task) is not a permitted target type at the R4 definition.
  2. HAPI enforces this and rejects writes that point basedOn at a disallowed resource type, surfacing as validation failure HAPI-0931. Any attempt to thread the CR↔QR link through basedOn is rejected at the server.

Instead, the CR↔QR link is carried on CommunicationRequest.payload[].contentReference: the initial payload points at the Questionnaire, and when the sidecar receives a completed QuestionnaireResponse it appends an additional payload entry whose contentReference points at that QuestionnaireResponse. The CR thus remains the single contract surface that knows about both the form and every answer that has come back against it.

The reverse direction — finding the CommunicationRequest that a Task produced, or conversely finding the Task that owns a given CommunicationRequest — is not searched via CommunicationRequest.about. Although Projo does populate CommunicationRequest.about with the Task reference for traceability, HAPI does not auto-index CommunicationRequest.about (tracked as HAPI-0524), so a search like CommunicationRequest?about=Task/123 does not return results reliably.

The load-bearing Task↔CR link is therefore carried on Task.output[] as an output parameter with code communication-request and a valueReference pointing at the CommunicationRequest. Queries that need to resolve the CR for a Task read Task.output directly; queries that need the reverse (CR → Task) read CommunicationRequest.about for the reference value but must not rely on it being indexed for search. This asymmetry is deliberate and is the reason the link is authored in both directions.