Using the PatientProgram was more of looking for the closest part of our model that could map to EOC. So it was clearly not the ideal. That is why, as you pointed out, we do not support mapping from FHIR EOC to the OpenMRS model.
Thanks @dkayiwa .. We are working on model expansion. We will propose a model and once we have some working version, we will showcase. If acceptable, we will look forward to inclusion as part of the core.
The above should provide a good idea. The source code of the above is here
The following is the suggestion for the enhanced model inline with FHIR Episode of Care. Note, R4 does not have reasons (associated to a list of clinical reasons - condition, service etc) but R6 has them. Therefore, we plan to do so as extension to R4 EOC. Also, EOC can be PLANNED for a patient, hence we introduce a ref to patient and also introduced status.
As explained above, we may store case_id, account info and other attributes for Episode Of care, and hence the reason to extend using attribute model. We are thinking of storing case_id/insurance_id, account info as attribute. In FHIR Episode of Care, we plan to model all these as extension. This is the proposed model for the enhanced EOC
not particularly, it was attempt to keep inline with FHIR spec (R6). Although I am not sure if removal is a common scenario. We can introduce voided in episode_reason.
Btw, there is also an associated model to keep the status history of EOC. Opinions are sort of divided, but I see no harm in introducing that.
(btw, I think discourse (talk) has plugins to show mermaid diagrams, I am not sure if OMRS talk has the plugin enabled)
flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
Create a block like:
```mermaid
flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
```
Should EOC map directly to encounters or to visits? I know FHIR has this as Encounter, but FHIR Encounters also encompass OMRS visits.
I think one other attribute that may be worth considering supporting is the careManager. It can be optional but seems worth having in the data model as the person âprimary responsibleâ for the EOC.
Incidentally, we want to have the â_revIncludeâ as part of the FHIR episode of care retrieval so that we can get list of encounters. Unfortunately, the current framework of SearchQueryInclude forces to me to retrieve using another BundleProvider implementation, which can cause
ân+1â query - effecting performance (especially when its by UUID and not primary key)
Encounter itself does not have entity mapping to Episode, meaning that I will have to override the SearchQueryIncludeImpl to handle that. Not a problem by itself, other than the additional queries mentioned above. Right now, our retrieval pattern is by a particular episode of care - so maybe thats ok.
I already have the references to the Encounter entity at the time of translation episodeTranslator.toFhirResource() from the episode.encounters - but I canât include them to the resulting bundle. One option is to add them as episodeOfCare.included resources if I were to do from episodeTranslator.toFhirResource(). but that violates the bundle â_revIncludeâ expectations. Maybe first cut, I would do the simple overriding of SearchQueryIncludeImpl and handle the _revInclude there.
My opinion: Will leave that to FHIR implementation (only).
Episode is in different module and not part of OpenMRS core, where visit and encounter entities are defined.
In the FHIR2 module (if it will have dependency to openmrs episode module), FhirEncounterService can have a postProcess method to handle association to EpisodeOfCare to encounter or visit.
If a visit is already associated to the episode (episode_visit), then the postProcess can subsequently associate any encounter belonging to that visit also to the episode. If visit is not associated with the EpisodeOfCare, then its associates only with encounter (episode_encounter).
Maybe as first cut, alternatively, we only associate episodeOfCare to visit or encounter if encounter.episodeOfCare is specified. And in that case (first cut), it purely depends on the implementing UX to pass on the episodeOfCare ref as part of encounter.
@dkayiwa about the voided attributes. FHIR does not have anything like a change record like behavior. However, we can interpret that when episodeOfCare is updated with a new set of âresaonsâ, mismatches get voided. However issue is that we wouldnât know the âvoided_reasonâ.
I can keep the field to be consistent with other OMRS models but other than an extension, I donât see how that can be implemented without a FHIR extension. (btw, episodeOfCare.reason would already an extension, atleast till R6)
In these cases, we usually just set the void_reason to some fixed string. E.g., thatâs what happens when we replace an obs with another value. It makes the field less valuable, but at least it can document which process itâs part of âVoided due to user removal from reasonsâ or something like that.
Technically, I donât think you really lose anything. Because you can always derive the visit from the encounter association. AFAIK, Openmrs does not demand that you must have a Visit before you create an Encounter.
However, I think it does introduce some bit of inconvenience for the implementing UX solution.
If a visit is associated with Episode of care (EOC), this is one time activity. Subsequently if any encounter is created within that visit, you donât have to âaskâ the end user to select an EOC. Implementing UX, can check whats the EOC context of the visit, and if EOC is already associated with the visit, then all encounters within the visit will be linked to the EOC as well.
E.g. in Bahmniâs case, in earlier implementation, we would start a EOC for a program enrollment. and we would have to determine if the program is associated with an EOC, and if yes, then any encounter done in context of program will be linked with the same EOC.
With the episode_visit introduced:
In FHIR, when the specified encounter is submitted, we can check if the encounterâs visit (encounter.partOf) is associated with an EOC, and if so, automatically associate in episode_encounter
If encounter.episodeOfCare is specified during submission, then its straight forward. Irrespective of whether encounter is in context of a visit or not, just associate the same in episode_encounter. You may argue that there is some potential of inconsistency, that what if encounter.visit is already part of EOC1, but the specified encounter.episodeOfCare is EOC2. Well, I would argue that second case overrides. (instead of throwing an error)
btw, our initial hack idea was to store EOC id as visit attribute. On submission of encounter, we could potentially check if EOC is already associated with the encounterâs visit, and then automatically populate episode_encounter. Its not a hack hack, its custom but not standard!
Please check the following diagram. I am not having enough time, but I have a basic FHIR APIs implented with GET (with revInclude Encounter:episodeOfCare), POST, PUT, DELETE etc. Not returning attributes as extensions as mentioned above yet.
erDiagram
episode ||--o{ episode_encounter : "has"
episode ||--o{ episode_patient_program : "has"
episode ||--o{ episode_visit : "has"
episode ||--o{ episode_reason : "has"
episode ||--o{ episode_attribute : "has"
episode ||--o{ episode_status_history : "tracks"
episode_attribute_type ||--o{ episode_attribute : "defines"
%% External table relationships
encounter ||--o{ episode_encounter : "linked to"
patient_program ||--o{ episode_patient_program : "linked to"
visit ||--o{ episode_visit : "linked to"
concept ||--o{ episode_reason : "defines use"
concept ||--o{ episode_reason : "defines value"
concept ||--o{ episode : "defines type"
patient ||--o{ episode : "has"
users ||--o{ episode : "manages/creates/modifies"
users ||--o{ episode_reason : "creates/voids"
users ||--o{ episode_attribute_type : "creates/modifies/retires"
users ||--o{ episode_attribute : "creates/modifies/voids"
users ||--o{ episode_status_history : "creates"
episode {
int episode_id PK
int patient_id FK "nullable"
int concept_id FK "episode type, nullable"
varchar status "default: UNKNOWN, nullable"
datetime date_started "nullable"
datetime date_ended "nullable"
int care_manager FK "nullable"
int creator FK "nullable"
datetime date_created "NOT NULL"
int changed_by FK "nullable"
datetime date_changed "nullable"
boolean voided "default: false, NOT NULL"
int voided_by FK "nullable"
datetime date_voided "nullable"
varchar void_reason "nullable"
char uuid UK "NOT NULL"
}
episode_encounter {
int episode_id FK "NOT NULL"
int encounter_id FK "NOT NULL"
}
episode_patient_program {
int episode_id FK "NOT NULL"
int patient_program_id FK "NOT NULL"
}
episode_visit {
int episode_id FK "NOT NULL"
int visit_id FK "NOT NULL"
}
episode_reason {
int episode_reason_id PK
int episode_id FK "NOT NULL"
int use_concept_id FK "nullable"
int value_concept_id FK "NOT NULL"
varchar value_reference "nullable"
int creator FK "nullable"
datetime date_created "NOT NULL"
boolean voided "default: false, NOT NULL"
int voided_by FK "nullable"
datetime date_voided "nullable"
varchar void_reason "nullable"
char uuid UK "NOT NULL"
}
episode_attribute_type {
int episode_attribute_type_id PK
varchar name "NOT NULL"
varchar description "nullable"
varchar datatype "nullable"
text datatype_config "nullable"
varchar preferred_handler "nullable"
text handler_config "nullable"
int min_occurs "NOT NULL"
int max_occurs "nullable"
int creator FK "NOT NULL"
datetime date_created "NOT NULL"
int changed_by FK "nullable"
datetime date_changed "nullable"
boolean retired "default: false, NOT NULL"
int retired_by FK "nullable"
datetime date_retired "nullable"
varchar retire_reason "nullable"
char uuid UK "NOT NULL, UNIQUE"
}
episode_attribute {
int episode_attribute_id PK
int episode_id FK "NOT NULL"
int attribute_type_id FK "NOT NULL"
text value_reference "NOT NULL"
int creator FK "NOT NULL"
datetime date_created "NOT NULL"
int changed_by FK "nullable"
datetime date_changed "nullable"
boolean voided "default: false, NOT NULL"
int voided_by FK "nullable"
datetime date_voided "nullable"
varchar void_reason "nullable"
char uuid UK "NOT NULL, UNIQUE"
}
episode_status_history {
int episode_status_history_id PK
int episode_id FK "NOT NULL"
varchar status "default: UNKNOWN, NOT NULL"
datetime date_started "NOT NULL"
datetime date_ended "nullable"
int creator FK "nullable"
datetime date_created "NOT NULL"
char uuid "NOT NULL"
}
%% External tables (OpenMRS Core)
encounter {
int encounter_id PK
}
patient_program {
int patient_program_id PK
}
visit {
int visit_id PK
}
concept {
int concept_id PK
}
patient {
int patient_id PK
}
users {
int user_id PK
}
Few comments about episodeOfCare.reason, I want to start with a simple concept reference as value (episode_reason.value_concept_id) - but it can also have a reason use. As per the spec, it should be something like
I have used a flattened db model for capturing reason including use (episode_reason.use_concept_id) and value (episode_reason.value_concept_id, episode_reason.value_reference). Otherwise, we get too nested db models inside. I would imagine a programatic grouping of values against the reason âuseâ should be sufficient.