Skip to content

time_accounting

time_accounting ¤

Time and quantity accounting per period (shift, hour, day, ...).

KPIs such as availability, utilization or performance are ratios of durations and quantities within a period. ISO 22400-2 defines them on the time that actually falls inside the period: a stop that starts at 07:00 and lasts until 15:00 contributes 7 h of down time to the 06:00-14:00 shift and 1 h to the 14:00-22:00 shift -- not 8 h to the shift it started in.

Booking a whole interval to the period of its start reading is a common shortcut that breaks every period-level KPI on change-encoded data (one row per transition) and on any interval that crosses a boundary. This module does the accounting correctly:

  • :func:reading_intervals -- one [t_i, t_{i+1}) interval per reading, the reading's state held until the next one (over-long gaps can be marked as "no data").
  • :func:shift_periods, :func:grouper_periods, :func:floor_periods -- the period grid covering a time range: shift windows (overnight shifts and the production day included), pd.Grouper / resample bins, or floor-aligned windows. Wall-clock bounds are localized in the data's timezone, so a DST day lasts 23 h or 25 h.
  • :func:split_intervals -- each interval cut at the period boundaries.
  • :func:time_by_period -- seconds per (period, state).
  • :func:spread_increments -- a counter increment between two readings spread over (t_prev, t] in proportion to time, so the parts produced across a boundary are shared between the periods they were produced in.

reading_intervals ¤

reading_intervals(
    times: Series, states: Series, *, max_gap: Any = None
) -> pd.DataFrame

One interval per reading: its state held until the next reading.

Parameters:

Name Type Description Default
times Series

Reading times of one signal, sorted.

required
states Series

State per reading, aligned with times.

required
max_gap Any

Optional cap ("15min", Timedelta). An interval longer than this is a logging gap: its state becomes :data:EXCLUDED.

None

Returns:

Type Description
DataFrame

DataFrame with start, end, state. The last reading has no

DataFrame

successor and opens no interval.

shift_periods ¤

shift_periods(
    t0: Timestamp,
    t1: Timestamp,
    shift_definitions: Mapping[str, tuple[str, str]],
    *,
    production_day: bool = True
) -> pd.DataFrame

Shift windows covering [t0, t1], labeled like the readings would be.

Each window is labeled with the shift that :func:~ts_shape.utils.shifts.assign_shift gives a reading in it ("unknown" for time no shift covers) and the date that :func:~ts_shape.utils.shifts.production_dates gives it (production_day=True: an overnight shift is one window on its start date) or the calendar date (production_day=False: an overnight shift is cut at midnight into two windows). Wall-clock bounds are localized in the timezone of t0.

Parameters:

Name Type Description Default
t0 Timestamp

Start of the range.

required
t1 Timestamp

End of the range.

required
shift_definitions Mapping[str, tuple[str, str]]

{name: ("HH:MM", "HH:MM")}.

required
production_day bool

Label by production day (default) or calendar date.

True

Returns:

Type Description
DataFrame

DataFrame with date, shift, start, end -- contiguous,

DataFrame

sorted windows overlapping [t0, t1].

grouper_periods ¤

grouper_periods(
    t0: Timestamp, t1: Timestamp, window: Any
) -> pd.DataFrame

The pd.Grouper(freq=window) bins covering [t0, t1].

Parameters:

Name Type Description Default
t0 Timestamp

Start of the range.

required
t1 Timestamp

End of the range.

required
window Any

Resample frequency ("1h", "1D", "W", ...).

required

Returns:

Type Description
DataFrame

DataFrame with period (the resample label), start and end

DataFrame

(the true bin bounds, calendar-correct on DST days and for

DataFrame

right-labeled frequencies), sorted.

floor_periods ¤

floor_periods(
    t0: Timestamp, t1: Timestamp, window: Any
) -> pd.DataFrame

Windows labeled by floor_local(t, window) covering [t0, t1].

Parameters:

Name Type Description Default
t0 Timestamp

Start of the range.

required
t1 Timestamp

End of the range.

required
window Any

Fixed frequency ("15min", "1h", "8h", "1D").

required

Returns:

Type Description
DataFrame

DataFrame with period (the floored label), start (= label)

DataFrame

and end (the next label; the last window ends one window

DataFrame

later, a calendar day for "D"), sorted.

split_intervals ¤

split_intervals(
    intervals: DataFrame,
    periods: DataFrame,
    *,
    keys: Sequence[str]
) -> pd.DataFrame

Cut each interval at the period boundaries.

Parameters:

Name Type Description Default
intervals DataFrame

start, end and any other columns (carried over).

required
periods DataFrame

Non-overlapping periods with start, end and the keys columns.

required
keys Sequence[str]

Period label columns copied to each piece.

required

Returns:

Type Description
DataFrame

One row per (interval, period) with positive overlap: the keys,

DataFrame

the interval's other columns, start / end of the piece,

DataFrame

seconds and fraction (piece / whole interval).

time_by_period ¤

time_by_period(
    intervals: DataFrame,
    periods: DataFrame,
    *,
    keys: Sequence[str],
    states: Sequence[str]
) -> pd.DataFrame

Seconds per period and state, intervals split at period boundaries.

Parameters:

Name Type Description Default
intervals DataFrame

start, end, state.

required
periods DataFrame

Non-overlapping periods with start, end, keys.

required
keys Sequence[str]

Period label columns.

required
states Sequence[str]

The state labels to report; each becomes a float column of seconds (0 when a period holds none of it). Intervals in other states are ignored.

required

Returns:

Type Description
DataFrame

One row per period that overlaps any interval, in time order, with

DataFrame

the keys, start, end and one column per state.

spread_increments ¤

spread_increments(
    times: Series,
    increments: Series,
    periods: DataFrame,
    *,
    keys: Sequence[str]
) -> pd.DataFrame

Counter increments spread over the time they were produced in.

The increment reported at reading t_i was produced during (t_{i-1}, t_i]. It is shared between the periods that interval overlaps, in proportion to the time in each (constant production rate between two readings). An increment with no preceding reading, or one between two readings at the same time, goes to the period holding its reading.

Parameters:

Name Type Description Default
times Series

Reading times, sorted.

required
increments Series

Increment per reading (aligned with times).

required
periods DataFrame

Non-overlapping periods with start, end, keys.

required
keys Sequence[str]

Period label columns.

required

Returns:

Type Description
DataFrame

DataFrame with the keys and quantity (float), one row per

DataFrame

period that received any share (zero shares included).

counter_by_period ¤

counter_by_period(
    times: Series,
    values: Series,
    periods: DataFrame,
    reading_keys: DataFrame,
    *,
    keys: Sequence[str]
) -> pd.DataFrame

Reset-aware counter quantity and resets per period.

The reset-aware increments (:func:~ts_shape.utils.counters.counter_increments) are spread over the time they were produced in (:func:spread_increments); a reset is a point event and counts in the period of the reading that shows it. Missing readings are skipped.

Parameters:

Name Type Description Default
times Series

Reading times of one counter, sorted.

required
values Series

Counter readings, aligned with times.

required
periods DataFrame

Non-overlapping periods with start, end, keys.

required
reading_keys DataFrame

The keys of each reading's own period, aligned with times; every such period is reported (with 0 when it received no production).

required
keys Sequence[str]

Period label columns.

required

Returns:

Type Description
DataFrame

DataFrame with the keys, quantity (float) and resets

DataFrame

(int), one row per period that holds a reading or received a share,

DataFrame

sorted by the keys.