Purpose & Requirements
A common need is to evaluate the evolution of metrics over time (per day, week, or month). We call these in-time metrics.
Requirements : platform version 2.5.0
Function
Function signature
function metrics.get_intime_metrics (
    _imaging_resource_ids int[],
    _time_from timestamp,
    _time_to timestamp,
    _time_zone text,
    _grouping_time_pd text,
    _include_weekdays int[],
    _change_patient_buffer_minutes float,
    _annotations_restrict_to jsonb,
    _annotations_exclude jsonb,
    _ignore_operating_hours bool default false,
    _delay_threshold_minutes int default 60
)Parameters
| Parameter | Type | Req? | Description | Example | 
|---|---|---|---|---|
| 
 | 
 | ✅ | List of imaging resource IDs to include. | 
 | 
| 
 | 
 | ✅ | Start of the time window (inclusive). | 
 | 
| 
 | 
 | ✅ | End of the time window (inclusive). | 
 | 
| 
 | 
 | ✅ | Time zone for interpreting timestamps. | 
 | 
| 
 | 
 | ✅ | Time grouping unit. Common values:  | 
 | 
| 
 | 
 | ✅ | Days of the week to include ( | 
 | 
| 
 | 
 | ⚙️ | Buffer (minutes) applied when switching patients to avoid inflating utilization. | 
 | 
| 
 | 
 | ⚙️ | Restrict metrics to items with specific annotations. | 
 | 
| 
 | 
 | ⚙️ | Exclude items with specific annotations. | 
 | 
| 
 | 
 | ⚙️ | Ignore configured operating hours if  | 
 | 
| 
 | 
 | ⚙️ | Threshold for considering an appointment/exam as delayed. Default:  | 
 | 
💡 Legend: ✅ Required · ⚙️ Optional (has default or can be null) \EndCondensedTable
Common time zones include:
- U.S.: - America/New_York(ET),- America/Chicago(CT),- America/Los_Angeles(PT)
- Europe: - Europe/London(UK),- Europe/Berlin(CET),- Europe/Paris(CET)
Example: daily metrics for all imaging resources
Try this readable, named-argument call (easier to maintain than remembering the position of all arguments):
select *
from metrics.get_intime_metrics(
  -- all scanners
  _imaging_resource_ids           => (select array_agg(resource_id) from imaging_resource),
  _time_from                      => now()::timestamp - interval '2 months', -- last 2 months
  _time_to                        => now()::timestamp,
  _time_zone                      => 'America/New_York',          -- output time zone
  _grouping_time_pd               => 'day',                       -- per-day buckets
  _include_weekdays               => array[1,2,3,4,5,6,7],        -- mon..sun
  _change_patient_buffer_minutes  => 5,                           -- 5 min pre/post exam 
  _ignore_operating_hours         => false
)
order by marker_date;👉 You’ll get a wide result—each column is a different metric. In the next section, we’ll decode these column names so you know exactly what each represents.
In the meantime, try variations: change 'day' → 'week', restrict weekdays to Monday–Friday with array[1,2,3,4,5], or try using _ignore_operating_hours=true.
- The in-time functions call an internal helper, - metrics.extend_timestamp_range, that snaps your- _time_from/- _time_toto full calendar buckets (in your- _time_zone):- 'day'→ start/end of the day
- 'week'→ start/end of the calendar week
- 'month'→ first/last day of the month
 - This prevents biased results caused by partial first/last buckets, keeps denominators consistent for rates/percentages, and handles DST boundaries cleanly. But be aware that the actual dates may not be exactly what you used as input. 
- The most recent bucket may be partial (e.g., “today”, “this week”). Check - period_is_incomplete = trueand filter it out for finalized reporting, e.g. with- where period_is_incomplete = false.
Column output
Demystifying column names
Metric column names follow a consistent taxonomy and are built using the following pattern: <scope>_<attribute>[_<agg1>]_<agg2>.
\BeginCondensedTable
| Token | Purpose | Examples | 
|---|---|---|
| 
 | What the metric is about | 
 | 
| 
 | What we measure, in snake-case | 
 | 
| 
 | An optional inner (usually daily) aggregate | 
 | 
| 
 | The final aggregate over the bucket | 
 | 
\EndCondensedTable
Examples (how names map to the pattern):
- exam_duration_median→- exam • duration • median
- appt_exam_delayed_pct→- appt_exam • delayed • pct(percentage of delayed appts)
- exam_gap_daily_max_avg→- exam • gap • dailymax • avg(avg of each day’s max gap)
- exam_daily_cnt_per_hour_avg→- exam • cnt_per_hour • daily • avg
- scanner_utilization_avg→- scanner • utilization • avg
The full list of columns returned by metrics.get_intime_metrics is described below:
| Column | Type | Units | Meaning | 
|---|---|---|---|
| marker_date | date | - | Label for the period (start-of-bucket). | 
| period_is_incomplete | bool | - | True if the bucket isn’t fully elapsed (e.g., “today” still in progress). | 
| interval_start | timestamp | - | Start timestamp of the bucket. | 
| interval_end | timestamp | - | End timestamp of the bucket. | 
| exam_start_min | timestamp | - | Earliest exam start in the bucket. | 
| exam_end_max | timestamp | - | Latest exam end in the bucket. | 
| appt_start_min | timestamp | - | Earliest appointment start in the bucket. | 
| appt_end_max | timestamp | - | Latest appointment end in the bucket. | 
| exam_accnumber_cnt | int | count | Exam count, calculated as the number of distinct exam accession numbers. | 
| exam_block_cnt | int | count | Exam count, calculated as the number of contiguous collections of acquisitions ("blocks"). | 
| exam_study_cnt | int | count | Exam count, calculated as the number of distinct study instance UIDs. | 
| appt_accnumber_cnt | int | count | Appointment count, calculated as the number of distinct scheduled accession numbers. | 
| appt_block_cnt | int | count | Appointment count, calculated as the number of appointment blocks scheduled. | 
| appt_order_cnt | int | count | Appointment count, calculated as the number of unique RIS orders linked to appointments. | 
| scanner_utilization_avg | numeric | fraction | Average scanner utilization (use_time/available_time), taking into account the patient buffer. | 
| schedule_utilization_avg | numeric | fraction | Average Schedule utilization (booked/available). | 
| exam_daily_cnt_per_hour_avg | numeric | per hr | Average exams per hour. | 
| appt_daily_cnt_per_hour_avg | numeric | per hr | Average appointments per hour. | 
| exam_gap_avg | numeric | min | Mean gap between consecutive exams. | 
| appt_gap_avg | numeric | min | Mean gap between consecutive appointments. | 
| exam_gap_daily_max_avg | numeric | min | Mean of each day’s max exam gap, averaged over the bucket. | 
| appt_gap_daily_max_avg | numeric | min | Mean of each day’s max appointment gap, averaged over the bucket. | 
| exam_duration_avg | numeric | min | Average Exam duration. | 
| exam_duration_median | numeric | min | Median Exam duration. | 
| appt_duration_avg | numeric | min | Average appointment duration. | 
| appt_duration_median | numeric | min | Median appointment duration. | 
| appt_exam_start_delay_avg | numeric | min | Average (exam_start − appt_start); positive=late start. | 
| appt_exam_start_delay_median | numeric | min | Median start delay. | 
| appt_exam_end_overrun_avg | numeric | min | Average (exam_end − appt_end); positive=overrun. | 
| appt_exam_end_overrun_median | numeric | min | Median end overrun. | 
| appt_exam_delayed_cnt | int | count | Appointments with start delay > threshold. | 
| appt_exam_delayed_pct | numeric | fraction | Share of delayed appointments. | 
| appt_exam_checkin_wait_time_avg | numeric | min | Average time from check-in to exam start. | 
| appt_exam_slot_utilization_avg | numeric | fraction | Average share of appointment slot used by the exam. | 
| scanner_start_finish_interval | numeric | min | First exam start → last exam end span. | 
| schedule_start_finish_interval | numeric | min | First booked slot start → last booked slot end span. | 
| scanner_open_to_start_delay_avg | numeric | min | Average time from scanner open to first exam start. | 
| scanner_close_to_end_overrun_avg | numeric | min | Average time beyond scanner close to last exam end. | 
| schedule_open_to_start_delay_avg | numeric | min | Average time from schedule open to first booked slot start. | 
| schedule_close_to_end_overrun_avg | numeric | min | Average time beyond schedule close to last booked slot end. | 
| schedule_operating_time_sum | numeric | min | Sum of scheduled operating time in the bucket. | 
| schedule_use_time_sum | numeric | min | Sum of booked slot durations (used time). | 
| scanner_use_time_sum | numeric | min | Sum of active scanning time (exam durations). | 
Notes:
- Timestamps are in the specified - _time_zone.
- Fractions are 0–1 (×100 for %). 
- Durations/intervals are minutes unless noted. 
Selecting only what you need
For dashboards or reports, you often don’t need all columns. You can select just a few columns using for example:
select
  marker_date,
  appt_accnumber_cnt,
  exam_accnumber_cnt,
  schedule_utilization_avg,
  scanner_utilization_avg
from metrics.get_intime_metrics(
  _imaging_resource_ids => (select array_agg(resource_id) from imaging_resource),
  _time_from             => now()::timestamp - interval '2 months',     -- last two months
  _time_to               => now()::timestamp,
  _time_zone             => 'America/New_York',
  _grouping_time_pd      => 'day',
  _include_weekdays      => array[1,2,3,4,5,6,7],
  _change_patient_buffer_minutes => 5,
  _ignore_operating_hours  => false
)
order by marker_date;This produces one row per day with key metrics:
- Number of scheduled appointments (unique accession numbers scheduled, - appt_accnumber_cnt)
- Number of completed exams (unique accession numbers performed, - exam_accnumber_cnt)
- Average schedule utilization ( - schedule_utilization_avg)
- Average scanner utilization ( - scanner_utilization_avg)
Variants
Variants by scanner
Sometimes you need to compare machines—spot under/over-utilized scanners, verify balancing across sites, or investigate delays tied to a specific unit. For that, use the per-device (_by_scanner) variant:
select 
  marker_date,
  station_name,
  appt_accnumber_cnt,
  exam_accnumber_cnt,
  schedule_utilization_avg,
  scanner_utilization_avg
from metrics.get_intime_metrics_by_scanner(
  _imaging_resource_ids           => (select array_agg(resource_id) from imaging_resource),
  _time_from                      => now()::timestamp - interval '2 months',
  _time_to                        => now()::timestamp,
  _time_zone                      => 'America/New_York',
  _grouping_time_pd               => 'day',
  _include_weekdays               => array[1,2,3,4,5,6,7],
  _change_patient_buffer_minutes  => 5,
  _ignore_operating_hours         => false
)
order by marker_date, station_name;This returns one row per day per scanner, with a new column station_name, so you can compare activity and utilization across machines.
💡 Tip: If the last bucket includes “today,” check
period_is_incompletebefore drawing conclusions.
Example : Top 5 under-utilized MR scanners in the last 30 days
You can tweak the definition of _imaging_resource_ids to include only MR scanners, and then aggregate the results to identify the top 5 under-utilized MR devices.
For example:
select 
  station_name,
  avg(scanner_utilization_avg) as util_30d_avg
from metrics.get_intime_metrics_by_scanner(
  _imaging_resource_ids           => (select array_agg(resource_id) from imaging_resource inner join imaging_resource_modality using (imaging_resource_id) where modality='MR'),
  _time_from                      => now()::timestamp - interval '30 days',
  _time_to                        => now()::timestamp,
  _time_zone                      => 'America/New_York',
  _grouping_time_pd               => 'day',
  _include_weekdays               => array[1,2,3,4,5,6,7],
  _change_patient_buffer_minutes  => 5,
  _ignore_operating_hours         => false
)
group by station_name
order by util_30d_avg asc
limit 5;Other in-time metric variants
If you want to break results out by patient class, anesthesia status, or section, you can use the following variants:
| function name | Adds column | When to use | 
|---|---|---|
| 
 | 
 | Compare inpatients vs. outpatients (and other site-specific classes). | 
| 
 | 
 | Compare metrics with/without anesthesia or by anesthesia type. | 
| 
 | 
 | Compare across sections defined at your site. | 
Each returns the same columns as get_intime_metrics(...), plus one extra dimension column, and produce one row per time bucket per value of that dimension. Note that values can be site-specific.
Example: by patient class (daily, last 2 months)
select
  marker_date,
  patient_class,
  exam_accnumber_cnt,
  appt_accnumber_cnt,
  scanner_utilization_avg,
  schedule_utilization_avg
from metrics.get_intime_metrics_by_patient_class(
  _imaging_resource_ids           => (select array_agg(resource_id) from imaging_resource),
  _time_from                      => now() - interval '2 months',
  _time_to                        => now(),
  _time_zone                      => 'America/New_York',
  _grouping_time_pd               => 'day',
  _include_weekdays               => array[1,2,3,4,5,6,7],
  _change_patient_buffer_minutes  => 5,
  _ignore_operating_hours         => false
)
order by marker_date, patient_class;Example: by anesthesia type (weekdays only)
select
  marker_date,
  anesthesia_type,
  appt_exam_delayed_pct,
  appt_exam_start_delay_median,
  exam_duration_median
from metrics.get_intime_metrics_by_sedation(
  _imaging_resource_ids           => (select array_agg(resource_id) from imaging_resource),
  _time_from                      => now() - interval '8 weeks',
  _time_to                        => now(),
  _time_zone                      => 'America/New_York',
  _grouping_time_pd               => 'week',
  _include_weekdays               => array[1,2,3,4,5], -- Mon–Fri
  _change_patient_buffer_minutes  => 5,
  _annotations_restrict_to        => null,
  _annotations_exclude            => null,
  _ignore_operating_hours         => false,
  _delay_threshold_minutes        => 60
)
order by marker_date, anesthesia_type;