← Methods repository
concept

EHR Data Provenance and Source Traceability

Documentation and machine-readable lineage that connects each analysis-ready EHR-derived variable back to the source system, event type, extraction date, transformation logic, source document or resource, and responsible process, so reviewers can audit whether the data are trustworthy for a specific RWE use.

Data_Quality_Assessmentprovenancesource-traceabilitydata-lineageaudit-trailehr-etlfhir-provenancesource-precedenceregulatory-readiness
Methods reference only. Use primary source citations and local policy before applying this in a study protocol, regulatory submission, payer dossier, or clinical decision.

In plain language

Provenance is the audit trail for clinical data. It tells you whether a study value came from an EHR order, a pharmacy dispensing, a nurse-charted administration, a claim, a registry abstraction, or a transformed combination of several sources. Without that trail, an analyst may not know whether a medication date means "prescribed," "filled," or "actually given," which can change the study result.

EHR data provenance and source traceability

is the ability to explain where an analysis value came from, how it moved through extraction and transformation, and which source artifact would be inspected if a reviewer challenged it. In RWE this is not a software nicety. It is a validity requirement. The FDA EHR/claims RWD guidance expects sponsors to document data accrual, curation, transformation, quality control, de-identification, linkage, and traceability back to original source data where appropriate. FHIR Provenance and W3C PROV provide a reusable vocabulary for this idea: entities, activities, and agents involved in producing a data value. The RWE task is to make that vocabulary operational in analytic datasets.

Core conceptual distinction

Provenance is not the same as data quality, source reliability, or a codebook. A codebook says what a variable is supposed to mean. Data quality checks say whether values look complete, conformant, and plausible. Provenance says which source produced a specific value and what transformations produced the version used in analysis. For example, "index_date = 2023-03-10" is not traceable unless the analyst can identify whether it came from a MedicationRequest authoredOn date, a pharmacy claim fill_date, a MedicationAdministration occurrence date, or a manually abstracted chart date; which extract contained it; what deduplication rule selected it; and whether the source was blinded or exposure-aware. Without that trail, disagreements cannot be resolved and regulatory review becomes a trust exercise rather than an audit.

Pros, cons, and trade-offs

- Row-level lineage vs dataset-level documentation: Dataset-level documentation is light and often enough for exploratory work, but it cannot support record-level challenges. Row-level lineage is heavier but lets a reviewer trace one outcome, exposure, or covariate back to its source table, resource ID, document, chart page, or adjudication packet. - Source-preserving ETL vs early harmonization: Harmonizing into OMOP, PCORnet, or a flat analysis table makes downstream programming easier. If the ETL drops source values, source codes, units, resource IDs, and load timestamps, it destroys auditability. Preserve source fields and a transformation manifest even when the analysis uses standardized fields. - Deterministic source precedence vs analyst judgment: A fixed hierarchy such as "administration record beats dispensing record beats order" is reproducible and auditable. It can be clinically wrong in unusual cases, such as an outside infusion missing from the EHR but billed in claims. Prefer a deterministic rule with exception logging over undocumented ad hoc overrides. - Tokenization and de-identification vs source trace-back: Privacy-preserving linkage and de-identification are essential, but they can break traceability if the token process cannot reconnect a study row to the source record under governed conditions. Use linkage tokens and re-identification firewalls that preserve audit paths without exposing identifiers to analysts.

When to use

. Use source traceability whenever an EHR-derived variable affects cohort entry, time zero, treatment assignment, exposure duration, outcome ascertainment, censoring, or key confounding adjustment. It is mandatory for regulatory-grade RWE, endpoint validation, chart abstraction, linked claims-EHR studies, FHIR ingestion pipelines, and any multi-site network where the same analytic field may originate from different source systems.

When NOT to use - and when it is actively misleading

- Do not use provenance labels as a substitute for validation. Knowing a value came from a discharge summary or a FHIR Observation does not prove it is clinically correct. - Do not collapse source types before assigning time zero. Order date, dispense date, administration date, service date, result date, authored date, and warehouse load date answer different temporal questions. Treating them as one generic "date" can create immortal time, exposure misclassification, or outcome-window errors. - Do not report traceability that cannot be exercised. If the analysis row contains a document ID but governance prevents anyone from retrieving the document for audit, traceability is weaker than the label implies. - Do not hide transformation code behind vendor black boxes. A source may be unusable for regulatory work if the ETL rules that create the analytic variable cannot be described, versioned, and reproduced. - Do not overwrite source values during standardization. Store both the source value and standardized value. Otherwise a reviewer cannot tell whether an implausible standardized value came from source error, unit conversion, vocabulary mapping, or analytic recoding.

Data-source operational depth

- EHR: Preserve source table, source system, encounter ID, document ID, FHIR resource ID, status, author, recorded time, event time, and extract/load time. Medication orders, problem lists, medication lists, MAR entries, notes, and lab feeds all carry different meanings even when they map to one OMOP domain. - Claims: Preserve claim ID, line ID, service date, paid/adjudication date, reversal or void status, diagnosis position, place of service, revenue center, billing units, and source benefit file. Claims provenance is strongest for payment lineage but weaker for clinical truth. - Registry: Preserve abstraction form version, abstractor identity or role, adjudication status, source document, abstraction date, and registry update cycle. Registry variables can mix direct abstraction, EHR import, and calculated fields. - Linked data: Preserve linkage token version, match confidence, source precedence rule, date reconciliation rule, and whether values are single-source, concordant, discordant, or derived. Linkability itself becomes a provenance feature because linked patients can differ from unlinked patients.

Worked example

A study defines initiation of an infused oncology drug. The linked dataset contains an EHR MedicationRequest on 2023-02-01, an inpatient pharmacy MedicationDispense on 2023-02-03, an EHR MedicationAdministration on 2023-02-04 at 09:12, and a medical claim J-code service date of 2023-02-05. A source-traceable pipeline stores all four records and applies the pre-specified hierarchy: administration date defines initiation when present; otherwise use medical claim service date for provider-administered drugs; otherwise use dispense date; never use order date as confirmed initiation. The analysis-ready index_date is 2023-02-04 and its provenance fields state `source_event_type = MedicationAdministration`, `source_record_id = MAR-88721`, `source_precedence_rule = admin_over_claim_over_dispense_over_order`, and `competing_source_dates = 2023-02-01|2023-02-03|2023-02-05`. If a reviewer asks why the claim date was not used, the rule and the competing source dates are visible without rerunning the ETL.

Worked example

Scenario

A linked EHR-claims oncology study must define the first confirmed dose of an infused therapy. Four source systems contain related medication records with four different dates. The team needs a traceable index_date.

Dataset

Competing source events for one patient's first infused therapy record

source_event_typesource_record_idevent_daterecorded_or_paid_datesource_meaning
MedicationRequestRXREQ-10442023-02-012023-02-01prescriber ordered therapy
MedicationDispenseDISP-20912023-02-032023-02-03inpatient pharmacy supplied product
MedicationAdministrationMAR-887212023-02-042023-02-04 09:12nurse charted dose administered
HCPCS_J_CODE_CLAIMCLM-7702-L42023-02-052023-03-18provider billed administered drug

Steps

  • Preserve all source events rather than overwriting them into one medication date.

  • Apply The Pre-Specified Source Hierarchy For Provider-Administered Drugs

    administration record first, then medical claim service date, then dispensing, then order.

  • Select the MedicationAdministration occurrence date as index_date because it confirms the drug was actually given.

  • Store source_event_type, source_record_id, hierarchy version, competing source dates, and ETL version alongside the analysis row.

  • Reviewers can now audit both the selected value and the reason competing dates were not selected.

Result

The analysis index_date is 2023-02-04, with source_event_type MedicationAdministration and source_record_id MAR-88721. The order, dispense, and claim dates are retained as competing provenance so the derivation can be audited.

Runnable example

python implementation

Build a source-traceable medication initiation table from competing order, dispense, administration, and claim events. Required inputs after normalization: events: person_id, drug_group, event_type, event_date, recorded_date, source_record_id, source_system...

import pandas as pd

SOURCE_RANK = {
    "MedicationAdministration": 1,
    "HCPCS_J_CODE_CLAIM": 2,
    "MedicationDispense": 3,
    "MedicationRequest": 4,
}

def traceable_initiation(events: pd.DataFrame, hierarchy_version: str = "admin_claim_dispense_order_v1") -> pd.DataFrame:
    e = events.copy()
    e["event_date"] = pd.to_datetime(e["event_date"])
    e["source_rank"] = e["event_type"].map(SOURCE_RANK).fillna(99)

    # For each person/drug, select the earliest event from the highest-precedence available source.
    e = e.sort_values(["person_id", "drug_group", "source_rank", "event_date"])
    selected = e.groupby(["person_id", "drug_group"], as_index=False).first()
    selected = selected.rename(columns={"event_date": "index_date"})

    # Preserve competing source dates for audit rather than discarding them.
    competing = (e.assign(src_date=e["event_type"] + ":" + e["event_date"].dt.strftime("%Y-%m-%d"))
                   .groupby(["person_id", "drug_group"])["src_date"]
                   .apply(lambda x: "|".join(sorted(x)))
                   .rename("competing_source_dates")
                   .reset_index())
    out = selected.merge(competing, on=["person_id", "drug_group"], how="left")
    out["source_precedence_rule"] = hierarchy_version
    return out[[
        "person_id", "drug_group", "index_date", "event_type", "source_record_id",
        "source_system", "source_precedence_rule", "competing_source_dates"
    ]]