본문으로 건너뛰기

DCS·MES·ERP 연동: DeltaV, Siemens, SAP

📍 현재 위치: 제4부 · 현실과 마주하기 — 검증된 히스토리안(historian)과 다리를 놓은 데 이어, 이제 우리는 오픈소스 스택을 그것이 결코 대체하지 못할 세 시스템에 연결합니다. 공장을 돌리는 DCS, 레시피를 실행하는 MES, 그리고 자재와 주문을 소유한 ERP입니다.

쉽게 말하면

병원을 떠올려 보세요. 수술실(DCS)은 환자를 분 단위로 통제합니다. 수술 워크플로 보드(MES)는 어떤 시술이 어느 방에서 일어나는지를 정하고 각 단계가 수행되었음을 서명합니다. 병원의 청구·물품 담당 부서(ERP, 즉 SAP)는 재고, 병상 배정, 그리고 서류를 소유합니다. 아무리 영리한 새 앱이라도 마취를 직접 운용하거나, 수술 기록에 서명하거나, 물품 주문을 발행하게 두지는 않을 것입니다. 그것들은 생명과 기록에 결정적이며 이미 책임 소재가 정해져 있기 때문입니다. 새 앱이 할 수 있는 일은 듣고(수술실이 무엇을 하는지 읽고), 워크플로 보드를 미러링(mirror)하여 분석이 볼 수 있게 하고, 공식 우편 투입구를 통해 물품 부서와 메모를 주고받는 것입니다. 이 장은 그 세 개의 청취 초소와 우편 투입구를 만듭니다 — 그리고 여기서 오픈소스가 진정으로 줄 수 없는 한 가지에 대해 솔직합니다. 바로 신뢰할 만한 GxP MES입니다(GxP는 의약품 제조를 규율하는 Good-x-Practice 규제의 우산입니다 — 제조에 대한 GMP, 시험실에 대한 GLP, 임상에 대한 GCP).

이 장에서 다루는 내용

열일곱 개 장에 걸쳐 우리는 완전한 오픈소스 플랫폼을 만들었고, 지난 장에서는 그것이 상용 히스토리안 곁에서 정중하게 공존하도록 가르쳤습니다. 그 히스토리안은 우호적인 상용 시스템이었습니다. 개방형 프로토콜로 말하고, 우리가 이미 이해하는 시계열을 주고받습니다. 이 장은 더 험한 지형으로 들어갑니다. 여기서 정직한 하이브리드(honest-hybrid)의 경계는 취향이 아니라 필연으로 그어집니다.

  • DeltaVSiemens 제어 데이터를, 검증된 제어 루프에 손대지 않고 OPC UA(개방형의 벤더 중립적 산업 프로토콜)로 오픈소스(OSS) 스택에 읽어 들이기 — DeltaV가 실제로 어떻게 연결되는지(실시간 값·알람·이력을 위한 래퍼 서버 — DA/A&E/HDA), 데이터를 어디에 저장하는지(세 개의 히스토리안, 즉 그 시계열·알람·배치 기록을 보관하는 데이터베이스들), 읽기 전용 NAMUR 개방 아키텍처(NOA) 이음매가 그것을 어떻게 공유하는지까지.
  • 신뢰할 만한 오픈소스 GxP MES는 존재하지 않는다는 냉정한 결론, 그리고 그것이 여러분의 아키텍처에 의미하는 바.
  • 자재, 로트, 작업 지시(work order)를 IDoc과 OData 위의 B2MML/ISA-95 메시지로 SAP/ERP와 교환하기.
  • 이 모든 교환이 왜 멱등(idempotent)하고 조정 가능(reconcilable)해야 하는지, 그리고 우리가 이미 PostgreSQL에 만들어 둔 ISA-88/95 모델(레시피, 장비, 배치를 기술하는 업계 표준 방식 — 절차에 대한 ISA-88, 엔터프라이즈 통합에 대한 ISA-95)이 어떻게 그 모든 것의 착륙장이 되는지.

이 모든 것을 하나로 묶는 실은, 어떤 설계 검토에서도 꺼내 들 수 있는 한 문장입니다. 검증된 DCS, MES, ERP는 여전히 기록 시스템(systems of record)으로 남고, 오픈소스 계층은 그것들을 미러링할 뿐 대체하지 않는다 [12].

세 시스템, 세 개의 다른 문

히스토리안에는 문이 두 개 있었습니다. DCS, MES, ERP는 각자 자신의 문을 가지며, 그것들은 서로 호환되지 않습니다. 함정은 이들을 하나의 "통합" 문제로 취급하는 것입니다. 이들은 세 개의 계약, 세 단계의 신뢰 수준, 세 개의 정직한 결론을 가진 세 개의 문제입니다.

Purdue/ISA-95 모델 — 공장을 번호 매겨진 레벨로 쌓아 올리는 참조 표준 — 은 방향을 자명하게 만듭니다. 레벨 1-2는 장비와 그 실시간 제어, 레벨 3은 실행(레시피 돌리기), 레벨 4는 비즈니스(주문과 자재)이며, 더 높은 번호일수록 더 추상적이고 더 권위 있는 기록 시스템으로서 "위에" 앉습니다. DCS는 레벨 1-2에, MES는 레벨 3에, ERP는 레벨 4에 자리합니다 [1]. 데이터와 신뢰는 그 검증된 시스템들로부터 우리의 분석 미러로 아래로 흐릅니다 — 거의 결코 그 반대로는 흐르지 않습니다. 그 단 하나의 설계 규칙이 우리를 곤경에서 벗어나게 합니다.

DeltaV와 Siemens: 제어 계층을 OPC UA로 읽기

분산 제어 시스템(distributed control system, DCS)은 바이오리액터(bioreactor) 스위트의 검증된 두뇌입니다 — BR101(진행 예제의 바이오리액터 용기)을 37 °C, pH 7.0, 용존 산소 약 40 %sat(포화도 백분율), 그리고 교반(agitation)을 정상 범위로 유지하는 제어 루프(어떤 측정값을 목표에 붙들어 두려고 노브를 살짝 미는 자동 피드백)를 담고 있습니다. 여러분은 그 루프들을 오픈소스로 다시 구현하지 않으며, 파이썬(Python) 스크립트에서 GMP(Good Manufacturing Practice) 제어 시스템으로 설정값(setpoint, 명령된 목표값)을 쓰지도 않습니다. 여러분이 하는 일은 읽는 것입니다.

두 주요 벤더 모두 깔끔한 읽기 경로를 건네줍니다. Emerson의 DeltaV는 런타임 파라미터, 알람과 이벤트, 이력 데이터를 OPC UA 서버를 통해 네이티브로 노출하므로, 어떤 표준 OPC UA 클라이언트든 제어 시스템을 변경하지 않고도 DeltaV 주소 공간을 탐색하고 값을 구독할 수 있습니다 [5]. Siemens SIMATIC 제어기도 정확히 같은 방식으로 OPC UA 서버 역할을 하며, 제조사·플랫폼에 독립적이고 보안이 적용된 상위 계층으로의 채널을 제공합니다 [6]. OPC UA 자체가 이를 이식 가능하게 만드는 표준입니다. 플랫폼 독립적이고, 서비스 지향적이며, 보안성을 갖춘 아키텍처로 IEC 62541로 표준화되어 있습니다 [4].

우리 바이오리액터는 이미 OPC UA로 말하므로 — 7장에서 opcua-server를 대상으로 opcua-collector를 만들었습니다 — DCS 다리는 클라이언트 측에서 거의 비용이 들지 않습니다. 오픈소스 SDK들은 성숙해 있습니다. node-opcua(MIT)는 DCS/PLC OPC UA 서버를 탐색하고, 읽고, 쓰고, 구독할 수 있으며 [8], 우리가 이미 쓰는 파이썬 asyncua 라이브러리도 같은 일을 합니다. 이 다리는 모든 포착 장과 동일한 형태입니다. 노드를 구독하고, 값 + 품질 + 타임스탬프를 받아, 한 행을 씁니다.

노트북에서는 DeltaV 서버도 SIMATIC 서버도 돌아가지 않으므로, 동반 저장소는 PI와 SAP에 쓰는 것과 같은 정직한 하이브리드 패턴을 따릅니다. 작은 dcs-mock — DeltaV/PCS7 스타일 노드(제어 계층 BR101 > TIC-101 > PID1 > PV)를 노출하고 골든 배치(golden batch, 모든 테스트가 그 값에 대해 단언하는 기준 실행)를 라이브 DA(Data Access, 실시간 값)와 이력 HDA(Historical Data Access) 양쪽으로 제공하는 asyncua 서버, commercial 프로파일 뒤 — 가 이제 pi-web-api-stub과 나란히 제공되므로, 라이선스가 걸린 하드웨어가 손닿는 곳에 없어도 다리를 개발하고 계약 테스트(contract-test)할 수 있습니다. 운영 전환은 서버 URL과 인증서를 바꾸는 일이지 코드를 바꾸는 일이 아닙니다. OPC UA로 읽은 DeltaV 값은, 우리 히스토리안이 저장하는 것과 정확히 같은 형태로 도착합니다(dcs-mock이 실제로 제공하는 읽기 결과이자, 실제 DeltaV Edge 서버가 지키는 계약):

{ "NodeId": "ns=2;s=BR101/TIC-101/PID1/PV", "DisplayName": "BR101.Temp.PV",
"Value": 37.02, "StatusCode": "Good", "SourceTimestamp": "2026-01-12T08:30:00Z",
"Unit": "degC" }

그것은 지난 장에서 로더가 사용하는 것을 본 ts.sensor_reading 행 계약(ts, tag, value, unit, quality, batch_id)에 일대일로 매핑됩니다. StatusCode "Good"은 OPC 품질 192(양호한 품질의 OPC 점을 가리키는 표준 숫자 코드 — 아래 필드별 설명이 192/64/0이 어디서 오는지 보여 줍니다)가 되며, DCS 태그 BR101.Temp.PV는 이미 5장의 태그 사전이 인식하는 이름입니다. DCS 다리는 의도적으로 이 책에서 가장 덜 흥미로운 코드입니다 — 그리고 바로 그것이 핵심입니다.

DeltaV OPC UA 읽기 결과의 해부 (필드별로)

그 JSON은 천천히 읽어 볼 가치가 있습니다. 그것은 예시가 아니라 다리가 소비하는 실제 아티팩트이기 때문입니다. dcs-mock 서비스(examples/services/dcs-mock/app.py)는 asyncua 서버를 만드는데, 그 LOOPS 테이블이 DeltaV 브라우즈 경로를 정규 태그·엔지니어링 단위·범위에 매핑한 다음, 각 PV를 Value, StatusCode, SourceTimestamp, 그리고 EngineeringUnits 속성을 실은 실제 DataValue로 제공합니다. examples/tests/test_opcua_bridges.py는 그것을 되읽어 행이 골든 배치와 자릿수까지 일치함을 단언합니다. DataValue 하나를 필드별로 해부해 보면 남는 부품이 없습니다 — 모든 필드는 ts.sensor_reading 6-튜플(ts 시계열 스키마가 각 판독값을 저장하는 6열짜리 행 — ts, tag, value, unit, quality, batch_id)의 한 열이 되며, 그 점의 신뢰 여부를 결정하는 것이 바로 StatusCode입니다.

DeltaV OPC UA DataValue 하나를 해부하는 신분증 카드 다이어그램. 헤더는 BR101.Temp.PV에 대한 DeltaV DataValue가 ts.sensor_reading로 매핑된다고 적는다. 행들은 DisplayName을 tag 열로, Value 37.02를 Double로서 value 열로, SourceTimestamp를 ts 열로, EngineeringUnits degC를 unit 열로 매핑하고, 초록색 StatusCode 블록은 Good이 quality 192로 매핑됨을 보이며 심각도 비트 Good 192, Uncertain 64, Bad 0과 Bad 점은 버리지 않고 quality 0으로 기록된다는 주석을 담고, batch_id는 다리가 공급하는 GMP 조인 키다. 보라색 패널은 NodeId 브라우즈 경로 ns=2 s=BR101 슬래시 TIC-101 슬래시 PID1 슬래시 PV를 모듈·기능 블록·파라미터로 해독하며, 읽기 전용 NOA 이음매가 데이터를 밖으로만 흘려보내고 결코 되쓰지 않는다는 빨간 주석을 단다. DeltaV DataValue 하나를 해부: DisplayName은 정규 태그를 싣고, Value/SourceTimestamp/EngineeringUnits가 value/ts/unit를 채우며, StatusCode는 192/64/0 품질 플래그가 되고, 문자열 NodeId는 DeltaV 브라우즈 경로입니다 — 한 방향 NOA 이음매를 건너 읽습니다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.

로더가 신경 쓰는 순서대로 필드를 짚어 봅시다. DisplayName은 장식이 아닙니다 — dcs-mock은 정규 태그(BR101.Temp.PV)를 바로 여기에 써넣어, 다리가 DeltaV 경로-태그 매핑을 사전 공유 목록이 아니라 탐색으로 발견할 수 있게 합니다. opcua_dcs_bridge.discover_pv()가 주소 공간을 재귀적으로 돌고 _tag_and_unit()이 정확히 이 속성을 읽습니다. ValueDouble이며, 컬렉터의 to_sample()이 소수점 넷째 자리로 반올림합니다 — PI 경로가 싣는 것과 동일한 정밀도여서(pi-web-api-stub은 PI 값을 미리 소수점 넷째 자리로 반올림하여 제공합니다) OSS 복사본과 DCS 원본이 자릿수까지 일치합니다. SourceTimestamp는 우리가 읽은 순간이 아니라 DCS가 루프를 샘플링한 순간입니다 — 미러를 동시대적으로 유지하는 구분입니다. EngineeringUnits는 값과 함께 이동하여 37.02가 결코 맨숫자가 되지 않게 합니다. dcs-mock은 실제 DeltaV PV가 하듯 그것을 (UNECE 네임스페이스 URI와 함께) OPC UA 속성으로 붙입니다.

핵심을 떠받치는 필드는 StatusCode입니다. 컬렉터의 quality_code()는 32비트 상태 워드의 상위 두 심각도 비트 — 00 Good, 01 Uncertain, 10/11 Bad — 를 취해 우리 smallint 192/64/0으로 접습니다. PI 다리의 Good/Questionable 매핑의 OPC 네이티브 쌍둥이입니다. Bad 읽기는 값을 싣지 않으므로(asyncua가 널로 만듭니다) to_sample()None을 통과시키고 그 점은 quality 0으로 기록되며, 결코 버려지지 않습니다 — 조사관은 공백을 추론하는 것이 아니라 봐야 합니다. 마지막으로 batch_id는 메시지가 싣지 않는 유일한 필드입니다: 다리가 백필 중인 실행에서 공급하며, 평평한 센서 행 — 자체적인 배치 맥락이 없는, 타임스탬프가 찍힌 단일 측정값 — 에서 s88.batch의 GMP 배치 기록으로 가는 조인 키(한 테이블의 행을 다른 테이블의 짝과 잇는 공유 값)입니다. 문자열 NodeId(ns=2;s=BR101/TIC-101/PID1/PV)는 DeltaV 브라우즈 경로 — 모듈, 기능 블록, 파라미터 — 이며, .PV 접미사가 중요합니다: 그것은 공정 (증거)이지 .SP 설정값(레시피)이 아니며, 전체 교환은 데이터를 밖으로만 흘리고 안으로는 아무것도 들이지 않는 읽기 전용 NOA 이음매를 건너 진행됩니다.

DataValue는 어디서 오는가 — 3부작 되짚기

이 하나의 OPC UA 읽기는 세 권의 책을 관통하는 실을 완성합니다. 그것이 싣는 온도는 실제 장비에서 생성됩니다: 1권은 그 루프가 물리적으로 존재하는 현장 — BR101을 37 °C로 유지하는 생산 바이오리액터 — 를 걷습니다. 이어 2권은 같은 읽기를 하나의 데이터 포인트로 규정하고, 이 코드가 답하는 열린 문제에 이름을 붙입니다 — DCS 태그가 어떻게 개방 프로토콜을 타고 스택을 거슬러 올라가는지 연결성·통합 표준에서, 그리고 제어·히스토리안 데이터가 어떻게 의미를 얻는지 자동화·제어 데이터에서. 이 장의 다리는 그 여정의 오픈소스 구현입니다: 물리적 PV가 StatusCode로 검증된 ts.sensor_reading 한 행이 됩니다.

DeltaV 내부: 어떻게 연결하고, 저장하고, 공유하는가

그 계약의 DeltaV 쪽을 열어 볼 가치가 있습니다. "DeltaV는 데이터를 OPC UA로 노출한다"는 말이 서로 다른 세 가지 질문을 조용히 답하기 때문입니다. 데이터는 어떻게 DCS를 떠나는가, DeltaV는 그것을 어디에 보관하는가, 그리고 우리 스택은 제어 루프를 어지럽히지 않고 그것을 어떻게 가져오는가?

어떻게 연결하는가. 대부분의 공장에서 DeltaV의 OPC UA 얼굴은 ProfessionalPLUSApplication Station 워크스테이션에 호스팅된 서버이며 — 그것은 래퍼(wrapper)입니다: DeltaV의 더 오래된 OPC 클래식(OPC Classic) 인터페이스를 OPC UA로 변환하여, 세 종류의 데이터를 위한 하나의 공통 엔드포인트를 내놓습니다 — DA(실시간 공정 값), A&E(알람과 이벤트), HDA(이력 값) [13]. PK 또는 PK Flex 제어기도 자기 OPC UA 서버를 임베드할 수 있지만, 그쪽은 실시간 Data Access만 제공합니다 — 알람도, 이력도 없습니다. 주소 공간은 DeltaV 제어 계층을 그대로 드러낸 것입니다: 제어 전략(control strategies) → 영역(area) → 모듈(module) → 기능 블록(function block) → 파라미터(parameter) → 필드(field) 순으로 탐색하므로(기능 블록이란 모듈에 배선된 하나의 재사용 가능한 제어 요소 — 이를테면 PID 제어기 — 입니다), 어떤 루프의 공정 값은 … > BR101 > TIC-101 > PID1 > PV > CV 같은 경로에서 도달되며, 마지막 CV(current value, 현재 값)는 PV 파라미터 아래의 실시간 필드입니다. 위의 예시적 읽기 결과는 그 목적지를 간결함을 위해 하나의 문자열 NodeId로 적었지만, Emerson이 실제로 문서화하는 것은 그 브라우즈 경로입니다 — 그리고 우리 서버와 똑같이, 클라이언트는 그것을 외우는 게 아니라 탐색으로 발견합니다. 와이어 위에서는 평범한 OPC UA입니다: opc.tcp 엔드포인트, 메시지 서명을 곁들인 128 또는 256비트 세션 암호화, 그리고 사용자명/비밀번호(또는 의도적으로 제한된 익명) 로그인.

데이터를 어떻게 저장하는가. DeltaV는 모든 것을 한곳에 두지 않습니다. 세 종류의 기록을 위해 세 개의 저장소를 두며, 어느 것이 어느 것인지 알면 어디서 읽을지가 정해집니다 [14].

  • Continuous Historian은 시계열을 보관합니다 — 아날로그·이산·텍스트 파라미터와 그 품질 — 설정 가능한 데드밴드(deadband)로 수집하여 바뀌지 않은 값을 매 스캔마다 다시 기록하지 않습니다. 표준 워크스테이션에서 약 250개 파라미터를 포착하고, Application Station에서는 수만 개로 확장됩니다.
  • Event Chronicle은 알람과 이벤트 — 공정, 시스템, 운영자, 안전, 그리고 사건 순서(SOE) — 를 Microsoft SQL Server 데이터베이스에 보관합니다.
  • Batch Historian은 배치·레시피 실행 기록을 보관하며, Batch Executive와 Event Chronicle로부터 SQL Server에 집계합니다.

이력은 DeltaV OPC History Server를 통해 OPC UA 래퍼에 도달하는데, 이 서버는 Continuous Historian에 HDA 인터페이스를 씌웁니다 — 그래서 클라이언트는 구독하는 바로 그 연결 위에서 파라미터를 HistoryRead로 되읽을 수 있습니다. (여기서 우리는 고정된 보존 기간을 일부러 적지 않습니다: DeltaV의 보존은 헤드라인 숫자가 아니라 설정된 아카이브 크기와 내보내기 정책의 함수입니다.)

나머지와 어떻게 연결되는가. 여기서 정직한 하이브리드 규율이 물어뜯습니다. 우리는 엄격하게 읽기 전용 경로를 택합니다: 컬렉터는 실시간 파라미터를 구독하고 이력을 읽으며, 결코 설정값을 쓰지 않습니다. 그것은 DeltaV 서버가 강제하는 제약이 아니라 — 서버는 기꺼이 쓰기를 받아들입니다 — 우리의 아키텍처 선택이며, 이름도 있습니다: NAMUR 개방 아키텍처(NAMUR Open Architecture, NOA, NE 175)입니다. NOA는 데이터가 검증된 제어 코어에서 밖으로만 흐르는 — "데이터 다이오드(data diode)"식 진행 방향의 — 모니터링·최적화용 두 번째 채널을 승인하며, OPC UA를 외부로 나가는 선으로 지목합니다 [15]. 우리 오픈소스 컬렉터(asyncua 또는 node-opcua — Emerson의 것이 아니라 우리 도구)는 그 읽기들을 이 책의 나머지가 기대하는 바로 그곳에 내려놓습니다: TimescaleDB의 ts.sensor_reading 행, 그리고 PostgreSQL의 ISA-88/95 배치·계보 테이블. DCS는 계속 제어하고, 우리는 계속 미러링합니다.

DeltaV가 오픈소스 미러로 데이터를 공급하는 3개 구역 다이어그램. 왼쪽은 DeltaV DCS 검증 코어다. BR101 제어 모듈이 실시간 PV·SP·OUT·MODE 파라미터를 싣고, 세 저장소로 이력화된다 — 시계열 값을 위한 Continuous Historian, SQL Server의 알람·이벤트를 위한 Event Chronicle, Batch Executive와 Event Chronicle로 만든 배치 기록을 위한 Batch Historian. PK 제어기가 자기만의 실시간 데이터 전용 OPC UA 서버를 임베드한다는 주석도 있다. 가운데는 ProfessionalPLUS 또는 Application Station 위의 OPC UA 래퍼 서버로, 실시간 값을 위한 DA, 이벤트를 위한 알람·이벤트, 이력을 위한 HDA를 노출한다. 오른쪽은, 데이터를 밖으로만 나르며 설정값을 되쓰지 않는 읽기 전용 NAMUR 개방 아키텍처 이음매를 건너, 오픈소스 컬렉터가 구독하고 이력을 읽어 TimescaleDB sensor_reading 행과 PostgreSQL ISA-88/95 배치·계보 저장소로 보낸다. DeltaV는 세 개의 내부 히스토리안 위에 하나의 OPC UA 래퍼 서버(DA, 알람·이벤트, HDA)를 노출하고, 오픈소스 계층은 엄격히 읽기 전용인 NOA 이음매를 건너 그것을 읽어 TimescaleDB와 PostgreSQL에 미러링합니다 — 결코 되쓰지 않습니다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.

다리 코드는 이미 존재합니다 — 작동하는 템플릿으로서. 여러분은 DCS 다리를 상상할 필요가 없습니다. 그것은 우리가 지난 장에서 출하하고 테스트한 PI 다리 — 많은 공장이 시계열을 장기 보관하는 데 쓰는 상용 PI System 공정 히스토리안(OSIsoft/AVEVA)으로 가는 다리 — 의 거의 복제본입니다. 그 다리는 examples/chapters/17-bridge-pi-historian/pi_bridge.py에 살고 있으며, examples/tests/test_bridges.pypi-web-api-stub을 대상으로 그것을 작동시킵니다. 그 두 개의 핵심 함수는 DCS 다리가 필요로 하는 바로 그 형태입니다 — 벤더의 품질 플래그를 OPC 품질 코드로 매핑한 다음, 정규(canonical) 행 튜플을 내보냅니다.

# examples/chapters/17-bridge-pi-historian/pi_bridge.py
def quality_code(item: dict) -> int:
"""PI Good/Questionable/Substituted -> OPC UA quality code."""
if item.get("Questionable"):
return 64 # Uncertain
return 192 if item.get("Good", True) else 0 # Good / Bad


def to_sensor_rows(items: list[dict], tag: str, batch_id: str) -> list[tuple]:
"""Map PI recorded values to ts.sensor_reading rows (ts, tag, value, unit, quality, batch_id)."""
return [(it["Timestamp"], tag, it["Value"], it.get("UnitsAbbreviation"),
quality_code(it), batch_id) for it in items]

DCS 다리는 이것을 정확히 복제합니다. pi_bridge.read_recorded()가 PI Web API를 대상으로 client.get(".../streams/{wid}/recorded")를 하는 자리에서, DCS 버전(opcua_dcs_bridge.py)은 asyncua로 OPC UA 노드를 HistoryRead하거나 — 그 라이브 DA 값을 구독하거나 — 해서 value + StatusCode + SourceTimestamp를 받습니다. quality_code()는 PI의 플래그 대신 OPC UA StatusCode의 심각도(severity)를 192/64/0으로 매핑하며, 7장 opcua-collectorto_sensor_rows() — 위 PI 함수의 OPC 네이티브 쌍둥이로, 태그를 샘플마다 싣습니다 — 가 그대로 재사용됩니다. 이것은 이제 스케치가 아니라 커밋되고 테스트된 코드입니다: examples/chapters/18-bridge-dcs-mes-erp/opcua_dcs_bridge.pydcs-mock 서비스를 상대로, 그리고 평평한 opcua-server를 이미 ts.sensor_reading으로 스트리밍하는 7장의 opcua-collector(compose capture 프로파일)와 함께 동작하며 — examples/tests/test_opcua_bridges.py가 OPC UA 행이 골든 배치와, 그리고 PI 행과, 자릿수와 품질 플래그까지 일치함을 단언합니다. DCS 포팅은 동일한 quality_code / to_sensor_rows 코어를 감싼 서로 다른 전송 호출일 뿐이며, make dcs-backfilldcs-mock에서 한 구간(window)을 히스토리안으로 적재합니다.

OPC UA가 제공되지 않을 때. 더 오래된 Siemens 스키드(skid, 프레임에 실려 도착하는 자체 완결형의 사전 조립된 공정 유닛)는 때때로 앞단에 OPC UA 서버 없이 네이티브 S7 프로토콜(Siemens의 독자적 PLC 통신 언어로, 그 Step7 툴셋에서 나온 것)만 노출합니다. 여기서 오픈소스의 답은 Apache PLC4X(Apache 2.0)입니다. 벤더 중립적인 라이브러리로, 그 S7(Step7) 드라이버가 Siemens S7 PLC를 직접 읽고 씁니다 [7]. 수십 가지 레거시 프로토콜로 말하므로, OPC UA가 잊고 간 브라운필드(brownfield, 새로운 "그린필드" 구축이 아니라 기존의 더 오래된 설치) 스키드를 위한 최후의 다리입니다. 11장에서는 레거시 Modbus 스키드에 같은 태그-읽고-행-쓰기 패턴을 사용했습니다(examples/chapters/09-legacy-skids-modbus-s7/modbus_reader.py에서 pymodbus로). DCS 경우는 그 패턴을 PLC4X를 통해 적용하되, 독립 스키드가 아니라 제어 시스템을 향하게 할 뿐입니다.

연결성 장들에서 이어 온 한 가지 정직한 주석. 모니터링 계층으로 올라가는 읽기 전용 OPC UA 채널은 바로 NAMUR 개방 아키텍처(NAMUR Open Architecture) 개념입니다 — 검증된 코어를 그대로 둔 채 분석을 위한 두 번째, 거의 읽기 전용인 데이터 경로입니다. 그것은 DCS를 재검증하지 않고 DCS 데이터를 얻는, 아키텍처적으로 승인된 방법입니다.

MES: 정직한 결론은 "신뢰할 만한 OSS GxP 선택지는 없다"

이제 그 어려운 문장입니다. ISA-95 레벨 3의 제조 실행 시스템(Manufacturing Execution System, MES)은 마스터 레시피를 실행되고 전자 서명된 배치 기록으로 바꾸는 것입니다. 작업 지시에 맞춰 자재를 불출하고, 작업 순서를 강제하며, 작업자 전자 서명을 포착하고, 품질 부서가 출하 승인하는 검토된 전자 배치 기록(electronic batch record, EBR)을 산출합니다. 그것은 현장에서 가장 무겁게 검증되고(시스템이 규정된 그대로 정확히 동작한다는 문서화된 위험 기반 증거를 지니며, 그 전 생애에 걸쳐 최신으로 유지됩니다), Part-11이 가장 짙게 배어 있는 시스템입니다 — Part 11은 FDA 21 CFR Part 11로, 전자 기록과 전자 서명을 법적으로 종이와 잉크만큼 신뢰할 수 있게 만드는 미국 규정입니다.

GMP 바이오 제조에서 이 자리를 신뢰할 만하게 채우는 오픈소스 제품은 없습니다. 이것은 이 책의 도구 조사가 빠뜨린 부분이 아니라, 그 조사의 발견입니다. 여러분은 조각들을 모을 수 있습니다 — 여기 워크플로 엔진 하나, 저기 eLN(electronic lab notebook, 전자 실험 노트) 하나, 그리고 PostgreSQL에 만든 우리 자신의 ISA-88/95 모델 — 하지만 그중 어느 것도 상용 MES(또는 검증된 페이퍼 온 글래스(paper-on-glass) 시스템, 즉 태블릿이 종이 배치 시트를 대체하는 시스템)가 제공하는, 검증되고 벤더가 책임지며 Part-11이 완비된 실행-및-전자-서명 패키지를 담고 있지 않습니다. GAMP 5(전산화 시스템 검증에 대한 업계 표준 가이드)는 — 그 제2판에서 — 오픈소스가 GxP에서 사용될 수 있음을 명시하지만, 오직 검증된 생애 주기 안에서, 사용에 비례하는 위험 기반(risk-based)·비판적 사고(critical-thinking) 보증과 함께여야 한다고 못박습니다 [12] — 그리고 수제 MES를 조립해 그 기준까지 검증하는 일은, 어떤 분석 팀도 부업으로 이길 수 있다고 자처해서는 안 되는 수년짜리 프로그램입니다.

그래서 아키텍처는 그 결론을 따릅니다. 상용 MES(또는 페이퍼 온 글래스)는 실행의 기록 시스템으로 남습니다. 대신 우리 오픈소스 계층은 정당한 두 가지 일을 합니다.

  1. MES의 구조적 산출물 — 레시피, 오퍼레이션과 페이즈, 배치와 그 실제 페이즈 구간 — 을 우리가 이미 만든 관계형 모델로 미러링하여, 분석과 대시보드가 맥락을 갖게 합니다.
  2. 실행 기록을 결코 발원시키지 않습니다. 전자 서명도, 자재 처분(material disposition)도, 출하 결정도 OSS 계층에 살지 않습니다.

좋은 소식은 그 미러가 이미 만들어져 있다는 것입니다. MES의 세계는 ISA-88/95이며, 우리는 4장에서 ISA-88/95를 PostgreSQL에 모델링했습니다. 다음은 examples/platform/db/10-isa88-95.sql에서 가져온 실제 골격입니다 — MES가 내보내는 장비 계층과 절차 모델이 이 테이블들에 곧바로 매핑됩니다.

-- 10-isa88-95.sql — the relational backbone (Chapter 4).
CREATE TABLE s88.unit ( -- the equipment a phase runs on
unit_id text PRIMARY KEY, -- e.g. BR101
area_id text NOT NULL REFERENCES s88.area,
name text NOT NULL,
unit_type text NOT NULL, -- bioreactor | chromatography | tff | fill_line ...
vendor text,
model text
);

CREATE TABLE s88.operation ( -- an ordered step of the recipe
operation_id text PRIMARY KEY,
recipe_id text NOT NULL REFERENCES s88.recipe,
seq_no int NOT NULL,
name text NOT NULL, -- Inoculation | Fed-batch | Harvest | ProteinA ...
unit_type text NOT NULL
);

CREATE TABLE s88.phase ( -- the smallest procedural element
phase_id text PRIMARY KEY,
operation_id text NOT NULL REFERENCES s88.operation,
seq_no int NOT NULL,
name text NOT NULL
);

그리고 배치 — 작업 지시가 만들어 내는 실행 — 와, 완성된 로트를 그 시드 트레인(seed train)까지 거슬러 추적하게 해 주는 계보(genealogy)는, MES가 기록할 그대로입니다(역시 examples/platform/db/10-isa88-95.sql에서):

CREATE TABLE s88.batch (
batch_id text PRIMARY KEY,
product_id text NOT NULL,
recipe_id text NOT NULL REFERENCES s88.recipe,
unit_id text NOT NULL REFERENCES s88.unit,
lot text,
status text NOT NULL DEFAULT 'in_progress', -- in_progress | complete | released | rejected
start_ts timestamptz NOT NULL,
end_ts timestamptz
);

-- lot genealogy: directed edges child -> parent (seed -> bioreactor -> pool -> DS -> DP)
CREATE TABLE s88.genealogy (
batch_id text REFERENCES s88.batch,
child text NOT NULL,
child_type text NOT NULL,
parent text NOT NULL,
parent_type text NOT NULL,
PRIMARY KEY (child, parent)
);

이것이 이 장에 나오는 모든 MES와 ERP 메시지의 수신단입니다. SAP가 생산 주문을 보내면, 그것은 s88.batch의 한 행이 됩니다. MES가 실제 페이즈 구간을 보고하면, 그것은 s88.batch_phase에 착륙합니다. ERP가 어떤 구성품 로트가 어떤 제품 로트에 투입되었는지 조정하면, 그 엣지들은 s88.genealogy에 착륙합니다. 우리의 시드 데이터는 진행 중인 사례를 위해 이것을 이미 채워 둡니다 — ACME Biologics 엔터프라이즈, Newark DE Plant 사이트, BR101(Sartorius Biostat STR 50), CHO-MAB-001 페드배치(fed-batch) 레시피(중국 햄스터 난소(Chinese-hamster-ovary) 세포에서 키우며 실행 내내 영양분을 공급하는 단클론 항체 공정으로, 여기서는 상류-더하기-포착으로 잘려 있습니다 — 그 오퍼레이션은 항체를 처음 포착하는 친화성 크로마토그래피 단계인 ProteinA에서 멈춥니다. 전체 트레인은 시딩된 TFF01과 충전 라인 위에 폴리싱, 바이러스, UF/DF, 충전 오퍼레이션을 더합니다 — 포착 크로마토그래피부터 제제화·충전까지 1권이 짚어 가는 하류 정제 단계들입니다), 그리고 의도적으로 거부된 BATCH-2026-004를 포함한 여섯 개의 캠페인 배치입니다. 그 미러는 오늘 실재하고 조회 가능합니다. 그것이 해서는 안 되는 일은, 배치가 실행되고 서명되는 장소가 되는 것입니다.

세 개의 차선이 하나의 오픈소스 미러로 흘러든다. OT 차선은 DeltaV와 Siemens를 OPC UA로 TimescaleDB에 읽어 들이고, MES 레벨-3 차선은 배치 구조를 B2MML로 PostgreSQL ISA-88/95 모델에 내보내며, 빨간 점선 장벽이 오픈소스에는 전자 서명도 출하 승인도 없음을 표시한다. ERP 레벨-4 차선은 SAP 자재와 주문을 IDoc과 OData로 교환한다. 모든 화살표가 미러를 향해 들어온다.

레벨 1-4에서의 정직한 하이브리드 경계: 오픈소스는 DCS를 OPC UA로 읽고, MES 배치 구조를 B2MML로 미러링하며, ERP 메시지를 교환합니다 — 그러나 검증된 시스템들은 실행, 서명, 처분을 그대로 유지합니다. OSS 계층은 미러일 뿐, 결코 원본이 아닙니다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.

SAP/ERP: 자재, 로트, 작업 지시를 B2MML로 교환하기

ERP는 — 바이오파마에서는 압도적으로 SAP S/4HANA입니다(SAP는 지배적인 엔터프라이즈 소프트웨어 벤더이고, S/4HANA는 그 현행 ERP 제품입니다) — 비즈니스의 진실을 소유합니다. 어떤 자재가 존재하는지, 어떤 로트가 출하 승인되었거나 격리되었는지, 어떤 작업 지시가 어떤 배치를 승인하는지입니다. OSS 계층은 센서 판독값을 의미 있게 만들기 위해 그 맥락이 필요하며, 때때로 결과를 다시 보고할 필요도 있습니다. 이를 수행하는 표준적이고 벤더 중립적인 방법은 B2MML(Business To Manufacturing Markup Language)로 직렬화된 ISA-95 / IEC 62264 객체 모델입니다. B2MML은 ISA-95의 XML 구현으로, 어떤 회사든 MESA(MESA International, B2MML 스키마를 발행하는 업계 단체)에 출처를 표기하면 로열티 없이 사용할 수 있습니다 [1][2][3]. 문헌도 이 패턴을 뒷받침합니다. 동료 심사를 거친 ERP↔MES 통합 사례들은 ISA-95 객체 모델을 B2MML XML 트랜잭션 메시지로 구현합니다 [11]. 그리고 "MES 배치 구조를 B2MML로 미러링한다"는 주장은 여기서 단지 서술되기만 하는 것이 아닙니다: 동반 저장소는 실제 형태의 B2MML 문서 두 개(examples/platform/ontology/b2mml/{master-recipe.xml,batch-production-record.xml})와, 그것들을 읽어 그래프 트리플(triple, 주어-술어-목적어 진술로, 지식 그래프의 기본 단위)을 내보내는 실행 가능한 b2mml_to_rdf.py 크로스워크(crosswalk, 한 데이터 형식의 필드를 다른 형식의 필드에 매핑하는 변환기)를 제공합니다 — 같은 교환을, 온톨로지 책의 현장과 디지털 트윈 장에서 한 걸음 더 나아가 다룹니다.

SAP 자체는 두 개의 구체적인 문을 제공합니다. 고전적인 것은 IDoc(Intermediate Document)으로, 자재·로트·주문 데이터를 교환하는 SAP의 구조화된 메시지이며, 통합 계층이 이를 B2MML로 그리고 B2MML에서 변환합니다 [10]. 현대적인 것은 OData입니다. SAP S/4HANA는 생산 주문과 관련 자재 데이터를 문서화된 OData API — 예를 들어 API_PRODUCTION_ORDER_2_SRV — 를 통해 노출하며, OSS 클라이언트는 이를 소비해 ERP 상태를 미러링합니다 [9]. PI 및 DCS와 마찬가지로, 동반 저장소의 계획은 sap-mock(commercial 프로파일 뒤에서 OData 엔드포인트와 IDoc XML 드롭 폴더를 제공하는 FastAPI 서비스)이어서, SAP 라이선스 없이 교환을 만들고 계약 테스트할 수 있게 합니다. 이것은 로드맵에 있을 뿐 아직 출하되지 않았으므로, 여기의 코드 조각들은 목표 계약입니다.

OData 문에서 끌어온 생산 주문은 이렇게 보입니다(문서화된 API_PRODUCTION_ORDER_2_SRV 형태에 맞춘 예시적인 SAP OData JSON):

{
"d": {
"ManufacturingOrder": "1000004711",
"Material": "MAB-001",
"ProductionPlant": "NEWARK",
"MfgOrderPlannedTotalQty": "1",
"ProductionUnit": "EA",
"MfgOrderScheduledStartDate": "2026-01-05T00:00:00Z",
"OrderIsReleased": true
}
}

다리의 일은, 결코 SAP인 척하지 않으면서 그것을 우리 모델에 착륙시키는 것입니다. 매핑은 작고 명시적입니다 — Materials88.batch.product_id, ProductionPlant → 사이트, 그리고 주문 ID는 안정적인 batch_id로 결정론적으로 해소되고(다리는 ManufacturingOrder 1000004711을 매번 같은 BATCH-2026-007로 매핑합니다) 또한 미러가 SAP로 다시 조정될 수 있도록 참조로도 보관됩니다.

-- Mirror a released SAP production order into the ISA-88/95 batch table.
-- SAP stays the system of record; this row is a faithful copy keyed to the order.
INSERT INTO s88.batch (batch_id, product_id, recipe_id, unit_id, lot, status, start_ts)
VALUES ('BATCH-2026-007', 'MAB-001', 'CHO-MAB-001', 'BR101', 'L26007',
'in_progress', '2026-01-05T00:00:00Z')
ON CONFLICT (batch_id) DO UPDATE
SET status = EXCLUDED.status,
product_id = EXCLUDED.product_id; -- idempotent: re-applying the same order is a no-op

SAP 생산 주문 메시지의 해부 (필드별로)

OData 쪽도 DeltaV DataValue가 받은 것과 똑같은 해부를 받을 자격이 있습니다 — 한 가지 정직한 표시와 함께. sap-mock은 로드맵에 있을 뿐 아직 출하되지 않았으므로 위 JSON은 목표 계약(문서화된 API_PRODUCTION_ORDER_2_SRV 형태 [9])이지만, 착륙 쪽은 완전히 실재합니다: 모든 필드가 s88.batch의 한 열에 매핑되며, 그 테이블은 examples/platform/db/seed/seed_cho_line.sql에 시딩되어 위의 INSERT … ON CONFLICT가 소비합니다. 주문을 필드별로 읽으면 각 값이 어느 슬롯을 채우는지, 그리고 어느 한 필드가 멱등성 키인지 드러납니다.

SAP OData 생산 주문 레코드 하나를 해부하는 신분증 카드 다이어그램. 헤더는 SAP OData 주문 1000004711이 s88.batch로 매핑된다고 적는다. 멱등성 키라고 표시된 호박색 강조 블록은 ManufacturingOrder가 조정 참조로 보관되며 ON CONFLICT batch_id 키로 쓰여 같은 주문을 다시 적용해도 무동작임을 보인다. 아래 행들은 Material MAB-001을 product_id로, ProductionPlant NEWARK를 사이트로, MfgOrderPlannedTotalQty 1과 ProductionUnit EA를 계획 수량 및 측정 단위로, MfgOrderScheduledStartDate를 start_ts로, OrderIsReleased true를 status in_progress로 매핑한다. 청록색 푸터 패널은 SAP가 기록 시스템으로 남고, recipe_id와 unit_id가 플랜트와 자재에서 해소되며, B2MML ISA-95 봉투가 OData JSON이든 IDoc이든 같은 필드를 싣고, MaterialLot 응답이 계보 엣지로 착륙한다고 적으며, 그 행을 결코 권위 있는 기록으로서 SAP에 되쓰지 않는다는 빨간 주석을 단다. SAP 주문 하나를 s88.batch에 대해 해부: Material/ProductionPlant/MfgOrderScheduledStartDate/OrderIsReleased가 제품·사이트·시작·상태를 채우고, ManufacturingOrder는 조정 참조이자 ON CONFLICT 멱등성 키로 보관됩니다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.

필드별로 봅시다. Material("MAB-001")은 제품이며, 시드 라인의 실제 product_idCHO-MAB-001 레시피에 있습니다 — 그래서 다리는 메시지가 그것들을 싣는다고 믿는 대신 플랜트-와-자재 맥락에서 recipe_idunit_id(BR101)를 해소합니다. ProductionPlant("NEWARK")는 이미 Newark DE Plant로 시딩된 s88.site입니다. MfgOrderPlannedTotalQty("1")와 ProductionUnit("EA")는 계획 수량과 그 측정 단위입니다 — 캠페인 배치 하나씩. MfgOrderScheduledStartDatestart_ts가 됩니다. OrderIsReleased: true가 관문입니다: 출하 승인된 주문만 미러링되어 status = 'in_progress'로 착륙합니다(시드 배치들은 이것이 도달할 수 있는 하류 상태를 보여 줍니다 — released, complete, 그리고 의도적으로 rejectedBATCH-2026-004). 결정적인 필드는 ManufacturingOrder("1000004711")입니다: 다리는 그것으로부터 안정적인 batch_id(BATCH-2026-007)를 결정론적으로 도출하므로, 같은 주문은 항상 같은 batch_id에 착륙합니다. 주문 ID 자체는 데이터 열로 복사되지 않고 조정 참조로 보관됩니다. 충돌 키 batch_id가 이렇게 도출되기에, ON CONFLICT (batch_id) 절이 바로 같은 주문의 재전송을 중복 배치가 아니라 무동작으로 만드는 것입니다. 주문이 이 OData JSON으로 오든 고전적 IDoc으로 오든, 통합 계층이 둘 다 같은 B2MML/ISA-95 봉투로 변환하므로 위 매핑은 어느 쪽이든 동일합니다 [2][3].

생산 주문 교환, 단계별로

두 해부를 합치면, SAP-에서-OSS로의 핸드셰이크는 놀랄 것 없는 짧고 멱등한 루프입니다.

  1. SAP가 주문을 출하 승인한다. OrderIsReleasedtrue로 뒤집힙니다. SAP는 주문 자체에 대해 기록 시스템으로 남습니다.
  2. OSS 다리가 그것을 끌어온다. 문서화된 OData 엔드포인트를 폴링하거나(또는 IDoc 드롭을 받아) 일곱 개 필드를 읽고, 플랜트와 자재에서 recipe_id/unit_id를 해소합니다.
  3. 하나의 멱등한 행을 착륙시킨다. INSERT … ON CONFLICT (batch_id) DO UPDATEManufacturingOrder에서 결정론적으로 도출된 batch_id에 정확히 하나의 s88.batch 행을 쓰거나 갱신합니다.
  4. 덮어쓰지 않고 조정한다. 주기적 점검이 출하 승인된 모든 SAP 주문에 정확히 하나의 미러링된 배치가 있음을 단언합니다. 불일치는 조용한 수정이 아니라 데이터 무결성 이벤트가 됩니다.
  5. 계보가 같은 방식으로 되돌아온다. ERP가 어떤 구성품 로트가 어떤 제품 로트에 투입되었는지 확인하면, MaterialLot/MaterialDefinition 객체가 s88.genealogy 엣지로 착륙합니다 — 미러는 계보를 기록하고, SAP는 권한을 유지합니다.

두 개의 문, 두 개의 신뢰 계약

DCS와 ERP 교환이 닮아 보이는지 — 둘 다 벤더 메시지를 정규 행으로 착륙시킨다 — 그러면서도 신뢰 경계의 반대편에 앉는지 이름 붙일 가치가 있습니다. 그것들은 두 개의 계약을 가진 두 개의 문입니다. DCS 문(OPC UA 위의 DA/A&E/HDA)은 읽기 계약입니다: 데이터는 실시간 공정 증거이고, 방향은 NOA 이음매를 건너 엄격히 코어-밖으로이며, 멱등성 메커니즘은 키 없는 시계열 하이퍼테이블(시계열을 위한 TimescaleDB의 자동 분할 테이블로, 여기서는 판독값마다 고유 제약이 없습니다)에 대한 구간-삭제-후-삽입입니다. ERP 문(B2MML을 싣는 IDoc/OData)은 교환 계약입니다: 데이터는 비즈니스 진실(주문, 자재, 로트)이고, 방향은 대부분 레벨 4에서 아래로이지만 때때로 보고된 결과가 위로 가며, 멱등성은 관계형 배치 테이블에 대한 기본 키 더하기 ON CONFLICT에서 옵니다. 동일한 정직한 하이브리드 규칙 — 미러링하되 대체하지 말라 — 이지만, OPC UA 문은 StatusCode를 신뢰하고 SAP 문은 ManufacturingOrder를 신뢰하며, 그 둘을 혼동하는 것이 미러가 조용히 그림자가 되는 방식입니다.

같은 B2MML 봉투가 반대 방향으로 자재-로트 정보를 실어 나릅니다. ISA-95의 MaterialLotMaterialDefinition 객체는 우리의 s88.genealogy 엣지에 매핑되므로, ERP가 포착 풀(capture pool) PApool-007이 바이오리액터 배치 BATCH-2026-007에서 왔음을 확인하면, 미러는 그 계보를 시드 데이터가 골든 배치(golden batch)에 대해 이미 하는 것과 정확히 같이 기록합니다.

batch_id,child,child_type,parent,parent_type
BATCH-2026-001,SEED-001,seed_train,WCB-CHO-001,wcb
BATCH-2026-001,BATCH-2026-001,bioreactor,SEED-001,seed_train
BATCH-2026-001,PApool-001,capture_pool,BATCH-2026-001,bioreactor
BATCH-2026-001,DS-001,drug_substance,PApool-001,capture_pool
BATCH-2026-001,DP-001,drug_product,DS-001,drug_substance

그것은 examples/datasets/lot_genealogy.csv에서 가져온 실제 데이터입니다 — 한 로트에 대한 작업 세포 은행(working cell bank) → 시드 트레인 → 바이오리액터 → 포착 풀 → 원료의약품(drug substance) → 완제의약품(drug product)의 전체 사슬이며, ERP가 주도하는 계보 교환이 재구성할 바로 그 계보입니다. 각 홉(hop)은 1권이 짚어 가는 실제 하류 단위공정이며, 계보 엣지는 자재가 그것을 건넜다는 기록입니다: 포착 풀은 친화성 포착에서 나온 저-pH Protein A 용리액이고(이것은 저-pH 바이러스 불활성화의 시작을 겸합니다), 원료의약품은 폴리싱 크로마토그래피, 바이러스 여과, 그리고 최종 UF/DF 농축·완충액 교환을 거쳐 살아남은 것입니다. 엣지는 각 단계가 어떻게 정제하는지에 대해서는 아무것도 말하지 않습니다 — 그것은 1권의 주제입니다 — 하지만 그것은 출하 조사나 회수가 실패한 완제의약품 바이알에서 그것이 나온 정확한 풀, 바이오리액터 배치, 세포 은행 바이알까지 거슬러 범위를 좁히게 해 주는 척추입니다. 바로 그래서 ERP가 그것을 권위 있게 유지하고 미러는 복사만 하는 것입니다.

멱등성과 조정: 미러를 정직하게 유지하는 규칙

이 장의 모든 교환은 복사본이며, 복사본은 어긋납니다(drift). 미러를 신뢰할 만하게 유지하는 규율은 히스토리안 장이 도입한 것과 동일합니다. 모든 쓰기를 멱등하게 만들고, 덮어쓰는 대신 조정하십시오.

멱등이란 같은 메시지를 다시 적용해도 아무것도 바뀌지 않는다는 뜻입니다. SAP는 IDoc을 재전송하고, OData 폴링은 겹치며, DCS 구독은 재연결 후 재생됩니다. "생산 주문 1000004711이 출하 승인되었다"가 세 번 도착하면, 여러분은 세 개가 아니라 하나의 배치 행으로 끝나야 합니다. 위의 ON CONFLICT (batch_id) DO UPDATE가 바로 그 보장입니다 — 같은 주문이 같은 batch_id를 결정론적으로 도출하므로, 그것을 다시 적용하는 것은 무동작(no-op)입니다. (시계열과의 비대칭에 주목하세요. 히스토리안의 하이퍼테이블(hypertable)은 (tag, ts)에 고유 키를 두지 않으므로, DCS 백필(backfill)은 20장에서 설명한 대로 구간-삭제-후-삽입(delete-window-then-insert)으로 멱등성을 얻는 반면, 관계형 배치/계보 테이블은 기본 키와 ON CONFLICT로 그것을 얻습니다.)

조정이란 두 시스템에 같은 질문을 주기적으로 던지고 그들이 일치함을 단언하는 것입니다. 출하 승인된 모든 SAP 주문에는 정확히 하나의 미러링된 배치가 있는가? 모든 s88.genealogy 엣지는 ERP가 확인한 자재 로트로 추적되는가? 불일치는 데이터 무결성(data-integrity) 이벤트로 기록되며, 결코 조용히 고쳐지지 않습니다 — 미러에서의 조용한 수정이 바로 그림자 기록(shadow record)이 태어나는 방식이고, 검증된 기록 시스템과 어긋나는 그림자 기록이야말로 조사관이 정확히 찾아내는 것이기 때문입니다. 규칙을 한 번 말하자면 이렇습니다. OSS 계층은 충실하게 동기화해야 하며, 결코 권한을 가진 병렬 기록이 되어서는 안 된다.

현장 기록이 말하는 것: DCS·MES·ERP 통합 실패

"그림자 기록" 경고는 이론적인 손사래가 아니라, 감독 기관(공장을 감사하는 규제 당국)이 이름 붙인 바로 그 실패 모드입니다. 영국 MHRA(Medicines and Healthcare products Regulatory Agency, 영국의 의약품 규제 당국)의 GXP 데이터 무결성 지침모든 데이터가 — 비공식적이거나 임시적인 사본에 보관된 데이터를 포함해 — GMP 검토 대상임을, 그리고 규제 기록의 통제되지 않은 병렬 사본이 인정된 무결성 위험임을 명시합니다. 그것이 선택적으로 보관되거나, 폐기되거나, 기록 시스템과 어긋나게 만들어질 수 있기 때문입니다 [16]. 불일치를 조용히 "고치는" 미러는, 그 정의에 따라, 지침이 경고하는 바로 그 종류의 비공식 기록을 제조하고 있는 것입니다. 그래서 조정 단계는 불일치를 치유하는 대신 기록합니다: 그 불일치가 조사관이 봐야 하는 기록입니다.

통합 문헌은 공학 쪽에서 같은 단층선을 가리킵니다. 이 장이 이미 인용한 동료 심사 ERP-MES 사례 연구는, 엔터프라이즈-제어 통합의 어려운 부분이 전송이 아니라 의미론적 매핑 — 한 시스템의 "주문", "자재", "로트"가 양쪽에서 같은 객체를 뜻하게 만드는 것 — 임을 발견했고, B2MML로 직렬화된 ISA-95 객체 모델이 바로 그 매핑을 못박아 두 기록이 어긋날 수 없게 하려고 존재한다고 밝혔습니다 [11]. 두 출처를 함께 읽으면 현실적인 현장 실패가 잡힙니다: 다리는 쓰기 쉽고 미묘하게 틀리기도 쉬우며, 그것이 틀어지는 방식은 그림자 기록으로 늙어 가는 조용한 의미론적 불일치입니다. 세 가지 구체적 형태가 반복됩니다.

  • 멱등하지 않은 재전송. SAP가 타임아웃 후 IDoc을 재전송하는데 ManufacturingOrder에서 도출된 batch_idON CONFLICT 키가 없으면, 미러는 이제 한 주문에 두 배치를 가집니다 — 출하 검토 때 SAP와 조정되지 않을 개수입니다.
  • 측정 단위 누락. DataValueEngineeringUnits를 읽히지 않은 채 도착하거나 ProductionUnit이 무시되면, 37.02나 계획 수량이 맨숫자로 착륙하고, 미러는 값이 무엇을 뜻하는지에 대해 출처와 조용히 어긋납니다.
  • 삼켜진 Bad 점. DCS 읽기가 Bad StatusCode를 반환하는데 순진한 다리가 그것을 "값 없음"으로 버리면, 히스토리안은 검증된 시스템이 공백을 기록한 자리에 끊김 없는 추세를 보여 줍니다 — 미러가 이제 원본보다 더 듣기 좋은 이야기를 하게 되며, 이는 그림자 기록이 기울 수 있는 가장 나쁜 방향입니다.

각각은 이미 코드에 있는 단 하나의 규율로 예방됩니다: 관계형 쓰기에 키를 매기고, 단위를 싣고, Bad 점을 quality 0으로 기록하라. 다리는 이 책에서 가장 덜 흥미로운 코드이지만, 다리의 무결성은 가장 중대한 것 중 하나입니다.

같은 교환을, 소비자가 신뢰할 수 있는 그래프로 표현하기

관계형 착륙장은 미러의 한 얼굴이고, 두 장 앞의 지식 그래프는 다른 얼굴이며, 둘은 이 다리들이 전달하는 동일한 사실을 싣습니다. 다리의 핵심 아티팩트가 어떻게 의미론이 되는지 명시적으로 말할 가치가 있습니다. 하류 소비자 — SPARQL 디지털 트레드 질의, SHACL 출하 관문, ML 소프트 센서 — 가 모두 와이어가 아니라 그 그래프에서 읽기 때문입니다. SAP가 확인한 계보 엣지는 하나의 RDF 트리플입니다: bp:PApool-007 bp:derivedFrom bp:BATCH-2026-007 — 주어 IRI, 온톨로지 책이 전이적 derivedFrom 척추로 개념화하는 술어, 그리고 걸어 갈 수 있는 목적어 IRI입니다. DataValue는 대신 타입 리터럴 트리플입니다: bp:BATCH-2026-007 bp:temperature "37.02"^^xsd:float, 그 float 데이터타입 태그는 OPC 읽기가 그랬던 것과 같은 정밀도를 싣습니다. ERP가 조정하는 계보 백본은 어떤 경쟁 질문(competency question) — "이 로트는 끝까지 거슬러 무엇에서 파생되었는가?" — 의 답이며, SPARQL (bp:derivedFrom)+ 속성 경로가 한 문장으로 풀어내는 그 걸음은, SQL이라면 재귀 CTE가 필요한 바로 그 걸음입니다. 그리고 앞 절이 타협 불가로 못박은 조정 규칙에는 형식적 쌍둥이가 있습니다: SHACL 셰이프 — 온톨로지 책의 출하 관문이 쓰는 바로 그 Shapes Constraint Language — 가 닫힌 세계(closed-world)로, 출하 승인된 모든 bp:Batch가 정확히 하나의 derivedFrom 부모와, 존재하며 범위 안에 있는 CQA를 싣는다고 단언하므로, 누락된 계보 엣지나 빠진 단위는 조용히 그림자 기록으로 늙어 가는 대신 지금 오류로 실패합니다. 다리는 트리플이 StatusCode와 단위를 온전히 실은 채 내보내짐을 보장하고, SHACL은 그래프가 구멍을 가진 채 출하를 주장할 수 없음을 보장합니다. 이 장의 두 해부 — DeltaV DataValue와 SAP 주문 — 는, 이렇게 읽으면 주어 IRI를 기다리는 두 개의 트리플일 뿐입니다.

왜 충실한 미러가 모든 학습 모델의 전제 조건인가

이 장의 마지막 주장 — 미러가 SPC와 소프트 센싱을 가능하게 하는 곳이라는 것 — 은 ML 생애 주기를 진지하게 받아들이면 더 날카로워지며, 그것이 바로 원시 태그 스트림이 아니라 맥락화된 미러가 5권이 그 위에 짓는 기반인 이유입니다. 세 가지 모델 쪽 규율이 이 다리들이 착륙시키는 계보와 배치 키에 직접 의존합니다.

  • 누설 없는, 배치-그룹 검증. 소프트 센서의 정직한 시험 오차는 오직 그룹별(grouped) 교차 검증으로만 측정됩니다 — 무작위 행이 아니라 배치 전체를 홀드아웃하여, 한 측정값과 같은 실행에서 나온 그 쌍둥이가 분할의 양쪽에 앉지 못하게 합니다. 그 그룹화는 DCS 다리가 공급하는 batch_id 조인 키와 ERP가 조정하는 s88.genealogy 척추 없이는 불가능합니다. 모델과 검증 장은 누설 없는, 배치에 정직한 분할이야말로 진짜 R²를 듣기 좋은 R²와 갈라놓는 것임을 보입니다. 다리의 가장 조용한 필드가 모델의 헤드라인 숫자를 신뢰할 만하게 만드는 필드입니다.
  • 적용 가능 영역과 드리프트를, 공정 드리프트와 구별하기. 모델은 오직 그것이 보정된 입력 영역 안에서만 신뢰할 수 있습니다. StatusCode로 검증되고 단위를 실은 판독값은, 모니터가 공변량 이동(covariate shift)(입력이 움직였다 — 프로브가 오염되거나, 새 원자재 로트가 들어왔거나)을 진짜 공정 드리프트(process drift)(살아 있는 배양 자체가 바뀐 것)와 구별하게 해 주는 것이며, MLOps 장이 그 구분을 위해 별개의 두 탐지기를 만듭니다. 삼켜진 Bad 점이나 빠진 단위는 단지 대시보드를 망가뜨리는 데 그치지 않습니다 — 그것은 모델의 적용 가능 영역을 조용히 옮기고 드리프트 경보가 거짓말을 하게 만듭니다.
  • 일급 기록으로서의 모델 계보. 바이알을 그 세포 은행까지 거슬러 추적하는 그 동일한 계보 규율이, 예측을 그것이 만들어진 정확한 데이터, 모델 버전, 배치까지 거슬러 추적합니다 — MLOps와 생애 주기가 GMP 요구 사항으로 다루는 계보입니다. 미러는 그 기록이 걸리는 기반이며, 바로 그래서 하이브리드 모델이나 디지털 트윈은 그것을 먹이는 다리만큼만 감사 가능합니다.

관통선은 냉정합니다: 학습 모델에게 쓰레기-입력은 단지 쓰레기-출력이 아닙니다 — 그것은 검증된 모델이 여전히 자기 모니터를 통과하면서 조용히 틀어지는 것입니다. 이 장이 미러에 부과하는 모든 규율 — 멱등성, 단위, Bad-점-을-quality-0으로 — 은, 하류에서 누군가 의약품에 관한 결정을 맡겨도 좋을 모델의 전제 조건입니다.

왜 중요한가

이 세 다리를 잘못 놓으면, 여러분은 알아볼 수 있는 두 가지 방식 중 하나로 실패합니다. 과욕을 부리거나 — 오픈소스 계층이 DCS에 설정값을 쓰거나, 배치 기록에 서명하거나, 자재를 처분하게 두어 — OSS 도구가 감당할 수 없는 검증과 Part-11 부담을, 환자 이익은 전혀 없이 상당한 규제 위험과 함께 떠안게 됩니다. 아니면 교환을 과소 설계하거나 — 멱등하지 않은 쓰기, 조정 없음 — 여러분의 미러가 SAP 및 MES로부터 조용히 어긋나다가, 출하 검토 중 대시보드가 공식 기록과 모순되는 지경에 이릅니다.

이들을 제대로 하면 분업은 깔끔하고 해방적입니다. DCS는 공정을 계속 제어하고, MES는 배치를 계속 실행하고 서명하며, SAP는 자재와 주문을 계속 소유합니다. 오픈소스 계층은 표준 문을 통해 세 가지를 모두 읽고, 모든 것을 하나의 ISA-88/95 모델에 착륙시키며, 신뢰할 수 있는 맥락 위에서 맥락화, SPC, 소프트 센싱(soft-sensing)을 수행하는 빠르고 저렴하며 제약 없는 장소가 됩니다(공정 분석: SPC, MVDA & 소프트 센서에서 스택 안에 만들고, 5권의 ML & AI 장들에서 처음부터 끝까지 모델링합니다). 각 상용 시스템은 자신이 책임지는 것에 대해 기록 시스템으로 남습니다 [12]. 표준들 — 제어를 위한 OPC UA [4], 비즈니스 교환을 위한 B2MML/ISA-95 [1] — 은 미러를 충실하게 만드는 이음새입니다.

실제 현장에서는

승인 제품을 만드는 mAb 공장에 들어가면, 여러분이 발견하는 토폴로지는 이렇습니다. 스위트를 돌리는 DeltaV 또는 Siemens PCS 7 DCS — Siemens의 더 새로운 PCS neo는 그린필드(greenfield) 라인에서 등장하지만, PCS 7이 여전히 지배적인 설치 기반(installed base)으로 남습니다 — 전자 배치 기록을 보유한 상용 MES(Werum PAS-X, Körber, Tulip-on-glass 또는 유사 제품), 그리고 모든 재무와 자재를 소유한 최상단의 SAP입니다. 통합 팀은 바로 이 장이 설명하는 문들 — DCS에서 나오는 OPC UA, SAP를 오가는 B2MML/IDoc/OData — 에 자기 경력을 바칩니다. 그리고 그들이 그렇게 하는 것은 바로 아무도 그 아래의 검증된 시스템을 대체하도록 허용되지 않기 때문입니다.

이 계층에 대한 정직한 OSS-대-상용 결론은 이 책에서 가장 냉정합니다. DCS 읽기 경로에서 오픈소스는 탁월합니다. node-opcua [8], asyncua, Apache PLC4X [7]는 제어 데이터를 미러링하는 데 필요한 모든 것을 주며, 읽기 전용 NOA 패턴은 무엇도 재검증하지 않고 그것을 한다는 뜻입니다. ERP 교환에서 오픈소스는 충분히 유능합니다. B2MML은 무료이고 로열티 없는 스키마이며 [3], 파이썬 OData/IDoc 클라이언트는 1년이 아니라 한 주말짜리 일입니다. 하지만 MES 자리에는 신뢰할 만한 오픈소스 GxP 제품이 그저 없으며, 그렇지 않은 척하는 것은 이 책이 할 수 있는 가장 위험한 과장일 것입니다. 순수 오픈소스는 여러분에게 플랫폼의 대략 80%를 줍니다. MES는 마지막 GxP 1마일이 하이브리드가 아니라 확고하고 정직하게 상용인 곳들 중 하나입니다.

우리 공정의 강화/연속(intensified/continuous) 변형 — 다중 컬럼 포착을 갖춘 관류(perfusion, 위의 단일 페드배치 대신 바이오리액터에 연속으로 공급하고 수확하는 방식) — 은 이 이음새들을 가로질러 흐르는 태그와 주문을 늘리기만 하며, 그것은 규율 있고 멱등하며 조정 가능한 미러를 덜 가치 있게가 아니라 더 가치 있게 만듭니다.

핵심 용어

  • GxP / GMP — GxP는 의약품 제조를 규율하는 Good-x-Practice 규제의 집합이다(제조에 대한 GMP, 시험실에 대한 GLP, 임상에 대한 GCP). GMP(Good Manufacturing Practice)는 그 집합의 제조 구성원이므로, 둘은 관련은 있지만 동의어는 아니다 — 현장에서 쓰이는 OSS 도구는 이 틀 안에 들어맞아야 한다.
  • Part 11(21 CFR Part 11) — 전자 기록과 전자 서명을 법적으로 종이와 잉크만큼 신뢰할 수 있게 만드는 미국 FDA 규정. 그것이 부과하는 전자 서명·EBR 부담이 MES 자리가 상용으로 남는 이유다.
  • 검증 / CSV(Validation) — 전산화 시스템이 규정된 그대로 정확히 동작한다는, 그 전 생애에 걸쳐 유지되는 문서화된 위험 기반 증거. 수제 MES를 규제 기준까지 검증하는 일은 수년짜리 프로그램이다.
  • DCS(Distributed Control System, 분산 제어 시스템) — ISA-95 레벨 1-2에서 공정 루프를 돌리는 검증된 제어 계층(Emerson DeltaV, Siemens PCS7/SIMATIC). OSS 계층은 이를 OPC UA로 읽으며 [5][6], 결코 설정값을 쓰지 않는다.
  • DeltaV OPC UA 래퍼(DA / A&E / HDA) — ProfessionalPLUS / Application Station에 있는 서버로, DeltaV의 OPC 클래식 인터페이스를 실시간 Data Access, Alarms & Events, Historical Data Access를 노출하는 하나의 OPC UA 엔드포인트로 변환한다 [13].
  • Continuous Historian / Event Chronicle / Batch Historian — DeltaV의 세 내부 저장소. 각각 시계열 공정 값(데드밴드로 수집), 알람/이벤트(SQL Server), 배치/레시피 기록(SQL Server, Batch Executive로부터)이다 [14].
  • MES(Manufacturing Execution System, 제조 실행 시스템) — 레시피를 실행하고, 전자 서명을 포착하며, 전자 배치 기록을 산출하는 레벨-3 시스템. 신뢰할 만한 오픈소스 GxP 선택지가 없어 상용으로 남는다 [12].
  • ERP(Enterprise Resource Planning, 전사적 자원 관리) — 자재, 로트, 작업 지시를 소유하는 레벨-4 비즈니스 시스템(SAP S/4HANA). IDoc과 OData로 교환된다 [9][10].
  • OPC UA — IEC 62541. DCS/PLC 제어 데이터를 OSS 스택으로 읽어 들이는, 플랫폼 독립적이고 보안성을 갖춘 프로토콜 [4].
  • Apache PLC4X — OPC UA 서버가 제공되지 않을 때 Siemens S7과 다수의 레거시 PLC 프로토콜을 읽는, 벤더 중립적 오픈소스 라이브러리(Apache 2.0) [7].
  • node-opcua — DCS OPC UA 서버를 탐색/읽기/구독하는 데 쓰이는 MIT 라이선스 Node.js OPC UA SDK [8].
  • B2MML(Business To Manufacturing Markup Language) — 자재/로트/작업 지시 교환에 쓰이는, 로열티 없는 ISA-95의 XML 구현 [2][3].
  • ISA-95 / IEC 62264 — B2MML이 직렬화하고 우리 PostgreSQL 모델이 구현하는, 엔터프라이즈-제어 통합을 위한 표준 모델과 용어 [1][11].
  • IDoc / OData — 주문과 자재 데이터를 교환하기 위한 SAP의 고전적 메시지 형식과 현대적 REST API [9][10].
  • 기록 시스템(system of record) — 어떤 종류의 데이터에 대한 권위 있고 검증된 출처. 여기서는 제어에 대한 DCS, 실행에 대한 MES, 자재에 대한 SAP. OSS 계층은 미러일 뿐, 결코 기록이 아니다 [12].
  • 멱등(idempotent) — 반복해도 안전한 쓰기. 관계형 미러는 기본 키와 ON CONFLICT로, 시계열은 구간-삭제-후-삽입으로 이를 얻는다.
  • 그림자 기록(shadow record) — 검증된 기록 시스템과 어긋나는, 통제되지 않은 병렬 복사본. 조정이 막기 위해 존재하는 실패 모드.
  • NAMUR 개방 아키텍처(NAMUR Open Architecture, NOA) — 검증된 제어 코어를 변경하지 않고 분석에 데이터를 공급하는, 승인된 거의-읽기-전용의 두 번째 채널.
  • OPC UA DataValue — DCS가 읽기당 반환하는 구조. Value, StatusCode(우리 192/64/0 품질이 됨), SourceTimestamp, 그리고 EngineeringUnits 속성으로, 각 필드가 ts.sensor_reading 6-튜플의 한 열을 채운다 [4].
  • ManufacturingOrder(멱등성 키) — 조정 참조로 보관되는 SAP 생산 주문 ID. 다리는 그것으로부터 안정적인 batch_id를 결정론적으로 도출하므로, ON CONFLICT (batch_id) 절이 같은 주문의 재전송을 중복 배치가 아니라 무동작으로 만든다 [9].
  • RDF 트리플 — 주어-술어-목적어 사실(계보 엣지 bp:PApool-007 bp:derivedFrom bp:BATCH-2026-007, 또는 타입 리터럴 판독값). 다리가 관계형 모델에 착륙시키는 같은 사실의 그래프 형태로, SPARQL이 걸어 가고 SHACL이 관문을 친다(의미론과 디지털 트레드에서 만든다).
  • SHACL 출하 관문 — 출하 승인된 모든 배치가 필요한 계보와, 존재하며 범위 안에 있는 CQA를 싣는다고 단언하는 닫힌 세계 Shapes Constraint Language 셰이프. 누락된 엣지나 빠진 단위는 그림자 기록으로 늙는 대신 지금 실패한다(온톨로지 책의 출하 관문).
  • 그룹별(leave-one-batch-out) 교차 검증 — 소프트 센서의 정직한 오차를 측정하는 누설 없는 방식. 무작위 행이 아니라 배치 전체를 홀드아웃하며, DCS 다리가 공급하는 batch_id 조인 키와 ERP가 조정하는 계보 척추에 의존한다(모델과 검증).
  • 적용 가능 영역 / 드리프트 — 모델이 보정된 입력 영역, 그리고 모니터가 입력 드리프트(오염된 프로브, 새 로트)를 진짜 살아 있는 배양의 공정 드리프트와 구별하게 해 주는 StatusCode로 검증되고 단위를 실은 판독값(MLOps와 생애 주기).

다음 이야기

이제 제어 데이터와 비즈니스 데이터는 우리 스택으로 충실하게 미러링되지만, 한 기록 시스템은 여전히 바깥에 서 있습니다. 바로 실험실입니다. 출하 시험 — 한 로트가 출하될지를 결정하는 분석 시험 — 은 흔히 상용 LIMS에 살며, 그것이 서명하는 결과가 가장 중요한 결과입니다. 다음 장 상용·오픈소스 LIMS 연동(Bridging to Commercial & Open-Source LIMS)은 그 세계로의 시료-및-시험성적서(certificate-of-analysis) 교환을 만듭니다. 솔직한 주석과 함께 — LabKey의 Part 11 기능은 유료 장벽 뒤에 있고, SENAITE와 openBIS는 각각 QC와 공정 개발 자리에 들어맞는다는 점, 그리고 가장 위험이 높은 곳에 적용되는 동일한 대체-말고-미러링의 규율입니다.