본문으로 건너뛰기

구현: 와이어에서 그래프까지

📍 현재 위치: 제5부 · 구현 — 방법론: SAMOD + LOT. 모델은 개념화되고, 형식화되고, 정렬되었습니다. 이제 그것을 먹여야 합니다. 이 장은 하역장입니다. 공장이 이미 구사하는 표준들 — PI 히스토리안 행, OPC UA 읽기, B2MML 배치 기록, Allotrope 분석 파일 — 이 어떻게 이 책의 나머지가 질의하는 바로 그 트리플이 되는지를, 각 크로스워크를 단언이 아니라 실행되는 코드로 증명하며 보여 줍니다.

손으로 친 Turtle(RDF의 사람이 읽을 수 있는 텍스트 구문으로, .ttl 파일에 쓰입니다)만 본 모델은 공장을 한 번도 만나 본 적이 없는 모델입니다. 이 책이 추론하는 데이터셋은 Turtle로 도착하지 않습니다. 그것은 초당 두 번씩 흐르는 히스토리안 태그로, 컨트롤러에서 읽힌 OPC UA 변수로, MES(Manufacturing Execution System — 배치를 돌리고 기록하는 현장 시스템)가 XML로 교환하는 ISA-88 레시피로, HPLC가 내보낸 크로마토그램으로 도착합니다. 구현이란 그 각각을 동일한 그래프(지식 그래프 — 이 책의 나머지가 질의하는, 연결된 사실의 집합으로, 모든 노드가 bp:BATCH-2026-001 같은 전역 이름을 얻으며, bp: 접두사는 이 책의 로컬 어휘를 가리키는 약칭입니다) — bp:BATCH-2026-001, bp:CCP-001, bp:SEC-Result-001 — 로 바꾸되, 새로운 동일성을 발명하지 않고 백만 개의 센서 점을 트리플(그래프의 원자적 사실 — BATCH-2026-001 → hasOutput → DP-001 같은 주어–술어–목적어 진술)로 평평하게 뭉개지 않는 행위입니다. 동반 데이터셋의 네 개의 작은 스크립트는 각각 하나의 실제 공장 표준을 받아 이미 instances.ttl에 살고 있는 bp: 트리플을 내보내므로, 크로스워크는 약속되는 것이 아니라 전시됩니다.

쉽게 말하면

네 대의 배달 트럭이 하나의 하역장에 후진해 댑니다. 한 대는 공장의 데이터 기록기에서 나온 온도 측정값을, 한 대는 같은 측정값을 생물반응기 컨트롤러에서 곧장, 한 대는 제조 시스템에서 나온 레시피·배치 서류를, 한 대는 HPLC에서 나온 실험실 결과를 싣고 있습니다. 트럭 위에서는 네 가지 다른 언어를 쓰지만 — 하역장에는 각각을 위한 번역기가 있고, 모든 짐은 동일한 창고 코드로 라벨이 붙은 채 내려옵니다. 이 장은 그 네 개의 번역기입니다. 요령은 창고가 라벨과 선반 번호(단위가 붙은 숫자와 파일을 가리키는 포인터)만 보관한다는 것입니다 — 백만 점짜리 크로마토그램(분석 기기가 만들어 내는 흔적 — 그 모양이 증거인, 수천 개의 강도-대-시간 점)을 선반 위에 풀어 놓지는 않습니다.

질문에서 출발하라

이 장은 무엇보다 하나의 역량 질문 — 완성된 온톨로지가 답할 수 있어야 하는, 번호가 매겨지고 합격/불합격 시험으로 돌아가는 평이한 언어의 요건 — 을 떠받칩니다: CQ-19저장된 모든 양은 단위를 지니는가, 맨숫자는 하나도 없는가? (명세에 정의됨). mAb(단일클론항체) 캠페인에서 그 요건은 장부 정리의 까다로움이 아닙니다. 원료의약품 로트는 단량체 순도를 95.0 스펙 하한과, 배양의 온도를 36.0–37.0 °C 범위와 비교해 출하되며, 비교는 양쪽이 같은 단위를 지닐 때만 의미가 있습니다. 와이어는 바로 그 단위가 떨어져 나가는 곳입니다 — 히스토리안 컬럼이 부동소수로 읽히고 그 옆의 Cel은 버려지며, 그래프는 출하 게이트가 안전하게 해석할 수 없는 스펙과 비교하게 될 벌거벗은 36.51들로 채워집니다. 맨숫자 2.41은 출하에서 탈락하는 고분자량 응집체의 퍼센트일 수도, 무해한 개수일 수도 있습니다. 단위가 없으면 배치 처분 결정은 모래 위에 세워집니다. 아래의 모든 스크립트는 일부러 단위를 경계 너머로 운반합니다. 이 장은 또한 구조적 질문 CQ-21(런이 어느 용기에서 일어났는가 — 장비를 배치 물질과 분리해 유지)과 계보의 추적 절반에도 봉사하는데, B2MML과 히스토리안 다리야말로 장비와 조밀한 스트림이 리콜을 추적 가능하게 만드는 동일성을 망치지 않고 런에 붙는 곳이기 때문입니다. (이 장을 관통하는 제조 용어 — 크로마토그램, Protein A 풀, 연마, UF/DF, 원료의약품, 수확, 시드 트레인, 워킹 세포 은행 — 이 처음이라면, 제1권이 세포에서 바이알까지의 물리적 사슬 전체를 평이한 언어로 다룹니다: 바이오의약품이란 무엇인가처음부터 끝까지 바이오공정 개관을 보십시오. 여기서는 그 순서와 혈통 간선만 필요합니다.)

두 와이어, 하나의 관측: 히스토리안과 OPC UA

mAb 배양을 돌리는 생산 생물반응기는 몇 초마다 일군의 프로브 — 온도, pH, 용존 산소 등 — 로 모니터링됩니다. 세포가 살아 있고 제어 전략이 각 파라미터를 2주 동안 좁은 범위 안에 붙들어 두기 때문입니다. 그 PAT(Process Analytical Technology — 인라인 센서 스트림) 데이터의 격류는 공장에서 가장 조밀한 스트림이며, 첫 크로스워크에는 같은 곳에 도착해야 하는 두 개의 정문이 있습니다. 측정값은 PI 히스토리안 행 — ts, tag, value, unit, quality, batch_id — 으로 그래프에 도달할 수도(여기서 PI 히스토리안은 각 센서 측정값을 한 행으로 저장하는 공장의 시계열 데이터베이스입니다), 한 홉 앞에서 생물반응기 컨트롤러에서 곧장 OPC UA(OPC Unified Architecture — 컨트롤러에서 라이브 값을 읽는 산업 프로토콜) 데이터 액세스 변수로 읽힐 수도 있습니다 [6]. 모델의 규율은 두 경로가 동일한 sosa:Observation(센서 측정값이 되는 표준 노드)을 발행(생성)한다는 것입니다 [4]. OPC UA NodeId의 문자열 식별자(;s= 뒤의 부분)가 바로 히스토리안 태그이기 때문입니다(ns=2;s=BR101.Temp.PVBR101.Temp.PV). 매핑은 RML(RDF Mapping Language — 어느 소스 필드가 어느 트리플이 되는지를 말하는 선언적 방식)로 선언되고 [5] historian_to_rdf.py에 의해 프로세스 안에서 실행됩니다:

# historian_to_rdf.py — each historian row becomes one sosa:Observation; the unit travels with it.
g.add((obs, RDF.type, SOSA.Observation))
g.add((obs, SOSA.observedProperty, prop))
g.add((obs, SOSA.hasSimpleResult, Literal(value, datatype=XSD.float)))
g.add((obs, SOSA.resultTime, Literal(ts, datatype=XSD.dateTime)))
# The unit is NO LONGER dropped: keep the raw UCUM code, and add the QUDT unit IRI where one exists.
g.add((obs, QUDT.ucumCode, Literal(unit, datatype=XSD.string)))
if unit in UCUM_TO_QUDT: # {"Cel": unit:DEG_C}
g.add((obs, QUDT.hasUnit, UCUM_TO_QUDT[unit]))
g.add((obs, BP.fromBatch, batch))
g.add((batch, BP.hasTrace, prop)) # the INDEX edge — the stream stays in PI

관측의 IRI 자체가 조인입니다: 두 로더 모두 https://example.org/historian/obs/{tag}/{ts} — 08:00:10 온도 읽기에 대해 obs/BR101.Temp.PV/2026-03-02T08:00:10Z — 를 발행하므로, 같은 프로브를 같은 순간에 읽은 히스토리안 행과 OPC UA 읽기는 우연히 일치하는 두 관측이 아니라, 두 문으로 도달한 하나의 노드입니다. 그 공유된 템플릿이 둘을 모두 실행했을 때 인덱스 간선이 정확히 들어맞는 이유입니다.

qudt:ucumCode 줄은 CQ-19를 운영 가능하게 만든 것입니다. 히스토리안의 단위 컬럼에 앉아 있던 Cel이 UCUM 코드로 관측에 함께 실려 오고, 깨끗한 QUDT 단위가 존재하는 곳에서는 그것이 또한 IRI로 붙습니다(Celunit:DEG_C). OPC UA 로더는 두 번째 문이 같은 방에 도착함을 증명합니다 — 그것은 EUInformation(공학 단위, UCUM 코드로)을 읽고, Good StatusCode를 PI 품질 192(PI가 양호한 품질의 값에 쓰는 정수)로 매핑하며, 동일한 관측 IRI를 발행합니다:

# opcua_to_rdf.py — the NodeId identifier IS the tag, so the OPC UA read lands the same observation.
def tag_of(node_id: str) -> str:
return node_id.split(";s=", 1)[1] if ";s=" in node_id else node_id
# ns=2;s=BR101.Temp.PV -> BR101.Temp.PV -> https://example.org/historian/obs/BR101.Temp.PV/<ts>

둘을 모두 실행하면 인덱스 간선과 단위 처리가 정확히 들어맞습니다. 히스토리안 다리는 그 인덱스 간선을 내보냅니다:

historian -> RDF: 23 triples from 3 rows

bp:hasTrace index edges (one per batch/tag — the stream stays in PI):
BATCH-2026-001 hasTrace https://example.org/historian/tag/BR101.Temp.PV
BATCH-2026-001 hasTrace https://example.org/historian/tag/BR101.pH.PV
OPC UA -> RDF: 16 triples from 2 variable reads

the NodeId identifier IS the historian tag, so both routes land the same observation:
ns=2;s=BR101.Temp.PV -> sosa:Observation (value 36.51, unit http://qudt.org/vocab/unit/DEG_C, quality 192)
ns=2;s=BR101.pH.PV -> sosa:Observation (value 7.02, unit pH, quality 192)

정직한 비대칭에 주목하십시오: Celunit:DEG_C로 해소되지만, pH에는 깨끗한 QUDT 단위가 없으므로 맨 UCUM 코드로 남습니다. 그래도 값은 결코 맨숫자가 아닙니다 — 그것은 pH를 자신의 코드로 지니고 있습니다 — 다만 QUDT IRI로의 크로스워크는 부분적이며, 스크립트는 그렇지 않은 척하지 않습니다. 이것이 작동 중인 인덱스 간선입니다: 배치는 태그 IRI로 가는 하나의 bp:hasTrace를 얻고, 수백만 개의 점은 PI에 머물며 결코 그래프로 평평하게 뭉개지지 않습니다.

Good192 품질 매핑은 겉치레가 아닙니다. 실제 생물반응기에서 OPC UA DataValueBadUncertain으로 돌아올 수 있습니다 — 프로브가 드리프트하거나, 루프가 서비스 우회 상태이거나, 순간적인 통신 손실이 있거나 — 그리고 나쁜 StatusCode가 찍힌 측정값은 결코 조용히 사용 가능한 관측이 되어서는 안 됩니다. Bad로 표시된 36.51 Cel은 그 CPP(Critical Process Parameter — 공장이 범위 안에 붙들어야 하는 다이얼)의 CPV(Continued Process Verification) / SPC(Statistical Process Control) 추세에서 제외되어야 하며 출하 결정을 뒷받침해서는 안 됩니다. 그것을 유효한 것으로 다루는 일은 터지기를 기다리는 ALCOA+(데이터 무결성) 지적 사항입니다. 품질 정수를 관측에 함께 실어 두는 것이 하류 질의가 단순한 이 아니라 사용 적합성을 말할 수 있게 하는 것입니다 — 단위가 함께 실려 오는 것과 같은 이유입니다: 검토자가 자격을 부여할 수 없는 숫자는 검토자가 신뢰할 수 없는 숫자입니다.

크로스워크는 어떻게 쓰이는가. 이 네 로더는 각각 완성되고 나면 영감의 산물처럼 보이지만, 각각은 동일하게 짧고 재현 가능한 절차로 작성됩니다 — 명세의 역량 질문 레시피의 구현판입니다. 가장 구체적인 사례, 즉 historian_to_rdf.py가 읽는 히스토리안 행 ("2026-03-02T08:00:10Z", "BR101.Temp.PV", 36.51, "Cel", 192, "BATCH-2026-001")에 대해 공개적으로 작업해 보는 것이 가치가 있습니다:

  1. 소스와 그 고유한 모양을 명명하라. 소스는 PI 히스토리안이고, 그 레코드는 고정 컬럼 행 — ts, tag, value, unit, quality, batch_id — 입니다. 모양을 먼저 고정하는 것이 매핑을 추측이 아니라 계약으로 만듭니다: 이후의 모든 단계는 명명된 하나의 필드를 가리킵니다.
  2. 각 필드를 그것이 가리키는 BFO 범주별로 분류하라. 필드는 결코 단지 데이터가 아닙니다 — 그것은 상위 척추 안의 어떤 종류의 것을 가리킵니다(BFO — Basic Formal Ontology — 는 물질적 사물, 품질, 프로세스 같은, 모든 용어가 그 아래로 분류되는 작은 최상위 종류 집합이며, 아래의 굵은 글씨 — 품질, 관측 가능 속성, 물질 존재, 독립 연속체 — 는 평범한 영어가 아니라 그것의 형식 클래스입니다). 센서 측정값 36.51은 프로세스에서 실현된 품질(온도)의 크기이므로 hasSimpleResult를 지닌 sosa:Observation이 되고, 단위 Cel은 그 크기가 측정되는 차원이며, 태그 BR101.Temp.PV는 스트림이 추적하는 관측 가능 속성을 명명하고, ts는 측정값을 프로세스 안에 위치시키며, batch_id BATCH-2026-001은 측정값이 관한 물질 존재입니다. 장비(BR101, 태그 안에 인코딩됨)는 별개의 독립 연속체입니다 — 그것은 배치 위에 두 번째 유형을 얻지 않습니다. 필드를 이렇게 분류하는 것이 바로 매핑이며, 나머지는 기계적 작업입니다.
  3. 동일성을 고정하는 필드들로부터 결정론적 IRI를 발행하라. IRI(Internationalized Resource Identifier)는 RDF가 모든 노드에 부여하는, 시스템을 가로질러 동일한 전역 고유 웹 이름이며, 결정론적이란 소스 필드로부터 고정된 템플릿으로 지어진다는 뜻이므로 같은 입력은 언제나 같은 이름을 산출합니다. 관측의 동일성은 그 태그와 순간이므로, 그 IRI는 템플릿 obs/{tag}/{ts} — 같은 프로브를 같은 순간에 읽은 OPC UA 읽기가 중복이 아니라 동일한 노드에 착지하도록 선택된 — 입니다. 동일성은 행마다 새로운 무작위 IRI가 아니라 소스 키에 대한 템플릿에서 옵니다.
  4. 유형이 정해진 트리플을 내보내며 단위를 경계 너머로 운반하라. 분류된 각 필드는 발행된 주제에 대한 하나의 트리플 — rdf:type sosa:Observation, hasSimpleResult 36.51, 그리고 qudt:ucumCode "Cel"(깨끗한 QUDT IRI가 존재하는 곳에서는 qudt:hasUnit unit:DEG_C도) — 이 됩니다. 단위는 결코 떨어지지 않으며, 그 단 하나의 규율이 CQ-19입니다.
  5. 페이로드가 아니라 인덱스 간선을 내보내라. 배치는 태그 IRI로 가는 정확히 하나의 bp:hasTrace를 얻고, 수백만 개의 점은 PI에 머뭅니다. 로더는 증거로서 작고 한정된 관측 집합을 내보낼 뿐, 결코 스트림을 내보내지 않습니다.
  6. 로더를 실행하고 그 결과를 손으로 단언된 그래프와 대조하라. 내보낸 트리플은 instances.ttl이 이미 보유한 bp: 노드와 일치하고 validate.py를 통과해야 합니다 — 크로스워크는 단언되는 것이 아니라 전시됩니다.

동일한 여섯 단계가 다른 세 로더를 썼습니다. 소스 표준과 필드-대-범주 분류만 바뀝니다. B2MML 로더의 2단계가 그 진가를 발휘하는 곳입니다 — <Equipment> 필드는 배치 위의 두 번째 유형이 결코 아니라 런 위의 occursIn 간선으로 분류됩니다 — 그리고 ASM 로더의 3단계는 JSON-LD @context에 기대어 필드-대-IRI 분류를 선언적으로 수행합니다. BFO 범주별 분류인 2단계는 기계가 대신해 줄 수 없는 단계이며, 마무리 절이 되돌아오는 손으로 쓴 부분입니다.

레시피와 배치 기록: B2MML, 장비는 따로 떼어 둔 채

이 책이 추론하는 계보는, 실제 현장에서는 RDF derivedFrom 간선으로 살지 않습니다 — 그것은 독점 MES 안의 한 행으로 살며, B2MML XML — Business To Manufacturing Markup Language, ISA-88 배치 제어 및 ISA-95 엔터프라이즈 통합 표준의, MESA 산업 그룹이 발행한 XML 형식 — 로 교환됩니다 [7]. b2mml_to_rdf.py는 실제 모양의 두 문서를 읽어 그 간극을 메웁니다: 마스터 레시피(그 계획 — 프로세스가 무엇을 해야 하는지) 하나와 실행된 배치 생산 기록(이 런에서 실제로 무슨 일이 일어났는지) 하나입니다. 마스터 레시피(Recipe-mAb-A, 버전 2.0)는 생산 레시피 요소, 규정된 파라미터, 그리고 장비 요구사항과 함께 bp:MasterBatchRecord로 매핑됩니다. 실행 기록은 이 장 전체에서 가장 중요한 모델링 규칙이 등장하는 곳입니다 — <Equipment> 참조는 배치 위의 두 번째 rdf:type이 결코 아니라, 런 위의 occursIn 간선이 됩니다:

# b2mml_to_rdf.py — the batch is typed ONCE; the vessel becomes occursIn on the RUN.
batch = BP[_txt(info, f"{B2}BatchID")]
g.add((batch, RDF.type, BP.Batch)) # typed once, as a Material — never also as the vessel
run = BP["CCP-001"]
g.add((run, RDF.type, BP.CellCultureProcess))
g.add((run, BP.hasOutput, batch))
g.add((run, BP.realizes, BP[_txt(info, f"{B2}MasterRecipeID")]))
equip = info.find(f"{B2}Equipment")
if equip is not None:
vessel = BP[_txt(equip, f"{B2}EquipmentID")] # BR-101: a SEPARATE equipment node
g.add((run, BP.occursIn, vessel))

이것은 적재 시점에 시행된 CQ-21이며, mAb 캠페인에서 용기와 배치가 무엇인지 때문에 중요합니다. 생물반응기 BR-101은 캠페인을 가로질러 지속합니다 — 지난달엔 다른 배치를 돌렸고 다음 달엔 또 다른 배치를 돌릴 것입니다. 배치 BATCH-2026-001은 이 런에만 존재하는 항체를 품은 배양입니다. 둘은 서로 다른 BFO 범주 — 용기는 캠페인을 가로질러 견디는 독립 연속체이고, 배치는 이 런에만 존재하는 물질입니다 — 이며, 모델은 그 둘을 융합하는 일을 두 가지 방식으로 한꺼번에 금지합니다. OWL 공리 bp:Material owl:disjointWith bp:Equipment(owl:disjointWith는 어떤 단일한 것도 두 종류일 수 없다고 말합니다)(그리고 bp:Batch owl:disjointWith bp:CellCultureProcess)는 DL 추론기(진술된 공리로부터 새로운 사실을 도출하는 소프트웨어)에게 그 모순을 진술합니다: Protégé(표준 온톨로지 편집기)에서 HermiT나 ELK 추론기로 그래프를 적재하면, 자신의 용기로 유형이 정해진 배치는 온톨로지를 모순으로 만듭니다. 그러나 동반 validate.py가 돌리는 OWL-RL 폐포는 owl:disjointWith를 일부러 무시합니다 — OWL-RL 규칙 프로파일에는 disjointness 규칙이 없습니다 — 그래서 실행되는 잡음은 닫힌 세계의 SHACL 셰이프(존재하는 데이터를 검사하는 검증 규칙)입니다: shapes.ttlMaterialNotEquipmentShape(sh:targetClass bp:Material ; sh:not [ sh:class bp:Equipment ])이며, 적재 시점에 발동하고 CQ-23에 의해 행사됩니다. disjointness는 한 번 단언되고 두 번 시행됩니다 — 추론기에 의해 열린 세계로(추론기는 빠진 사실을 거짓으로 가정하지 않으므로 오직 진술된 모순만 표시합니다), SHACL에 의해 닫힌 세계로(SHACL은 존재하는 것을 진실 전부로 다루므로 빠지거나 잘못된 것을 표시할 수 있습니다). 순진한 로더는 <Equipment>BR-101</Equipment>를 읽고 배치를 생물반응기로 찍어, 물질을 그것이 만들어진 용기와 뒤섞어 버립니다 — 바로 그 두 가드가 잡아내려 존재하는, 심어진 혼동이자, 방치하면 어떤 배치가 주어진 용기에서 돌았는지 묻거나 리콜을 런을 거슬러 추적하는 일을 불가능하게 만드는 오류입니다. 충실한 로더는 BATCH-2026-001bp:Batch로 유지하고, 오직 런 bp:CCP-001만이 bp:BR-101에서 occursIn하도록 합니다. 세포 배양 런도 균일하지 않습니다 — 그것은 세포가 증식하는 성장 단계와 항체를 만드는 생산 단계를 거치며, 각 단계는 고유한 파라미터 범위를 가집니다. 이 로더가 읽는 실행 기록은 생산 단계를 싣고 있으므로, 그 실측값은 CCP-001-production 단계에 붙은 bp:RealizedParameterSetting으로 넘어오고, 각각 qudt:QuantityValue를 지녀 설정점이 결코 맨숫자가 아니게 합니다: Celunit:DEG_C로, 공급 속도의 일당 단위 /dunit:PER-DAY로 해소됩니다. 실행해 보십시오:

B2MML -> RDF: 8 triples from the master recipe, 25 more from the as-run batch record (33 total)

the <Equipment> reference becomes occursIn on the RUN, not a 2nd type on the batch:
CCP-001 occursIn BR-101
BATCH-2026-001 a Batch (typed once — Material only)

이것들은 instances.ttl이 손으로 단언하는 동일한 bp:CCP-001, bp:BR-101, bp:Recipe-mAb-A입니다 — MES → 온톨로지 크로스워크가 처음부터 끝까지 전시된 것입니다.

실험실 결과: Allotrope ASM를 동일한 SEC-Result 노드로

분석 스트림은 넷 중 가장 풍부하며, 정반대의 두 실수에 가장 빠지기 쉽습니다: 크로마토그램을 트리플로 평평하게 뭉개거나, 결과를 벤더 파일 안의 맨숫자로 남겨 두거나. 와이어 포맷이 문제가 되기도 전에, 결과는 모양부터 올바르게 잡혀야 하며, 가장 흔한 분석 모델링 오류는 "그 시험"을 하나의 노드로 융합하는 것입니다. 모델은 대신 그것을 세 종류의 것으로 나눕니다. mAb 출하에서 그 분리야말로 숫자를 방어 가능하게 만들기 때문입니다. 방법(bp:SEC-Method) — 검증된 크기 배제 절차 — 은 하나의 계획이며, 복사 가능한 정보이고(bp:Method rdfs:subClassOf bp:InformationArtifact이므로 물질이나 프로세스 disjointness와 결코 충돌하지 않습니다), 오늘 돌리든 다음 캠페인에 돌리든 동일한 SEC 방법입니다. 분석법 실행(assay)(bp:SEC-Assay-001)은 occurrent(일어나는 것 — BFO 용어로 사건 또는 프로세스)입니다: 명명된 분석가가 날짜가 찍힌 장비에서 특정 시료에 그 방법을 실제로 돌린 것입니다. 그리고 결과(bp:SEC-Result-001)는 정보 산물(기록된 정보의 한 조각, 역시 BFO 범주)이며, 시료에 관한 유형이 정해진 사실입니다. 이 셋을 떼어 두면 그래프는 하나의 검증된 방법이 여러 분석법 실행으로 돌아 여러 결과를 낳았음을, 두 출하 결과가 같은 방법에서 나왔음을, 또는 두 런 사이에 방법이 개정되었음을 말할 수 있습니다 — 융합된 하나의 "시험" 노드로는 어느 것도 표현할 수 없고, 로트가 문제 될 때 품질 조사관에게는 모두 필요한 것들입니다. 그것들을 융합하면 출하 추적은 출처 없는 하나의 숫자로 무너집니다.

시료는 네 번째 노드이며, 실험실에서 프로세스로 돌아가는 다리입니다. 시료는 그것이 인출된 배치로부터 derivedFrom된 물질 존재이므로, 시료에 관한 결과는 전이적으로 배치에 관한 증거가 됩니다. 이것이 분석 숫자가 계보에 다시 합류하는 방식입니다 — 배치 문자열로 찍히는 것이 아니라, 로트에서 파생된 검체에 관함으로써, 전이적 derivedFrom이 자동으로 세포 은행까지 거슬러 걷는 사슬로 합류합니다. 결과는 스칼라와 곡선을 가리키는 포인터를 담을 뿐, 곡선 자체는 결코 담지 않습니다:

# instances.ttl — method (plan) / assay (occurrent) / sample / result, with the curve referenced.
bp:SEC-Method a bp:Method ; rdfs:label "validated SEC method" .
bp:SMP-DS-001 a bp:Sample ; rdfs:label "DS-001 release sample" ; bp:derivedFrom bp:DS-001 .
bp:SEC-Assay-001 a bp:SECAssay ; rdfs:label "SEC assay on DS-001 sample" ;
bp:realizes bp:SEC-Method ;
bp:hasInput bp:SMP-DS-001 ;
bp:hasDevice bp:HPLC-07 ;
bp:performedBy bp:Analyst-AB ;
bp:assayDate "2026-03-10"^^xsd:date ;
bp:hasResult bp:SEC-Result-001 .
bp:SEC-Result-001 a bp:SECResult ; rdfs:label "SEC result for DS-001" ;
bp:isAbout bp:SMP-DS-001 ;
bp:monomerPct "98.611"^^xsd:float ;
bp:specLow 95.0 ; bp:verdict "PASS" ;
bp:hasChromatogram bp:ADF-SEC-001 . # the heavy curve, referenced not embedded
bp:ADF-SEC-001 a bp:AnalyticalDataFile ;
rdfs:seeAlso <https://example.org/adf/SEC-Result-001.adf> .

실제 현장에서 그 동일한 사실은 Turtle로 도착하는 일이 드물고 — 단 하나의 모양으로 도착하는 일도 드뭅니다. 같은 SEC 순도가 Agilent 시스템에서 나올 수도, Waters 시스템에서 나올 수도 있으며, 각각 자신의 파일 포맷과 자신의 필드 이름을 내보냅니다. 이 장비 수준의 이질성이 HPLC를 교체하거나 작업을 위탁 실험실로 옮기는 일을 은근히 고통스럽게 만듭니다. Allotrope(분석 데이터를 위한 벤더 중립 표준을, 그 AFO 온톨로지를 포함해 발행하는 산업 컨소시엄)는 바로 그 문제에 대한 답입니다: 그것은 어느 벤더가 만들었든 분석 결과에 하나의 표준화된 의미를 부여하므로, monomerPct는 단위가 붙은 숫자일 뿐 아니라 알려진 측정 유형의 결과이고, 유형이 정해진 장비유형이 정해진 시료명명된 방법으로 만든 것 — 실험실이 장비를 바꾸든 런이 현장을 옮기든 동일한 사실이 됩니다 [1]. 와이어 위에서 그 사실은 대개 ASM — Allotrope Simple Model, 장비 소프트웨어가 내보내는 경량 JSON-LD 문서이자 완전한 ADF 데이터 큐브의 더 값싼 형제 — 로 도착합니다. ASM는 JSON-LD이므로 그 @context가 각 평범한 키를 동일한 bp:, af-r:, qudt: IRI로 매핑하며, 그래서 어느 벤더의 내보내기를 적재하든 동일한 bp:SEC-Result-001 트리플을 산출합니다. asm_to_rdf.py는 그 하나의 문서를 파싱하고 핵심 트리플을 데이터셋과 대조해 확인합니다:

ASM JSON-LD -> RDF: 7 triples from one Allotrope Simple Model document

same bp:SEC-Result-001 as the Turtle (cheaper ingest, identical meaning):
[OK] typed bp:SECResult
[OK] typed AFO chromatogram
[OK] monomerPct 98.611
[OK] verdict PASS
[OK] hasChromatogram -> ADF-SEC-001

이 책 전체를 꿰는 동일한 98.611 단량체 순도, 동일한 PASS 판정, bp:ADF-SEC-001을 가리키는 동일한 포인터 — 무거운 ADF가 아니라 가벼운 JSON 경로에서 나옵니다. ASM 문서의 "@type": ["bp:SECResult", "af-r:AFR_0000410"]는 두 라벨을 한꺼번에 운반합니다: 그 이중 유형 지정은 일부러 중복적이지만 align.ttlbp:SECResult rdfs:subClassOf af-r:AFR_0000410과 일관되므로, 장비 내보내기가 독립적으로 서서 소비자가 정렬 파일을 결코 적재하지 않더라도 AFO 크로마토그램으로 올바르게 읽힙니다. OBI(Ontology for Biomedical Investigations)는 조사 틀(계획된 프로세스로서의 분석법 실행)을, Allotrope(AFO, the Allotrope Foundation Ontology)는 분석적 의미(크기 배제 크로마토그램으로서의 결과)를 제공하며 [1][2], 정렬 파일은 bp:SECResult를 양쪽 모두로 유형 지정(하위 클래스로 선언)합니다.

SEC가 그 하나의 검체에 돌린 유일한 분석법 실행은 아니며, 하역장은 출하 패널 전체를 서로 끊긴 숫자의 행이 아니라 하나의 집합으로 착지시켜야 합니다. 동일한 bp:SMP-DS-001 시료는 세 개의 결과를 지닙니다. mAb는 세 가지 직교하는 품질 질문에 대해 동시에 출하되기 때문입니다: SEC(크기 배제 크로마토그래피)에 의한 단량체 순도, CEX(양이온 교환 크로마토그래피)에 의한 전하 변이체 분포, ELISA(항체 기반 분석법)에 의한 HCP(숙주 세포 단백질) 불순물입니다. 데이터셋은 셋 모두를 동일한 검체에 대해 단언하며, 각각은 그 의미를 제공하는 온톨로지로 유형이 정해집니다:

# instances.ttl — the release panel: three assays, three results, one sample, one batch.
bp:CEX-Assay-001 a bp:CEXAssay ; rdfs:label "CEX assay on DS-001 sample" ;
bp:hasInput bp:SMP-DS-001 ; bp:hasResult bp:CEX-Result-001 .
bp:CEX-Result-001 a bp:CEXResult ; rdfs:label "CEX result for DS-001" ;
bp:isAbout bp:SMP-DS-001 ; bp:cexMainPct "70.686"^^xsd:float ; bp:verdict "PASS" .
bp:HCP-Assay-001 a bp:HCPAssay ; rdfs:label "HCP ELISA on DS-001 sample" ;
bp:hasInput bp:SMP-DS-001 ; bp:hasResult bp:HCP-Result-001 .
bp:HCP-Result-001 a bp:Result ; rdfs:label "HCP result for DS-001" ;
bp:isAbout bp:SMP-DS-001 ; bp:hcpPpm "12.0"^^xsd:float ; bp:verdict "PASS" .

bp:HCPAssayrdfs:subClassOf obo:OBI_0000661 — OBI의 ELISA — 인데, 여기서는 AFO에 단일한 검증된 IRI가 없는 반면, SEC 결과는 AFO 크로마토그램의 하위 클래스입니다. 두 어휘는 중복이 아니라 상보적이며, 각각 상대가 명명하지 않는 것을 명명합니다. 방법·분석법 실행·시료·결과를 별개의 노드로 유지하는 보상은 정확히 이것입니다: 출하 패널은 하나의 배치에서 파생된 하나의 검체를 공유하는 결과들의 집합이지, 문자열을 공유하는 컬럼들의 납작한 기록이 아닙니다 — 그래서 "이 로트의 모든 CQA가 진정으로 이 배치에서 파생된 시료에서 통과했는가?"라는 질문은 사무적 대조가 아니라 순회가 됩니다.

셋은 각각 서로 다른 실패 모드에 답하며, 그것이 그것들이 직교하면서 함께 돌아가는 이유입니다: SEC 단량체 순도(95.0 % 하한 대비 98.611 %)는 크기 변이체 — 응집체와 단편 — 를 게이트하고, CEX 주봉 분율(60–80 % 창 안의 70.686 %)은 전하 변이체 — 탈아미드화, C-말단 라이신, 시알릴화 드리프트 — 를 게이트하며, ELISA에 의한 HCP(100 ppm 상한 대비 12.0 ppm, 여기서 ppm은 제품 mg당 숙주 세포 단백질 ng)는 공정 유래 불순물 제거를 게이트합니다. 원료의약품에서의 12 ppm을 Protein A 풀에서 측정된 850 ppm과 대비하면, 그것은 연마 및 UF/DF 단계가 전달하도록 검증된 약 70배 제거이며 — 검토자가 바로 이 isAbout 결과의 사슬에서 재구성하는 종류의 단계별 감소입니다. (이 포획, 연마, UF/DF 정제 단계와 SEC/CEX/HCP 출하 패널은 제1권에서 가르칩니다.)

두 구역. 왼쪽은 인덱스 지식 그래프(인디고): method(SEC 계획, 정보), assay(날짜와 분석가를 지닌 OBI 프로세스), sample(물질 개체), device(유형이 정해진 기기), 그리고 DS-001 원료의약품 로트가 있고, 스펙 한계 안의 bp 98.611(xsd, 퍼센트)을 담은 초록색 result 노드, 판정 PASS가 함께 있습니다. 보라색 derivedFrom 간선이 sample에서 DS-001로 이어지고 about 간선이 result에서 sample로 이어집니다. 오른쪽은 벤더 중립 파일의 창고(앰버): Allotrope ADF 크로마토그램 큐브(HDF5, 수천 개의 점)와 AnIML 개방형 XML 시리즈입니다. 하나의 보라색 hasChromatogram IRI 포인터가 result에서 ADF 파일로 경계를 가로지릅니다. 창고가 아니라 인덱스: 유형이 정해진 스칼라 결과와 IRI 포인터는 그래프에 살고, 조밀한 크로마토그램은 hasChromatogram으로 도달하는 자신의 Allotrope ADF 파일에 머뭅니다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.

평가: 페이로드가 아니라 인덱스 — 그리고 어디에나 단위

네 스크립트는 함께 구현이 보호해야 할 두 불변항을 입증합니다. 첫째는 인덱스 대 페이로드이며, mAb에서 그것은 저장 최적화가 아니라 정확성 규칙입니다. 98.611 같은 스칼라는 트리플로 깔끔하게 매핑됩니다. 유형이 정해진 하나의 숫자가 바로 그래프가 추론하고 95.0 스펙 하한과 비교할 수 있는 사실이기 때문입니다. 크로마토그램은 그렇지 않습니다: 그것은 수천 개의 강도-대-시간 점이며, 그 모양 — 단량체 봉우리가 응집체 및 단편 봉우리에 대해 어디에 놓이는가 — 이 98.611을 정당화하는 실제 증거입니다. 그 배열을 주어-술어-목적어 트리플로 평평하게 뭉개면 그래프가 폭발하면서도 로트가 문제 될 때 검토자가 다시 살피는 바로 그 모양을 잃게 됩니다. 그래서 어느 로더도 스트림이나 곡선을 결코 내보내지 않습니다. 히스토리안과 OPC UA 다리는 배치/태그당 하나의 bp:hasTrace에 더해 작고 한정된 관측 집합을 내보냅니다 — 그래프는 인덱스를, PI는 점을 보관합니다. 분석 로더는 monomerPct 98.611과 하나의 bp:hasChromatogram IRI를 보관합니다 — 그래프는 출하 숫자를, ADF 파일은 그것을 뒷받침하는 곡선을 보관합니다. 조사관은 어느 결과가 탈락했고 무엇에 관한 것이었는지를 그래프에 질의하고, 원신호를 다시 살펴야 할 때만 IRI를 따라 크로마토그램으로 갑니다: 그래프는 작고 질의 가능하게 유지되고, 증거는 그것을 담도록 만들어진 파일에 머뭅니다 [1][3].

둘째는 CQ-19: 맨숫자 금지, 출하 결정이 의지하는 불변항입니다. 하역장을 건너는 모든 수치는 자신의 단위를 운반합니다. 히스토리안과 OPC UA 관측은 qudt:ucumCode를, 그리고 존재하는 곳에서는 qudt:hasUnit을 운반합니다. B2MML 실현 설정은 UCUM 코드와 QUDT 단위 IRI를 둘 다 지닌 qudt:QuantityValue를 운반합니다. ASM 결과는 monomerPctxsd:float로 유형 지정해 98.61195.0 퍼센트 하한과 비교되는 명백한 퍼센트가 되게 하지, 결코 떠도는 스칼라가 되지 않게 합니다. 정직한 간극 — pH는 차원 없는 로그 양이므로 pH에는 깨끗한 QUDT 단위가 없다는 것 — 은 숨겨지는 게 아니라 보고됩니다. 그것이 단위가 거기 있는 핵심입니다: 어느 측정값이 해소 가능한 단위 IRI까지 끝내 도달했고 어느 것이 코드만 운반하는지를, 배치가 처분되는 순간에 모호함을 발견하는 대신 볼 수 있습니다.

단위 불변항에 대한 두 가지 정직한 범위 주석. 첫째, 하역장은 단위를 가지 모양으로 운반합니다. 센서 관측은 qudt:ucumCode(그리고 해소되는 곳에서는 qudt:hasUnit IRI)를 sosa:Observation에 곧장 매답니다. 반면 B2MML 실현 설정은 완전한 qudt:QuantityValue — 수치값, UCUM 코드, QUDT 단위 IRI, qudt:hasQuantityKind를 하나의 노드에 — 로 구상화합니다. 구상화된 형태가 더 엄격한데, 단위가 추론기가 검사할 수 있는 단일 노드 안에서 크기에 묶이기 때문입니다. 둘째, CQ-19를 시행하는 감사 질의 — CQ-19.rq — 는 오직 qudt:QuantityValue 노드만 겨냥합니다: 그것은 qudt:numericValue는 있으나 qudt:hasUnitqudt:ucumCode도 없는 위반자를 반환하며, 그래프는 올바르게 영을 반환합니다. 출하 스칼라 monomerPct, cexMainPct, hcpPpm은 단위(퍼센트, 퍼센트, ppm)가 형제 QuantityValue가 아니라 속성의 정의에 사는 xsd:float 리터럴이므로, 구성상 단위를 지니지만 그 특정 질의의 밖에 앉아 있습니다. 그래서 이 장의 주장은 정확합니다: 모든 크기는 단위를 지니며, 맨숫자 금지 질의는 단위가 조용히 사라질 수 있는 구상화된 경로를 단속합니다.

잘 먹여진 그래프가 그다음 떠받치는 것: 모델 계층

로더의 규율이 이만큼 야단법석을 떨 가치가 있는 이유는, 그것이 채우는 그래프가 나중에 학습 모델이 딛고 서는 그라운드 트루스이기 때문입니다. 동반 머신러닝 및 AI 책은 결코 맨 부동소수로 학습하지 않습니다. 그것은 정확히 이 하역장이 만들어 내는, 유형이 정해지고 단위를 운반하며 혈통에 닻을 내린 진술로 학습합니다. 공급 편차가 응집체 일탈을 설명할 수 있는지를 묻는 모델은 BATCH-2026-001의 생산 단계에서 실현되고 그 세포 은행까지 거슬러 파생된 0.40 /d의 공급 속도를 추론할 수 있습니다. 그것은 단위와 혈통이 벗겨진 같은 숫자는 파싱조차 할 수 없습니다. 따라서 크로스워크는 출하 게이트의 입력일 뿐 아니라, 침묵하는 프로세스 값을 모델이 학습할 수 있는 특징으로 끌어올리는 AI 준비성 단계입니다. 이 장이 보호하는 세 불변항은 모델링 책이 그렇지 않았다면 어렵게 풀어야 할 세 문제와 일대일로 맞물립니다.

첫째, 그래프는 구성상 누수 없는, 그룹화된 학습 데이터를 공급합니다. 바이오공정 ML에서 가장 해로운 실수는 행 단위 무작위 분할 — 거의 동일한 배치 내 두 스펙트럼을 학습/시험 경계를 가로질러 흩뿌려, 모델이 거의 쌍둥이 사이를 보간하고 환상의 점수를 보고하게 하는 것 — 입니다. 해법은 배치별 분할(GroupKFold, 또는 배치가 귀할 때는 leave-one-batch-out)이며, 그것은 모든 행에 대해 그것이 어느 배치에 속하는지를 아는 것을 요구합니다. 히스토리안 로더가 내보내는 bp:fromBatch 간선 — 그 자체가 rdfs:subPropertyOf bp:derivedFrom — 이 바로 그 그룹 키입니다: 그래프에 대한 질의가 손으로 유지하는 조인 테이블 없이 모델에게 정직한 leave-one-batch-out 분할을 건넵니다. 리콜의 범위를 정하는 동일한 혈통 간선이 폴드의 범위도 정하기 때문입니다. 리콜을 추적하는 그래프와 학습 집합을 분할하는 그래프는 같은 그래프입니다.

둘째, 품질 StatusCode는 학습 데이터 게이트이기도 합니다. 하역장이 SPC 추세에서 제외하는 Bad/Uncertain 관측은 정확히 소프트 센서의 보정 집합에 들어가서는 안 되는 행입니다 — Bad로 표시된 드리프트하는 프로브의 36.51 Cel은 어려운 예제가 아니라 오염된 레이블이며, 그것으로 학습하는 모델은 기기의 결함을 학습합니다. 품질 정수를 모든 sosa:Observation에 함께 실어 두면, 특징 파이프라인이 적합하기도 전에 사용 적합성으로 필터링할 수 있으며, 이것이 모델링 책의 누수 없는 특징 규율의 와이어 수준 유사물입니다.

셋째, 그래프는 유창한 모델이 신뢰되는 대신 검사될 수 있게 하는 것입니다. GraphRAG 시스템은 "BATCH-2026-001은 무엇에서 파생되었는가?"에, 이 하역장이 먹인 유형이 정해진 bp:derivedFrom 간선을 순회한 뒤 그것을 인용함으로써 답합니다 — 그래서 대형 언어 모델이 그럴듯한 혈통을 발명하는 대신 참된 혈통을 서술합니다. 부적합한 출하를 거부하는 동일한 SHACL 출하 게이트는 부적합한 검색도 거부할 수 있습니다: 하위 그래프가 모델에 건네지기 전에, 적합성은 모든 결과가 단위를, 모든 배치가 bp:derivedFrom 부모를, 모든 관측이 품질을 지님을 — 모델이 학습 기억에서 기꺼이 채울 구멍이 아니라 — 인증합니다. 이것이 모델링 책이 반대편에서 명명하는 검증 역설입니다: 끊임없이 학습하는 모델은 검증된 상태로 유지하기 어렵지만, 모든 사실이 유형이 정해지고 단위를 지니며 SHACL에 적합한 추론된 그래프는 모델을 그것에 대조해 측정할 수 있는 고정된 그라운드 트루스입니다. 맨숫자 그래프는 아무것도 떠받치지 못합니다. 이 장이 전시하는 규율이 동반 책이 짓는 모든 모델의 전제 조건입니다.

감사 추적의 얼굴: ALCOA+만이 아니라 Part 11 / Annex 11

품질 정수와 단위를 경계 너머로 운반하는 것은 데이터 무결성의 절반이며, 나머지 절반은 누가 무엇을, 언제, 어디서 기록했는가입니다. 히스토리안 행의 quality 플래그와 B2MML 기록의 <Equipment> 참조는 둘 다 그 자체가 21 CFR Part 11EU GMP Annex 11 아래의 통제된 기록인 시스템에서 비롯됩니다 — 배치가 처분되는 어떤 데이터에 대해서도 완전하고, 귀속 가능하며, 변조가 드러나는 감사 추적을 요구하는 전자기록·전자서명 규칙입니다. 충실한 로더는 그 의무를 벗어나는 게 아니라 물려받습니다: 하역장이 관측을 착지시킬 때, obs/{tag}/{ts} 템플릿은 소스 순간을 보존하여 그래프 사실이 그것이 나온 컨트롤러 읽기와 귀속 가능하고 동시적으로 남게 하며, ADF 파일을 가리키는 rdfs:seeAlso 포인터는 그 숫자를 조용히 옮겨 적는 대신 원본 분석 기록을 온전하게 유지합니다. 그래프는 기록 시스템에 대한 인덱스이지 그것을 대체하는 것이 아닙니다 — 바로 그것이 그래프를 Part 11 방어 가능하게 유지합니다: 히스토리안, MES, LIMS는 서명되고 감사 추적된 소스로 남고, 로더는 규제가 요구하는 보관 사슬을 깨지 않으면서 유형이 정해지고 질의 가능한 계층을 더합니다. 타임스탬프를 떨어뜨리거나, 크로마토그램의 숫자를 복사하면서 그것이 나온 파일을 잃는 크로스워크는 통제된 기록을 통제되지 않은 파생물로 바꿔 버릴 것입니다 — 위의 ALCOA+ 논의가 데이터 품질 측에서 표시하는 것과 동일한 실패 모드인, 터지기를 기다리는 Annex 11 지적 사항입니다.

미해결 부분: 충실한 로더는 생성된 계약이 아니라 손으로 쓴 계약이다

이 네 스크립트는 각각 하나의 크로스워크를 충실히 실행하지만 — 각각은 소스 표준과 타깃 모델을 둘 다 이해한 사람이 작성한 것이며, 그것은 거저 일반화되지 않습니다. historian-map.rml.ttl의 RML 매핑은 선언적이며 — 논리적 소스, 주제 맵 템플릿, 그리고 한 줌의 술어-목적어 맵 — 실제 가상 그래프 엔진(Ontop, RML 프로세서) — 그래프 질의를 즉석에서 히스토리안 자신의 데이터베이스에 대한 SQL로 번역해 답하는 도구로, 그래서 데이터가 별도의 그래프 저장소로 복사되어 나오는 일이 결코 없습니다(그래프는 가상이며, 질의될 때만 계산되고, 추출 단계가 없습니다) — 이 그것을 라이브 SQL 히스토리안 위에서 돌릴 수 있습니다:

# historian-map.rml.ttl — the declarative form of the same crosswalk an RML engine (Ontop) could run over live SQL.
ex:ObservationMap a rr:TriplesMap ;
rml:logicalSource [ rml:source "sensor_reading.csv" ; rml:referenceFormulation ql:CSV ] ;
rr:subjectMap [ rr:template "https://example.org/historian/obs/{tag}/{ts}" ; rr:class sosa:Observation ] ;
rr:predicateObjectMap [ rr:predicate sosa:hasSimpleResult ; rr:objectMap [ rml:reference "value" ; rr:datatype xsd:float ] ] ;
rr:predicateObjectMap [ rr:predicate qudt:ucumCode ; rr:objectMap [ rml:reference "unit" ] ] .

Python 로더는 프로세스 안에서 정확히 이 맵을 실행합니다. RML을 보관하는 의의는 동일한 선언을 가상 그래프 엔진에 넘겨 추출 단계 없이 히스토리안의 SQL 위에서 곧장 돌릴 수 있다는 것 — 그래서 히스토리안 크로스워크가 넷 중 유일하게 진정으로 기계 실행 가능한 이유입니다. 여기서조차 두 결정은 순수한 RML을 넘어서 함수 확장(RML-FNML, 매핑이 단지 필드를 복사하는 대신 작은 함수를 호출하게 하는 RML의 부분)을 통해서만 표현됩니다: OPC UA 읽기가 동일한 obs/{tag}/{ts} 주제에 합류하게 하는 NodeId-대-태그 분할, 그리고 코드를 해소 가능한 단위 IRI로 승격하는 Celunit:DEG_C UCUM-대-QUDT 조회입니다. OPC UA NodeId-대-태그 규칙, B2MML의 "장비는 두 번째 유형이 아니라 occursIn이 된다" 규칙, ASM @context는 모두 모델러가 내린 결정이지 기계가 도출한 사실이 아니었습니다. CQ-21이 금지하는 혼동 — 배치가 그 용기로 유형이 정해지는 것 — 은 자동 생성된 매핑이 저지르는 바로 그 실수입니다. B2MML XML이 정말로 <Equipment>BR-101</Equipment> 참조를 배치 요소 안에 넣기 때문입니다. 로더가 그것을 곧이곧대로 받아 BATCH-2026-001을 생물반응기로 찍으면, 물질과 용기가 하나의 노드로 융합되고, 런-occursIn-용기로 흘러야 할 계보 간선이 무너지며, 리콜이 세포 은행까지 거슬러 걷는 혈통이 가장 데이터가 풍부한 단계에서 끊깁니다. 따라서 충실한 로더는 생성된 계약이 아니라 손으로 쓴 계약입니다.

스크립트별 노력 아래에는 더 깊은 간극이 있습니다: 분석 도메인 하나만 해도 목표는 공유하지만 매끄러운 접합은 공유하지 않는 세 개의 큰 온톨로지 노력의 만남점에 앉아 있습니다. OBI는 조사를, AFO는 분석 결과를, IOF(Industrial Ontologies Foundry) 바이오제약 모듈은 시료가 나온 제조 프로세스를 모델링합니다 — 그리고 원리상 모두 조화 가능하지만, 그것들을 융합하는 단일한 턴키 매핑은 없습니다. AFO로 모델링된 결과와 IOF로 모델링된 배치는 팀이 작성하는 크로스워크를 통해서만 만납니다. OBI와 AFO는 둘 다 BFO와 그 위에 세워진 OBO(Open Biological and Biomedical Ontologies) 계열에 기반하므로, 여기서 다리 놓이지 않은 이음매는 OBO/AFO ↔ IOF이며 — AFO는 제삼의 독립된 뿌리가 아니라 OBO 쪽의 두 번째 OBO 계열 당사자입니다 — 발견 장들을 관통하는 동일한 이음매가 이제 분석 어휘까지 아우르게 된 것입니다. B2MML이나 Allotrope를 IOF에 기반한 트리플로 매핑하는, 발행된 턴키 어댑터는 없습니다. 모든 공장이 자신의 것을 작성하고 유지하며, 소스 스키마가 진화함에 따라 그 어댑터를 올바르게 유지하는 비용은, 타깃 측의 어떤 온톨로지 엄밀함으로도 갚아지지 않는 실재하고 반복되는 세금입니다.

도입 장벽은 분석 결과에서 가장 날카로우며, 그것은 표준 성숙도의 비용이 아니라 규율의 비용입니다. AFO는 크고 진정으로 복잡합니다. 완전한 준수 — 모든 결과를 완전한 주석과 함께 준수하는 ADF나 ASM로 내보내는 것 — 는 무거운 일이며, 2026년 현재 대부분의 실험실은 그것을 하지 않았습니다. 한 공장은 수천 개의 결과를 벤더 메타데이터가 붙은 맨숫자로 보관하면서 어느 것도 실제로 AFO 의미를 운반하지 않는데도 표준화되었다고 부를 수 있으며, 그래서 실제로서의 FAIR가 주장으로서의 FAIR에 뒤처집니다(FAIR = Findable, Accessible, Interoperable, Reusable — 이 표준들이 봉사하는 데이터 품질 목표). 도구는 존재하고 표준은 실재합니다. 이 장이 전시하는 아키텍처(스칼라는 인덱스, 배열은 참조, 모든 것에 유형과 단위)는 명확하고 올바릅니다. 여전히 고르지 못한 것은 모든 로더가 그것을 지키게 하는 일상의 규율입니다. 구현은 모델이 도해이기를 멈추고 유지보수 부담이 되기 시작하는 곳입니다 — 정직한 부담이지만, 부담입니다.

왜 중요한가

그래프는 그것을 채우는 로더만큼만 신뢰할 수 있으며, mAb 공장에서 로더가 가장 보호해야 할 두 가지는 출하 게이트와 계보입니다. 출하 게이트는 원료의약품 로트를 처분하는 결정입니다: 검토자는 모든 CQA 결과 — 95.0을 넘는 단량체 순도, 스펙 안의 전하 변이체 분포, 한계 미만의 HCP 불순물 — 가 진정으로 그 로트에서 파생된 시료에서 통과하는지를 확인합니다. 하역장이 단위를 떨어뜨리면 그 비교는 안전하게 해석할 수 없는 숫자와 대조해 검증되고, 통과 또는 탈락 판정은 추측이 됩니다. 계보는 바이알에 담긴 의약품 완제품에서 원료의약품 로트, 수확, 생산 배치, 시드 트레인을 거쳐 워킹 세포 은행까지 이어지는 derivedFrom 사슬 — 리콜이 어느 로트가 기원을 공유하는지 범위를 정하려 걷는 사슬 — 입니다. 그 걷기는 저장된 경로가 아니라 추론기 추론입니다: bp:derivedFromowl:TransitiveProperty이고, 로더가 만드는 시료-대-로트 홉(bp:SMP-DS-001 bp:derivedFrom bp:DS-001)과 히스토리안의 bp:fromBatch(그 자체가 rdfs:subPropertyOf bp:derivedFrom)는 OWL-RL 폐포가 출하 시료에서 워킹 세포 은행까지의 사슬 전체를 함의하기에 충분하며, 이것이 바로 CQ-22가 돌리는 구조적 검사입니다. 그래서 하역장이 결과를 시료에 bp:isAbout으로, 시료를 로트에 derivedFrom으로 착지시킬 때, 그것은 단지 간선을 기록하는 것이 아니라, 누구도 혈통을 손으로 코딩하지 않고도 리콜 질의가 임의의 깊이에 도달하게 하는 전이성을 가진 그 하나의 속성을 먹이는 것입니다. 하역장이 배치를 그 용기와 혼동하면 런-대-용기 간선이 무너지고 그 걷기는 가장 데이터가 풍부한 고리를 잃습니다. 하역장이 크로마토그램을 평평하게 뭉개면 그래프가 쓸 수 없을 때까지 부풉니다. 결과를 맨 덩어리로 남기면 출하 증거는 질의 불가능해지고 그래프는 쓸모없어집니다. 여기 네 크로스워크는 대안을 보여 줍니다: 공장이 이미 구사하는 각 실제 표준이 단위와 함께, 페이로드를 가리키는 인덱스 포인터 뒤에 동일한 유형의 노드로 착지합니다 — 그래서 계보, 추적, 출하 증거가 모두 그래프가 스트림을 삼키는 일 없이 질의 가능합니다. 이곳이 상류 장들의 값싼 규율이 보상을 치르거나 조용히 버려지는 곳입니다.

실제 현장에서는

히스토리안, OPC UA, B2MML, Allotrope는 실제 바이오제약 공장이 오늘날 돌리는 표준이며 — 성숙도는 고르지 않은데, 그것이 핵심입니다. PI 히스토리안과 OPC UA는 프로세스 데이터의 생산 기본값입니다. B2MML은 MES 벤더(Körber PAS-X, Siemens Opcenter, Rockwell FactoryTalk Batch)가 레시피와 배치 기록을 교환하는 방식입니다. Allotrope의 ADF와 그 경량 형제 ASM는 분석 결과에 벤더 중립적 의미를 부여하고 실제 실험실에 배치되어 있는 한편, SiLA 2와 OPC UA LADS 장비 통합은 시범 도입되었으나 아직 기본은 아닙니다 [1][2][3]. 기록 시스템을 똑바로 구분해 두면 도움이 됩니다: 히스토리안과 OPC UA는 배치에 키가 걸린 고빈도 CPP/PAT 스트림 — 온도, pH, DO, 교반, 공급 — 을 소유하고, MES는 실행된 레시피, 단계 전환, 물질 계보를 B2MML 전자 배치 기록으로 교환하며 소유하고, LIMS(Laboratory Information Management System)는 방법·분석가·장비를 갖춘 개별 QC 결과를 Allotrope로 내보내며 소유합니다. 하역장의 일은 각각에서 하나의 데이텀을 기록 시스템을 옮기지 않고 동일한 그래프로 착지시키는 것입니다 — 그래프는 인덱싱하고, 소스 시스템은 여전히 보유합니다. 동반 오픈소스 책은 동일한 와이어-에서-그래프 패턴을 돌아가는 스택에서 보여 주고, 제8부의 실제로 생산에 있는 것에 대한 계층별 조사는 공장이 이 표준 중 어느 것을 진정으로 돌리는지 대 시범하는지를 짚습니다. 여전히 최전선으로 남은 것은 모든 로더가 기본적으로 단위를 운반하고 인덱스 경계를 존중하게 만드는 일, 그리고 모든 접합에 손으로 만든 어댑터 없이 OBI, AFO, IOF를 하나의 그래프로 꿰매는 일입니다.

핵심 용어

  • 트리플 / 그래프(Triple / graph) — 트리플은 하나의 주어–술어–목적어 사실(예: DS-001 → derivedFrom → POL-001)이고, 그래프(지식 그래프)는 그러한 모든 사실이 연결된 것으로, 이 책의 나머지가 질의하는 대상입니다.
  • IRI / 접두사(IRI / prefix) — IRI는 RDF가 모든 노드에 부여하는, 시스템을 가로질러 동일한 전역 고유 웹 이름이고, 접두사(bp:, qudt:, obo:, sosa:, af-r:)는 한 어휘의 웹 주소를 가리키는 약칭이므로 bp:Batch는 전체 IRI의 짧은 이름입니다.
  • BFO / OBO / IOF — BFO(Basic Formal Ontology)는 모든 용어가 그 아래로 분류되는 최상위 종류 집합이고, OBO는 그 위에 세워진 온톨로지 계열(OBI, AFO)이며, IOF(Industrial Ontologies Foundry)는 이 책이 정렬하는 제조 측 계열입니다.
  • 역량 질문(Competency question, CQ) — 온톨로지가 답해야 하는, 번호가 매겨지고 합격/불합격 인수 시험으로 돌아가는 평이한 언어의 요건(예: CQ-19, 맨숫자 금지).
  • 크로스워크(Crosswalk) — 한 표준의 구조를 타깃 온톨로지의 트리플로 매핑하는 것. 여기서는 네 개(히스토리안, OPC UA, B2MML, ASM)이며, 각각 단언이 아니라 실행되는 스크립트로 전시됩니다.
  • SOSA 관측(SOSA observation) — 센서 측정값이 되는 W3C Sensor/Observation/Sample/Actuator 노드. 관측된 속성, 단순 결과, 결과 시각, 단위를 운반합니다.
  • UCUM 코드 / QUDT 단위(UCUM code / QUDT unit) — 측정값과 함께 운반되는 단위: 맨 UCUM 코드(예: Cel, pH)와, 존재하는 곳에서는 해소 가능한 QUDT 단위 IRI(unit:DEG_C) — CQ-19의 "맨숫자 금지"를 구체화한 것입니다.
  • B2MML — MES 도구가 레시피와 배치 기록을 교환하는 데 쓰는 ISA-88/ISA-95의 MESA XML 직렬화. <Equipment>가 배치 위의 두 번째 유형이 아니라 런 위의 occursIn이 되도록 매핑됩니다.
  • ASM(Allotrope Simple Model)@context가 Turtle과 동일한 IRI로 해소되는 Allotrope의 경량 JSON-LD 결과 포맷. 그래서 가벼운 파일이 완전한 ADF와 동일한 bp:SEC-Result-001을 산출합니다.
  • 인덱스 대 페이로드(Index versus payload) — 로더가 보호하는 경계: 그래프는 숫자와 hasTrace / hasChromatogram 포인터를 보관하고, 조밀한 스트림과 곡선은 PI나 ADF/AnIML 파일에 머뭅니다.
  • 방법 / 분석법 실행 / 결과(Method / assay / result) — 각각 계획, occurrent, 정보 산물. 융합된 하나의 "시험"이 아니라 세 노드이므로, 하나의 검증된 방법이 여러 분석법 실행으로 돌아 여러 결과를 낳을 수 있고 완전한 출하 출처가 따라옵니다.
  • 시료(Sample) — 로트에서 인출된 검체. 배치로부터 derivedFrom으로 모델링되므로, 시료에 관한 결과는 전이적으로 배치에 관한 증거가 됩니다 — 분석 숫자를 계보에 다시 잇는 다리입니다.
  • 출하 패널(Release panel) — 원료의약품 로트를 게이트하는 하나의 검체에 대한 CQA 결과의 집합(여기서는 SEC 단량체, CEX 전하 변이체, HCP 불순물). 각각이 동일한 시료를 isAbout하므로 게이트는 사무적 대조가 아니라 순회가 됩니다.
  • 그라운드 트루스(Ground truth, ML 용) — 학습 모델이 닻을 내리는, 검증되고 유형이 정해지며 단위를 운반하는 그래프. 리콜을 추적하는 동일한 적재된 그래프가 누수 없는 배치별 학습 데이터도 공급하고 GraphRAG 답변도 떠받칩니다.
  • 배치별 분할(Batch-grouped split) — 한 배치의 모든 행을 통째로 학습 또는 시험에 넣어(GroupKFold, leave-one-batch-out) 보류된 배치가 진정으로 미관측이 되게 하는 것. 히스토리안 로더가 내보내는 bp:fromBatch 간선이 그 그룹 키입니다.
  • Part 11 / Annex 11 — 21 CFR Part 11과 EU GMP Annex 11, 전자기록·전자서명 규칙. 그래프는 서명된 히스토리안/MES/LIMS 기록에 대한 귀속 가능하고 동시적인 인덱스로 남으며, 감사 추적을 깨는 대체물이 결코 되지 않습니다.

다음 이야기

그래프는 먹여졌습니다: 네 개의 공장 표준이 동일하게 유형이 정해지고 단위를 운반하며 인덱스를 존중하는 노드로 착지합니다. 이제 우리는 그래프가 무엇을 답할 수 있는지 묻습니다. 다음 장, 질의로서의 역량 질문은 요구사항 카탈로그를 이 적재된 그래프에 대해 돌아가는 SPARQL과 추론기 검사로 바꿉니다 — 뿌리까지의 혈통, 캠페인을 가로지르는 영향, 품질 속성의 궤적 — 제1~5부에 걸쳐 설계된 모델이 그것이 만들어진 목적인 질문들에 실제로 답함을 증명합니다.