Skip to content

schedule_adherence

schedule_adherence ¤

Production schedule adherence: planned quantity vs. what the line made.

OEE answers "how well did the line run?" but not "did it make what the plan asked for?". Those come apart routinely: a line at 92 % OEE running the wrong order is still behind schedule, and a line that lost two hours to a changeover can be on plan if the plan accounted for it.

This detector joins a planned schedule -- period start/end, planned quantity, order id -- against actual production derived from a monotonic counter, and reports per period how far ahead or behind the line is, plus the rate it would have to hold for the rest of the period to recover.

Actual quantities are reset-aware by default (see :mod:ts_shape.utils.counters): a counter that drops to zero at a part change inside a scheduled period would otherwise report the period as catastrophically behind. Pass handle_resets=False for the naive last - first reading.

ProductionScheduleAdherenceEvents ¤

ProductionScheduleAdherenceEvents(
    dataframe: DataFrame,
    schedule: DataFrame,
    *,
    event_uuid: str = "prod:schedule_adherence",
    time_column: str = "systime",
    uuid_column: str = "uuid",
    value_column: str = "value_double",
    schedule_start_column: str = "start",
    schedule_end_column: str = "end",
    planned_qty_column: str = "planned_qty",
    order_id_column: str = "order_id"
)

Bases: Base

Production: schedule vs. actual adherence.

The schedule is a plain DataFrame — one row per planned period::

schedule = pd.DataFrame({
    "start":       pd.to_datetime(["2024-05-02 06:00", "2024-05-02 14:00"]),
    "end":         pd.to_datetime(["2024-05-02 14:00", "2024-05-02 22:00"]),
    "planned_qty": [400, 400],
    "order_id":    ["WO-1001", "WO-1002"],
})

adherence = ProductionScheduleAdherenceEvents(df, schedule)
adherence.adherence_by_period("counter:line-1")

Column names are configurable via the constructor, so an MES export can be passed through without renaming.

Methods: - adherence_by_period: Planned vs. actual and recovery rate per period. - deviation_events: Point events for periods breaching the deviation bands. - adherence_summary: Roll-up across the scheduled horizon.

Initialize the adherence tracker.

Parameters:

Name Type Description Default
dataframe DataFrame

Timeseries frame holding the production counter.

required
schedule DataFrame

Planned schedule, one row per period.

required
event_uuid str

UUID stamped on emitted events.

'prod:schedule_adherence'
time_column str

Timestamp column of dataframe.

'systime'
uuid_column str

Signal-identifier column of dataframe.

'uuid'
value_column str

Counter-value column of dataframe.

'value_double'
schedule_start_column str

Period-start column of schedule.

'start'
schedule_end_column str

Period-end column of schedule.

'end'
planned_qty_column str

Planned-quantity column of schedule.

'planned_qty'
order_id_column str

Optional order-identifier column of schedule. Missing is fine — the emitted order_id is then None.

'order_id'

Raises:

Type Description
TypeError

If schedule is not a DataFrame.

ValueError

If schedule lacks a required column, or a period ends before it starts.

adherence_by_period ¤

adherence_by_period(
    counter_uuid: str,
    *,
    handle_resets: bool = True,
    as_of: Timestamp | str | None = None,
    ahead_pct: float = 105.0,
    behind_pct: float = 95.0,
    critical_pct: float = 80.0
) -> pd.DataFrame

Compare planned against actual production for every scheduled period.

recovery_required_rate is the units per hour the line must hold over the time still left in the period to finish on plan, evaluated at as_of. It is 0 for a period already at or above plan and NaN once the period is over — a finished period cannot be recovered, and reporting a rate for it would invite planning against a number that means nothing.

Parameters:

Name Type Description Default
counter_uuid str

Identifier of the monotonic production counter.

required
handle_resets bool

Treat a decreasing counter as a reset and count the post-reset value as new production. Default True. The increments are computed once over the whole counter signal; a reading reports the production since the previous reading, so a period [start, end] gets the increments of the readings in (start, end]. The production between the last reading before start and the first reading in the period is counted, and a reading on a boundary shared by two periods is booked once, to the period ending there. When False, actual_qty is max(0, last - first) of the readings in [start, end].

True
as_of Timestamp | str | None

Evaluation time for the recovery rate. Defaults to the last timestamp present in the data.

None
ahead_pct float

Adherence above this is ahead.

105.0
behind_pct float

Adherence below this is behind.

95.0
critical_pct float

Adherence below this is critical_behind.

80.0

Returns:

Type Description
DataFrame

Summary-shape DataFrame: start, end, duration_seconds,

DataFrame

uuid, source_uuid, order_id, planned_qty,

DataFrame

actual_qty, schedule_adherence_pct, deviation_units,

DataFrame

deviation_type, recovery_required_rate,

DataFrame

remaining_seconds, sample_count, resets.

Raises:

Type Description
ValueError

If the band thresholds are not ordered critical_pct <= behind_pct <= ahead_pct.

deviation_events ¤

deviation_events(
    counter_uuid: str,
    *,
    handle_resets: bool = True,
    as_of: Timestamp | str | None = None,
    ahead_pct: float = 105.0,
    behind_pct: float = 95.0,
    critical_pct: float = 80.0,
    include_ahead: bool = False
) -> pd.DataFrame

Emit one event per period that deviates from plan.

Parameters:

Name Type Description Default
counter_uuid str

Identifier of the monotonic production counter.

required
handle_resets bool

See :meth:adherence_by_period.

True
as_of Timestamp | str | None

See :meth:adherence_by_period.

None
ahead_pct float

Adherence above this is ahead.

105.0
behind_pct float

Adherence below this is behind.

95.0
critical_pct float

Adherence below this is critical_behind.

80.0
include_ahead bool

Also emit rows for periods running ahead of plan. Off by default — running ahead is rarely an alert, but it is worth reporting when the plan feeds a downstream commitment.

False

Returns:

Type Description
DataFrame

Point-shape DataFrame: systime, uuid, source_uuid,

DataFrame

order_id, planned_qty, actual_qty,

DataFrame

schedule_adherence_pct, deviation_units,

DataFrame

deviation_type, recovery_required_rate, severity,

DataFrame

direction, threshold.

adherence_summary ¤

adherence_summary(
    counter_uuid: str,
    *,
    handle_resets: bool = True,
    as_of: Timestamp | str | None = None,
    ahead_pct: float = 105.0,
    behind_pct: float = 95.0,
    critical_pct: float = 80.0
) -> pd.DataFrame

Roll the per-period adherence up across the scheduled horizon.

overall_adherence_pct is computed from the summed quantities, not as the mean of the per-period percentages: a period with a planned quantity of 10 should not weigh as much as one planning 1000.

Parameters:

Name Type Description Default
counter_uuid str

Identifier of the monotonic production counter.

required
handle_resets bool

See :meth:adherence_by_period.

True
as_of Timestamp | str | None

See :meth:adherence_by_period.

None
ahead_pct float

Adherence above this is ahead.

105.0
behind_pct float

Adherence below this is behind.

95.0
critical_pct float

Adherence below this is critical_behind.

80.0

Returns:

Type Description
DataFrame

Summary-shape DataFrame with a single row: start, end,

DataFrame

duration_seconds, uuid, source_uuid,

DataFrame

total_planned_qty, total_actual_qty,

DataFrame

overall_adherence_pct, deviation_units, periods,

DataFrame

periods_on_track, periods_behind,

DataFrame

periods_critical_behind, periods_ahead,

DataFrame

on_plan_rate_pct, worst_order_id,

DataFrame

worst_adherence_pct, sample_count.