@things-factory/headless-twin 10.0.12 → 10.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +57 -2
  2. package/dist-server/engine/canonical-ingest.js +43 -2
  3. package/dist-server/engine/canonical-ingest.js.map +1 -1
  4. package/dist-server/engine/command-routing.js +1 -1
  5. package/dist-server/engine/command-routing.js.map +1 -1
  6. package/dist-server/engine/index.d.ts +3 -0
  7. package/dist-server/engine/index.js +4 -0
  8. package/dist-server/engine/index.js.map +1 -1
  9. package/dist-server/engine/ingest-health.d.ts +335 -0
  10. package/dist-server/engine/ingest-health.js +434 -0
  11. package/dist-server/engine/ingest-health.js.map +1 -0
  12. package/dist-server/engine/integration-coverage.d.ts +76 -0
  13. package/dist-server/engine/integration-coverage.js +73 -0
  14. package/dist-server/engine/integration-coverage.js.map +1 -0
  15. package/dist-server/engine/integration-probes.d.ts +65 -0
  16. package/dist-server/engine/integration-probes.js +100 -0
  17. package/dist-server/engine/integration-probes.js.map +1 -0
  18. package/dist-server/engine/integration-runner.d.ts +45 -0
  19. package/dist-server/engine/integration-runner.js +59 -0
  20. package/dist-server/engine/integration-runner.js.map +1 -0
  21. package/dist-server/engine/integration-target-profile.d.ts +57 -0
  22. package/dist-server/engine/integration-target-profile.js +79 -0
  23. package/dist-server/engine/integration-target-profile.js.map +1 -0
  24. package/dist-server/engine/kpi-fold.d.ts +27 -0
  25. package/dist-server/engine/kpi-fold.js +47 -4
  26. package/dist-server/engine/kpi-fold.js.map +1 -1
  27. package/dist-server/engine/kpi-query.js +128 -14
  28. package/dist-server/engine/kpi-query.js.map +1 -1
  29. package/dist-server/engine/live-feed-registry.js +2 -16
  30. package/dist-server/engine/live-feed-registry.js.map +1 -1
  31. package/dist-server/engine/load-meter.d.ts +1 -1
  32. package/dist-server/engine/load-meter.js +12 -3
  33. package/dist-server/engine/load-meter.js.map +1 -1
  34. package/dist-server/engine/log.d.ts +18 -0
  35. package/dist-server/engine/log.js +80 -0
  36. package/dist-server/engine/log.js.map +1 -0
  37. package/dist-server/engine/loop-lag.d.ts +54 -0
  38. package/dist-server/engine/loop-lag.js +87 -0
  39. package/dist-server/engine/loop-lag.js.map +1 -0
  40. package/dist-server/engine/model-gap.d.ts +108 -0
  41. package/dist-server/engine/model-gap.js +95 -0
  42. package/dist-server/engine/model-gap.js.map +1 -0
  43. package/dist-server/engine/restart-policy.d.ts +3 -3
  44. package/dist-server/engine/restart-policy.js +4 -4
  45. package/dist-server/engine/restart-policy.js.map +1 -1
  46. package/dist-server/engine/runtime-key.js +1 -1
  47. package/dist-server/engine/runtime-key.js.map +1 -1
  48. package/dist-server/engine/stage-path.d.ts +83 -0
  49. package/dist-server/engine/stage-path.js +118 -0
  50. package/dist-server/engine/stage-path.js.map +1 -0
  51. package/dist-server/engine/twin-engine.d.ts +352 -16
  52. package/dist-server/engine/twin-engine.js +1037 -166
  53. package/dist-server/engine/twin-engine.js.map +1 -1
  54. package/dist-server/index.js +34 -2
  55. package/dist-server/index.js.map +1 -1
  56. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.d.ts +5 -0
  57. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +58 -0
  58. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -0
  59. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.d.ts +5 -0
  60. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +55 -0
  61. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -0
  62. package/dist-server/migrations/index.js +7 -1
  63. package/dist-server/migrations/index.js.map +1 -1
  64. package/dist-server/service/index.d.ts +5 -2
  65. package/dist-server/service/index.js +18 -7
  66. package/dist-server/service/index.js.map +1 -1
  67. package/dist-server/service/reference/discovery-result.d.ts +1 -1
  68. package/dist-server/service/reference/discovery-result.js +1 -1
  69. package/dist-server/service/reference/discovery-result.js.map +1 -1
  70. package/dist-server/service/reference/index.d.ts +4 -1
  71. package/dist-server/service/reference/index.js +6 -1
  72. package/dist-server/service/reference/index.js.map +1 -1
  73. package/dist-server/service/reference/reference-adapter.d.ts +184 -3
  74. package/dist-server/service/reference/reference-adapter.js +28 -4
  75. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  76. package/dist-server/service/reference/reference-assessment.d.ts +68 -0
  77. package/dist-server/service/reference/reference-assessment.js +136 -0
  78. package/dist-server/service/reference/reference-assessment.js.map +1 -0
  79. package/dist-server/service/reference/reference-live.d.ts +12 -1
  80. package/dist-server/service/reference/reference-live.js +116 -11
  81. package/dist-server/service/reference/reference-live.js.map +1 -1
  82. package/dist-server/service/reference/reference-master.d.ts +47 -1
  83. package/dist-server/service/reference/reference-master.js +34 -4
  84. package/dist-server/service/reference/reference-master.js.map +1 -1
  85. package/dist-server/service/reference/reference-probe.d.ts +92 -0
  86. package/dist-server/service/reference/reference-probe.js +186 -0
  87. package/dist-server/service/reference/reference-probe.js.map +1 -0
  88. package/dist-server/service/reference/reference-progress-subscription.d.ts +17 -0
  89. package/dist-server/service/reference/reference-progress-subscription.js +94 -0
  90. package/dist-server/service/reference/reference-progress-subscription.js.map +1 -0
  91. package/dist-server/service/reference/reference-progress.d.ts +38 -0
  92. package/dist-server/service/reference/reference-progress.js +71 -0
  93. package/dist-server/service/reference/reference-progress.js.map +1 -0
  94. package/dist-server/service/reference/reference-resolver.js +210 -43
  95. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  96. package/dist-server/service/reference/twin-reference.d.ts +32 -0
  97. package/dist-server/service/reference/twin-reference.js +10 -0
  98. package/dist-server/service/reference/twin-reference.js.map +1 -1
  99. package/dist-server/service/twin-control/twin-control-mutation.js +4 -3
  100. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  101. package/dist-server/service/twin-event/journal-count.d.ts +21 -0
  102. package/dist-server/service/twin-event/journal-count.js +26 -0
  103. package/dist-server/service/twin-event/journal-count.js.map +1 -0
  104. package/dist-server/service/twin-event/twin-event-keys.d.ts +9 -0
  105. package/dist-server/service/twin-event/twin-event-keys.js +15 -18
  106. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  107. package/dist-server/service/twin-event/twin-event-type.d.ts +3 -1
  108. package/dist-server/service/twin-event/twin-event-type.js +18 -1
  109. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  110. package/dist-server/service/twin-event/twin-event.d.ts +1 -0
  111. package/dist-server/service/twin-event/twin-event.js +9 -1
  112. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  113. package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
  114. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  115. package/dist-server/service/twin-ingest-window/index.d.ts +5 -0
  116. package/dist-server/service/twin-ingest-window/index.js +10 -0
  117. package/dist-server/service/twin-ingest-window/index.js.map +1 -0
  118. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +12 -0
  119. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +123 -0
  120. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -0
  121. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.d.ts +1 -0
  122. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +51 -0
  123. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -0
  124. package/dist-server/service/twin-ingest-window/twin-ingest-window.d.ts +16 -0
  125. package/dist-server/service/twin-ingest-window/twin-ingest-window.js +108 -0
  126. package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +1 -0
  127. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  128. package/dist-server/service/twin-journal/twin-journal-query.js +58 -5
  129. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  130. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +14 -0
  131. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +72 -2
  132. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  133. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  134. package/dist-server/service/twin-metrics/twin-metrics-query.js +78 -3
  135. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  136. package/dist-server/service/twin-model/axis-journal-evidence.d.ts +2 -0
  137. package/dist-server/service/twin-model/axis-journal-evidence.js +89 -0
  138. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -0
  139. package/dist-server/service/twin-model/epcis-coverage.d.ts +1 -1
  140. package/dist-server/service/twin-model/epcis-coverage.js +19 -7
  141. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  142. package/dist-server/service/twin-model/iec61850-coverage.d.ts +1 -1
  143. package/dist-server/service/twin-model/iec61850-coverage.js +18 -7
  144. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  145. package/dist-server/service/twin-model/isa95-coverage.d.ts +14 -0
  146. package/dist-server/service/twin-model/isa95-coverage.js +23 -9
  147. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  148. package/dist-server/service/twin-model/item-ref.d.ts +24 -0
  149. package/dist-server/service/twin-model/item-ref.js +88 -0
  150. package/dist-server/service/twin-model/item-ref.js.map +1 -0
  151. package/dist-server/service/twin-model/name-index.d.ts +36 -0
  152. package/dist-server/service/twin-model/name-index.js +116 -0
  153. package/dist-server/service/twin-model/name-index.js.map +1 -0
  154. package/dist-server/service/twin-model/project-structure.js +1 -1
  155. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  156. package/dist-server/service/twin-model/status-tally.d.ts +9 -0
  157. package/dist-server/service/twin-model/status-tally.js +37 -0
  158. package/dist-server/service/twin-model/status-tally.js.map +1 -0
  159. package/dist-server/service/twin-model/twin-lineage-query.js +40 -12
  160. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  161. package/dist-server/service/twin-model/twin-model-item-query.js +38 -39
  162. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  163. package/dist-server/service/twin-model/twin-model-query.js +156 -4
  164. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  165. package/dist-server/service/twin-model/twin-model-tree-query.js +7 -0
  166. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  167. package/dist-server/service/twin-readiness/index.d.ts +2 -0
  168. package/dist-server/service/twin-readiness/index.js +6 -0
  169. package/dist-server/service/twin-readiness/index.js.map +1 -0
  170. package/dist-server/service/twin-readiness/twin-readiness-query.d.ts +3 -0
  171. package/dist-server/service/twin-readiness/twin-readiness-query.js +103 -0
  172. package/dist-server/service/twin-readiness/twin-readiness-query.js.map +1 -0
  173. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  174. package/dist-server/service/twin-space/twin-space-resolver.js +5 -4
  175. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  176. package/dist-shared/entity-delta.d.ts +23 -3
  177. package/dist-shared/entity-delta.js +12 -8
  178. package/dist-shared/entity-delta.js.map +1 -1
  179. package/dist-shared/kpi-broadcast.js +1 -1
  180. package/dist-shared/kpi-broadcast.js.map +1 -1
  181. package/dist-shared/touched-items.d.ts +7 -0
  182. package/dist-shared/touched-items.js +80 -0
  183. package/dist-shared/touched-items.js.map +1 -0
  184. package/package.json +7 -7
  185. package/server/engine/canonical-ingest.ts +92 -7
  186. package/server/engine/command-routing.ts +1 -1
  187. package/server/engine/index.ts +4 -0
  188. package/server/engine/ingest-health.ts +704 -0
  189. package/server/engine/integration-coverage.ts +147 -0
  190. package/server/engine/integration-probes.ts +144 -0
  191. package/server/engine/integration-runner.ts +95 -0
  192. package/server/engine/integration-target-profile.ts +103 -0
  193. package/server/engine/kpi-fold.ts +72 -4
  194. package/server/engine/kpi-query.ts +129 -15
  195. package/server/engine/live-feed-registry.ts +2 -1
  196. package/server/engine/load-meter.ts +12 -3
  197. package/server/engine/log.ts +72 -0
  198. package/server/engine/loop-lag.ts +120 -0
  199. package/server/engine/model-gap.ts +168 -0
  200. package/server/engine/restart-policy.ts +4 -4
  201. package/server/engine/runtime-key.ts +1 -1
  202. package/server/engine/stage-path.ts +172 -0
  203. package/server/engine/twin-engine.ts +1135 -162
  204. package/server/index.ts +35 -2
  205. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +54 -0
  206. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +53 -0
  207. package/server/migrations/index.ts +7 -1
  208. package/server/service/index.ts +11 -0
  209. package/server/service/reference/discovery-result.ts +1 -1
  210. package/server/service/reference/index.ts +6 -1
  211. package/server/service/reference/reference-adapter.ts +197 -5
  212. package/server/service/reference/reference-assessment.ts +215 -0
  213. package/server/service/reference/reference-live.ts +123 -11
  214. package/server/service/reference/reference-master.ts +64 -4
  215. package/server/service/reference/reference-probe.ts +264 -0
  216. package/server/service/reference/reference-progress-subscription.ts +73 -0
  217. package/server/service/reference/reference-progress.ts +95 -0
  218. package/server/service/reference/reference-resolver.ts +200 -12
  219. package/server/service/reference/twin-reference.ts +39 -1
  220. package/server/service/twin-control/twin-control-mutation.ts +4 -3
  221. package/server/service/twin-event/journal-count.ts +53 -0
  222. package/server/service/twin-event/twin-event-keys.ts +16 -1
  223. package/server/service/twin-event/twin-event-type.ts +26 -2
  224. package/server/service/twin-event/twin-event.ts +7 -0
  225. package/server/service/twin-forecast/twin-forecast-query.ts +2 -1
  226. package/server/service/twin-ingest-window/index.ts +7 -0
  227. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +125 -0
  228. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +57 -0
  229. package/server/service/twin-ingest-window/twin-ingest-window.ts +113 -0
  230. package/server/service/twin-instance/twin-instance.ts +1 -1
  231. package/server/service/twin-journal/twin-journal-query.ts +59 -5
  232. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +77 -3
  233. package/server/service/twin-metrics/twin-metrics-query.ts +81 -3
  234. package/server/service/twin-model/axis-journal-evidence.ts +57 -0
  235. package/server/service/twin-model/epcis-coverage.ts +2 -8
  236. package/server/service/twin-model/iec61850-coverage.ts +2 -8
  237. package/server/service/twin-model/isa95-coverage.ts +22 -9
  238. package/server/service/twin-model/item-ref.ts +80 -0
  239. package/server/service/twin-model/name-index.ts +94 -0
  240. package/server/service/twin-model/project-structure.ts +1 -1
  241. package/server/service/twin-model/status-tally.ts +37 -0
  242. package/server/service/twin-model/twin-lineage-query.ts +38 -9
  243. package/server/service/twin-model/twin-model-item-query.ts +26 -27
  244. package/server/service/twin-model/twin-model-query.ts +155 -4
  245. package/server/service/twin-model/twin-model-tree-query.ts +7 -0
  246. package/server/service/twin-readiness/index.ts +3 -0
  247. package/server/service/twin-readiness/twin-readiness-query.ts +96 -0
  248. package/server/service/twin-space/twin-space-resolver.ts +5 -4
  249. package/shared/entity-delta.ts +31 -7
  250. package/shared/kpi-broadcast.ts +1 -1
  251. package/shared/touched-items.ts +73 -0
  252. package/test/axis-journal-evidence.test.ts +71 -0
  253. package/test/axis-read.test.ts +101 -1
  254. package/test/boot-resume.test.ts +92 -26
  255. package/test/broadcast-cost-baseline.test.ts +200 -0
  256. package/test/broadcast-period.test.ts +58 -0
  257. package/test/canonical-ingest-vocabularies.test.ts +36 -1
  258. package/test/canonical-quantity-door.test.ts +61 -0
  259. package/test/command-routing.test.ts +1 -1
  260. package/test/declared-location-types.test.ts +89 -0
  261. package/test/discovery-result.test.ts +1 -1
  262. package/test/duration-estimators.test.ts +1 -1
  263. package/test/entity-delta.test.ts +2 -2
  264. package/test/ingest-bench.test.ts +3 -3
  265. package/test/ingest-health-engine.test.ts +248 -0
  266. package/test/ingest-health-wiring.test.ts +119 -0
  267. package/test/ingest-health.test.ts +306 -0
  268. package/test/ingest-history.test.ts +247 -0
  269. package/test/integration-probes.test.ts +103 -0
  270. package/test/integration-runner.test.ts +95 -0
  271. package/test/item-ref.test.ts +78 -0
  272. package/test/journal-read-discipline.test.ts +177 -0
  273. package/test/journal-retention.test.ts +133 -0
  274. package/test/journal-sort-axis.test.ts +142 -0
  275. package/test/journal-write-door.test.ts +110 -0
  276. package/test/journal-write-trend.test.ts +115 -0
  277. package/test/kernel-kind-guard.test.ts +4 -4
  278. package/test/kpi-fold.test.ts +90 -3
  279. package/test/kpi-query-bench.test.ts +2 -2
  280. package/test/lineage-survives-restart.test.ts +17 -1
  281. package/test/live-cursor-wiring.test.ts +87 -0
  282. package/test/live-feed-registry.test.ts +9 -1
  283. package/test/live-kernel-facts.test.ts +7 -7
  284. package/test/live-mirror-parity.test.ts +6 -0
  285. package/test/load-meter.test.ts +29 -15
  286. package/test/local-declarations.test.ts +1 -1
  287. package/test/log-stamp.test.ts +59 -0
  288. package/test/loop-lag.test.ts +82 -0
  289. package/test/model-gap.test.ts +153 -0
  290. package/test/oee-accumulator.test.ts +4 -0
  291. package/test/operational-vocabulary.test.ts +4 -4
  292. package/test/read-failure-visible.test.ts +145 -0
  293. package/test/reference-grounding.test.ts +70 -0
  294. package/test/resolve-ts-siblings.mjs +52 -0
  295. package/test/restart-policy.test.ts +7 -7
  296. package/test/revision-axis.test.ts +93 -0
  297. package/test/runtime-key.test.ts +2 -2
  298. package/test/scale-twin-bench.test.ts +2 -2
  299. package/test/spec-coverage.test.ts +1 -1
  300. package/test/stage-path.test.ts +95 -0
  301. package/test/standard-coverage.test.ts +15 -3
  302. package/test/status-tally.test.ts +55 -0
  303. package/test/structure-revision-db.test.ts +4 -0
  304. package/test/tenant-registry-db.test.ts +1 -1
  305. package/test/time-range.test.ts +152 -0
  306. package/test/touched-items.test.ts +61 -0
  307. package/test/twin-event-keys.test.ts +10 -2
  308. package/test/twin-model-item-db.test.ts +2 -0
  309. package/test/warm-start-seam.test.ts +4 -0
  310. package/test/yield-loop.test.ts +57 -2
  311. package/tsconfig.shared.tsbuildinfo +1 -1
  312. package/tsconfig.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"entity-delta.js","sourceRoot":"","sources":["../shared/entity-delta.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 트윈 State 스냅샷 → 라이브 바인딩 delta 목록 (순수 매핑 — pubsub/도메인 무관 → 단위 테스트 가능).\n * publishEntityData 가 이 목록을 엔티티별 시그니처로 dedup 후 data(tag) 채널로 발행한다.\n *\n * ── 규율: 레퍼런스가 아는 것은 **전부** 내보낸다 ────────────────────────────\n * 트윈에서 사소한 속성은 없다. 소속 구역·소속 팔레트·수량·만료처럼 곁가지로 보이는 값이 실제로는\n * 구역 롤업·적재 표현·로트 추적의 전제다. 그래서 여기서 **필드를 손으로 골라 담지 않는다.**\n *\n * 예전에는 엔티티마다 필요할 것 같은 필드만 열거했고, 그 결과 계약에 있는 것이 조용히 누락됐다\n * (2026-08-01 감사: 노드 parentId 누락 · 작업 전량 누락 · 물품 전량 누락). 화이트리스트 방식은\n * **계약이 자라도 화면이 따라오지 못한다** — 커널이 속성을 늘릴 때마다 이 파일을 고쳐야 하고, 고치는\n * 것을 잊으면 아무 신호 없이 정보가 사라진다.\n *\n * 그래서 반대로 한다: **상태 객체가 가진 것을 그대로 통과**시키고, 통과시키지 **않을** 것만 이유와 함께\n * 열거한다(EXCLUDED). 새 속성은 자동으로 화면에 도달하고, 빼려면 이유를 적어야 한다.\n *\n * ── 표식은 도메인 낱말을 훔치지 않는다 ─────────────────────────────────────\n * 예전 payload 는 엔티티 종류 표식을 `kind` 로 실었다. 그런데 `kind` 는 **도메인의 낱말**이다 —\n * 무버 kind=cutter, 작업 kind=putaway, 오더 kind=workorder. 표식이 그 이름을 차지하면 도메인 속성을\n * 실을 자리가 없어진다(같은 병을 오늘 ox-timeline 의 `group` 에서도 고쳤다).\n * 그래서 표식은 `entity`(우리 이름)로 옮기고 `kind` 는 도메인에 돌려준다.\n *\n * payload 계약(씬 컴포넌트가 소비): `entity`(표식) + 그 엔티티의 모든 속성(도메인 `kind` 포함).\n * entity = location · equipment · person · asset · order · task · item,\n * 그리고 집약 태그(__orders__ · __tasks__ · __items__ · __persons__ · __assets__).\n */\n\nexport interface EntityDelta {\n tag: string\n data: any\n}\n\n/** 집약 고정 태그 — 클라 provider 와 일치해야 한다(런타임 생성 엔티티는 개별 배치가 불가하므로 집약 뷰). */\n/*\n * 집약 채널의 접두사 — **실제 태그는 트윈 식별자를 붙여 만든다.**\n *\n * 예전에는 이 상수가 곧 태그였다. 그런데 발행 키는 (도메인, 태그)이고, **한 공간에 트윈이 여럿**\n * 있으면(실측: 3곳 × 3개) 셋이 같은 태그로 발행해 서로 덮었다. 보드의 주문 목록판이 세 공장의 주문\n * 중 마지막에 도착한 것을 보여 준다 — 오류 없이.\n *\n * 구간 성과 태그가 이미 `__kpi__:<트윈>` 으로 트윈을 붙이고 있었다(`kpi-broadcast.ts`). 같은 규약으로 맞춘다.\n */\nexport const ORDERS_TAG_PREFIX = '__orders__'\nexport const TASKS_TAG_PREFIX = '__tasks__'\nexport const ITEMS_TAG_PREFIX = '__items__'\nexport const PERSONS_TAG_PREFIX = '__persons__'\nexport const ASSETS_TAG_PREFIX = '__assets__'\n/** 에너지 집약 — 수요 구간·피크·계약은 **트윈 하나의 사실**이라 개체 태그가 없다. */\nexport const ENERGY_TAG_PREFIX = '__energy__'\n\n/** 이 트윈의 집약 채널 태그. 트윈 식별자가 없으면 만들지 않는다 — 덮어쓰기의 원인이었다. */\nexport function aggregateTag(prefix: string, instanceId: string): string {\n if (!instanceId) throw new Error('aggregateTag: instanceId required — 트윈 구분이 없는 태그는 다른 트윈의 것을 덮는다')\n\n return `${prefix}:${instanceId}`\n}\n\nexport const ordersTag = (instanceId: string) => aggregateTag(ORDERS_TAG_PREFIX, instanceId)\nexport const tasksTag = (instanceId: string) => aggregateTag(TASKS_TAG_PREFIX, instanceId)\nexport const itemsTag = (instanceId: string) => aggregateTag(ITEMS_TAG_PREFIX, instanceId)\nexport const personsTag = (instanceId: string) => aggregateTag(PERSONS_TAG_PREFIX, instanceId)\nexport const assetsTag = (instanceId: string) => aggregateTag(ASSETS_TAG_PREFIX, instanceId)\nexport const energyTag = (instanceId: string) => aggregateTag(ENERGY_TAG_PREFIX, instanceId)\n\n/**\n * 통과시키지 않는 필드 — **빼는 데는 이유가 필요하다.**\n *\n * · `id`/`epc` — 태그로 이미 나가므로 개별 payload 에 중복해 싣지 않는다(집약 뷰에는 넣는다).\n * · `oee` — 원시 누적(idleMs 등)이 매 delta 증가해 \"무변화 생략\" 을 무력화한다. shapeOee 로 정형화해 얹는다.\n */\nconst EXCLUDED = new Set(['id', 'epc', 'oee'])\n\n/**\n * OEE 원시 비율/카운트 → 표시용 정수 퍼센트 + 카운트.\n * 원시 누적 ms(idleMs 등)는 매 delta 증가해 무변화-생략을 무력화하므로 제외(유휴 자원의 매초 재방송 억제).\n * oee 미측정이면 undefined.\n */\nfunction shapeOee(o: any): any {\n if (!o) return undefined\n return {\n overall: Math.round((o.overall ?? 0) * 100),\n availability: Math.round((o.availability ?? 0) * 100),\n performance: Math.round((o.performance ?? 0) * 100),\n quality: Math.round((o.quality ?? 0) * 100),\n good: o.goodCount ?? 0,\n scrap: o.scrapCount ?? 0\n }\n}\n\n/**\n * 엔티티 하나의 payload — `entity` 표식 + 나머지 전부(도메인 `kind` 포함).\n *\n * `undefined` 값은 싣지 않는다(키의 유무가 시그니처를 흔들지 않게). 값이 없는 것과 키가 없는 것은\n * 소비처에서 같으므로 안전하고, 무변화 생략이 잘 듣는다.\n */\nfunction payloadOf(entityKind: string, entity: any): any {\n /* 표식은 `entity` — 도메인의 `kind` 를 덮지 않는다. */\n const data: any = { entity: entityKind }\n for (const [k, v] of Object.entries(entity ?? {})) {\n if (EXCLUDED.has(k) || v === undefined) continue\n data[k] = v\n }\n return data\n}\n\n/**\n * 집약 뷰의 항목 — 개별 payload 와 같은 내용 + 식별자.\n * 집약은 \"전체를 한 컴포넌트가 추적\" 하는 용도라 식별자가 있어야 한다.\n */\nfunction aggregateItem(entity: any, idKey: 'id' | 'epc'): any {\n const out: any = { [idKey]: entity?.[idKey] }\n for (const [k, v] of Object.entries(entity ?? {})) {\n if (k === idKey || k === 'oee' || v === undefined) continue\n out[k] = v\n }\n const shaped = shapeOee(entity?.oee)\n if (shaped) out.oee = shaped\n return out\n}\n\n/**\n * 물품 집약의 상한 — 큰 현장은 물품이 수만 개라, 전량을 매 방송에 실으면 전달이 현장을 느리게 한다.\n *\n * **조용히 자르지 않는다**: 넘치면 `total`(전체 수)과 `truncated`(잘린 수)를 함께 실어, 소비처가\n * \"이게 전부\" 라고 오해하지 않게 한다. 개별 물품은 태그별 delta 로 따로 나가고 그쪽은 시그니처 dedup\n * 이 걸려 **변한 것만** 재방송된다.\n */\nexport const ITEMS_AGGREGATE_LIMIT = 500\n\n/**\n * 자리별 에너지 — **그 자리의 계량기들이 잰 것**을 그 자리 태그에 실을 수 있게 모은다.\n *\n * ── 왜 자리에도 필요한가 (2026-08-14) ───────────────────────────────────────\n * 카탈로그는 자리에도 `metered` 를 준다(수전·분기·구역 계량은 재는 자리다). 그런데 값은 계량기\n * **설비** 태그로만 나갔다. 그래서 계통도의 분기 자리에 계량 컴포넌트를 놓으면 **영원히 「—」** 였다 —\n * 능력은 「잰다」고 말하는데 값이 오지 않는 자리.\n *\n * 「이 구간이 얼마를 먹나」는 자리의 사실이고, 사람은 계통도의 자리에 붙여 본다.\n *\n * ── 계층은 여기서 문제가 되지 않는다 ────────────────────────────────────────\n * 각 자리에는 **그 자리에 붙은 계량기만** 더한다(수전 자리에는 수전 계량기, 분기 자리에는 그 분기의\n * 것). 커널이 부하를 뿌리로만 세는 것과 다른 층의 계산이다 — 여기서는 자리마다 자기 것을 말한다.\n *\n * 근거를 함께 낸다(`meters`) — 그 자리의 수가 어느 계량기에서 왔는지 되짚을 수 있어야 한다.\n */\nfunction energyByLocation(state: any): Map<string, Record<string, unknown>> {\n const out = new Map<string, Record<string, unknown>>()\n const points = state?.energy?.points\n if (!Array.isArray(points) || !points.length) return out\n\n /* 계량기 → 자리. 자리를 모르는 계량기는 자리 payload 에 실을 데가 없다(설비 태그로만 나간다). */\n const locOf = new Map<string, string>()\n for (const e of state?.equipment ?? []) if (e?.id && e?.homeLocation) locOf.set(e.id, e.homeLocation)\n\n for (const pt of points) {\n const loc = pt?.id ? locOf.get(pt.id) : undefined\n if (!loc) continue\n const acc = out.get(loc) ?? { meters: [] as string[], samplesInWindow: 0 }\n ;(acc.meters as string[]).push(pt.id)\n acc.samplesInWindow = Number(acc.samplesInWindow ?? 0) + (Number(pt.samplesInWindow) || 0)\n /* 값은 **있는 것만** 더한다 — 하나도 없으면 키를 만들지 않는다(0 은 「안 썼다」가 된다). */\n if (Number.isFinite(Number(pt.kW))) acc.kW = Number(acc.kW ?? 0) + Number(pt.kW)\n if (Number.isFinite(Number(pt.kWh))) acc.kWh = Number(acc.kWh ?? 0) + Number(pt.kWh)\n /* 나이는 **가장 최근**을 쓴다 — 자리에 계량기가 여럿이면 그중 마지막 관측이 그 자리의 나이다. */\n if (pt.atMs !== undefined) {\n const iso = new Date(Number(pt.atMs)).toISOString()\n if (!acc.measuredAt || String(acc.measuredAt) < iso) acc.measuredAt = iso\n }\n out.set(loc, acc)\n }\n return out\n}\n\nexport function buildEntityDeltas(state: any, instanceId: string): EntityDelta[] {\n const out: EntityDelta[] = []\n const locEnergy = energyByLocation(state)\n\n for (const n of state?.locations ?? []) {\n /* 에너지는 자리 payload 에 **합친다** — 같은 태그로 두 번 보내면 뒤엣것이 앞을 덮는다. */\n const energy = locEnergy.get(n.id)\n out.push({ tag: n.id, data: energy ? { ...payloadOf('location', n), ...energy } : payloadOf('location', n) })\n }\n for (const m of state?.equipment ?? []) {\n /* Mobile(location/motion) + Operable(status/held) + Processable(oee) — 한 payload 로.\n * oee 만 정형화가 필요해 따로 얹는다(나머지는 통과). */\n const data = payloadOf('equipment', m)\n const oee = shapeOee(m?.oee)\n if (oee) data.oee = oee\n out.push({ tag: m.id, data })\n }\n /* 사람 — 설비와 별개 엔티티다. 같은 통과 규칙(가진 것을 전부 내보낸다). */\n for (const p of state?.persons ?? []) {\n out.push({ tag: p.id, data: payloadOf('person', p) })\n }\n /* 물리 자산(반복사용) — 물품도 설비도 아닌 네 번째 자원. */\n for (const a of state?.assets ?? []) {\n out.push({ tag: a.id, data: payloadOf('asset', a) })\n }\n for (const o of state?.orders ?? []) {\n out.push({ tag: o.id, data: payloadOf('order', o) })\n }\n for (const t of state?.tasks ?? []) {\n out.push({ tag: t.id, data: payloadOf('task', t) })\n }\n for (const it of state?.items ?? []) {\n /* 물품의 태그는 epc(개체 식별자) — 노드·무버와 달리 id 가 아니다. */\n if (it?.epc) out.push({ tag: it.epc, data: payloadOf('item', it) })\n }\n\n /* ── 집약 뷰 ──────────────────────────────────────────────────────────────\n * 런타임에 생기고 사라지는 엔티티(오더·작업·물품)는 보드에 개별 배치할 수 없다 → 한 컴포넌트가\n * 전체를 추적하는 집약 태그를 항상 방출한다. */\n const orders = state?.orders ?? []\n out.push({ tag: ordersTag(instanceId), data: { entity: 'orders', orders: orders.map((o: any) => aggregateItem(o, 'id')) } })\n\n const tasks = state?.tasks ?? []\n out.push({ tag: tasksTag(instanceId), data: { entity: 'tasks', tasks: tasks.map((t: any) => aggregateItem(t, 'id')) } })\n\n /* 사람 집약 — 인원은 보드에 개별 배치할 수도 있지만(설비처럼), 전체를 한눈에 보는 소비처도 있다.\n * 인원을 선언하지 않은 트윈에서는 빈 배열이 나간다(없음과 모름을 구별). */\n const persons = state?.persons ?? []\n out.push({ tag: personsTag(instanceId), data: { entity: 'persons', persons: persons.map((p: any) => aggregateItem(p, 'id')) } })\n\n const assets = state?.assets ?? []\n out.push({ tag: assetsTag(instanceId), data: { entity: 'assets', assets: assets.map((a: any) => aggregateItem(a, 'id')) } })\n\n const items = state?.items ?? []\n const shown = items.slice(0, ITEMS_AGGREGATE_LIMIT)\n out.push({\n tag: itemsTag(instanceId),\n data: {\n entity: 'items',\n items: shown.map((it: any) => aggregateItem(it, 'epc')),\n total: items.length,\n /* 잘렸으면 그 사실을 말한다 — \"이게 전부\" 로 읽히면 재고를 과소 판단한다. */\n ...(items.length > shown.length ? { truncated: items.length - shown.length } : {})\n }\n })\n\n /*\n * ── 에너지 (EMS) ─────────────────────────────────────────────────────────\n *\n * 계측은 `state.energy` 에 산다 — 자원 축(`equipment`·`locations`)이 아니다. 그래서 이 매핑이 없으면\n * 보드에 놓은 계량기가 **아무 값도 받지 못한다**(컴포넌트는 있는데 영원히 빈 모습).\n *\n * 두 갈래로 낸다:\n * · **개체별** — 계량 지점 하나하나의 마지막 관측(kW·kWh·역률·시각). 태그는 그 설비 id 다.\n * · **집약** — 수요 구간·피크·계약전력. 이것은 트윈 하나의 사실이라 개체에 실을 수 없다.\n *\n * 값이 없으면 **키를 넣지 않는다**(0 으로 만들지 않는다) — 「못 읽었다」와 「0 이었다」는 다른 사실이고,\n * 그 구별이 이 트윈의 값어치다.\n */\n const energy = state?.energy\n if (energy) {\n for (const pt of energy.points ?? []) {\n if (!pt?.id) continue\n out.push({\n tag: pt.id,\n data: {\n entity: 'meter',\n ...(pt.kW !== undefined ? { kW: pt.kW } : {}),\n ...(pt.kWh !== undefined ? { kWh: pt.kWh } : {}),\n ...(pt.powerFactor !== undefined ? { powerFactor: pt.powerFactor } : {}),\n /* 마지막 표본 시각 — 계측이 끊긴 것을 화면이 스스로 알 수 있게(우리가 판정하지 않는다). */\n ...(pt.atMs !== undefined ? { measuredAt: new Date(pt.atMs).toISOString() } : {}),\n samplesInWindow: pt.samplesInWindow ?? 0\n }\n })\n }\n out.push({\n tag: energyTag(instanceId),\n data: {\n entity: 'energy',\n ...(energy.contractKW !== undefined ? { contractKW: energy.contractKW } : {}),\n ...(energy.open ? { open: energy.open } : {}),\n ...(energy.peakSince ? { peakSince: energy.peakSince } : {}),\n /* 마감된 구간은 **최근 것만** 낸다 — 하루치를 매 방송에 실으면 채널이 그것으로 찬다.\n 전체 수는 함께 낸다(조용히 자르지 않는다). */\n recent: (energy.closed ?? []).slice(-8),\n closedTotal: energy.closedTotal ?? 0\n }\n })\n }\n\n return out\n}\n"]}
1
+ {"version":3,"file":"entity-delta.js","sourceRoot":"","sources":["../shared/entity-delta.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 트윈 State 스냅샷 → 라이브 바인딩 delta 목록 (순수 매핑 — pubsub/도메인 무관 → 단위 테스트 가능).\n * publishEntityData 가 이 목록을 엔티티별 시그니처로 dedup 후 data(tag) 채널로 발행한다.\n *\n * ── 규율: 레퍼런스가 아는 것은 **전부** 내보낸다 ────────────────────────────\n * 트윈에서 사소한 속성은 없다. 소속 구역·소속 팔레트·수량·만료처럼 곁가지로 보이는 값이 실제로는\n * 구역 롤업·적재 표현·로트 추적의 전제다. 그래서 여기서 **필드를 손으로 골라 담지 않는다.**\n *\n * 예전에는 엔티티마다 필요할 것 같은 필드만 열거했고, 그 결과 계약에 있는 것이 조용히 누락됐다\n * (2026-08-01 감사: 노드 parentId 누락 · 작업 전량 누락 · 물품 전량 누락). 화이트리스트 방식은\n * **계약이 자라도 화면이 따라오지 못한다** — 커널이 속성을 늘릴 때마다 이 파일을 고쳐야 하고, 고치는\n * 것을 잊으면 아무 신호 없이 정보가 사라진다.\n *\n * 그래서 반대로 한다: **상태 객체가 가진 것을 그대로 통과**시키고, 통과시키지 **않을** 것만 이유와 함께\n * 열거한다(EXCLUDED). 새 속성은 자동으로 화면에 도달하고, 빼려면 이유를 적어야 한다.\n *\n * ── 표식은 도메인 낱말을 훔치지 않는다 ─────────────────────────────────────\n * 예전 payload 는 엔티티 종류 표식을 `kind` 로 실었다. 그런데 `kind` 는 **도메인의 낱말**이다 —\n * 무버 kind=cutter, 작업 kind=putaway, 오더 kind=workorder. 표식이 그 이름을 차지하면 도메인 속성을\n * 실을 자리가 없어진다(같은 병을 오늘 ox-timeline 의 `group` 에서도 고쳤다).\n * 그래서 표식은 `entity`(우리 이름)로 옮기고 `kind` 는 도메인에 돌려준다.\n *\n * payload 계약(씬 컴포넌트가 소비): `entity`(표식) + 그 엔티티의 모든 속성(도메인 `kind` 포함).\n * entity = location · equipment · person · asset · order · task · item,\n * 그리고 집약 태그(__orders__ · __tasks__ · __items__ · __persons__ · __assets__).\n */\n\nexport interface EntityDelta {\n tag: string\n data: any\n}\n\n/** 집약 고정 태그 — 클라 provider 와 일치해야 한다(런타임 생성 엔티티는 개별 배치가 불가하므로 집약 뷰). */\n/*\n * 집약 채널의 접두사 — **실제 태그는 트윈 식별자를 붙여 만든다.**\n *\n * 예전에는 이 상수가 곧 태그였다. 그런데 발행 키는 (도메인, 태그)이고, **한 공간에 트윈이 여럿**\n * 있으면(실측: 3곳 × 3개) 셋이 같은 태그로 발행해 서로 덮었다. 보드의 주문 목록판이 세 공장의 주문\n * 중 마지막에 도착한 것을 보여 준다 — 오류 없이.\n *\n * 구간 성과 태그가 이미 `__kpi__:<트윈>` 으로 트윈을 붙이고 있었다(`kpi-broadcast.ts`). 같은 규약으로 맞춘다.\n */\nexport const ORDERS_TAG_PREFIX = '__orders__'\nexport const TASKS_TAG_PREFIX = '__tasks__'\nexport const ITEMS_TAG_PREFIX = '__items__'\nexport const PERSONS_TAG_PREFIX = '__persons__'\nexport const ASSETS_TAG_PREFIX = '__assets__'\n/** 에너지 집약 — 수요 구간·피크·계약은 **트윈 하나의 사실**이라 개체 태그가 없다. */\nexport const ENERGY_TAG_PREFIX = '__energy__'\n\n/** 이 트윈의 집약 채널 태그. 트윈 식별자가 없으면 만들지 않는다 — 덮어쓰기의 원인이었다. */\nexport function aggregateTag(prefix: string, instanceId: string): string {\n if (!instanceId) throw new Error('aggregateTag: instanceId required — 트윈 구분이 없는 태그는 다른 트윈의 것을 덮는다')\n\n return `${prefix}:${instanceId}`\n}\n\nexport const ordersTag = (instanceId: string) => aggregateTag(ORDERS_TAG_PREFIX, instanceId)\nexport const tasksTag = (instanceId: string) => aggregateTag(TASKS_TAG_PREFIX, instanceId)\nexport const itemsTag = (instanceId: string) => aggregateTag(ITEMS_TAG_PREFIX, instanceId)\nexport const personsTag = (instanceId: string) => aggregateTag(PERSONS_TAG_PREFIX, instanceId)\nexport const assetsTag = (instanceId: string) => aggregateTag(ASSETS_TAG_PREFIX, instanceId)\nexport const energyTag = (instanceId: string) => aggregateTag(ENERGY_TAG_PREFIX, instanceId)\n\n/**\n * 통과시키지 않는 필드 — **빼는 데는 이유가 필요하다.**\n *\n * · `id`/`epc` — 태그로 이미 나가므로 개별 payload 에 중복해 싣지 않는다(집약 뷰에는 넣는다).\n * · `oee` — 원시 누적(idleMs 등)이 매 delta 증가해 \"무변화 생략\" 을 무력화한다. shapeOee 로 정형화해 얹는다.\n */\nconst EXCLUDED = new Set(['id', 'epc', 'oee'])\n\n/**\n * OEE 원시 비율/카운트 → 표시용 정수 퍼센트 + 카운트.\n * 원시 누적 ms(idleMs 등)는 매 delta 증가해 무변화-생략을 무력화하므로 제외(유휴 자원의 매초 재브로드캐스팅 억제).\n * oee 미측정이면 undefined.\n */\nfunction shapeOee(o: any): any {\n if (!o) return undefined\n return {\n overall: Math.round((o.overall ?? 0) * 100),\n availability: Math.round((o.availability ?? 0) * 100),\n performance: Math.round((o.performance ?? 0) * 100),\n quality: Math.round((o.quality ?? 0) * 100),\n good: o.goodCount ?? 0,\n scrap: o.scrapCount ?? 0\n }\n}\n\n/**\n * 엔티티 하나의 payload — `entity` 표식 + 나머지 전부(도메인 `kind` 포함).\n *\n * `undefined` 값은 싣지 않는다(키의 유무가 시그니처를 흔들지 않게). 값이 없는 것과 키가 없는 것은\n * 소비처에서 같으므로 안전하고, 무변화 생략이 잘 듣는다.\n */\nfunction payloadOf(entityKind: string, entity: any): any {\n /* 표식은 `entity` — 도메인의 `kind` 를 덮지 않는다. */\n const data: any = { entity: entityKind }\n for (const [k, v] of Object.entries(entity ?? {})) {\n if (EXCLUDED.has(k) || v === undefined) continue\n data[k] = v\n }\n return data\n}\n\n/**\n * 집약 뷰의 항목 — 개별 payload 와 같은 내용 + 식별자.\n * 집약은 \"전체를 한 컴포넌트가 추적\" 하는 용도라 식별자가 있어야 한다.\n */\nfunction aggregateItem(entity: any, idKey: 'id' | 'epc'): any {\n const out: any = { [idKey]: entity?.[idKey] }\n for (const [k, v] of Object.entries(entity ?? {})) {\n if (k === idKey || k === 'oee' || v === undefined) continue\n out[k] = v\n }\n const shaped = shapeOee(entity?.oee)\n if (shaped) out.oee = shaped\n return out\n}\n\n/**\n * 물품 집약의 상한 — 큰 현장은 물품이 수만 개라, 전량을 매 브로드캐스팅에 실으면 전달이 현장을 느리게 한다.\n *\n * **조용히 자르지 않는다**: 넘치면 `total`(전체 수)과 `truncated`(잘린 수)를 함께 실어, 소비처가\n * \"이게 전부\" 라고 오해하지 않게 한다. 개별 물품은 태그별 delta 로 따로 나가고 그쪽은 시그니처 dedup\n * 이 걸려 **변한 것만** 재브로드캐스팅된다.\n */\nexport const ITEMS_AGGREGATE_LIMIT = 500\n\n/**\n * 자리별 에너지 — **그 자리의 계량기들이 잰 것**을 그 자리 태그에 실을 수 있게 모은다.\n *\n * ── 왜 자리에도 필요한가 (2026-08-14) ───────────────────────────────────────\n * 카탈로그는 자리에도 `metered` 를 준다(수전·분기·구역 계량은 재는 자리다). 그런데 값은 계량기\n * **설비** 태그로만 나갔다. 그래서 계통도의 분기 자리에 계량 컴포넌트를 놓으면 **영원히 「—」** 였다 —\n * 능력은 「잰다」고 말하는데 값이 오지 않는 자리.\n *\n * 「이 구간이 얼마를 먹나」는 자리의 사실이고, 사람은 계통도의 자리에 붙여 본다.\n *\n * ── 계층은 여기서 문제가 되지 않는다 ────────────────────────────────────────\n * 각 자리에는 **그 자리에 붙은 계량기만** 더한다(수전 자리에는 수전 계량기, 분기 자리에는 그 분기의\n * 것). 커널이 부하를 뿌리로만 세는 것과 다른 층의 계산이다 — 여기서는 자리마다 자기 것을 말한다.\n *\n * 근거를 함께 낸다(`meters`) — 그 자리의 수가 어느 계량기에서 왔는지 되짚을 수 있어야 한다.\n */\nfunction energyByLocation(state: any): Map<string, Record<string, unknown>> {\n const out = new Map<string, Record<string, unknown>>()\n const points = state?.energy?.points\n if (!Array.isArray(points) || !points.length) return out\n\n /* 계량기 → 자리. 자리를 모르는 계량기는 자리 payload 에 실을 데가 없다(설비 태그로만 나간다). */\n const locOf = new Map<string, string>()\n for (const e of state?.equipment ?? []) if (e?.id && e?.homeLocation) locOf.set(e.id, e.homeLocation)\n\n for (const pt of points) {\n const loc = pt?.id ? locOf.get(pt.id) : undefined\n if (!loc) continue\n const acc = out.get(loc) ?? { meters: [] as string[], samplesInWindow: 0 }\n ;(acc.meters as string[]).push(pt.id)\n acc.samplesInWindow = Number(acc.samplesInWindow ?? 0) + (Number(pt.samplesInWindow) || 0)\n /* 값은 **있는 것만** 더한다 — 하나도 없으면 키를 만들지 않는다(0 은 「안 썼다」가 된다). */\n if (Number.isFinite(Number(pt.kW))) acc.kW = Number(acc.kW ?? 0) + Number(pt.kW)\n if (Number.isFinite(Number(pt.kWh))) acc.kWh = Number(acc.kWh ?? 0) + Number(pt.kWh)\n /* 나이는 **가장 최근**을 쓴다 — 자리에 계량기가 여럿이면 그중 마지막 관측이 그 자리의 나이다. */\n if (pt.atMs !== undefined) {\n const iso = new Date(Number(pt.atMs)).toISOString()\n if (!acc.measuredAt || String(acc.measuredAt) < iso) acc.measuredAt = iso\n }\n out.set(loc, acc)\n }\n return out\n}\n\n/**\n * 물품 축만 **범위를 줄일 수 있다** (2026-08-21).\n *\n * ── 왜 이 인자가 생겼나 ─────────────────────────────────────────────────────\n * 브로드캐스팅 한 번이 만드는 payload 는 엔티티 수만큼이고 그 대부분이 물품이다(실측: 엔티티 3,611 중\n * 물품 2,400 · 만드는 데 1.8ms · 문자열로 바꾸는 데 1.0ms). 바뀐 것이 물품 하나여도 전부 만들어\n * 「같다」를 확인하고 버렸다.\n *\n * `items` 를 주면 **그 태그의 물품만** 개별 payload 를 만든다. 주지 않으면(`undefined`) 전부 만든다 —\n * 부르는 쪽이 무엇이 바뀌었는지 말할 수 없을 때의 답이다.\n *\n * **집약 뷰는 범위를 받지 않는다.** 집약은 목록 전체가 한 payload 라서, 한 물품이 바뀌면 그 payload 는\n * 통째로 새로 만들어야 한다(상한 500으로 이미 묶여 있다). 자리·설비·인원·자산·작업·오더도 범위를 받지\n * 않는다 — 수가 적고, 잘못 걸러서 잃는 것이 크다(자리 점유는 물품이 떠난 자리와 도착한 자리가 함께\n * 바뀌는데 사건은 한 지점만 말한다).\n */\nexport interface EntityDeltaScope {\n /** 개별 payload 를 만들 물품 태그. 없으면 전부. */\n items?: ReadonlySet<string>\n}\n\nexport function buildEntityDeltas(state: any, instanceId: string, scope?: EntityDeltaScope): EntityDelta[] {\n const out: EntityDelta[] = []\n const locEnergy = energyByLocation(state)\n\n for (const n of state?.locations ?? []) {\n /* 에너지는 자리 payload 에 **합친다** — 같은 태그로 두 번 보내면 뒤엣것이 앞을 덮는다. */\n const energy = locEnergy.get(n.id)\n out.push({ tag: n.id, data: energy ? { ...payloadOf('location', n), ...energy } : payloadOf('location', n) })\n }\n for (const m of state?.equipment ?? []) {\n /* Mobile(location/motion) + Operable(status/held) + Processable(oee) — 한 payload 로.\n * oee 만 정형화가 필요해 따로 얹는다(나머지는 통과). */\n const data = payloadOf('equipment', m)\n const oee = shapeOee(m?.oee)\n if (oee) data.oee = oee\n out.push({ tag: m.id, data })\n }\n /* 사람 — 설비와 별개 엔티티다. 같은 통과 규칙(가진 것을 전부 내보낸다). */\n for (const p of state?.persons ?? []) {\n out.push({ tag: p.id, data: payloadOf('person', p) })\n }\n /* 물리 자산(반복사용) — 물품도 설비도 아닌 네 번째 자원. */\n for (const a of state?.assets ?? []) {\n out.push({ tag: a.id, data: payloadOf('asset', a) })\n }\n for (const o of state?.orders ?? []) {\n out.push({ tag: o.id, data: payloadOf('order', o) })\n }\n for (const t of state?.tasks ?? []) {\n out.push({ tag: t.id, data: payloadOf('task', t) })\n }\n for (const it of state?.items ?? []) {\n /* 물품의 태그는 epc(개체 식별자) — 자리·설비와 달리 id 가 아니다. */\n if (!it?.epc) continue\n /* 범위가 주어졌으면 그 안의 물품만 만든다 — 나머지는 값이 그대로이므로 보낼 것이 없다. */\n if (scope?.items && !scope.items.has(it.epc)) continue\n out.push({ tag: it.epc, data: payloadOf('item', it) })\n }\n\n /* ── 집약 뷰 ──────────────────────────────────────────────────────────────\n * 런타임에 생기고 사라지는 엔티티(오더·작업·물품)는 보드에 개별 배치할 수 없다 → 한 컴포넌트가\n * 전체를 추적하는 집약 태그를 항상 방출한다. */\n const orders = state?.orders ?? []\n out.push({ tag: ordersTag(instanceId), data: { entity: 'orders', orders: orders.map((o: any) => aggregateItem(o, 'id')) } })\n\n const tasks = state?.tasks ?? []\n out.push({ tag: tasksTag(instanceId), data: { entity: 'tasks', tasks: tasks.map((t: any) => aggregateItem(t, 'id')) } })\n\n /* 사람 집약 — 인원은 보드에 개별 배치할 수도 있지만(설비처럼), 전체를 한눈에 보는 소비처도 있다.\n * 인원을 선언하지 않은 트윈에서는 빈 배열이 나간다(없음과 모름을 구별). */\n const persons = state?.persons ?? []\n out.push({ tag: personsTag(instanceId), data: { entity: 'persons', persons: persons.map((p: any) => aggregateItem(p, 'id')) } })\n\n const assets = state?.assets ?? []\n out.push({ tag: assetsTag(instanceId), data: { entity: 'assets', assets: assets.map((a: any) => aggregateItem(a, 'id')) } })\n\n const items = state?.items ?? []\n const shown = items.slice(0, ITEMS_AGGREGATE_LIMIT)\n out.push({\n tag: itemsTag(instanceId),\n data: {\n entity: 'items',\n items: shown.map((it: any) => aggregateItem(it, 'epc')),\n total: items.length,\n /* 잘렸으면 그 사실을 말한다 — \"이게 전부\" 로 읽히면 재고를 과소 판단한다. */\n ...(items.length > shown.length ? { truncated: items.length - shown.length } : {})\n }\n })\n\n /*\n * ── 에너지 (EMS) ─────────────────────────────────────────────────────────\n *\n * 계측은 `state.energy` 에 산다 — 자원 축(`equipment`·`locations`)이 아니다. 그래서 이 매핑이 없으면\n * 보드에 놓은 계량기가 **아무 값도 받지 못한다**(컴포넌트는 있는데 영원히 빈 모습).\n *\n * 두 갈래로 낸다:\n * · **개체별** — 계량 지점 하나하나의 마지막 관측(kW·kWh·역률·시각). 태그는 그 설비 id 다.\n * · **집약** — 수요 구간·피크·계약전력. 이것은 트윈 하나의 사실이라 개체에 실을 수 없다.\n *\n * 값이 없으면 **키를 넣지 않는다**(0 으로 만들지 않는다) — 「못 읽었다」와 「0 이었다」는 다른 사실이고,\n * 그 구별이 이 트윈의 값어치다.\n */\n const energy = state?.energy\n if (energy) {\n for (const pt of energy.points ?? []) {\n if (!pt?.id) continue\n out.push({\n tag: pt.id,\n data: {\n entity: 'meter',\n ...(pt.kW !== undefined ? { kW: pt.kW } : {}),\n ...(pt.kWh !== undefined ? { kWh: pt.kWh } : {}),\n ...(pt.powerFactor !== undefined ? { powerFactor: pt.powerFactor } : {}),\n /* 마지막 표본 시각 — 계측이 끊긴 것을 화면이 스스로 알 수 있게(우리가 판정하지 않는다). */\n ...(pt.atMs !== undefined ? { measuredAt: new Date(pt.atMs).toISOString() } : {}),\n samplesInWindow: pt.samplesInWindow ?? 0\n }\n })\n }\n out.push({\n tag: energyTag(instanceId),\n data: {\n entity: 'energy',\n ...(energy.contractKW !== undefined ? { contractKW: energy.contractKW } : {}),\n ...(energy.open ? { open: energy.open } : {}),\n ...(energy.peakSince ? { peakSince: energy.peakSince } : {}),\n /* 마감된 구간은 **최근 것만** 낸다 — 하루치를 매 브로드캐스팅에 실으면 채널이 그것으로 찬다.\n 전체 수는 함께 낸다(조용히 자르지 않는다). */\n recent: (energy.closed ?? []).slice(-8),\n closedTotal: energy.closedTotal ?? 0\n }\n })\n }\n\n return out\n}\n"]}
@@ -4,7 +4,7 @@ exports.kpiTag = exports.KPI_TAG_PREFIX = void 0;
4
4
  /*
5
5
  * 구간 성과 카드의 **대상 식별자**.
6
6
  *
7
- * 예전에는 이 파일이 방송 payload 도 만들었다(`shapeKpiBroadcast`). 2026-08-06 에 방송을 없애고 카드가
7
+ * 예전에는 이 파일이 브로드캐스팅 payload 도 만들었다(`shapeKpiBroadcast`). 2026-08-06 에 브로드캐스팅을 없애고 카드가
8
8
  * `twinKpi` 를 직접 묻게 바꿔서, 남은 것은 팔레트가 대상을 가리킬 때 쓰는 식별자뿐이다.
9
9
  * 왜 바꿨는지는 `twin-engine.ts` 의 주석과 `operato-twin/client/scene/twin-kpi-source.ts` 에 있다.
10
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"kpi-broadcast.js","sourceRoot":"","sources":["../shared/kpi-broadcast.ts"],"names":[],"mappings":";;;AAAA;;;;;;GAMG;AACU,QAAA,cAAc,GAAG,SAAS,CAAA;AAEvC,sBAAsB;AACf,MAAM,MAAM,GAAG,CAAC,UAAkB,EAAE,EAAE,CAAC,GAAG,sBAAc,IAAI,UAAU,EAAE,CAAA;AAAlE,QAAA,MAAM,UAA4D;AAE/E,kFAAkF","sourcesContent":["/*\n * 구간 성과 카드의 **대상 식별자**.\n *\n * 예전에는 이 파일이 방송 payload 도 만들었다(`shapeKpiBroadcast`). 2026-08-06 에 방송을 없애고 카드가\n * `twinKpi` 를 직접 묻게 바꿔서, 남은 것은 팔레트가 대상을 가리킬 때 쓰는 식별자뿐이다.\n * 왜 바꿨는지는 `twin-engine.ts` 의 주석과 `operato-twin/client/scene/twin-kpi-source.ts` 에 있다.\n */\nexport const KPI_TAG_PREFIX = '__kpi__'\n\n/** 이 트윈의 구간 성과 태그. */\nexport const kpiTag = (instanceId: string) => `${KPI_TAG_PREFIX}:${instanceId}`\n\n/** 표식은 `entity` 로 — `kind` 는 도메인의 낱말이라 표식이 훔치지 않는다(`entity-delta.ts` 와 같은 규약). */\n"]}
1
+ {"version":3,"file":"kpi-broadcast.js","sourceRoot":"","sources":["../shared/kpi-broadcast.ts"],"names":[],"mappings":";;;AAAA;;;;;;GAMG;AACU,QAAA,cAAc,GAAG,SAAS,CAAA;AAEvC,sBAAsB;AACf,MAAM,MAAM,GAAG,CAAC,UAAkB,EAAE,EAAE,CAAC,GAAG,sBAAc,IAAI,UAAU,EAAE,CAAA;AAAlE,QAAA,MAAM,UAA4D;AAE/E,kFAAkF","sourcesContent":["/*\n * 구간 성과 카드의 **대상 식별자**.\n *\n * 예전에는 이 파일이 브로드캐스팅 payload 도 만들었다(`shapeKpiBroadcast`). 2026-08-06 에 브로드캐스팅을 없애고 카드가\n * `twinKpi` 를 직접 묻게 바꿔서, 남은 것은 팔레트가 대상을 가리킬 때 쓰는 식별자뿐이다.\n * 왜 바꿨는지는 `twin-engine.ts` 의 주석과 `operato-twin/client/scene/twin-kpi-source.ts` 에 있다.\n */\nexport const KPI_TAG_PREFIX = '__kpi__'\n\n/** 이 트윈의 구간 성과 태그. */\nexport const kpiTag = (instanceId: string) => `${KPI_TAG_PREFIX}:${instanceId}`\n\n/** 표식은 `entity` 로 — `kind` 는 도메인의 낱말이라 표식이 훔치지 않는다(`entity-delta.ts` 와 같은 규약). */\n"]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 이 사건이 건드린 물품 태그들. **말할 수 없으면 `null`** — 호출자는 그때 전부 다시 만든다.
3
+ *
4
+ * 개체를 말하는 자리를 전부 본다: `epcList`(관측·커미셔닝) · `parentID`(집약의 부모) ·
5
+ * `childEPCs`(집약의 자식) · `inputEPCList`·`outputEPCList`(변환의 입출력).
6
+ */
7
+ export declare function touchedItemKeys(envelope: any): Set<string> | null;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /*
3
+ * 한 사건이 **어느 물품을 건드렸나** — 브로드캐스팅이 바뀐 것만 만들 수 있게. 순수(import 없음).
4
+ *
5
+ * ── 왜 물품만인가 (2026-08-21 실측) ─────────────────────────────────────────
6
+ * 브로드캐스팅 한 번이 만드는 payload 는 엔티티 수만큼이고, 그 대부분이 물품이다(실측: 엔티티 3,611 중
7
+ * 물품 2,400). 자리·설비·인원·자산은 모델이 정한 수라 적고(자리 최대 200, 평균 19), 작업·오더는 수백이다.
8
+ * 그래서 **물품 축만 걸러도** 만드는 양의 3분의 2가 사라진다.
9
+ *
10
+ * 나머지 축은 **매번 전부 만든다.** 걸러서 얻는 것이 적고, 잘못 걸러서 잃는 것이 크기 때문이다 —
11
+ * 예를 들어 물품이 한 자리에서 다른 자리로 가면 **두 자리의 점유가 함께 바뀌는데** EPCIS 사건은 읽은
12
+ * 지점 하나만 말한다. 그것으로 자리를 걸렀으면 떠난 자리의 점유가 낡은 값으로 남는다(오류 없이).
13
+ *
14
+ * ── 모르면 전부다 ───────────────────────────────────────────────────────────
15
+ * 이 함수가 `null` 을 돌려주면 「이 사건이 어느 물품을 건드렸는지 말할 수 없다」는 뜻이고, 그때 호출자는
16
+ * **전부 다시 만든다**(지금까지의 거동). 낯선 어휘가 오면 조용히 빠뜨리는 것이 아니라 비싸게 안전한
17
+ * 쪽으로 떨어진다 — 화면이 낡는 것보다 계산을 한 번 더 하는 것이 낫다.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.touchedItemKeys = touchedItemKeys;
21
+ /** 물품 태그는 개체 식별자(epc)다 — 자리·설비와 달리 id 가 아니다(entity-delta 와 같은 규약). */
22
+ function push(out, v) {
23
+ if (typeof v === 'string' && v)
24
+ out.add(v);
25
+ }
26
+ /**
27
+ * 물품을 건드리지 않는다고 **선언된** 사건들 — 커널의 운영 델타와 에너지 사건.
28
+ *
29
+ * 이 목록은 커널 계약(`OP_EVENT`·`ENERGY_EVENT`)에서 왔다. 여기 없는 낱말이 오면 「모른다」로 떨어져
30
+ * 전부 다시 만든다 — 커널이 사건을 늘렸을 때 조용히 빠뜨리는 대신 비싸게 안전한 쪽을 고른다.
31
+ *
32
+ * 근거: 미러의 리듀서에서 이 사건들은 작업·설비·인원·자산·오더·계량만 바꾼다(물품 축을 만지지 않는다).
33
+ * 자리 점유는 이 사건들도 바꾸지만, 자리는 **어차피 매번 전부 다시 만든다**(위 주석).
34
+ */
35
+ const ITEM_FREE_EVENTS = new Set([
36
+ 'task.status',
37
+ 'equipment.status',
38
+ 'person.status',
39
+ 'asset.status',
40
+ 'order.status',
41
+ 'quality.output',
42
+ 'attention.acked'
43
+ ]);
44
+ /**
45
+ * 이 사건이 건드린 물품 태그들. **말할 수 없으면 `null`** — 호출자는 그때 전부 다시 만든다.
46
+ *
47
+ * 개체를 말하는 자리를 전부 본다: `epcList`(관측·커미셔닝) · `parentID`(집약의 부모) ·
48
+ * `childEPCs`(집약의 자식) · `inputEPCList`·`outputEPCList`(변환의 입출력).
49
+ */
50
+ function touchedItemKeys(envelope) {
51
+ const type = envelope?.eventType;
52
+ if (typeof type !== 'string')
53
+ return null;
54
+ const d = envelope?.data ?? envelope;
55
+ if (!d || typeof d !== 'object')
56
+ return null;
57
+ if (type.startsWith('energy.') || ITEM_FREE_EVENTS.has(type))
58
+ return new Set();
59
+ /* EPCIS 밖의 낯선 낱말 — 무엇을 바꿨는지 우리가 모른다. */
60
+ if (!type.startsWith('epcis.'))
61
+ return null;
62
+ const out = new Set();
63
+ for (const v of d.epcList ?? [])
64
+ push(out, v);
65
+ for (const v of d.childEPCs ?? [])
66
+ push(out, v);
67
+ for (const v of d.inputEPCList ?? [])
68
+ push(out, v);
69
+ for (const v of d.outputEPCList ?? [])
70
+ push(out, v);
71
+ push(out, d.parentID);
72
+ /*
73
+ * 개체를 하나도 말하지 않은 EPCIS 사건은 **부류 수량**으로 말한 것이다(`quantityList`). 그 부류의
74
+ * 물품이 어느 것인지는 여기서 알 수 없으므로 전부 다시 만든다.
75
+ */
76
+ if (!out.size)
77
+ return null;
78
+ return out;
79
+ }
80
+ //# sourceMappingURL=touched-items.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"touched-items.js","sourceRoot":"","sources":["../shared/touched-items.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;AAgCH,0CAwBC;AAtDD,sEAAsE;AACtE,SAAS,IAAI,CAAC,GAAgB,EAAE,CAAU;IACxC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC;QAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IAC/B,aAAa;IACb,kBAAkB;IAClB,eAAe;IACf,cAAc;IACd,cAAc;IACd,gBAAgB;IAChB,iBAAiB;CAClB,CAAC,CAAA;AAEF;;;;;GAKG;AACH,SAAgB,eAAe,CAAC,QAAa;IAC3C,MAAM,IAAI,GAAG,QAAQ,EAAE,SAAS,CAAA;IAChC,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACzC,MAAM,CAAC,GAAG,QAAQ,EAAE,IAAI,IAAI,QAAQ,CAAA;IACpC,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAE5C,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,GAAG,EAAE,CAAA;IAC9E,wCAAwC;IACxC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IAE3C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IAC7C,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,IAAI,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IAC/C,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,IAAI,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IAClD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa,IAAI,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IACnD,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAA;IAErB;;;OAGG;IACH,IAAI,CAAC,GAAG,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAE1B,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["/*\n * 한 사건이 **어느 물품을 건드렸나** — 브로드캐스팅이 바뀐 것만 만들 수 있게. 순수(import 없음).\n *\n * ── 왜 물품만인가 (2026-08-21 실측) ─────────────────────────────────────────\n * 브로드캐스팅 한 번이 만드는 payload 는 엔티티 수만큼이고, 그 대부분이 물품이다(실측: 엔티티 3,611 중\n * 물품 2,400). 자리·설비·인원·자산은 모델이 정한 수라 적고(자리 최대 200, 평균 19), 작업·오더는 수백이다.\n * 그래서 **물품 축만 걸러도** 만드는 양의 3분의 2가 사라진다.\n *\n * 나머지 축은 **매번 전부 만든다.** 걸러서 얻는 것이 적고, 잘못 걸러서 잃는 것이 크기 때문이다 —\n * 예를 들어 물품이 한 자리에서 다른 자리로 가면 **두 자리의 점유가 함께 바뀌는데** EPCIS 사건은 읽은\n * 지점 하나만 말한다. 그것으로 자리를 걸렀으면 떠난 자리의 점유가 낡은 값으로 남는다(오류 없이).\n *\n * ── 모르면 전부다 ───────────────────────────────────────────────────────────\n * 이 함수가 `null` 을 돌려주면 「이 사건이 어느 물품을 건드렸는지 말할 수 없다」는 뜻이고, 그때 호출자는\n * **전부 다시 만든다**(지금까지의 거동). 낯선 어휘가 오면 조용히 빠뜨리는 것이 아니라 비싸게 안전한\n * 쪽으로 떨어진다 — 화면이 낡는 것보다 계산을 한 번 더 하는 것이 낫다.\n */\n\n/** 물품 태그는 개체 식별자(epc)다 — 자리·설비와 달리 id 가 아니다(entity-delta 와 같은 규약). */\nfunction push(out: Set<string>, v: unknown): void {\n if (typeof v === 'string' && v) out.add(v)\n}\n\n/**\n * 물품을 건드리지 않는다고 **선언된** 사건들 — 커널의 운영 델타와 에너지 사건.\n *\n * 이 목록은 커널 계약(`OP_EVENT`·`ENERGY_EVENT`)에서 왔다. 여기 없는 낱말이 오면 「모른다」로 떨어져\n * 전부 다시 만든다 — 커널이 사건을 늘렸을 때 조용히 빠뜨리는 대신 비싸게 안전한 쪽을 고른다.\n *\n * 근거: 미러의 리듀서에서 이 사건들은 작업·설비·인원·자산·오더·계량만 바꾼다(물품 축을 만지지 않는다).\n * 자리 점유는 이 사건들도 바꾸지만, 자리는 **어차피 매번 전부 다시 만든다**(위 주석).\n */\nconst ITEM_FREE_EVENTS = new Set([\n 'task.status',\n 'equipment.status',\n 'person.status',\n 'asset.status',\n 'order.status',\n 'quality.output',\n 'attention.acked'\n])\n\n/**\n * 이 사건이 건드린 물품 태그들. **말할 수 없으면 `null`** — 호출자는 그때 전부 다시 만든다.\n *\n * 개체를 말하는 자리를 전부 본다: `epcList`(관측·커미셔닝) · `parentID`(집약의 부모) ·\n * `childEPCs`(집약의 자식) · `inputEPCList`·`outputEPCList`(변환의 입출력).\n */\nexport function touchedItemKeys(envelope: any): Set<string> | null {\n const type = envelope?.eventType\n if (typeof type !== 'string') return null\n const d = envelope?.data ?? envelope\n if (!d || typeof d !== 'object') return null\n\n if (type.startsWith('energy.') || ITEM_FREE_EVENTS.has(type)) return new Set()\n /* EPCIS 밖의 낯선 낱말 — 무엇을 바꿨는지 우리가 모른다. */\n if (!type.startsWith('epcis.')) return null\n\n const out = new Set<string>()\n for (const v of d.epcList ?? []) push(out, v)\n for (const v of d.childEPCs ?? []) push(out, v)\n for (const v of d.inputEPCList ?? []) push(out, v)\n for (const v of d.outputEPCList ?? []) push(out, v)\n push(out, d.parentID)\n\n /*\n * 개체를 하나도 말하지 않은 EPCIS 사건은 **부류 수량**으로 말한 것이다(`quantityList`). 그 부류의\n * 물품이 어느 것인지는 여기서 알 수 없으므로 전부 다시 만든다.\n */\n if (!out.size) return null\n\n return out\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/headless-twin",
3
- "version": "10.0.12",
3
+ "version": "10.0.13",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "author": "heartyoh <heartyoh@hatiolab.com>",
@@ -22,16 +22,16 @@
22
22
  "clean": "npm run clean:server && npm run clean:shared",
23
23
  "migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration",
24
24
  "typecheck": "tsc --noEmit && tsc -p tsconfig.shared.json --noEmit",
25
- "test": "node --test test/*.test.ts",
25
+ "test": "node --import ./test/resolve-ts-siblings.mjs --test test/*.test.ts",
26
26
  "build:shared": "tsc -p tsconfig.shared.json",
27
27
  "clean:shared": "rm -rf dist-shared tsconfig.shared.tsbuildinfo"
28
28
  },
29
29
  "dependencies": {
30
- "@operato/twin-kernel": "^0.7.38",
31
- "@things-factory/auth-base": "^10.0.10",
32
- "@things-factory/cache-service": "^10.0.10",
30
+ "@operato/twin-kernel": "^0.7.53",
31
+ "@things-factory/auth-base": "^10.0.13",
32
+ "@things-factory/cache-service": "^10.0.13",
33
33
  "@things-factory/env": "^10.0.8",
34
- "@things-factory/shell": "^10.0.10"
34
+ "@things-factory/shell": "^10.0.13"
35
35
  },
36
- "gitHead": "3cc678c07e9031a820662d43410f93b796ca012f"
36
+ "gitHead": "808b3b9e3bf15c058316ec4c73455108df7722aa"
37
37
  }
@@ -1,4 +1,4 @@
1
- import { ingest, ingestEnergyRecords, ingestEnergyEquipmentRecords, ingestOperationalRecords, isAggregationRecord,
1
+ import { ingest, ingestEnergyRecords, ingestEnergyEquipmentRecords, ingestOperationalRecords, isAggregationRecord, isTransformationRecord,
2
2
  isEnergyRecord, isEnergyEquipmentRecord, isOperationalRecord, type AdapterRule, type EnergyRecord,
3
3
  type EnergyEquipmentRecord, type OperationalRecord, type IngestResult } from '@operato/twin-kernel'
4
4
 
@@ -29,6 +29,24 @@ const TWIN_INGEST_RULES: AdapterRule[] = [
29
29
  bizStep: '$.bizStep',
30
30
  disposition: '$.disposition',
31
31
  epc: '$.epc',
32
+ /* 비직렬 수량 — 개체 없이 「이 자리에 이 품목이 얼마 남았다」만 오는 관측이 있다. */
33
+ quantityList: '$.quantityList',
34
+ readPoint: '$.readPoint',
35
+ bizLocation: '$.bizLocation'
36
+ }
37
+ },
38
+ {
39
+ /* **변환의 사실** — 무엇으로 무엇이 되었나. 이름은 표준의 것을 그대로 쓴다(갈래 판정이 그 이름을 본다). */
40
+ sourceType: 'twin-transformation',
41
+ mapping: {
42
+ type: 'TransformationEvent',
43
+ bizStep: '$.bizStep',
44
+ disposition: '$.disposition',
45
+ inputEPCList: '$.inputEPCList',
46
+ inputQuantityList: '$.inputQuantityList',
47
+ outputEPCList: '$.outputEPCList',
48
+ outputQuantityList: '$.outputQuantityList',
49
+ transformationID: '$.transformationID',
32
50
  readPoint: '$.readPoint',
33
51
  bizLocation: '$.bizLocation'
34
52
  }
@@ -57,7 +75,20 @@ const TWIN_INGEST_RULES: AdapterRule[] = [
57
75
 
58
76
  /** 개체 관측 — 물건 하나가 어디서 무엇이 됐나. */
59
77
  export interface CanonicalRecord {
60
- epc: string
78
+ /**
79
+ * 개체 하나의 식별자. **`quantityList` 를 쓰면 없어도 된다** — 낱개 번호가 없는 자재(밀가루 3.5kg)는
80
+ * 개체가 없다. 둘 다 없으면 무엇을 관측했는지 말하지 않은 것이므로 커널 매핑이 거부한다.
81
+ */
82
+ epc?: string
83
+ /**
84
+ * 클래스와 수량 — `{ epcClass, quantity, uom? }`. **잔량 절대값을 싣는다**(「얼마 뺐다」가 아니라
85
+ * 「얼마 남았다」). 그래야 사건 하나를 놓쳐도 다음 사건이 정답을 다시 말해 주고 오차가 쌓이지 않는다.
86
+ *
87
+ * 담김(`childQuantityList`)이 하루 먼저 이 자리를 받았다 — 그때와 같은 이유다. 원본이 비직렬 수량을
88
+ * 말할 길이 없어서, 그것이 주된 경로인 시스템(식품 제조 MES)은 라이브를 낼 수 없었다. 커널의
89
+ * 관측 리듀서는 처음부터 이 값을 읽었고 문만 없었다.
90
+ */
91
+ quantityList?: { epcClass: string; quantity?: number; uom?: string }[]
61
92
  action: string // ADD | OBSERVE | DELETE
62
93
  bizStep: string
63
94
  disposition?: string
@@ -71,6 +102,37 @@ export interface CanonicalRecord {
71
102
  * 이름은 표준의 것을 그대로 쓴다(`parentID`·`childEPCs`) — 여기서 다시 지으면 매핑이 번역이 되고,
72
103
  * 같은 사실에 두 어휘가 생긴다.
73
104
  */
105
+ /**
106
+ * 변환 관측 — **무엇으로 무엇이 되었나.** 입력과 출력이 **한 사실에 함께** 있어야 계보가 남는다.
107
+ *
108
+ * 따로 보내면(「이 로트가 없어졌다」와 「이 제품이 생겼다」) 그 둘을 이을 근거가 없다. 그래서 표준이
109
+ * 한 사건에 둘을 담고, 우리 레코드도 같은 모양이다.
110
+ *
111
+ * 이름은 **표준의 것을 그대로** 쓴다(`inputQuantityList` 등). 갈래 판정이 이 이름으로 이루어지므로
112
+ * 커넥터 낱말(`consumed`·`produced`)로 담으면 판정되지 않는다 — 커넥터가 정규 이름으로 담아 보내고
113
+ * 매핑은 그 안에서 값을 고른다.
114
+ *
115
+ * **없는 쪽은 필드를 만들지 않는다.** 빈 배열을 실으면 「없다」고 말하는 것이 된다. 소실(N→0)과
116
+ * 생성(0→N)도 표준이 인정하는 변환이다.
117
+ *
118
+ * `transformationID` 가 한 오더의 여러 단계를 잇는다 — 소비를 먼저 알고 산출을 나중에 알게 되는 원본에서
119
+ * 그 둘을 묶는 유일한 자리다.
120
+ */
121
+ export interface CanonicalTransformationRecord {
122
+ /** 개체로 소비된 것. */
123
+ inputEPCList?: string[]
124
+ /** 클래스와 수량으로 소비된 것 — 로트 단위 소비가 이 자리다. */
125
+ inputQuantityList?: { epcClass: string; quantity?: number; uom?: string }[]
126
+ outputEPCList?: string[]
127
+ outputQuantityList?: { epcClass: string; quantity?: number; uom?: string }[]
128
+ /** 한 오더의 여러 단계를 잇는 식별자. */
129
+ transformationID?: string
130
+ bizStep: string
131
+ disposition?: string
132
+ readPoint?: string
133
+ bizLocation?: string
134
+ }
135
+
74
136
  export interface CanonicalAggregationRecord {
75
137
  parentID: string
76
138
  /** 개체로 담긴 자식. 수량으로만 담겼으면 없다(둘 중 하나는 있어야 한다). */
@@ -89,10 +151,11 @@ export function ingestCanonicalRecords(
89
151
  records:
90
152
  | CanonicalRecord
91
153
  | CanonicalAggregationRecord
154
+ | CanonicalTransformationRecord
92
155
  | EnergyRecord
93
156
  | EnergyEquipmentRecord
94
157
  | OperationalRecord
95
- | (CanonicalRecord | CanonicalAggregationRecord | EnergyRecord | EnergyEquipmentRecord | OperationalRecord)[]
158
+ | (CanonicalRecord | CanonicalAggregationRecord | CanonicalTransformationRecord | EnergyRecord | EnergyEquipmentRecord | OperationalRecord)[]
96
159
  | undefined
97
160
  | null,
98
161
  tenantId: string,
@@ -107,7 +170,7 @@ export function ingestCanonicalRecords(
107
170
  * 물류 어휘 안에는 모양이 둘이다(개체 관측 `epc` / 담김 `parentID`+`childEPCs`).
108
171
  *
109
172
  * 판정은 커널이 한 곳에서 한다(`isEnergyRecord`·`isOperationalRecord`·`isAggregationRecord`) — 여기서 다시 짐작하면
110
- * 소비처마다 답이 달라진다. 봉투는 같은 것을 쓰므로 아래 소비처(미러·저널·방송)는 이 갈림을 알
173
+ * 소비처마다 답이 달라진다. 봉투는 같은 것을 쓰므로 아래 소비처(미러·저널·브로드캐스팅)는 이 갈림을 알
111
174
  * 필요가 없다.
112
175
  */
113
176
  const energy = arr.filter(isEnergyRecord) as EnergyRecord[]
@@ -120,14 +183,36 @@ export function ingestCanonicalRecords(
120
183
  * 아는 상태가 남았다(같은 화면이 두 구동에서 다른 것을 말한다).
121
184
  */
122
185
  const operational = arr.filter(isOperationalRecord) as OperationalRecord[]
123
- const epcis = arr.filter(r => !isEnergyRecord(r) && !isEnergyEquipmentRecord(r) && !isOperationalRecord(r)) as (CanonicalRecord | CanonicalAggregationRecord)[]
186
+ const epcis = arr.filter(r => !isEnergyRecord(r) && !isEnergyEquipmentRecord(r) && !isOperationalRecord(r)) as (CanonicalRecord | CanonicalAggregationRecord | CanonicalTransformationRecord)[]
124
187
 
125
188
  /* 품목 어휘 안에서도 갈림이 있다(개체 관측 / 담김) — 그 판정도 커널의 것을 부른다. */
126
189
  const epcisResult = epcis.length
127
190
  ? ingest(
128
- epcis.map(r => ({ ...r, sourceType: isAggregationRecord(r) ? 'twin-aggregation' : 'twin' })),
191
+ epcis.map(r => ({
192
+ ...r,
193
+ /* 판정은 커널이 한 곳에서 한다 — 여기서 다시 짐작하면 소비처마다 답이 달라진다. */
194
+ sourceType: isTransformationRecord(r)
195
+ ? 'twin-transformation'
196
+ : isAggregationRecord(r)
197
+ ? 'twin-aggregation'
198
+ : 'twin'
199
+ })),
129
200
  TWIN_INGEST_RULES,
130
- { tenantId, defaultEventTime }
201
+ /*
202
+ * **레코드가 시각을 말하면 그것을 쓴다** (2026-08-23).
203
+ *
204
+ * `eventTimePath` 를 주지 않던 동안 이 경로는 **언제나 폴링 시각**을 썼다(`defaultEventTime`).
205
+ * 그래서 커넥터가 이미 싣고 있던 `eventTime`(변환 레코드의 `finishedAt` 등)이 **조용히
206
+ * 무시됐다** — 커넥터를 붙인 쪽은 그것이 먹는다고 믿고 있었고, 어디에도 오류가 나지 않았다.
207
+ *
208
+ * 무엇이 걸렸나: 계보(변환)의 시각이다. 4월에 만든 로트가 8월에 만들어진 것으로 저널에 남았다.
209
+ * 그리고 실측 소요를 재는 폴드가 봉투 시각을 쓰므로(§`kpi-fold`: `workMs = 완료 − 첫 착수`),
210
+ * 폴링 간격을 작업시간으로 배울 수 있었다 — 못 배우는 것보다 나쁘다.
211
+ *
212
+ * 레코드에 `eventTime` 이 없으면 커널이 조용히 기본값으로 떨어진다(§`resolve` — 없는 값은
213
+ * 오류가 아니다). 그래서 이 한 줄이 기존 경로를 막지 않는다: 시각을 말하는 레코드만 사실이 된다.
214
+ */
215
+ { tenantId, defaultEventTime, eventTimePath: 'eventTime' }
131
216
  )
132
217
  : { accepted: [], rejected: [] }
133
218
  const energyResult = energy.length ? ingestEnergyRecords(energy, { tenantId, defaultEventTime }) : { accepted: [], rejected: [] }
@@ -57,7 +57,7 @@ export function routeCommand(inst: RoutableInstance | undefined, ownedByCaller:
57
57
  if (!type) return { target: 'reject', errorCode: 'unknown-command' }
58
58
 
59
59
  if (inst.mode !== 'live') {
60
- /* 시뮬 — 커널이 세계다. 런타임이 없으면 예외를 던지지 않고 이유를 말한다. */
60
+ /* 시뮬 — 커널이 세계다. 런타임이 없으면 오류를 내지 않고 이유를 말한다. */
61
61
  return inst.hasRuntime ? { target: 'runtime' } : { target: 'reject', errorCode: 'no-runtime' }
62
62
  }
63
63
 
@@ -12,6 +12,7 @@ export * from '@things-factory/headless-twin/dist-shared/entity-delta.js'
12
12
  export * from '@things-factory/headless-twin/dist-shared/twin-level.js'
13
13
  /* 부하 계기판 — 어떤 작업이 이벤트 루프를 얼마나 쓰는지. */
14
14
  export * from './load-meter.js'
15
+ export * from './loop-lag.js'
15
16
  /* 런타임 레지스트리 키 규약 — 소비처(다른 키 캐시·테스트)가 `:` 를 다시 적지 않게 함께 내보낸다. */
16
17
  export * from './runtime-key.js'
17
18
  export * from './kpi-query.js'
@@ -20,8 +21,11 @@ export * from './travel-estimator.js'
20
21
  export * from './measured-estimator.js'
21
22
  export * from './model-basis.js'
22
23
  export * from './live-feed-registry.js'
24
+ /* 동기화 건강 — 「이 트윈이 현장과 맞춰지고 있나」. 판정·추이·사유·표본. */
25
+ export * from './ingest-health.js'
23
26
  /* 로컬 선언(현장·트윈) — 소비처(twin-ai 도구)가 명세·검사 규칙을 다시 적지 않게. */
24
27
  export * from './local-declarations.js'
25
28
  export * from './operation-basis.js'
26
29
  export * from './measured-yield.js'
27
30
  export * from './declared-stimulus.js'
31
+ export * from './integration-coverage.js'