> 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-ems.md).

# EMS 계산 로직 상세

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

EMS(Energy Management System)는 ISO 50001 에너지 관리 표준에 기반하여 설비의 에너지 소비, 효율, 탄소 배출 지표를 산출하는 플러그인입니다. GHG Protocol(온실가스 프로토콜)에 따라 Scope 1(직접 연료 연소)과 Scope 2(전력 사용) 배출량을 분리 계산합니다.

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

***

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

### 지표 전체 관계도

```
전력량계 센서                     연료 사용 데이터
    │                                │
    ▼                                ▼
┌──────────────┐             ┌──────────────┐
│ 에너지 통계   │             │  연료 통계     │
│ total_kwh    │             │ LNG, Diesel  │
│ peak_kw      │             │ LPG, Gasoline│
│ avg_kw       │             └──────────────┘
└──────────────┘                     │
    │       │                        │
    │       │                        ▼
    │       │               ┌──────────────┐
    │       └──────────────▶│ Scope 1 CO2  │ ← 직접 연료 연소
    │                       │ (연료별 계수)  │
    │                       └──────────────┘
    │                                │
    ▼                                │
┌──────────────┐                     │
│ Scope 2 CO2  │ ← 전력 사용         │
│ (전력 계수)   │                     │
└──────────────┘                     │
    │                                │
    └────────────┬───────────────────┘
                 ▼
         ┌──────────────┐
         │  총 CO2 배출  │
         │ = S1 + S2    │
         └──────────────┘
                 │
    ┌────────────┼────────────┐
    ▼            ▼            ▼
에너지/단위   CO2/단위    에너지 효율
```

***

### 총 에너지 소비량 <a href="#total-energy" id="total-energy"></a>

```
total_energy_kwh = SUM(전력 이벤트의 consumed_kwh)
```

작업지시 구간 내 전력량계 적산값의 총 합계입니다.

| 항목                 | 설명            | 단위  |
| ------------------ | ------------- | --- |
| `total_energy_kwh` | 총 전력 소비량      | kWh |
| `peak_power_kw`    | 구간 내 최대 순시 전력 | kW  |
| `avg_power_kw`     | 구간 내 평균 전력    | kW  |

전력 이벤트가 없으면(`event_count = 0`) 모든 에너지 지표는 `0.0`입니다.

***

### 단위 생산당 에너지 <a href="#energy-per-unit" id="energy-per-unit"></a>

```
                     total_energy_kwh
energy_per_unit = ─────────────────────
                   total_good_count
```

양품 1개를 생산하는 데 소비된 에너지입니다.

| 항목                 | 설명        | 소스           |
| ------------------ | --------- | ------------ |
| `total_energy_kwh` | 총 에너지 소비량 | EMS 에너지 통계   |
| `total_good_count` | 양품 수량     | OEE 계산 결과 참조 |

* 단위: kWh/unit
* 양품 수량이 0이면 `0.0`
* **값이 낮을수록 에너지 효율이 좋음**

**예시:**

* 총 에너지: 1,500.5 kWh, 양품: 10,000개
* **energy\_per\_unit = 1,500.5 / 10,000 = 0.15 kWh/unit**

***

### 에너지 효율 (부하율) <a href="#energy-efficiency" id="energy-efficiency"></a>

```
                     avg_power_kw
energy_efficiency = ──────────────
                    peak_power_kw
```

설비의 전력 사용 안정성을 나타냅니다. \*\*부하율(Load Factor)\*\*이라고도 합니다.

* 범위: `0.0 ~ 1.0`
* 1.0에 가까울수록 일정한 전력을 소비하며 효율적
* 낮을수록 전력 사용에 변동이 큼 (피크 전력 대비 평균이 낮음)
* peak\_power\_kw가 0이거나 total\_energy\_kwh가 0이면 `0.0`

**예시:**

* 평균 전력: 172.6kW, 피크 전력: 210.5kW
* **energy\_efficiency = 172.6 / 210.5 = 0.82 (82%)**
* 전력 사용이 비교적 안정적

***

## 탄소 배출량 계산 <a href="#co2" id="co2"></a>

EMS는 GHG Protocol에 따라 Scope 1(직접 배출)과 Scope 2(간접 배출)를 분리 계산합니다.

### Scope 2 — 전력 사용 배출 <a href="#scope2" id="scope2"></a>

```
scope2_co2_kg = total_energy_kwh × CO2_EMISSION_FACTOR
```

전력 소비에 따른 간접 탄소 배출량입니다.

| 항목                    | 기본값        | 단위         | 설명                    |
| --------------------- | ---------- | ---------- | --------------------- |
| `CO2_EMISSION_FACTOR` | **0.4594** | kg CO2/kWh | 한국 전력 배출계수 (2023년 기준) |

* `mm_config` 테이블의 `EMS_CO2_EMISSION_FACTOR` 키로 오버라이드 가능
* 지역/국가별로 배출계수가 다르므로 설정으로 변경 가능

**예시:**

* 총 에너지: 1,500.5 kWh
* **scope2\_co2 = 1,500.5 × 0.4594 = 689.33 kg CO2**

***

### Scope 1 — 직접 연료 연소 배출 <a href="#scope1" id="scope1"></a>

```
scope1_co2_kg = (LNG_m3 × LNG_FACTOR)
             + (Diesel_L × DIESEL_FACTOR)
             + (LPG_L × LPG_FACTOR)
             + (Gasoline_L × GASOLINE_FACTOR)
```

설비에서 직접 연소하는 연료에 의한 탄소 배출량입니다.

### 연료별 배출계수

| 연료                 | 기본 배출계수   | 단위        | 설정 키                           |
| ------------------ | --------- | --------- | ------------------------------ |
| **LNG** (액화천연가스)   | **2.176** | kg CO2/m3 | `EMS_LNG_EMISSION_FACTOR`      |
| **Diesel** (경유)    | **2.601** | kg CO2/L  | `EMS_DIESEL_EMISSION_FACTOR`   |
| **LPG** (액화석유가스)   | **1.536** | kg CO2/L  | `EMS_LPG_EMISSION_FACTOR`      |
| **Gasoline** (휘발유) | **2.168** | kg CO2/L  | `EMS_GASOLINE_EMISSION_FACTOR` |

* 모든 배출계수는 `mm_config` 테이블에서 오버라이드 가능
* 기본값은 `EMSKeys` 클래스에 상수로 정의

**예시:**

* LNG: 100m3, Diesel: 50L, LPG: 0L, Gasoline: 0L
* **scope1\_co2 = (100 × 2.176) + (50 × 2.601) + 0 + 0 = 217.6 + 130.05 = 347.65 kg CO2**

### 연료 데이터 입력

| 필드               | 타입     | 단위        | 설명      |
| ---------------- | ------ | --------- | ------- |
| `lng_m3`         | double | m3 (입방미터) | LNG 소비량 |
| `diesel_liter`   | double | L (리터)    | 경유 소비량  |
| `lpg_liter`      | double | L (리터)    | LPG 소비량 |
| `gasoline_liter` | double | L (리터)    | 휘발유 소비량 |

***

### 총 CO2 배출량 <a href="#total-co2" id="total-co2"></a>

```
co2_emission_kg = scope1_co2_kg + scope2_co2_kg
```

Scope 1과 Scope 2를 합산한 총 탄소 배출량입니다.

**예시:**

* Scope 1: 347.65 kg, Scope 2: 689.33 kg
* **총 CO2 = 347.65 + 689.33 = 1,036.98 kg CO2**

***

### 단위 생산당 CO2 <a href="#co2-per-unit" id="co2-per-unit"></a>

```
                  co2_emission_kg
co2_per_unit = ─────────────────────
                total_good_count
```

양품 1개 생산에 따른 탄소 배출량입니다.

* 단위: kg CO2/unit
* 양품 수량이 0이면 `0.0`

**예시:**

* 총 CO2: 1,036.98 kg, 양품: 10,000개
* **co2\_per\_unit = 1,036.98 / 10,000 = 0.1037 kg CO2/unit**

***

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

### 데이터 흐름

```
전력량계 센서                         연료 사용 기록
    │                                    │
    ▼                                    ▼
CEP Engine (EQL)                   FUEL_USAGE 이벤트
    │                                    │
    ▼                                    ▼
POWER_USAGE 이벤트               EMSFuelUsageHandler
    │                                    │
    ▼                                    ▼
EMSPowerUsageHandler             Cassandra 연료 이벤트 저장
    │
    ├── 1. 적산 전력 단조증가 검증
    │      (미터 리셋 감지 시 WARN 로그)
    │
    ├── 2. 순시 전력 추정
    │      power_kw = (current_kwh - prev_kwh)
    │                 / ((current_ts - prev_ts) / 3,600,000)
    │
    ├── 3. Cassandra 전력 이벤트 저장
    │
    ├── 4. 활성 주문 확인
    │
    └── 5. EMS 지표 재계산 → mm_order_ems UPSERT
                            + EMSCalculatedEvent 발행
```

### 에너지 통계 (EMSEnergyStatsDTO)

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

| 필드                 | 타입     | 단위  | 설명       |
| ------------------ | ------ | --- | -------- |
| `total_energy_kwh` | double | kWh | 총 전력 소비량 |
| `peak_power_kw`    | double | kW  | 최대 순시 전력 |
| `avg_power_kw`     | double | kW  | 평균 전력    |
| `event_count`      | long   | 건   | 전력 이벤트 수 |

### 연료 통계 (EMSFuelStatsDTO)

Cassandra 연료 이벤트 테이블에서 집계합니다.

| 필드               | 타입     | 단위 | 설명       |
| ---------------- | ------ | -- | -------- |
| `lng_m3`         | double | m3 | LNG 소비량  |
| `diesel_liter`   | double | L  | 경유 소비량   |
| `lpg_liter`      | double | L  | LPG 소비량  |
| `gasoline_liter` | double | L  | 휘발유 소비량  |
| `event_count`    | long   | 건  | 연료 이벤트 수 |

### OEE 참조 데이터

| 필드                 | 용도                                       |
| ------------------ | ---------------------------------------- |
| `total_good_count` | 단위당 에너지/CO2 계산의 분모                       |
| `run_time_ms`      | 가동 시간 (시간 환산: `run_time_ms / 3,600,000`) |

***

## 순시 전력 추정 로직 <a href="#power-estimation" id="power-estimation"></a>

전력량계 센서는 적산 전력(kWh)만 제공하므로, 순시 전력(kW)은 연속된 두 이벤트의 차이로 추정합니다.

```
               consumed_kwh(t) - consumed_kwh(t-1)
power_kw = ─────────────────────────────────────────────
           (timestamp(t) - timestamp(t-1)) / 3,600,000
```

### 미터 리셋 감지

적산 전력은 단조 증가해야 합니다. 현재 값이 이전 값보다 작으면 미터 리셋으로 판단하고 WARN 로그를 출력합니다. 계산은 계속 진행됩니다.

***

## 설정 파라미터 <a href="#config" id="config"></a>

모든 배출계수는 PostgreSQL `mm_config` 테이블에서 오버라이드할 수 있습니다. 설정이 없으면 `EMSKeys` 클래스의 기본값을 사용합니다.

| 설정 키                           | 기본값    | 단위         | 설명                    |
| ------------------------------ | ------ | ---------- | --------------------- |
| `EMS_CO2_EMISSION_FACTOR`      | 0.4594 | kg CO2/kWh | 전력 CO2 배출계수 (한국 2023) |
| `EMS_LNG_EMISSION_FACTOR`      | 2.176  | kg CO2/m3  | LNG 배출계수              |
| `EMS_DIESEL_EMISSION_FACTOR`   | 2.601  | kg CO2/L   | 경유 배출계수               |
| `EMS_LPG_EMISSION_FACTOR`      | 1.536  | kg CO2/L   | LPG 배출계수              |
| `EMS_GASOLINE_EMISSION_FACTOR` | 2.168  | kg CO2/L   | 휘발유 배출계수              |

배출계수를 변경하면 다음 EMS 계산 주기(10초)부터 새 계수가 적용됩니다.

***

## 이벤트 및 트리거 <a href="#events" id="events"></a>

| 이벤트           | 핸들러                    | 처리                            |
| ------------- | ---------------------- | ----------------------------- |
| `POWER_USAGE` | `EMSPowerUsageHandler` | 적산 전력 저장 + 순시 전력 추정 + EMS 재계산 |
| `FUEL_USAGE`  | `EMSFuelUsageHandler`  | 연료 소비 저장 + EMS 재계산            |

### 재계산 주기

1. **이벤트 기반**: `POWER_USAGE` 또는 `FUEL_USAGE` 이벤트 수신 시 즉시 재계산
2. **스케줄 기반**: `EMSCalculationJob`이 10초 주기로 활성 주문의 모든 설비에 대해 일괄 계산 (5 병렬 스레드)

***

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

```
calculate(asset_id, oee, from_ts, to_ts)
│
├── 1. 설정 로드
│      mm_config에서 배출계수 조회 (없으면 기본값)
│
├── 2. OEE 참조 데이터 추출
│      total_good_count, run_time_ms
│
├── 3. 에너지 통계 조회
│      selectAssetEnergyStats(asset_id, from_ts, to_ts)
│      → total_energy_kwh, peak_power_kw, avg_power_kw
│      → event_count = 0 이면 에너지 지표 전체 0
│
├── 4. 단위 에너지 / 효율 계산
│      energy_per_unit = total_kwh / good_count
│      efficiency = avg_kw / peak_kw
│
├── 5. Scope 2 CO2 계산
│      scope2 = total_kwh × co2_factor
│
├── 6. Scope 1 CO2 계산
│      연료 통계 조회 → 연료별 (소비량 × 배출계수) 합산
│
└── 7. 결과 조립 + 저장
       ├── total_co2 = scope1 + scope2
       ├── co2_per_unit = total_co2 / good_count
       ├── run_time_hours = run_time_ms / 3,600,000
       ├── PostgreSQL mm_order_ems UPSERT
       ├── Cassandra 듀얼 라이트
       └── EMSCalculatedEvent 발행
```

***

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

### PostgreSQL — mm\_order\_ems

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

| 컬럼                          | 타입        | 단위       | 설명                  |
| --------------------------- | --------- | -------- | ------------------- |
| `order_id`                  | varchar   | -        | 작업지시 ID (PK)        |
| `insert_timestamp`          | timestamp | -        | 저장 시각               |
| `site_id`                   | varchar   | -        | 사이트 ID              |
| `area_id`                   | varchar   | -        | 영역 ID               |
| `line_id`                   | varchar   | -        | 라인 ID               |
| `asset_id`                  | varchar   | -        | 설비 ID               |
| `total_energy_kwh`          | double    | kWh      | 총 전력 소비량            |
| `energy_per_unit_kwh`       | double    | kWh/unit | 단위 생산당 에너지          |
| `energy_efficiency`         | double    | 0\~1     | 에너지 효율 (부하율)        |
| `co2_emission_kg`           | double    | kg       | 총 CO2 배출량           |
| `co2_per_unit_kg`           | double    | kg/unit  | 단위 생산당 CO2          |
| `peak_power_kw`             | double    | kW       | 피크 전력               |
| `avg_power_kw`              | double    | kW       | 평균 전력               |
| `run_time_hours`            | double    | hours    | 가동 시간               |
| `scope1_co2_kg`             | double    | kg       | Scope 1 CO2 (직접 연료) |
| `scope2_co2_kg`             | double    | kg       | Scope 2 CO2 (전력)    |
| `last_calculated_timestamp` | timestamp | -        | 마지막 계산 시각           |
| `payload`                   | jsonb     | -        | 전체 결과 JSON          |

### Cassandra

| 테이블                                 | PK                                                     | 용도                                     |
| ----------------------------------- | ------------------------------------------------------ | -------------------------------------- |
| `tm_asset_ems`                      | `(site_id), area_id, line_id, asset_id, order_id`      | 설비별 EMS 최신 지표                          |
| `tm_asset_ems_history`              | `(asset_id), order_id, year, month, day, hour, minute` | EMS 이력                                 |
| `tm_asset_ems_history_by_timestamp` | `(asset_id), timestamp`                                | 타임스탬프 기반 조회                            |
| `tm_asset_ems_power_event`          | `(asset_id), timestamp`                                | 전력 이벤트 원본 (`power_kw`, `consumed_kwh`) |

***

## 출력 정밀도 <a href="#precision" id="precision"></a>

| 지표                                                  | 소수점 자릿수 |
| --------------------------------------------------- | ------- |
| `total_energy_kwh`                                  | 2자리     |
| `peak_power_kw`, `avg_power_kw`                     | 2자리     |
| `co2_emission_kg`, `scope1_co2_kg`, `scope2_co2_kg` | 2자리     |
| `energy_per_unit_kwh`                               | 4자리     |
| `energy_efficiency`                                 | 4자리     |
| `co2_per_unit_kg`                                   | 4자리     |
| `run_time_hours`                                    | 2자리     |

***

## 종합 예시 <a href="#example" id="example"></a>

아래는 하나의 작업지시 기간 동안의 EMS 계산 결과 예시입니다.

### 입력 데이터

| 항목             | 값                     |
| -------------- | --------------------- |
| 총 전력 소비        | 1,500.50 kWh          |
| 피크 전력          | 210.50 kW             |
| 평균 전력          | 172.60 kW             |
| 양품 수량 (OEE 참조) | 10,000개               |
| 가동 시간 (OEE 참조) | 30,600,000 ms (8.5시간) |
| LNG 소비         | 100 m3                |
| 경유 소비          | 50 L                  |

### 계산 결과

| 지표          | 계산                           | 결과                  |
| ----------- | ---------------------------- | ------------------- |
| 단위 에너지      | 1,500.50 / 10,000            | **0.1501 kWh/unit** |
| 에너지 효율      | 172.60 / 210.50              | **0.8199**          |
| Scope 2 CO2 | 1,500.50 × 0.4594            | **689.33 kg**       |
| Scope 1 CO2 | (100 × 2.176) + (50 × 2.601) | **347.65 kg**       |
| 총 CO2       | 689.33 + 347.65              | **1,036.98 kg**     |
| 단위 CO2      | 1,036.98 / 10,000            | **0.1037 kg/unit**  |
| 가동 시간       | 30,600,000 / 3,600,000       | **8.50 hours**      |

### 해석

* 양품 1개당 **0.15kWh**의 전력을 소비합니다
* 전력 사용 안정성(부하율)은 \*\*82%\*\*로 양호합니다
* 직접 연료(Scope 1)에서 **347.65kg**, 전력(Scope 2)에서 **689.33kg**의 CO2를 배출합니다
* 양품 1개당 **0.1037kg CO2**를 배출합니다

***

## OEE 의존 관계 <a href="#oee-dependency" id="oee-dependency"></a>

EMS는 OEE 데이터에 의존합니다:

```
OEE Calculator
    │
    ├── total_good_count ──▶  단위 에너지/CO2 계산의 분모
    │
    ├── run_time_ms ─────▶  가동 시간 (시간 환산)
    │
    └── 활성 주문 정보 ───▶  EMS의 집계 윈도우 (from_ts, to_ts)
```

* OEE가 계산되지 않은 설비에서는 EMS도 계산되지 않습니다
* 양품 수량이 0이면 단위당 지표(energy\_per\_unit, co2\_per\_unit)는 `0.0`으로 반환됩니다

***

## 지표 해석 가이드 <a href="#interpretation" id="interpretation"></a>

| 지표         | 좋은 상태         | 주의 필요                   | 활용 방법     |
| ---------- | ------------- | ----------------------- | --------- |
| 단위 에너지     | 낮을수록 좋음       | 증가 추세 시 설비 점검           | 에너지 원가 관리 |
| 에너지 효율     | 1.0에 가까울수록 좋음 | 0.5 이하면 변동 큼            | 전력 품질 관리  |
| CO2 배출량    | 낮을수록 좋음       | 규제 기준 초과 시              | 탄소 배출 보고  |
| 피크 전력      | 계약 전력 이내      | 초과 시 추가 과금              | 피크 전력 관리  |
| Scope 1 비율 | 업종에 따라 다름     | Scope 1 비중 높으면 연료 전환 검토 | 탈탄소 전략 수립 |
