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 queryableThe 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 targetA 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 ClarityEpic'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 modelEpic'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 Table | Domain | Primary Key | DW Equivalent | Notes |
|---|---|---|---|---|
| PAT_ENC | Encounters | PAT_ENC_CSN_ID | FACT_ENCOUNTER | Primary encounter table — central join point for most queries |
| PAT_ENC_HSP | Inpatient | PAT_ENC_CSN_ID | FACT_INPATIENT_VISIT | Hospital-specific encounter data: admit date, discharge date, LOS |
| PATIENT | Demographics | PAT_ID | DIM_PATIENT | Patient demographics: DOB, sex, race, address — contains PHI |
| PAT_ENC_DX | Diagnoses | PAT_ENC_CSN_ID + LINE | FACT_ENCOUNTER_DX | Encounter diagnosis codes — join to CLARITY_EDG for descriptions |
| CLARITY_EDG | Diagnoses | DX_ID | DIM_ICD10 | Diagnosis code reference — ICD-9 and ICD-10 codes and descriptions |
| ORDER_PROC | Orders | ORDER_PROC_ID | FACT_ORDER | Procedure and lab orders — links to results via ORDER_PROC_ID |
| CLARITY_EAP | Procedures | PROC_ID | DIM_PROCEDURE | Procedure master — CPT codes, procedure names, departments |
| ORDER_RESULTS | Lab Results | ORDER_ID + LINE | FACT_LAB_RESULT | Numeric and text lab results — join to ORDER_PROC for context |
| CLARITY_ADT | ADT Events | EVENT_ID | FACT_ADT_EVENT | Admission, discharge, transfer events for patient flow analytics |
| HSP_ACCOUNT | Billing | HSP_ACCOUNT_ID | FACT_CLAIM_HEADER | Hospital account billing data — charges, payments, adjustments |
| CLARITY_SER | Providers | PROV_ID | DIM_PROVIDER | Provider master — names, specialty, NPI, department affiliations |
| CLARITY_DEP | Departments | DEPARTMENT_ID | DIM_DEPARTMENT | Department master — name, specialty, facility, cost center |
| CLARITY_POS | Facilities | POS_ID | DIM_FACILITY | Place of service master — facility name, address, NPI |
| ZC_PAT_CLASS | Reference | PAT_CLASS_C | DIM_PATIENT_CLASS | Category lookup — decodes PAT_CLASS_C to Inpatient/Outpatient/ED |
| COVERAGE | Insurance | COVERAGE_ID | DIM_INSURANCE | Insurance 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.
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.
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.
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.
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.
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 stagedmodels/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.
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;
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;
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
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;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.
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.
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.
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.
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.