본문으로 건너뛰기

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

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

손으로 친 Turtle만 본 모델은 공장을 한 번도 만나 본 적이 없는 모델입니다. 이 책이 추론하는 데이터셋은 Turtle로 도착하지 않습니다. 그것은 초당 두 번씩 흐르는 히스토리안 태그로, 컨트롤러에서 읽힌 OPC UA 변수로, MES가 XML로 교환하는 ISA-88 레시피로, HPLC가 내보낸 크로마토그램으로 도착합니다. 구현이란 그 각각을 동일한 그래프 — bp:BATCH-2026-001, bp:CCP-001, bp:SEC-Result-001 — 로 바꾸되, 새로운 동일성을 발명하지 않고 백만 개의 센서 점을 트리플로 평평하게 뭉개지 않는 행위입니다. 동반 데이터셋의 네 개의 작은 스크립트는 각각 하나의 실제 공장 표준을 받아 이미 instances.ttl에 살고 있는 bp: 트리플을 내보내므로, 크로스워크는 약속되는 것이 아니라 전시됩니다.

쉽게 말하면

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

질문에서 출발하라

이 장은 무엇보다 하나의 역량 질문을 떠받칩니다: CQ-19저장된 모든 양은 단위를 지니는가, 맨숫자는 하나도 없는가? (명세에 정의됨). mAb 캠페인에서 그 요건은 장부 정리의 까다로움이 아닙니다. 원료의약품 로트는 단량체 순도를 95.0 스펙 하한과, 배양의 온도를 36.0–37.0 °C 범위와 비교해 출하되며, 비교는 양쪽이 같은 단위를 지닐 때만 의미가 있습니다. 와이어는 바로 그 단위가 떨어져 나가는 곳입니다 — 히스토리안 컬럼이 부동소수로 읽히고 그 옆의 Cel은 버려지며, 그래프는 출하 게이트가 안전하게 해석할 수 없는 스펙과 비교하게 될 벌거벗은 36.51들로 채워집니다. 맨숫자 2.41은 출하에서 탈락하는 고분자량 응집체의 퍼센트일 수도, 무해한 개수일 수도 있습니다. 단위가 없으면 배치 처분 결정은 모래 위에 세워집니다. 아래의 모든 스크립트는 일부러 단위를 경계 너머로 운반합니다. 이 장은 또한 구조적 질문 CQ-21(런이 어느 용기에서 일어났는가 — 장비를 배치 물질과 분리해 유지)과 계보의 추적 절반에도 봉사하는데, B2MML과 히스토리안 다리야말로 장비와 조밀한 스트림이 리콜을 추적 가능하게 만드는 동일성을 망치지 않고 런에 붙는 곳이기 때문입니다.

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

mAb 배양을 돌리는 생산 생물반응기는 몇 초마다 일군의 프로브 — 온도, pH, 용존 산소 등 — 로 모니터링됩니다. 세포가 살아 있고 제어 전략이 각 파라미터를 2주 동안 좁은 범위 안에 붙들어 두기 때문입니다. 그 PAT 데이터의 격류는 공장에서 가장 조밀한 스트림이며, 첫 크로스워크에는 같은 곳에 도착해야 하는 두 개의 정문이 있습니다. 측정값은 PI 히스토리안 행 — ts, tag, value, unit, quality, batch_id — 으로 그래프에 도달할 수도, 한 홉 앞에서 생물반응기 컨트롤러에서 곧장 OPC UA 데이터 액세스 변수로 읽힐 수도 있습니다. 모델의 규율은 두 경로가 동일한 sosa:Observation을 발행한다는 것입니다. OPC UA NodeId의 문자열 식별자가 바로 히스토리안 태그이기 때문입니다(ns=2;s=BR101.Temp.PVBR101.Temp.PV). 매핑은 RML로 선언되고 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

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

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

이 책이 추론하는 계보는, 실제 현장에서는 RDF derivedFrom 간선으로 살지 않습니다 — 그것은 독점 MES 안의 한 행으로 살며, B2MML XML(ISA-88/ISA-95의 MESA 직렬화)로 교환됩니다. 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 범주이며, Material owl:disjointWith Equipment는 그것들을 융합하는 일을 표시된 오류로 만듭니다. 순진한 로더는 <Equipment>BR-101</Equipment>를 읽고 배치를 생물반응기로 찍어, 물질을 그것이 만들어진 용기와 뒤섞어 버립니다 — 바로 disjointness 가드가 잡아내려 존재하는, 심어진 혼동이자, 방치하면 어떤 배치가 주어진 용기에서 돌았는지 묻거나 리콜을 런을 거슬러 추적하는 일을 불가능하게 만드는 오류입니다. 충실한 로더는 BATCH-2026-001bp:Batch로 유지하고, 오직 런 bp:CCP-001만이 bp:BR-101에서 occursIn하도록 합니다. 세포 배양 런도 균일하지 않습니다 — 그것은 세포가 증식하는 성장 단계와 항체를 만드는 생산 단계를 거치며, 각 단계는 고유한 파라미터 범위를 가집니다 — 그래서 단계별 실측값은 그것이 다스리는 단계에 붙은 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) — 검증된 크기 배제 절차 — 은 하나의 계획이며, 복사 가능한 정보이고, 오늘 돌리든 다음 캠페인에 돌리든 동일한 SEC 방법입니다. 분석법 실행(assay)(bp:SEC-Assay-001)은 occurrent입니다: 명명된 분석가가 날짜가 찍힌 장비에서 특정 시료에 그 방법을 실제로 돌린 것입니다. 그리고 결과(bp:SEC-Result-001)는 정보 산물이며, 시료에 관한 유형이 정해진 사실입니다. 이 셋을 떼어 두면 그래프는 하나의 검증된 방법이 여러 분석법 실행으로 돌아 여러 결과를 낳았음을, 두 출하 결과가 같은 방법에서 나왔음을, 또는 두 런 사이에 방법이 개정되었음을 말할 수 있습니다 — 융합된 하나의 "시험" 노드로는 어느 것도 표현할 수 없고, 로트가 문제 될 때 품질 조사관에게는 모두 필요한 것들입니다. 그것들을 융합하면 출하 추적은 출처 없는 하나의 숫자로 무너집니다.

시료는 네 번째 노드이며, 실험실에서 프로세스로 돌아가는 다리입니다. 시료는 그것이 인출된 배치로부터 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는 바로 그 문제에 대한 답입니다: 그것은 어느 벤더가 만들었든 분석 결과에 하나의 표준화된 의미를 부여하므로, 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 경로에서 나옵니다. OBI는 조사 틀(계획된 프로세스로서의 분석법 실행)을, Allotrope(AFO)는 분석적 의미(크기 배제 크로마토그램으로서의 결과)를 제공하며 [1][2], 정렬 파일은 bp:SECResult를 양쪽 모두로 유형 지정합니다.

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

# 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가 진정으로 이 배치에서 파생된 시료에서 통과했는가?"라는 질문은 사무적 대조가 아니라 순회가 됩니다.

인덱스 구역과 창고 구역을 가진 하역장 도해: 왼쪽에서는 네 개의 인입 차선이 하나의 지식 그래프로 수렴합니다 — PI 히스토리안 행과 OPC UA 변수 읽기가 둘 다 UCUM 코드 Cel과 QUDT 단위 DEG_C를 지닌 값 36.51을 운반하는 동일한 SOSA 관측으로 착지하고, B2MML 마스터 레시피와 실행 배치 기록이 용기 BR-101에서 occursIn하는 런 CCP-001로, 그리고 물질로 한 번만 유형이 정해진 배치 BATCH-2026-001로 착지하며, Allotrope ASM JSON-LD 결과가 스펙과 PASS 판정과 함께 monomerPct 98.611을 담은 SEC-Result-001 노드로 착지합니다. 오른쪽에서는 창고 구역이 그래프가 가리키기만 하는 조밀한 페이로드 — PI 점 스트림과 Allotrope ADF 크로마토그램 큐브 — 를 보관하며, 하나의 hasChromatogram IRI 포인터와 하나의 hasTrace 태그 간선이 경계를 가로지릅니다. 캡션은 스칼라와 포인터는 그래프에 인덱싱되고 배열은 파일에 머문다고 적습니다. 하역장: 네 개의 공장 표준 — 히스토리안, OPC UA, B2MML, Allotrope ASM — 이 각각 동일한 유형의 노드로서 하나의 그래프에 건너오며, 모든 양이 자신의 단위를 운반하는 한편, 조밀한 스트림과 크로마토그램은 hasTracehasChromatogram 포인터 뒤 자신의 파일에 머뭅니다. 저자가 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까지 끝내 도달했고 어느 것이 코드만 운반하는지를, 배치가 처분되는 순간에 모호함을 발견하는 대신 볼 수 있습니다.

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

이 네 스크립트는 각각 하나의 크로스워크를 충실히 실행하지만 — 각각은 소스 표준과 타깃 모델을 둘 다 이해한 사람이 작성한 것이며, 그것은 거저 일반화되지 않습니다. historian-map.rml.ttl의 RML 매핑은 선언적이며 실제 가상 그래프 엔진(Ontop, RML 프로세서)이 그것을 라이브 SQL 히스토리안 위에서 돌릴 수 있습니다. 그러나 OPC UA NodeId-대-태그 규칙, B2MML의 "장비는 두 번째 유형이 아니라 occursIn이 된다" 규칙, ASM @context는 모두 모델러가 내린 결정이지 기계가 도출한 사실이 아니었습니다. CQ-21이 금지하는 혼동 — 배치가 그 용기로 유형이 정해지는 것 — 은 자동 생성된 매핑이 저지르는 바로 그 실수입니다. B2MML XML이 정말로 <Equipment>BR-101</Equipment> 참조를 배치 요소 안에 넣기 때문입니다. 로더가 그것을 곧이곧대로 받아 BATCH-2026-001을 생물반응기로 찍으면, 물질과 용기가 하나의 노드로 융합되고, 런-occursIn-용기로 흘러야 할 계보 간선이 무너지며, 리콜이 세포 은행까지 거슬러 걷는 혈통이 가장 데이터가 풍부한 단계에서 끊깁니다. 따라서 충실한 로더는 생성된 계약이 아니라 손으로 쓴 계약입니다.

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

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

왜 중요한가

그래프는 그것을 채우는 로더만큼만 신뢰할 수 있으며, mAb 공장에서 로더가 가장 보호해야 할 두 가지는 출하 게이트와 계보입니다. 출하 게이트는 원료의약품 로트를 처분하는 결정입니다: 검토자는 모든 CQA 결과 — 95.0을 넘는 단량체 순도, 스펙 안의 전하 변이체 분포, 한계 미만의 HCP 불순물 — 가 진정으로 그 로트에서 파생된 시료에서 통과하는지를 확인합니다. 하역장이 단위를 떨어뜨리면 그 비교는 안전하게 해석할 수 없는 숫자와 대조해 검증되고, 통과 또는 탈락 판정은 추측이 됩니다. 계보는 바이알에 담긴 의약품 완제품에서 원료의약품 로트, 수확, 생산 배치, 시드 트레인을 거쳐 워킹 세포 은행까지 이어지는 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]. 동반 오픈소스 책은 동일한 와이어-에서-그래프 패턴을 돌아가는 스택에서 보여 주고, 제8부의 실제로 생산에 있는 것에 대한 계층별 조사는 공장이 이 표준 중 어느 것을 진정으로 돌리는지 대 시범하는지를 짚습니다. 여전히 최전선으로 남은 것은 모든 로더가 기본적으로 단위를 운반하고 인덱스 경계를 존중하게 만드는 일, 그리고 모든 접합에 손으로 만든 어댑터 없이 OBI, AFO, IOF를 하나의 그래프로 꿰매는 일입니다.

핵심 용어

  • 크로스워크(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하므로 게이트는 사무적 대조가 아니라 순회가 됩니다.

다음 이야기

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