Clinia
How-to Guides

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 the warnings section for details).
  • summary.loadMs: time taken (in milliseconds) to generate the resolution
  • meta: pagination metadata for the facts array (total facts across all pages, the returned page, and the perPage size).
  • 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",
    "confidence": 0.9,
    "reasoning": "Both records share SNOMED code 44054006.",
    "sources": [
      { "origin": "Condition/abc", "type": "fhir", "reliability": 0.85 },
      { "origin": "observation/456", "type": "cda", "reliability": 0.8 }
    ],
    "conflicts": []
  }
}

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

ValueLayerWhat happened
source-ref-equality0Same source record re-ingested — identical origin reference
deterministic-code1Records share a standard code (SNOMED, RxNorm, ICD-10, LOINC)
nlp-normalization2Same concept with different display text or minor variation
embedding-similarity3Semantically similar with no shared code
llm-adjudication4Ambiguous case escalated to the LLM for clinical judgment
novel-entitynoneRecord was not merged with any other. 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

StrategyWhen used
temporal-precedencePrefer the most recently dated source
corroborationMajority of sources agree on one value
source-hierarchyPrefer the more reliable source type
specificityPrefer the more specific or detailed value
llm-adjudicatedComplex 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:

  1. Do the records share a standard code? If not, deterministic matching (layer 1) cannot fire.
  2. Are the display names similar? Layer 2 uses NLP normalization, very different display text may not match.
  3. What was the embedding similarity score? Layer 3 fires only above the escalation threshold.
  4. Was it escalated to LLM? If max_llm_calls_per_patient was 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.

On this page