shift_reporting
shift_reporting ¤
Shift-based production reporting.
Simple module for shift summaries: - Production by shift - Shift performance comparison - Shift targets and actuals
ShiftReportEvents ¤
ShiftReportEvents(
dataframe: DataFrame,
*,
time_column: str = "systime",
shift_definitions: (
dict[str, tuple[str, str]] | None
) = None
)
Bases: Base
Simple shift-based production reporting.
Each UUID represents one signal: - counter_uuid: production counter - part_id_uuid: part number (optional)
Merge keys: [date, shift] for shift-level outputs.
Pipeline example::
reporter = ShiftReportEvents(df)
prod = reporter.shift_production('counter')
# → merge with QualityEvents.nok_by_shift() on [date, shift]
# → merge with DowntimeEvents.downtime_by_shift() on [date, shift]
# → feed combined DataFrame into ShiftHandoverEvents.from_shift_data()
Example usage
reporter = ShiftReportEvents(df, shift_definitions={ "day": ("06:00", "14:00"), "afternoon": ("14:00", "22:00"), "night": ("22:00", "06:00"), })
Production per shift¤
shift_prod = reporter.shift_production( counter_uuid='counter_signal', part_id_uuid='part_number_signal' )
Compare shifts¤
comparison = reporter.shift_comparison(counter_uuid='counter_signal')
Initialize shift reporter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataframe
|
DataFrame
|
Input DataFrame with timeseries data |
required |
time_column
|
str
|
Name of timestamp column (default: 'systime') |
'systime'
|
shift_definitions
|
dict[str, tuple[str, str]] | None
|
Dictionary mapping shift names to (start, end) times Default: 3-shift operation (06:00-14:00, 14:00-22:00, 22:00-06:00) |
None
|
Example shift_definitions
{ "shift_1": ("06:00", "14:00"), "shift_2": ("14:00", "22:00"), "shift_3": ("22:00", "06:00"), }
shift_production ¤
shift_production(
counter_uuid: str,
part_id_uuid: str | None = None,
*,
value_column_counter: str = "value_integer",
value_column_part: str = "value_string",
date: str | None = None,
handle_resets: bool = True,
production_day: bool = True
) -> pd.DataFrame
Production quantity per shift.
The reset-aware increments are computed once over the whole counter signal and each one is booked to the shift of the reading that reports it, so the shift quantities add up to the signal total: the parts made between the last reading of one shift and the first reading of the next are counted in the next shift (in the example, 310 parts over 06:00-21:30 are 150 + 160, not 150 + 150). A shift boundary is also where counters commonly reset (shift change, part change, PLC restart); a decreasing counter is treated as a reset and the post-reset production is counted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
counter_uuid
|
str
|
Production counter UUID |
required |
part_id_uuid
|
str | None
|
Part number UUID (optional, for part-specific production) |
None
|
value_column_counter
|
str
|
Column containing counter values |
'value_integer'
|
value_column_part
|
str
|
Column containing part numbers |
'value_string'
|
date
|
str | None
|
Specific date in 'YYYY-MM-DD' format (optional). Selects the readings of that calendar date; their increments are still computed on the full signal, so the first reading of the day carries the production since the reading before it. |
None
|
handle_resets
|
bool
|
When True (default), treat a decreasing counter as a reset and
count the production after the reset instead of losing it. The
result gains a |
True
|
production_day
|
bool
|
When True, the post-midnight portion of an overnight
shift (e.g. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with production by shift: |
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
Example
import ts_shape df = ts_shape.make_timeseries(["counter"], n_points=32, freq="30min", ... start="2025-01-01 06:00", noise=0, drift=310, value_column="value_integer") out = ts_shape.ShiftReportEvents(df).shift_production("counter") out[["date", "shift", "quantity"]] date shift quantity 0 2025-01-01 shift_1 150 1 2025-01-01 shift_2 160
shift_comparison ¤
shift_comparison(
counter_uuid: str,
*,
value_column_counter: str = "value_integer",
days: int = 7
) -> pd.DataFrame
Compare shift performance over recent days.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
counter_uuid
|
str
|
Production counter UUID |
required |
value_column_counter
|
str
|
Column containing counter values |
'value_integer'
|
days
|
int
|
Number of recent days to analyze (default: 7) |
7
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with shift comparison: |
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
Example
import ts_shape df = ts_shape.make_timeseries(["counter"], n_points=96, freq="30min", ... start="2025-01-01 06:00", baseline=10, noise=3, value_column="value_integer") df["value_integer"] = df["value_integer"].cumsum() # running part counter out = ts_shape.ShiftReportEvents(df).shift_comparison("counter") out[["shift", "avg_quantity", "min_quantity", "max_quantity"]] shift avg_quantity min_quantity max_quantity 0 shift_1 150.0 144 156 1 shift_2 163.5 157 170 2 shift_3 156.5 147 166
shift_targets ¤
shift_targets(
counter_uuid: str,
targets: dict[str, float],
*,
value_column_counter: str = "value_integer",
date: str | None = None
) -> pd.DataFrame
Compare actual production to shift targets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
counter_uuid
|
str
|
Production counter UUID |
required |
targets
|
dict[str, float]
|
Dictionary mapping shift names to target quantities |
required |
value_column_counter
|
str
|
Column containing counter values |
'value_integer'
|
date
|
str | None
|
Specific date in 'YYYY-MM-DD' format (optional) |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with target comparison: |
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
DataFrame
|
|
Example
import ts_shape df = ts_shape.make_timeseries(["counter"], n_points=32, freq="30min", ... start="2025-01-01 06:00", noise=0, drift=310, value_column="value_integer") rep = ts_shape.ShiftReportEvents(df) out = rep.shift_targets("counter", targets={"shift_1": 150, "shift_2": 200}) out[["shift", "actual", "target", "variance", "achievement_pct"]] shift actual target variance achievement_pct 0 shift_1 150 150 0 100.0 1 shift_2 160 200 -40 80.0
best_and_worst_shifts ¤
best_and_worst_shifts(
counter_uuid: str,
*,
value_column_counter: str = ...,
days: int = ...,
as_frame: Literal[True] = ...
) -> pd.DataFrame
best_and_worst_shifts(
counter_uuid: str,
*,
value_column_counter: str = ...,
days: int = ...,
as_frame: Literal[False]
) -> dict[str, pd.DataFrame]
best_and_worst_shifts(
counter_uuid: str,
*,
value_column_counter: str = "value_integer",
days: int = 30,
as_frame: bool = True
) -> dict[str, pd.DataFrame] | pd.DataFrame
Identify best and worst performing shifts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
counter_uuid
|
str
|
Production counter UUID |
required |
value_column_counter
|
str
|
Column containing counter values |
'value_integer'
|
days
|
int
|
Number of recent days to analyze (default: 30) |
30
|
as_frame
|
bool
|
When True (default), return a single DataFrame (see
below). |
True
|
Returns:
| Type | Description |
|---|---|
dict[str, DataFrame] | DataFrame
|
With |
dict[str, DataFrame] | DataFrame
|
|
dict[str, DataFrame] | DataFrame
|
|
dict[str, DataFrame] | DataFrame
|
|
dict[str, DataFrame] | DataFrame
|
The best rows come first (highest quantity first), then the worst |
dict[str, DataFrame] | DataFrame
|
rows (lowest quantity first). A shift can appear in both groups |
dict[str, DataFrame] | DataFrame
|
when there are fewer than ten shifts. |
dict[str, DataFrame] | DataFrame
|
With |
dict[str, DataFrame] | DataFrame
|
|
dict[str, DataFrame] | DataFrame
|
|
Example
import ts_shape df = ts_shape.make_timeseries(["counter"], n_points=96, freq="30min", ... start="2025-01-01 06:00", baseline=10, noise=3, value_column="value_integer") df["value_integer"] = df["value_integer"].cumsum() # running part counter ranked = ts_shape.ShiftReportEvents(df).best_and_worst_shifts("counter") best = ranked[ranked["rank_type"] == "best"] best[["date", "shift", "quantity"]].head(3) date shift quantity 0 2025-01-01 shift_2 170 1 2025-01-01 shift_3 166 2 2025-01-02 shift_2 157