Run an end-to-end HEOR outcomes study
This scenario runs a full health economics and outcomes research study inside PJI: define the population in Assistant, check its concepts against standard vocabularies, save it as a cohort, then review outcomes, healthcare utilization, and cost availability for that same cohort. It moves between cohort-level analytics and patient-level evidence, so every summarized number can be opened down to the records behind it. The page stops once the study output has been recorded and, where your deployment supports it, exported to your own analysis environment.
Running an HEOR Outcomes Study on Real-World Data
This video has no audio. It is a visual walkthrough of the screen.
Who this is for
Anyone who has to design a real-world evidence study and defend what the data can actually support:
- HEOR researchers and outcomes analysts scoping a study population
- Real-world evidence and epidemiology teams working from OMOP data
- Clinical data managers validating diagnosis and medication concept sets
- Medical affairs and market access teams reviewing treatment patterns and utilization
- The platform administrator who configures clinical measures and knows which OMOP tables are populated
Why it matters
A protocol that promises time-to-treatment, co-medication patterns, and cost outcomes commits you to facts the data may not carry. Diagnosis-to-treatment timing is only interpretable when the index date is real. A metformin start recorded only in a clinic progress note, and never coded into drug_exposure, reads downstream as a patient who was never treated.
Those facts also have to be normalized to standard terminologies before they can be compared or counted. A cohort built from a concept set that is broader or narrower than the study intent still returns clean-looking numbers, and those numbers answer a different question than the one in the protocol. The same is true of an outcome you cannot trace: a rate without a denominator you can inspect is a claim, not evidence.
The value of this workflow is that every cohort-level number can be opened down to the patient records behind it. An Assistant count opens into a cohort, a cohort opens into a patient timeline, and a timeline row opens into its source. PJI structures the data and surfaces that evidence; clinicians make the clinical decisions about the care it describes.
What you gain
A saved study cohort with its diagnosis and medication concepts validated against SNOMED and RxNorm and reconciled with the concepts Assistant actually covered. Alongside it: exploratory time-to-metformin distributions, cohort co-medication counts compared with Clinical Guidelines, cohort-level clinical measure results, a visits-by-type utilization breakdown, and a direct answer on whether cost outcomes can be calculated at all.
The output holds up because the criteria stay visible in Cohort Builder, the SQL stays in Query History, and each cohort-level figure is traceable to patient records and their sources. Where the data cannot support an outcome, you get that answer in writing rather than a blank. Missing cost data documented as missing is a usable study finding; missing cost data read as zero cost is not.
Before you start
- Patient data ingested into OMOP, with diagnosis, medication, procedure, observation, and visit data available. Gaps in any of these become gaps in the study, not errors you will see at query time
- A target disease and basic study criteria decided before you open Assistant. Scoping the question in the chat produces a population you cannot reproduce later
- Access to Assistant, Clinical Guidelines, Medical Terminology, Cohort Builder, Patient Timeline, Clinical Measures, and Database Explorer
- At least one clinical measure relevant to the study prepared in your deployment. Without a configured measure there is no outcome step to run
- A check on whether the OMOP
costtable is populated. You can complete the workflow without it, provided the missing data is documented clearly and never interpreted as zero cost
The example used throughout is a Type 2 diabetes and metformin study. Any patient shown is illustrative and synthetic.
Step 1: Define the study population in Assistant
- Open Assistant.
- Enter the study question exactly as scoped:
patients with Type 2 diabetes who have had at least one metformin fill - Review the response and the returned patient count. In the reference recording this returns 82 patients before any cohort filters.
- Open the Stats view and review Covered Concepts where available, especially
condition_concept_idrows such as Type 2 diabetes mellitus (201826, SNOMED). - Leave the chat open. Steps 2 and 3 continue in it.
"Metformin fill" is an operational study phrase, not a data guarantee. Confirm whether the underlying drug_exposure records represent prescriptions, dispensing events, claims, or another medication source before reading them as pharmacy fills.
Step 2: Explore diagnosis timing and time to metformin
- In the same chat, ask when the initial diagnosis was made.
- Ask for the first recorded metformin exposure date for each patient.
- Combine the two: for patients with Type 2 diabetes who have at least one recorded metformin exposure, ask for the first recorded Type 2 diabetes diagnosis date, the first metformin exposure date, and the number of days between them.
- Review the result table columns for first diagnosis date, first recorded metformin exposure date, and days between them.
Treat negative day values as data-quality or index-date signals, not as HEOR endpoints. Record the distribution as exploratory time-to-treatment evidence.
Step 3: Compare Assistant co-medications with Clinical Guidelines
- Still in Assistant, ask what the most common co-medications prescribed after metformin are in this cohort.
- Review the result table. In the reference recording, common rows include metformin dose forms, glipizide, lisinopril, and atorvastatin, along with some noisy non-therapy concepts.
- Highlight the clinically relevant co-medications, such as glipizide (a sulfonylurea), rather than presenting every row as antidiabetic second-line therapy.
- Open Clinical Guidelines and ask what the recommended first-line pharmacologic therapy for Type 2 diabetes is.
- Check whether the guideline identifies metformin as a commonly used initial therapy, and note the patient-specific circumstances it flags: cardiovascular disease, heart failure, chronic kidney disease, or obesity may favor other treatment classes. Verify the answer against the guideline citations the platform returns.
The two outputs answer different questions and should be recorded as a comparison. Assistant shows the drugs actually present in the cohort after metformin, including co-medications and antidiabetic add-ons such as glipizide. Clinical Guidelines identifies recommended therapy classes, and where relevant the add-on or alternative classes including sulfonylureas, SGLT-2 inhibitors, DPP-4 inhibitors, and GLP-1 receptor agonists. Present the Assistant counts as exploratory cohort findings.
Step 4: Validate the concepts in Medical Terminology
- Open Data Enrichment > Medical Terminology.
- Search for the study diagnosis, for example Type 2 diabetes mellitus.
- Filter to SNOMED where needed and identify the relevant diagnosis concepts. In the reference recording the primary concept code is SNOMED 44054006.
- Record the selected diagnosis concept names and codes.
- Clear or refine the search, look up metformin, filter to RxNorm where needed, and record the selected medication concept names and codes.
- Return to the Assistant result and open Stats.
- Compare the Medical Terminology findings with Assistant Covered Concepts by concept name and vocabulary.
- Confirm the diagnosis concepts use the expected vocabulary, typically SNOMED for Type 2 diabetes in OMOP-backed results, and that the medication concepts match the intended metformin definition, typically RxNorm.
- If the Assistant concepts are broader or narrower than the study intent, refine the Assistant request and repeat the comparison before creating the cohort.
Do not compare the numbers directly. Medical Terminology shows the source concept code (SNOMED 44054006), while Assistant Stats shows the OMOP concept_id for the same term (201826). They are not expected to match. Use vocab.concept to confirm that a concept code and a concept_id refer to the same concept.
Step 5: Review and save the cohort
- Return to the Assistant result that defines the Type 2 diabetes and metformin population.
- Choose Create Cohort from that result, or open the generated cohort in Cohort Builder.
- Review the criteria created from the Assistant request and confirm they include both Type 2 diabetes and metformin treatment.
- Add any required demographic or date criteria. For example, apply a note-date range such as 2007-07-01 to 2026-07-26.
- Confirm the filtered patient count. In the reference recording the Assistant base of 82 patients drops to 77 after the note-date filter.
- Enter a clear cohort name and description, then save.
- Confirm the cohort appears in the cohort list with the final patient count.
Record the count before and after any filter. The difference is part of the study definition.
Step 6: Review one patient's clinical journey
- Open the saved cohort and select a representative patient.
- Open that patient in Patient Timeline.
- Search or filter for Type 2 diabetes and locate the diagnosis event.
- Review the events recorded before and after the diagnosis across conditions, medications, procedures, and visits or encounters.
- Locate the metformin treatment, typically under Medications.
- Confirm whether the medication row is marked as structured data or another source, and open provenance or source details where available.
- Open the related source document where supporting evidence exists.
This is patient-level treatment history and nothing more. The cohort-level co-medication and guideline comparison already happened in Step 3; a single timeline does not replace it.
Step 7: Review clinical measures across the cohort
- Open Clinical Measures and select the saved HEOR study cohort.
- Select a measure relevant to Type 2 diabetes. In the reference recording the measure is eAG from HbA1c. Other examples include diabetes control, HbA1c monitoring, complication rate, and guideline adherence.
- Run or open the selected measure.
- Review the cohort-level result first, including numerator, denominator, and patient count where available.
- Review the patient-level results table for the full cohort before focusing on any single patient.
- Open an individual patient result only after the cohort-level output has been reviewed.
- Where a measure or patient-result download is supported, export the cohort-scoped output rather than a single-patient file.
- Record the measure result for the study.
Step 8: Analyze healthcare utilization
- Open Data Integration > Database Explorer.
- Select the SQL Query tab, then select OMOP Database (Default).
- Get the saved cohort UUID from the Cohort Builder URL: the final path segment. A URL ending in
/agents/cohort-builder/59649e9e-596e-4fbd-9145-78b1e135cb62gives the UUID59649e9e-596e-4fbd-9145-78b1e135cb62. - Replace
COHORT_IDin the query below with that UUID and run it. In the walkthrough this step is labeled Healthcare utilization: visits by type.
WITH cohort_patients AS (
SELECT DISTINCT
cm.patient_id::int AS person_id
FROM tpj.cohort_membership cm
WHERE cm.cohort_id = 'COHORT_ID'::uuid
AND COALESCE(cm.is_es_only, false) = false
)
SELECT
COALESCE(
vc.concept_name,
v.visit_source_value,
'Unknown'
) AS visit_type,
COUNT(*) AS visit_count,
COUNT(DISTINCT v.person_id) AS patient_count
FROM cohort_patients cp
JOIN tpj.visit_occurrence v
ON v.person_id = cp.person_id
LEFT JOIN vocab.concept vc
ON vc.concept_id = v.visit_concept_id
GROUP BY
COALESCE(
vc.concept_name,
v.visit_source_value,
'Unknown'
)
ORDER BY visit_count DESC;
The result gives one row per utilization category: visit_type is the standard OMOP visit concept or the source value, visit_count is the total visits in that category, and patient_count is the number of distinct cohort patients with at least one visit in it. Expect inpatient, outpatient, emergency, laboratory, office, telehealth, and other categories, depending on what the data holds.
Then reconcile those results against the full cohort:
WITH cohort_patients AS (
SELECT DISTINCT
cm.patient_id::int AS person_id
FROM tpj.cohort_membership cm
WHERE cm.cohort_id = 'COHORT_ID'::uuid
AND COALESCE(cm.is_es_only, false) = false
)
SELECT
COUNT(DISTINCT cp.person_id) AS cohort_patient_count,
COUNT(DISTINCT v.person_id) AS patients_with_visits,
COUNT(DISTINCT cp.person_id)
- COUNT(DISTINCT v.person_id) AS patients_without_visits,
COUNT(v.visit_occurrence_id) AS total_visits
FROM cohort_patients cp
LEFT JOIN tpj.visit_occurrence v
ON v.person_id = cp.person_id;
Confirm that cohort_patient_count matches the patient count shown for the same cohort in Cohort Builder, and that total_visits covers all visit records linked to the cohort. A patient can appear in several visit categories, so category-level patient counts must never be added together to recover the cohort population.
The values below are illustrative results from a reference deployment at the time this scenario was prepared, and change as the underlying data changes.
| Field | Result |
|---|---|
cohort_patient_count | 77 |
patients_with_visits | 75 |
patients_without_visits | 2 |
total_visits | 593 |
| Visit type | Visit count | Patient count |
|---|---|---|
| Outpatient Visit | 416 | 72 |
| No matching concept | 64 | 10 |
| Inpatient Visit | 52 | 29 |
| Emergency Room Visit | 41 | 39 |
| Laboratory Visit | 9 | 4 |
| Office Visit | 6 | 4 |
| Telehealth | 3 | 3 |
| Ambulatory Clinic / Center | 2 | 2 |
Read together: 77 patients, 75 of them with at least one visit, 2 with no linked visit records, and 593 visits across eight overlapping categories. No matching concept marks records where a standard visit concept name could not be resolved. Review those records rather than discarding them.
Database Explorer keeps the SQL in Query History for reference. Record the finalized queries and results in your approved study documentation system when formal traceability is required.
Step 9: Check cost data availability
- Stay in Data Integration > Database Explorer, on the SQL Query tab, against OMOP Database (Default).
- Replace
COHORT_IDwith the same cohort UUID and run the query below. In the walkthrough this step is labeled Cost availability for the cohort.
WITH cohort_patients AS (
SELECT DISTINCT
cm.patient_id::int AS person_id
FROM tpj.cohort_membership cm
WHERE cm.cohort_id = 'COHORT_ID'::uuid
AND COALESCE(cm.is_es_only, false) = false
),
cost_events AS (
SELECT visit_occurrence_id AS event_id, person_id, 'visit' AS domain
FROM tpj.visit_occurrence
UNION ALL
SELECT drug_exposure_id, person_id, 'drug'
FROM tpj.drug_exposure
UNION ALL
SELECT procedure_occurrence_id, person_id, 'procedure'
FROM tpj.procedure_occurrence
UNION ALL
SELECT device_exposure_id, person_id, 'device'
FROM tpj.device_exposure
UNION ALL
SELECT observation_id, person_id, 'observation'
FROM tpj.observation
UNION ALL
SELECT measurement_id, person_id, 'measurement'
FROM tpj.measurement
),
cohort_costs AS (
SELECT
ce.person_id,
c.cost_id,
c.cost_domain_id,
c.total_charge,
c.total_cost,
c.total_paid,
c.paid_by_payer,
c.paid_by_patient,
c.amount_allowed
FROM tpj.cost c
JOIN cost_events ce
ON ce.event_id = c.cost_event_id
AND ce.domain = LOWER(c.cost_domain_id)
JOIN cohort_patients cp
ON cp.person_id = ce.person_id
)
SELECT
(SELECT COUNT(*) FROM cohort_patients) AS cohort_patient_count,
COUNT(DISTINCT person_id) AS patients_with_cost_data,
COUNT(DISTINCT cost_id) AS cost_record_count,
COUNT(total_cost) AS records_with_total_cost,
SUM(total_charge) AS total_charge,
SUM(total_cost) AS total_cost,
SUM(total_paid) AS total_paid,
SUM(paid_by_payer) AS paid_by_payer,
SUM(paid_by_patient) AS paid_by_patient,
SUM(amount_allowed) AS amount_allowed
FROM cohort_costs;
Four counts tell you whether cost outcomes are possible at all: cohort_patient_count is the cohort size, patients_with_cost_data is the number of cohort patients linked to at least one OMOP cost record, cost_record_count is the number of linked records from tpj.cost, and records_with_total_cost is how many of those carry a populated total_cost. The six monetary columns sum the corresponding tpj.cost fields across the linked records.
Interpret the result by these rules:
cost_record_count > 0means cost records are available for the cohort.cost_record_count = 0means no linked OMOP cost records are available.- A populated record with a NULL monetary field means that field is not populated in the available data.
- A missing or NULL monetary value is never zero cost. Document it as missing.
The values below are illustrative results from a reference deployment and change as the underlying data changes.
| Field | Result |
|---|---|
cohort_patient_count | 77 |
patients_with_cost_data | 77 |
cost_record_count | 369 |
records_with_total_cost | 369 |
total_charge | 18856.80 |
total_cost | 15714.00 |
total_paid | 14142.60 |
paid_by_payer | 11785.50 |
paid_by_patient | 2357.10 |
amount_allowed | 14928.30 |
In that deployment every cohort patient had at least one linked cost record once sample cost data was loaded, with visit-linked and drug-linked costs both contributing. The per-record amounts were kept in a modest illustrative range and are not real-world claim amounts. A deployment with no cost rows returns patients_with_cost_data = 0 and cost_record_count = 0 from the same query. That means cost outcomes cannot be calculated from the available data, not that these patients incurred no healthcare cost.
Optional: review available costs by OMOP domain
When cost_record_count > 0, run this query with the same cohort UUID to group the available cost records by their OMOP domain.
WITH cohort_patients AS (
SELECT DISTINCT
cm.patient_id::int AS person_id
FROM tpj.cohort_membership cm
WHERE cm.cohort_id = 'COHORT_ID'::uuid
AND COALESCE(cm.is_es_only, false) = false
),
cost_events AS (
SELECT visit_occurrence_id AS event_id, person_id, 'visit' AS domain
FROM tpj.visit_occurrence
UNION ALL
SELECT drug_exposure_id, person_id, 'drug'
FROM tpj.drug_exposure
UNION ALL
SELECT procedure_occurrence_id, person_id, 'procedure'
FROM tpj.procedure_occurrence
UNION ALL
SELECT device_exposure_id, person_id, 'device'
FROM tpj.device_exposure
UNION ALL
SELECT observation_id, person_id, 'observation'
FROM tpj.observation
UNION ALL
SELECT measurement_id, person_id, 'measurement'
FROM tpj.measurement
)
SELECT
c.cost_domain_id,
COUNT(DISTINCT c.cost_id) AS cost_record_count,
COUNT(DISTINCT ce.person_id) AS patient_count,
COUNT(c.total_cost) AS records_with_total_cost,
SUM(c.total_charge) AS total_charge,
SUM(c.total_cost) AS total_cost,
SUM(c.total_paid) AS total_paid,
SUM(c.paid_by_payer) AS paid_by_payer,
SUM(c.paid_by_patient) AS paid_by_patient,
SUM(c.amount_allowed) AS amount_allowed
FROM tpj.cost c
JOIN cost_events ce
ON ce.event_id = c.cost_event_id
AND ce.domain = LOWER(c.cost_domain_id)
JOIN cohort_patients cp
ON cp.person_id = ce.person_id
GROUP BY c.cost_domain_id
ORDER BY cost_record_count DESC;
The same reference cohort returned two domains. These counts are illustrative.
cost_domain_id | cost_record_count |
|---|---|
| Visit | 215 |
| Drug | 154 |
Record which OMOP domains and which monetary fields hold usable cost information. If no cost records are available, skip this query and document the finding instead: no linked OMOP cost records were available for the selected cohort, cost outcomes could not be evaluated from the current deployment, and missing cost data was not interpreted as zero cost.
Step 10: Prepare the study output
- Confirm the saved cohort represents the intended study population, and record its name, patient count, and criteria.
- Record the validated diagnosis and medication concepts used in the study definition.
- Record the Assistant timing findings, the co-medication findings, and the Clinical Guidelines comparison notes.
- Record the selected clinical measures and outcome-indicator results at cohort level.
- Record the Database Explorer utilization and cost results.
- Record which representative patient journeys were reviewed, without copying or retaining identifiable patient information outside the approved platform.
- Where the deployment supports cohort or curation export, export the study population for downstream analysis.
- Transfer any exported data only to an approved statistical analysis environment, under the applicable privacy, security, and access-control requirements.
The reference recording ends after the cost-availability review in Database Explorer.
Confirm the study output holds up
- The Assistant request represents the intended study population.
- The diagnosis-date, metformin-exposure-date, and time-to-metformin questions return reviewable result tables.
- The co-medication question returns reviewable drug counts.
- Clinical Guidelines returns a reviewable recommendation that can be compared with the Assistant findings.
- Medical Terminology returns the intended SNOMED diagnosis concepts and the intended RxNorm medication concepts.
- Assistant Covered Concepts align with the Medical Terminology findings, or the differences are reviewed and resolved.
- The saved cohort criteria match the study question, and the patient count is recorded before and after any note-date filter.
- The selected patient contains the expected diagnosis and treatment events, displayed chronologically, with supporting evidence available where expected.
- The clinical measure uses the correct cohort and was reviewed at cohort level before any patient detail.
- The utilization query uses the intended population and is reviewed as visits by type.
- Cost availability is reviewed for the cohort, and any absence is documented as missing rather than zero.
- Any data exported through an enabled workflow complies with the applicable privacy, security, and access-control requirements.
Limitations
- PJI provides the workflow components rather than a single dedicated HEOR screen. The study design is yours; the platform supplies the population, the evidence, and the queries.
- Assistant timing and co-medication answers and Patient Timeline history support exploratory treatment-pattern review. Formal statistical HEOR analysis continues in an external environment.
- Outcome review depends on which clinical measures are configured in your deployment for the target disease. Confirm the measures your protocol needs exist before you commit to them.
- Cost outcomes depend on the OMOP
costtable being populated in your deployment. Check before promising cost analysis in a study protocol. An empty result means cost outcomes cannot be calculated, not that cost was zero. - Utilization analysis depends on populated OMOP visit data. Patients with no linked
visit_occurrencerecords are absent from the utilization breakdown even though they remain in the cohort. - Database Explorer displays utilization and cost query results in the UI and keeps the SQL in Query History. It is not a study documentation system; record finalized queries and results where your organization requires them.
- PJI structures data and surfaces the evidence behind it. Clinicians make the clinical decisions about the care that evidence describes.