본문으로 건너뛰기

이름 붙이기: 태그, 계층구조, 통합 네임스페이스(UNS)

📍 현재 위치: 3장에서는 ISA-88/95 배치(batch) 및 장비 모델을 PostgreSQL에 담았습니다. 2부에서 살아 있는 측정값을 단 하나라도 수집하기 전에, 이 장은 모든 신호에 이름을 부여합니다 — 히스토리안(historian), 브로커(broker), 지식 그래프(knowledge graph), 그리고 규제 당국이 모두 동의할 수 있는 하나의 이름을요.

넷스케이프(Netscape)의 유명한 엔지니어 필 칼튼(Phil Karlton)은 컴퓨터 과학에서 정말 어려운 것은 단 두 가지뿐이라고 말했다고 전해집니다. 캐시 무효화(cache invalidation)와 이름 짓기(naming things)입니다. 바이오 제조 공장에서 두 번째 문제는 첫 번째 책의 약속 — 모든 숫자를 그 출처까지 추적하기 — 이 애초에 달성 가능한지를 조용히 결정합니다. 바이오리액터(bioreactor) 온도 프로브는 지식을 내보내지 않습니다. 태그(tag)를 내보냅니다. TT-101 = 37.02처럼 짧은 문자열과 실수(float) 하나입니다. 만약 같은 프로브가 DeltaV 컨트롤러에서는 TIC_101.PV, 히스토리안에서는 BR1_TEMP, 그리고 어느 과학자가 관리하는 스프레드시트에서는 Reactor1 Temperature라고 불린다면, 당신에게는 하나의 신호가 있는 것이 아니라 세 개가 있는 것이며, 어떤 기계도 이들이 같은 것임을 알 수 없습니다. 이것을 SCADA, MES, ERP에 걸쳐 만 개의 태그로 곱하면, 그 결과는 업계에서 가장 흔하면서도 가장 화려하지 않은 데이터 관리 실패 원인이 됩니다 [1].

이 장은 그 처방이며, 대부분 코드보다는 규율(discipline)에 관한 것입니다. 우리는 하나의 명명 규칙(naming convention)을 설계하고, 이를 ISA-95에 근거하게 하며, 통합 네임스페이스(Unified Namespace, UNS) 토픽 트리(topic tree)와 Sparkplug B 토픽으로 투영하고, 거버넌스(governance)가 적용된 데이터로 저장합니다. 그리고 — 이것이 실습 중심의 책이기에 — 잘못된 태그가 플랫폼에 들어오는 것을 거부하는 린터(linter)를 함께 제공합니다. 코드는 작습니다. 그 대가는 하류(downstream)의 모든 것입니다.

쉽게 말하면

태그 이름은 데이터의 우편 주소입니다. BR101.Temp.PV는 "바이오리액터 101온도 측정값의 현재 값(present value)"이며 — newark/upstream/BR101/Temp는 공장 전체가 둘러볼 수 있는 폴더 경로로 쓴 같은 주소입니다. 주소 형식을 한 번 정하고, 적어 두고, 출입문에 로봇을 세워 두어 형식에 맞지 않는 것은 모두 돌려보내십시오. 그 후에는 이후의 모든 장이 자기 편지를 알맞은 상자에 넣기만 하면 됩니다.

이 장에서 다루는 내용

우리는 왜 하나의 신호가 정확히 하나의 정규(canonical) 이름을 가져야 하는지로 시작한 다음, 3장의 ISA-95 계층구조 위에 규칙을 세웁니다. 각 태그를 UNS 경로와 Sparkplug B 토픽으로 바꾸고, ISA-5.1 계기 태그(instrument tag)와 ISA-95 7부 별칭화(aliasing)가 어떻게 현장(floor)과 클라우드(cloud)가 라벨에 대해 서로 다르게 부르면서도 연결의 끈을 잃지 않게 하는지 살펴보며, 이 사전(dictionary) 전체를 거버넌스가 적용된 데이터로 PostgreSQL에 저장합니다. 마지막으로, 동반 저장소(companion repo)의 실제 린터를 시뮬레이터의 16개 태그에 대해 실행하여 통과하는 것을 지켜보고, 실제 공장에서 벌어지는 종류의 표류(drift)를 거부하는 것을 지켜봅니다.

하나의 신호, 하나의 정규 이름

진행 중인 사례의 골든 데이터(golden data)를 열어 원시 태그를 보십시오. 다음은 동반 저장소의 datasets/fedbatch_timeseries_10min.sample.csv의 일부입니다 — 우리의 14일간 유가식(fed-batch) CHO 런(run)이 생성하는 롱 포맷(long-format) 시계열입니다.

datasets/fedbatch_timeseries_10min.sample.csv (excerpt)
ts,tag,value,unit,quality,batch_id
2026-01-05 00:00:00+00:00,BR101.Agitation.PV,81.4323,rpm,192,BATCH-2026-001
2026-01-05 00:00:00+00:00,BR101.Agitation.SP,81.6008,rpm,192,BATCH-2026-001
2026-01-05 00:00:00+00:00,BR101.DO.PV,40.8224,%sat,192,BATCH-2026-001
2026-01-05 00:00:00+00:00,BR101.DO.SP,40.0,%sat,192,BATCH-2026-001

모든 행이 이미 tagunit을 담고 있습니다. 규율의 질문은 이것입니다. 누가 BR101.DO.PV가 영원히 같은 프로브, 같은 단위를 의미하도록 보장하는가 — 그리고 다음 분기에 아무도 BR101.DissolvedO2를 새로 만들지 않도록 보장하는가? 답은 *규칙(convention) 더하기 등록부(register) 더하기 관문(gate)*입니다. 규칙은 적법한 태그가 어떻게 생겼는지를 말하고, 등록부는 모든 적법한 태그의 단일 목록이며, 관문은 현실이 등록부에서 표류할 때 큰 소리로 실패하는 자동화입니다.

저장소가 사용하는 규칙은 의도적으로 지루하며, 점(dot)으로 구분된 세 개의 세그먼트입니다.

<UNIT>.<Measurement>.<Attr> e.g. BR101.Temp.PV
  • UNIT은 3장에서 시드(seed)한 ISA-95 모델의 장비 한 조각입니다 — BR101(생산 바이오리액터), N1SEED(시드 트레인(seed-train) 용기), PA01(단백질 A(Protein A) 포획 스키드(capture skid)).
  • Measurement는 측정되는 물리적 대상입니다 — Temp, pH, DO, Agitation, Titer.
  • Attr은 이 특정 숫자의 역할입니다. PV(현재값/공정값(present/process value)), SP(설정값(setpoint)), MV(조작값/출력값(manipulated/output value)), Rate, 또는 State.

그 마지막 세그먼트는 보이는 것보다 더 중요합니다. 설정값(우리가 요청한 것)과 공정값(우리가 얻은 것)의 차이는 지시(instruction)와 측정(measurement)의 차이이며 — 이 둘을 뒤섞는 것은 고전적인 데이터 무결성(data-integrity) 오류입니다. BR101.Temp.SPBR101.Temp.PV를 별개의 이름 붙은 신호로 유지하는 것은 ALCOA+ 원칙의 명명 계층(naming-layer)에서의 표현입니다. 값은 그 진정한 출처에 귀속(attributable)될 수 있어야 하며, 그 출처가 사람이든, 여기서처럼 특정 계기에서 자동으로 생성된 측정값이든 마찬가지입니다 [2][3].

이름을 ISA-95에 근거하게 하기

밑에 지도가 없는 규칙은 그저 문자열 형식일 뿐입니다. BR101이 의미를 갖는 이유는 그것이 ISA-95 장비 계층구조 — 기업(enterprise) → 사이트(site) → 영역(area) → 작업 센터/유닛(work-center/unit) — 의 실제 위치로 해석(resolve)되기 때문입니다. 이는 바로 이 목적을 위해 표준이 정의한 기술 중립적(technology-agnostic) 주소 공간입니다 [4]. 우리의 시드된 모델은 생산 바이오리액터를 Newark 사이트 → 상류(upstream) 영역 → BR101에 두고, 단백질 A 스키드를 Newark → 하류(downstream) → PA01에 둡니다. 태그 사전의 일은 그 배치를 모든 신호와 함께 들고 다니는 것이어서, 숫자가 결코 고아(orphan)가 되지 않게 하는 것입니다.

동반 코드에서 그 매핑은 examples/chapters/04-naming-uns/naming.py의 맨 위에 작고 명시적인 테이블로 존재합니다 — 시드된 Postgres 모델이 인코딩하는 것과 같은 계층구조입니다.

examples/chapters/04-naming-uns/naming.py
TAG_RE = re.compile(r"^[A-Z][A-Z0-9]+\.[A-Za-z][A-Za-z0-9]+\.(PV|SP|MV|Rate|State)$")

# the equipment hierarchy the tags live under (matches the seeded ISA-95 model)
UNIT_AREA = {"BR101": ("newark", "upstream"), "N1SEED": ("newark", "upstream"),
"PA01": ("newark", "downstream"), "PBR201": ("newark", "upstream")}
QUDT = {"degC": "DEG_C", "pH": "PH", "%sat": "PERCENT", "rpm": "REV-PER-MIN",
"kg": "KiloGM", "bar": "BAR", "L": "L", "%": "PERCENT", "g/L": "GM-PER-L"}

잠시 멈춰 볼 만한 두 가지 설계 결정이 있습니다. 첫째, TAG_RE는 *기계가 읽을 수 있는 계약(contract)*입니다. 태그는 대문자로 시작하는 유닛 코드, 측정 항목, 그리고 닫힌 집합(closed set)에 속하는 속성 역할 중 하나일 때에만 적법합니다. 그 닫힌 집합이 바로 동의어(synonym)의 느린 부패를 멈추는 것입니다. 둘째, QUDT 테이블은 각 공장 측정 단위(unit-of-measure) 문자열을 QUDT 단위 IRI에 매핑합니다(예를 들어 degChttp://qudt.org/vocab/unit/DEG_C). 단위는 값의 정체성의 일부입니다 — "37"은 그것이 섭씨라는 것을 알기 전까지는 무의미합니다 — 그리고 지금 그것을 전역 어휘(global vocabulary)에 못 박아 두는 것이 나중에 16장의 지식 그래프가 시스템들을 가로질러 추론할 수 있게 합니다. 위 데이터에서 %sat과 단순 %의 구분은 바로 이 테이블이 종이 위에서 한 번 해결하도록 강제하는 종류의 단위 모호성이며, 일탈(deviation) 조사 도중에 다투는 일을 막아 줍니다.

태그에서 토픽으로: 통합 네임스페이스

태그 사전은 신호를 일관되게(consistent) 만듭니다. 통합 네임스페이스(Unified Namespace)는 신호를 둘러볼 수 있게(browsable) 만듭니다. 제조 IoT 커뮤니티에서 널리 알려진 UNS 개념은 단일 실시간 계층구조 — 비즈니스 그 자체처럼 의미론적으로(semantically) 조직되고 브로커 중립적(broker-agnostic)인 — 이며, 어떤 시스템이든 무언가의 현재 상태를 찾으러 가는 단 하나의 장소가 됩니다 [5]. 우리가 따르는 강력한 권고는, 평행한 분류 체계(taxonomy)를 새로 만드는 대신 그 계층구조를 ISA-95 레벨 — 기업 / 사이트 / 영역 / 라인(Line) / 셀(Cell) — 에 맞춰 형성하라는 것입니다 [6].

구체적으로, UNS는 MQTT 토픽의 트리입니다. MQTT는 OASIS 및 ISO/IEC 20922로 표준화된 경량 발행/구독(publish/subscribe) 전송(transport)입니다 [7]. 토픽은 슬래시로 구분된 경로일 뿐이며, 폴더와 같습니다. 커뮤니티의 모범 사례는 계층적으로 일반에서 구체로(general-to-specific) 가는 레벨, 전부 소문자, 공백 없음입니다 — 그래야 경로가 예측 가능하고 단일 레벨(+) 및 다중 레벨(#) 와일드카드(wildcard)가 유용한 일을 합니다 [8]. 그 경로를 손에 쥐면, 대시보드(dashboard)는 newark/upstream/+/Temp를 구독하여 상류 영역의 모든 리액터 온도를 일일이 이름 붙이지 않고도 즉시 받을 수 있습니다.

naming.py는 정규 태그로부터 직접 UNS 경로를 도출하므로, 주소 공간은 사전과 결코 어긋날 수 없습니다.

examples/chapters/04-naming-uns/naming.py
def uns_path(tag: str) -> str:
unit, measurement, _attr = tag.split(".")
site, area = UNIT_AREA[unit]
return f"{site}/{area}/{unit}/{measurement}"


def sparkplug_topic(tag: str, group: str = "newark", edge: str = "edge1") -> str:
unit = tag.split(".")[0]
return f"spBv1.0/{group}/DDATA/{edge}/{unit}"

아래 그림은 하나의 신호가 어떻게 세 가지 뷰(view)로 동시에 투영되는지 보여 줍니다.

바이오리액터 온도 신호가 정렬된 세 가지 뷰로 투영된 모습: 정규 점 표기 태그, ISA-95 기업-사이트-영역-유닛 계층구조에 근거한 슬래시 구분 UNS 토픽 트리, 그리고 고정 차수(fixed-arity)의 Sparkplug B 토픽.

하나의 신호, 세 개의 조율된 주소: 정규 태그는 기록 시스템(system of record)이고, UNS 경로는 사람과 대시보드가 둘러볼 수 있는 트리이며, Sparkplug B 토픽은 전송 포맷(wire format)입니다. 셋 모두 같은 사전에서 생성되므로 서로 어긋날 수 없습니다.

Original diagram by the authors, created with AI assistance.

Sparkplug B: 더 엄격한 사촌

평범한 MQTT 토픽은 자유 형식(free-form)인데, 이는 그것의 매력이자 위험입니다. 두 팀이 트리를 서로 다르게 조직하는 것을 아무것도 막지 못합니다. Sparkplug B(Eclipse Sparkplug 3.0 명세)는 MQTT 위에 놓인 의견이 강한(opinionated) 프로파일로, 토픽 구조를 정확히 다섯 레벨 — spBv1.0/<group_id>/<message_type>/<edge_node_id>/<device_id> — 로 고정하고, 발행자(publisher)가 살아 있는지 구독자(subscriber)가 항상 알 수 있도록 출생/사망(birth/death) 세션 상태 관리를 추가합니다 [9]. 이것이 sparkplug_topic()spBv1.0/newark/DDATA/edge1/BR101을 반환하는 이유입니다. newark 그룹, DDATA(장비 데이터(device-data)) 메시지, 엣지 노드(edge node) edge1에서, 장비 BR101에 대한 것입니다.

두 가지 토픽 형태는 중복되는 것이 아니라 — 서로 다른 청중을 위해 봉사합니다. UNS 경로는 사람과 대시보드가 둘러볼 수 있는 단일 진실 공급원(single source of truth)이고, Sparkplug 토픽은 엣지 게이트웨이(edge gateway)가 실제로 발행하는, 규율 잡힌 고정 차수 전송 포맷이며, 이는 다음 장에서 실제로 연결합니다. 하나의 사전에서 둘 다 생성하는 것이 바로 공장이 유연한 둘러보기 트리 엄격한 전송 계약을 둘이 결코 서로 모순되지 않게 유지하는 방법입니다 [6].

하나를 위한 두 이름: ISA-5.1과 7부 별칭화

지금까지 우리는 깨끗한 백지를 가정했습니다. 실제 공장은 깨끗한 백지가 아닙니다. P&ID 상의 계기 루프(instrument loop)는 히스토리안이 존재하기도 전부터 TT-101이라고 불려 왔습니다 — 이는 ISA-5.1, 즉 계측 기호 및 식별 표준이 정의한 형태의 태그이며, 거기서 첫 글자는 측정 변수를 나타내고 나머지는 루프를 식별합니다(TT = 온도 트랜스미터(temperature transmitter), PIC = 압력 지시 컨트롤러(pressure indicating controller)) [10]. DCS는 이를 TIC_101.PV로 노출합니다. ERP는 이를 비용 센터(cost-center) 자산 번호로만 압니다. 모두에게 BR101.Temp.PV로 이름을 바꾸라고 요구하는 것은 아무도 자금을 대지 않을 다년간의 변경 관리(change-control) 프로젝트입니다.

표준이 축복한 답은 하나의 이름을 강요하는 것이 아니라 그것들이 동등하다고 선언하는 것입니다. ISA-95 7부, 즉 별칭 서비스 모델(Alias Service Model)은 바로 서로 다른 시스템이 같은 객체에 사용하는 서로 다른 식별자(identifier)를 조화시키기 위해 존재하며, 그래서 하나의 물리적 신호가 여러 이름을 지니더라도 플랫폼이 자기 일관성(self-consistent)을 유지하게 합니다 [11]. 우리 세계에서 이것은 정규 BR101.Temp.PV가 기록 시스템이고, TT-101, TIC_101.PV, 그리고 ERP 자산 번호가 모두 그것으로 해석되는 *별칭(alias)*이라는 것을 뜻합니다. 태그 사전은 그 해석 테이블의 자연스러운 보금자리이며, 그것을 (접착 코드(glue code)에 파묻는 대신) 데이터로 저장하는 것이 매핑을 감사 가능(auditable)하게 만드는 것입니다.

사전을 거버넌스가 적용된 데이터로 저장하기

파이썬 파일 안에만 존재하는 규칙은 제안입니다. 데이터베이스 스스로가 강제하는 기본 키(primary key)와 테스트 스위트(test suite)가 실행하는 린터를 갖춘, 거버넌스가 적용된 데이터베이스 테이블에 존재하는 규칙은 정책(policy)입니다. 동반 저장소의 examples/platform/db/40-gov.sql은 전체 스택이 가동되면 사전이 안착할 스키마(schema)를 생성합니다 — 각 컬럼을 그 하류 소비자(consumer)에 묶는 주석들에 주목하십시오.

examples/platform/db/40-gov.sql
-- The tag dictionary (Ch 4): every signal's canonical name, asset, unit, the
-- QUDT unit IRI, its UNS path and Sparkplug topic, and a deadband. The naming
-- linter (chapters/04-naming-uns/naming.py, exercised by tests/test_chapters.py
-- under `make test`) rejects any tag not matching the convention.
CREATE TABLE gov.tag_dictionary (
tag text PRIMARY KEY, -- BR101.Temp.PV
asset text NOT NULL, -- BR101
measurement text NOT NULL, -- Temperature
unit text NOT NULL, -- degC
qudt_unit text, -- http://qudt.org/vocab/unit/DEG_C
uns_path text NOT NULL, -- newark/upstream/BR101/Temperature
sparkplug_topic text NOT NULL, -- spBv1.0/newark/DDATA/edge1/BR101
data_type text NOT NULL DEFAULT 'Double',
deadband numeric NOT NULL DEFAULT 0
);

tag 컬럼은 기본 키입니다. 이제 데이터베이스 스스로가 "하나의 정규 이름, 한 번만 사용"을 강제합니다. uns_pathsparkplug_topic은 읽기 시점에 다시 계산되는 것이 아니라 저장되므로, 5장의 발행자와 14장의 맥락화(contextualization) 뷰가 린터가 승인한 동일한 토픽을 읽습니다. deadband 컬럼은 조용하지만 중요한 세부 사항입니다 — 이는 그 아래로는 측정값을 발행할 가치가 없는 변화 임계값(change threshold)이며, 이를 태그 옆에 저장하는 것이 수집 정책(capture policy)을 컬렉터(collector) 설정 곳곳에 흩어 놓는 대신 거버넌스 아래에 유지합니다. 같은 gov 스키마는 관할 정책(jurisdiction policy)(23장)과 공급자 등록부(supplier register)(22장)도 담고 있어서, 데이터 에 관한 모든 데이터가 한곳에 자리합니다.

naming.pybuild_dictionary()는 라이브 태그 집합에서 이 테이블이 기대하는 바로 그 행 — 자산, 측정, 단위, QUDT IRI, UNS 경로, Sparkplug 토픽 — 을 생성하며, 이것이 바로 가동 중인 스택, 시드 데이터, 그리고 책이 결코 조용히 서로 어긋날 수 없게 하는 방법입니다.

린터: 출입문의 로봇

여기가 바로 이 실습 중심의 책이 제목값을 하는 곳입니다. 규칙은 무언가가 그것을 모든 변경에 대해, 영원히, 자동으로 강제할 때에만 실재합니다. 그 무언가가 린터이며 — 정말로 아주 작습니다. examples/chapters/04-naming-uns/naming.py에서 가져왔습니다.

examples/chapters/04-naming-uns/naming.py
def lint_tag(tag: str) -> str | None:
"""Return None if the tag is valid, else a reason string."""
if not TAG_RE.match(tag):
return f"'{tag}' does not match <UNIT>.<Measurement>.<Attr>"
unit = tag.split(".")[0]
if unit not in UNIT_AREA:
return f"unit '{unit}' is not in the equipment hierarchy"
return None


def lint_dataset() -> tuple[pd.DataFrame, list[str]]:
df = pd.read_parquet(DATA / "fedbatch_timeseries.parquet")
tags_units = dict(df.groupby("tag")["unit"].first())
problems = [f"{t}: {lint_tag(t)}" for t in tags_units if lint_tag(t)]
return build_dictionary(tags_units), problems

두 가지 검사, 두 가지 실패 모드입니다. 첫째, 태그가 구조적 계약 TAG_RE에 부합하는가? 둘째, 잘 형성되었더라도 그 유닛 코드가 계층구조 속 실제 장비 조각에 대응하는가? 존재하지 않는 용기를 위한 완벽한 형태의 태그는 잘못 형성된 태그만큼이나 잘못된 것입니다 — 그것은 글씨체만 좋은 고아입니다.

이것을 실제 골든 데이터셋에 대해 실행하면(python chapters/04-naming-uns/naming.py) 실제로 테스트된 출력을 얻습니다 — 생성된 사전의 처음 여덟 행과 깨끗한 건강 진단서입니다.

tag asset measurement unit qudt_unit uns_path sparkplug_topic
BR101.Agitation.PV BR101 Agitation rpm http://qudt.org/vocab/unit/REV-PER-MIN newark/upstream/BR101/Agitation spBv1.0/newark/DDATA/edge1/BR101
BR101.Agitation.SP BR101 Agitation rpm http://qudt.org/vocab/unit/REV-PER-MIN newark/upstream/BR101/Agitation spBv1.0/newark/DDATA/edge1/BR101
BR101.DO.PV BR101 DO %sat http://qudt.org/vocab/unit/PERCENT newark/upstream/BR101/DO spBv1.0/newark/DDATA/edge1/BR101
BR101.DO.SP BR101 DO %sat http://qudt.org/vocab/unit/PERCENT newark/upstream/BR101/DO spBv1.0/newark/DDATA/edge1/BR101
BR101.FeedA.PV BR101 FeedA kg http://qudt.org/vocab/unit/KiloGM newark/upstream/BR101/FeedA spBv1.0/newark/DDATA/edge1/BR101
BR101.FeedB.PV BR101 FeedB kg http://qudt.org/vocab/unit/KiloGM newark/upstream/BR101/FeedB spBv1.0/newark/DDATA/edge1/BR101
BR101.OffgasCO2.PV BR101 OffgasCO2 % http://qudt.org/vocab/unit/PERCENT newark/upstream/BR101/OffgasCO2 spBv1.0/newark/DDATA/edge1/BR101
BR101.OffgasO2.PV BR101 OffgasO2 % http://qudt.org/vocab/unit/PERCENT newark/upstream/BR101/OffgasO2 spBv1.0/newark/DDATA/edge1/BR101

16 tags; lint problems: 0

유가식 태그 열여섯 개 전부가 통과합니다. 더 중요한 것은, 저장소의 테스트 스위트가 계약의 양쪽 절반을 모두 단언(assert)한다는 점입니다 — 실제 태그가 통과한다는 것 린터가 실제로 쓰레기를 거부한다는 것입니다. examples/tests/test_chapters.py에서 가져왔습니다.

examples/tests/test_chapters.py
def test_ch04_naming_linter_passes_on_real_tags():
import naming

dictionary, problems = naming.lint_dataset()
assert len(dictionary) == 16
assert problems == [] # every simulator tag obeys the convention
assert naming.lint_tag("badtag") is not None # and the linter actually rejects bad ones
assert naming.uns_path("BR101.Temp.PV") == "newark/upstream/BR101/Temp"

관문이 중요한지 보려면, 다음은 실제로 공장에서 나타나는 종류의 표류에 대해 실행한 lint_tag()입니다.

'BR101.Temp.PV' -> None
'TT101' -> 'TT101' does not match <UNIT>.<Measurement>.<Attr>
'br101.temp.pv' -> 'br101.temp.pv' does not match <UNIT>.<Measurement>.<Attr>
'TK205.Temp.PV' -> unit 'TK205' is not in the equipment hierarchy
'BR101.Temp.Value' -> 'BR101.Temp.Value' does not match <UNIT>.<Measurement>.<Attr>

원시 P&ID 태그(TT101), 소문자 오타(br101.temp.pv), 잘 형성되었지만 알 수 없는 용기(TK205), 그리고 동의어 침입(PV 대신 Value) 각각이 구체적인 이유와 함께 잡힙니다. 동반 저장소에서 이 계약은 테스트 스위트에 연결되어 있습니다 — make testtest_ch04_naming_linter_passes_on_real_tags를 실행하며, 어떤 실제 태그라도 규칙을 어기면 실패합니다 — 그래서 명명은 모두가 무시하는 위키 페이지가 아니라 빨갛게 변하는 단언이 됩니다. 같은 검사를 당신만의 배포에서 프리커밋 훅(pre-commit hook)이나 CI 관문으로 승격시키는 것이 다음 단계이며, GxP SOP가 궁극적으로 요구하게 될 단계입니다.

왜 중요한가

명명은 ALCOA+가 성공하거나 조용히 실패하는 첫 번째 장소입니다. 어떤 숫자를 가리키며 어떤 계기가, 어떤 단위로, 어떤 장비에서, 어떤 배치에서 그것을 생산했는지 말할 수 없다면, 그것을 귀속 가능(attributable) 하게도, 출처까지 추적 가능(traceable to its source) 하게도 만들 수 없습니다 — 그리고 MHRA와 FDA의 데이터 무결성 지침은 모두 그 추적 가능성을 근간으로 취급합니다 [2][3]. 이 책 속 이후의 모든 능력 — 히스토리안의 조인(join), 크로마토그래피(chromatography) 단계 이벤트, 지식 그래프의 계보(lineage) 질의, 감사 추적(audit trail)의 해시 체인(hash chain) — 은 각 데이터 패킷(packet) 위의 주소가 올바르고 고유하다고 가정합니다. 명명을 제대로 하면 그 계층들이 조립(compose)됩니다. 잘못하면 일탈 조사 도중 손으로 동의어를 조화시키는 데 프로그램의 남은 시간을 쓰게 되는데, 이는 동료 심사(peer-reviewed)를 거친 바이오프로세싱 4.0(Bioprocessing 4.0) 문헌이 MES/DCS/SCADA 환경 전반에 풍토병처럼 만연하다고 지목하는 바로 그 실패 모드입니다 [1].

실제 현장에서는

솔직한 그림은, 오픈 소스가 여기서 대부분의 길을, 그것도 유난히 깔끔하게 데려다준다는 것입니다. 규칙은 그저 규율이고, UNS는 무료 MQTT 브로커 위의 토픽 트리이며, 사전은 Postgres 테이블이고, 린터는 당신이 온전히 소유하는 파이썬 오십 줄입니다(저장소에서 MIT 라이선스). 이름을 잘 붙이기 위해 반드시 사야 하는 상용 제품은 없습니다 — 이것이 순수 OSS 답이 진정으로 완전한 몇 안 되는 계층 중 하나가 명명인 이유입니다.

OSS가 건네주지 않는 것은 그것을 둘러싼 거버넌스이며, 이것이 솔직한 부분입니다. 상용 UNS 및 네임스페이스 도구 — HighByte Intelligence Hub, Ignition 플랫폼의 태그 시스템, AVEVA의 자산 프레임워크(asset framework) — 는 그래픽 모델러(graphical modeler), 역할 기반 변경 관리(role-based change control), 번들된 별칭화 서비스를 함께 제공하며, 우리의 gov.tag_dictionary + 린터 접근법은 당신이 그 주위에 변경 관리를 연결해야만 그것을 재현합니다. 우리가 기대는 표준들은 실재하며 현행입니다. ISA-95 1부는 2025년판(ANSI/ISA-95.00.01-2025) 기준으로 현행이고 [4], 7부의 별칭 서비스 모델은 시스템들을 가로질러 이름을 조화시키는 축복받은 방법이며 [11], ISA-5.1은 여전히 P&ID 상의 루프 태그를 다스리고 [10], Sparkplug 3.0과 MQTT는 사실상의(de-facto) UNS 전송입니다 [9][7]. 하지만 표준은 계약이지 집행자가 아닙니다. 집행자는 당신의 CI 관문이며 — GxP 맥락에서는, 어떤 태그도 그것을 통과하지 않고는 생산에 들어가지 않는다고 말하는 검증된(validated) SOP입니다. NIIMBL의 SABRE 시설 — 2024년 4월 착공 이래 델라웨어주 뉴어크에서 건설 중인 NIIMBL / 델라웨어 대학교(University of Delaware)의 파일럿 규모(pilot-scale) 현행 우수 제조 관리 기준(current Good Manufacturing Practice, cGMP) 공장 — 은 첫 프로브가 배선되기 전에 하나의 네임스페이스를 정착시키는 것이 일관된 데이터 플랫폼과 10년간의 조화 작업 사이의 차이를 만드는, 바로 그런 종류의 신축(greenfield) 부지입니다. 우리는 그 현실에 조용히 경의를 표하며 우리의 시뮬레이션 부지를 newark라고 이름 붙입니다.

생산을 위한 솔직한 메모 하나 더입니다. 우리의 린터는 구조와 장비 소속(membership)을 검사하지, 의미론적(semantic) 중복은 검사하지 않습니다. 누군가 두 번째 측정 이름을 계층구조에 추가하면, 린터는 BR101.DO.PV와 가상의 BR101.DissolvedOxygen.PV를 둘 다 기꺼이 받아들일 것입니다. 그것을 잡아내는 것 — 진정한 동의어 탐지(synonym detection) — 은 7부 별칭 테이블이 채워지고 사람이 검토하는 것을 필요로 하며, 이는 거버넌스이지 정규식(regex)이 아닙니다.

핵심 용어

  • 태그(Tag) — 이름 붙은 신호로, 여기서는 <UNIT>.<Measurement>.<Attr>(예: BR101.Temp.PV); 데이터 포인트의 우편 주소.
  • 정규 이름(Canonical name) — 한 신호에 대한 하나의 공식 태그; 사전의 기본 키이자 기록 시스템.
  • 통합 네임스페이스(Unified Namespace, UNS) — 현재 공장 상태에 대한 단일 진실 공급원인 실시간, 브로커 중립적 계층구조로, 비즈니스처럼 조직되며 (여기서는) ISA-95 레벨에 맞춰 형성됨 [5].
  • MQTT 토픽(MQTT topic) — 슬래시로 구분된 발행/구독 경로; +(단일 레벨)와 #(다중 레벨) 와일드카드를 지원 [7][8].
  • Sparkplug B — 토픽을 spBv1.0/<group>/<msg_type>/<edge>/<device>로 고정하고 출생/사망 상태를 추가하는 의견이 강한 MQTT 프로파일 [9].
  • ISA-5.1 계기 태그(ISA-5.1 instrument tag) — 첫 글자가 측정 변수인 P&ID 루프 라벨(예: TT-101, PIC) [10].
  • ISA-95 7부 별칭화(ISA-95 Part 7 aliasing) — 서로 다른 시스템 식별자를 하나의 정규 객체와 동등하다고 선언하는 별칭 서비스 모델 [11].
  • QUDT 단위 IRI(QUDT unit IRI) — 값의 단위를 기계가 읽을 수 있게 못 박는 측정 단위의 전역 식별자(예: .../unit/DEG_C).
  • 태그 사전(Tag dictionary) — 모든 적법한 태그와 그 계층구조 배치, 단위, UNS 경로, Sparkplug 토픽의 거버넌스 등록부(gov.tag_dictionary).
  • 린터(naming.py) — 규칙에 부합하지 않거나 장비 계층구조에 존재하지 않는 태그를 거부하는 검사; 저장소에서는 make test 아래 test_ch04_naming_linter_passes_on_real_tags 테스트로 실행되며, 생산에서 프리커밋 훅이나 CI 관문으로 승격시키기에 자연스러운 대상.

다음 이야기

이제 모든 신호는 플랫폼 전체가 동의하는 이름, ISA-95 트리 속의 자리, 둘러볼 수 있는 UNS 경로, 타고 갈 Sparkplug 토픽, 그리고 그 모든 것을 정직하게 유지하는 출입문의 로봇을 갖습니다. 우리는 주소록을 만들었습니다. 다음 장 OT 언어로 말하기: OPC UA, MQTT, 그리고 Sparkplug B는 우리가 방금 설계한 전송 포맷을 이어받아 실제 연결 백본(backbone)을 세웁니다 — 자기 기술적(self-describing) OPC UA 서버, Mosquitto 브로커, 그리고 진짜 인증서 보안(certificate security)을 갖춘 Sparkplug B 발행자 — 그래서 주소가 붙은 그 신호들이 시뮬레이션된 바이오리액터에서 스택으로 살아서 흐르기 시작합니다.