/** 커널 스냅샷에서 우리가 읽는 것만. 나머지 필드는 알 필요가 없다. */ export interface ModelGapSource { locations?: { id?: string; origin?: string; }[]; equipment?: { id?: string; origin?: string; }[]; persons?: { id?: string; origin?: string; }[]; /** 커널이 담을 줄 몰라 세어 둔 사건 — 종류별 개수와 처음·마지막 시각. */ unhandled?: { eventType: string; count: number; firstAtMs?: number; lastAtMs?: number; }[]; /** * EMS 상태 — **`energy` 안에 있다.** * * `unknownEquipment` 를 루트에서 읽고 있었다(2026-08-20에 잡음). 커널은 그것을 `EnergyState` 안에 * 담으므로, 루트에서 읽으면 **영원히 `null`** 이다 — 오류도 없이 「에너지 트윈이 아니다」로 읽힌다. * 나만 읽는 필드를 잘못된 경로에서 읽으면 이렇게 오류 없이 사라진다. * * 계량 지점의 `origin` 은 **없을 수 있다**(옛 스냅샷에서 이어받은 지점). 그때는 「선언되었다」가 * 아니라 **모른다**다. */ energy?: { points?: { id?: string; origin?: string; }[]; /** 모델이 모르는 설비가 에너지 상태를 보내 온 횟수. 0 이면 커널이 이 칸을 만들지 않는다. */ unknownEquipment?: number; }; } /** * 화면에 내는 id 수 상한. * * 오타 하나를 찾는 데 스무 개면 충분하고, 원천이 수천 개를 보내는 경우(모델을 아예 안 맞춘 경우)에 * 목록으로 화면을 채울 이유가 없다. **넘친 수는 함께 낸다** — 알리지 않고 자르지 않는다. */ export declare const GAP_ID_LIMIT = 20; export interface ModelGapAxis { /** 관측으로만 알게 된 id 수. */ count: number; /** 그중 화면에 싣는 것(최대 `GAP_ID_LIMIT`). */ ids: string[]; /** 상한에서 밀려난 수. 0 이면 전부 실었다. */ more: number; /** * 출처를 **모르는** 수 — 표시가 없는 항목. * * 「선언되었다」로 세지 않는다. 계량기는 이 표시가 늦게 생겼으므로 옛 스냅샷에서 이어받은 지점에는 * 칸이 없다(`undefined`). 그것을 마스터로 세면 오타를 놓치고, 관측으로 세면 정상을 문제로 만든다. * 둘 다 틀리므로 **모른다고 센다.** */ unknown: number; } export interface ModelGapView { locations: ModelGapAxis; equipment: ModelGapAxis; persons: ModelGapAxis; /** * EMS 계량 지점. * * ── 이 축이 가장 무겁다 ──────────────────────────────────────────────────── * 모르는 `meterId` 는 알리지 않고 새 계량기가 되고 그 kW 가 그 현장의 **수요 구간·피크·요금 판정**에 * 더해진다. 오타 하나면 진짜 계량기는 멈추고 유령이 누적된다 — 숫자는 그럴듯하고 틀린다. 자리 오타는 * 스키매틱이 비는 것으로 드러나지만, 이건 그럴듯한 숫자로 나온다. * * 커널이 `origin` 을 **표본마다 다시 맞춘다** — 선언의 정본은 모델이고 모델은 채택으로 늘어나므로, * 한 번 찍고 두면 나중에 선언된 계량기가 영원히 `observed` 로 남는다. 그래서 이 값은 「지금 모델에 * 있는가」의 답이고, 「한 번 오타였다」의 답이 아니다. */ meters: ModelGapAxis; /** 세 축의 합 — 화면이 「있나 없나」를 한 번에 묻기 위한 값. */ total: number; /** 커널이 담을 줄 모른 사건들(종류별). 참조 문제와 다른 사실이므로 따로 낸다. */ unhandled: { eventType: string; count: number; lastAt: string | null; }[]; /** EMS: 모델이 모르는 설비의 에너지 보고 횟수. 없으면 `null`(0 과 구별한다). */ unknownEquipmentReports: number | null; /** * 아직 덮지 못하는 축 — **모르는 것을 모른다고 말한다.** * * 계량기는 커널 0.7.39 부터 덮인다(그 전에는 `origin` 표시가 없어 오타를 찾아 줄 수 없었다). * 비어 있는 것이 정상이고, 새 축이 표시 없이 들어오면 여기에 이름을 넣는다 — 화면이 「다 찾아 * 준다」고 말하지 않게. */ notCovered: string[]; } /** 계량기 축이 아직 덮이지 않는 이유 — 화면이 그대로 말할 수 있게 이름을 여기 둔다. */ export declare const NOT_COVERED_METERS = "meters"; /** * 스냅샷 → 모델 갭. * * 스냅샷이 없으면 `null` 이다 — **0 이 아니다.** 트윈이 돌지 않으면 비교할 상대가 없고, 「없다」와 * 「세어 보지 못했다」는 다른 사실이다(모델 인스펙션이 같은 규율을 쓴다). */ export declare function modelGapOf(snapshot: ModelGapSource | null | undefined): ModelGapView | null;