분석 실험실: 기기, LIMS, ELN
📍 현재 위치: 2부 공정을 포착하기. 우리는 생산 현장이 만들어내는 모든 것을 포착했습니다 — 모든 인라인 태그, 모든 크로마토그래피 단계, 모든 풀링(pooling) 결정. 이제 우리는 현장을 떠나 QC 실험실로 향합니다. 분자의 품질이 마침내 판정되는 곳, 그리고 배치가 출하될지 거부될지를 결정하는 데이터를 포착하는 법을 배우는 곳입니다.
바이오리액터와 스키드(skid)는 주방과 같습니다. 오븐이 180 °C였고 타이머가 40분 동안 돌았다고 알려주죠. 분석 실험실은 음식 평론가입니다. 오븐이 무엇을 말했는지에는 관심이 없습니다 — 케이크를 맛보고 평결을 적습니다. 그 평결(순수한가? 올바른 항체인가? 주사해도 안전한가?)은 공장 전체에서 가장 높은 위험을 지닌 데이터입니다. 의약품을 출하할 수 있게 해주는 데이터이기 때문입니다. 그래서 실험실의 일, 그리고 이 장의 일은 각 평결을 단 하나의 질문에 대한 철벽 같은 답과 함께 포착하는 것입니다. 누가, 어떤 기기로, 어떤 규격에 대비하여 이것을 측정했고, 사후에 아무도 몰래 그 값을 바꾸지 않았음을 증명할 수 있는가?
이 장에서 다루는 내용
생산 현장은 공정을 측정하고, QC 실험실은 제품을 측정합니다. Protein A 포획 이후, 원료의약품(drug substance) 시료가 실험실로 보내지고, 그곳에서 한 무리의 기기가 출하 질문에 답합니다. 얼마나 순수한가(크기 배제 HPLC, size-exclusion HPLC), 전하가 얼마나 올바른가(양이온 교환 HPLC, cation-exchange HPLC), 숙주세포 단백질(host-cell protein)과 DNA가 얼마나 오염시키는가, 엔도톡신(endotoxin)이 얼마나 있는가. 이 결과들에 더해, 운전 내내 채취하는 일일 앳라인(at-line) 시료가 곧 분석성적서(certificate of analysis)이며 — 검사관이 가장 먼저 읽는 문서입니다.
이 장은 그 데이터를 오픈 소스로 포착하는 법을 보여줍니다.
- 시뮬레이터가 만들어내는 결정론적 오프라인 / 앳라인 분석(offline / at-line assay) 과 HPLC 출하 패널(release panel), 그리고 그것들을 담는
lab.sample/lab.test/lab.result모델. - 기기에서 데이터를 빼내기: OPC UA LADS 디바이스 서버, SiLA 2, 그리고 벤더 중립 분석 포맷인 AnIML 과 Allotrope ASM/AFO.
- 시료 접수(sample login), 워크시트(worksheet), 검증된 결과를 위한 오픈 소스 LIMS(SENAITE), 그리고 암호학적 전자서명과 함께 방법(method) 및 실험의 출처(provenance)를 기록하는 ELN(eLabFTW).
- 검증된 결과를 배치 기록(batch record)으로 다시 끌어오기 — 그리고 이 도구들 중 어느 것도 기본 상태로는 규정을 준수하지 못하는 Part 11 격차에 대해 가차 없이 정직하기.
아래의 모든 숫자는 SIM_SEED=2026으로 바이트 단위까지 똑같이 재생성할 수 있는 파일에서 나옵니다.
두 종류의 실험실 데이터: 앳라인과 출하
실험실은 서로 뚜렷이 구별되는 두 개의 스트림을 만들어내며, 그것들은 리듬이 다릅니다.
첫째는 앳라인 / 오프라인 공정 모니터링(at-line / offline process monitoring) 입니다. 하루에 두 번, 작업자가 바이오리액터에서 몇 밀리리터를 뽑아 세포 계수기(cell counter), 대사물 분석기(metabolite analyzer), 삼투압계(osmometer)에 통과시킵니다. 이것들은 배양물이 지금 어떻게 지내고 있는지 — 생존 세포 밀도(viable cell density), 생존율, 포도당, 젖산, 암모니아 — 를 알려줍니다. 이들은 인라인 태그의 오프라인 쌍둥이이며, 8장의 일 전부가 바로 이 둘을 대조하여 조정하는 것이었습니다. 동반 저장소(repo)는 인라인 추적선(trace)이 비롯되는 바로 그 동역학 상태로부터 이것들을 생성하므로, 벤치 숫자가 온라인 곡선과 일치합니다 — 다만 잡음이 더 많고 더 듬성듬성할 뿐입니다.
examples/sim/bioproc_sim/offline_assays.py를 보면, 샘플링 주기(cadence)와 측정 모델이 명시적입니다.
# examples/sim/bioproc_sim/offline_assays.py
def sample(result: BatchResult | None = None, batch_id: str = "BATCH-2026-001") -> pd.DataFrame:
"""Two offline samples per day from the fed-batch state, with assay noise + LoD."""
if result is None:
result = simulate(batch_id)
s = result.state
rng = stream_rng("offline_assays", result.batch_id)
minutes = []
day = 0.0
while day <= 14.0 + 1e-9:
for frac in (0.25, 0.75): # ~06:00 and ~18:00
m = int(round((day + frac) * 1440))
if m < len(s):
minutes.append(m)
day += 1.0
14일짜리 유가식(fed-batch) 동안 하루 두 번(대략 06:00과 18:00)이면 28개의 공정 중(in-process) 시료가 나옵니다. 각 값은 참된 동역학 상태에 분석별 작은 잡음 항을 더한 것입니다 — VCD 판독값은 Xv × (1 + N(0, 0.05))로, 생존율은 state + N(0, 1.2)로 뽑힙니다 — 이것이 바로 벤치 기기가 센서와 다른 방식입니다. 같은 진실에, 그 위에 약간의 측정 산포(scatter)가 얹힌 것이죠.
python -m bioproc_sim.offline_assays를 실행하면 datasets/offline_assays.csv의 처음 커밋된 행들이 이렇게 보입니다 — 넓고 정돈된 표로, 시료당 한 행입니다.
sample_id,batch_id,sample_time,sample_point,VCD_e6_per_mL,viability_pct,glucose_g_L,lactate_g_L,glutamine_mM,ammonia_mM,osmolality_mOsm_kg,titer_g_L,pH_offline
BATCH-2026-001-OFF-001,BATCH-2026-001,2026-01-05 06:00:00+00:00,BR101,0.34,96.6,6.18,0.13,4.13,0.68,293,0.002,7.06
BATCH-2026-001-OFF-002,BATCH-2026-001,2026-01-05 18:00:00+00:00,BR101,0.43,96.6,6.26,0.19,4.31,0.38,292,0.008,7.04
BATCH-2026-001-OFF-003,BATCH-2026-001,2026-01-06 06:00:00+00:00,BR101,0.56,99.0,6.01,0.32,3.83,0.45,287,0.014,7.05
둘째 스트림은 출하 시험(release testing) 입니다. 일단 원료의약품이 존재하면, QC 실험실은 그것이 출하될 수 있는지를 결정하는 패널을 돌립니다. 이것이 고위험 데이터입니다. 같은 모듈에서, 출하 규격(release spec)은 (name, low, high, unit, target, sd)의 표로 코딩되어 있습니다.
# examples/sim/bioproc_sim/offline_assays.py
_RELEASE_SPECS = [
("SEC_monomer_pct", 95.0, 100.0, "%", 98.5, 0.4),
("SEC_HMW_pct", 0.0, 3.0, "%", 1.1, 0.3),
("CEX_main_pct", 60.0, 80.0, "%", 70.0, 2.0),
("HCP_ng_per_mg", 0.0, 100.0, "ng/mg", 22.0, 8.0),
("residual_ProteinA_ng_per_mg", 0.0, 20.0, "ng/mg", 4.0, 1.5),
("host_cell_DNA_ng_per_dose", 0.0, 10.0, "ng/dose", 1.2, 0.5),
("endotoxin_EU_per_mL", 0.0, 5.0, "EU/mL", 0.3, 0.15),
# ... bioburden, SEC_LMW, CEX_acidic, CEX_basic
]
각 시험은 목표값 주변에서 값을 뽑고, 한계(limit)에 대비하여 PASS 또는 OOS(규격 이탈, out of specification)를 표시합니다.
# examples/sim/bioproc_sim/offline_assays.py
val = target + (rng.normal(0, sd) if sd > 0 else 0.0)
val = float(np.clip(val, low, high))
rows.append({
"batch_id": bid, "test": name, "value": round(val, 3), "unit": unit,
"spec_low": low, "spec_high": high,
"result": "PASS" if low <= val <= high else "OOS",
})
배치당 11개 시험, 골든 캠페인(golden campaign)의 6개 배치 — 도합 66행입니다. datasets/hplc_results.csv를 보면 시뮬레이터가 의도적인 실패를 정확히 하나 심어두었습니다 — 이 트릴로지의 나머지 거버넌스 장치가 잡아내려고 존재하는 바로 그런 종류의 것입니다.
batch_id,test,value,unit,spec_low,spec_high,result
BATCH-2026-001,SEC_monomer_pct,98.611,%,95.0,100.0,PASS
BATCH-2026-001,HCP_ng_per_mg,28.203,ng/mg,0.0,100.0,PASS
...
BATCH-2026-004,HCP_ng_per_mg,128.0,ng/mg,0.0,100.0,OOS
BATCH-2026-004는 100 ng/mg 한계에 대비하여 128 ng/mg의 숙주세포 단백질 결과 를 가집니다 — 그 배치를 동결시키고 조사를 개시해야 마땅한 단 하나의 숫자입니다. 우리가 이 숫자가 어떻게 저장되는지에 그토록 신경 쓰는 이유는, 그것이 OOS일 때 변조 증거가 남아야(tamper-evident) 하고, 귀속 가능해야(attributable) 하며, 몰래 "고치는" 것이 불가능해야 하기 때문입니다. FDA의 데이터 무결성 지침은 QC 출하 데이터가 가장 강력한 감사 추적(audit-trail) 및 품질 부서 검토 기대치를 짊어진다고 명시하며 [12], 21 CFR Part 11이 그 결과가 충족해야 하는 전자기록 및 전자서명 기준을 설정합니다 [13].
실험실 데이터 모델: sample → test → result
이 모든 것은 이후의 모든 장이 재사용하는 세 개의 테이블에 착륙합니다. examples/platform/db/30-lab-events.sql을 보면 이렇습니다.
-- examples/platform/db/30-lab-events.sql
CREATE TABLE lab.sample (
sample_id text PRIMARY KEY,
batch_id text REFERENCES s88.batch,
sample_time timestamptz NOT NULL,
sample_point text NOT NULL,
sample_type text NOT NULL DEFAULT 'in_process' -- in_process | release | stability
);
CREATE TABLE lab.test (
test_id text PRIMARY KEY,
name text NOT NULL,
unit text,
spec_low numeric,
spec_high numeric
);
CREATE TABLE lab.result (
result_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sample_id text NOT NULL REFERENCES lab.sample,
test_id text REFERENCES lab.test,
value numeric,
text_value text,
unit text,
result_ts timestamptz NOT NULL DEFAULT now(),
analyst text,
instrument_id text,
status text NOT NULL DEFAULT 'preliminary', -- preliminary | verified | rejected
UNIQUE (sample_id, test_id, result_ts)
);
CREATE INDEX ON lab.result (sample_id);
세 개의 열이 규제상의 무게 대부분을 짊어집니다. sample.batch_id는 ISA-95 배치 테이블로 곧장 들어가는 외래 키(foreign key)이므로, 모든 결과가 그것이 판정하는 로트(lot)에 영구히 결합됩니다 — 귀속성(attributability)의 척추입니다. result.analyst와 result.instrument_id는 "누가 그리고 무엇으로"에 답합니다. 그리고 result.status는 실험실 워크플로 자체를 인코딩합니다. 결과는 preliminary로 태어나, 두 번째 자격 있는 사람이 검토하면(4-눈 원칙, four-eyes principle) verified가 되며, rejected될 수도 있습니다. preliminary 결과는 출하 데이터가 아닙니다. 오직 verified된 것만 출하 데이터입니다. UNIQUE (sample_id, test_id, result_ts) 제약은 결과를 결코 조용히 덮어쓰지 못한다는 뜻입니다 — 재시험(re-test)은 새 타임스탬프를 가진 새 행이지, 결코 편집(edit)이 아닙니다. 이것이 감사 추적이 정직하게 유지되는 방식입니다.
기기에서 배치 기록까지. 디바이스는 LADS / SiLA / AnIML / ASM을 말하고, SENAITE는 시료 접수와 preliminary→verified 전환을 소유하며, eLabFTW는 방법 기록에 서명하고 타임스탬프를 찍습니다. 오직 검증된 결과만이 PostgreSQL과 배치 기록으로 넘어갑니다. 빨간 메모는 순수 OSS가 아직 Part 11을 충족하지 못하는 지점을 표시합니다.
Original diagram by the authors, created with AI assistance.
기기에서 데이터 빼내기
실험실 통합의 고되고 화려하지 않은 진실은, 대부분의 분석 기기가 섬(island)이라는 것입니다. HPLC는 자기만의 크로마토그래피 데이터 시스템을 가지고 있고, 플레이트 리더(plate reader)는 독점 포맷의 덩어리(blob)를 내보내며, 세포 계수기는 PDF를 인쇄합니다. 아래 표준들의 핵심 목적은 숫자를 다시 타이핑하는 일을 멈추는 것입니다.
가장 새롭고 가장 유망한 것은 OPC UA LADS — 실험실 및 분석 디바이스 표준(Laboratory and Analytical Device Standard), OPC 30500으로, 2023년 OPC Foundation이 SPECTARIS, VDMA와 함께 발표했습니다. 이것은 실험실 기기에 바이오리액터 스키드가 쓰는 것과 같은 자기 서술적(self-describing) OPC UA 주소 공간을 부여하며, 디바이스 타입에 무관한 정보 모델을 하드웨어(Hardware) 뷰와 기능(Functional) 뷰로 나눕니다. 그래서 적정기(titrator)와 HPLC가 같은 모양으로 결과를 노출합니다 [1]. 동료 심사를 거친 설계 논거는 읽어볼 가치가 있습니다. LADS가 존재하는 이유는 바로, 네트워크화된 실험실이 수십 개의 드라이버 대신 하나의 모델을 필요로 했기 때문입니다 [2]. LADS 서버는 결과를 Allotrope 문서로 첨부할 수 있으며, 더 오래되고 가벼운 형제가 하나 있습니다 — 실험실 디바이스를 명령(commanding) 하기 위한 SiLA 2(운전 시작, 결과 읽기)로 [6], gRPC/HTTP2 기반의 자기 서술적 표준이며, 그 기능 정의 언어(Feature Definition Language)는 클라이언트가 런타임에 디바이스의 역량을 발견하게 해줍니다 [7]. 실무에서 LADS와 SiLA는 상호 보완적입니다. 기기를 구동하는 데는 SiLA, 결과를 공장 데이터 패브릭(data fabric)으로 발행하는 데는 LADS/OPC UA입니다.
최소한의 LADS 스타일 결과 노드는, 저장소의 예시용 examples/ingest/lads_server.js가 스케치하듯이 이렇게 생겼습니다. 이 파일은 LADS 정보 모델을 본떠 교육용 스케치로 커밋되어 있으며 — 실행 가능하고 인증된 LADS 서버가 아닙니다(저장소는 5/7장을 위한 asyncua OPC UA 바이오리액터 서버를 진짜 OPC UA 스택으로 제공합니다).
// examples/ingest/lads_server.js — illustrative LADS-shaped result node (not a certified LADS server)
const fnSet = addObject(device, "FunctionalUnitSet");
const hplc = addFunctionalUnit(fnSet, "HPLC_Titer");
addAnalogResult(hplc, {
name: "ProteinConcentration",
value: 5.877, unit: "g/L", // QUDT-mapped engineering unit
sampleId: "BATCH-2026-001-DS",
method: "SOP-AT-HPLC-001",
measuredAt: "2026-01-20T10:15:00Z"
});
벤더 중립 결과 파일: AnIML과 Allotrope
기기가 LADS를 말하든 말하지 않든, 여러분은 여전히 그 출력을 원래 벤더의 소프트웨어 없이도 20년 후에 읽을 수 있는 포맷으로 보관하고 싶을 것입니다. 두 개의 오픈 표준이 이 일을 하며, 저장소는 같은 HPLC 역가(titer) 측정에 대해 각각의 예시를 하나씩 제공합니다.
AnIML(분석 정보 마크업 언어, Analytical Information Markup Language)은 더 오래된, ASTM이 관리하는(소위원회 E13.15) XML 포맷입니다. 그 설계 목표는 처음부터 시료(sample), 방법(method), 감사 추적(audit-trail), 서명(signature) 섹션을 명시적으로 갖춘 벤더 중립 문서였으며 [4], 일반 코어(generic core)에 기법별 정의를 더한 형태로 만들어졌습니다 [5]. 커밋된 datasets/hplc_titer.animl.xml은 의도적으로 최소화되었지만 유효한 모양의 예시입니다.
<!-- examples/datasets/hplc_titer.animl.xml -->
<AnIML xmlns="urn:org:astm:animl:schema:core:draft:0.90" version="0.90">
<SampleSet>
<Sample sampleID="BATCH-2026-001-DS" name="Drug Substance"/>
</SampleSet>
<ExperimentStepSet>
<ExperimentStep experimentStepID="titer-hplc" name="Protein A HPLC titer">
<Result name="Titer">
<SeriesSet name="titer" length="1">
<Series name="concentration" dependency="dependent" seriesID="c" seriesType="Float32">
<IndividualValueSet><F>5.877</F></IndividualValueSet>
<Unit label="g/L"/>
</Series>
</SeriesSet>
</Result>
<Method name="SOP-AT-HPLC-001"/>
</ExperimentStep>
</ExperimentStepSet>
</AnIML>
더 새로운, JSON 네이티브 선택지는 Allotrope 단순 모델(Allotrope Simple Model, ASM) 입니다 — Allotrope 데이터 모델의 JSON 표현으로, Allotrope 재단 온톨로지(Allotrope Foundation Ontology, AFO)의 통제된 어휘를 사용하므로, 각 필드의 의미(meaning) 가 이름뿐 아니라 기계가 처리할 수 있게(machine-actionable) 됩니다 [3]. 같은 역가 측정을 datasets/hplc_titer.asm.json으로 표현하면 이렇습니다.
{
"$asm.manifest": "http://purl.allotrope.org/manifests/core/REC/2024/06/manifest.schema",
"measurement aggregate document": {
"measurement document": [
{
"sample document": {
"batch identifier": "BATCH-2026-001",
"sample identifier": "BATCH-2026-001-DS"
},
"device system document": {
"device identifier": "HPLC-07",
"model number": "OpenHPLC-1"
},
"measurement identifier": "BATCH-2026-001-titer",
"protein concentration": { "value": 5.877, "unit": "g/L" },
"measurement time": "2026-01-20T10:15:00Z"
}
]
}
}
두 파일 모두 시료 BATCH-2026-001-DS에 대해 5.877 g/L로 일치한다는 점에 주목하세요 — 그것이 핵심입니다. 실험실이 측정하는 역가는, 분석 잡음 범위 안에서, 크로마토그래피 장이 계산한 용출액(eluate) 역가입니다. 어떤 표준을 고를지는 대체로 어떤 하류 도구에 먹일 것인가의 문제입니다. 보관과 ASTM 정렬 규제 패키지를 위해서는 AnIML, FAIR 데이터 레이크와 온톨로지 기반 질의를 위해서는 ASM입니다. 어느 쪽이든, 필드 수준의 단위는 QUDT에 매핑되므로 16장의 지식 그래프(knowledge graph)가 그것들에 대해 추론할 수 있습니다.
SENAITE: 워크플로를 위한 오픈 소스 LIMS
결과 파일 더미는 실험실이 아닙니다. LIMS(실험실 정보 관리 시스템, Laboratory Information Management System)는 시료를 접수하고, 시험을 배정하고, 분석자(analyst)의 결과를 포착하며 — 결정적으로 — 예비 숫자를 출하된 숫자로 바꾸는 검증(verification) 워크플로를 돌리는 시스템입니다. 이 책이 사용하는 오픈 소스 LIMS는 SENAITE 로, Plone/Zope 스택 위에 구축되고 GPL-2.0 으로 라이선스된 엔터프라이즈 LIMS입니다 [8]. 동반 스택은 이것을 lab 프로필(senaite/senaite:2.6.0) 뒤에서 돌립니다. 첫 부팅이 몇 분 걸린다는 점을 유의하세요. Plone이 꽤 많은 것을 부트스트랩하기 때문입니다.
통합 패턴은 API 우선(API-first)입니다. SENAITE는 JSON REST API를 제공하므로, 저장소의 예시용 examples/ingest/senaite_import.py 스케치는 배치에 대비하여 시료를 등록하고 앳라인 결과를 게시한 뒤, 나중에 검증된 것만 다시 읽어옵니다(라우트 이름과 POST 본문은 실제 senaite.jsonapi에 충실하며, 주변 오케스트레이션은 구성된 lab 프로필을 가정합니다).
# examples/ingest/senaite_import.py — register sample + push results via the SENAITE REST API
import requests
S = requests.Session()
S.auth = ("lab_importer", PASSWORD) # service account, not a person
base = "http://senaite:8080/senaite/@@API/senaite/v1" # /<plone-site-id>/@@API/...
# 1) create the analysis request (sample login) bonded to the batch
ar = S.post(f"{base}/create", json={
"portal_type": "AnalysisRequest",
"Client": "uid-of-internal-qc",
"SampleType": "drug-substance",
"ClientSampleID": "BATCH-2026-001-DS",
"Analyses": ["SEC_monomer_pct", "HCP_ng_per_mg", "endotoxin_EU_per_mL"],
}).json()
# 2) submit a result for one analysis (still 'preliminary' until verified)
S.post(f"{base}/update", json={
"uid": ar["items"][0]["Analyses"][0]["uid"],
"Result": "98.611",
})
그 뒤에 따라오는 워크플로 — 제출(submit) → 검증(verify) → 발행(publish) — 이 SENAITE가 존재하는 이유입니다. 분석자가 제출하고, 두 번째 자격 있는 사용자가 검증하며(SENAITE는 검증자가 제출자가 아님을 강제할 수 있습니다), 그제서야 결과가 발행 가능해집니다. 그 검증된 결과를 우리의 PostgreSQL lab.result 테이블로 끌어오는 것은, 아직 검증되지 않은 것은 무엇도 가져오기를 거부하는 작고 신중한 동기화(sync)입니다.
# examples/ingest/senaite_import.py — only verified results cross into the system of record
for item in S.get(f"{base}/search",
params={"portal_type": "Analysis",
"review_state": "verified"}).json()["items"]:
db.execute(
"INSERT INTO lab.result (sample_id, test_id, value, unit, analyst, "
"instrument_id, status) VALUES (%s, %s, %s, %s, %s, %s, 'verified') "
"ON CONFLICT (sample_id, test_id, result_ts) DO NOTHING",
(item["ClientSampleID"], item["getKeyword"], item["Result"],
item["Unit"], item["getAnalyst"], item["Instrument"]))
이것이 preliminary 실험실 잡음을 배치 기록 밖에 묶어두는 관문(gate)입니다. review_state=verified 필터가 단 한 줄에 담긴 통제 전부입니다.
eLabFTW: 방법과 실험 출처를 위한 ELN
LIMS는 결과를 기록하고, ELN(전자 실험 노트, Electronic Lab Notebook)은 어떻게 그것을 얻었는지 를 기록합니다 — 방법, 일탈(deviation), 추론, 그리고 분석자의 서명된 진술인 "나는 이 날짜에 기기 HPLC-07에서 SOP-AT-HPLC-001을 수행했다." 이 책은 eLabFTW 를 사용하며, AGPL-3.0 으로 라이선스됩니다(elabftw/elabimg:5.1.15, MySQL 사이드카 포함). 그 카피레프트(copyleft)가 여러분 자신의 코드에 아무것도 부과하지 않도록 네트워크 너머에서 독립 실행형(standalone)으로 돌립니다.
규제 실험실을 위한 eLabFTW의 두드러진 기능은 암호학적입니다. 실험 기록 위에 Ed25519ph 전자서명(eLabFTW가 쓰는, 사전 해시된 Ed25519 변형)과 RFC 3161 신뢰 타임스탬프(trusted timestamp)를 적용할 수 있습니다 [10]. RFC 3161은 IETF의 타임스탬프 프로토콜로, 신뢰할 수 있는 타임스탬핑 기관(Timestamping Authority)이 여러분 문서의 해시(hash) 위에 TimeStampToken을 반환합니다 — 그래서 내용을 TSA로 보내는 일 없이도, 나중에 그 콘텐츠가 그 순간에 변경되지 않은 채 존재했음을 증명할 수 있습니다 [11]. 수집 패턴은 다시 REST 우선이며, 저장소의 예시용 examples/ingest/elabftw_ingest.py에 스케치되어 있습니다.
# examples/ingest/elabftw_ingest.py — sign + timestamp the method record via the eLabFTW API
import elabapi_python
cfg = elabapi_python.Configuration()
cfg.api_key = {"api_key": ELAB_TOKEN}
cfg.host = "https://elabftw/api/v2"
api = elabapi_python.ExperimentsApi(elabapi_python.ApiClient(cfg))
# attach the AnIML/ASM result files to the experiment, then sign + timestamp it
api.post_experiment(body={"title": "HPLC titer — BATCH-2026-001-DS",
"category": "release-testing"})
# the signature (Ed25519ph) and RFC 3161 token are applied through the UI/API
# and lock the entry; later edits create a new, separately signed version.
일단 서명되고 타임스탬프가 찍히면 항목이 잠깁니다. 이후의 어떤 변경이든 자기만의 서명을 가진 새 버전을 만들므로, 이력은 추가 전용(append-only)으로 남습니다.
왜 중요한가
실험실은 배치가 살거나 죽는 곳입니다. 다른 모든 장은 공정에 관한 데이터를 포착하고, 이 장은 제품에 대한 평결을 포착합니다. BATCH-2026-004의 그 128 ng/mg HCP 결과는 출하된 로트와 25만 달러짜리 손실 처리(write-off) 사이의 차이입니다 — 그리고 만약 그것이 조용히 편집될 수 있다면, 품질 시스템 전체가 허구가 됩니다. 그러니 여기의 통제들은 관료적 장식이 아닙니다. preliminary → verified 상태, 제2자 검증, 불변의 결과 행, 서명되고 타임스탬프 찍힌 방법 기록 — 그 각각이 검사관에게 단 한 문장을 방어 가능하게 만들기 위해 존재합니다. "이 결과는 이 분석자가, 이 적격 기기에서, 이 검증된 방법에 대비하여 측정했으며, 그 이후로 변하지 않았다." 그 문장을 옳게 만들면 배치 기록은 신뢰할 수 있고, 틀리게 만들면 하류의 어떤 것도 의미가 없습니다.
실제 현장에서는
상업용 QC 실험실에서 기록 시스템은 거의 언제나 검증된 상업용 LIMS입니다 — LabWare, STARLIMS, 또는 Thermo SampleManager — Empower나 OpenLab 같은 크로마토그래피 데이터 시스템과 연결되고, 기기는 벤더 드라이버를 통해, 혹은 점점 더, SiLA/LADS를 통해 통합됩니다. 우리의 OSS 스택은 그것들을 대체하는 척하지 않습니다. 같은 모양들 — 시료 접수, 검증 워크플로, 벤더 중립 결과 파일, 서명된 방법 기록 — 을 보여주어, 통합 패턴이 전이되도록 합니다.
2026년을 위한 정직한 닻 몇 가지.
- NIIMBL — 바이오의약품 혁신을 위한 미국의 민관 합작 Manufacturing USA 연구소 — 은 분석 방법 및 데이터 표준화 작업에 자금을 댔는데, 이는 바로 실험실 데이터 상호운용성이 인정된 병목(bottleneck)이기 때문입니다. 그 SABRE 시설(2024년 4월 착공한 NIIMBL / 델라웨어 대학교의 파일럿 규모 cGMP — current Good Manufacturing Practice — 시설)은 차세대 공정을 돌리는 장소로 지어지고 있으며, QC와 PAT 데이터가 이 장이 스케치하는 그런 종류의 플랫폼에 공급됩니다. SABRE는 시설이지, 데이터 프로그램이 아닙니다.
- LADS는 진정으로 새롭습니다. OPC 30500은 2023년 것이고, 인증된 서버 구현은 2026년에도 여전히 막 나오는 중입니다. 그래서 실제 현장에서는 성숙한 LADS 서버보다 훨씬 더 많은 SiLA 2, 순수 OPC UA, 독점 드라이버를 만나게 됩니다. 그 표준은 올바른 방향이지만, 아직 기본 현실은 아닙니다.
이 계층에 대한 정직한 OSS 대 상업용 평결. 오픈 소스는 기제(mechanics) 를 진정으로 다룹니다. SENAITE는 시료 접수부터 검증까지 완전한 워크플로를 돌리고, eLabFTW는 기록에 서명하고 타임스탬프를 찍으며, AnIML/ASM은 내구성 있는 벤더 중립 데이터를 줍니다. 하지만 어느 도구도 기본 상태로는 21 CFR Part 11을 준수하지 않으며, 이 책은 그 점에 대해 명시적입니다. SENAITE의 유일하게 발표된 Part 11 격차 분석은 2019년(v1.3.2 대상) 것이며, 실재하고 닫히지 않은 격차들을 나열합니다 — 전자서명 통제, 보존(retention), 그리고 비밀번호/접근 통제 모두가 구성이나 강화(hardening)를 필요로 합니다 [9]. 저장소는 그 격차 목록을 /compliance/gap-analyses 아래에 제공하며, SENAITE를 준수하는 LIMS가 아니라 교육용 LIMS로 취급합니다. eLabFTW 자체 문서도 더 평이한 말로 같은 것을 말합니다. 그것은 암호학적 원시 요소(primitive)를 제공하지만, 준수 여부는 여러분이 그것을 어떻게 구성하고, 검증하고, 운영하느냐에 달려 있습니다 [10]. 출하 데이터에 대한 강력한 감사 추적과 검토 기대치는 선택 사항이 아니며 [12], Part 11은 이 시스템들이 넘어야 할 기준을 설정합니다 [13]. 순수 OSS는 워크플로와 데이터 모양을 줍니다 — 아마 80% 정도까지. 검증된 전자서명, 잠긴 접근 통제, 공급자 책임(supplier accountability), 그리고 정식 IQ/OQ/PQ가 GxP의 마지막 한 마장(last mile)이며, 우리는 그 하이브리드를 5부에서 정직하게 구축합니다.
핵심 용어
- LIMS — 실험실 정보 관리 시스템(Laboratory Information Management System). 시료 접수, 시험 배정, 결과, 그리고 검증 워크플로를 관리함(여기서는 SENAITE).
- ELN — 전자 실험 노트(Electronic Lab Notebook). 방법, 실험, 추론을 서명과 함께 기록함(여기서는 eLabFTW).
- 앳라인 / 오프라인 분석(at-line / offline assay) — 공정에서 뽑아 벤치 기기에서 측정하는 시료(VCD, 생존율, 대사물). 인라인 태그의 오프라인 쌍둥이.
- 출하 시험(release testing) — 배치가 출하될 수 있는지를 결정하는 QC 패널(SEC/CEX HPLC, HCP, 숙주세포 DNA, 엔도톡신, 미생물 한도).
- OOS — 규격 이탈(Out Of Specification). 검증된 한계 밖의 결과로, 반드시 배치를 동결시키고 조사를 촉발해야 함.
- 분석성적서(certificate of analysis, CofA) — 규격과 합격/불합격이 포함된 출하 결과의 집합으로, 출하된 로트에 동반됨.
- OPC UA LADS — 실험실 및 분석 디바이스 표준(Laboratory and Analytical Device Standard, OPC 30500). 실험실 기기를 위한 자기 서술적 OPC UA 정보 모델.
- SiLA 2 — 실험실 디바이스를 명령하고 발견하기 위한 gRPC/HTTP2 표준. LADS를 보완함.
- AnIML — 분석 정보 마크업 언어(Analytical Information Markup Language). 시료/방법/감사/서명 섹션을 갖춘 벤더 중립 분석 데이터를 위한 ASTM XML 포맷.
- Allotrope ASM / AFO — FAIR하고 기계가 처리할 수 있는 분석 데이터를 위해 Allotrope 재단 온톨로지를 사용하는, JSON 네이티브 Allotrope 단순 모델.
- 검증(4-눈, four-eyes) — 예비 결과가 검증되고 출하 가능한 결과가 되기 전에 두 번째 자격 있는 사람이 검토하는 통제.
- RFC 3161 타임스탬프 — 문서의 해시 위에 찍는 신뢰 타임스탬프 토큰으로, 그 콘텐츠가 어느 시점에 변경되지 않은 채 존재했음을 증명함.
다음 이야기
우리는 이제 제품의 평결을 포착했습니다 — 모든 앳라인 시료와 모든 출하 결과, 실험실에서 태어나 자신의 배치에 결합된 것들. 하지만 분자는 여전히 완성되고 라벨이 붙은 바이알(vial)이 되어야 하며, 그 충전(fill)을 둘러싼 청정 공간은 제품 자체만큼 면밀히 감시되어야 합니다. 다음 장 충전-완료, 포장 및 환경 모니터링(Fill-Finish, Packaging & Environmental Monitoring) 은 QC 실험실을 떠나 충전 라인과 청정실(cleanroom)로 향합니다. 그곳에서 고카디널리티(high-cardinality) 텔레메트리 — 입자 계수(particle count), 충전 중량(fill weight), 직렬화(serialization) 이벤트, PackML 라인 상태 — 가 단단한 GxP 경계를 만납니다.