Touch and Dwell
Two distinct measures of “time on a job at a station”. Both are central to the stations view.
Definitions
| Measure | Definition | Source |
|---|---|---|
| Touch | Sum of operator time entries on the job at this station. Many shifts to one job. | oee_time_entries rows where batch_id = job.id AND user_id IS NOT NULL |
| Dwell | Wall-clock since the job arrived at this station. Ticks regardless of operator activity. | NOW() - v_oee_batches.actual_start_ts |
A job is the per-station instance of a batch. When a batch moves to a new station a new job is instantiated there, so Touch and Dwell are always scoped to one (job, station) pair.
Why both
- Touch answers “how much labor have we spent on this job?” — the input to OPS Efficiency (Std Hours ÷ Touch).
- Dwell answers “how long has this job been sitting here?” — catches batches stuck in the station with no operator attention.
A batch with high Dwell but flat Touch is stalled — operators left, the batch hasn’t moved, but no labor is being recorded. The station dashboard surfaces this as a flat segment on the Touch chart.
Standard for comparison
standard_hours is configured per (product, station) in oee_product_equipment_config. It’s the expected Touch on this product at this station.
The station view derives three warning levels from Touch vs standard:
| Level | Trigger |
|---|---|
| green | Touch < 1× standard |
| yellow | Touch ≥ 1× standard |
| red | Touch ≥ 2× standard |
If no standard is configured for the (product, station) pair, the job has no warning level — the system never defaults to yellow on absent data. KPI tiles render — instead of 0.0.
Touch sum formula
SUM(CASE
WHEN te.duration_override_minutes IS NOT NULL THEN te.duration_override_minutes
WHEN te.end_ts IS NOT NULL THEN EXTRACT(EPOCH FROM (te.end_ts - te.start_ts)) / 60.0
ELSE EXTRACT(EPOCH FROM (NOW() - te.start_ts)) / 60.0
END)
FROM oee_time_entries te
WHERE te.batch_id = <job.id>
AND te.user_id IS NOT NULLBoth 'shift'-tagged (live clock-in) and 'labor'-tagged (retroactive gap-fill) entries are valid Touch contributions. The filter is on user_id IS NOT NULL, not on tag.
Schema gotcha — trg_prevent_station_equipment_time_entry
A trigger on oee_time_entries silently DROPs any insert where equipment_id IS NOT NULL AND user_id IS NULL and the equipment is a station. Touch entries must always be operator-scoped — the station context is implied via batch_id (the job belongs to one station).
Synthetic test entries that need to read as Touch must therefore be (user_id NOT NULL, equipment_id NULL, batch_id = job.id), tagged with 'shift' so the My Shifts UI and labor analytics also recognize them.