/* * IEC 61850 적합성 기준 — **에너지 트윈이 표준의 어디를 채우고 있나.** * * ── 왜 따로 있나 ──────────────────────────────────────────────────────────── * 인스펙션의 적합성 패널은 트윈 종류와 무관하게 ISA-95 하나만 말하고 있었다. 에너지 트윈에서 그것은 * 거짓은 아니지만(설비·계층·속성은 에너지에서도 ISA-95 자원이다) **부족하다** — 계량기·차단기·태양광· * 축전지의 어휘를 주는 것은 IEC 61850 이고, 무엇이 비었는지도 그 목록으로만 셀 수 있다. * * ── 주장 수준을 못 박는다 ─────────────────────────────────────────────────── * 여기서 재는 것은 **어휘와 모델**이다. 프로토콜(MMS·GOOSE·샘플드값)을 구현했다는 뜻이 아니며, * 그 주장은 적합성 시험이 뒷받침해야 한다. 표 이름을 「IEC 61850 (vocabulary)」로 쓰는 이유다. * * ── 값은 관측이 아니라 선언이다 ───────────────────────────────────────────── * ISA-95 표와 같은 규율이다(`isa95-coverage.ts` 머리말): 사람이 판단해 적고, 틀리면 여기를 고친다. * 자동으로 알 수 있는 것 하나만 자동으로 한다 — 축 이름이 커널 축 목록에 없으면 구조가 없는 것이다. */ import { reconcileAxes, type Isa95Concept, type CoverageLevel } from './isa95-coverage.js' /* * Part 7-4 — 논리 노드 클래스. 이 프로파일이 쓰는 것만 적는다(표준 전체를 옮기지 않는다): * 계량·계측·개폐·발전·저장. */ const LOGICAL_NODES: Isa95Concept[] = [ // vocabulary-guard: allow — IEC 61850 의 「논리 노드(logical node)」는 표준 용어다(우리가 은퇴시킨 node 어휘가 아니다) /* * 계량(적산) — 커넥터가 누적 전력량을 실어 오고 상태가 지점별로 들고 있다. 화면은 계량 컴포넌트가 * 지점의 값을 보인다. */ { std: 'MMTR', part: '7-4', label: 'twin.iec61850.MMTR', axis: 'equipment', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.iec61850.note.MMTR' }, /* * 계측(유효전력·역률) — 표본이 수요 구간에 합쳐지고, 구간·피크·계약 대비가 그 위에 선다. * 이 프로파일에서 가장 두껍게 채운 자리다. */ { std: 'MMXU', part: '7-4', label: 'twin.iec61850.MMXU', axis: 'demandWindows', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.iec61850.note.MMXU' }, /* * ── 셋이 함께 올라갔다 (2026-08-15) ────────────────────────────────────── * 개폐·발전·저장은 오래 「구조만 있음」이었다. 능력이 상태 필드를 선언해 두었는데 **값을 받는 문이 * 없었기 때문**이다. 이제 셋 다 실제로 돈다: 설비 상태가 계약에 서고(`EquipmentState`), 커널이 * `energy.equipment` 로 받아 그 설비에 적고, 씬 컴포넌트가 그린다. * * 세 층을 함께 올리는 이유: 값이 없는데 화면만 만들면 영원히 「—」인 카드가 되고, 화면이 없는데 * 기능만 올리면 아무도 그 값을 보지 못한다. 「값이 먼저, 컴포넌트는 그 다음」의 결과다. */ { std: 'XCBR', part: '7-4', label: 'twin.iec61850.XCBR', axis: null, shownOn: ['equipment'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.iec61850.note.XCBR' }, { std: 'DPVA', part: '7-4', label: 'twin.iec61850.DPVA', axis: null, shownOn: ['equipment'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.iec61850.note.DPVA' }, { std: 'ZBAT', part: '7-4', label: 'twin.iec61850.ZBAT', axis: null, shownOn: ['equipment'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.iec61850.note.ZBAT' } ] export const IEC61850_CONCEPTS: Isa95Concept[] = [...LOGICAL_NODES] // vocabulary-guard: allow — 표준 용어(위 주석) export function iec61850Coverage(axes: string[]): { model: string concepts: (Isa95Concept & { axisMissing?: boolean })[] totals: { concepts: number; structure: number; behavior: number; surface: number } } { const concepts = IEC61850_CONCEPTS.map(c => reconcileAxes(c, new Set(axes))) const count = (k: 'structure' | 'behavior' | 'surface') => concepts.filter(c => c[k] === 'full').length return { /* 이름에 (vocabulary) 를 달아 둔다 — 프로토콜 적합성으로 읽히지 않게. */ model: 'IEC 61850 (vocabulary)', concepts, totals: { concepts: concepts.length, structure: count('structure'), behavior: count('behavior'), surface: count('surface') } } }