Patient Journey

A patient journey is the journey a patient takes through a healthcare system. It could be for a particular condition, or over a lifetime.

Introduction

Patient journey data is inherently both highly heterogeneous and deeply interconnected, spanning clinical records, laboratory results, medications, imaging, lifestyle factors, and social determinants of health.

By definition, these diverse data points form a complex web of relationships, i.e. a patient’s dietary habits influence metabolic conditions, exercise patterns correlate with cardiovascular outcomes, and environmental exposures connect to respiratory diseases. Traditional relational databases struggle to capture and query these intricate, multi-dimensional connections efficiently, often requiring complex joins across dozens of tables.

Neo4j’s flexible, schema-optional graph database model excels in this domain by naturally representing patients, conditions, treatments, and lifestyle factors as nodes with relationships that mirror real-world clinical causality and correlation. This approach enables healthcare organizations to seamlessly integrate disparate data sources while maintaining the semantic richness essential for comprehensive patient journey analytics.

Scenario

In this scenario, we explore how Neo4j can be utilized to model and analyze several journeys through the healthcare system by different patients. This includes tracking interactions with various healthcare providers, diagnoses received, and treatments administered. It is by no means an exhaustive example, but rather a starting point to illustrate how Neo4j can be applied to patient journey use cases.

patient journey
Figure 1. An example of a Patient Journey

Solution

A patient journey tracks an individual’s complete healthcare experience across multiple touchpoints, providers, and treatments over time. Neo4j’s graph database excels at connecting fragmented patient data from disparate EHRs, lab results, prescriptions, and specialist visits into a unified, relationship-rich model.

This approach enables healthcare organizations to visualize complex care pathways, identify treatment gaps, understand how different interventions impact outcomes and optimise patient welfare.

Graph queries can in real time traverse a patient’s entire medical history to surface patterns like medication interactions, referral bottlenecks, or care coordination issues that traditional databases struggle to uncover. Ultimately, Neo4j empowers clinicians and administrators to deliver more personalized, continuous care while optimizing resource allocation across the healthcare network.

Modelling

Modelling a patient journey in Neo4j involves defining nodes and relationships that represent the key entities and interactions within a patient’s healthcare experience.

Data Model

Neo4j’s labelled property graph model naturally aligns with the OMOP Common Data Model by representing clinical concepts as nodes (Person, Condition, Drug, Procedure), their attributes as properties (dates, codes, values), and clinical events as relationships (HAS_CONDITION, PRESCRIBED_DRUG), enabling intuitive mapping of standardized observational health data while preserving the rich semantic connections between patients, diagnoses, treatments, and outcomes that OMOP vocabularies define.

Overview

This model captures a simplified patient journey, showing how it could be represented.

FIUC BLS PatientJourney Simple
Figure 2. Patient Journey model overview

Nodes

These nodes are used in the Patient Journey data model:

  • Patient - a unique individual receiving healthcare services.

  • Encounter - a specific interaction between a patient and the healthcare system, such as a hospital visit or outpatient appointment.

  • Observation - a clinical measurement or finding recorded during an encounter, such as vital signs or lab results.

  • Provider - a healthcare professional involved in the patient’s care, such as a doctor or nurse.

  • Speciality - a medical speciality associated with a provider, such as cardiology or general practice.

  • Organisation - a healthcare institution or facility, such as a hospital or clinic that a Provider is associated with.

  • Drug - a medication prescribed to a patient during an encounter. Drugs are uniquely identified by a code (e.g. SNOMED CT, RxNorm) in combination with dosage.

  • Condition - a medical diagnosis or health condition identified during an encounter.

Relationships

The below relationships are used in the Patient Journey data model:

  • HAS_ENCOUNTER - connects a Patient to their Encounters.

  • HAS_OBSERVATION - connects an Encounter to its recorded Observations

  • PRESCRIBED - connects an Encounter to the Drugs prescribed during that encounter.

  • DIAGNOSED - connects an Encounter to the Conditions diagnosed during that encounter.

  • ATTENDED_BY - connects an Encounter to the Provider who attended to the patient during that encounter.

  • HAS_SPECIALITY - connects a Provider to their Speciality.

  • BELONGS_TO - connects a Provider to the Organisation they are affiliated with.

  • NEXT - connects sequential Encounters in a Patient's journey.

Demo Data

This Cypher statement will create the example graph in the Neo4j database:

// MERGE patients
MERGE (pas:Patient {id: '987-223-1345', name: 'Alice Smith', birthDate: date('1975-03-22')})
MERGE (pct:Patient {id: '987-223-1346', name: 'Charlotte Tegreba', birthDate: date('1992-03-22')})
MERGE (paj:Patient {id: '987-783-1346', name: 'Alex Jerdaschl', birthDate: date('1986-11-14')})

// MERGE providers, specialities and organisation
MERGE (p1:Provider {id: 'prov-001', name: 'Dr. Emily Smith'})
MERGE (p2:Provider {id: 'prov-002', name: 'Dr. Anisha Jones'})
MERGE (p3:Provider {id: 'prov-078', name: 'Dr. Paul Vionnet'})

MERGE (sp1:Speciality {name: 'Cardiology'})
MERGE (sp2:Speciality {name: 'General Practice'})
MERGE (sp3:Speciality {name: 'Nephrology'})

MERGE (org1:Organisation {id: 'org-001', name: 'City Health Clinic', address: '123 Main St, Metropolis'})
MERGE (org2:Organisation {id: 'org-002', name: 'Centre for Kidney Health', address: '498 Main St, Metropolis'})

// MERGE provider relationships
MERGE (p1)-[:HAS_SPECIALITY]->(sp1)
MERGE (p2)-[:HAS_SPECIALITY]->(sp2)
MERGE (p3)-[:HAS_SPECIALITY]->(sp3)

MERGE (p1)-[:BELONGS_TO]->(org1)
MERGE (p2)-[:BELONGS_TO]->(org1)
MERGE (p3)-[:BELONGS_TO]->(org2)

//// Alice Smith encounters and data ////

// MERGE encounter 1 and associated data
MERGE (eas1:Encounter {id: 'enc-as75-001', date: date('2025-11-10'), type: 'Inpatient'})
MERGE (oas11:Observation {id: 'obs-as75-011', code: '8320-2', value: '175/80', unit: 'mmHg'})
MERGE (oas12:Observation {id: 'obs-as75-012', code: '29463-7', value: 68, unit: 'kg'})
MERGE (dras1:Drug {code:'386873009', name: 'Lisinopril', dosage: '10 mg', frequency: 'Once daily'})
MERGE (conas1:Condition {code: '38341003', description: 'Hypertensive disorder, systemic arterial (disorder)'})

// MERGE encounter 2 and associated data
MERGE (eas2:Encounter {id: 'enc-as75-002', date: date('2025-11-12'), type: 'Outpatient'})
MERGE (oas21:Observation {id: 'obs-as75-021', code: '8320-2', value: '140/80', unit: 'mmHg'})
MERGE (oas22:Observation {id: 'obs-as75-022', code: '29463-7', value: 68.1, unit: 'kg'})
MERGE (dras2:Drug {code:'386873009', name: 'Lisinopril', dosage: '10 mg', frequency: 'Once daily'})
MERGE (conas2:Condition {code: '44054006', description: 'Diabetes mellitus type 2 (disorder)'})

// MERGE encounter 3 and associated data
MERGE (eas3:Encounter {id: 'enc-as75-003', date: date('2025-11-24'), type: 'Outpatient'})
MERGE (oas31:Observation {id: 'obs-as75-031', code: '8320-2', value: '120/80', unit: 'mmHg'})
MERGE (oas32:Observation {id: 'obs-as75-032', code: '29463-7', value: 69.6, unit: 'kg'})

// MERGE encounter 1 relationships
MERGE (pas)-[:HAS_ENCOUNTER]->(eas1)
MERGE (eas1)-[:HAS_OBSERVATION]->(oas11)
MERGE (eas1)-[:HAS_OBSERVATION]->(oas12)
MERGE (eas1)-[:PRESCRIBED]->(dras1)
MERGE (eas1)-[:DIAGNOSED]->(conas1)
MERGE (eas1)-[:ATTENDED_BY]->(p1)

// MERGE encounter 2 relationships
MERGE (eas1)-[:NEXT]->(eas2)
MERGE (pas)-[:HAS_ENCOUNTER]->(eas2)
MERGE (eas2)-[:HAS_OBSERVATION]->(oas21)
MERGE (eas2)-[:HAS_OBSERVATION]->(oas22)
MERGE (eas2)-[:DIAGNOSED]->(conas1)
MERGE (eas2)-[:DIAGNOSED]->(conas2)
MERGE (eas2)-[:PRESCRIBED]->(dras2)
MERGE (eas2)-[:ATTENDED_BY]->(p2)

// MERGE encounter 3 relationships
MERGE (eas2)-[:NEXT]->(eas3)
MERGE (pas)-[:HAS_ENCOUNTER]->(eas3)
MERGE (eas3)-[:HAS_OBSERVATION]->(oas31)
MERGE (eas3)-[:HAS_OBSERVATION]->(oas32)
MERGE (eas3)-[:ATTENDED_BY]->(p2)

////  Charlotte Tegreba encounters and data ////

// MERGE encounter 1 and associated data
MERGE (ect1:Encounter {id: 'enc-ct92-001', date: date('2025-11-10'), type: 'Inpatient'})
MERGE (oct11:Observation {id: 'obs-ct92-011', code: '8320-2', value: '175/80', unit: 'mmHg'})
MERGE (oct12:Observation {id: 'obs-ct92-012', code: '29463-7', value: 68, unit: 'kg'})
MERGE (drct1:Drug {code:'386873009', name: 'Lisinopril', dosage: '10 mg', frequency: 'Once daily'})
MERGE (conct1:Condition {code: '38341003', description: 'Hypertensive disorder, systemic arterial (disorder)'})

// MERGE encounter 2 and associated data
MERGE (ect2:Encounter {id: 'enc-ct92-002', date: date('2025-11-12'), type: 'Outpatient'})
MERGE (oct21:Observation {id: 'obs-ct92-021', code: '8320-2', value: '140/80', unit: 'mmHg'})
MERGE (oct22:Observation {id: 'obs-ct92-022', code: '29463-7', value: 68.1, unit: 'kg'})
MERGE (drct2:Drug {code:'386873009', name: 'Lisinopril', dosage: '10 mg', frequency: 'Once daily'})
MERGE (conct2:Condition {code: '44054006', description: 'Diabetes mellitus type 2 (disorder)'})

// MERGE encounter 3 and associated data
MERGE (ect3:Encounter {id: 'enc-ct92-003', date: date('2025-11-24'), type: 'Outpatient'})
MERGE (oct31:Observation {id: 'obs-ct92-031', code: '8320-2', value: '120/80', unit: 'mmHg'})
MERGE (oct32:Observation {id: 'obs-ct92-032', code: '29463-7', value: 69.6, unit: 'kg'})

// MERGE encounter 1 relationships
MERGE (pct)-[:HAS_ENCOUNTER]->(ect1)
MERGE (ect1)-[:HAS_OBSERVATION]->(oct11)
MERGE (ect1)-[:HAS_OBSERVATION]->(oct12)
MERGE (ect1)-[:PRESCRIBED]->(drct1)
MERGE (ect1)-[:DIAGNOSED]->(conct1)
MERGE (ect1)-[:ATTENDED_BY]->(p1)

// MERGE encounter 2 relationships
MERGE (ect1)-[:NEXT]->(ect2)
MERGE (pct)-[:HAS_ENCOUNTER]->(ect2)
MERGE (ect2)-[:HAS_OBSERVATION]->(oct21)
MERGE (ect2)-[:HAS_OBSERVATION]->(oct22)
MERGE (ect2)-[:DIAGNOSED]->(coct1)
MERGE (ect2)-[:DIAGNOSED]->(coct2)
MERGE (ect2)-[:PRESCRIBED]->(dct2)
MERGE (ect2)-[:ATTENDED_BY]->(p2)

// MERGE encounter 3 relationships
MERGE (e2ct)-[:NEXT]->(ect3)
MERGE (pct)-[:HAS_ENCOUNTER]->(ect)
MERGE (ect3)-[:HAS_OBSERVATION]->(oct31)
MERGE (ect3)-[:HAS_OBSERVATION]->(oct32)
MERGE (ect3)-[:ATTENDED_BY]->(p2)


//// Alex Jerdaschl encounters and data ////

// MERGE encounter 1 and associated data
MERGE (eaj1:Encounter {id: 'enc-aj86-001', date: date('2025-11-10'), type: 'Inpatient'})
MERGE (oaj11:Observation {id: 'obs-aj86-011', code: '8320-2', value: '173/80', unit: 'mmHg'})
MERGE (oaj12:Observation {id: 'obs-aj86-012', code: '29463-7', value: 60, unit: 'kg'})
MERGE (draj1:Drug {code:'703674001', name: 'Dapagliflozin', dosage: '10 mg', frequency: 'Once daily'})
MERGE (conaj2:Condition {code: '38341003', description: 'Hypertensive disorder, systemic arterial (disorder)'})
MERGE (con3:Condition {code: '709044004', description: 'Chronic kidney disease (disorder)'})

// MERGE encounter 2 and associated data
MERGE (eaj2:Encounter {id: 'enc-aj86-002', date: date('2025-11-12'), type: 'Outpatient'})
MERGE (oaj21:Observation {id: 'obs-aj86-021', code: '8320-2', value: '140/80', unit: 'mmHg'})
MERGE (oaj22:Observation {id: 'obs-aj86-022', code: '29463-7', value: 68.1, unit: 'kg'})
MERGE (draj2:Drug {code:'386873009', name: 'Lisinopril', dosage: '10 mg', frequency: 'Once daily'})

// MERGE encounter 1 relationships
MERGE (paj)-[:HAS_ENCOUNTER]->(eaj1)
MERGE (eaj1)-[:HAS_OBSERVATION]->(oaj11)
MERGE (eaj1)-[:HAS_OBSERVATION]->(oaj12)
MERGE (eaj1)-[:PRESCRIBED]->(draj1)
MERGE (eaj1)-[:DIAGNOSED]->(conaj3)
MERGE (eaj1)-[:ATTENDED_BY]->(p1)

// MERGE encounter 2 relationships
MERGE (eaj1)-[:NEXT]->(eaj2)
MERGE (paj)-[:HAS_ENCOUNTER]->(eaj2)
MERGE (eaj2)-[:HAS_OBSERVATION]->(oaj21)
MERGE (eaj2)-[:HAS_OBSERVATION]->(oaj22)
MERGE (eaj2)-[:DIAGNOSED]->(conaj3)
MERGE (eaj2)-[:DIAGNOSED]->(conaj2)
MERGE (eaj2)-[:PRESCRIBED]->(draj2)
MERGE (eaj2)-[:ATTENDED_BY]->(p3)

Schema

If you call:

// Show neo4j schema
CALL db.schema.visualization()

You will see the following response:

8 nodes connected with relationships
Figure 3. The Schema of the Patient Journey model

Cypher Queries

This section provides a set of example Cypher queries that can be used to explore the patient journey graph from different perspectives.

Patient based queries

These queries are focused on retrieving information from a patient-centric perspective.

Given a Patient what Drugs were PRESCRIBED?

This query gets you every drug prescribed to the patient with ID 987-223-1345 across their entire journey.

// Get all the drugs a patient has been prescribed
MATCH (p:Patient {id: '987-223-1345'})-[:HAS_ENCOUNTER]->(:Encounter)-[:PRESCRIBED]->(d:Drug)
RETURN p.name AS Name, collect(DISTINCT d.name) AS `Prescribed Drugs`;

Given a Patient what Conditions were DIAGNOSED?

For the patient with ID 987-223-1345, this query retrieves all the conditions they have been diagnosed with during their healthcare journey.

// Get all the conditions a patient has been diagnosed with
MATCH (p:Patient {id: '987-223-1345'})-[:HAS_ENCOUNTER]->(:Encounter)-[:DIAGNOSED]->(c:Condition)
RETURN p.name AS Name, collect(DISTINCT c.description) AS conditions;

Given a Patient and their Condition - what are the comorbidities of other Patients with the same Condition?

In this query, we want to find out all the comorbidities for the conditions that other patients have. As a healthcare provider, this information could prove useful to be able to target potential investigations or treatments.

MATCH
    (p:Patient {id: '987-223-1345'})-[:HAS_ENCOUNTER]->(:Encounter)-[:DIAGNOSED]->(c:Condition),
    (c)<-[:DIAGNOSED]-(:Encounter)<-[:HAS_ENCOUNTER]-(other:Patient)-[:HAS_ENCOUNTER]->(:Encounter)-[:DIAGNOSED]->(c2:Condition)
WHERE c <> c2
RETURN c2.code AS Code, c2.description AS Description, count(DISTINCT other) AS PatientCount
ORDER BY PatientCount DESC;

Condition based queries

These queries are focused on retrieving information from a condition-centric perspective.

What comorbidities are associated with a particular Condition?

Here we’re getting all the comorbidities associated with a particular condition. This could be useful for clinical decision support systems to identify common co-occurring conditions. By ordering by occurrences, we can see which comorbidities are most frequently associated with the given condition.

// What are the comorbidities associated with particular `Condition`?
MATCH (c1:Condition {code: '38341003'})<-[:DIAGNOSED]-(:Encounter)-[:DIAGNOSED]->(c2:Condition)
WHERE c1 <> c2
RETURN c2.code AS Code, c2.description AS Description, count(*) AS Occurrences
ORDER BY Occurrences DESC;

Provider based queries

These queries are focused on retrieving information from a provider-centric perspective.

Given a Provider what Patients have they seen, and how often?

This query allows a healthcare organization to analyze provider workloads and patient distribution by identifying which patients a specific provider has seen and the frequency of those encounters.

//* Given a provider what patients have they seen?
MATCH (p:Provider {id: 'prov-002'})<-[:ATTENDED_BY]-(:Encounter)<-[:HAS_ENCOUNTER]-(pt:Patient)
RETURN pt.id AS PatientId, pt.name AS `Patient Name`, count(*) AS Encounters
ORDER BY Encounters DESC;

Given a Provider what Conditions have they DIAGNOSED?

This would be useful to understand the case mix a provider is handling, which can inform resource allocation, training needs, and quality improvement initiatives.

// Given a provider what diagnoses have they made?
MATCH (p:Provider {id: 'prov-002'})<-[:ATTENDED_BY]-(:Encounter)-[:DIAGNOSED]->(c:Condition)
RETURN c.code AS Code, c.description AS Description, count(*) AS Diagnoses
ORDER BY Diagnoses DESC;

Given a Provider what Drugs have they PRESCRIBED?

This can be used to see if a given provider is following prescribing guidelines, or to identify patterns in medication use that may warrant further investigation.

// Given a provider what drugs have they prescribed?
MATCH (p:Provider {id: 'prov-002'})<-[:ATTENDED_BY]-(:Encounter)-[:PRESCRIBED]->(d:Drug)
RETURN d.code AS Code, d.name AS Name, count(*) AS Prescriptions

What are the providers for a given Patient?

Has a patient had a consistent care provider, or have they seen many different providers? This query helps to identify all the healthcare providers that a specific patient has interacted with during their healthcare journey, and can be useful for care coordination and understanding the patient’s care network.

// Get the names of the providers a patient has seen
MATCH (pat:Patient {id: '987-223-1345'})-[:HAS_ENCOUNTER]->(:Encounter)-[:ATTENDED_BY]->(provider:Provider)
RETURN provider.name AS Provider

Graph Data Science (GDS)

Neo4j’s Graph Data Science library enables sophisticated patient journey analytics using the information of the relationships.

  • Community detection algorithms identify sub-phenotypes of patients by clustering individuals with similar clinical trajectories, comorbidities, and treatment responses.

  • Centrality algorithms like PageRank and Degree Centrality can highlight critical entities within a patient’s medical history such as; severe allergies, adverse drug reactions, or key diagnoses—that disproportionately influence care decisions and outcomes.

  • Path finding algorithms can reveal optimal treatment sequences by analysing successful care pathways across similar patient cohorts, while Similarity algorithms identify patients with comparable conditions for comparative effectiveness research.

  • Link prediction capabilities can forecast potential complications or disease progressions based on relationship patterns observed in historical patient data, enabling proactive intervention strategies.

Real World Examples

It’s not just theory, there are real world examples of Neo4j being used to analyse patient journeys.

AstraZeneca

AstraZeneca is using Neo4j to improve patient outcomes by analysing patient journeys.

Improving Patient Outcomes with Graph Algorithms

Improving Patient Outcomes with Graph Algorithms

There is an accompanying blog post from the presentation.