Files
secs-gem/include/secsgem/gem/ept_state.hpp
T
raphael 7bff01c363 F1: EptStateMachine (E116 Equipment Performance Tracking)
Adds the six E116-0712 §6.2 buckets for classifying equipment time:

  NonScheduledTime (0)   not scheduled to operate
  ScheduledDowntime (1)  planned maintenance window
  UnscheduledDowntime (2) faults / unplanned stoppage
  Engineering (3)        engineering / qualification time
  Standby (4)            idle but available
  Productive (5)         actively producing

Wire-byte values pinned via static_assert to E116 §10.3.

The FSM is a classifier rather than a strict lifecycle — every
(state, event) pair is legal — but it remains data-driven through the
shared CarrierTransitionTable template so the default cross-product is
expressible declaratively.

The state-change handler also surfaces dwell time (how long the
previous state was held) computed off std::chrono::steady_clock, so
accounting code can compute MTBF / availability / utilization from a
single source without maintaining a parallel timestamp log.

4 test cases cover the initial state, every event firing, dwell-time
reporting, and the no-op same-state event (no handler call).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 02:17:16 +02:00

76 lines
2.5 KiB
C++

#pragma once
#include <chrono>
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <vector>
#include "secsgem/gem/carrier_state.hpp" // shared CarrierTransition template
// E116 §6 Equipment Performance Tracking — top-level equipment state
// model that classifies operating time into productive / non-productive
// categories. Each transition is timestamped so the application can
// compute MTBF / availability / utilization from the same source.
//
// The states correspond directly to the SEMI-defined buckets in E116-
// 0712 §6.2; the wire byte values match the order the spec presents.
namespace secsgem::gem {
enum class EptState : uint8_t {
NonScheduledTime = 0, // equipment not scheduled to operate
ScheduledDowntime = 1, // scheduled maintenance
UnscheduledDowntime = 2, // unscheduled downtime (faults)
Engineering = 3, // engineering / qualification time
Standby = 4, // idle, available but not producing
Productive = 5, // actively producing
NoState = 255,
};
const char* ept_state_name(EptState s);
std::optional<EptState> parse_ept_state(const std::string& s);
// Events correspond to transitions the equipment can declare; events
// are intentionally state-neutral (any -> X) since EPT is a "what kind
// of time is this" classifier rather than a strict lifecycle.
enum class EptEvent {
EnterNonScheduled,
EnterScheduledDown,
EnterUnscheduledDown,
EnterEngineering,
EnterStandby,
EnterProductive,
};
const char* ept_event_name(EptEvent e);
using EptTable = CarrierTransitionTable<EptState, EptEvent>;
EptTable default_ept_table();
class EptStateMachine {
public:
using StateChangeHandler =
std::function<void(EptState from, EptState to, EptEvent,
std::chrono::milliseconds dwell_before_change)>;
EptStateMachine();
EptState state() const { return state_; }
void set_state_change_handler(StateChangeHandler h) { on_change_ = std::move(h); }
// Fire a transition. Returns true if a row matched. The dwell time
// (how long we sat in the previous state) is computed off the
// monotonic clock and surfaced to the handler so accounting code
// doesn't need a parallel timestamp tracker.
bool on_event(EptEvent e);
private:
EptTable table_;
EptState state_ = EptState::NonScheduledTime;
std::chrono::steady_clock::time_point entered_;
StateChangeHandler on_change_;
};
} // namespace secsgem::gem