> 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/examples.md).

# 18. 통합 예제 모음

V5 클라이언트로 자주 수행하는 엔드투엔드 시나리오를 모아둡니다.

## 18.1 신규 공장 부팅 — 전체 마스터 데이터 등록

새 사이트를 등록하고, 자산 트리(Area → Line → Equipment) → OPC → Tag 까지 한 번에 셋업하는 시나리오입니다.

```java
import plantpulse.api.v5.APIClient_V5;
import plantpulse.api.v5.dto.request.*;
import plantpulse.api.v5.dto.response.*;

public class BootstrapNewSite {
    public static void main(String[] args) throws Exception {

        try (APIClient_V5 client = new APIClient_V5(
                "HTTPS", "100.68.69.41", 7443, "api", TOKEN, false)) {

            client.connect();

            // 1) 사이트 등록
            SiteRequestV5 site = new SiteRequestV5();
            site.setSite_id("SITE_DJ");
            site.setSite_name("대전 1공장");
            site.setLat("36.3504");
            site.setLng("127.3845");
            client.site().create(site);

            // 2) Area
            AssetRequestV5 area = new AssetRequestV5();
            area.setSite_id("SITE_DJ");
            area.setParent_asset_id("SITE_DJ");
            area.setAsset_id("ASSET_DJ_A_0001");
            area.setAsset_name("조립구역");
            area.setAsset_type("A");
            client.asset().create(area);

            // 3) Line
            AssetRequestV5 line = new AssetRequestV5();
            line.setSite_id("SITE_DJ");
            line.setParent_asset_id("ASSET_DJ_A_0001");
            line.setAsset_id("ASSET_DJ_L_0001");
            line.setAsset_name("1번 조립 라인");
            line.setAsset_type("L");
            client.asset().create(line);

            // 4) Equipment (3대)
            for (int i = 1; i <= 3; i++) {
                AssetRequestV5 eq = new AssetRequestV5();
                eq.setSite_id("SITE_DJ");
                eq.setParent_asset_id("ASSET_DJ_L_0001");
                eq.setAsset_id(String.format("ASSET_DJ_M_%04d", i));
                eq.setAsset_name("CNC #" + i);
                eq.setAsset_type("M");
                eq.setTable_type("CNC");
                client.asset().create(eq);
            }

            // 5) OPC (PLC)
            OPCRequestV5 plc = new OPCRequestV5();
            plc.setOpc_id("OPC_PLC_L1_001");
            plc.setOpc_name("Line1 메인 PLC");
            plc.setOpc_type("PLC");
            plc.setOpc_sub_type("SIEMENS_S7");
            plc.setSite_id("SITE_DJ");
            plc.setOpc_server_ip("192.168.10.50");
            plc.setOpc_agent_ip("192.168.10.5");
            plc.setOpc_agent_port(60000);
            client.opc().create(plc);

            // 6) Tag (각 Equipment에 RPM/온도/카운트 3개씩)
            for (int i = 1; i <= 3; i++) {
                String equipId = String.format("ASSET_DJ_M_%04d", i);
                for (String metric : new String[]{"RPM", "TEMP", "COUNT"}) {
                    TagRequestV5 tag = new TagRequestV5();
                    tag.setSite_id("SITE_DJ");
                    tag.setOpc_id("OPC_PLC_L1_001");
                    tag.setLinked_asset_id(equipId);
                    tag.setTag_id(String.format("TAG_PLC_L1_M%04d_%s", i, metric));
                    tag.setTag_name("CNC#" + i + " " + metric);
                    tag.setTag_source("OPC");
                    tag.setJava_type("Double");
                    tag.setUnit(metricUnit(metric));
                    client.tag().create(tag);
                }
            }

            System.out.println("부팅 완료");
        }
    }

    private static String metricUnit(String m) {
        switch (m) {
            case "RPM":   return "RPM";
            case "TEMP":  return "C";
            case "COUNT": return "EA";
            default:      return "";
        }
    }
}
```

## 18.2 ERP 마스터 동기화 (멱등)

ERP에서 받은 고객/제품/직원 데이터를 V5 마스터로 동기화. `exists()` 확인 후 `create()` 또는 `update()` 분기.

```java
public static void syncCustomers(APIClient_V5 client, List<ErpCustomer> erpData) {
    for (ErpCustomer erp : erpData) {
        String customerId = "CUST_" + erp.code;

        CustomerRequestV5 req = new CustomerRequestV5();
        req.setCustomer_id(customerId);
        req.setCustomer_name(erp.name);
        req.setSite_id(erp.siteId);
        req.setExternal_customer_id(erp.id);
        req.setCompany_code(erp.companyCode);
        req.setManager_name(erp.contact);
        req.setManager_email(erp.email);

        if (client.customer().exists(customerId)) {
            client.customer().update(customerId, req);
        } else {
            client.customer().create(req);
        }
    }
}
```

## 18.3 MES 작업지시 생성·시작·종료

```java
public static void runMesWorkOrder(APIClient_V5 client, MesOrder mes) {
    // 1) Order 생성
    String orderId = "ORD_" + mes.date + "_" + String.format("%03d", mes.seq);
    OrderRequestV5 req = new OrderRequestV5();
    req.setOrder_id(orderId);
    req.setTitle(mes.title);
    req.setSite_id("SITE_DJ");
    req.setAsset_id(mes.equipId);
    req.setProduct_id(mes.productId);
    req.setEmployee_id(mes.operatorId);
    req.setCustomer_id(mes.customerId);
    req.setTarget_units(mes.qty);
    req.setUnit("EA");
    req.setTime_per_unit_in_ms(mes.cycleTime);
    req.setFix_start_timestamp(mes.plannedStart);
    req.setFix_end_timestamp(mes.plannedEnd);
    req.setExternal_order_id(mes.mesOrderId);

    OrderResponseV5 created = client.order().create(req);
    if (created == null) {
        log.error("작업지시 생성 실패: " + orderId);
        return;
    }

    // 2) 작업 시작 (생성 직후가 아닌, 라인 IDLE 신호 받은 시점에 호출)
    if (!client.order().start(orderId)) {
        log.warn("시작 실패 — 현재 상태 확인 필요");
        return;
    }

    // 3) 작업 진행 모니터링 ...
    // ...

    // 4) 작업 종료
    client.order().end(orderId);
}
```

## 18.4 라인 전체 헬스 대시보드

라인 산하 Equipment의 Tag 수, 최근 알람, 현재 작업지시를 한 번에 모으는 대시보드 데이터 수집기.

```java
import java.util.List;
import java.util.stream.Collectors;

public static DashboardSnapshot collectLineHealth(APIClient_V5 client, String lineId) {
    DashboardSnapshot snap = new DashboardSnapshot();

    // 1) Line 산하 Equipment
    List<AssetResponseV5> equipments = client.asset().list().stream()
            .filter(a -> lineId.equals(a.getParent_asset_id()))
            .filter(a -> "M".equals(a.getAsset_type()))
            .collect(Collectors.toList());

    snap.equipmentCount = equipments.size();

    // 2) 각 Equipment 통계
    for (AssetResponseV5 eq : equipments) {
        long tagCount = client.tag().countByAsset(eq.getAsset_id());
        snap.totalTags += tagCount;

        PathResponseV5 path = client.path().getByAsset(eq.getAsset_id());
        snap.paths.put(eq.getAsset_id(),
            String.format("%s > %s > %s > %s",
                path.getSite_name(), path.getArea_name(),
                path.getLine_name(), path.getEquipment_name()));
    }

    // 3) 최근 알람 — 라인 산하 자산만
    List<String> assetIds = equipments.stream()
            .map(AssetResponseV5::getAsset_id)
            .collect(Collectors.toList());

    snap.recentAlarms = client.alarm().list(200).stream()
            .filter(a -> a.getTag_location() != null
                      && assetIds.stream()
                                 .anyMatch(id -> a.getTag_location().contains(id)))
            .collect(Collectors.toList());

    // 4) 현재 진행중 작업지시
    snap.activeOrders = client.order().list().stream()
            .filter(o -> "START".equals(o.getStatus())
                      || "PAUSE".equals(o.getStatus()))
            .filter(o -> assetIds.contains(o.getAsset_id()))
            .collect(Collectors.toList());

    return snap;
}
```

## 18.5 Tag 알람 임계치 일괄 적용

운영 환경 변경에 따라 특정 라인의 모든 온도 태그에 알람 임계치를 일괄 적용.

```java
public static void applyTempAlarmsToLine(APIClient_V5 client, String lineId) {
    // 1) 라인 산하 Equipment
    List<AssetResponseV5> equipments = client.asset().list().stream()
            .filter(a -> lineId.equals(a.getParent_asset_id()))
            .collect(Collectors.toList());

    // 2) 각 Equipment의 온도 태그
    for (AssetResponseV5 eq : equipments) {
        List<TagResponseV5> tempTags = client.tag().listByAsset(eq.getAsset_id())
                .stream()
                .filter(t -> "C".equals(t.getUnit()))
                .collect(Collectors.toList());

        // 3) patchAlarm으로 임계치 일괄 적용 (서버에서 CEP/EQL 재배포)
        for (TagResponseV5 t : tempTags) {
            TagAlarmPatchRequestV5 alarm = new TagAlarmPatchRequestV5();
            alarm.setUse_alarm("Y");
            alarm.setLo_lo("0");
            alarm.setLo("10");
            alarm.setHi("80");
            alarm.setHi_hi("95");
            alarm.setTrip_hi("100");
            alarm.setDuplicate_check_minutes(5);
            alarm.setSend_email("Y");
            client.tag().patchAlarm(t.getTag_id(), alarm);
        }
    }
}
```

## 18.6 점검 일정과 작업지시 충돌 검사

```java
import java.util.Set;
import java.util.stream.Collectors;

public static List<OrderResponseV5> conflictsWithMaintenance(
        APIClient_V5 client, String fromIso, String toIso) {

    // 1) 점검 일정의 자산 ID 집합
    Set<String> mtnAssets = client.calendar().search(fromIso, toIso).stream()
            .filter(c -> "MTN".equals(c.getTarget_type()))
            .map(CalendarResponseV5::getAsset_id)
            .collect(Collectors.toSet());

    // 2) 같은 기간 진행중·예정 작업지시 중 점검 대상 자산
    return client.order().list().stream()
            .filter(o -> "WAIT".equals(o.getStatus()) || "START".equals(o.getStatus()))
            .filter(o -> mtnAssets.contains(o.getAsset_id()))
            .collect(Collectors.toList());
}
```

## 18.7 Flow 헬스 알림

에러가 발생한 Flow를 발견하면 알림을 보내는 모니터링 워커.

```java
public static void monitorFlowHealth(APIClient_V5 client) throws InterruptedException {
    Map<String, Long> lastErrorCounts = new HashMap<>();

    while (true) {
        for (FlowResponseV5 flow : client.flow().list()) {
            long prev = lastErrorCounts.getOrDefault(flow.getFlow_id(), 0L);
            long curr = flow.getError_count();

            if (curr > prev) {
                long newErrors = curr - prev;
                sendAlert(String.format("Flow %s 에서 신규 에러 %d건 발생",
                    flow.getFlow_name(), newErrors));
            }
            lastErrorCounts.put(flow.getFlow_id(), curr);
        }
        Thread.sleep(30_000);
    }
}
```

## 18.8 Tag CUD 라이프사이클 (테스트 코드 패턴)

스모크 테스트나 통합 테스트에서 자주 쓰는 패턴 — 생성/조회/수정/삭제 후 격리 자원 청소까지.

```java
public static void tagCudCycle(APIClient_V5 client) {
    long ts = System.currentTimeMillis();
    String opcId = "OPC_TEST_" + ts;
    String tagId = "TAG_TEST_" + ts;

    // 1) 부모 OPC 생성 (Tag의 FK)
    OPCRequestV5 opcReq = new OPCRequestV5();
    opcReq.setOpc_id(opcId);
    opcReq.setOpc_name("test-opc-" + ts);
    opcReq.setOpc_type("VIRTUAL");
    opcReq.setSite_id("SITE_00001");
    opcReq.setOpc_agent_ip("127.0.0.1");
    opcReq.setOpc_agent_port(0);
    client.opc().create(opcReq);

    try {
        // 2) Tag 생성
        TagRequestV5 req = new TagRequestV5();
        req.setTag_id(tagId);
        req.setTag_name("test-tag-" + ts);
        req.setOpc_id(opcId);
        req.setSite_id("SITE_00001");
        req.setJava_type("Double");
        req.setUnit("C");
        req.setTag_source("OPC");
        TagResponseV5 created = client.tag().create(req);
        assert created != null;

        // 3) 조회 + 존재 확인
        TagResponseV5 fetched = client.tag().get(tagId);
        assert fetched != null;
        assert client.tag().exists(tagId);

        // 4) 수정
        req.setTag_name("test-tag-" + ts + "-updated");
        TagResponseV5 updated = client.tag().update(tagId, req);
        assert updated != null;
        assert req.getTag_name().equals(updated.getTag_name());

        // 5) 삭제
        assert client.tag().delete(tagId);
        assert !client.tag().exists(tagId);
    } finally {
        // 6) 임시 OPC 정리
        client.opc().delete(opcId);
    }
}
```

## 다음 단계

* 도메인별 상세 매뉴얼은 [README](/plantpulse-platform/developer/api-manual.md) 의 목차에서 선택하세요.
* [도메인 ID 규칙](/plantpulse-platform/developer/api-manual/id-rules.md) 으로 ID 설계 원칙을 다시 확인하시면 좋습니다.
