제1장에서는 대기 CO2 직접 포집(DAC)의 주장이 그 뒤에 있는 데이터만큼만 믿을 수 있다는 점을 살펴보았다. 지하로 들어간 1톤의 이산화탄소 제거(CDR)는 눈으로 보거나 무게를 달거나 검사할 수 없으므로, 구매자·검증기관·등록부가 실제로 받는 것은 레코드의 사슬뿐이다. 이 장은 그 사슬을 맨 아래부터 쌓기 시작한다. 여기서 다루는 것은 플랜트 자체와 그 플랜트가 분 단위로 하는 일을 기술하는 세 가지 레코드, 곧 시설 정보(Facility Information) 레코드, 실시간 포집(운영) 레코드, 흡착제 물질(Sorbent Material) 레코드이다. 포집된 CO2가 그 뒤에 어떻게 되는지를 기술하는 저장 이벤트, 검증 보고서, 탄소제거 크레딧은 제3장에서 이어서 다룬다.
이 장의 근거는 WIA 대기 CO2 직접 포집 표준(명세 식별자 WIA-ENE-050, 슬러그 direct-air-capture)의 PHASE-2-DATA 문서와, PHASE-1-FOUNDATION §5.1의 측정 요건이다. 스키마는 JSON Schema draft 2020-12로 작성되어 있다. 이 장은 스키마를 통째로 옮겨 싣지 않는다. 대신 의미를 담은 필드를 차례로 짚고, 명세가 모호한 지점을 드러내며, JSON Schema만으로는 표현할 수 없어 실제 검증기가 추가로 해야 하는 검사를 덧붙인다. 이 장의 모든 예시 값은 가상의 회사가 운영하는 가상의 플랜트 dac-example-reykjanes-01의 것이다. 모두 설명을 위한 예시이며 실존 시설의 측정값이 아니다.
PHASE-2-DATA §1.2는 DAC 데이터를 일곱 범주로 나눈다. 각 범주를 감사인이 던질 법한 서로 다른 질문에 대한 답으로 읽으면 이해가 쉽다.
| # | 범주 | 답하는 질문 | 명세상 위치 | 이 책에서 다루는 곳 |
|---|---|---|---|---|
| 1 | 시설 데이터 | 어떤 플랜트이며 무엇을 하도록 설계되었는가? | 시설 정보 스키마(§2) | 이 장 2.2 |
| 2 | 운영 데이터 | 플랜트가 지금 무엇을 하고 있는가? | 실시간 포집 스키마(§3) | 이 장 2.3 |
| 3 | 흡착제 데이터 | 무엇이 포집을 하며, 얼마나 닳았는가? | 흡착제 물질 스키마(§4) | 이 장 2.4 |
| 4 | 에너지 데이터 | 에너지를 얼마나, 어디서 가져와 썼는가? | 독립 스키마 없음: 포집 레코드의 energy 블록과 시설 레코드의 energySource | 2.2, 2.3, 전력망 연계는 제7장 |
| 5 | 저장 데이터 | CO2는 어디로 갔는가? | 저장 이벤트 스키마(§5) | 제3장 |
| 6 | 검증 데이터 | 누가 수치를 확인했고 무엇을 발견했는가? | 검증 보고서 스키마(§6) | 제3장, 제6장 |
| 7 | 탄소 크레딧 데이터 | 무엇이 누구에게 팔렸고, 폐기되었는가? | 탄소 크레딧 스키마(§7) | 제3장, 제6장 |
네 번째 행에 주목할 필요가 있다. 명세는 에너지를 별도 범주로 나열하지만 독립된 에너지 스키마는 정의하지 않는다. 에너지 정보는 시설 레코드의 설계값 energySource.energyPerTonCO2와 모든 포집 레코드의 energy 블록에 담긴 측정값으로 나뉘어 있다. 이 구조로도 운영은 가능하다. 그러나 시간 단위의 에너지 출처(예를 들어 특정 전력구매계약에서 온 비율)를 남기고 싶은 구현자는, 표준이 전용 레코드를 추가하기 전까지 확장 필드에 이를 실어야 한다. 이 문제는 제7장에서 전력망 연계를 다룰 때 다시 살펴본다.
명세는 일곱 범주 모두에 적용되는 다섯 가지 설계 원칙을 제시한다. 실무에서는 각각이 다음과 같은 공학적 결정으로 이어진다.
@context를 붙인다. 교환 단위는 레코드이고, 규범 형식에 CSV 전용 경로는 없다.additionalProperties: false가 설정되어 있지 않으므로 추가 필드도 검증을 통과한다. 편리하지만 위험하다. 철자가 틀린 필드명(예: captureRatePerModul)도 아무 경고 없이 받아들여진다. 이를 잡는 방법은 2.7절에서 설명한다.facilityId, sorbentId 같은 식별자는 참조를 따라가고 레코드 유형을 넘나들며 결합할 수 있도록 설계된다. 패턴 검사를 거친 안정적인 ID가 있어야 나중에 크레딧 하나를 그것을 만들어 낸 포집 레코드까지 거슬러 추적할 수 있다.시설 레코드는 다른 모든 레코드가 가리키는 기준점이다. 이 레코드는 모듈이 추가되거나, 저장 협력사가 바뀌거나, 플랜트가 정비에 들어갈 때처럼 드물게만 바뀐다. 변경이 생기면 기존 레코드를 덮어쓰지 말고 새 버전을 만들어야 한다. 과거에 발행된 검증 보고서는 발행 당시 유효했던 시설 기술을 계속 가리킬 수 있어야 하기 때문이다.
최상위 필수 속성은 facilityId, name, location, technology, capacity의 다섯 개이다. 그 하위 객체 안에서 다시 다섯 필드가 필수이다. location.latitude, location.longitude, location.country, technology.type, capacity.annualTonsCO2가 그것이다. 나머지는 모두 선택이므로, 유효한 최소 시설 레코드는 상당히 작다. 어떤 등록부가 프로젝트를 등재하기 전에 저장 협력사와 에너지원을 반드시 요구하고 싶다면, 기본 스키마를 고치지 말고 그 위에 프로파일로 요구사항을 표현해야 한다.
식별자는 ^dac-[a-z0-9-]+$ 패턴을 만족해야 한다. 즉 dac-라는 고정 접두어 뒤에 소문자, 숫자, 하이픈만 올 수 있다. 대문자, 밑줄, 마침표는 거부된다. 패턴은 내부 구조를 강제하지 않으므로 조직 안에서 하나의 규칙을 정해 두는 편이 좋다. dac-<운영사>-<사이트>-<nn> 형태가 쓰기 편하다. 이 ID는 모든 포집 레코드, 저장 이벤트, 크레딧이 참조하므로 시설이 해체된 뒤에도 절대 재사용해서는 안 된다.
location.country는 ISO 3166-1 alpha-2 코드(IS, US, KR처럼 대문자 두 글자)를 쓴다. 스키마의 패턴 ^[A-Z]{2}$는 형태만 보므로 XX도 통과한다. 엄격한 검증기는 실제 코드 목록과 대조해야 한다. 위도와 경도는 각각 ±90°, ±180° 범위로 제한된다. 스키마는 측지 기준계를 명시하지 않는다. 사실상 WGS 84가 쓰이므로 구현 프로파일에 이를 적어 두어야 한다. altitude는 해발 미터 단위이다.
시설 레코드의 의미 대부분은 다음 네 열거형에 담겨 있다.
| 필드 | 허용 값 | 구현 메모 |
|---|---|---|
technology.type | solid-sorbent-dac, liquid-solvent-dac, electrochemical-dac, hybrid-dac | 어떤 하위 블록이 의미를 갖는지를 결정한다. 액체 용매 플랜트에는 모듈별 흡착제 포화도가 없으므로, 포집 레코드의 sorbent 블록은 0으로 채우지 말고 아예 생략해야 한다. |
operational.status | operational, testing, maintenance, decommissioned | 시설 수준의 생애주기 상태이다. 현재 사이클 단계를 나타내는 포집 레코드의 status.operationalState와 혼동하지 말아야 한다. |
storage.permanence | permanent, >10000-years, >1000-years, varies | 표준이 정한 표기(label)일 뿐 과학적 결론이 아니다. 실제로 인정되는 영구성은 저장 경로와 등록부 방법론이 결정한다(제3장, 제6장). |
operational.maintenanceCycle | weekly, monthly, quarterly, annually | 계획된 예방 정비 주기이다. 포집 레코드에 maintenance가 얼마나 자주 나타날지에 대한 기대치를 정하고, 가동률을 설명하는 근거가 된다. |
알아 둘 열거형이 두 개 더 있다. energySource.type은 geothermal, solar, wind, hydro, nuclear, natural-gas, grid-mix 가운데 하나이고, storage.method는 mineralization, geological-storage, ocean-storage, utilization, hybrid 가운데 하나이다. 둘 다 단일 값이므로, 지열과 계통 전력을 함께 쓰는 플랜트는 이를 정확히 표현할 수 없다. 이런 경우에는 주된 에너지원을 가장 잘 나타내는 값을 고르고, energySource.renewable을 사실대로 설정한 뒤, 세부 구성은 확장 객체에 넣는다. 순제거량 계산에 중요한 것은 이 표기가 아니라 포집 레코드에 담긴 측정 에너지이다.
capacity 블록은 설계 용량을 기록한다. annualTonsCO2(연간 톤), modules(모듈 수), captureRatePerModule(모듈당 하루 CO2 톤)이다. 세 값은 간단한 산술로 연결되며, 이 관계가 맞지 않는 시설 레코드는 거부해야 한다.
annualTonsCO2 ≈ modules × captureRatePerModule × 365
이 값은 가동률 100 %를 가정한 정격(nameplate) 수치이다. 실제 연간 포집량은 이보다 낮다. operational.uptime이 1보다 작고, 포집 속도가 외기 조건에 따라 변하기 때문이다. 정격과 측정값은 분리해서 다뤄야 한다. 시설 레코드는 플랜트가 무엇을 하도록 지어졌는지를 말하고, 포집 레코드의 합계는 플랜트가 실제로 무엇을 했는지를 말한다. 둘을 비교하는 등록부는 정격보다 꾸준히 많이 주장하는 시설을 가려낼 수 있다. 이는 단위 오류나 이중 보고의 전형적인 징후이다.
energySource.energyPerTonCO2 역시 설계값이며 단위는 CO2 톤당 MWh이다. 이에 대응하는 측정값은 포집 레코드의 energy.specificEnergy이다. 둘은 단위는 같지만 의미는 다르므로, 대시보드에서 같은 계열로 섞어 그려서는 안 된다.
다음 레코드는 이 책 전체에서 사용하는 가상의 플랜트를 기술한다. 수치는 서로 모순되지 않도록 골랐으며(12개 모듈 × 0.25 t/일 × 365 = 1,095 t/년), 업계의 대표값이 아니라 예시이다.
{
"facilityId": "dac-example-reykjanes-01",
"name": "Example Reykjanes DAC Plant 1 (fictional)",
"location": {
"latitude": 63.85,
"longitude": -22.60,
"altitude": 40,
"country": "IS",
"region": "Example region"
},
"technology": {
"type": "solid-sorbent-dac",
"sorbent": "sorbent-example-amine-b07",
"manufacturer": "Example Capture Systems",
"model": "EX-M250",
"generation": "pilot-2"
},
"capacity": {
"annualTonsCO2": 1095,
"modules": 12,
"captureRatePerModule": 0.25
},
"energySource": {
"type": "geothermal",
"provider": "Example Heat & Power",
"renewable": true,
"energyPerTonCO2": 2.0
},
"storage": {
"method": "mineralization",
"site": "site-example-basalt-01",
"permanence": "permanent"
},
"operational": {
"startDate": "2024-01-10",
"status": "operational",
"uptime": 0.95,
"maintenanceCycle": "monthly"
},
"contacts": {
"operator": "Example Carbon Ltd.",
"email": "ops@example.com",
"website": "https://example.com"
},
"metadata": {
"createdAt": "2024-01-05T00:00:00Z",
"updatedAt": "2026-03-01T00:00:00Z",
"version": "1.0.0"
}
}
몇 가지 세부 사항을 짚어 둔다. 첫째, 스키마에서 technology.sorbent는 자유 문자열이지만, 여기서는 흡착제 물질 레코드(2.4절)의 sorbentId를 담았다. 설명문을 링크로 바꾼 것이며, 연결 데이터 원칙을 실제로 적용한 사례이다. 둘째, storage.depth(여기서는 생략)도 자유 문자열("Storage depth range")이다. "700–2000 m" 같은 문자열은 질의할 수도 검사할 수도 없다. 등록부에서 깊이가 중요하다면 {"minM": …, "maxM": …} 같은 구조화된 확장을 추가하고 문자열은 표시용으로만 둔다. 셋째, metadata.version은 스키마가 아니라 이 레코드의 버전이다. 스키마 버전은 $schema나 프로파일 식별자에 담는다.
시설 레코드가 기준점이라면 포집 레코드는 심장 박동이다. 플랜트는 샘플링 주기마다 이 레코드를 하나씩 내보낸다. PHASE-1-FOUNDATION §5.1.1은 1–5분마다 수집하도록 요구하므로, 시설 하나가 1년에 수십만 건을 만들어 낸다. 시간별 집계, 일일 검증 요약, 저장 인계(custody transfer), 그리고 최종적으로 크레딧에 이르기까지 사슬의 뒷부분은 모두 이 레코드에서 계산된다. 그래서 이 레코드의 일관성이 무엇보다 중요하다.
필수 속성은 timestamp(RFC 3339 / ISO 8601 date-time), facilityId(앞과 같은 패턴), capture, energy의 네 개이다. capture 안에서는 currentRate만 필수이다. 레코드는 여섯 블록으로 구성된다.
capture — currentRate(t/일), 누적값 dailyTotal·monthlyTotal·yearlyTotal(t), efficiency(0–1의 비율), co2Purity(0–100의 백분율).energy — currentConsumption(kW), dailyTotal(MWh), specificEnergy(MWh/t), renewablePercentage(0–100).environmental — ambientCO2(ppm), temperature(°C), humidity(상대습도 0–100 %), pressure(hPa), windSpeed(m/s).sorbent — cycleNumber, saturation(0–1), expectedLifetime(사이클 수), regenerationTemp(°C).airflow — rate(m³/min), pressure(Pa), 접촉기 전후 pressureDrop(Pa).status — operationalState와 alerts 배열.외기 조건이 레코드에 들어가는 이유는 포집 성능이 그 조건에 좌우되기 때문이다. 예를 들어 고체 아민 흡착제는 습도와 온도에 민감하고, 외기 CO2 농도는 처리하는 공기 1세제곱미터당 얻을 수 있는 CO2의 양을 정한다. 나중에 검증기관이 포집 속도의 하락을 발견했을 때, 그것이 고장이나 데이터 조작이 아니라 날씨 때문이었음을 운영자가 보여 줄 수 있게 해 주는 것이 바로 환경 블록이다.
status.operationalState는 adsorption, regeneration, idle, maintenance, error 다섯 값 가운데 하나이다. 고체 흡착제 플랜트에서는 앞의 두 값이 한 사이클 안에서 번갈아 나타난다(제5장에서 이를 상태 기계로 정식화한다). 연속 운전하는 액체 용매 플랜트에서는 공기 접촉과 재생이 서로 다른 설비에서 동시에 일어나므로 이 구분이 덜 자연스럽다. 이런 플랜트는 보통 공기 접촉기가 돌아가는 동안 adsorption을 보고하고, 가동 중단은 시설 수준의 maintenance 상태로 표현한다.
핵심 질문은 이것이 누구의 상태인가이다. 여러 모듈로 이루어진 고체 흡착제 플랜트에서는 일부 모듈이 흡착하는 동안 다른 모듈은 재생하므로, 시설 수준의 단일 상태는 모호하다. 스키마에는 모듈 식별자가 없다. 권장하는 방법은 두 가지이다. 모듈마다 레코드를 하나씩 내보내고 확장 필드에 모듈 ID를 추가하거나, 시설당 레코드 하나를 내보내되 operationalState는 다수 모듈의 상태를 나타내고 모듈별 내역은 확장 필드에 담는 것이다. 어느 쪽을 택하든 합계를 내는 방식이 달라지므로 반드시 프로파일에 적어 두어야 한다.
alerts의 각 항목은 severity(info, warning, error, critical), message, 그리고 자체 timestamp를 갖는다. 경보는 감사 추적의 일부이다. 포집 데이터에서 공백을 발견한 검증기관은 그에 대응하는 경보를 찾는다. 따라서 메시지는 자유 서술만으로 쓰지 말고, 기계가 해석할 수 있는 고정 코드 뒤에 사람이 읽을 설명을 붙이는 형태(예: "HUMIDITY_HIGH: inlet RH above configured limit")가 좋다.
아래 레코드는 가상 시나리오에서 UTC 기준 하루가 시작되고 6시간이 지난 시점에 생성된 것이다. 값은 예시이며, 위 시설 레코드와도, 레코드 내부에서도 서로 맞도록 일부러 골랐다. 2.7절에서 이 값들을 실제로 검사한다.
{
"timestamp": "2026-03-15T06:00:00Z",
"facilityId": "dac-example-reykjanes-01",
"capture": {
"currentRate": 2.9,
"dailyTotal": 0.72,
"efficiency": 0.78,
"co2Purity": 97.2
},
"energy": {
"currentConsumption": 242,
"dailyTotal": 1.45,
"specificEnergy": 2.01,
"renewablePercentage": 100
},
"environmental": {
"ambientCO2": 425,
"temperature": 4.0,
"humidity": 78,
"pressure": 1008,
"windSpeed": 6.2
},
"sorbent": {
"cycleNumber": 3120,
"saturation": 0.62,
"expectedLifetime": 11157,
"regenerationTemp": 98
},
"airflow": {
"rate": 4200,
"pressure": 100800,
"pressureDrop": 180
},
"status": {
"operationalState": "adsorption",
"alerts": [
{
"severity": "info",
"message": "HUMIDITY_HIGH: inlet RH above advisory level",
"timestamp": "2026-03-15T05:52:10Z"
}
]
}
}
여기서 currentRate는 시설 수준으로 표현했다(12개 모듈 전체에서 2.9 t/일로, 정격 3.0 t/일보다 약간 낮다). 명세의 필드 설명("Current capture rate in tons/day")은 이 값이 모듈 단위인지 시설 단위인지 밝히지 않는다. 두 해석의 차이는 모듈 수만큼의 배수이며, 나중에 물질수지를 깨뜨리는 전형적인 조용한 오류가 바로 이것이다. 하나의 의미를 정하고, 프로파일에 기록하고, 검사해야 한다(2.7절). 또한 monthlyTotal과 yearlyTotal은 생략했다. 긴 기간의 누적값은 현장 장비가 보낸 값을 믿기보다 서버에서 저장된 레코드로 계산하는 편이 낫다.
고체 흡착제 플랜트에서 흡착제는 포집을 수행하는 엔진이자 가장 중요한 소모품이다. 흡착제의 상태가 한 사이클에 실어 나를 수 있는 CO2의 양, 톤당 필요한 열, 그리고 (그 자체로 내재 배출을 동반하는) 교체 시점을 결정한다. 흡착제 물질 레코드는 그 이력을 한곳에 모은다.
필수 필드는 세 개이다. ^sorbent-[a-z0-9-]+$ 패턴을 만족해야 하는 sorbentId, type, 그리고 properties 객체이다. type은 solid-amine, liquid-solvent, metal-organic-framework, zeolite, activated-carbon, other 가운데 하나이다. 식별자는 제품군이 아니라 사이트에 설치된 배치(batch)를 가리켜야 한다. 열화는 설치된 배치 단위로 추적되기 때문이다. 제조사의 로트 번호는 별도 필드인 batchNumber에 담는다.
properties 객체는 사양이나 시험으로 확인된 물질의 성질을 기술한다.
maxLoading — 평형 상태에서 물질이 담을 수 있는 최대 CO2 양으로, 흡착제 1 g당 CO2 mmol 단위이다.workingCapacity — 플랜트의 흡착 조건과 재생 조건 사이에서 실제로 오가는 양으로, 역시 mmol/g 단위이다. 재생이 흡착제를 완전히 비우지 못하고, 흡착도 대개 완전 평형 전에 멈추므로 항상 maxLoading보다 작다. 플랜트 경제성과 물질수지에서 중요한 값은 workingCapacity이다.regenerationTemp(°C)와 regenerationPressure(bar) — 설계 재생 조건이다. 제1장에서 설명했듯이 온도·진공 스윙 흡착(TVSA)을 쓰는 아민 흡착제의 재생 온도는 보통 80–120 °C 범위이다.cycleLifetime — 교체 전까지 기대되는 사이클 수.degradationRate — 사이클당 용량 손실(비율).selectivity — CO2/N2 선택도 비.lifecycle 객체는 설치된 배치를 시간에 따라 추적한다. currentCycles, currentCapacity(mmol/g), degradation(0–1의 누적 비율), remainingLifetime(사이클 수)이다. installDate, replacementDate와 함께 이 값들이 있어야 감사인이 플랜트 출력 저하를 설명되지 않는 손실이 아니라 흡착제 노화와 연결할 수 있다.
명세의 흡착제 예시 레코드에는 적재량, 수명, 선택도에 구체적인 숫자가 적혀 있다. 이 숫자들은 자리표시자로 취급해야 한다. 공인된 성능 기준값이 아니며, 실제 값은 물질, 공정 조건, 시험 방법에 따라 크게 달라진다. 자체 시험값을 보고하고, 시험 방법은 확장 필드에 인용한다.
스키마는 사이클당 열화율을 제공하지만 그것을 어떻게 적용할지는 말하지 않는다. 합리적인 모델이 두 가지 있고, 결과는 조금 다르다.
기하(복리) 모델: C(N) = C0 × (1 − r)^N
선형 모델: C(N) = C0 × (1 − r × N)
C0 = 초기 용량(mmol/g), r = degradationRate, N = 사이클 수
예시로 workingCapacity가 1.2 mmol/g이고 degradationRate를 사이클당 2 × 10⁻⁵로 가정한 배치가 3,120 사이클을 돈 경우를 보자.
| 항목 | 기하 모델 | 선형 모델 |
|---|---|---|
| 3,120 사이클 후 잔존 비율 | (1 − 0.00002)^3120 ≈ 0.9395 | 1 − 0.0624 = 0.9376 |
currentCapacity(mmol/g) | 1.2 × 0.9395 ≈ 1.127 | 1.2 × 0.9376 ≈ 1.125 |
degradation(누적) | ≈ 0.0605 | 0.0624 |
| 초기 용량의 80 %에 이르는 사이클 수 | ln 0.8 / ln(1 − 0.00002) ≈ 11,157 | 0.2 / 0.00002 = 10,000 |
N = 3,120일 때 remainingLifetime | ≈ 8,037 | 6,880 |
수천 사이클 정도에서는 두 모델의 차이가 거의 없지만, 수명 종료 예측은 천 사이클 이상 벌어진다. 여기서 쓴 80 % 수명 종료 기준도 예시일 뿐이며, 표준은 이를 정의하지 않는다. 따라서 적합한 구현은 세 가지를 프로파일에 적어 두어야 한다. 어떤 모델을 쓰는지, 열화율이 어느 용량(maxLoading 또는 workingCapacity)에 적용되는지, 어떤 기준으로 수명 종료를 정의하는지이다. 그렇지 않으면 같은 레코드를 읽는 두 시스템이 서로 다른 remainingLifetime을 계산하게 된다. 포집 레코드의 sorbent.expectedLifetime(2.3절 예시의 11,157 사이클) 역시 별도의 상수가 아니라 흡착제 레코드와 같은 계산에서 나와야 한다.
용량을 CO2 질량으로 환산해 두면 흡착제 레코드와 포집 레코드를 이어 볼 수 있어 유용하다. CO2의 몰질량은 약 44.01 g/mol이므로 1 mmol/g은 흡착제 1 g당 CO2 약 44 mg에 해당한다. 1.127 mmol/g이면 작동 사이클 한 번에 흡착제 1 g당 CO2 약 49.6 mg, 즉 흡착제 1톤당 CO2 약 0.050톤을 옮긴다. 예를 들어 어떤 모듈이 하루 2 사이클을 완주한다고 가정하면(명세의 예시 흡착 구간만 해도 8–12시간이다. 제5장 참조), 하루 0.25톤을 포집하는 데 그 모듈에 필요한 흡착제는 약 0.25 ÷ (2 × 0.0496) ≈ 2.5톤이다. 하루 사이클 수는 명세 값이 아니라 여기서 둔 가정이다. 그래도 이 방법은 검증기관이 보고된 흡착제 보유량의 타당성을 빠르게 점검하는 수단이 된다.
스키마는 단위를 description 문자열에만 적어 두며, 검증기는 이 문자열을 무시한다. 더 곤란한 것은 같은 종류의 양이 곳에 따라 다른 척도를 쓴다는 점이다. 다음 표는 이 장의 세 레코드 유형에서 오류를 가장 자주 일으키는 필드를 모은 것이다.
| 필드 | 명세상 단위/척도 | 권장 UCUM 코드 | 함정 |
|---|---|---|---|
capture.efficiency | 비율 0–1 | 1 | 0.78 대신 78을 보내면 검증에 실패한다(maximum 1). 다행인 경우이다. |
capture.co2Purity | 백분율 0–100 | % | 97.2 대신 0.972를 보내면 검증을 통과하고, 순도 0.972 %라는 뜻이 된다. |
operational.uptime | 비율 0–1(설명에는 "percentage"라고 적힘) | 1 | 설명과 범위가 어긋난다. 범위를 따른다. |
sorbent.saturation, lifecycle.degradation | 비율 0–1 | 1 | 재생 개시 조건(제5장)은 이 비율을 기준으로 표현된다. |
energy.renewablePercentage, environmental.humidity | 백분율 0–100 | % | co2Purity와 같은 조용한 통과 위험이 있다. |
environmental.pressure | hPa | hPa | 해수면에서 약 1,000이다. |
airflow.pressure, airflow.pressureDrop | Pa | Pa | 절대압이면 약 100,000으로, 바로 위 필드와 100배 차이가 난다. |
properties.regenerationPressure | bar | bar | 절대압인지 게이지압인지 명시되지 않았다. 진공 재생에서는 이 구분이 중요하다. |
capture.currentRate, captureRatePerModule | t/일 | t/d | 모듈 단위인지 시설 단위인지 프로파일에서 고정해야 한다(2.3절). |
capture.dailyTotal | t(UTC 00:00부터 누적) | t | "하루"는 현지 플랜트 시각이 아니라 UTC 기준이어야 한다. |
energy.currentConsumption | kW | kW | 에너지가 아니라 전력이다. 합산하면 안 된다. |
energy.dailyTotal | MWh | MW.h | capture.dailyTotal과 이름은 같지만 단위는 다르다. |
energy.specificEnergy, energyPerTonCO2 | MWh/t | MW.h/t | 전기와 열이 한데 묶여 있다. 아래 설명 참조. |
environmental.ambientCO2 | ppm | [ppm] | 몰분율(µmol/mol)임을 밝혀야 하며, 건조 공기 기준인지 습윤 공기 기준인지가 중요하다. |
maxLoading, workingCapacity, currentCapacity | mmol/g | mmol/g | 제품 자료에 따라 wt %나 g/kg을 쓰기도 한다. 기록 전에 환산한다. |
airflow.rate | m³/min | m3/min | 실제 체적인지 표준(환산) 체적인지 명시되지 않았다. |
이 표에는 구조적인 빈틈 두 가지가 숨어 있다. 첫째는 비율과 백분율의 혼동이다. 백분율 필드에 비율 값을 넣어도 0.972 같은 값은 0–100 범위 안에 있으므로 검증을 통과한다. 반대 방향의 오류는 잡히지만 이 방향은 잡히지 않는다. 둘째는 열과 전기의 구분이다. 고체 흡착제 플랜트에서 에너지 수요의 대부분은 재생에 쓰이는 저온 열이고, 송풍기와 진공 펌프는 전기를 쓴다. MWh/t 하나로는 어느 쪽이 얼마인지 알 수 없는데, 두 에너지의 배출계수는 대개 크게 다르다. 순제거량(제1장 1.4절)을 계산하려면 둘을 따로 알아야 한다.
표준은 단위 주석 방식을 정의하지 않으므로, 이 책은 표준과 충돌하지 않는 방식을 하나 권장한다. 스키마 수준에서는 각 수치 속성 옆에 UCUM(Unified Code for Units of Measure) 코드를 쓰는 "x-unit": "MW.h/t" 같은 사용자 정의 키워드를 추가한다. JSON Schema 2020-12는 알 수 없는 키워드를 주석(annotation)으로 허용한다. 레코드 수준에서는 에너지를 에너지원별로 나누는 확장 객체를 추가한다. 예를 들면 다음과 같다.
"energy": {
"currentConsumption": 242,
"dailyTotal": 1.45,
"specificEnergy": 2.01,
"renewablePercentage": 100,
"x-breakdown": {
"electricityMWh": 0.38,
"heatMWh": 1.07,
"heatSource": "geothermal",
"unitsNote": "MWh, 00:00Z to timestamp; illustrative values"
}
}
기본 스키마가 추가 속성을 허용하므로 이 확장은 표준 스키마를 바꾸지 않고도 검증을 통과하며, 이를 모르는 소비자는 그냥 무시한다. 어떤 확장이든 지켜야 할 규칙은 기본 필드와 모순되어서는 안 된다는 것이다. 이 예에서는 electricityMWh + heatMWh가 dailyTotal과 같아야 한다.
스키마는 유효한 레코드가 어떤 모양인지를 말하고, PHASE-1-FOUNDATION §5.1은 그 안의 숫자를 어떻게 만들어 내고 보관해야 하는지를 말한다. JSON 문서를 증거로 바꾸는 것은 바로 이 요건들이다.
표준은 네 가지 주기를 정한다. 운영 데이터는 1–5분마다 수집하고, 집계 데이터는 시간마다 보고하며, 검증을 위해 일일 요약을 만들고, 공개 보고에는 월간 집계를 쓴다. 이 장의 레코드로 말하면 포집 레코드가 1–5분 주기의 산출물이다. 시간별·일별 산출물은 이 레코드에서 계산하며, 계산된 값마다 어떤 원시 레코드를 포괄하는지(시작·종료 타임스탬프와 레코드 수)를 밝혀 검증기관이 다시 계산할 수 있게 해야 한다. 누락 구간은 보간으로 메우지 말고 공백으로 드러나야 한다. 메웠다면 그 사실을 플래그로 표시해야 하며, 이를 담는 품질 플래그는 제5장에서 설명한다.
표준은 측정량마다 정확도 목표와 신뢰수준을 정한다.
| 항목 | 정확도 | 신뢰수준 | 적용되는 레코드 필드 |
|---|---|---|---|
| 포집 CO2 | ±5 % | 95 % | capture.currentRate, capture.dailyTotal |
| 에너지 소비 | ±3 % | 95 % | energy.dailyTotal, energy.currentConsumption |
| 흡착제 질량 | ±2 % | 99 % | 흡착제 보유량(확장 필드, 기본 필드 아님) |
| 외기 CO2 | ±10 ppm | 90 % | environmental.ambientCO2 |
| 온도 | ±1 °C | 95 % | environmental.temperature, sorbent.regenerationTemp |
| 압력 | ±5 % | 95 % | environmental.pressure, airflow.* |
스키마에는 개별 값의 불확도를 담을 필드가 없다. 제6장에서 이 불확도를 검증량으로 전파하게 되므로, 시설별·계측기별로 교정 성적서와 그 불확도를 기록하고 데이터에서 그 교정 레코드를 참조하는 것이 좋은 실무이다. 추적 가능한 계측기가 뒤에 없는 포집 값은 소수점 아래 자릿수가 아무리 많아도 ±5 % 수준을 방어할 수 없다. 또한 행마다 신뢰수준(90 %, 95 %, 99 %)이 다르므로, 공통 기준으로 맞추기 전에는 이 수치들을 그대로 합칠 수 없다.
표준은 최소 보존 기간을 정한다. 원시 데이터는 7년, 집계 데이터는 30년, 검증 기록과 블록체인 해시는 영구 보존이다. 1분 주기의 포집 레코드라면 시설 하나의 7년치 원시 데이터가 수백만 건에 이른다. 시계열 데이터베이스로 충분히 다룰 수 있는 규모이지만, 처음부터 저장 계획을 세워 둔 경우에 한해서이다. 여기서 두 가지 실무적 결론이 나온다. 첫째, 원시 데이터보다 오래 남는 집계값은 스스로를 설명해야 한다. 7년이 지나면 아무도 다시 계산할 수 없으므로 계산 방법, 원천 레코드 수, 원천 집합의 해시를 함께 담아야 한다. 둘째, 관할 법령이나 등록부가 더 긴 보존을 요구하면 더 긴 기간이 적용된다. 표준의 수치는 상한이 아니라 하한이다.
JSON Schema는 필드를 하나씩 따로 검증한다. dailyTotal이 음이 아닌 숫자라는 것은 말할 수 있지만, 그 값이 currentRate에 비추어 타당한지, specificEnergy가 정말 에너지를 CO2로 나눈 값인지는 말하지 못한다. 실제 데이터 오류는 이런 필드 간 관계에서 드러나며, 명세 자체의 예시 레코드도 이 가운데 여러 검사에서 실패한다. 이 책이 자체 예시를 쓰는 중요한 이유이다. 적합한 검증기는 스키마 검증을 통과한 뒤 최소한 다음 검사를 수행해야 한다.
| 검사 | 관계식 | 예시 값(2.2, 2.3절) |
|---|---|---|
| C1 정격 | annualTonsCO2 ≈ modules × captureRatePerModule × 365 | 12 × 0.25 × 365 = 1,095 ✓ |
| C2 속도 상한 | currentRate ≤ modules × captureRatePerModule × (1 + 허용오차) | 2.9 ≤ 3.0 ✓ |
| C3 일일 누적 | dailyTotal ≤ 최대 속도 × 00:00Z 이후 경과 시간 ÷ 24 | 0.72 ≤ 3.0 × 6 ÷ 24 = 0.75 ✓ |
| C4 비에너지 | specificEnergy ≈ energy.dailyTotal ÷ capture.dailyTotal | 1.45 ÷ 0.72 ≈ 2.01 ✓ |
| C5 전력과 에너지 | energy.dailyTotal ≈ 평균 kW × 시간 ÷ 1000 | 242 × 6 ÷ 1000 ≈ 1.45 ✓ |
| C6 압력 척도 | 개방형 접촉기라면 airflow.pressure(Pa) ≈ environmental.pressure(hPa) × 100 | 100,800 ≈ 1,008 × 100 ✓ |
| C7 참조 | facilityId가 존재하고, technology.sorbent가 흡착제 레코드로 해석됨 | 둘 다 해석됨 ✓ |
| C8 미지 필드 | 모든 속성이 스키마에 있거나 x-로 시작함 | x-breakdown만 있음 ✓ |
C3에서 currentRate 대신 최대 속도를 쓰는 이유는 속도가 하루 중에도 변하기 때문이다. 자정 이후 정격 최대치로 포집했을 때의 양보다 많은 일일 누적값은 현재 속도가 얼마이든 불가능하다. C5는 근사일 뿐이다. currentConsumption은 순간값이므로, 제대로 된 검사는 저장된 레코드 전체에 걸쳐 이를 적분해야 한다. C6은 접촉기가 대기압 근처에서 운전될 때만 적용된다. 진공 재생 중에는 계통 압력이 훨씬 낮은 것이 정상이다. 허용오차는 구현이 정할 몫이다. 2.6절의 정확도 목표(예: CO2 5 %, 에너지 3 %)에서 출발하고, 검사 실패는 자동 거부가 아니라 설명이 필요한 경고로 다루는 것이 합리적이다.
다음 Node.js 코드는 포집 레코드를 Ajv로 2020-12 스키마에 대해 검증한 뒤 필드 간 검사를 수행하는 골격이다. 스키마 파일명과 profile 객체는 예시이며, Ajv API 호출은 실제 라이브러리의 것이다.
// npm install ajv ajv-formats
import Ajv2020 from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
import { readFileSync } from "node:fs";
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
const ajv = new Ajv2020({ allErrors: true, strict: false }); // strict:false 여야 "x-unit" 키워드가 허용된다
addFormats(ajv); // date, date-time, email, uri
const validateCapture = ajv.compile(load("schemas/dac-operational.schema.json"));
// 2.3–2.5절에서 내린 프로파일 결정을 한곳에 적어 둔다
const profile = { rateScope: "facility", tolCO2: 0.05, tolEnergy: 0.03 };
function crossChecks(rec, facility) {
const issues = [];
const cap = facility.capacity;
const nameplateDay = cap.modules * cap.captureRatePerModule; // t/일
// C1 정격 일관성(시설 레코드)
const annual = nameplateDay * 365;
if (Math.abs(annual - cap.annualTonsCO2) > profile.tolCO2 * cap.annualTonsCO2)
issues.push(`C1: annualTonsCO2 ${cap.annualTonsCO2} vs computed ${annual}`);
// C2 속도 상한
if (rec.capture.currentRate > nameplateDay * (1 + profile.tolCO2))
issues.push("C2: currentRate above nameplate (per-module value sent?)");
// C3 UTC 경과 시간 대비 일일 누적
const t = new Date(rec.timestamp);
const hours = t.getUTCHours() + t.getUTCMinutes() / 60;
const d = rec.capture.dailyTotal;
if (d !== undefined && d > nameplateDay * hours / 24 * (1 + profile.tolCO2))
issues.push(`C3: dailyTotal ${d} t impossible after ${hours.toFixed(2)} h`);
// C4 비에너지
const e = rec.energy;
if (d > 0 && e.dailyTotal !== undefined && e.specificEnergy !== undefined) {
const se = e.dailyTotal / d;
if (Math.abs(se - e.specificEnergy) > profile.tolEnergy * se)
issues.push(`C4: specificEnergy ${e.specificEnergy} vs computed ${se.toFixed(2)}`);
}
return issues;
}
const facility = load("data/dac-example-reykjanes-01.json");
const record = load("data/capture-2026-03-15T0600Z.json");
if (!validateCapture(record)) {
console.error(validateCapture.errors); // 스키마 오류: 거부
} else {
const warnings = crossChecks(record, facility); // 필드 간 검사: 설명하거나 거부
console.log(warnings.length ? warnings : "OK");
}
설계상 짚어 둘 점이 세 가지 있다. 첫째, 스키마 오류와 필드 간 경고를 따로 보고한다. 앞의 것은 레코드가 형식상 잘못되었다는 뜻이고, 뒤의 것은 형식은 맞지만 물리적으로 의심스럽다는 뜻이다. 둘째, C2 메시지는 가장 흔한 원인을 함께 알려 준다. 속도가 정격을 크게 넘으면, 2.3절에서 설명했듯이 시설 단위 필드에 모듈 단위 값이 들어갔거나 그 반대인 경우가 대부분이다. 셋째, profile 객체가 구현의 해석을 명시적으로 드러낸다. 같은 객체를 적합성 주장(제8장)과 함께 공개하면 검증기관은 어떤 규칙이 적용되었는지 정확히 알 수 있다.
미지 필드 검사(C8)는 스키마의 엄격한 사본을 만들어 수행하는 것이 가장 좋다. 모든 객체에 unevaluatedProperties: false를 걸고, x-로 시작하는 이름을 허용하는 patternProperties 항목을 추가한다. 이 사본은 허용적인 표준 스키마와 나란히 경고 모드로 돌린다. 그러면 정당한 확장은 거부하지 않으면서 captureRatePerModul 같은 오타를 드러낼 수 있다.
시설, 포집, 흡착제 레코드는 증거 사슬의 물리적 토대를 이룬다. 시설 레코드는 정체성과 설계 용량을 고정하고, 포집 레코드는 1–5분 해상도로 실제 일어난 일을 기록하며, 흡착제 레코드는 용량이 시간에 따라 왜 변하는지를 설명한다. 표준의 스키마는 구조를 올바르게 잡았지만 네 가지를 구현자에게 맡겨 둔다. 각 숫자의 척도(비율인가 백분율인가, Pa인가 hPa인가), 각 속도의 범위(모듈 단위인가 시설 단위인가), 파생 수명 뒤에 있는 모델, 그리고 레코드를 물리적으로 타당하게 만드는 필드 간 관계이다. 적합한 구현은 이 네 가지를 공개 프로파일에서 확정하고 자동으로 검사한다. 제3장에서는 포집된 CO2를 따라 플랜트 밖으로 나가, 저장 이벤트와 검증 보고서를 거쳐 탄소제거 크레딧까지 이어지는 과정을 살펴본다.