Audit Entity Resolution
Read the resolution report to understand how entities from different sources were merged, what conflicts were found, and which resolver made each decision.
Audit Entity Resolution
The resolution report records every merge decision made by the 4-layer pipeline for a patient. Use it to understand why two records were (or were not) merged, what conflicts were found between sources, and how each conflict was resolved.
Fetch the report
curl "https://<workspace-id>.w.clinia.cloud/v1/patients/{patientId}/resolution?page=0&perPage=100" \
-H "Authorization: Bearer <access-token>"The facts list is paginated: page is zero-indexed (default 0) and perPage defaults to 100 (maximum 1000). Summary counts always cover the patient's full graph, not just the returned page.
Report structure
{
"summary": {
"totalFacts": 18,
"totalEvents": 11,
"mergedFacts": 3,
"warningCount": 1,
"loadMs": 840
},
"meta": { "total": 18, "page": 0, "perPage": 100 },
"facts": [ ... ],
"warnings": [ ... ]
}summary.totalFacts: total number of facts persisted for the patient after resolution.summary.totalEvents: total number of clinical events in the patient graph.summary.mergedFacts: number of facts carrying two or more contributing sources. If this is 0 after ingesting overlapping sources, every record from every source was treated as unique.summary.warningCount: number of warnings generated during the resolution process (see thewarningssection for details).summary.loadMs: time taken (in milliseconds) to generate the resolutionmeta: pagination metadata for thefactsarray (totalfacts across all pages, the returnedpage, and theperPagesize).facts: one entry per persisted fact on the requested page — every fact type, single- or multi-source — with its full provenance trace. Ordered by fact type, then display.warnings: facts or events that could not be processed cleanly.
Reading a fact
{
"id": "condition:snomed:44054006",
"type": "condition",
"display": "Type 2 Diabetes Mellitus",
"provenance": {
"resolvedBy": "deterministic-code-exact",
"confidence": 0.95,
"reasoning": "settled by deterministic-code-exact",
"sources": [
{ "origin": "Condition/abc", "type": "fhir", "reliability": 0.85 },
{ "origin": "observation/456", "type": "cda", "reliability": 0.8 }
],
"conflicts": []
}
}provenance is always present. A fact merged from several sources lists every contributing record under provenance.sources. A fact seen in only one source has a single entry there.
resolvedBy values
resolvedBy is the id of the stop rule that settled the decision, not the bare cascade layer name — several rules can attach to the same layer.
| Value | Layer | What happened |
|---|---|---|
source-ref-equality | 0 | Same source record re-ingested — identical origin reference |
deterministic-code-exact | 1 | Records share a standard code (SNOMED, RxNorm, ICD-10, LOINC) |
deterministic-code-hierarchical | 1 | Records share hierarchically related codes in the same system |
deterministic-code-mismatch | 1 | Different codes in a shared system — confident non-match |
nlp-normalization-match | 2 | Same concept with different display text or minor variation |
embedding-similarity | 3 | Semantically similar with no shared code |
llm-adjudication | 4 | Ambiguous case escalated to the LLM for clinical judgment |
cascade-exhausted-default-reject | none | Every layer escalated without a match — the weakest reject there is |
novel-entity | none | No pair was ever compared. Treated as a new fact |
Layer 4 fires for at most ~15 pairs per patient. Higher layers are more expensive; lower layer numbers are cheaper and faster.
Reading conflicts
When sources disagree on an attribute value, both values are recorded along with the resolution strategy:
{
"conflicts": [
{
"field": "status",
"values": [
{ "value": "active", "source": "Condition/abc" },
{ "value": "resolved", "source": "observation/456" }
],
"winner": "active",
"strategy": "temporal-precedence",
"explanation": "Condition/abc is more recent (2023-09-07 vs 2021-04-12)."
}
]
}Conflict resolution strategies
| Strategy | When used |
|---|---|
temporal-precedence | Prefer the most recently dated source |
corroboration | Majority of sources agree on one value |
source-hierarchy | Prefer the more reliable source type |
specificity | Prefer the more specific or detailed value |
llm-adjudicated | Complex conflict requiring clinical evaluation |
Reading warnings
{
"warnings": [
{
"source": "cda",
"id": "observation/789",
"message": "No recognised code system. Could not match by code.",
"severity": "low"
}
]
}Warnings are non-fatal. The fact is still ingested, but it could not be matched by the deterministic layer and may have been handled by a more expensive layer or left unmerged.
Debugging a non-merge
If you expected two records to merge but they didn't, you will see two separate facts in facts, each carrying a single source (typically resolvedBy: "novel-entity"), instead of one fact carrying both. Check:
- Do the records share a standard code? If not, deterministic matching (layer 1) cannot fire.
- Are the display names similar? Layer 2 uses NLP normalization, very different display text may not match.
- What was the embedding similarity score? Layer 3 fires only above the escalation threshold.
- Was it escalated to LLM? If
max_llm_calls_per_patientwas reached, remaining ambiguous pairs are left unmerged rather than burning more LLM budget.
See Entity Resolution for a full explanation of the 4-layer cascade.