> 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/api-manual/id-rules.md).

# 2. 도메인 ID 규칙

PlantPulse의 모든 마스터 데이터는 **서버측 정규식 검증**을 따릅니다. 도메인 Validator(`SiteValidator`, `OPCValidator`, `AssetValidator`, `TagValidator`, …)가 정규식 매칭을 강제하며, 위반하면 `create()` / `update()` 호출이 `null`을 반환하고 응답 envelope에 `_code=E1002` (VALIDATION\_FAILED)가 포함됩니다. 정확한 ID 설계는 **확장 가능한 IoT 시스템 구축의 출발점**입니다.

> 📖 플랫폼 전반의 컨벤션 요약은 [도메인 ID 네이밍 컨벤션](/plantpulse-platform/developer/id-naming-convention.md) 페이지를 참고하세요.

## 2.1 한눈에 보는 ID 규칙 (서버 정규식 기준)

| 도메인                             | Regex                            | 권장 패턴                               | 예시                                       |
| ------------------------------- | -------------------------------- | ----------------------------------- | ---------------------------------------- |
| **Site**                        | `^V?SITE_[A-Z0-9_]+$`            | `SITE_<토큰>` (또는 `VSITE_…` Virtual)  | `SITE_DJ`, `VSITE_AGG_01`                |
| **OPC**                         | `^(OPC\|EDGE\|TEST)_[A-Z0-9_]+$` | `OPC_<유형>_<번호>` / `EDGE_<위치>_<번호>`  | `OPC_00303`, `EDGE_GW01`, `TEST_VIRTUAL` |
| **Edge**                        | `^(EDGE\|OPC)_[A-Z0-9_]+$`       | `EDGE_<위치>_<번호>`                    | `EDGE_LINE1_01`                          |
| **Asset** (Area/Line/Equipment) | `^ASSET_[A-Z0-9_]+$`             | `ASSET_<사이트>_<타입>_<번호>`             | `ASSET_DJ_L_0001`                        |
| **Tag**                         | `^V*TAG_[A-Z0-9_]+$`             | `TAG_<OPC>_<번호>` (또는 `VTAG_` 가상 태그) | `TAG_EDGE_00303_90007`, `VTAG_OEE_LINE1` |
| **AlarmConfig**                 | `^ALARM_CONFIG_[A-Z0-9_]+$`      | `ALARM_CONFIG_<지표>_<번호>`            | `ALARM_CONFIG_TEMP_HIGH_01`              |
| **Employee**                    | `^EMP_[A-Z0-9_]+$`               | `EMP_<사번>`                          | `EMP_E12345`                             |
| **Customer**                    | `^CUSTOMER_[A-Z0-9_]+$`          | `CUSTOMER_<번호>`                     | `CUSTOMER_001`                           |
| **Product**                     | `^PRODUCT_[A-Z0-9_]+$`           | `PRODUCT_<SKU>`                     | `PRODUCT_A001`                           |
| **Flow**                        | `^FLOW_[A-Z0-9_]+$`              | `FLOW_<용도>_<주기>`                    | `FLOW_OEE_DAILY`                         |
| **User (login)**                | `^[A-Z][A-Z0-9_]*$`              | 대문자 시작, `[A-Z0-9_]`                 | `ADMIN`, `OPERATOR_01`                   |
| Calendar                        | (자유)                             | `CAL_<YYYYMMDD>_<순번>`               | `CAL_20260514_0001`                      |
| Order                           | (자유)                             | `ORD_<YYYYMMDD>_<순번>`               | `ORD_20260514_001`                       |

> ⚠️ **검증 강제**: 위 표에서 정규식이 적힌 모든 도메인은 서버에서 검사합니다. 위반 시 V5는 `null` 반환 + `_code=E1002`, `_message="site_id must match ^V?SITE_[A-Z0-9_]+$"` 등이 응답에 포함됩니다.
>
> ⚠️ **Customer/Product는 전체 단어**: `CUSTOMER_`, `PRODUCT_`가 정확한 접두사입니다. 짧은 `CUST_`, `PROD_`는 거부됩니다.
>
> ⚠️ **API\_ 접두사는 OPC에서 사용 불가**: 외부 API 채널이라도 `opc_id`는 `OPC_` / `EDGE_` / `TEST_` 중 하나로 시작해야 합니다.

## 2.2 Site ID

```
정규식: ^V?SITE_[A-Z0-9_]+$
허용: SITE_… 또는 VSITE_… (Virtual Site — 물리 사이트 없이 만드는 가상/집계용 사이트)
```

### 예시

```java
import plantpulse.api.v5.dto.request.SiteRequestV5;
import plantpulse.api.v5.dto.response.SiteResponseV5;

SiteRequestV5 req = new SiteRequestV5();
req.setSite_id("SITE_DJ");                 // 대전공장
req.setSite_name("대전 1공장");
req.setLat("36.3504");
req.setLng("127.3845");

SiteResponseV5 created = client.site().create(req);
if (created == null) {
    System.err.println("등록 실패 — site_id 정규식 확인 필요");
}

// Virtual Site (테스트·집계용)
SiteRequestV5 vsite = new SiteRequestV5();
vsite.setSite_id("VSITE_AGG_01");
vsite.setSite_name("집계 가상 사이트");
client.site().create(vsite);
```

### ❌ 잘못된 예 (모두 V5에서 `null` 반환)

```java
req.setSite_id("DJ_FACTORY");    // ❌ SITE_ 누락 → E1002
req.setSite_id("site_00001");    // ❌ 소문자 → E1002
req.setSite_id("00001");         // ❌ 접두사 없음 → E1002
req.setSite_id("VVSITE_01");     // ❌ V는 0 또는 1개만 (V?)
```

## 2.3 OPC ID

```
정규식: ^(OPC|EDGE|TEST)_[A-Z0-9_]+$
허용: OPC_…  / EDGE_…  / TEST_…
```

OPC ID는 데이터 수집의 **물리·논리적 출처**를 나타냅니다. 같은 OPC 타입 안에서도 의미 분리가 필요할 때는 `_` 로 계층을 표현합니다.

### OPC Type 별 권장 패턴

| OPC Type     | 용도                                   | 권장 ID 접두              |
| ------------ | ------------------------------------ | --------------------- |
| `OPC`        | OPC UA / OPC DA 서버                   | `OPC_<UA서버이름>_<번호>`   |
| `PLC`        | Siemens, Mitsubishi, Allen-Bradley 등 | `OPC_PLC_<라인>_<번호>`   |
| `MODBUS`     | Modbus TCP/RTU 디바이스                  | `OPC_MB_<장비>_<번호>`    |
| `DATABASE`   | 외부 RDBMS 폴링                          | `OPC_DB_<시스템>`        |
| `FILE`       | REST API/파일 임포트                      | `OPC_FILE_<시스템>_<번호>` |
| `VIRTUAL`    | 테스트·시뮬레이션                            | `TEST_VIRTUAL_<번호>`   |
| Edge Gateway | 엣지 게이트웨이 (별도 `edge_id` 사용)           | `EDGE_<위치>_<번호>`      |

> ⚠️ 과거 문서에서 `API_` 접두사를 언급한 적이 있으나 **현재 서버 검증은 `OPC_`/`EDGE_`/`TEST_`만 허용**합니다. 외부 API/파일 채널이라도 OPC 도메인에서는 `OPC_FILE_…` 형태를 사용하세요.

### 예시

```java
import plantpulse.api.v5.dto.request.OPCRequestV5;

OPCRequestV5 req = new OPCRequestV5();
req.setOpc_id("OPC_EDGE_00303");
req.setOpc_name("Line1 Edge Gateway");
req.setOpc_type("PLC");                    // OPC/PLC/MODBUS/DATABASE/FILE/VIRTUAL
req.setOpc_sub_type("SIEMENS_S7");
req.setSite_id("SITE_DJ");
req.setOpc_server_ip("192.168.10.50");
req.setOpc_agent_ip("192.168.10.5");
req.setOpc_agent_port(60000);
client.opc().create(req);

// ERP 연동 — OPC_FILE_ 접두사 (API_ 아님!)
OPCRequestV5 fileChannel = new OPCRequestV5();
fileChannel.setOpc_id("OPC_FILE_ERP_001");
fileChannel.setOpc_type("FILE");
fileChannel.setSite_id("SITE_DJ");
client.opc().create(fileChannel);

// 테스트·시뮬레이션
OPCRequestV5 testOpc = new OPCRequestV5();
testOpc.setOpc_id("TEST_VIRTUAL_001");
testOpc.setOpc_type("VIRTUAL");
testOpc.setSite_id("SITE_DJ");
client.opc().create(testOpc);
```

## 2.4 Edge ID

```
정규식: ^(EDGE|OPC)_[A-Z0-9_]+$
```

`edge_id`는 엣지 게이트웨이 디바이스 식별자입니다. OPC와 정규식이 거의 같지만 `TEST_` 접두사는 허용되지 않습니다.

```java
req.setEdge_id("EDGE_LINE1_01");
req.setEdge_id("OPC_EDGE_00303");   // OPC도 가능
// req.setEdge_id("TEST_EDGE_01");  // ❌ edge_id는 TEST_ 불가
```

## 2.5 Asset ID — 계층 구조의 핵심

Asset은 **Area → Line → Equipment** 의 3계층 트리를 형성합니다. ID에 타입 코드를 포함시켜 시각적으로 계층을 파악할 수 있게 설계하시는 것을 권장합니다.

```
정규식: ^ASSET_[A-Z0-9_]+$
권장 형식: ASSET_<사이트토큰>_<타입코드>_<번호>
```

### Asset Type 코드

| asset\_type 값 | 의미                      | 권장 ID 패턴             |
| ------------- | ----------------------- | -------------------- |
| `A`           | Area (구역)               | `ASSET_<사이트>_A_<번호>` |
| `L`           | Line (라인)               | `ASSET_<사이트>_L_<번호>` |
| `M`           | Equipment (설비, Machine) | `ASSET_<사이트>_M_<번호>` |

> 💡 **EQUIPMENT 코드가 `M`인 이유**: Equipment = Machine. 검색·필터링에서 짧고 명확한 한 글자 코드를 사용합니다.

### 표준 ID 패턴 예시

대전공장(`SITE_DJ`)의 1번 구역, 그 안의 2번 라인, 그 위의 3번 설비:

```
ASSET_DJ_A_0001     ← Area (1구역)
  └ ASSET_DJ_L_0002 ← Line (2번 라인)
      └ ASSET_DJ_M_0003 ← Equipment (3번 설비)
```

### 예시 — 자산 트리 생성

```java
import plantpulse.api.v5.dto.request.AssetRequestV5;
import plantpulse.api.v5.dto.response.AssetResponseV5;

// 1) 사이트 직속 Area
AssetRequestV5 area = new AssetRequestV5();
area.setSite_id("SITE_DJ");
area.setParent_asset_id("SITE_DJ");        // 부모는 사이트 ID
area.setAsset_id("ASSET_DJ_A_0001");
area.setAsset_name("조립구역");
area.setAsset_type("A");
client.asset().create(area);

// 2) Area 산하 Line
AssetRequestV5 line = new AssetRequestV5();
line.setSite_id("SITE_DJ");
line.setParent_asset_id("ASSET_DJ_A_0001");  // 부모는 Area
line.setAsset_id("ASSET_DJ_L_0001");
line.setAsset_name("1라인");
line.setAsset_type("L");
client.asset().create(line);

// 3) Line 산하 Equipment
AssetRequestV5 equip = new AssetRequestV5();
equip.setSite_id("SITE_DJ");
equip.setParent_asset_id("ASSET_DJ_L_0001"); // 부모는 Line
equip.setAsset_id("ASSET_DJ_M_0001");
equip.setAsset_name("CNC #1");
equip.setAsset_type("M");
equip.setTable_type("CNC");
client.asset().create(equip);
```

### parent\_asset\_id 규칙

| 자식 타입           | parent\_asset\_id 값                    |
| --------------- | -------------------------------------- |
| Area (`A`)      | 사이트 ID (예: `SITE_DJ`)                  |
| Line (`L`)      | Area의 asset\_id (예: `ASSET_DJ_A_0001`) |
| Equipment (`M`) | Line의 asset\_id (예: `ASSET_DJ_L_0001`) |

Equipment 아래에 다시 하위 Equipment를 둘 수도 있습니다(서브어셈블리). 그 경우 parent\_asset\_id는 상위 Equipment의 asset\_id입니다.

## 2.6 Tag ID

```
정규식: ^V*TAG_[A-Z0-9_]+$
허용: TAG_…, VTAG_…, VVTAG_… (V 누적 가능 — Virtual Tag 파생 단계)
```

Tag는 데이터 포인트(센서 값, 카운터, 상태 등)의 정의입니다. OPC와 1:1, Asset과는 0:N (선택적 연결) 관계입니다.

### 권장 패턴

| 시나리오                 | 패턴                       | 예시                     |
| -------------------- | ------------------------ | ---------------------- |
| OPC 연결 태그            | `TAG_<OPC ID 일부>_<채널번호>` | `TAG_EDGE_00303_90007` |
| 자체 정의 태그             | `TAG_<도메인>_<번호>`         | `TAG_PROD_COUNT_001`   |
| Virtual Tag (1단계)    | `VTAG_<용도>_<번호>`         | `VTAG_OEE_LINE1`       |
| Virtual Tag (2단계 파생) | `VVTAG_<용도>_<번호>`        | `VVTAG_AGGREGATED_01`  |

> 💡 **Virtual Tag의 `V` 누적**: 계산식·집계 태그의 파생 단계가 깊어질수록 `V`를 추가합니다. 보통은 `VTAG_` 한 단계로 충분합니다.

### 예시

```java
import plantpulse.api.v5.dto.request.TagRequestV5;

TagRequestV5 req = new TagRequestV5();
req.setTag_id("TAG_EDGE_00303_90007");
req.setTag_name("Spindle RPM");
req.setOpc_id("OPC_EDGE_00303");
req.setSite_id("SITE_DJ");
req.setLinked_asset_id("ASSET_DJ_M_0001");
req.setJava_type("Double");
req.setUnit("RPM");
req.setTag_source("OPC");
req.setDescription("Spindle 회전수 (RPM)");
client.tag().create(req);

// Virtual Tag — 라인별 OEE 집계
TagRequestV5 vtag = new TagRequestV5();
vtag.setTag_id("VTAG_OEE_LINE1");
vtag.setTag_name("Line1 OEE");
vtag.setSite_id("SITE_DJ");
vtag.setTag_source("VIRTUAL");
client.tag().create(vtag);
```

### tag\_id vs tag\_name vs alias\_name

| 필드           | V5에서 변경 가능한가                 | 용도                           |
| ------------ | ---------------------------- | ---------------------------- |
| `tag_id`     | 변경 불가 (재생성 권장)               | 시스템 내부 고유 키. **불변**          |
| `tag_name`   | `update()` 또는 `patchBasic()` | 사용자에게 보이는 이름                 |
| `alias_name` | `patchMetadata()`            | 외부 시스템과 매핑하는 별칭 (예: HMI 태그명) |

> 가능하면 `tag_id`는 **불변**으로 두고, 표시 이름은 `tag_name` / `alias_name`을 사용하시는 것을 권장합니다. tag\_id 변경은 외부 시계열 데이터와의 정합성 이슈가 발생할 수 있습니다.

## 2.7 AlarmConfig / Employee / Customer / Product / Flow

이 도메인들도 모두 **서버 검증이 강제**됩니다. 정확한 전체 접두사를 사용하세요.

```java
// Alarm Config
AlarmConfigRequestV5 alarm = new AlarmConfigRequestV5();
alarm.setAlarm_config_id("ALARM_CONFIG_TEMP_HIGH_01");
alarm.setSite_id("SITE_DJ");

// Employee
EmployeeRequestV5 emp = new EmployeeRequestV5();
emp.setEmployee_id("EMP_E12345");
emp.setRole_code("OPERATOR");                // OPERATOR, SUPERVISOR 등

// Customer — CUSTOMER_ (전체 단어, CUST_ 아님!)
CustomerRequestV5 cust = new CustomerRequestV5();
cust.setCustomer_id("CUSTOMER_001");
cust.setExternal_customer_id("ERP_C001");

// Product — PRODUCT_ (전체 단어, PROD_ 아님!)
ProductRequestV5 prod = new ProductRequestV5();
prod.setProduct_id("PRODUCT_A001");
prod.setProduct_code("SKU-A001");

// Flow
FlowRequestV5 flow = new FlowRequestV5();
flow.setFlow_id("FLOW_OEE_DAILY");
flow.setSite_id("SITE_DJ");
```

### ❌ 자주 발생하는 거부 사례

```java
cust.setCustomer_id("CUST_001");      // ❌ CUST_ 아님 → E1002
prod.setProduct_id("PROD_A001");      // ❌ PROD_ 아님 → E1002
emp.setEmployee_id("E12345");          // ❌ EMP_ 누락 → E1002
flow.setFlow_id("flow_oee_daily");    // ❌ 소문자 → E1002
```

## 2.8 User ID (로그인 계정)

```
정규식: ^[A-Z][A-Z0-9_]*$
규칙: 대문자로 시작, 이후 [A-Z0-9_]만 허용
```

다른 도메인과 달리 **고정 접두사가 없고**, 첫 글자가 대문자여야 합니다.

```
✅ ADMIN
✅ OPERATOR_01
✅ KOPENS_USER
❌ admin              → 소문자 시작
❌ 1USER              → 숫자 시작
❌ _ADMIN             → _ 시작
```

## 2.9 Calendar / Order — 검증 없음

이 두 도메인은 **서버 검증이 없으므로** ID 형식이 자유롭습니다. 다만 운영 일관성과 검색 편의를 위해 권장 패턴을 따라주시기 바랍니다.

### Calendar

```java
import plantpulse.api.v5.dto.request.CalendarRequestV5;

CalendarRequestV5 cal = new CalendarRequestV5();
cal.setCalendar_id("CAL_20260514_0001");   // CAL_<YYYYMMDD>_<순번>
cal.setSite_id("SITE_DJ");
cal.setAsset_id("ASSET_DJ_M_0001");
cal.setTarget_type("MTN");                  // 점검 일정
```

| target\_type | 의미                  |
| ------------ | ------------------- |
| `MTN`        | 정기 점검 (Maintenance) |
| `HOLIDAY`    | 휴일/비조업              |
| `INSPECTION` | 검사                  |
| `MEETING`    | 회의                  |

### Order

```java
import plantpulse.api.v5.dto.request.OrderRequestV5;

OrderRequestV5 order = new OrderRequestV5();
order.setOrder_id("ORD_20260514_001");      // ORD_<YYYYMMDD>_<순번>
order.setSite_id("SITE_DJ");
order.setAsset_id("ASSET_DJ_M_0001");
order.setCustomer_id("CUSTOMER_001");        // CUSTOMER_ 주의!
order.setProduct_id("PRODUCT_A001");         // PRODUCT_ 주의!
order.setEmployee_id("EMP_E12345");
order.setTarget_units(500);
```

작업지시 상태(ISA-88): `WAIT → START → END / ABORTED` (자세한 내용은 [Order 서비스](/plantpulse-platform/developer/api-manual/order-service.md))

## 2.10 ID 설계 모범 사례

### 권장 사항

1. **불변성 유지** — 한번 부여한 ID는 변경하지 마세요. 변경이 필요하면 새 ID로 마이그레이션하시는 것을 권장합니다.
2. **계층을 ID에 표현** — `ASSET_<사이트>_<타입>_<번호>` 처럼 계층 정보를 ID에 포함시키면 운영 가시성이 좋아집니다.
3. **고정 자릿수 사용** — 번호 부분은 `0001`, `00001` 처럼 패딩해 정렬을 유지하세요.
4. **대문자·언더스코어 고정** — 시스템 전반에서 대소문자 혼용은 피하고 `[A-Z0-9_]` 패턴을 유지합니다.
5. **외부 시스템 ID는 별도 필드** — ERP/MES 원본 ID는 `external_*_id` 필드에 보관하고, PlantPulse ID는 자체 체계를 유지하세요.

### 피해야 할 패턴

* ❌ 한글, 공백, 특수문자 사용 — `ASSET_라인1`, `OPC 001`
* ❌ 너무 짧거나 무의미한 ID — `S1`, `A`, `T01`
* ❌ 접두사 누락 또는 잘못된 약어 — `CUST_001` (정답: `CUSTOMER_001`), `PROD_A001` (정답: `PRODUCT_A001`)
* ❌ 의미가 ID에 박혀있어 변경 불가 — `ASSET_DJ_L_OLD_BROKEN_LINE`

## 2.11 검증 실패 응답 확인

V5는 검증 실패 시 메서드가 `null` (또는 `false`)을 반환합니다. 자세한 에러는 envelope에서 확인할 수 있습니다.

```java
import plantpulse.api.v5.service.BaseServiceV5;
import plantpulse.json.JSONObject;

// V5 서비스 내부는 envelope를 외부에 노출하지 않으나,
// 디버그 모드를 켜면 로그에 출력됩니다.
client = new APIClient_V5(proto, host, port, user, token, true);  // debug=true
```

응답 envelope 예시:

```json
{
  "_status": "ERROR",
  "_code": "E1002",
  "_message": "validation failed: site_id must match ^V?SITE_[A-Z0-9_]+$",
  "_http_status": 400
}
```

자세한 에러 코드는 [응답 형식 및 에러 처리](/plantpulse-platform/developer/api-manual/error-handling.md) 참고.

## 다음 단계

* [응답 형식 및 에러 처리](/plantpulse-platform/developer/api-manual/error-handling.md) — 에러 코드 매핑
* [Site 서비스](/plantpulse-platform/developer/api-manual/site-service.md) — 사이트 생성으로 시작
* [Asset 서비스](/plantpulse-platform/developer/api-manual/asset-service.md) — 자산 트리 구축
* [Tag 서비스](/plantpulse-platform/developer/api-manual/tag-service.md) — 데이터 포인트 정의
