Back to Guides
EHR Systems

Epic Clarity Data Model Guide

Everything a healthcare data engineer needs to work with Epic Clarity — the reporting database behind 38% of US hospitals. Covers Clarity architecture, key tables, SQL patterns, dbt models, and Snowflake integration.

What Is Epic Clarity?

Epic Clarity is the relational reporting database within the Epic EHR ecosystem, containing a read-optimized copy of clinical, administrative, and operational data extracted nightly from Epic Chronicles — the underlying operational store. Epic is installed at approximately 38% of US hospitals and most large academic medical centers, making Clarity one of the most widely encountered data sources in healthcare data engineering.

Unlike Chronicles, which uses a proprietary columnar format requiring Epic-specific tooling, Clarity runs on Microsoft SQL Server with a standard relational schema queryable by any SQL-fluent data engineer. This makes Clarity the primary data extraction point for health system analytics teams building data warehouses on Snowflake, Databricks, and BigQuery.

Clarity contains hundreds of tables organized around core clinical entities — patients, encounters, orders, results, billing, and scheduling — with naming conventions and structural patterns that differ significantly from FHIR, X12 EDI, and standard healthcare data warehouse conventions data engineers may be accustomed to from claims-side work.

Chronicles vs Clarity vs Cogito vs Caboodle

Understanding the four layers of the Epic data architecture is essential before building any pipeline or report against Epic data.

Chronicles

Operational database — not directly queryable

The transactional EHR database storing all clinical and administrative data in Epic's proprietary Master Files format. Chronicles uses a columnar storage model optimized for real-time EHR workflows, not SQL analytics. Data engineers do not query Chronicles directly — it is the upstream source that feeds Clarity via nightly ETL.

Clarity

SQL reporting database — primary extraction target

A relational Microsoft SQL Server database populated nightly from Chronicles, containing normalized copies of clinical, billing, and operational data in SQL-queryable tables. Clarity is the standard data extraction point for health system data engineering teams building cloud data warehouses. Most Clarity data is 1 day behind Chronicles.

Cogito

Epic analytics platform — built on Clarity

Epic's bundled analytics platform including SlicerDicer self-service analytics, Radar operational dashboards, and pre-built reporting workbench reports — all querying Clarity under the hood. Clinical analysts use Cogito tools; data engineers work directly with the underlying Clarity SQL to build custom pipelines and marts not available in pre-built reports.

Caboodle

Epic enterprise data warehouse — dimensional model

Epic's purpose-built enterprise data warehouse using a dimensional star schema model with fact and dimension tables, designed for complex population health and executive analytics with better query performance than normalized Clarity. Many health systems run both Clarity (for operational reporting) and Caboodle (for population health analytics) in parallel.

Key Epic Clarity Tables for Data Engineers

Clarity contains thousands of tables, but most data engineering work centers on a core set of high-value tables. The table below covers the most commonly extracted tables by domain, with the standard join key and equivalent concept in a normalized data warehouse.

Clarity TableDomainPrimary KeyDW EquivalentNotes
PAT_ENCEncountersPAT_ENC_CSN_IDFACT_ENCOUNTERPrimary encounter table — central join point for most queries
PAT_ENC_HSPInpatientPAT_ENC_CSN_IDFACT_INPATIENT_VISITHospital-specific encounter data: admit date, discharge date, LOS
PATIENTDemographicsPAT_IDDIM_PATIENTPatient demographics: DOB, sex, race, address — contains PHI
PAT_ENC_DXDiagnosesPAT_ENC_CSN_ID + LINEFACT_ENCOUNTER_DXEncounter diagnosis codes — join to CLARITY_EDG for descriptions
CLARITY_EDGDiagnosesDX_IDDIM_ICD10Diagnosis code reference — ICD-9 and ICD-10 codes and descriptions
ORDER_PROCOrdersORDER_PROC_IDFACT_ORDERProcedure and lab orders — links to results via ORDER_PROC_ID
CLARITY_EAPProceduresPROC_IDDIM_PROCEDUREProcedure master — CPT codes, procedure names, departments
ORDER_RESULTSLab ResultsORDER_ID + LINEFACT_LAB_RESULTNumeric and text lab results — join to ORDER_PROC for context
CLARITY_ADTADT EventsEVENT_IDFACT_ADT_EVENTAdmission, discharge, transfer events for patient flow analytics
HSP_ACCOUNTBillingHSP_ACCOUNT_IDFACT_CLAIM_HEADERHospital account billing data — charges, payments, adjustments
CLARITY_SERProvidersPROV_IDDIM_PROVIDERProvider master — names, specialty, NPI, department affiliations
CLARITY_DEPDepartmentsDEPARTMENT_IDDIM_DEPARTMENTDepartment master — name, specialty, facility, cost center
CLARITY_POSFacilitiesPOS_IDDIM_FACILITYPlace of service master — facility name, address, NPI
ZC_PAT_CLASSReferencePAT_CLASS_CDIM_PATIENT_CLASSCategory lookup — decodes PAT_CLASS_C to Inpatient/Outpatient/ED
COVERAGEInsuranceCOVERAGE_IDDIM_INSURANCEInsurance coverage — payer, plan, policy number, effective dates

Understanding Epic Clarity Naming Conventions

Epic Clarity uses consistent naming conventions that differ significantly from ISO-11179 standards used in modern data warehouses. Understanding these patterns is essential for mapping Clarity columns to your standard column naming conventions.

_C suffixPAT_CLASS_C, ENC_TYPE_C, SEX_C

Category fields — store a numeric code that maps to a text value in a corresponding ZC_ lookup table. Always join to ZC_[CATEGORY] to decode the display value.

_ID suffixPAT_ID, PAT_ENC_CSN_ID, ORDER_PROC_ID

Identifier fields — unique record identifiers. PAT_ID identifies a patient across all encounters. CSN_ID (Contact Serial Number) uniquely identifies each encounter. Always store as VARCHAR, not INTEGER.

ZC_ prefixZC_PAT_CLASS, ZC_ENC_TYPE, ZC_SEX

Zap Category lookup tables — each ZC_ table decodes a specific _C code field. ZC_PAT_CLASS maps PAT_CLASS_C numeric codes to display names like Inpatient, Outpatient, Emergency.

CLARITY_ prefixCLARITY_EDG, CLARITY_EAP, CLARITY_SER

Master or reference tables containing core entity definitions — CLARITY_EDG for diagnoses, CLARITY_EAP for procedures, CLARITY_SER for providers. These are typically joined to fact-like tables for description lookups.

LINE columnPAT_ENC_DX.LINE, ORDER_RESULTS.LINE

Sequence number within a parent record — used in multi-value tables where a single encounter or order can have multiple rows. Always include LINE in your GROUP BY and ORDER BY when aggregating these tables.

Common Epic Clarity SQL Patterns

The following SQL patterns cover the most common Clarity reporting use cases. These queries follow standard SQL Server syntax as used in Clarity directly, and can be adapted for Snowflake or BigQuery after replication.

Patient Encounters with Diagnoses

The most common Clarity query pattern — encounters joined to primary diagnosis codes

SELECT
    e.PAT_ENC_CSN_ID          AS enctr_id,
    e.PAT_ID                  AS pat_id,
    e.CONTACT_DATE            AS enctr_dt,
    e.ENC_TYPE_C              AS enc_type_c,
    zt.NAME                   AS enc_type_desc,
    e.DEPARTMENT_ID           AS dept_id,
    d.DEPARTMENT_NAME         AS dept_nm,
    dx.ICD10_CODE             AS prim_diag_cd,
    edg.DX_NAME               AS prim_diag_desc,
    s.PROV_NAME               AS rndrng_prvdr_nm,
    s.NPI                     AS rndrng_prvdr_npi
FROM PAT_ENC e
-- Decode encounter type category
LEFT JOIN ZC_ENC_TYPE zt
    ON zt.ENC_TYPE_C = e.ENC_TYPE_C
-- Department
LEFT JOIN CLARITY_DEP d
    ON d.DEPARTMENT_ID = e.DEPARTMENT_ID
-- Primary diagnosis (LINE = 1)
LEFT JOIN PAT_ENC_DX dx
    ON dx.PAT_ENC_CSN_ID = e.PAT_ENC_CSN_ID
    AND dx.LINE = 1
-- Diagnosis description
LEFT JOIN CLARITY_EDG edg
    ON edg.DX_ID = dx.DX_ID
-- Rendering provider
LEFT JOIN CLARITY_SER s
    ON s.PROV_ID = e.VISIT_PROV_ID
WHERE e.CONTACT_DATE >= DATEADD(month, -12, GETDATE())
    AND e.ENC_CLOSED_YN = 'Y'
ORDER BY e.CONTACT_DATE DESC;

Inpatient Admissions with LOS

Hospital encounters with length of stay and discharge disposition

SELECT
    e.PAT_ENC_CSN_ID              AS enctr_id,
    e.PAT_ID                      AS pat_id,
    h.HOSP_ADMSN_TIME             AS admit_ts,
    h.HOSP_DISCHRG_TIME           AS dschrg_ts,
    DATEDIFF(day,
        h.HOSP_ADMSN_TIME,
        h.HOSP_DISCHRG_TIME)      AS los_days,
    zd.NAME                       AS dschrg_dispo_desc,
    h.DRG_ID                      AS drg_cd,
    h.DRG_MPI_CODE                AS ms_drg_cd,
    p.PAT_MRN_ID                  AS mrn,
    p.BIRTH_DATE                  AS pat_dob,
    p.PAT_SEX                     AS pat_sex
FROM PAT_ENC e
JOIN PAT_ENC_HSP h
    ON h.PAT_ENC_CSN_ID = e.PAT_ENC_CSN_ID
JOIN PATIENT p
    ON p.PAT_ID = e.PAT_ID
LEFT JOIN ZC_DISCH_DISP zd
    ON zd.DISCH_DISP_C = h.DISCH_DISP_C
WHERE h.HOSP_ADMSN_TIME >= DATEADD(year, -1, GETDATE())
    AND h.HOSP_DISCHRG_TIME IS NOT NULL
ORDER BY h.HOSP_ADMSN_TIME DESC;

Lab Orders and Results

Joining orders to results — the most common source of confusion in Clarity

SELECT
    op.ORDER_PROC_ID          AS order_id,
    op.PAT_ENC_CSN_ID         AS enctr_id,
    op.ORDER_TIME             AS order_ts,
    op.RESULT_TIME            AS result_ts,
    eap.PROC_NAME             AS proc_nm,
    eap.PROC_CODE             AS cpt_cd,
    r.LINE                    AS result_line,
    r.COMPONENT_ID            AS loinc_component_id,
    r.ORD_VALUE               AS result_val,
    r.REFERENCE_UNIT          AS result_unit,
    r.REFERENCE_LOW           AS ref_rng_low,
    r.REFERENCE_HIGH          AS ref_rng_high,
    r.RESULT_FLAG             AS abnrml_flg,
    r.RESULT_STATUS_C         AS result_sts_c
FROM ORDER_PROC op
JOIN CLARITY_EAP eap
    ON eap.PROC_ID = op.PROC_ID
LEFT JOIN ORDER_RESULTS r
    ON r.ORDER_ID = op.ORDER_PROC_ID
WHERE op.ORDER_TIME >= DATEADD(month, -3, GETDATE())
    AND op.ORDER_STATUS_C = 5   -- 5 = Resulted
ORDER BY op.ORDER_TIME DESC, r.LINE;

ADT Events for Patient Flow

Admission, discharge, transfer events — essential for bed management and throughput analytics

SELECT
    a.PAT_ENC_CSN_ID          AS enctr_id,
    a.EVENT_ID                AS adt_event_id,
    a.EVENT_TIME              AS adt_event_ts,
    za.NAME                   AS adt_event_type,
    a.FROM_BASE_CLASS_C       AS from_pat_cls_c,
    a.TO_BASE_CLASS_C         AS to_pat_cls_c,
    fd.DEPARTMENT_NAME        AS from_dept_nm,
    td.DEPARTMENT_NAME        AS to_dept_nm,
    a.FROM_ROOM_ID            AS from_room_id,
    a.TO_ROOM_ID              AS to_room_id,
    DATEDIFF(minute,
        LAG(a.EVENT_TIME) OVER
            (PARTITION BY a.PAT_ENC_CSN_ID
             ORDER BY a.EVENT_TIME),
        a.EVENT_TIME)         AS mins_in_prior_location
FROM CLARITY_ADT a
LEFT JOIN ZC_ADT_EVENT_TYPE za
    ON za.EVENT_TYPE_C = a.EVENT_TYPE_C
LEFT JOIN CLARITY_DEP fd
    ON fd.DEPARTMENT_ID = a.FROM_DEPARTMENT_ID
LEFT JOIN CLARITY_DEP td
    ON td.DEPARTMENT_ID = a.TO_DEPARTMENT_ID
WHERE a.EVENT_TIME >= DATEADD(day, -30, GETDATE())
ORDER BY a.PAT_ENC_CSN_ID, a.EVENT_TIME;

dbt Model for Epic Clarity in Snowflake

After replicating Clarity tables to Snowflake, use dbt to build a standardized staging layer that applies ISO-11179 column naming, decodes category codes, and enforces data quality tests before data reaches your analytics marts.

models/staging/stg_clarity_encounters.sql

{{ config(
    materialized='view',
    schema='staging',
    tags=['clarity', 'encounters']
) }}

with source as (
    select * from {{ source('clarity', 'PAT_ENC') }}
),

-- Decode category codes from ZC_ lookup tables
enc_type as (
    select ENC_TYPE_C, NAME as enc_type_desc
    from {{ source('clarity', 'ZC_ENC_TYPE') }}
),

staged as (
    select
        -- Identifiers (map Clarity names to ISO-11179 standards)
        e.PAT_ENC_CSN_ID::varchar(18)   as enctr_id,
        e.PAT_ID::varchar(18)            as pat_id,
        e.DEPARTMENT_ID::varchar(18)     as dept_id,
        e.VISIT_PROV_ID::varchar(18)     as rndrng_prvdr_id,

        -- Dates
        e.CONTACT_DATE::date             as enctr_dt,
        e.APPT_TIME::timestamp           as appt_ts,
        e.CHECKIN_TIME::timestamp        as checkin_ts,
        e.CHECKOUT_TIME::timestamp       as checkout_ts,

        -- Category fields — store both code and decoded description
        e.ENC_TYPE_C                     as enc_type_c,
        et.enc_type_desc,
        e.PAT_CLASS_C                    as pat_cls_c,

        -- Flags
        e.ENC_CLOSED_YN                  as enc_closed_flg,
        e.APPT_STATUS_C                  as appt_sts_c,

        -- Metadata
        e.CONTACT_DATE::date             as source_date
    from source e
    left join enc_type et on et.ENC_TYPE_C = e.ENC_TYPE_C
    where e.CONTACT_DATE >= dateadd(year, -3, current_date)
)

select * from staged

models/staging/stg_clarity_encounters.yml

version: 2
models:
  - name: stg_clarity_encounters
    columns:
      - name: enctr_id
        tests: [not_null, unique]
      - name: pat_id
        tests: [not_null]
      - name: enctr_dt
        tests: [not_null]
      - name: enc_closed_flg
        tests:
          - accepted_values:
              values: ['Y', 'N']

Convert your Clarity DDL for Snowflake, BigQuery, or Databricks using the free DDL Converter →

Epic Clarity Data Quality Rules

Common data quality issues encountered when building pipelines from Epic Clarity and how to detect them in your dbt models or SQL validation queries.

HIGH

Duplicate CSN_IDs after replication

Some Clarity replication tools create duplicate rows during CDC catch-up. Always validate uniqueness on PAT_ENC_CSN_ID in your staging layer before building fact tables.

-- Detect duplicate CSNs in replicated PAT_ENC
SELECT PAT_ENC_CSN_ID, COUNT(*) as cnt
FROM stg_clarity_encounters
GROUP BY PAT_ENC_CSN_ID
HAVING COUNT(*) > 1;
MEDIUM

Undecoded ZC_ category codes

Analysts querying raw _C fields see numeric codes like 3 instead of "Inpatient". Always join to ZC_ tables in staging and carry both the code and decoded description forward.

-- Find encounters with unresolvable enc_type_c codes
SELECT enc_type_c, COUNT(*) as cnt
FROM stg_clarity_encounters
WHERE enc_type_desc IS NULL
  AND enc_type_c IS NOT NULL
GROUP BY enc_type_c;
HIGH

Missing NPI on CLARITY_SER providers

Not all Epic provider records have NPI populated — non-clinical staff, test accounts, and foreign providers may lack NPIs. Validate before joining to claims data.

-- Providers missing NPI in Clarity
SELECT PROV_ID, PROV_NAME, PROV_TYPE
FROM CLARITY_SER
WHERE NPI IS NULL
  OR LEN(NPI) != 10
  AND PROV_TYPE_C NOT IN (20, 21); -- exclude non-clinical types
HIGH

Negative LOS from data entry errors

Admission/discharge timestamp entry errors occasionally produce negative length of stay values. Filter these out or flag them in your staging model.

-- Inpatient encounters with invalid LOS
SELECT PAT_ENC_CSN_ID, HOSP_ADMSN_TIME, HOSP_DISCHRG_TIME,
    DATEDIFF(day, HOSP_ADMSN_TIME, HOSP_DISCHRG_TIME) as los_days
FROM PAT_ENC_HSP
WHERE HOSP_DISCHRG_TIME < HOSP_ADMSN_TIME
   OR DATEDIFF(day, HOSP_ADMSN_TIME, HOSP_DISCHRG_TIME) > 365;
MEDIUM

Open encounters never closed

PAT_ENC rows with ENC_CLOSED_YN = N represent encounters still open for documentation. Include or exclude based on your use case — most reporting excludes open encounters.

-- Volume of open vs closed encounters by month
SELECT
    YEAR(CONTACT_DATE)  as yr,
    MONTH(CONTACT_DATE) as mo,
    ENC_CLOSED_YN,
    COUNT(*)            as enc_cnt
FROM PAT_ENC
WHERE CONTACT_DATE >= DATEADD(month, -6, GETDATE())
GROUP BY YEAR(CONTACT_DATE), MONTH(CONTACT_DATE), ENC_CLOSED_YN
ORDER BY yr, mo;

Migrating Epic Clarity to Snowflake or Databricks

Most health systems extract Clarity tables into a cloud data platform for scalable analytics beyond what Clarity's on-premises SQL Server can support. Here is the recommended migration architecture.

1

Extract

Use Fivetran, AWS DMS, Azure Data Factory, or custom JDBC extraction to replicate Clarity tables from SQL Server to cloud storage. Schedule nightly full refreshes for small tables and CDC-based incremental loads for large tables like PAT_ENC and ORDER_PROC.

2

Stage

Land raw Clarity tables in a bronze layer with minimal transformation. Preserve original column names, data types, and all rows including soft-deleted records. Add a received_ts metadata column for audit and incremental tracking.

3

Transform

Apply dbt models to decode ZC_ category codes, rename columns to ISO-11179 standards, join provider NPI from CLARITY_SER, and build encounter, diagnosis, order, and result fact tables conforming to your enterprise data warehouse standards.

Column naming tip: When migrating Clarity columns to your warehouse, map Epic naming conventions to ISO-11179 healthcare data standards → For example: PAT_ENC_CSN_ID → enctr_id, CONTACT_DATE → enctr_dt, VISIT_PROV_ID → rndrng_prvdr_id. Use the Naming Auditor tool → to validate column names against healthcare standards.

Frequently Asked Questions

What is Epic Clarity?

Epic Clarity is the relational reporting database component of the Epic electronic health record system, containing a read-optimized copy of clinical, administrative, and operational data extracted from Epic Chronicles, the underlying operational database. Clarity uses a traditional relational schema with hundreds of tables organized around clinical entities — patients, encounters, orders, results, and billing — making it accessible to SQL-fluent data engineers and report writers without requiring knowledge of Epic's proprietary Chronicles data model.

What is the difference between Epic Chronicles, Clarity, and Cogito?

Epic Chronicles is the operational database using a proprietary columnar storage format optimized for transactional EHR workflows — it is not directly queryable with standard SQL. Epic Clarity is a relational SQL database populated nightly from Chronicles via the ETL process, designed for reporting and analytics. Epic Cogito is Epic's analytics platform built on top of Clarity, providing pre-built dashboards, SlicerDicer self-service analytics, and Radar dashboards. Data engineers typically work directly with Clarity via SQL, while clinical analysts use Cogito tools that query Clarity under the hood.

How often does Epic Clarity refresh?

Epic Clarity refreshes on a nightly ETL cycle by default, meaning Clarity data is typically 1 day behind the operational Chronicles database. Some Epic implementations configure more frequent Clarity refreshes — commonly every 4-8 hours for high-priority tables — but real-time data is not available in standard Clarity. For near-real-time analytics, Epic provides Caboodle, a purpose-built enterprise data warehouse with more frequent refresh cycles, as an alternative to direct Clarity reporting.

What is Epic Caboodle and how does it differ from Clarity?

Epic Caboodle is Epic's enterprise data warehouse built on a dimensional model with fact and dimension tables, designed for complex cross-domain analytics with better performance on aggregated queries than the normalized Clarity schema. While Clarity mirrors the Chronicles operational structure in relational form, Caboodle uses a star schema architecture similar to traditional data warehouses. Many health systems use both — Clarity for detailed, record-level operational reporting and Caboodle for population health and executive analytics.

How do I connect to Epic Clarity from Snowflake or Databricks?

The standard approach is to replicate Clarity tables to your cloud data platform using a CDC or scheduled extraction tool such as Fivetran, AWS DMS, Azure Data Factory, or a custom Python/SQL extraction pipeline. Clarity runs on Microsoft SQL Server, so JDBC/ODBC connections are straightforward. Once replicated, you can build dbt models on top of the raw Clarity tables in Snowflake or Databricks, applying ISO-11179 column naming standards and dimensional modeling to create analytics-ready marts from the normalized Clarity source schema.

What are the most important Epic Clarity tables for data engineers?

The most commonly queried Clarity tables are: PAT_ENC and PAT_ENC_HSP for encounter and hospitalization data, PATIENT for demographics, CLARITY_EDG for diagnosis codes, CLARITY_EAP for procedure codes, ORDER_PROC for orders, PAT_ENC_DX for encounter diagnoses, HSP_ACCOUNT for hospital billing, CLARITY_ADT for ADT events, and ZC_ prefix tables for category value lookups. The PAT_ENC table is typically the central join point for most Clarity reporting queries, linked by PAT_ENC_CSN_ID.

What is PAT_ENC_CSN_ID in Epic Clarity?

PAT_ENC_CSN_ID stands for Patient Encounter Contact Serial Number ID — it is the unique identifier for each patient encounter in Epic Clarity, equivalent to an encounter_id or visit_id in standard healthcare data warehouse terminology. The CSN is the primary join key linking most Clarity tables to a specific patient encounter. In your data warehouse, map CSN_ID to your standard encounter_id column following ISO-11179 naming conventions.

How should I handle Epic ZC_ category tables in my data warehouse?

Epic ZC_ (Zap Category) tables are lookup tables storing the text labels for coded fields throughout Clarity, following a naming pattern of ZC_[CATEGORY_NAME]. For example, ZC_PAT_CLASS maps the PAT_CLASS_C numeric code to values like Inpatient, Outpatient, or Emergency. In your dbt models, left join to relevant ZC_ tables to decode category codes, but store both the raw numeric code and the decoded description in your dimensional models to avoid breaking joins when Epic administrators add or rename category values.

Related Resources