/* * 한 사건이 **어느 물품을 건드렸나** — 브로드캐스팅이 바뀐 것만 만들 수 있게. 순수(import 없음). * * ── 왜 물품만인가 (2026-08-21 실측) ───────────────────────────────────────── * 브로드캐스팅 한 번이 만드는 payload 는 엔티티 수만큼이고, 그 대부분이 물품이다(실측: 엔티티 3,611 중 * 물품 2,400). 자리·설비·인원·자산은 모델이 정한 수라 적고(자리 최대 200, 평균 19), 작업·오더는 수백이다. * 그래서 **물품 축만 걸러도** 만드는 양의 3분의 2가 사라진다. * * 나머지 축은 **매번 전부 만든다.** 걸러서 얻는 것이 적고, 잘못 걸러서 잃는 것이 크기 때문이다 — * 예를 들어 물품이 한 자리에서 다른 자리로 가면 **두 자리의 점유가 함께 바뀌는데** EPCIS 사건은 읽은 * 지점 하나만 말한다. 그것으로 자리를 걸렀으면 떠난 자리의 점유가 낡은 값으로 남는다(오류 없이). * * ── 모르면 전부다 ─────────────────────────────────────────────────────────── * 이 함수가 `null` 을 돌려주면 「이 사건이 어느 물품을 건드렸는지 말할 수 없다」는 뜻이고, 그때 호출자는 * **전부 다시 만든다**(지금까지의 거동). 낯선 어휘가 오면 알리지 않고 빠뜨리는 것이 아니라 비싸게 안전한 * 쪽으로 떨어진다 — 화면이 낡는 것보다 계산을 한 번 더 하는 것이 낫다. */ /** 물품 태그는 개체 식별자(epc)다 — 자리·설비와 달리 id 가 아니다(entity-delta 와 같은 규약). */ function push(out: Set, v: unknown): void { if (typeof v === 'string' && v) out.add(v) } /** * 물품을 건드리지 않는다고 **선언된** 사건들 — 커널의 운영 델타와 에너지 사건. * * 이 목록은 커널 계약(`OP_EVENT`·`ENERGY_EVENT`)에서 왔다. 여기 없는 낱말이 오면 「모른다」로 떨어져 * 전부 다시 만든다 — 커널이 사건을 늘렸을 때 알리지 않고 빠뜨리는 대신 비싸게 안전한 쪽을 고른다. * * 근거: 미러의 리듀서에서 이 사건들은 작업·설비·인원·자산·오더·계량만 바꾼다(물품 축을 만지지 않는다). * 자리 점유는 이 사건들도 바꾸지만, 자리는 **어차피 매번 전부 다시 만든다**(위 주석). */ const ITEM_FREE_EVENTS = new Set([ 'task.status', 'equipment.status', 'person.status', 'asset.status', 'order.status', 'quality.output', 'attention.acked' ]) /** * 이 사건이 건드린 물품 태그들. **말할 수 없으면 `null`** — 호출자는 그때 전부 다시 만든다. * * 개체를 말하는 자리를 전부 본다: `epcList`(관측·커미셔닝) · `parentID`(집약의 부모) · * `childEPCs`(집약의 자식) · `inputEPCList`·`outputEPCList`(변환의 입출력). */ export function touchedItemKeys(envelope: any): Set | null { const type = envelope?.eventType if (typeof type !== 'string') return null const d = envelope?.data ?? envelope if (!d || typeof d !== 'object') return null if (type.startsWith('energy.') || ITEM_FREE_EVENTS.has(type)) return new Set() /* EPCIS 밖의 낯선 낱말 — 무엇을 바꿨는지 우리가 모른다. */ if (!type.startsWith('epcis.')) return null const out = new Set() for (const v of d.epcList ?? []) push(out, v) for (const v of d.childEPCs ?? []) push(out, v) for (const v of d.inputEPCList ?? []) push(out, v) for (const v of d.outputEPCList ?? []) push(out, v) push(out, d.parentID) /* * 개체를 하나도 말하지 않은 EPCIS 사건은 **부류 수량**으로 말한 것이다(`quantityList`). 그 부류의 * 물품이 어느 것인지는 여기서 알 수 없으므로 전부 다시 만든다. */ if (!out.size) return null return out }