제7장
WIA-BIO-016의 레코드와 계산 결과가 공통기술문서(CTD)의 어디에 자리 잡는지, SDK 위에 만든 도구가 어떤 데이터 무결성 의무를 지는지, 골든 벡터로 구현을 어떻게 시험하는지, 그리고 WIA 적합성 주장이 정직하게 말할 수 있는 범위가 어디까지인지를 다룬다.
앞의 여섯 장은 하나의 증거 사슬을 쌓아 올렸다. 분자를 특성분석하고, 결합을 측정하고, 노출과 효과를 모델링하고, 면역원성을 선별하고, 시간에 따른 안정성을 추적하며, 후속 제품이라면 대조약과의 유사성을 품질 속성 하나하나에 대해 논증했다. 그러나 이 증거는 규제기관이 심사할 수 있는 문서로 정리되기 전까지는 규제상 아무런 가치가 없다. 또 그 증거를 만든 소프트웨어는 정답이 알려진 입력으로 시험되기 전까지는 믿을 수 없다. 이 장은 마지막 단계의 이 두 측면을 함께 다룬다.
전반부는 제출 문서에 관한 것이다. ICH M4가 정한 공통기술문서(CTD)의 구조, 바이오의약품의 모듈 3이 담는 품질(CMC) 내용, WIA-BIO-016 명세서가 가리키는 ICH 및 규제기관 가이드라인, 그리고 GMP 데이터나 제출 데이터를 생산하는 모든 컴퓨터화 시스템에 적용되는 데이터 무결성 요구를 설명한다. 후반부는 소프트웨어에 관한 것이다. WIA-BIO-016 SDK와 CLI 구현을 시험하는 방법을 보이고, 그 시험이 버전 1.0 소스에서 드러내는 결함을 기록하며, 공통 WIA 부속서가 요구하는 적합성 증거를 설명한다. 마지막으로 어떤 기술적 세부보다 중요한 경계를 긋는다. WIA 적합성 인증서가 주장할 수 있는 것은 무엇이고, 절대 무엇으로 오인되어서는 안 되는가 하는 경계다.
ICH M4는 ICH 규제 회원기관이 받아들이는 품목허가 신청서의 통일된 형식인 공통기술문서(Common Technical Document, CTD)를 정의한다. CTD는 흔히 삼각형으로 그려진다. 모듈 1은 지역별 행정 정보로서 통일되지 않은 부분이고, 모듈 2는 개요와 요약이며, 모듈 3·4·5는 각각 품질, 비임상, 임상 자료의 본 보고서를 담는다. 품질 부분의 구성은 ICH M4Q에 기술되어 있다. 바이오의약품에서는 제조공정과 제품의 측정 속성이 분리될 수 없기 때문에, 이 품질 부분이 대개 제출 문서 가운데 가장 방대하고 기술적으로 밀도가 높은 부분이 된다.
WIA-BIO-016 §9.2는 모듈 3.2.S(원료의약품)와 3.2.P(완제의약품)의 항목을 나열한다. 심사자는 M4Q의 번호 체계로 문서를 찾아 읽으므로 전체 번호를 알아 둘 필요가 있다.
| 항목 | 내용 |
|---|---|
| 3.2.S.1 | 일반 정보: 명칭, 구조, 일반 특성 |
| 3.2.S.2 | 제조: 제조소, 제조공정과 공정 관리, 원자재 관리(세포은행 포함), 핵심 공정과 중간체 관리, 공정 밸리데이션, 제조공정 개발 경위 |
| 3.2.S.3 | 특성분석: 구조 및 기타 특성 규명, 불순물 |
| 3.2.S.4 | 원료의약품 관리: 기준, 시험방법, 시험방법 밸리데이션, 배치 분석, 기준 설정 근거 |
| 3.2.S.5 – S.7 | 표준품 또는 표준물질, 용기·마개 시스템, 안정성 |
| 3.2.P.1 – P.8 | 완제의약품: 성상 및 조성, 제제 개발 경위, 제조, 첨가제 관리, 완제의약품 관리, 표준품, 용기·마개, 안정성 |
| 3.2.A | 부록: 시설 및 장비, 외래성 인자 안전성 평가, 새로운 첨가제 |
| 3.2.R | 지역별 정보 |
명세서만 읽어서는 놓치기 쉬운 점이 두 가지 있다. 첫째, 명세서의 3.2.P 목록에는 독립된 항목인 P.4(첨가제 관리)가 빠져 있다. 둘째, 세포배양으로 생산하는 제품의 바이러스 제거 자료는 3.2.A.2 외래성 인자 안전성 평가에 제시된다. CHO 세포 유래 항체를 심사하는 사람이 가장 꼼꼼히 읽는 항목 가운데 하나지만, WIA-BIO-016에는 이를 담을 데이터 타입이 없다.
명세서는 같은 모듈 3 골격을 쓰는 세 가지 허가 경로를 언급한다. 미국에서 새로운 바이오의약품은 공중보건서비스법(PHS Act) 351(a)조에 따른 생물학적제제 허가신청(BLA)으로 허가된다. 바이오시밀러는 생물의약품 가격경쟁 및 혁신법(BPCI Act)이 도입한 351(k)조의 간소화 경로를 이용하는데, 독자적인 전체 개발 프로그램 대신 이미 허가된 대조약과의 바이오시밀러성 입증에 의존한다. 유럽연합에서는 재조합 DNA 기술로 만든 제품의 품목허가 신청(MAA)이 유럽의약품청(EMA)의 중앙허가절차를 거친다. 바이오시밀러의 품질 모듈은 오리지널 제품보다 가볍지 않다. 오히려 더 무겁다. 독립적으로 완결된 품질 자료에 더해 제6장에서 다룬 비교 분석 자료까지 담아야 하기 때문이다.
RegulatorySubmission 타입WIA-BIO-016은 신청 자체를 메타데이터로 표현한다. RegulatorySubmission 인터페이스에는 pathway, 제출 type(ind, bla, maa, amendment, annual-report), indication, 선택적 phase, 예상 제출일과 예상 허가일, 그리고 orphanDrug와 fastTrack 두 플래그가 있다. RegulatoryPathway 유니온은 fda-bla, ema-maa, pmda-japan, nmpa-china, biosimilar-351k, accelerated, breakthrough를 제공한다.
이 유니온에는 성격이 다른 세 가지 축이 섞여 있다. fda-bla와 biosimilar-351k는 법적 경로이고, pmda-japan과 nmpa-china는 규제기관이며, accelerated와 breakthrough는 신청을 대체하는 것이 아니라 신청에 덧붙는 신속 심사 제도다. 혁신 치료제(breakthrough)로 지정된 항체도 결국 BLA로 제출된다. "우리 351(k) 프로그램 가운데 신속 심사 지정을 함께 받은 것은 무엇인가" 같은 질문에 답해야 하는 구현이라면 규제기관, 법적 경로, 신속 심사 제도를 별도 필드로 모델링하고, 교환용으로는 WIA 열거값을 그대로 유지하는 편이 좋다. 한국 식품의약품안전처(MFDS)나 캐나다 보건부(Health Canada)처럼 열거형에 없는 지역도 같은 방식의 확장이 필요하다. 모델에 자유 텍스트 탈출구가 없으므로 자체 코드 목록을 두는 것이 현실적인 해법이다.
// 교환용 WIA 값을 유지하는 확장 예시
interface SubmissionRecord extends RegulatorySubmission {
agency: 'FDA' | 'EMA' | 'PMDA' | 'NMPA' | 'MFDS' | 'HC' | string;
legalBasis?: '351(a)' | '351(k)' | 'centralised' | string;
expeditedPrograms?: Array<'breakthrough' | 'accelerated' | 'fast-track' | 'priority-review' | string>;
sequenceNumber?: string; // eCTD 시퀀스, 예: "0000"
}
화학·제조·품질관리(CMC)는 제품을 일관되게 만들고 적절히 관리할 수 있음을 증명하는 부분이다. 명세서 §9.1.2는 원료의약품에 대해 공정 설명, 공정 중 관리, 기준, 안정성 자료를, 완제의약품에 대해 제제 조성, 제조공정, 용기·마개, 안정성 자료를 핵심 요소로 든다. 각각에 한 마디씩 해석을 덧붙인다.
CellCultureParams와 PurificationProcess(제6장)이지만, 이 타입들은 결과값을 담을 뿐 공정 설명이 밝혀야 하는 변수 범위는 담지 않는다.명세서 §9.4는 "전형적인 mAb 기준"을 나열한다. 이는 출하 기준이 어떤 모양을 갖는지 보여 주는 예시로 읽어야 하며, 모든 제품이 충족해야 할 한계로 읽어서는 안 된다. 실제 허용 기준은 제품마다 다르고, 그 제품의 제조 이력과 임상 경험으로 근거를 제시한다.
| 시험 항목(예시) | §9.4의 예시 기준 | 설명 |
|---|---|---|
| 성상 | 맑거나 약간 유백색 | StabilityResult.appearance에 대응 |
| pH | 6.0 ± 0.5 | 제형이 정하는 값이지 제품군 규칙이 아니다 |
| 단백질 농도 | 표시량의 95~105% | 보통 제품 고유의 흡광계수를 써서 UV A280으로 측정 |
| 순도 / 크기 변이체 | SEC 단량체 ≥95%, 응집체 <5% | QualityAttributes.monomer, .aggregates에 대응 |
| 전하 변이체 | 주 피크 60~80% | 제품 특이성이 매우 큼. 산성·염기성 그룹과 함께 범위로 보고 |
| 역가 | 표준품 대비 80~120% | 자체 표준품(3.2.S.5) 대비 생물학적 시험 |
| 엔도톡신 | <1 EU/mg | 한계는 최대 투여량과 투여 경로에서 도출 |
| 무균 | 적합(USP <71>) | 약전 시험 |
| 바이오버든 | <10 CFU/mL(멸균 전) | 멸균 여과 단계 전에 적용하는 공정 중 한계 |
이 표는 WIA 모델이 어디서 멈추는지도 보여 준다. QualityAttributes는 순도, 단량체, 응집체, 단편, 전하 변이체 그룹, 역가, 엔도톡신, 숙주세포 단백질, 잔류 DNA의 수치를 저장한다. 그러나 허용 기준, 시험방법, 로트 번호, 시험일, 시험자는 저장하지 않는다. 3.2.S.4.4나 3.2.P.5.4의 배치 분석 표를 만들어 내려는 도구라면 이 필드를 추가해야 한다. 다음 절에서 설명하듯 이 필드들은 선택 사항이 아니다.
명세서 §9.3은 일련의 ICH 가이드라인을, §10.1은 네 건의 참고문헌을 든다. 아래 표에 실은 바이오시밀러 관련 규제기관 문서 두 건과 ICH Q6B, 그리고 이 책에서 쓰지 않아 표에서 뺀 FDA의 2020년 COVID-19 백신 가이던스이다. 아래 표는 각 문서의 정식 주제와 이 책에서 해당 내용에 기대는 장을 정리한 것이다. 개정 차수는 시간에 따라 바뀌므로, 제출 문서에 인용하기 전에는 반드시 ICH 웹사이트에서 현행 버전을 확인해야 한다.
| 가이드라인 | 주제 | 관련 장·항목 |
|---|---|---|
| ICH Q5A | 사람 또는 동물 유래 세포주에서 생산한 생명공학 제품의 바이러스 안전성 평가 | 제6장(바이러스 제거), 3.2.A.2 |
| ICH Q5B | 생산 세포 내 발현 구조체 분석 | 제1장, 3.2.S.2.3 |
| ICH Q5C | 생명공학·생물학적 제품의 안정성 시험 | 제5장, 3.2.S.7 및 3.2.P.8 |
| ICH Q5D | 세포 기질의 유래와 특성분석 | 제1장, 3.2.S.2.3 |
| ICH Q5E | 제조공정 변경에 따른 제품의 동등성(comparability) | 제6장 |
| ICH Q6B | 기준: 생명공학·생물학적 제품의 시험방법과 허용 기준 | 제2장, 3.2.S.4 및 3.2.P.5 |
| ICH Q8, Q9, Q10, Q11 | 제제 개발, 품질 위험 관리, 의약품 품질 시스템, 원료의약품 개발과 제조 | 제2장(QbD), 3.2.S.2.6 및 3.2.P.2 |
| ICH S6(R1) | 생명공학 유래 의약품의 전임상 안전성 평가 | 모듈 4 |
| ICH M4 (M4Q) | 공통기술문서와 그 품질 부분 | 이 장 |
| FDA 가이던스(2015) | 치료용 단백질 제품의 대조약 대비 바이오시밀러성 입증에서의 품질 고려사항 | 제6장 |
| EMA 가이드라인(2014) | 동등생물의약품(similar biological medicinal products) 총괄 가이드라인 | 제6장 |
명세서 §10.3의 인용 하나는 옮겨 쓰기 전에 바로잡아야 한다. USP 일반 장 <1220>의 제목은 "Analytical Procedure Life Cycle(분석 절차 수명주기)"이다. 분석 절차를 수명주기 전반에 걸쳐 관리하는 방법을 다루는 일반 장이며, 명세서의 표기처럼 단일클론항체 전용 장이 아니다. 이에 대응하는 ICH 문서인 밸리데이션에 관한 Q2(R2)와 분석 절차 개발에 관한 Q14는 제8장에서 다룬다.
모듈 3에 들어가는 모든 숫자는 기록이며, 규제기관은 그것이 종이에 쓰였든 소프트웨어가 만들었든 기록이 신뢰할 만하기를 기대한다. 이 기대를 줄여 부르는 말이 ALCOA+다. 데이터는 귀속성(누가 만들고 바꿨는가, Attributable), 판독성(Legible), 동시성(활동이 일어난 시점에 기록, Contemporaneous), 원본성(또는 인증된 사본, Original), 정확성(Accurate)을 갖춰야 하고, 여기에 완전성, 일관성, 지속성, 가용성(Complete, Consistent, Enduring, Available)이 더해진다. 미국에서는 21 CFR Part 11이 전자 기록과 전자 서명을 종이 기록과 자필 서명에 준하는 것으로 보는 조건을 정하고, EU에서는 GMP 가이드의 Annex 11이 컴퓨터화 시스템을 다룬다. ISPE의 GAMP 5 같은 업계 지침은 이런 시스템을 위험 기반으로 밸리데이션하는 방법을 설명한다.
"표준 SDK"라는 이름이 실제보다 많은 것을 약속하는 것처럼 들릴 수 있으므로 여기서는 분명히 해 둔다. BiopharmaSDK의 함수는 순수한 계산이다. 숫자를 받아 숫자를 돌려준다. 사용자, 시각, 기기, 시료, 로트, 변경 사유라는 개념이 없고, 디스크에 아무것도 쓰지 않으며, 아무것에도 서명하지 않는다. types.ts의 타입에는 식별자 필드가 전혀 없어서, 서로 다른 로트의 QualityAttributes 객체 두 개를 구별할 방법이 없다. 계산 라이브러리로서는 결함이 아니지만, 이는 SDK 위에 만든 실험실 시스템이 Part 11이나 Annex 11 준수의 부담을 전부 스스로 진다는 뜻이다. "WIA-BIO-016 SDK를 사용한다"는 업체의 말은 그 업체 기록의 무결성에 대해 아무것도 말해 주지 않는다.
실무적인 방법은 모든 SDK 호출을 계산 레코드로 감싸는 것이다. 레코드에는 입력을 받은 그대로, 실행된 코드의 정체, 반환된 출력을 그대로, 그리고 감사 메타데이터를 저장한다. 아래 구조는 설명을 위한 설계 예시이며 WIA-BIO-016의 일부가 아니다.
{
"recordId": "calc-000731",
"function": "BiopharmaSDK.calculateBindingAffinity",
"sdkVersion": "1.0.0",
"codeDigest": "sha256:<빌드된 패키지의 다이제스트>",
"inputs": {
"antibodyConc": 1e-9,
"antigenConc": 1e-8,
"complexConc": 9e-10,
"unit": "M"
},
"outputs": {
"kd": 1.011111111111112e-9,
"fractionBound": 0.8999999999999999,
"bindingStrength": "moderate",
"therapeuticSuitability": true
},
"sourceData": ["spr-run-2291/sensorgram.csv"],
"performedBy": "analyst-17",
"performedAt": "2026-01-01T09:30:00Z",
"reason": "initial calculation",
"signature": null
}
이 레코드의 세 가지 세부는 이론이 아니라 코드를 실제로 실행해 보고 얻은 것이다. 첫째, 명세서 부록 A.1의 입력에 대한 fractionBound는 0.9가 아니라 0.8999999999999999다. 흔한 부동소수점 현상이므로 레코드에는 원값을 그대로 저장하고, 비교할 때는 허용 오차를 써야 한다. 둘째, 복합체 농도가 0이면 calculateBindingAffinity는 예외를 던지지 않고 kd: Infinity를 돌려주는데, JSON.stringify는 Infinity를 조용히 null로 바꾼다. 따라서 결과를 JSON으로 직렬화하는 시스템은 유한하지 않은 수를 따로 처리하지 않으면 "결합이 관찰되지 않음"과 "값 없음"의 차이를 잃는다. 셋째, codeDigest가 있어야 레코드를 재현할 수 있다. 이것이 없으면 나중에 SDK가 패치되었을 때 저장된 결과의 의미가 아무도 모르게 바뀔 수 있다.
의무가 놓이는 자리. 감사 추적, 접근 통제, 전자 서명, 기록 보존, 백업, 시스템 밸리데이션은 결과를 저장하고 사용하는 시스템, 즉 LIMS, 전자 실험노트, 분석 플랫폼의 몫이다. SDK는 결정적(deterministic)이고 버전이 매겨져 있다는 점에서 그 시스템의 일을 쉽게 해 줄 수는 있지만, 그 일을 대신할 수는 없다.
명세서 부록 A는 세 개의 계산 예를 싣고 있다. 규범 문서의 일부이므로 골든 벡터로 쓰기에 자연스럽지만, 부록의 해석 하나가 틀렸기 때문에 그대로 옮기지 말고 다시 계산해야 한다. 아래 표는 버전 1.0 SDK에 그 입력을 넣어 실행한 결과다.
| 벡터 | 입력 | 기대 출력(SDK 1.0) | 비고 |
|---|---|---|---|
| A.1 친화도 | 항체 1 nM, 항원 10 nM, 복합체 0.9 nM | kd ≈ 1.0111 × 10⁻⁹ M, fractionBound ≈ 0.9, bindingStrength moderate, therapeuticSuitability true | 부록은 "고친화도"라 하지만 §4.1.2와 코드 모두 1~10 nM을 중간(moderate)으로 분류 |
| A.2 PK | 투여량 100 mg, F 1.0, CL 0.2 L/h, Vd 5 L | auc 500 mg·h/L, ke 0.04 h⁻¹, halfLife 17.325 h, c0 20 mg/L, dosingInterval 24 h | 가상의 소형 단백질. IgG의 전형이 아님 |
| A.3 IRS | ADA 0.15, 중증도 5, 기간 2, 내성 0.5 | riskScore 3, riskLevel low, predictedADA 0.1545, monitoringFrequency standard | WIA 선별용 휴리스틱이며 규제 지표가 아님 |
| 반응속도 | kon 1 × 10⁵ M⁻¹s⁻¹, koff 1 × 10⁻⁴ s⁻¹ | kd 1 × 10⁻⁹ M, complexHalfLife ≈ 6931 s | 부록 A에 없는 파생 벡터 |
| EC50에서의 PD | C = EC50 = 1, Emax 100, n = 1 | effect 50, receptorOccupancy 0.5, therapeuticRange 0.5~9 | 점유율을 Kd가 아닌 EC50으로 계산(제4장의 결함) |
골든 벡터는 코드의 동작을, 결함까지 포함해 고정한다. 위의 PD 벡터가 통과하는 것은 바로 SDK가 점유율을 EC50으로 계산하기 때문이다. 결함을 고치면 이 벡터도 바뀌어야 하며, 그 변경은 릴리스 노트에 동작 변경으로 기록되어야 한다. 벡터를 별도 문서가 아니라 코드 옆에서 버전 관리해야 하는 이유가 여기에 있다.
SDK의 모든 입력 검사는 코드 BIO007(INVALID_PARAMETERS)의 BiopharmaError를 던진다. BiopharmaErrorCode는 BIO001부터 BIO010까지 열 개의 코드를 정의하지만, 버전 1.0에서 나머지 아홉 개는 어떤 함수도 던지지 않으므로 시험에서 이를 기대할 수 없다. BIO007을 던지는 것이 확인된 입력은 다음과 같다.
calculateBindingAffinity: 항체 또는 항원 농도가 0 이하이거나, 복합체 농도가 어느 한 반응물보다 클 때calculatePharmacokinetics: 투여량, 청소율, 분포용적이 0 이하이거나 생체이용률이 0~1을 벗어날 때assessImmunogenicity: ADA 비율이 0~1을 벗어나거나 중증도가 1~10을 벗어날 때calculateKinetics: 속도상수가 0 이하일 때calculatePharmacodynamics: 농도나 Emax가 음수이거나 EC50이 0 이하일 때실패해야 하는데 실패하지 않는 입력도 그만큼 교훈적이다. 복합체 농도가 0이면 kd: Infinity와 bindingStrength: "very-low"가 반환된다. assessImmunogenicity에서 toleranceFactor가 0이면 위험 점수가 무한대가 되고, 내약 계수나 기간 계수가 음수이면 점수가 음수가 되어 코드는 이를 low로 분류한다. 두 계수 모두 검증되지 않는다. 적합성 시험 세트는 이 경우들을 문서화된 기지(旣知) 동작으로 포함해야 하며, 이에 대한 검증을 추가한 구현은 이를 의도적 편차로 선언해야 한다.
패키지 매니페스트는 ts-jest 프리셋과 **/__tests__/**/*.ts 또는 *.test.ts 시험 패턴으로 jest를 설정하고, test, test:coverage, typecheck 스크립트를 정의한다. 그러나 소스 트리에는 시험 파일이 하나도 없어서, 지금은 이 스크립트들이 실행할 대상이 없다. 최소한의 출발점은 다음과 같다.
// src/__tests__/golden.test.ts
import { BiopharmaSDK, BiopharmaErrorCode } from '../index';
const sdk = new BiopharmaSDK();
describe('부록 A 골든 벡터', () => {
test('A.1 결합 친화도는 high가 아니라 moderate', () => {
const r = sdk.calculateBindingAffinity({
antibodyConc: 1e-9, antigenConc: 10e-9, complexConc: 0.9e-9,
} as any);
expect(r.kd).toBeCloseTo(1.0111e-9, 13);
expect(r.fractionBound).toBeCloseTo(0.9, 12);
expect(r.bindingStrength).toBe('moderate');
});
test('A.2 약동학', () => {
const r = sdk.calculatePharmacokinetics({
dose: 100, bioavailability: 1, clearance: 0.2, volumeOfDistribution: 5,
} as any);
expect(r.auc).toBeCloseTo(500, 9);
expect(r.halfLife).toBeCloseTo(17.325, 9);
expect(r.dosingInterval).toBe(24);
});
});
describe('음성 벡터', () => {
test('복합체가 항체보다 많으면 BIO007', () => {
let err: any;
try {
sdk.calculateBindingAffinity({
antibodyConc: 1e-9, antigenConc: 10e-9, complexConc: 2e-9,
} as any);
} catch (e) {
err = e;
}
expect(err?.code).toBe(BiopharmaErrorCode.INVALID_PARAMETERS); // 'BIO007'
});
});
as any 캐스트는 입력 인터페이스가 선언한 단위, 측정법 같은 필드를 이 예제가 생략했기 때문에 들어가 있다. 실제 시험에서는 완전한 객체를 구성해야 한다.
시험을 돌리기 전에 코드가 컴파일되어야 한다. 버전 1.0 소스에 TypeScript 컴파일러(버전 5.9)를 strict·no-emit 모드로 실행하면 44건의 오류가 보고되는데, 모두 중복 export다. types.ts는 각 타입을 선언하는 자리에서 export한 뒤 끝의 export type { … } 블록에서 다시 export하고, BIOPHARMA_CONSTANTS, BiopharmaErrorCode, BiopharmaError도 한 번 더 export한다. index.ts는 이미 export class BiopharmaSDK로 선언해 놓고 끝에서 export { BiopharmaSDK }를 또 쓴다. 오류 코드는 TS2323("Cannot redeclare exported variable")과 TS2484("Export declaration conflicts with exported declaration")다. 수정은 순전히 기계적이다. types.ts 끝의 export 블록 두 개와 index.ts의 중복 줄을 지우면 된다. 그러고 나면 함수가 실행되어 위 표의 벡터를 그대로 만들어 낸다. 그 전까지는 typecheck와 build 스크립트가 성공하리라 기대할 수 없다. 수정하지 않은 소스에 대해 "WIA 시험 세트를 돌렸다"고 주장하는 사람이 있다면 어떻게 돌렸는지 물어볼 일이다.
셸 도구 cli/wia-bio-016.sh는 수식을 bc -l에 파이프로 넘겨 계산한다. 그런데 GNU bc는 과학적 표기법을 받아들이지 않아서 1e-9라는 입력은 구문 오류가 된다. calc-affinity와 validate의 기본값이 바로 이 표기로 쓰여 있고, 스크립트 내부의 분류 임계값(1e-12, 100e-12, MIN_THERAPEUTIC_KD=10e-9)도 마찬가지이며, 도움말의 예제도 이 표기를 쓴다. 결과는 충돌보다 나쁘다. 스크립트가 멈추지 않기 때문이다.
$ bash cli/wia-bio-016.sh calc-affinity 2>&1 | grep -E 'error|Kd|strength'
(standard_in) 1: syntax error
...
Dissociation constant (Kd): 0.00e+00 M
Binding strength: LOW
$ bash cli/wia-bio-016.sh validate --affinity 1e-6 2>/dev/null | grep -E 'Affinity|VALID'
Affinity: Within therapeutic range
Candidate is VALID for development
$ echo $?
0
bc 호출이 실패할 때마다 빈 문자열이 나오고, (( )) 안의 빈 산술식은 거짓으로 평가되며, 실패가 명령 치환 안에서 일어나므로 set -e도 스크립트를 멈추지 않는다. 그래서 친화도 명령은 Kd를 0으로 출력하면서 "LOW"라고 판정하고, 검증 명령은 스크립트 자신의 10 nM 임계값보다 천 배 약한 1 µM 결합체를 "치료 범위 이내"로 받아들인다. 입력을 소수 형식(0.000000001)으로 넘기면 표시되는 Kd는 바로잡히지만 비교는 여전히 틀린다. 임계값이 과학적 표기법으로 남아 있기 때문이다. calc-pk, immunogenicity, simulate-pkpd 명령은 일반 소수를 쓰므로 올바르게 동작한다.
견고한 해법은 지수가 들어올 수 있는 곳에서 bc를 빼는 것이다. awk는 수식에서도, -v로 넘긴 변수에서도 과학적 표기법을 그대로 해석한다.
calc() { awk "BEGIN { printf \"%.6e\", $1 }"; }
lt() { awk -v a="$1" -v b="$2" 'BEGIN { exit !(a + 0 < b + 0) }'; }
kd=$(calc "($ab - $cx) * ($ag - $cx) / $cx") # 기본값이면 1.011111e-09
if lt "$kd" 1e-12; then echo "ultra-high"
elif lt "$kd" 100e-12; then echo "very-high"
elif lt "$kd" 1e-9; then echo "high"
elif lt "$kd" 10e-9; then echo "moderate"
else echo "low"
fi
각 입력을 bc에 넘기기 전에 printf '%.20f'로 한 번 정규화하는 대안도 있지만, 이 방법은 소수점 20번째 자리 뒤의 숫자를 조용히 버린다. 값이 작아질수록 정밀도가 줄어(펨토몰 값은 유효숫자가 여섯 자리만 남는다) 10−20보다 작은 값은 0이 되며, 스크립트 안의 임계값도 따로 십진 표기로 고쳐 써야 한다. 어떤 해법을 택하든 CLI 시험에는 도움말 예제를 글자 그대로 포함해야 하며, 종료 코드만으로는 믿을 수 없으므로 표준 오류에 출력이 하나라도 있으면 실패로 처리해야 한다.
WIA-BIO-016 명세서 자체는 적합성 요구사항을 정의하지 않는다. spec/PHASE-1부터 PHASE-4까지 네 파일에는 시험 벡터, 증거 패키지, 버전 관리, 상호운용 프로파일에 관한 부속서가 있지만, 이는 WIA 공통 템플릿이다. 네 파일에 사실상 글자 그대로 반복되고, 파일 머리에는 다른 표준 식별자와 날짜가 적혀 있다. 부속서가 기술하는 발상 자체는 건전하고 쓸모가 있어 이 절에서도 활용하지만, 인용할 때는 WIA-BIO-016 고유의 요구사항이 아니라 WIA 표준 공통의 비규범 부속서로 밝혀야 한다.
supported, partial(사유 포함), excluded(정당화 포함)로 표시하는 JSON 문서로, 부분 구현이 완전 구현으로 오인되지 않게 한다.이 요소들을 이 표준에 맞추려면 세 가지 조정이 필요하다. 첫째, WIA-BIO-016에는 번호가 붙은 요구사항이 없으므로 매니페스트가 참조할 대상이 없다. 구현자가 식별자를 직접 정해야 하며, 가장 덜 자의적인 방법은 명세서의 절과 SDK 함수에 식별자를 묶는 것이다. 둘째, WIA-BIO-016은 네트워크 API를 정의하지 않으므로 OpenAPI 문서는 해당 사항이 없다. 지어낸 인터페이스로 채우지 말고 사유와 함께 excluded로 표시해야 한다. 셋째, 부속서가 가리키는 벡터 디렉터리(tests/phase-vectors/…)는 소스 트리에 존재하지 않는다. 이 장의 벡터는 제안일 뿐 공개된 WIA 벡터 세트가 아니다.
{
"standard": "WIA-BIO-016",
"specVersion": "1.0.0",
"implementation": "example-lims-biopharma-module",
"implementationVersion": "2.3.1",
"requirements": [
{ "id": "BIO016-S4.1-KD", "status": "supported",
"evidence": "vectors/a1-affinity.json" },
{ "id": "BIO016-S5.2-RO", "status": "partial",
"reason": "수용체 점유율을 EC50 대신 Kd로 계산(SDK 1.0과의 의도적 편차)" },
{ "id": "BIO016-S6.2-IRS", "status": "supported",
"evidence": "vectors/a3-irs.json",
"note": "선별용 휴리스틱으로만 사용하며 사용자에게 임상 위험으로 표시하지 않음" },
{ "id": "BIO016-CLI", "status": "excluded",
"reason": "셸 CLI를 배포하지 않음" },
{ "id": "WIA-ANNEX-OPENAPI", "status": "excluded",
"reason": "WIA-BIO-016은 네트워크 API를 정의하지 않음" }
]
}
이 예의 요구사항 식별자는 설명을 위해 지어낸 것이다. 중요한 것은 모양이다. 각 줄은 검증 가능한 동작 하나를 지목하고, 상태를 밝히며, 증거를 가리키거나 공백의 이유를 설명한다. 모든 줄이 supported이면서 어느 줄에도 증거가 없는 매니페스트는 매니페스트가 아니라 홍보물이다.
이 책이 제안한 수정 가운데 여럿은 관찰 가능한 동작을 바꾼다. 수용체 점유율을 Kd로 계산하는 것, 0 이하의 내약 계수를 거부하는 것, CLI의 비교를 고치는 것이 그렇다. SemVer에서 이들은 모두 호출자가 보는 것을 바꾸는 변경이며, 메이저인지 마이너인지는 기존 호출자가 이전 출력에 의존할 수 있었느냐에 달려 있다. 반면 중복 export를 고치는 것은 패키지가 컴파일되고 나면 호출자가 관찰할 수 있는 것을 아무것도 바꾸지 않으므로 패치다. 증거 패키지에는 벡터를 어느 SDK 버전, 가능하면 어느 패키지 다이제스트에 대해 실행했는지를 밝혀서, 업그레이드 뒤에도 합격 매트릭스가 여전히 유효한지 감사자가 판단할 수 있게 해야 한다.
마지막 경계는 실무에서 가장 자주 넘어서는 경계다. WIA 적합성 주장은 소프트웨어에 관한 진술이다. 특정 구현이 특정 버전에서, 선언된 입력 집합에 대해 규정된 출력을 내며, 어디서 편차가 있는지를 정직하게 밝힌다는 진술이다. 의약품, 제조소, 임상 결과에 관한 진술이 아니다.
| 적합성 주장이 말할 수 있는 것 | 적합성 주장이 말할 수 없는 것 |
|---|---|
| 이 모듈 버전 2.3.1은 매니페스트에 나열한 WIA-BIO-016 골든 벡터를 재현한다 | 이 모듈로 분석한 제품은 안전하거나 유효하거나 허가받을 수 있다 |
| 이 요구사항들은 지원하고, 이것들은 밝힌 사유로 부분 지원하며, 이것들은 제외한다 | 번호 붙은 요구사항이 없는 표준에 "완전히 준수"한다 |
| 증거 패키지는 이 빌드에서 생성되었고 이 키로 서명되었다 | 시스템이 21 CFR Part 11 또는 EU Annex 11에 따라 밸리데이션되었다 |
| 면역원성 위험 점수를 WIA 정의대로 계산한다 | 그 점수가 임상 면역원성을 예측하거나 어떤 규제기관에서 인정된다 |
| 바이오시밀러성 함수가 WIA의 속성별 휴리스틱을 적용한다 | 제품이 FDA 또는 EMA 기준에 따라 바이오시밀러이거나 상호교체 가능하다 |
| — | 제조사가 GMP 인증을 받았거나 제품이 규제 허가를 받았다 |
이 경계가 형식 이상의 의미를 갖는 이유는 두 가지다. 첫째, WIA-BIO-016의 출력 가운데 여럿이 규제 결론처럼 보인다. assessBiosimilarity라는 함수는 "similar"라고 읽히는 필드를 돌려주고, validateDrugCandidate는 개발 기간, 비용, 성공 확률을 돌려준다. 제6장에서 보았듯 이들은 코드에 박아 넣은 휴리스틱이며, 인증서 옆에 나란히 제시하면 독자가 이를 검증된 값으로 받아들이게 된다. 둘째, 이런 도구의 사용자는 규제 산업에서 일하며, 그곳에서 라벨이나 판매 문서의 오해를 부르는 주장은 그것을 믿은 사람들에게 실제 결과를 남긴다. 할 수 있는 것보다 적게 주장한 업체는 조금 불편할 뿐이지만, 뒷받침할 수 있는 것보다 많이 주장한 업체는 고객을 오도한 것이다.
장 요약. WIA-BIO-016으로 쌓은 증거가 최종적으로 읽히는 곳은 CTD, 특히 모듈 3이다. 모델은 특성분석, 품질 속성, 안정성, 동등성을 비교적 잘 다루지만 기준, 표준품, 용기·마개, 바이러스 안전성은 구현자에게 맡긴다. SDK는 데이터 무결성 기능이 없는 순수 계산의 집합이므로 Part 11과 Annex 11의 의무는 이를 둘러싼 시스템이 진다. 버전 1.0 소스를 시험하면, 중복 export를 지우기 전까지 타입 검사를 통과하지 못하는 패키지, 실제로 쓰이는 오류 코드가 하나뿐인 점, 검증되지 않는 경계 사례, 그리고 과학적 표기법 입력에서 종료 코드 0과 함께 틀린 답을 조용히 내놓는 CLI가 드러난다. 적합성 증거는 WIA 공통 템플릿을 정직하게 맞춰 따르면 되고, 인증서는 증거가 보여 주는 것만 주장해야 한다.
assessBiosimilarity, predictShelfLife, calculatePharmacokinetics의 출력은 각각 CTD의 어느 항목에 들어가는가? 마지막 것이 모듈 3에 들어가지 않는 이유는 무엇인가?RegulatoryPathway를 규제기관, 법적 경로, 신속 심사 제도로 나누어야 하는 이유는 무엇인가?validate --affinity 1e-6이 종료 코드 0과 함께 유효한 후보를 반환하는 이유는 무엇이며, 0.000001을 넘겨도 해결되지 않는 이유는 무엇인가?