Healthcare 360 Intelligence
Commercial customer data lived in 20+ systems with no shared meaning. Every business question became a bespoke SQL request, and two teams could get two answers.
My role. Architect and hands-on lead — semantic model, entity resolution, Graph-RAG, governance
- source systems unified
- 20+source systems unified
- recurring ad-hoc requests replaced
- ~80%recurring ad-hoc requests replaced
- SHACL-validated before it reaches the graph
- Every loadSHACL-validated before it reaches the graph
- AWS Neptune
- RDF / OWL
- SHACL
- SPARQL
- Graph-RAG
- Entity resolution
- LLMs
Architecture reconstructed with synthetic data; proprietary implementation excluded.
01Problem
Commercial teams at a large pharmaceutical company needed to answer simple-sounding questions — which physicians in this territory engaged with this content, and what happened next? — and could not. Customer data lived in more than twenty systems. Every question became a bespoke SQL request to an analytics team, took days, and two teams asking the same question could get two different answers.
02Context
- Users: brand, field, market-access and analytics teams — people who act on an answer, not people who write queries.
- Constraint: a regulated environment. An answer nobody can trace to a source is not usable, however fast it arrives.
- Baseline: the recurring ad-hoc requests analysts were answering by hand. That is what the system had to replace, and it is what success was measured against.
03Data
Structured and unstructured data from CRM, offline engagement, web analytics and other commercial systems — 20+ sources. The hard part is identity: the same physician or hospital carries a different identifier in every system, with typos, missing national IDs, and organisations that change names.
| Source type | Typical problem | |---|---| | CRM | the most trusted record, but incomplete for digital channels | | Offline engagement | free-text names, no national identifier | | Web analytics | email or cookie only, no clinical attributes |
04Architecture
- CRMEngagementWebOffline
- Entity resolutionblock → match → cluster → survivorship
- Mapping to ontologyRDF / OWL classes and properties
- SHACL gatenon-conforming records rejected with a reason
- Knowledge graphAWS Neptune
- Graph-RAG servicesecure API · access control per use case
And the retrieval path for a single question — every step is inspectable, which is what makes the answer defensible:
- Question“Which cardiologists in the North territory saw the new dosing content?”
- Entity detectionspecialty · territory · content asset
- Hybrid retrievalontology terms + text match + embeddings
- Graph traversalgoverned query over defined relationships
- Context assemblyrows + source record IDs
- LLM answerconstrained to the assembled context
- Citation checkevery claim maps to a returned record
05What I built
- The semantic model: the classes, relationships and constraints that define a commercial customer.
- Entity-resolution pipelines across CRM, offline engagement and web analytics — deterministic rules, fuzzy and embedding similarity, confidence scores, survivorship.
- The Graph-RAG assistant, from use-case definition through retrieval-quality validation, exposed through secure API and model-serving endpoints.
- Access and governance controls, and the adoption and scaling recommendation to leadership.
The public companion repository reconstructs the ingestion gate on synthetic data. This is the shape that keeps a malformed physician record out of the graph:
# An HCP must have exactly one specialty, a segment from the controlled list, and
# at least one affiliation — the conditions that keep the graph queryable.
:HCPShape a sh:NodeShape ;
sh:targetClass :HCP ;
sh:property [ sh:path :specialty ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ] ;
sh:property [ sh:path :segment ; sh:minCount 1 ; sh:maxCount 1 ;
sh:in ( "EarlyAdopter" "GrowthPotential" "Established" ) ] ;
sh:property [ sh:path :affiliatedWith ; sh:minCount 1 ] .
# Content is only servable when its MLR status is a recognised value.
:ContentShape a sh:NodeShape ;
sh:targetClass :ContentModule ;
sh:property [ sh:path :mlrStatus ; sh:minCount 1 ;
sh:in ( "Approved" "InReview" "Expired" ) ] .06Modeling / AI approach
Entity resolution decides the ceiling of everything downstream. Deterministic identifiers win when both records have one; otherwise a surname match is never enough on its own — a first name and one corroborating field must agree. From the public rebuild:
def is_match(a, b):
# deterministic identifiers decide when both records carry one
if a["npi"] and b["npi"]:
return a["npi"] == b["npi"] # equal -> match; differ -> not
if a["email"] and b["email"]:
return a["email"] == b["email"] # a shared email is a strong match
# probabilistic: surname must agree, BOTH first names must be present and agree
# (surname alone is never enough), and one more field must corroborate.
if fuzz.ratio(a["last"], b["last"]) < 88:
return False
fa, fb = a["first"], b["first"]
if not fa or not fb:
return False
first_ok = (fa == fb or (fa[0] == fb[0] and (len(fa) == 1 or len(fb) == 1))
or fuzz.ratio(fa, fb) >= 78)
if not first_ok:
return False
return ((a["city"] and a["city"] == b["city"])
or (a["org"] and fuzz.token_sort_ratio(a["org"], b["org"]) >= 80))The language model never queries raw tables. It is given the ontology's vocabulary, produces a query over defined relationships, and writes its answer only from rows the graph returned. A synthetic example of the kind of query it produces:
◇ Illustrative · synthetic example, not production code
PREFIX c: <https://example.org/commercial#>
SELECT ?hcp ?name ?engagedOn ?source
WHERE {
?hcp a c:HCP ;
c:specialty "Cardiology" ;
c:inTerritory c:North ;
c:name ?name .
?e a c:Engagement ;
c:withHCP ?hcp ;
c:usedContent c:DosingDeck_v3 ;
c:date ?engagedOn ;
c:sourceRecord ?source . # carried through to the citation
}
ORDER BY DESC(?engagedOn)
LIMIT 5007Evaluation
- Data quality: SHACL shapes run on every load; a record that fails is rejected with the violated constraint, not silently dropped.
- Entity resolution: scored pairwise against labelled ground truth. The public rebuild reaches precision ~0.93, recall ~0.84, F1 ~0.88 on 220 synthetic entities — precision deliberately favoured, because merging two real physicians is the worse error.
- Retrieval quality: validated with business and analytics stakeholders on their real recurring questions before access widened.
- Citations: an answer whose claims do not map to returned records is treated as a failure, regardless of how fluent it is.
08Production considerations
- Access controls defined per use case before rollout, not after.
- Served through secure APIs and model-serving endpoints so other tools inherit the same governance.
- The ontology evolves as sources are added; the SHACL gate is what keeps existing questions answerable when it does.
09Results
- ~80% of recurring ad-hoc data requests replaced by the assistant.
- One governed definition of a commercial customer across 20+ systems.
- The graph became the foundation of a next-best-action design — eligibility as a deterministic graph rule, relevance as a ranker. Read the architecture →
10Tradeoffs / lessons
- Ontology first is slower in week one and faster by month three. Every later question reused the same definitions instead of re-arguing them.
- Precision over recall in entity resolution. A missed merge costs a duplicate row; a wrong merge sends one doctor's history to another.
- Citations are a product feature. The adoption conversation changed when a brand lead could click through to the source record.