Slide 1

Slide 1 text

How we built Care Plans A case study in building a principled healthcare domain service Rahul Goma Phulore Monitor, Babylon Health 2020.12.02

Slide 2

Slide 2 text

A bit about me - Senior backend engineer in the Monitor tribe. - ~10 years experience. - Past: ThoughtWorks, SoundCloud, eBay, two small startups. - Background in programming languages/systems, type systems, human computer interaction.

Slide 3

Slide 3 text

Project background - A care plan is a living object shared between a patient, their authorised family members, care teams, clinicians etc. - A care plan is co-created by and collaborated upon by several different actors. - To start with, it will contain health goals patients can achieve through self-management, and actions that will help them take there. - Hypothesis: The improved visibility/locality, improved patient participation and personalisation will lead to greater adherence from patients, and therefore to better health outcomes.

Slide 4

Slide 4 text

Governing principles for our work - Embrace DDD. Establish a shared understanding of the domain among all stakeholders. - Balance agile development with solid deliberate design. - Build with interoperability as a first-class goal. It shouldn’t be an afterthought. - Greenfield project – Use as a vehicle for experimentation and innovation.

Slide 5

Slide 5 text

Shared understanding - Early and continuous involvement of SMEs (subject matter experts) and other key stakeholders. - Clinicians. - Clinical safety folks. - Product managers. - Data tribe. - Collaborate on building a shared understanding of the domain (problem space + solution space) with SMEs. - Capture the understanding as tangible evolving artefacts. - Adhere to DDD (domain-driven design) principles. - Ubiquitous Language, Aggregates, Bounded Contexts.

Slide 6

Slide 6 text

Domain ontology

Slide 7

Slide 7 text

Domain ontology - What we are building for the MVP:

Slide 8

Slide 8 text

Some orthogonal requirements - Care plans are living and evolving artefacts. They comprise many interconnected objects of many different entities evolving at their own pace. - Care plans are co-created and collaborated upon by patients, clinicians, care teams. - Clinicians build care plans in an interactive UI. - We need access to entire care plan graphs as of any point in time.

Slide 9

Slide 9 text

Git-like workflow

Slide 10

Slide 10 text

Git-like workflow

Slide 11

Slide 11 text

Engineering challenge - Metamodel: a model with which to model your model.

Slide 12

Slide 12 text

Engineering challenge - Can we come up with a metamodel which scales with the domain ontology, requiring minimal work per added model? - The features that should scale up: - Retrieving the latest view of the whole of, or a subset of, the object graph. - Retrieving the state of the whole of, or a subset of, the object graph, as of any point in time. - Interactive editing. - Basic revision control. - Persistence layer mapping. - API representations.

Slide 13

Slide 13 text

The metamodel Entity Draft Revision₁₃ Revision₁ Revision₁₂ Revision₁₁ ... Create a draft Promote a draft

Slide 14

Slide 14 text

Modelling with a pseudo-PL - The rub with the reification: The incidental is a part of the picture, and can distract from the essential. - With a pseudo-PL, you can focus on the essence, imagining away any incidental elements. - You could imagine a combination of syntactic constructs, abstraction mechanisms, type system features that no real PLs have. Imagination is the limit.

Slide 15

Slide 15 text

Modelling with a pseudo-PL Model: CarePlan = @revisionable record { name: Text goals: Seq Goal } Goal = @revisionable record { description: Text }

Slide 16

Slide 16 text

Modelling with a pseudo-PL Metamodel: Frame base = record { id: Id base, revisionId: RevisionId base, entityCreatedAt: OffsetDateTime, revisionCreatedAt: OffsetDateTime, } ++ base.map( case field when field.isRevisionable -> (field.name : Frame field.type) case field -> (field.name : field.type) )

Slide 17

Slide 17 text

Modelling with a pseudo-PL Metamodel: Draft base = record { draftId: DraftId base, upstreamRevisionId: Optional (RevisionId base), createdAt: OffsetDateTime, updatedAt: OffsetDateTime } ++ base.map( case field when field.isRevisionable -> (field.name : Draft field.type) case field -> (field.name : Unfinished field.type) )

Slide 18

Slide 18 text

Modelling with a pseudo-PL Metamodel: Unfinished: Type -> Type Unfinished Text = Optional String Unfinished Foo = Bar Unfinished other = typeError "No unfinished state known for $other"

Slide 19

Slide 19 text

Modelling with a pseudo-PL Application: carePlanDraft: Draft CarePlan carePlanDraft = { draftId = “xxx-yyy-zzz”, upstreamRevisionId = .Present “abc”, createdAt = 2020-11-30T22:50:59+0000, updatedAt = 2020-11-30T22:50:59+0000, goals = Seq [goalDraft1, goalDraft2] }

Slide 20

Slide 20 text

Modelling with a pseudo-PL Application: goalDraft1: Draft Goal goalDraft1 = { draftId = “g1”, upstreamRevisionId = .Present “g0”, createdAt = 2020-11-30T22:50:59+0000, updatedAt = 2020-11-30T22:50:59+0000, description = .Absent }

Slide 21

Slide 21 text

Modelling with a pseudo-PL Application: goalDraft2: Draft Goal goalDraft2 = { draftId = “g2”, upstreamRevisionId = .Absent, createdAt = 2020-11-30T22:50:59+0000, updatedAt = 2020-11-30T22:50:59+0000, description = .Present “Lose weight by 5kg.” }

Slide 22

Slide 22 text

Modelling with a pseudo-PL Encodings in some real PLs: https://gist.github.com/missingfaktor/4e3e5216e5b85da5ebe1702c5bfa5955

Slide 23

Slide 23 text

Persistence layer mapping - We need temporal + graph semantics. (A limited version thereof.) - We use PostgreSQL as a “database engine”, and implement these semantics on top. - This can be thought of as us “greenspunning” databases that support such semantics natively. e.g. IrminDB, Datomic, Crux etc.

Slide 24

Slide 24 text

Persistence layer mapping - Each revisionable entity has 2 tables associated with it: - Entity - Revision - Together they build frames. - Revisions are immutable. Updates are made by inserting new revisions. Old revisions remain available for access. - Since different entities evolve at different paces, associations are stored in separate tables.

Slide 25

Slide 25 text

- Imagine A references B and C. We say that that makes A “dependent” on B and C. - We represent with this data with these records: - AE, BE, CE, A0, B0, C0, A0-B0, A0-C0. - When B is updated, A must be too. The new records will look like: - AE, BE, CE, A1, B1, C0, A1-B1, A1-C0. Persistence layer mapping A C B

Slide 26

Slide 26 text

- We decided to use GraphQL. - Provides a rich substrate for exposing our semantic universe. - Typed and introspectable APIs. - Excellent tooling. - Different metamodel applications are modeled as different concrete types in the GraphQL layer. - We innovated some patterns for using GraphQL with Java which could be leveraged in other projects. API layer

Slide 27

Slide 27 text

- The data model is inspired by FHIR, but isn’t quite FHIR. - FHIR is complex. - Adopting it as a primary modelling tool is difficult. Requires extensive use of advanced techniques such as: - BasicObject refinement. - Extension points. - Profiling. - It has to be a company-wide decision with willingness to invest in education, review process, tooling. Interoperability

Slide 28

Slide 28 text

- Assuming we don’t use advanced FHIR modelling techniques, it’s safe to assume that entities in the “Babylon universe” (B) and “FHIR universe” (F) are not isomorphic. - 3 levels of lossiness: - Not all B entity types map to F entity types. - For the B entity types that map to F entity types, not all B entities map to F entities. - For the B entities that map to F entities, not all B attributes map to F attributes. Interoperability

Slide 29

Slide 29 text

Interoperability Care Plans Domain Service Care Plans Projector Domain Event DFHIR Event Hydrant

Slide 30

Slide 30 text

- Bleeding edge, well, bleeds. Tools have rough edges. Patterns aren’t established yet. - You hit abstraction walls pretty quickly with Java and Spring. A lot of boilerplate. Not quite plug and play. - GraphQL type system has quite a few rough edges too. It’s a relatively young protocol that’s still evolving. - Sophistication comes at a price: Usually a higher initial learning curve. (But we believe the ROI justifies the cost.) Challenges

Slide 31

Slide 31 text

- The domain ontology will grow. Richer goal and action types, prescriptions, condition templates, progress tracking, and more coming. - Integration with other Monitor/Babylon features such as food swaps. - Pessimistic locking is too aggressive. We need to allow for concurrent modes of manipulation. - Improve the innards. Future Directions

Slide 32

Slide 32 text

Thank You! 💜💜💜

Slide 33

Slide 33 text

Bonus Slides! 󰛾🎄󰖠

Slide 34

Slide 34 text

- Map the entities to UNIX style plain text files and directories structure. - By making sure no line of text has multiple data, diffs can be made to work at semantically richer layers. - References will be weak, and stored in separate plain text files. - Store this in an S3 directory per patient. - S3 has versioning built into it. - You could even initialise a Git repository in the root patient directory. - Major downside: Queryability suffers. S3 or Git don’t have a semantic understanding of your data, so complex queries become impractical. Alternate persistence layer mapping with S3 + Git

Slide 35

Slide 35 text

- The current implementation has pessimistic locks on care plans. - Any one clinician can be in a “modification session” at any given time. Patients and other actors currently cannot modify care plans. - Future directions: - Keep the patient-modifiable fields (P) strictly separate from clinician-modifiable fields (C). P changes are instantaneous. C changes happen through drafts. P changes can be ported back instantly to C drafts. (Analog: rebasing master onto another branch.) - Implement rudimentary conflict resolution. Not too difficult with our metamodel. It will need a dedicated UI though. Pessimistic locking is too aggressive

Slide 36

Slide 36 text

- Draft data types permit “unfinished” states. - The GraphQL API provides operations for incrementally and atomically modifying/constructing objects. - GraphQL supports batching out of the box. Needs some tweaks for supporting referring to a result of operation(N) in operation(N + 1). Interactive editing

Slide 37

Slide 37 text

- Revision control systems primarily come in two flavours: - Those storing snapshots. Patches are generated on the fly. e.g. Git, Mercurial. - Those storing patches. Concrete views are built by applying patches. e.g. Pijul, Darcs. - We chose to go with the snapshot model because we need access to full objects as of any revision much more frequently (both in contexts and time) than we need the diffs between them. Patch vs Snapshot model