> For the complete documentation index, see [llms.txt](https://kopens.gitbook.io/plantpulse-platform/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kopens.gitbook.io/plantpulse-platform/developer/plugin-oee.md).

# OEE 계산 로직 상세

## 개요 <a href="#overview" id="overview"></a>

OEE(Overall Equipment Effectiveness)는 설비가 계획된 시간 동안 얼마나 효율적으로 가동되었는지를 종합적으로 나타내는 핵심 KPI입니다. ISO 22400-2:2014 및 SEMI E10 표준에 기반하며, **가용률(Availability) x 성능(Performance) x 품질(Quality)** 세 가지 요소의 곱으로 산출됩니다.

이 문서에서는 OEE 계산에 사용되는 수식, 입력 데이터, 내부 처리 로직을 상세히 설명합니다.

***

## 핵심 계산식 <a href="#formula" id="formula"></a>

### OEE 최종 수식

```
OEE = Availability × Performance × Quality
```

모든 요소는 `[0.0, 1.0]` 범위로 클램프됩니다. 계산 결과가 `NaN`이나 `Infinity`이면 `0.0`으로 대체합니다.

***

### 가용률 (Availability) <a href="#availability" id="availability"></a>

```
                    run_time_ms
Availability = ─────────────────────────────
               planned_production_time_observed_ms
```

| 항목                                    | 설명                         | 단위 |
| ------------------------------------- | -------------------------- | -- |
| `run_time_ms`                         | 설비가 RUN 상태로 가동한 실제 시간      | ms |
| `planned_production_time_observed_ms` | 주문 경과 시간에서 계획 다운타임을 차감한 시간 | ms |

**계획 생산 시간(PPT observed) 산출:**

```
PPT_observed = order_duration_time_ms - (setup_time_ms + planned_stop_time_ms + planned_start_time_ms)
```

* `order_duration_time_ms`: 주문 시작 \~ 현재(또는 주문 종료) 경과 시간
* `setup_time_ms`, `planned_stop_time_ms`, `planned_start_time_ms`: 계획 다운타임 합계

**예시:**

* 주문 경과 시간: 28,800,000ms (8시간)
* 셋업: 1,800,000ms (30분), 계획 정지: 3,600,000ms (1시간)
* PPT observed = 28,800,000 - (1,800,000 + 3,600,000 + 0) = 23,400,000ms
* RUN 시간: 18,720,000ms (5.2시간)
* **가용률 = 18,720,000 / 23,400,000 = 0.80 (80%)**

***

### 성능 (Performance) <a href="#performance" id="performance"></a>

```
                ideal_cycle_time_ms × total_production_count
Performance = ──────────────────────────────────────────────
                              run_time_ms
```

| 항목                       | 설명                                              | 단위 |
| ------------------------ | ----------------------------------------------- | -- |
| `ideal_cycle_time_ms`    | 제품 1개 이론 생산 소요 시간 (작업지시의 `time_per_unit_in_ms`) | ms |
| `total_production_count` | 양품 + 불량 총 생산 수량                                 | 개  |
| `run_time_ms`            | 설비 RUN 상태 가동 시간                                 | ms |

**예시:**

* 이상 사이클타임: 60,000ms (1분/개)
* 총 생산 수량: 250개
* RUN 시간: 18,720,000ms
* **성능 = (60,000 × 250) / 18,720,000 = 0.80 (80%)**

***

### 품질 (Quality) <a href="#quality" id="quality"></a>

```
              total_good_count
Quality = ─────────────────────
          total_production_count
```

| 항목                       | 설명              | 단위 |
| ------------------------ | --------------- | -- |
| `total_good_count`       | 양품 수량           | 개  |
| `total_production_count` | 양품 + 불량 총 생산 수량 | 개  |

총 생산 수량이 0이면 품질은 `0.0`입니다.

**예시:**

* 양품: 240개, 불량: 10개
* **품질 = 240 / 250 = 0.96 (96%)**

***

### OEE 종합 예시

위 예시를 종합하면:

```
OEE = 0.80 × 0.80 × 0.96 = 0.6144 (61.44%)
```

***

## 입력 데이터 <a href="#input-data" id="input-data"></a>

OEE 계산에 필요한 데이터는 크게 3가지 소스에서 수집됩니다.

### 1. 작업지시 (Work Order)

PostgreSQL `mm_order` 테이블에서 조회합니다.

| 필드                    | 설명             | 용도                        |
| --------------------- | -------------- | ------------------------- |
| `order_id`            | 작업지시 ID (PK)   | 결과 저장 키                   |
| `target_units`        | 목표 생산 수량       | 생산 달성률 계산                 |
| `time_per_unit_in_ms` | 제품 1개 이론 생산 시간 | `ideal_cycle_time_ms`로 사용 |
| `fix_start_timestamp` | 주문 시작 시간       | 집계 시작점                    |
| `fix_end_timestamp`   | 주문 종료 시간       | 집계 종료점                    |
| `customer_id`         | 고객 ID          | 추적성                       |
| `product_id`          | 제품 ID          | 추적성                       |

### 2. 생산 수량

Cassandra `ASSET_OEE_COUNT_TABLE`에서 집계합니다.

| 필드                  | 설명            |
| ------------------- | ------------- |
| `total_good_count`  | 양품 수량 합계      |
| `total_bad_count`   | 불량 수량 합계      |
| `defect_cause_code` | 불량 원인 코드 (선택) |

### 3. 설비 상태 시간

Cassandra 상태 전이 테이블에서 설비 상태별 누적 시간을 집계합니다.

| 필드                 | 설명       | 상태             |
| ------------------ | -------- | -------------- |
| `run_ms`           | 정상 가동 시간 | RUN            |
| `idle_ms`          | 유휴/대기 시간 | IDLE           |
| `fault_ms`         | 고장 시간    | FAULT          |
| `stop_ms`          | 정지 시간    | STOP           |
| `setup_ms`         | 셋업/준비 시간 | SETUP          |
| `planned_stop_ms`  | 계획 정지 시간 | PLANNED\_STOP  |
| `planned_start_ms` | 계획 시작 시간 | PLANNED\_START |

각 상태별 전이 횟수(`*_count`)도 함께 집계됩니다.

***

## 설비 상태 모델 <a href="#equipment-status" id="equipment-status"></a>

OEE는 7가지 설비 상태를 추적합니다. 각 상태는 OEE 시간 분류에서 서로 다른 역할을 합니다.

| 상태                 | 색상코드            | OEE 시간 분류 | 설명       |
| ------------------ | --------------- | --------- | -------- |
| **RUN**            | `#2E7D32` (녹색)  | 가치 창출 시간  | 정상 생산 가동 |
| **IDLE**           | `#1565C0` (파란색) | 비계획 다운타임  | 유휴/대기    |
| **FAULT**          | `#C62828` (빨간색) | 비계획 다운타임  | 설비 고장    |
| **STOP**           | `#424242` (회색)  | 비계획 다운타임  | 작업자 정지   |
| **SETUP**          | `#0277BD` (청색)  | 계획 다운타임   | 셋업/준비    |
| **PLANNED\_STOP**  | `#757575` (진회색) | 계획 다운타임   | 계획 정지    |
| **PLANNED\_START** | `#6A1B9A` (보라색) | 계획 다운타임   | 계획 시작    |

### 시간 구조 다이어그램

```
|◄──────────────────── 주문 예정 시간 (order_plan_time) ────────────────────►|
|                                                                          |
|◄───────────── 주문 경과 시간 (order_duration_time) ──────────►|           |
|                                                               |           |
|◄── 계획 다운타임 ──►|◄───── 계획 생산 시간 (PPT observed) ────►|           |
|  SETUP+PLANNED_STOP  |                                        |           |
|  +PLANNED_START      |                                        |           |
|                      |◄── 가동시간 ──►|◄── 비계획 다운타임 ──►|           |
|                      |     (RUN)       |  IDLE+FAULT+STOP     |           |
```

***

## Overflow 클램프 <a href="#overflow-clamp" id="overflow-clamp"></a>

설비 상태 시간의 합이 주문 경과 시간을 초과하면 **우선순위 기반**으로 초과분을 삭감합니다. 가치 창출 시간(RUN)을 최대한 보존하기 위해 우선도가 낮은 상태부터 먼저 삭감합니다.

### 삭감 우선순위 (낮은 것부터 먼저 삭감)

```
삭감 순서:
  1. PLANNED_STOP     ← 가장 먼저 삭감
  2. PLANNED_START
  3. SETUP
  4. STOP
  5. FAULT
  6. IDLE
  7. RUN              ← 가장 마지막에 삭감 (가치 창출 시간 보존)
```

### 처리 방식

각 우선순위 단계에서 초과량(`overflow`)을 해당 상태 시간에서 차감합니다:

```
overflow = 전체 상태 시간 합 - order_duration_time_ms

for each status in priority_order:
    if overflow <= 0: break
    reduction = min(status_time, overflow)
    status_time -= reduction
    overflow -= reduction
```

***

## 생산성 파생 지표 <a href="#derived-metrics" id="derived-metrics"></a>

OEE 계산 과정에서 다음 생산성 지표도 함께 산출됩니다.

### 사이클타임 변형

| 지표                                | 수식                                                    | 설명                     |
| --------------------------------- | ----------------------------------------------------- | ---------------------- |
| `cycle_time_per_unit_run_ms`      | `run_time_ms / total_production_count`                | 실 가동 시간 기준 제품 1개 소요 시간 |
| `cycle_time_per_good_unit_run_ms` | `run_time_ms / total_good_count`                      | 실 가동 시간 기준 양품 1개 소요 시간 |
| `cycle_time_per_unit_duration_ms` | `order_duration_ms / total_production_count`          | 주문 경과 시간 기준            |
| `cycle_time_per_unit_ppt_ms`      | `planned_production_time_ms / total_production_count` | 계획 생산 시간 기준            |

### 처리량 및 재공재고

| 지표                   | 수식                                      | 설명                      |
| -------------------- | --------------------------------------- | ----------------------- |
| `throughput_per_sec` | `total_good_count / order_duration_sec` | 초당 양품 생산율               |
| `lead_time_sec`      | `run_time_sec / total_good_count`       | 양품 1개 생산 리드타임           |
| `wip_estimated`      | `throughput_per_sec × lead_time_sec`    | 재공재고 추정값 (Little's Law) |

### 비율 지표

| 지표                | 수식                                          | 설명           |
| ----------------- | ------------------------------------------- | ------------ |
| `total_good_rate` | `total_good_count / total_production_count` | 양품률          |
| `total_bad_rate`  | `total_bad_count / total_production_count`  | 불량률          |
| `production_rate` | `total_good_count / target_units`           | 목표 대비 생산 달성률 |

***

## 이벤트 트리거 <a href="#event-trigger" id="event-trigger"></a>

OEE는 다음 이벤트 발생 시 실시간으로 재계산됩니다.

| 이벤트                         | 핸들러                         | 트리거 조건                        |
| --------------------------- | --------------------------- | ----------------------------- |
| `EQUIPMENT_STATUS`          | `OEEEquipmentStatusHandler` | 설비 상태 변경 (RUN, IDLE, FAULT 등) |
| `START_ORDER` / `END_ORDER` | `OEEOrderHandler`           | 작업지시 시작/종료                    |
| `GOOD_COUNT` / `BAD_COUNT`  | `OEECountHandler`           | 양품/불량 수량 변경                   |

스케줄 기반 일괄 계산도 `engine.properties` 설정 주기에 따라 병렬(10스레드)로 수행됩니다.

***

## PLANNED\_STOP 락 메커니즘 <a href="#planned-stop-lock" id="planned-stop-lock"></a>

`PLANNED_STOP` 상태에서는 `PLANNED_START`가 수신될 때까지 다른 상태 변경을 거부합니다. 계획 정지 기간 중 데이터 무결성을 보장하는 역할입니다.

```
정상 상태 전이:  RUN ↔ IDLE ↔ FAULT ↔ STOP  (자유 전이)
                        │
                        ▼
               PLANNED_STOP 수신 → 락 시작
                        │
               PLANNED_START만 허용 (그 외 무시 + WARN 로그)
                        │
               PLANNED_START 수신 → 락 해제 → 정상 복귀
```

### 계획정지 유형 코드

| 코드                | 설명           |
| ----------------- | ------------ |
| `PM`              | 정기 점검/유지보수   |
| `CLEANING`        | 세척/클리닝       |
| `CALIBRATION`     | 설비 교정        |
| `BREAK`           | 휴식/식사 시간     |
| `MEETING`         | 회의/교육        |
| `REGULATORY_STOP` | 법정/규제 준수 정지  |
| `NON_WORKING_DAY` | 비가동일/공휴일     |
| `ENERGY_SAVING`   | 에너지 절약/부하 관리 |

***

## 계산 흐름 (9단계) <a href="#calculation-flow" id="calculation-flow"></a>

```
calculate(current_timestamp, asset)
│
├── 1. 주문 조회
│      OrderDAO.selectCurrentAssetOrder(asset_id)
│      → 활성 주문이 없으면 계산 스킵
│      → PAUSED 주문이면 기존 결과 유지
│
├── 2. 기간 클리핑
│      effective_end = min(now, order_end)
│      order_duration = effective_end - order_start
│
├── 3. 생산 수량 집계
│      Cassandra에서 양품/불량 수량 조회
│      total_production_count = good + bad
│
├── 4. 설비 상태 시간 집계 + Overflow 클램프
│      7가지 상태별 누적 시간 조회
│      합계 > order_duration이면 우선순위 기반 삭감
│
├── 5. OEE 3요소 계산
│      availability = run_time / PPT_observed
│      performance = (ideal_cycle_time × count) / run_time
│      quality = good_count / total_count
│      oee = availability × performance × quality
│
├── 6. 생산성 지표 계산
│      throughput, lead_time, wip, cycle_times
│
├── 7. 알람 집계
│      alarm_count (INFO/WARN/ERROR 레벨별)
│
├── 8. 결과 JSON 조립 (74+ 필드)
│
└── 9. 저장 + 이벤트 발행
       ├── PostgreSQL mm_order_oee UPSERT
       ├── Cassandra 듀얼 라이트 (현재값 + 히스토리)
       └── CEPClient.sendEvent(OEE) → ProductionChangeEventBus
```

***

## 데이터베이스 스키마 <a href="#database" id="database"></a>

### PostgreSQL — mm\_order\_oee

주문별 최신 OEE 스냅샷을 UPSERT 방식으로 관리합니다. PK는 `order_id`입니다.

| 컬럼 그룹        | 주요 컬럼                                                                                                                                                                                    |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **식별**       | `order_id`, `insert_timestamp`, `scope`                                                                                                                                                  |
| **위치**       | `site_id`, `area_id`, `line_id`, `asset_id`                                                                                                                                              |
| **주문**       | `order_start_timestamp`, `order_end_timestamp`, `effective_end_timestamp`                                                                                                                |
| **제품**       | `customer_id`, `product_id`, `target_units`, `time_per_unit_in_ms`                                                                                                                       |
| **사이클타임**    | `ideal_cycle_time_ms`, `ideal_run_rate_ms`                                                                                                                                               |
| **생산 수량**    | `total_production_count`, `total_good_count`, `total_bad_count`                                                                                                                          |
| **생산 비율**    | `total_good_rate`, `total_bad_rate`, `production_rate`                                                                                                                                   |
| **시간 (절대값)** | `order_plan_time_ms`, `order_scheduled_time_ms`, `order_duration_time_ms`                                                                                                                |
| **다운타임**     | `planned_downtime_total_ms`, `planned_production_time_ms`, `planned_downtime_observed_ms`, `planned_production_time_observed_ms`                                                         |
| **상태별 시간**   | `setup_time_ms`, `idle_time_ms`, `fault_time_ms`, `stop_time_ms`, `run_time_ms`, `down_time_ms`, `operation_time_ms`, `unknown_time_ms`, `planned_stop_time_ms`, `planned_start_time_ms` |
| **상태별 비율**   | `*_time_percent` (위 상태별 시간에 대응)                                                                                                                                                          |
| **상태 전이 횟수** | `idle_time_count`, `fault_time_count`, `stop_time_count`, `run_time_count`, `setup_time_count`, `planned_stop_time_count`, `planned_start_time_count`                                    |
| **진행 상태**    | `progress_status`, `progress_duration_ms`                                                                                                                                                |
| **알람**       | `alarm_count`, `alarm_count_by_info`, `alarm_count_by_warn`, `alarm_count_by_error`                                                                                                      |
| **OEE 지표**   | `availability`, `quality`, `performance`, `oee`                                                                                                                                          |
| **생산성**      | `throughput_per_sec`, `lead_time_sec_from_good_raw`, `lead_time_sec`, `lead_time_source`, `wip_estimated`, `wip_method`                                                                  |
| **메타**       | `last_calculated_timestamp`, `payload` (JSON)                                                                                                                                            |

### Cassandra

| 테이블                                 | PK                                                     | 용도          |
| ----------------------------------- | ------------------------------------------------------ | ----------- |
| `tm_asset_oee`                      | `(site_id), area_id, line_id, asset_id, order_id`      | 설비별 OEE 현재값 |
| `tm_asset_oee_history`              | `(asset_id), order_id, year, month, day, hour, minute` | OEE 히스토리    |
| `tm_asset_oee_history_by_timestamp` | `(asset_id), timestamp`                                | 타임스탬프 기반 조회 |

***

## 방어 로직 <a href="#safety" id="safety"></a>

| 유틸                  | 동작                                       | 용도       |
| ------------------- | ---------------------------------------- | -------- |
| `safeDiv(num, den)` | `den ≤ 0` 또는 `!isFinite` → `0.0`         | 0 나누기 방지 |
| `clamp01(v)`        | `!isFinite` → `0.0`, 그 외 `[0, 1]` 범위 클램프 | 비율 값 정규화 |
| `max0(v)`           | 음수 → `0L`                                | 음수 시간 방지 |

PAUSED 상태의 주문은 재계산을 스킵하고 마지막 결과를 유지합니다.
