Skip to content

shift_handover

shift_handover ¤

Automated shift handover report generation.

Standardize shift-to-shift communication: - Production summary - Quality summary - Downtime summary - Issues to watch

Two modes of operation: 1. From raw signals: generate_report(counter_uuid, ok_counter_uuid, ...) 2. From pre-computed DataFrames: from_shift_data(production_df, quality_df, downtime_df)

ShiftHandoverEvents ¤

ShiftHandoverEvents(
    dataframe: DataFrame,
    *,
    time_column: str = "systime",
    shift_definitions: (
        dict[str, tuple[str, str]] | None
    ) = None
)

Bases: Base

Generate automated shift handover reports.

Combines production, quality, and downtime data into a single summary suitable for shift handover meetings.

Merge keys: [date, shift] — the report output is keyed on these columns.

Two usage patterns:

Pattern A — from raw signals (one-step):

report = ShiftHandoverEvents(df)
result = report.generate_report(
    counter_uuid='prod', ok_counter_uuid='ok',
    nok_counter_uuid='nok', run_state_uuid='state',
    targets={'shift_1': 450, 'shift_2': 450},
)

Pattern B — pipeline (compose from upstream modules):

# Step 1: compute upstream DataFrames
prod   = ShiftReportEvents(df).shift_production('counter')
qual   = QualityEvents(df).nok_by_shift('ok', 'nok')
downtime = DowntimeEvents(df).downtime_by_shift('state')

# Step 2: assemble into handover report
result = ShiftHandoverEvents.from_shift_data(
    production_df=prod,
    quality_df=qual,
    downtime_df=downtime,
    targets={'shift_1': 450, 'shift_2': 450},
)

Both patterns return the same output schema.

Run state may be a string state signal (running_value="Running" in value_string) or a boolean run signal (value_bool, True = running); see :meth:generate_report. downtime_seconds is the downtime in seconds; downtime_minutes is kept as a convenience.

from_shift_data staticmethod ¤

from_shift_data(
    production_df: DataFrame,
    quality_df: DataFrame | None = None,
    downtime_df: DataFrame | None = None,
    *,
    targets: dict[str, float] | None = None,
    report_date: str | None = None
) -> pd.DataFrame

Build a handover report from pre-computed shift-level DataFrames.

This is the pipeline-friendly entry-point. Instead of reading raw signals, it accepts DataFrames that were already computed by upstream modules (ShiftReportEvents, QualityEvents, DowntimeEvents, etc.).

Parameters:

Name Type Description Default
production_df DataFrame

DataFrame with [date, shift, quantity] — e.g. from ShiftReportEvents.shift_production().

required
quality_df DataFrame | None

DataFrame with [date, shift, ok_parts, nok_parts, quality_pct] — e.g. from QualityEvents.nok_by_shift(). Optional.

None
downtime_df DataFrame | None

DataFrame with [date, shift, availability_pct, downtime_seconds] — e.g. from DowntimeEvents.downtime_by_shift(). Optional. A frame with only the legacy downtime_minutes still works, with a DeprecationWarning, until the next major release.

None
targets dict[str, float] | None

Per-shift production targets (dict).

None
report_date str | None

Specific date (YYYY-MM-DD). If None, uses latest.

None

Returns:

Type Description
DataFrame

DataFrame with columns:

DataFrame

date, shift, production, production_target, production_achievement_pct,

DataFrame

ok_parts, nok_parts, quality_pct, availability_pct, downtime_minutes,

DataFrame

downtime_seconds. When downtime_df carries only one of

DataFrame

downtime_seconds / downtime_minutes, the other is derived

DataFrame

from it.

generate_report ¤

generate_report(
    counter_uuid: str,
    ok_counter_uuid: str,
    nok_counter_uuid: str,
    run_state_uuid: str,
    *,
    targets: dict[str, float] | None = None,
    quality_target_pct: float = 98.0,
    availability_target_pct: float = 90.0,
    running_value: str | bool = "Running",
    value_column_counter: str = "value_integer",
    value_column_state: str = "value_string",
    report_date: str | None = None,
    handle_resets: bool = True,
    production_day: bool = True
) -> pd.DataFrame

Generate a shift handover report from raw timeseries signals.

For pipeline usage with pre-computed DataFrames, use :meth:from_shift_data instead.

Parameters:

Name Type Description Default
counter_uuid str

UUID of production counter.

required
ok_counter_uuid str

UUID of good parts counter.

required
nok_counter_uuid str

UUID of the NOK part counter.

required
run_state_uuid str

UUID of machine state signal.

required
targets dict[str, float] | None

Per-shift production targets.

None
quality_target_pct float

Quality target percentage.

98.0
availability_target_pct float

Availability target percentage.

90.0
running_value str | bool

Value indicating machine is running. A bool matches a boolean run signal (value_bool by default); a boolean run signal is also used, with True as running, when the state rows carry no value_column_state value.

'Running'
value_column_counter str

Column for counter values.

'value_integer'
value_column_state str

Column for state values.

'value_string'
report_date str | None

Specific date (YYYY-MM-DD). If None, uses latest date.

None
handle_resets bool

When True (default), treat a decreasing counter as a reset and count the production after the reset for production, ok_parts and nok_parts. A shift boundary is exactly where counters commonly reset, so max(0, last - first) (handle_resets=False) under-reports any shift containing a reset. The increments are computed once over each whole signal (before report_date is selected); the increment between two readings is spread over the time between them and split at the shift boundaries in proportion to time, so the shifts add up to the signal totals and production / ok_parts / nok_parts can be fractional; with handle_resets=False the production between two shifts' readings is lost. The result gains a resets column with the number of production-counter resets in each shift.

True
production_day bool

When True, an overnight shift's post-midnight readings are attributed to the shift's start date, so a night shift is one contiguous (date, shift) group instead of being split across two calendar dates. Default True. Only affects schedules with an overnight shift.

True

Returns:

Type Description
DataFrame

DataFrame with columns:

DataFrame

start, end, duration_seconds (the shift window from

DataFrame

shift_definitions, tz-aware in the data's tz; an overnight

DataFrame

shift ends on the next day), date, shift, production,

DataFrame

production_target, production_achievement_pct, ok_parts,

DataFrame

nok_parts, quality_pct, availability_pct, downtime_minutes

DataFrame

(convenience), downtime_seconds, and resets (only when

DataFrame

handle_resets=True).

highlight_issues ¤

highlight_issues(
    counter_uuid: str | None = ...,
    ok_counter_uuid: str | None = ...,
    nok_counter_uuid: str | None = ...,
    run_state_uuid: str | None = ...,
    *,
    report_df: DataFrame | None = ...,
    thresholds: dict[str, float] | None = ...,
    targets: dict[str, float] | None = ...,
    running_value: str | bool = ...,
    value_column_counter: str = ...,
    value_column_state: str = ...,
    report_date: str | None = ...,
    as_frame: Literal[True] = ...
) -> pd.DataFrame
highlight_issues(
    counter_uuid: str | None = ...,
    ok_counter_uuid: str | None = ...,
    nok_counter_uuid: str | None = ...,
    run_state_uuid: str | None = ...,
    *,
    report_df: DataFrame | None = ...,
    thresholds: dict[str, float] | None = ...,
    targets: dict[str, float] | None = ...,
    running_value: str | bool = ...,
    value_column_counter: str = ...,
    value_column_state: str = ...,
    report_date: str | None = ...,
    as_frame: Literal[False]
) -> list[dict[str, Any]]
highlight_issues(
    counter_uuid: str | None = None,
    ok_counter_uuid: str | None = None,
    nok_counter_uuid: str | None = None,
    run_state_uuid: str | None = None,
    *,
    report_df: DataFrame | None = None,
    thresholds: dict[str, float] | None = None,
    targets: dict[str, float] | None = None,
    running_value: str | bool = "Running",
    value_column_counter: str = "value_integer",
    value_column_state: str = "value_string",
    report_date: str | None = None,
    as_frame: bool = True
) -> pd.DataFrame | list[dict[str, Any]]

Identify issues that need attention.

Can be called in two ways:

  1. From raw signals (provide UUIDs): highlight_issues('prod', 'ok', 'nok', 'state', thresholds={...})

  2. From a pre-built report DataFrame: highlight_issues(report_df=my_report, thresholds={...})

Parameters:

Name Type Description Default
counter_uuid str | None

UUID of production counter (raw-signal mode).

None
ok_counter_uuid str | None

UUID of good parts counter (raw-signal mode).

None
nok_counter_uuid str | None

UUID of the NOK part counter (raw-signal mode).

None
run_state_uuid str | None

UUID of machine state signal (raw-signal mode).

None
report_df DataFrame | None

Pre-computed report DataFrame (pipeline mode). If provided, UUID arguments are ignored.

None
thresholds dict[str, float] | None

Minimum acceptable values for each metric. Defaults to production_achievement_pct=95, quality_pct=98, availability_pct=90.

None
targets dict[str, float] | None

Per-shift production targets.

None
running_value str | bool

Value indicating machine is running.

'Running'
value_column_counter str

Column for counter values.

'value_integer'
value_column_state str

Column for state values.

'value_string'
report_date str | None

Specific date (YYYY-MM-DD).

None
as_frame bool

When True (default), return a DataFrame (see below). as_frame=False returns the list of dicts; it is deprecated, emits a DeprecationWarning and will be removed in the next major release.

True

Returns:

Type Description
DataFrame | list[dict[str, Any]]

With as_frame=True (default), one row per issue with columns

:attr:`ISSUE_COLUMNS`

start, end, duration_seconds (the shift

DataFrame | list[dict[str, Any]]

window of the report row; NaT / NaN when the report has none,

DataFrame | list[dict[str, Any]]

e.g. one built by :meth:from_shift_data), date, shift, metric,

DataFrame | list[dict[str, Any]]

value, threshold, severity, severity_level (warn / critical).

DataFrame | list[dict[str, Any]]

With as_frame=False (deprecated), a list of dicts with keys

DataFrame | list[dict[str, Any]]

shift, metric, value, threshold, severity.

DataFrame | list[dict[str, Any]]

severity is 'warning' (within 5% of threshold) or 'critical'.

Raises:

Type Description
ValueError

If report_df is None and any of the four signal UUIDs is missing, so no report can be built.