# Multi-Source EHR Import **Date:** 2026-03-27 **Status:** Current --- ## Table of Contents 1. [The Problem](#1-the-problem) 2. [The Solution](#2-the-solution) 3. [Step-by-Step Workflow](#3-step-by-step-workflow) 4. [Handling Conflicts](#4-handling-conflicts) 5. [Expected Results](#5-expected-results) 6. [CI Integration](#6-ci-integration) --- ## 1. The Problem When a patient retrieves their health records from multiple EHR systems, the resulting exports contain heavily overlapping data. For example, importing 31 MyChart documents from a single patient's history may contain 619 immunization records, when the patient's actual immunization count is approximately 25. The same pattern applies to medications, conditions, lab results, and vital signs. This happens for several reasons: - Each document (office visit note, care summary, discharge summary) contains a complete snapshot of the patient's record as of that date - Records are copied between systems without a shared identifier - Amendments and corrections appear as additional records alongside the originals - Different EHR systems assign different internal IDs to the same clinical fact A naive import that appends all records produces a Pod with hundreds of duplicate entries that are impossible to use directly. --- ## 2. The Solution Cascade Protocol solves the deduplication problem with two mechanisms working together: **Deterministic IDs.** Every record is assigned a [content-hashed URI](./deterministic-ids.md) derived from its clinical content — not the EHR's internal ID. Two records from different systems that describe the same vaccination (same patient, same vaccine code, same date) produce the same URI. This makes deduplication reliable across sources. **Cross-batch reconciliation.** The `--reconcile-existing` flag on `cascade pod import` loads existing Pod records as a deduplication baseline before processing the new import. Any incoming record whose URI matches an already-imported record is skipped. Records that have conflicting field values (not just different IDs) are flagged for manual resolution. Together these mechanisms reduce 619 immunization records to the expected ~25 without requiring any manual curation of duplicates. --- ## 3. Step-by-Step Workflow ### Prerequisites - `cascade` CLI installed - An initialized Pod: `cascade pod init ./my-pod` - One or more C-CDA exports downloaded from your EHR patient portal ### Step 1 — Convert the first EHR export ```bash cascade convert --from c-cda epic-export.zip \ --source-system "epic-mychart" \ > pod/source-epic.ttl ``` The `--source-system` tag is stored in provenance metadata on each record. It is used later to identify which source a conflict's values came from. Supported input formats: C-CDA R2.1 XML files and IHE XDM ZIP bundles (the ZIP format used by Epic MyChart's "Download My Data" feature). ### Step 2 — Import the first source into the Pod ```bash cascade pod import ./my-pod pod/source-epic.ttl ``` This writes records into `my-pod/clinical/` and updates the Pod's type index. No `--reconcile-existing` is needed for the first import since the Pod is empty. Check record counts after the initial import: ```bash cascade pod info ./my-pod ``` ### Step 3 — Convert the second EHR export ```bash cascade convert --from c-cda cerner-export.xml \ --source-system "cerner-powerChart" \ > pod/source-cerner.ttl ``` ### Step 4 — Import the second source with reconciliation ```bash cascade pod import ./my-pod pod/source-cerner.ttl \ --source-system "cerner-powerChart" \ --reconcile-existing ``` The `--reconcile-existing` flag causes the command to: 1. Load all records already present in `./my-pod/clinical/` 2. Compute deterministic URIs for all incoming records 3. Skip any incoming record whose URI matches an existing record (exact duplicate) 4. Flag any incoming record that shares a URI with an existing record but has differing field values (conflict) ### Step 5 — Check for conflicts ```bash cascade pod conflicts ./my-pod ``` If the exit code is 0, all duplicates were resolved automatically and the Pod is ready to use. If the exit code is 1, one or more records had conflicting values that require a manual decision. ### Step 6 — Resolve conflicts List conflicts with detail to see what needs resolving: ```bash cascade pod conflicts ./my-pod --format json ``` For each conflict, decide which source's value is correct and record the resolution: ```bash cascade pod resolve ./my-pod --conflict conflict-1 --keep source-a cascade pod resolve ./my-pod --conflict conflict-2 --keep source-b ``` After resolving all conflicts: ```bash cascade pod conflicts ./my-pod echo "Exit code: $?" # Should be 0 ``` ### Repeating the workflow for additional sources The same pattern extends to any number of sources. Always use `--reconcile-existing` for the second and subsequent imports: ```bash # Third source cascade convert --from c-cda lab-export.zip --source-system "quest-diagnostics" > pod/source-quest.ttl cascade pod import ./my-pod pod/source-quest.ttl --source-system "quest-diagnostics" --reconcile-existing cascade pod conflicts ./my-pod ``` --- ## 4. Handling Conflicts Conflicts arise when two sources both have a record for the same clinical fact but disagree on a field value. Common examples: - Two systems record the same immunization with dates one day apart (data entry variation or timezone normalization) - One system records a medication dose as "10 mg" and another as "10mg" after string normalization fails to match them - A condition has different status values across systems (active vs. inactive) ### Resolution storage Resolutions are written to `/settings/user-resolutions.ttl` as RDF triples. This file provides an audit trail and can be inspected or exported like any other Pod resource. ### Idempotence Because resolutions are stored persistently, re-running an import after resolving conflicts does not re-create the resolved conflicts. The resolution file is checked during reconciliation, and previously resolved conflicts are applied automatically. --- ## 5. Expected Results After importing and deduplicating multiple EHR exports, record counts are typically reduced by 80–95% compared to the raw import. This reflects the true size of the patient's health history rather than the number of times each record appeared across documents. Example reduction for a patient with records from two EHR systems: | Record type | Raw records (2 sources) | After deduplication | |-------------|------------------------|---------------------| | Immunizations | 619 | ~25 | | Medications | 340 | ~18 | | Conditions | 210 | ~12 | | Lab results | 890 | ~180 | | Vital signs | 1,200 | ~400 | Lab results and vital signs have lower reduction ratios because individual measurements (each blood pressure reading, each lab draw) are genuinely distinct records, not duplicates. --- ## 6. CI Integration The `cascade pod conflicts` command exits with code 1 if any unresolved conflicts exist. Use this in CI to enforce a clean pod state before downstream processing: ```bash #!/bin/bash set -e # Import all sources cascade pod import ./my-pod source-a.ttl cascade pod import ./my-pod source-b.ttl --reconcile-existing # Fail the pipeline if conflicts exist cascade pod conflicts ./my-pod ``` For automated pipelines where manual conflict resolution is not possible, consider using `--trust` scores to drive automatic resolution of minor conflicts, and reserving the manual resolution workflow for significant disagreements. --- *See also:* - [CLI Reference](./cli-reference.md) - [Deterministic IDs](./deterministic-ids.md) - [Pod Structure Specification](../spec/pod-structure.md)