@things-factory/headless-twin 10.0.14 → 10.0.17

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 (478) hide show
  1. package/dist-server/engine/attention-digest.d.ts +4 -4
  2. package/dist-server/engine/attention-digest.js +7 -7
  3. package/dist-server/engine/attention-digest.js.map +1 -1
  4. package/dist-server/engine/canonical-ingest.d.ts +11 -2
  5. package/dist-server/engine/canonical-ingest.js +137 -8
  6. package/dist-server/engine/canonical-ingest.js.map +1 -1
  7. package/dist-server/engine/command-routing.js +1 -1
  8. package/dist-server/engine/command-routing.js.map +1 -1
  9. package/dist-server/engine/declared-stimulus.js +1 -1
  10. package/dist-server/engine/declared-stimulus.js.map +1 -1
  11. package/dist-server/engine/energy-topology.js +1 -1
  12. package/dist-server/engine/energy-topology.js.map +1 -1
  13. package/dist-server/engine/ingest-dedupe.d.ts +38 -0
  14. package/dist-server/engine/ingest-dedupe.js +145 -0
  15. package/dist-server/engine/ingest-dedupe.js.map +1 -0
  16. package/dist-server/engine/ingest-health.d.ts +124 -9
  17. package/dist-server/engine/ingest-health.js +169 -15
  18. package/dist-server/engine/ingest-health.js.map +1 -1
  19. package/dist-server/engine/integration-probes.d.ts +3 -3
  20. package/dist-server/engine/integration-probes.js +6 -6
  21. package/dist-server/engine/integration-probes.js.map +1 -1
  22. package/dist-server/engine/kpi-baseline.js +4 -4
  23. package/dist-server/engine/kpi-baseline.js.map +1 -1
  24. package/dist-server/engine/kpi-fold.d.ts +122 -12
  25. package/dist-server/engine/kpi-fold.js +247 -18
  26. package/dist-server/engine/kpi-fold.js.map +1 -1
  27. package/dist-server/engine/kpi-query.d.ts +11 -11
  28. package/dist-server/engine/kpi-query.js +185 -28
  29. package/dist-server/engine/kpi-query.js.map +1 -1
  30. package/dist-server/engine/live-feed-registry.d.ts +1 -1
  31. package/dist-server/engine/live-feed-registry.js +1 -1
  32. package/dist-server/engine/live-feed-registry.js.map +1 -1
  33. package/dist-server/engine/load-meter.d.ts +1 -1
  34. package/dist-server/engine/load-meter.js.map +1 -1
  35. package/dist-server/engine/local-declarations.d.ts +7 -7
  36. package/dist-server/engine/local-declarations.js +9 -9
  37. package/dist-server/engine/local-declarations.js.map +1 -1
  38. package/dist-server/engine/log.d.ts +1 -1
  39. package/dist-server/engine/log.js +1 -1
  40. package/dist-server/engine/log.js.map +1 -1
  41. package/dist-server/engine/measured-estimator.js +1 -1
  42. package/dist-server/engine/measured-estimator.js.map +1 -1
  43. package/dist-server/engine/measured-yield.js +1 -1
  44. package/dist-server/engine/measured-yield.js.map +1 -1
  45. package/dist-server/engine/model-basis.d.ts +1 -1
  46. package/dist-server/engine/model-basis.js +1 -1
  47. package/dist-server/engine/model-basis.js.map +1 -1
  48. package/dist-server/engine/model-gap.d.ts +3 -3
  49. package/dist-server/engine/model-gap.js +1 -1
  50. package/dist-server/engine/model-gap.js.map +1 -1
  51. package/dist-server/engine/model-vocabulary.js +1 -1
  52. package/dist-server/engine/model-vocabulary.js.map +1 -1
  53. package/dist-server/engine/oee-accumulator.d.ts +4 -4
  54. package/dist-server/engine/oee-accumulator.js +3 -3
  55. package/dist-server/engine/oee-accumulator.js.map +1 -1
  56. package/dist-server/engine/property-effects.js +2 -2
  57. package/dist-server/engine/property-effects.js.map +1 -1
  58. package/dist-server/engine/read-time.d.ts +22 -0
  59. package/dist-server/engine/read-time.js +81 -0
  60. package/dist-server/engine/read-time.js.map +1 -0
  61. package/dist-server/engine/restart-policy.js +1 -1
  62. package/dist-server/engine/restart-policy.js.map +1 -1
  63. package/dist-server/engine/runtime-key.js +1 -1
  64. package/dist-server/engine/runtime-key.js.map +1 -1
  65. package/dist-server/engine/spec-coverage.d.ts +2 -2
  66. package/dist-server/engine/spec-coverage.js +1 -1
  67. package/dist-server/engine/spec-coverage.js.map +1 -1
  68. package/dist-server/engine/stage-path.d.ts +4 -4
  69. package/dist-server/engine/stage-path.js +1 -1
  70. package/dist-server/engine/stage-path.js.map +1 -1
  71. package/dist-server/engine/state-axes.d.ts +1 -1
  72. package/dist-server/engine/state-axes.js +2 -2
  73. package/dist-server/engine/state-axes.js.map +1 -1
  74. package/dist-server/engine/travel-estimator.d.ts +2 -2
  75. package/dist-server/engine/travel-estimator.js +1 -1
  76. package/dist-server/engine/travel-estimator.js.map +1 -1
  77. package/dist-server/engine/twin-engine.d.ts +243 -53
  78. package/dist-server/engine/twin-engine.js +690 -191
  79. package/dist-server/engine/twin-engine.js.map +1 -1
  80. package/dist-server/engine/warm-start.d.ts +2 -2
  81. package/dist-server/engine/warm-start.js +5 -5
  82. package/dist-server/engine/warm-start.js.map +1 -1
  83. package/dist-server/index.js +1 -1
  84. package/dist-server/index.js.map +1 -1
  85. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +1 -1
  86. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -1
  87. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +3 -3
  88. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -1
  89. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +2 -2
  90. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -1
  91. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.d.ts +5 -0
  92. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js +54 -0
  93. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +1 -0
  94. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.d.ts +5 -0
  95. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js +44 -0
  96. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +1 -0
  97. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.d.ts +6 -0
  98. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js +84 -0
  99. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +1 -0
  100. package/dist-server/migrations/index.js +7 -1
  101. package/dist-server/migrations/index.js.map +1 -1
  102. package/dist-server/routes.d.ts +1 -0
  103. package/dist-server/routes.js +69 -5
  104. package/dist-server/routes.js.map +1 -1
  105. package/dist-server/service/index.d.ts +2 -2
  106. package/dist-server/service/index.js +27 -24
  107. package/dist-server/service/index.js.map +1 -1
  108. package/dist-server/service/reference/control-routing.d.ts +1 -1
  109. package/dist-server/service/reference/control-routing.js +1 -1
  110. package/dist-server/service/reference/control-routing.js.map +1 -1
  111. package/dist-server/service/reference/discovery-result.d.ts +2 -2
  112. package/dist-server/service/reference/discovery-result.js +3 -3
  113. package/dist-server/service/reference/discovery-result.js.map +1 -1
  114. package/dist-server/service/reference/hook-contract.d.ts +43 -0
  115. package/dist-server/service/reference/hook-contract.js +59 -0
  116. package/dist-server/service/reference/hook-contract.js.map +1 -0
  117. package/dist-server/service/reference/ingest-space.d.ts +1 -1
  118. package/dist-server/service/reference/ingest-space.js +4 -4
  119. package/dist-server/service/reference/ingest-space.js.map +1 -1
  120. package/dist-server/service/reference/knob-defaults.d.ts +1 -1
  121. package/dist-server/service/reference/knob-defaults.js +4 -4
  122. package/dist-server/service/reference/knob-defaults.js.map +1 -1
  123. package/dist-server/service/reference/reference-adapter.d.ts +140 -8
  124. package/dist-server/service/reference/reference-adapter.js +27 -0
  125. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  126. package/dist-server/service/reference/reference-hook.d.ts +20 -0
  127. package/dist-server/service/reference/reference-hook.js +133 -0
  128. package/dist-server/service/reference/reference-hook.js.map +1 -0
  129. package/dist-server/service/reference/reference-live.d.ts +2 -2
  130. package/dist-server/service/reference/reference-live.js +88 -13
  131. package/dist-server/service/reference/reference-live.js.map +1 -1
  132. package/dist-server/service/reference/reference-master.d.ts +14 -14
  133. package/dist-server/service/reference/reference-master.js +34 -12
  134. package/dist-server/service/reference/reference-master.js.map +1 -1
  135. package/dist-server/service/reference/reference-resolver.d.ts +1 -1
  136. package/dist-server/service/reference/reference-resolver.js +10 -10
  137. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  138. package/dist-server/service/reference/twin-reference.d.ts +3 -3
  139. package/dist-server/service/reference/twin-reference.js.map +1 -1
  140. package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
  141. package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
  142. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  143. package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
  144. package/dist-server/service/twin-audit/command-audit.js.map +1 -1
  145. package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
  146. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
  147. package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
  148. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
  149. package/dist-server/service/twin-control/twin-control-mutation.js +1 -1
  150. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  151. package/dist-server/service/twin-event/twin-event-keys.d.ts +22 -2
  152. package/dist-server/service/twin-event/twin-event-keys.js +37 -5
  153. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  154. package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
  155. package/dist-server/service/twin-event/twin-event-type.js +8 -1
  156. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  157. package/dist-server/service/twin-event/twin-event.d.ts +2 -2
  158. package/dist-server/service/twin-event/twin-event.js +32 -4
  159. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  160. package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
  161. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
  162. package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
  163. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  164. package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
  165. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  166. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
  167. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
  168. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
  169. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
  170. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
  171. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  172. package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
  173. package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
  174. package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
  175. package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
  176. package/dist-server/service/twin-journal/event-type-split.js +102 -0
  177. package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
  178. package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
  179. package/dist-server/service/twin-journal/journal-order.js +17 -0
  180. package/dist-server/service/twin-journal/journal-order.js.map +1 -0
  181. package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
  182. package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
  183. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  184. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
  185. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
  186. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  187. package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
  188. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  189. package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
  190. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
  191. package/dist-server/service/twin-model/epcis-coverage.js +36 -1
  192. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  193. package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
  194. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  195. package/dist-server/service/twin-model/isa95-coverage.d.ts +15 -2
  196. package/dist-server/service/twin-model/isa95-coverage.js +32 -8
  197. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  198. package/dist-server/service/twin-model/item-ref.js +2 -2
  199. package/dist-server/service/twin-model/item-ref.js.map +1 -1
  200. package/dist-server/service/twin-model/name-index.d.ts +1 -1
  201. package/dist-server/service/twin-model/name-index.js +1 -1
  202. package/dist-server/service/twin-model/name-index.js.map +1 -1
  203. package/dist-server/service/twin-model/project-structure.d.ts +1 -1
  204. package/dist-server/service/twin-model/project-structure.js +3 -3
  205. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  206. package/dist-server/service/twin-model/twin-equipment.d.ts +1 -1
  207. package/dist-server/service/twin-model/twin-equipment.js +1 -1
  208. package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
  209. package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
  210. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  211. package/dist-server/service/twin-model/twin-location.d.ts +1 -1
  212. package/dist-server/service/twin-model/twin-location.js.map +1 -1
  213. package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
  214. package/dist-server/service/twin-model/twin-model-item-query.js +14 -14
  215. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  216. package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
  217. package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
  218. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
  219. package/dist-server/service/twin-model/twin-model-query.js +117 -28
  220. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  221. package/dist-server/service/twin-model/twin-model-tree-query.js +22 -0
  222. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  223. package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
  224. package/dist-server/service/twin-model/twin-operation.js +1 -1
  225. package/dist-server/service/twin-model/twin-operation.js.map +1 -1
  226. package/dist-server/service/twin-space/move-space.js +2 -2
  227. package/dist-server/service/twin-space/move-space.js.map +1 -1
  228. package/dist-server/service/twin-space/representation-role.d.ts +2 -0
  229. package/dist-server/service/twin-space/representation-role.js +18 -0
  230. package/dist-server/service/twin-space/representation-role.js.map +1 -0
  231. package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
  232. package/dist-server/service/twin-space/space-integrity.js.map +1 -1
  233. package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
  234. package/dist-server/service/twin-space/twin-space-representation.js +16 -0
  235. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  236. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  237. package/dist-server/service/twin-space/twin-space-resolver.js +36 -7
  238. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  239. package/dist-server/service/twin-structure/twin-structure.js +3 -3
  240. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  241. package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
  242. package/dist-server/service/twin-subject/event-subjects.js +137 -0
  243. package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
  244. package/dist-server/service/twin-subject/index.d.ts +9 -0
  245. package/dist-server/service/twin-subject/index.js +16 -0
  246. package/dist-server/service/twin-subject/index.js.map +1 -0
  247. package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
  248. package/dist-server/service/twin-subject/item-resolver.js +93 -0
  249. package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
  250. package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
  251. package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
  252. package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
  253. package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
  254. package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
  255. package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
  256. package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
  257. package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
  258. package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
  259. package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
  260. package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
  261. package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
  262. package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
  263. package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
  264. package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
  265. package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
  266. package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
  267. package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
  268. package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
  269. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  270. package/dist-shared/entity-delta.d.ts +1 -1
  271. package/dist-shared/entity-delta.js +3 -3
  272. package/dist-shared/entity-delta.js.map +1 -1
  273. package/dist-shared/touched-items.js +2 -2
  274. package/dist-shared/touched-items.js.map +1 -1
  275. package/dist-shared/twin-level.js +1 -1
  276. package/dist-shared/twin-level.js.map +1 -1
  277. package/package.json +3 -3
  278. package/server/engine/attention-digest.ts +9 -9
  279. package/server/engine/canonical-ingest.ts +170 -13
  280. package/server/engine/command-routing.ts +1 -1
  281. package/server/engine/declared-stimulus.ts +1 -1
  282. package/server/engine/energy-topology.ts +1 -1
  283. package/server/engine/ingest-dedupe.ts +149 -0
  284. package/server/engine/ingest-health.ts +234 -20
  285. package/server/engine/integration-probes.ts +7 -7
  286. package/server/engine/kpi-baseline.ts +4 -4
  287. package/server/engine/kpi-fold.ts +395 -27
  288. package/server/engine/kpi-query.ts +212 -46
  289. package/server/engine/live-feed-registry.ts +1 -1
  290. package/server/engine/load-meter.ts +1 -1
  291. package/server/engine/local-declarations.ts +13 -13
  292. package/server/engine/log.ts +1 -1
  293. package/server/engine/measured-estimator.ts +1 -1
  294. package/server/engine/measured-yield.ts +1 -1
  295. package/server/engine/model-basis.ts +2 -2
  296. package/server/engine/model-gap.ts +3 -3
  297. package/server/engine/model-vocabulary.ts +1 -1
  298. package/server/engine/oee-accumulator.ts +4 -4
  299. package/server/engine/property-effects.ts +2 -2
  300. package/server/engine/read-time.ts +77 -0
  301. package/server/engine/restart-policy.ts +1 -1
  302. package/server/engine/runtime-key.ts +1 -1
  303. package/server/engine/spec-coverage.ts +3 -3
  304. package/server/engine/stage-path.ts +5 -5
  305. package/server/engine/state-axes.ts +2 -2
  306. package/server/engine/travel-estimator.ts +2 -2
  307. package/server/engine/twin-engine.ts +764 -201
  308. package/server/engine/warm-start.ts +6 -6
  309. package/server/index.ts +1 -1
  310. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
  311. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
  312. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
  313. package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
  314. package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
  315. package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
  316. package/server/migrations/index.ts +7 -1
  317. package/server/routes.ts +68 -5
  318. package/server/service/index.ts +4 -1
  319. package/server/service/reference/control-routing.ts +1 -1
  320. package/server/service/reference/discovery-result.ts +3 -3
  321. package/server/service/reference/hook-contract.ts +103 -0
  322. package/server/service/reference/ingest-space.ts +4 -4
  323. package/server/service/reference/knob-defaults.ts +4 -4
  324. package/server/service/reference/reference-adapter.ts +166 -9
  325. package/server/service/reference/reference-hook.ts +145 -0
  326. package/server/service/reference/reference-live.ts +92 -14
  327. package/server/service/reference/reference-master.ts +39 -17
  328. package/server/service/reference/reference-resolver.ts +10 -10
  329. package/server/service/reference/twin-reference.ts +3 -3
  330. package/server/service/twin-attention/twin-attention-query.ts +1 -1
  331. package/server/service/twin-audit/command-audit.ts +1 -1
  332. package/server/service/twin-audit/twin-audit-event.ts +2 -2
  333. package/server/service/twin-audit/twin-audit-query.ts +1 -1
  334. package/server/service/twin-control/twin-control-mutation.ts +1 -1
  335. package/server/service/twin-event/twin-event-keys.ts +37 -5
  336. package/server/service/twin-event/twin-event-type.ts +18 -1
  337. package/server/service/twin-event/twin-event.ts +32 -6
  338. package/server/service/twin-forecast/forecast-metrics.ts +1 -1
  339. package/server/service/twin-forecast/gap-analytics.ts +1 -1
  340. package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
  341. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
  342. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
  343. package/server/service/twin-instance/twin-instance.ts +4 -4
  344. package/server/service/twin-journal/event-type-split-shape.ts +112 -0
  345. package/server/service/twin-journal/event-type-split.ts +144 -0
  346. package/server/service/twin-journal/journal-order.ts +20 -0
  347. package/server/service/twin-journal/twin-journal-query.ts +224 -20
  348. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +57 -3
  349. package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
  350. package/server/service/twin-model/axis-journal-evidence.ts +1 -1
  351. package/server/service/twin-model/epcis-coverage.ts +36 -1
  352. package/server/service/twin-model/iec61850-coverage.ts +1 -1
  353. package/server/service/twin-model/isa95-coverage.ts +45 -8
  354. package/server/service/twin-model/item-ref.ts +2 -2
  355. package/server/service/twin-model/name-index.ts +1 -1
  356. package/server/service/twin-model/project-structure.ts +4 -4
  357. package/server/service/twin-model/twin-equipment.ts +2 -2
  358. package/server/service/twin-model/twin-lineage-query.ts +16 -5
  359. package/server/service/twin-model/twin-location.ts +1 -1
  360. package/server/service/twin-model/twin-model-item-query.ts +14 -14
  361. package/server/service/twin-model/twin-model-mutation.ts +4 -4
  362. package/server/service/twin-model/twin-model-query.ts +117 -28
  363. package/server/service/twin-model/twin-model-tree-query.ts +25 -1
  364. package/server/service/twin-model/twin-operation.ts +2 -2
  365. package/server/service/twin-space/move-space.ts +3 -3
  366. package/server/service/twin-space/representation-role.ts +15 -0
  367. package/server/service/twin-space/space-integrity.ts +1 -1
  368. package/server/service/twin-space/twin-space-representation.ts +37 -0
  369. package/server/service/twin-space/twin-space-resolver.ts +39 -7
  370. package/server/service/twin-structure/twin-structure.ts +3 -3
  371. package/server/service/twin-subject/event-subjects.ts +155 -0
  372. package/server/service/twin-subject/index.ts +10 -0
  373. package/server/service/twin-subject/item-resolver.ts +92 -0
  374. package/server/service/twin-subject/subject-query-shape.ts +84 -0
  375. package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
  376. package/server/service/twin-subject/twin-subject-event.ts +101 -0
  377. package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
  378. package/server/service/twin-subject/twin-subject-query.ts +210 -0
  379. package/server/service/twin-subject/twin-subject-writer.ts +189 -0
  380. package/server/service/twin-target/twin-target-resolver.ts +1 -1
  381. package/shared/entity-delta.ts +3 -3
  382. package/shared/touched-items.ts +2 -2
  383. package/shared/twin-level.ts +1 -1
  384. package/test/adopt-structure-live.test.ts +3 -3
  385. package/test/aggregate-not-findone.test.ts +112 -0
  386. package/test/attention-digest.test.ts +6 -6
  387. package/test/axis-read.test.ts +2 -2
  388. package/test/boot-resume.test.ts +20 -7
  389. package/test/broadcast-period.test.ts +2 -2
  390. package/test/broadcast-queue-overflow.test.ts +244 -0
  391. package/test/canonical-ingest-vocabularies.test.ts +179 -7
  392. package/test/canonical-quantity-door.test.ts +2 -2
  393. package/test/checkpoint-refuses-empty.test.ts +2 -2
  394. package/test/connector-capability-declaration.test.ts +88 -0
  395. package/test/control-capability.test.ts +1 -1
  396. package/test/cursor-stall-not-read-failure.test.ts +2 -2
  397. package/test/declaration-reaches-model.test.ts +5 -5
  398. package/test/declared-stimulus.test.ts +4 -4
  399. package/test/discovery-result.test.ts +2 -2
  400. package/test/entity-delta.test.ts +4 -4
  401. package/test/event-subjects.test.ts +147 -0
  402. package/test/event-time-column.test.ts +3 -3
  403. package/test/event-type-split.test.ts +155 -0
  404. package/test/fold-gap-speaks.test.ts +53 -0
  405. package/test/forecast-metrics.test.ts +1 -1
  406. package/test/forecast-tuning.test.ts +1 -1
  407. package/test/ingest-health-engine.test.ts +2 -2
  408. package/test/ingest-health-wiring.test.ts +7 -5
  409. package/test/ingest-health.test.ts +6 -6
  410. package/test/ingest-history.test.ts +2 -2
  411. package/test/ingest-idempotent.test.ts +132 -0
  412. package/test/ingest-space.test.ts +3 -3
  413. package/test/integration-probes.test.ts +3 -3
  414. package/test/integration-runner.test.ts +1 -1
  415. package/test/item-ref.test.ts +1 -1
  416. package/test/journal-order-axis.test.ts +80 -0
  417. package/test/journal-read-discipline.test.ts +16 -6
  418. package/test/journal-retention.test.ts +171 -29
  419. package/test/journal-sort-axis.test.ts +1 -1
  420. package/test/journal-write-door.test.ts +3 -3
  421. package/test/journal-write-trend.test.ts +1 -1
  422. package/test/kernel-kind-guard.test.ts +3 -3
  423. package/test/knob-defaults.test.ts +1 -1
  424. package/test/kpi-baseline-db.test.ts +2 -2
  425. package/test/kpi-fold.test.ts +185 -14
  426. package/test/kpi-query-bench.test.ts +3 -3
  427. package/test/live-cursor-wiring.test.ts +6 -6
  428. package/test/live-kernel-facts.test.ts +11 -11
  429. package/test/live-mirror-parity.test.ts +3 -3
  430. package/test/load-meter.test.ts +1 -1
  431. package/test/local-declarations.test.ts +10 -10
  432. package/test/log-stamp.test.ts +1 -1
  433. package/test/measured-yield.test.ts +2 -2
  434. package/test/merge-streams.test.ts +70 -0
  435. package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
  436. package/test/model-basis.test.ts +2 -2
  437. package/test/model-gap.test.ts +4 -4
  438. package/test/model-vocabulary.test.ts +2 -2
  439. package/test/move-space.test.ts +1 -1
  440. package/test/oee-accumulator.test.ts +2 -2
  441. package/test/operational-vocabulary.test.ts +2 -2
  442. package/test/project-structure-db.test.ts +2 -2
  443. package/test/projection-reaches-screen.test.ts +2 -2
  444. package/test/property-effects.test.ts +3 -3
  445. package/test/read-failure-ledger.test.ts +106 -0
  446. package/test/read-failure-visible.test.ts +7 -7
  447. package/test/read-time.test.ts +118 -0
  448. package/test/reference-grounding.test.ts +1 -1
  449. package/test/reference-hook.test.ts +86 -0
  450. package/test/registry-key-guard.test.ts +1 -1
  451. package/test/rename-twin.test.ts +81 -0
  452. package/test/restart-policy.test.ts +3 -3
  453. package/test/resync-origin-site.test.ts +3 -3
  454. package/test/revision-axis.test.ts +14 -8
  455. package/test/runtime-key.test.ts +3 -3
  456. package/test/snapshot-freshness.test.ts +4 -4
  457. package/test/source-outcome-audit.test.ts +1 -1
  458. package/test/space-integrity.test.ts +1 -1
  459. package/test/space-representation.test.ts +66 -0
  460. package/test/space-scope-includes-stopped.test.ts +55 -0
  461. package/test/spec-coverage.test.ts +1 -1
  462. package/test/standard-coverage.test.ts +43 -1
  463. package/test/status-tally.test.ts +1 -1
  464. package/test/structure-revision-db.test.ts +7 -7
  465. package/test/subject-fast-path.test.ts +148 -0
  466. package/test/subject-read-model.test.ts +175 -0
  467. package/test/touched-items.test.ts +2 -2
  468. package/test/twin-audit.test.ts +1 -1
  469. package/test/twin-event-keys.test.ts +49 -3
  470. package/test/twin-model-item-db.test.ts +5 -5
  471. package/test/twin-model-tree-db.test.ts +35 -2
  472. package/test/twin-origin-resync.test.ts +1 -1
  473. package/test/warm-start-seam.test.ts +1 -1
  474. package/test/warm-start.test.ts +6 -6
  475. package/test/withheld-door.test.ts +95 -0
  476. package/test/yield-loop.test.ts +4 -4
  477. package/tsconfig.shared.tsbuildinfo +1 -1
  478. package/tsconfig.tsbuildinfo +1 -1
@@ -1,9 +1,9 @@
1
1
  /*
2
- * 업무 KPI 폴드 검증 — 저널을 접어 시간창 지표를 만드는 규칙을 고정한다.
2
+ * 업무 KPI 폴드 검증 — 저널을 계산해 시간창 지표를 만드는 규칙을 고정한다.
3
3
  *
4
4
  * 무엇을 지키는가:
5
5
  * ① **성과는 완료 시점으로 센다** — 시작 시점으로 세면 진행 중인 것이 섞여 처리량이 부풀려진다.
6
- * ② **셈에서 빠진 것을 밝힌다**(unpaired) — 시작 이벤트를 못 찾은 완료를 조용히 버리면 평균이 거짓이 된다.
6
+ * ② **셈에서 빠진 것을 밝힌다**(unpaired) — 시작 이벤트를 못 찾은 완료를 알리지 않고 버리면 평균이 거짓이 된다.
7
7
  * ③ **없는 것을 0 으로 꾸미지 않는다** — 저널이 비었으면 eventsSeen 0 으로 그 사실이 보인다.
8
8
  * ④ 시간축은 eventTime 차분 — sim 클록 오프셋이 있어도 차분은 정확하다.
9
9
  */
@@ -82,7 +82,7 @@ test('분위수: p50·p90 을 낸다(작은 표본에서 과장하지 않는다)
82
82
  assert.equal(r.workTime.p90Ms, 10000)
83
83
  })
84
84
 
85
- test('시작 이벤트를 못 찾은 완료는 unpaired 로 밝힌다 — 평균에서 조용히 빠지지 않게', () => {
85
+ test('시작 이벤트를 못 찾은 완료는 unpaired 로 밝힌다 — 평균에서 오류 없이 빠지지 않게', () => {
86
86
  const r = foldKpi([task('t1', 'completed', 30)], WINDOW)
87
87
  assert.equal(r.throughput.tasks, 1, '완료는 센다')
88
88
  assert.equal(r.leadTime.count, 0, '리드타임은 계산할 수 없다')
@@ -159,7 +159,7 @@ test('오더 완료: 양이 답한다 — 낱말이 진행 중이라 말해도',
159
159
  test('오더 완료: 같은 오더를 한 번만 센다 — 폴링 원본이 다시 보내도', () => {
160
160
  /*
161
161
  * 폴링하는 원본은 같은 오더의 종결을 여러 주기에 걸쳐 다시 보낼 수 있다(결함이 아니라 폴링의 성질).
162
- * 사건마다 세면 처리량이 조용히 부풀려진다 — 작업 쪽은 커널의 짝맞춤이 이미 그 규율을 갖고 있었고
162
+ * 사건마다 세면 처리량이 알리지 않고 부풀려진다 — 작업 쪽은 커널의 짝맞춤이 이미 그 규율을 갖고 있었고
163
163
  * 오더만 없었다.
164
164
  */
165
165
  const r = foldKpi([order('o1', 'completed', 10), order('o1', 'completed', 20), order('o2', 'completed', 30)], WINDOW)
@@ -233,7 +233,7 @@ test('같은 전이가 두 번 와도 착수는 처음 것, 생성은 마지막
233
233
  })
234
234
 
235
235
  /* ── 구간 쪼개기(추세) ─────────────────────────────────────────────────────
236
- * 화면의 추세선이 여기서 나온다. 조회는 한 번이고 창만 바꿔 다시 접는다.
236
+ * 화면의 추세선이 여기서 나온다. 조회는 한 번이고 창만 바꿔 다시 계산한다.
237
237
  */
238
238
  test('구간 쪼개기: N 등분해 구간별 처리량을 낸다', () => {
239
239
  /* 1시간 창을 4등분(15분씩). 각 구간에 한 건씩 완료되게 놓는다. */
@@ -336,7 +336,7 @@ test('축의 합이 전체 처리량과 같다 — 어느 축에서도 건이
336
336
  }
337
337
  })
338
338
 
339
- test('축의 값이 없던 건은 unknown 으로 모인다 — 조용히 버리지 않는다', () => {
339
+ test('축의 값이 없던 건은 unknown 으로 모인다 — 알리지 않고 버리지 않는다', () => {
340
340
  const events = [...three('a', 100, { kind: 'move', resourceRef: 'fk-1' }), ...three('b', 200, { kind: 'move' })]
341
341
  const r = foldKpi(events, WINDOW, { groupBy: 'resource' })
342
342
  assert.deepEqual(r.groups?.items.map(g => g.key).sort(), ['fk-1', 'unknown'])
@@ -374,7 +374,7 @@ test('구역 축은 노드→구역 지도를 받아야 낸다 — 지도가 없
374
374
  assert.deepEqual(mapped.groups?.items.map(g => [g.key, g.tasks]), [['zone-a', 2]], '두 노드가 한 구역으로 모인다')
375
375
  })
376
376
 
377
- test('축이 많으면 잘라내고 잘린 축의 수와 그 안의 건수를 함께 알린다 — 조용한 절단 금지', () => {
377
+ test('축이 많으면 잘라내고 잘린 축의 수와 그 안의 건수를 함께 알린다 — 들어온 것이 없는 절단 금지', () => {
378
378
  /* 축마다 건수를 다르게 둔다: 축의 수만 알리면 "그 외 20개" 가 20건인지 60건인지 알 수 없다. */
379
379
  const events: KpiEvent[] = []
380
380
  let t = 0
@@ -403,7 +403,7 @@ test('짝을 못 찾은 건도 축에서 센다 — 건수는 세고 소요만
403
403
  })
404
404
 
405
405
  /* ── 시간 비교 ──────────────────────────────────────────────────────────────
406
- * "29초" 만으로는 좋아졌는지 나빠졌는지 알 수 없다. 창을 옮겨 한 번 더 접는 규칙을 고정한다.
406
+ * "29초" 만으로는 좋아졌는지 나빠졌는지 알 수 없다. 창을 옮겨 한 번 더 계산 규칙을 고정한다.
407
407
  */
408
408
  test('직전 구간: 같은 길이로 바로 앞 — 경계가 맞물린다(겹치거나 벌어지지 않게)', () => {
409
409
  const w = shiftWindow(WINDOW.fromMs, WINDOW.toMs, 'previous')
@@ -431,7 +431,7 @@ test('중앙값 차이: 한쪽이 결측이면 차이는 없다 — 0 으로 내
431
431
  assert.equal(deltaP50(missing, measured), undefined)
432
432
  })
433
433
 
434
- test('비교: 옮긴 창으로 접으면 앞 구간의 성과가 나온다 — 같은 폴드, 창만 다르다', () => {
434
+ test('비교: 옮긴 창으로 계산하면 앞 구간의 성과가 나온다 — 같은 폴드, 창만 다르다', () => {
435
435
  /* 현재 구간에 2건, 직전 구간에 1건. */
436
436
  const events = [
437
437
  ...three('now-1', 100, { kind: 'move' }),
@@ -500,7 +500,7 @@ test('교차: 지금만 있는 축은 isNew — delta 를 건수와 같게 적
500
500
  assert.equal(nw.prevTasks, undefined)
501
501
  })
502
502
 
503
- test('교차: 사라진 축을 따로 알린다 — 멈춘 구역이 표에서 조용히 빠지지 않게', () => {
503
+ test('교차: 사라진 축을 따로 알린다 — 멈춘 구역이 표에서 오류 없이 빠지지 않게', () => {
504
504
  const c = crossGroups([g('pick', 5)], [g('pick', 5), g('putaway', 12)])
505
505
  assert.deepEqual(c.disappeared, [{ key: 'putaway', tasks: 12 }])
506
506
  })
@@ -547,7 +547,7 @@ test('교대별로 쪼갠다 — 완료 시각에서 계산한다(저널에 없
547
547
  assert.equal(r.groups?.items.reduce((s, g) => s + g.tasks, 0), r.throughput.tasks, '축의 합이 전체와 같다')
548
548
  })
549
549
 
550
- test('현장의 시각 기준으로 읽는다 — UTC 로 조용히 가정하지 않는다', () => {
550
+ test('현장의 시각 기준으로 읽는다 — UTC 로 알리지 않고 가정하지 않는다', () => {
551
551
  const events = [...doneAtHour('t1', 20)] // 절대 20시
552
552
  const utc = foldKpi(events, DAY, { groupBy: 'shift', shifts: SHIFTS_3 })
553
553
  const local = foldKpi(events, DAY, { groupBy: 'shift', shifts: SHIFTS_3, utcOffsetMinutes: -420 })
@@ -630,7 +630,7 @@ test('모르는 지표 이름은 짐작해 매핑하지 않는다', () => {
630
630
  /* ── 목표의 범위 (공간 vs 인스턴스) ──────────────────────────────────────────
631
631
  *
632
632
  * 이 규칙은 원래 DB 접근 뒤에 숨어 있어 **화면을 눌러 봐야만** 확인할 수 있었다. 로그인 뒤 화면을
633
- * 자동으로 검증할 방법이 없는 프로젝트라 그런 규칙은 조용히 미검증으로 쌓인다 — 그래서 밖으로 뺐다.
633
+ * 자동으로 검증할 방법이 없는 프로젝트라 그런 규칙은 알리지 않고 미검증으로 쌓인다 — 그래서 밖으로 뺐다.
634
634
  */
635
635
  const row = (metric: string, target: number, scope: { spaceId?: string; instanceId?: string }) => ({
636
636
  metric,
@@ -788,7 +788,7 @@ test('그 창에 완료가 없으면 원단위는 정의되지 않는다 — 전
788
788
  test('계측이 끊긴 구간은 세지 않고 몇 개를 못 셌는지 말한다', () => {
789
789
  const r = foldKpi([win(T, T + q, 400), win(T + q, T + 2 * q), orderDone(T + 60_000)], { fromMs: T, toMs: T + 2 * q })
790
790
  assert.equal(r.energy?.counted, 1)
791
- assert.equal(r.energy?.skipped, 1, '조용히 빼면 이 합이 그 창의 전부인 줄 읽는다')
791
+ assert.equal(r.energy?.skipped, 1, '알리지 않고 빼면 이 합이 그 창의 전부인 줄 읽는다')
792
792
  })
793
793
 
794
794
  test('창에 걸친 구간은 세지 않는다 — 부분을 비례로 자르면 추정이 하나 더 생긴다', () => {
@@ -1134,7 +1134,7 @@ test('피크 구간에 표본이 없던 분기는 피크 값을 만들지 않는
1134
1134
  * ── 완료로 읽지 못한 오더 상태 낱말을 **센다** (2026-08-23) ──────────────────
1135
1135
  *
1136
1136
  * 오더의 상태 어휘는 계약이 도메인에 넘긴 것이라(`picking`·`shipped`·`FINISHED`…) 이 폴드는 「완료로
1137
- * 읽히는 표현」을 동의어 목록으로 받는다. 그 대가는 **목록에 없는 낱말이 조용히 0 으로 세어진다**는 것이다.
1137
+ * 읽히는 표현」을 동의어 목록으로 받는다. 그 대가는 **목록에 없는 낱말이 알리지 않고 0 으로 세어진다**는 것이다.
1138
1138
  *
1139
1139
  * 실제로 그랬다: `finished` 가 빠져 있어 첫 실 시스템의 완료 오더가 전부 0 이었고, 사람이 화면을 보고
1140
1140
  * 의심해서야 드러났다. 목록을 넓히는 것으로는 다음 원본을 막지 못한다 — 그래서 **못 읽은 것을 센다.**
@@ -1174,3 +1174,174 @@ test('상태를 말하지 않은 사건은 여기 오지 않는다 — 그것은
1174
1174
  const r = foldKpi([{ eventType: 'order.status', eventTime: at(10), payload: { data: { orderId: 'o-1' } } }], WINDOW)
1175
1175
  assert.deepEqual(r.orderStatusNotCounted, {})
1176
1176
  })
1177
+
1178
+ /* ── 밖에서 마감되어 온 사용 구간과 청구서 (2026-08-29) ────────────────────
1179
+ *
1180
+ * ★ 계량 표본이 오지 않는 현장이 있다. 원본이 「이 구간에 X 썼다」를 이미 마감해 준다.
1181
+ * 그 값을 쓰지 않으면 그 현장의 화면은 영영 빈손이다.
1182
+ *
1183
+ * 두 회계를 **더하지 않는다** — 같은 전기를 두 번 센다.
1184
+ */
1185
+
1186
+ const usagePeriod = (kWh: number, fromSec: number, toSec: number, extra: Record<string, unknown> = {}): KpiEvent => ({
1187
+ eventType: 'energy.usage.period',
1188
+ eventTime: at(toSec),
1189
+ payload: { data: { meterId: 'incoming-1', from: at(fromSec), to: at(toSec), kWh, ...extra } }
1190
+ })
1191
+
1192
+ test('★ 계량 표본이 없어도 사용 구간으로 답한다 — 근거를 밝힌다', () => {
1193
+ const k = foldKpi([usagePeriod(474.48, 0, 1800, { basis: 'provider' }), usagePeriod(471.6, 1800, 3600, { basis: 'provider' })], WINDOW)
1194
+ assert.equal(k.energy?.kWh, 474.48 + 471.6)
1195
+ assert.equal(k.energy?.kWhBasis, 'usage-period')
1196
+ assert.equal(k.energy?.usage?.counted, 2)
1197
+ assert.deepEqual(k.energy?.usage?.byBasis, [{ basis: 'provider', counted: 2 }])
1198
+ })
1199
+
1200
+ test('창에 걸친 구간은 세지 않는다 — 반만 세면 합이 창을 넘는다', () => {
1201
+ const k = foldKpi([usagePeriod(100, -1800, 1800)], WINDOW)
1202
+ assert.equal(k.energy?.usage, undefined)
1203
+ })
1204
+
1205
+ test('★ 구간마다 제 단가로 매긴다 — 단일 단가로 접지 않는다', () => {
1206
+ /* 새벽 87.3 · 낮 222.3 — 하나로 접으면 「피크를 깎아 얼마를 아끼나」가 사라진다. */
1207
+ const k = foldKpi(
1208
+ [
1209
+ usagePeriod(100, 0, 1800, { unitPrice: 87.3, currency: 'KRW', basis: 'provider' }),
1210
+ usagePeriod(100, 1800, 3600, { unitPrice: 222.3, currency: 'KRW', basis: 'provider' })
1211
+ ],
1212
+ WINDOW,
1213
+ { tariff: { energyChargePerKWh: 140.2, currency: 'KRW' } } as any
1214
+ )
1215
+ assert.equal(k.energy?.usage?.charge, 100 * 87.3 + 100 * 222.3)
1216
+ assert.equal((k.energy?.cost as any)?.energyCharge, 100 * 87.3 + 100 * 222.3, '구간별 단가가 이긴다')
1217
+ assert.ok(!(k.energy?.cost as any)?.notModeled.includes('tou'))
1218
+ })
1219
+
1220
+ test('일부만 단가를 실었으면 요금을 만들지 않고 그 수를 낸다', () => {
1221
+ const k = foldKpi(
1222
+ [usagePeriod(100, 0, 1800, { unitPrice: 87.3, currency: 'KRW' }), usagePeriod(100, 1800, 3600)],
1223
+ WINDOW
1224
+ )
1225
+ assert.equal(k.energy?.usage?.charge, undefined, '있는 것만 더하면 그 합이 창의 요금인 척한다')
1226
+ assert.equal(k.energy?.usage?.unpriced, 1)
1227
+ })
1228
+
1229
+ test('★ 청구서는 사실로 나란히 둔다 — 창이 다르면 견주지 않는다', () => {
1230
+ const bill: KpiEvent = {
1231
+ eventType: 'energy.bill',
1232
+ eventTime: at(3600),
1233
+ payload: { data: { from: at(600), to: at(3000), total: 1_000_000, currency: 'KRW', billingDemandKW: 1650 } }
1234
+ }
1235
+ const k = foldKpi([bill], WINDOW)
1236
+ assert.equal(k.energy?.bill?.total, 1_000_000)
1237
+ assert.equal(k.energy?.bill?.billingDemandKW, 1650)
1238
+ assert.equal(k.energy?.bill?.matchesWindow, false, '한 달 청구서를 하루 창의 값과 빼면 아무것도 뜻하지 않는다')
1239
+ assert.equal(k.energy?.bill?.ourTotalMinusBilled, undefined)
1240
+ })
1241
+
1242
+ test('★ 창이 청구 주기와 같으면 우리 계산과의 차이를 낸다 — 0 이 아니면 우리가 틀렸다', () => {
1243
+ const bill: KpiEvent = {
1244
+ eventType: 'energy.bill',
1245
+ eventTime: at(3600),
1246
+ payload: { data: { from: at(0), to: at(3600), total: 100_000, currency: 'KRW' } }
1247
+ }
1248
+ const k = foldKpi([bill, usagePeriod(1_000, 0, 3600, { unitPrice: 87.3, currency: 'KRW' })], WINDOW, {
1249
+ tariff: { energyChargePerKWh: 87.3, currency: 'KRW' }
1250
+ } as any)
1251
+ assert.equal(k.energy?.bill?.matchesWindow, true)
1252
+ assert.equal(k.energy?.bill?.diff?.total, 1_000 * 87.3 - 100_000)
1253
+ })
1254
+
1255
+ test('★ 총액이 없는 청구서도 항목별로 견준다 — 실제 첫 청구서에 기본요금만 있었다', () => {
1256
+ const bill: KpiEvent = {
1257
+ eventType: 'energy.bill',
1258
+ eventTime: at(3600),
1259
+ payload: { data: { from: at(0), to: at(3600), demandCharge: 13_728_000, currency: 'KRW', billingDemandKW: 1650 } }
1260
+ }
1261
+ const k = foldKpi([bill], WINDOW, {
1262
+ tariff: { demandChargePerKW: 8_320, billingDemandKW: 1_650, currency: 'KRW' }
1263
+ } as any)
1264
+ assert.equal(k.energy?.bill?.diff?.demandCharge, 0, '요금적용전력으로 매기면 청구서와 같다')
1265
+ assert.equal(k.energy?.bill?.diff?.total, undefined, '한쪽에 없는 항목은 견주지 않는다')
1266
+ })
1267
+
1268
+ test('요금적용전력을 선언하지 않으면 기본요금이 어긋나는 것이 드러난다', () => {
1269
+ const bill: KpiEvent = {
1270
+ eventType: 'energy.bill',
1271
+ eventTime: at(3600),
1272
+ payload: { data: { from: at(0), to: at(3600), demandCharge: 13_728_000, currency: 'KRW' } }
1273
+ }
1274
+ /* 선언이 없으면 커널은 창의 피크로 조건부 값을 낸다 — 그 차이가 그대로 나온다. */
1275
+ const peakWindow: KpiEvent = {
1276
+ eventType: 'energy.demand.window',
1277
+ eventTime: at(1800),
1278
+ payload: { data: { startMs: T0 + 900_000, endMs: T0 + 1_800_000, maxKW: 600, meanKW: 600, samples: 4 } }
1279
+ }
1280
+ const k = foldKpi([bill, peakWindow], WINDOW, {
1281
+ tariff: { demandChargePerKW: 8_320, currency: 'KRW' }
1282
+ } as any)
1283
+ const d = k.energy?.bill?.diff?.demandCharge
1284
+ assert.ok(d !== undefined && d !== 0, `어긋남이 드러나야 한다: ${d}`)
1285
+ })
1286
+
1287
+ /* ── 요금적용전력은 청구서가 먼저다 (2026-08-29) ───────────────────────────
1288
+ *
1289
+ * ★ 그 값은 최근 몇 달의 최고 수요로 **매달 다시 정해진다.** 선언에 박아 두면 사람이 매달
1290
+ * 갱신해야 하고, 안 하면 낡은 값으로 기본요금이 조용히 틀린다.
1291
+ */
1292
+
1293
+ const billEvent = (fromSec: number, toSec: number, data: Record<string, unknown>): KpiEvent => ({
1294
+ eventType: 'energy.bill',
1295
+ eventTime: at(toSec),
1296
+ payload: { data: { from: at(fromSec), to: at(toSec), currency: 'KRW', ...data } }
1297
+ })
1298
+
1299
+ test('★ 창을 덮는 청구서가 있으면 그 값으로 매긴다', () => {
1300
+ const k = foldKpi([billEvent(0, 3600, { demandCharge: 1, billingDemandKW: 1650 })], WINDOW, {
1301
+ tariff: { demandChargePerKW: 8_320, billingDemandKW: 1200, currency: 'KRW' }
1302
+ } as any)
1303
+ assert.equal(k.energy?.billingDemand?.kW, 1650)
1304
+ assert.equal(k.energy?.billingDemand?.from, 'bill')
1305
+ assert.equal((k.energy?.cost as any)?.demandCharge, 1650 * 8_320, '선언 1200 이 아니라 청구서 1650 으로 매긴다')
1306
+ })
1307
+
1308
+ test('★ 선언이 청구서와 다르면 낡았다고 말한다 — 감추면 아무도 다시 읽지 않는다', () => {
1309
+ const k = foldKpi([billEvent(0, 3600, { demandCharge: 1, billingDemandKW: 1650 })], WINDOW, {
1310
+ tariff: { demandChargePerKW: 8_320, billingDemandKW: 1200, currency: 'KRW' }
1311
+ } as any)
1312
+ assert.equal(k.energy?.billingDemand?.declaredKW, 1200)
1313
+ assert.equal(k.energy?.billingDemand?.stale, true)
1314
+ })
1315
+
1316
+ test('선언과 청구서가 같으면 낡았다고 하지 않는다', () => {
1317
+ const k = foldKpi([billEvent(0, 3600, { demandCharge: 1, billingDemandKW: 1650 })], WINDOW, {
1318
+ tariff: { demandChargePerKW: 8_320, billingDemandKW: 1650, currency: 'KRW' }
1319
+ } as any)
1320
+ assert.equal(k.energy?.billingDemand?.stale, undefined)
1321
+ })
1322
+
1323
+ test('★ 창에 청구서가 없으면 마지막 청구서의 값을 이어 쓴다 — 요금 제도가 그렇게 군다', () => {
1324
+ const k = foldKpi([usagePeriod(100, 0, 3600, { unitPrice: 87.3, currency: 'KRW' })], WINDOW, {
1325
+ tariff: { demandChargePerKW: 8_320, currency: 'KRW' },
1326
+ lastBill: { to: at(-86400), billingDemandKW: 1650 }
1327
+ } as any)
1328
+ assert.equal(k.energy?.billingDemand?.kW, 1650)
1329
+ assert.equal(k.energy?.billingDemand?.from, 'carried-forward')
1330
+ assert.equal((k.energy?.cost as any)?.demandBasis, 'billing-demand')
1331
+ })
1332
+
1333
+ test('청구가 오지 않는 현장은 선언을 쓴다', () => {
1334
+ const k = foldKpi([usagePeriod(100, 0, 3600, { unitPrice: 87.3, currency: 'KRW' })], WINDOW, {
1335
+ tariff: { demandChargePerKW: 8_320, billingDemandKW: 900, currency: 'KRW' }
1336
+ } as any)
1337
+ assert.equal(k.energy?.billingDemand?.from, 'declaration')
1338
+ assert.equal(k.energy?.billingDemand?.kW, 900)
1339
+ assert.equal(k.energy?.billingDemand?.stale, undefined)
1340
+ })
1341
+
1342
+ test('셋 다 없으면 이 칸을 만들지 않는다 — 빈 값을 지어내지 않는다', () => {
1343
+ const k = foldKpi([usagePeriod(100, 0, 3600, { unitPrice: 87.3, currency: 'KRW' })], WINDOW, {
1344
+ tariff: { demandChargePerKW: 8_320, currency: 'KRW' }
1345
+ } as any)
1346
+ assert.equal(k.energy?.billingDemand, undefined)
1347
+ })
@@ -7,7 +7,7 @@
7
7
  * · 브로드캐스팅 — 서버가 주기마다 전 단계를 계산해 밀어 준다. 자리가 트윈당 최대 200개고 지표가 6개라
8
8
  * 태그가 1,200개가 된다. **모델에 카드를 하나도 안 놓아도 계산은 돈다.**
9
9
  * · 질의 — 카드가 필요할 때 묻는다. **모델에 놓인 카드 수만큼만** 든다. 시각(`toTime`)도 물을 수 있어
10
- * 타임라인과 맞고, 공간 단위 합산(여러 트윈을 한 번에 접기)은 질의에만 있다.
10
+ * 타임라인과 맞고, 공간 단위 합산(여러 트윈을 한 번에 계산)은 질의에만 있다.
11
11
  *
12
12
  * 질의로 정하려면 **한 번의 비용**을 알아야 한다. 카드 20장이 30초마다 물으면 감당되는지가 결정을
13
13
  * 가른다. 짐작하지 않고 잰다.
@@ -82,7 +82,7 @@ before(async () => {
82
82
  const d = await ds.getRepository(Domain).save({ name: 'kpi-bench', subdomain: 'kpi-bench' } as any)
83
83
  domainId = d.id
84
84
 
85
- /* 자리 계층 — 자리 200개를 구역 4개에 나눠 붙인다(구역 단위 접기 비용도 재려면 부모가 있어야 한다). */
85
+ /* 자리 계층 — 자리 200개를 구역 4개에 나눠 붙인다(구역 단위 계산 비용도 재려면 부모가 있어야 한다). */
86
86
  const locations = Array.from({ length: LOCATIONS }, (_, i) => ({
87
87
  id: `loc-${i}`,
88
88
  type: 'storage',
@@ -125,6 +125,6 @@ test('bench: 구간 성과 폴드 한 번의 비용 vs 창 안의 작업 수', a
125
125
  console.log(` 작업 ${count.toLocaleString()}건(이벤트 ${(count * 2).toLocaleString()}줄) · 자리 ${LOCATIONS}개`)
126
126
  console.log(` 폴드 1회 ${plain.toFixed(1)}ms`)
127
127
  console.log(` 구역별 폴드 1회 ${byArea.toFixed(1)}ms`)
128
- console.log(` 카드 20장 동시 ${(plain * 20).toFixed(0)}ms (직렬 상한 — 실제로는 한 번 접어 나눠 쓸 수 있다)`)
128
+ console.log(` 카드 20장 동시 ${(plain * 20).toFixed(0)}ms (직렬 상한 — 실제로는 한 번 계산해 나눠 쓸 수 있다)`)
129
129
  }
130
130
  })
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * ★ 읽기 커서는 **부르는 쪽이 자리를 가리켜야** 이어진다 — 커넥터는 그 이름을 알 수 없다.
3
3
  *
4
- * ── 무엇이 조용히 끊겨 있었나 (2026-08-23) ──────────────────────────────────
4
+ * ── 무엇이 알리지 않고 끊겨 있었나 (2026-08-23) ──────────────────────────────────
5
5
  * 커서를 저장할 컬럼과 계약을 만들고 배선까지 했는데, **한 번도 저장되지 않았다.** 저장할 자리를
6
6
  * `cfg`·`site` 에서 찾으려 했기 때문이다.
7
7
  *
@@ -12,7 +12,7 @@
12
12
  * 원본은 그것을 모른다. 커넥터가 채울 수 없는 값을 커넥터에서 읽으려 한 것이 잘못이었다.
13
13
  *
14
14
  * 그리고 그 실패는 **로그로는 말했다** — 「this feed does not say which reference it reads from」.
15
- * 그 한 줄로 다른 레인이 바로 짚었다(조용히 넘기지 않은 것이 값을 했다). 그러나 로그는 사람이 볼 때만
15
+ * 그 한 줄로 다른 레인이 바로 짚었다(알리지 않고 넘기지 않은 것이 값을 했다). 그러나 로그는 사람이 볼 때만
16
16
  * 값이 있다. 배선이 다시 끊기면 이 시험이 먼저 말한다.
17
17
  *
18
18
  * ── 왜 「늘었나」로 확인하면 안 되나 ────────────────────────────────────────
@@ -44,10 +44,10 @@ test('커서를 저장할 자리는 부르는 쪽이 가리킨다 — cfg·site
44
44
  assert.match(code, /startReferenceLiveFeed/, '대상 함수가 남아 있다')
45
45
  })
46
46
 
47
- test('모든 호출처가 그 이름을 넘긴다 — 한 곳만 빠져도 그 경로는 조용히 창이 미끄러진다', () => {
47
+ test('모든 호출처가 그 이름을 넘긴다 — 한 곳만 빠져도 그 경로는 알리지 않고 창이 미끄러진다', () => {
48
48
  /*
49
49
  * 호출처가 둘이고 **둘 다** `TwinReference` 를 들고 있다(부팅 재개 · 인제스트 직후 기동).
50
- * 하나만 넘기면 그 경로로 세워진 트윈만 커서를 잃고, 그 차이는 오류 없이 조용하다.
50
+ * 하나만 넘기면 그 경로로 세워진 트윈만 커서를 잃고, 그 차이는 오류 없이 들어온 것이 없다.
51
51
  */
52
52
  const calls = [...codeOf(LIVE).matchAll(/startReferenceLiveFeed\(([^)]*)\)/g)].map(m => m[1])
53
53
  const resolverCalls = [...codeOf(RESOLVER).matchAll(/startReferenceLiveFeed\([\s\S]{0,200}?\)/g)].map(m => m[0])
@@ -58,7 +58,7 @@ test('모든 호출처가 그 이름을 넘긴다 — 한 곳만 빠져도 그
58
58
  }
59
59
  })
60
60
 
61
- test('이름이 없으면 조용히 돌지 않는다 — 말한다', () => {
61
+ test('이름이 없으면 알리지 않고 돌지 않는다 — 말한다', () => {
62
62
  /*
63
63
  * 커서 없이 도는 것은 「창이 매번 미끄러진다」는 뜻이고, 그 손실은 오류 없이 사라진다. 그래서 그
64
64
  * 사실을 로그로 말한다 — 실제로 그 한 줄이 이 결함을 찾아냈다.
@@ -82,6 +82,6 @@ test('커서는 레퍼런스의 것이다 — 인스턴스마다 두지 않는
82
82
  })
83
83
 
84
84
  test('저장 실패를 삼키지 않는다', () => {
85
- /* 조용히 실패하면 다음 재기동에서 창이 다시 미끄러지고, 이 컬럼을 만든 이유가 사라진다. */
85
+ /* 오류 없이 실패하면 다음 재기동에서 창이 다시 미끄러지고, 이 컬럼을 만든 이유가 사라진다. */
86
86
  assert.match(codeOf(LIVE), /live cursor not saved/, '저장 실패를 말한다')
87
87
  })
@@ -65,7 +65,7 @@ test('커널이 낸 사실도 브로드캐스팅을 예약한다 — 피크·마
65
65
 
66
66
  /*
67
67
  * ── 구조 전환도 브로드캐스팅한다 (2026-08-18) ───────────────────────────────────────
68
- * 화면이 보는 것 상당수가 구조에서 파생된다(계약 대비 판정·주목 신호·자리 색). 구조 전환이 조용히
68
+ * 화면이 보는 것 상당수가 구조에서 파생된다(계약 대비 판정·주목 신호·자리 색). 구조 전환이 알리지 않고
69
69
  * 끝나면 지도 레일은 다음 계측 표본이 올 때까지 옛 화면을 들고 있고, 헤더 배지는 폴링으로 먼저 알아
70
70
  * 「배지엔 있고 목록엔 없는」 어긋난 화면이 된다(실화면에서 봤다).
71
71
  */
@@ -101,18 +101,18 @@ test('과거 화면의 「지금」은 잘라 낸 뒤의 마지막 사실이다
101
101
  const body = methodOf('recover')
102
102
  /* 예전에는 전량(rows)의 마지막 시각을 썼다 — 물어본 시점보다 뒤의 이벤트라 과거 화면이 미래를
103
103
  기준으로 「늦었다」를 판정할 수 있었다. 이제 rows 는 이미 잘린 것이다. */
104
- assert.match(body, /const wanted = rows/, '자른 것과 접는 것이 같아야 한다')
104
+ assert.match(body, /const wanted = rows/, '자른 것과 계산하는 것이 같아야 한다')
105
105
  })
106
106
 
107
107
  /*
108
- * ── 재개점에서 이어 접는다 (2026-08-18) ──────────────────────────────────────
109
- * 접은 결과는 이미 남기고 있었지만, 그 지점의 **재개점**(리듀서 내부 상태 전부 + 가동 누적기)이 없어서
110
- * 이벤트가 하나만 늘어도 0부터 다시 접어야 했다. 동치는 커널 시험이 증명한다(0부터 == 재개점 + 꼬리).
111
- * 여기서는 **호스트가 그 씨앗을 실제로 쓰는지**를 지킨다 — 배선이 사라지면 지름길은 조용히 사라진다.
108
+ * ── 재개점에서 이어서 계산한다 (2026-08-18) ──────────────────────────────────────
109
+ * 계산한 결과는 이미 남기고 있었지만, 그 지점의 **재개점**(리듀서 내부 상태 전부 + 가동 누적기)이 없어서
110
+ * 이벤트가 하나만 늘어도 0부터 다시 계산해야 했다. 동치는 커널 시험이 증명한다(0부터 == 재개점 + 꼬리).
111
+ * 여기서는 **호스트가 그 씨앗을 실제로 쓰는지**를 지킨다 — 배선이 사라지면 지름길은 오류 없이 사라진다.
112
112
  */
113
113
  test('폴드가 재개점을 쓰고, 새 재개점을 함께 남긴다', () => {
114
114
  const body = methodOf('recover')
115
- assert.match(body, /replayFrom\(model, resume\.fold\.reducer/, '씨앗이 있으면 꼬리만 접는다')
115
+ assert.match(body, /replayFrom\(model, resume\.fold\.reducer/, '씨앗이 있으면 꼬리만 계산한다')
116
116
  assert.match(body, /revision: MoreThan\(from\)/, '읽는 것도 그 뒤만')
117
117
  assert.match(body, /oee\.restore\(resume\.fold\.oee\)/, '가동 누적기도 이어받는다(없으면 가용률이 작아진다)')
118
118
  assert.match(body, /replayWithCheckpoint\(/, '씨앗이 없을 때도 새 재개점을 만든다')
@@ -122,7 +122,7 @@ test('폴드가 재개점을 쓰고, 새 재개점을 함께 남긴다', () => {
122
122
  test('씨앗을 좁게 쓴다 — 캐시가 사실을 이기지 않는다', () => {
123
123
  const body = methodOf('recover')
124
124
  /* 지금을 물었을 때만·구조가 그대로일 때만. 시간여행은 목표 이전 재개점이 필요하고(사슬은 다음 단계),
125
- 구조가 바뀌면 그 경계에서 갈라 접어야 한다. */
125
+ 구조가 바뀌면 그 경계에서 갈라 계산해야 한다. */
126
126
  assert.match(body, /asOfNowRead && tip && cachedForResume && cachedForResume\.fold/, '지금 읽기에만 쓴다')
127
127
  assert.match(body, /\(cachedForResume\.structureRev \?\? null\) === tip\.structureRev/, '구조가 같을 때만 쓴다')
128
128
  assert.match(body, /segments\.length <= 1/, '구조가 갈린 폴드에는 쓰지 않는다')
@@ -131,10 +131,10 @@ test('씨앗을 좁게 쓴다 — 캐시가 사실을 이기지 않는다', () =
131
131
  /*
132
132
  * ── 재개점 **사슬** (2026-08-18) ─────────────────────────────────────────────
133
133
  * 최신 재개점 하나로는 과거를 도울 수 없다 — 그것은 언제나 목표보다 뒤에 있다. 그래서 지점을 여러 개
134
- * 남기고, 과거를 물으면 **목표 직전** 지점에서 접는다. 지점은 각각 상태 전체를 들고 있어 무겁기 때문에
135
- * 눈금(리비전 간격)을 넘을 때만 남기고 최근 몇 개만 든다 — 더 과거는 0부터 접는다(알려진 한계).
134
+ * 남기고, 과거를 물으면 **목표 직전** 지점에서 계산한다. 지점은 각각 상태 전체를 들고 있어 무겁기 때문에
135
+ * 눈금(리비전 간격)을 넘을 때만 남기고 최근 몇 개만 든다 — 더 과거는 0부터 계산한다(알려진 한계).
136
136
  */
137
- test('과거 읽기는 목표 직전 지점에서 접는다 — 사슬을 쓴다', () => {
137
+ test('과거 읽기는 목표 직전 지점에서 계산한다 — 사슬을 쓴다', () => {
138
138
  const body = methodOf('recover')
139
139
  assert.match(body, /this\.chainSeedFor\(domainId, instanceId, \{/, '과거 씨앗을 사슬에서 고른다')
140
140
  assert.match(body, /structureCount <= 1/, '구조가 갈린 트윈에서는 쓰지 않는다')
@@ -247,7 +247,7 @@ test('★ withLiveAttentions 는 채널을 하나도 떨어뜨리지 않는다(
247
247
 
248
248
  /*
249
249
  * ── 되짚은 상태에도 신호가 있어야 한다 (2026-08-05) ────────────────────────
250
- * 라이브·시뮬은 커널이 신호를 스스로 내지만, 저널을 접는 경로는 프로젝터 상태만 낸다 — 신호 키가
250
+ * 라이브·시뮬은 커널이 신호를 스스로 내지만, 저널을 계산하는 경로는 프로젝터 상태만 낸다 — 신호 키가
251
251
  * 아예 없었다. 지도는 `snap.attentions` 를 읽으므로 **과거를 되짚으면 주의 레일이 텅 비었고**,
252
252
  * 기동돼 있지 않은 트윈을 보는 화면도 같았다. 실 저널로 재 보니 `rosarito-wms` 는 246건이 비어 있었다.
253
253
  *
@@ -260,7 +260,7 @@ test('★ 프로젝터 폴드 상태에는 신호 키가 없다 — 보강이
260
260
  const proj = new StateProjector(model)
261
261
  for (const env of events) proj.apply(env)
262
262
  const raw: any = proj.snapshot()
263
- assert.equal(raw.attentions, undefined, '접은 상태는 신호를 스스로 내지 않는다(커널이 아니다)')
263
+ assert.equal(raw.attentions, undefined, '계산한 상태는 신호를 스스로 내지 않는다(커널이 아니다)')
264
264
  })
265
265
 
266
266
  test('★ 보강하면 다시 계산한 상태에도 신호가 있다 — 지도의 주의 레일이 비지 않게', () => {
@@ -274,6 +274,6 @@ test('★ 보강하면 다시 계산한 상태에도 신호가 있다 — 지도
274
274
  tasks: []
275
275
  }
276
276
  const out = withLiveAttentions(st)
277
- assert.ok((out.attentions ?? []).length > 0, '접은 상태에서도 규칙이 신호를 낸다')
277
+ assert.ok((out.attentions ?? []).length > 0, '계산한 상태에서도 규칙이 신호를 낸다')
278
278
  assert.equal(out.attentions![0].kind, 'bottleneck')
279
279
  })
@@ -84,7 +84,7 @@ test('경고는 쿨다운을 둔다 — 느린 트윈이 매 틱 경고하면
84
84
  const slow = 900
85
85
 
86
86
  assert.equal(judgeCycle(m, slow, TICK, 1000).warn, true, '처음엔 말한다')
87
- assert.equal(judgeCycle(m, slow, TICK, 2000).warn, false, '쿨다운 안에는 조용히')
87
+ assert.equal(judgeCycle(m, slow, TICK, 2000).warn, false, '쿨다운 안에는 알리지 않고')
88
88
  assert.equal(judgeCycle(m, slow, TICK, 1000 + WARN_COOLDOWN_MS).warn, true, '쿨다운이 지나면 다시')
89
89
 
90
90
  assert.equal(m.overBudget, 3, '말하지 않아도 세기는 한다')
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * 현장 선언 — **구조는 원천이, 수는 현장이.**
3
3
  *
4
- * 이 시험이 지키는 것: 사용자가 넣은 수가 ① 검사를 거치고(단위를 잘못 읽은 값이 조용히 들어가지 않는다)
4
+ * 이 시험이 지키는 것: 사용자가 넣은 수가 ① 검사를 거치고(단위를 잘못 읽은 값이 알리지 않고 들어가지 않는다)
5
5
  * ② 원천에서 다시 그려도 살아남고 ③ 갈 곳이 없으면 그 사실이 남는다.
6
6
  */
7
7
  import { test } from 'node:test'
@@ -122,7 +122,7 @@ test('같은 속성을 다시 넣으면 새 값이 이긴다 — 현장이 값
122
122
  const second = mergeLocalDeclarations(first, [
123
123
  { target: 'location', id: 'incoming-1', properties: [{ id: 'tariff.demandChargePerKW', value: '24' }, { id: 'tariff.currency', value: 'USD' }] }
124
124
  ])
125
- assert.equal(second.length, 1, '같은 대상은 한 줄로 접힌다')
125
+ assert.equal(second.length, 1, '같은 대상은 한 줄로 합쳐진다')
126
126
  assert.deepEqual(second[0].properties.map(p => [p.id, p.value]), [
127
127
  ['tariff.demandChargePerKW', '24'],
128
128
  ['tariff.currency', 'USD']
@@ -154,7 +154,7 @@ test('현장 값이 원천 값을 덮는다 — 고쳐 넣었다는 것은 원
154
154
  assert.equal(p.source, 'local')
155
155
  })
156
156
 
157
- test('대상이 사라졌으면 그 사실을 낸다 — 갈 곳 없는 선언을 조용히 버리지 않는다', () => {
157
+ test('대상이 사라졌으면 그 사실을 낸다 — 갈 곳 없는 선언을 알리지 않고 버리지 않는다', () => {
158
158
  const { missingTargets } = applyLocalDeclarations(emsModel(), [
159
159
  { target: 'equipment', id: 'ess-gone', properties: [{ id: 'storage.capacityKWh', value: '500' }] }
160
160
  ])
@@ -191,7 +191,7 @@ test('경고 문장이 무엇을 해야 하는지 말한다 — 우리가 시간
191
191
  * 필드 이름을 틀린 호출(예: id 대신 targetId)이 「받아준 선언이 없다」는 답만 받고 rejected 는 비어
192
192
  * 있었다. 사용자는 값을 넣었다고 믿는데 화면은 아무것도 바뀌지 않고, 고칠 단서도 없다.
193
193
  */
194
- test('대상 id 가 없으면 이유를 말한다 — 조용히 건너뛰지 않는다', () => {
194
+ test('대상 id 가 없으면 이유를 말한다 — 알리지 않고 건너뛰지 않는다', () => {
195
195
  const { accepted, rejected } = validateLocalDeclarations('ems', [
196
196
  { properties: [{ id: 'contract.kW', value: '3500' }] } as any
197
197
  ])
@@ -289,7 +289,7 @@ test('기억이 없는 옛 선언은 손대지 않고 그렇게 말한다 —
289
289
  assert.equal((back.model.locations[0] as any).properties[0].value, '600', '값은 그대로다 — 다음 재읽기가 바로잡는다')
290
290
  })
291
291
 
292
- test('겹에 없는 것을 뺐다고 하지 않는다 — 조용한 성공이 가장 비싸다', () => {
292
+ test('겹에 없는 것을 뺐다고 하지 않는다 — 성공이라 답하면서 아무 일도 하지 않는 것이 가장 비싸다', () => {
293
293
  const { revoked, notFound } = revokeLocalDeclarations(
294
294
  [{ target: 'location', id: 'incoming-1', properties: [{ id: 'contract.kW', value: '600', replaced: '3500' }] }],
295
295
  [
@@ -309,7 +309,7 @@ test('겹에 없는 것을 뺐다고 하지 않는다 — 조용한 성공이
309
309
  * 이고(SAP 는 `P3`, Oracle 은 `W01` 로 같은 현장을 부른다), 여기 담기는 것은 그 시스템이 **모르는**
310
310
  * 값이다 — 이름이 내용과 정반대였다. 축은 「누가 말했나」이고 답은 `reference` ↔ `local` 둘이다.
311
311
  *
312
- * 낱말은 조용히 되돌아간다(복사·자동완성·옛 문서). 그래서 검사로 못 박는다.
312
+ * 낱말은 알리지 않고 되돌아간다(복사·자동완성·옛 문서). 그래서 검사로 못 박는다.
313
313
  */
314
314
  test('이 겹의 어휘는 local 이다 — site·facility 로 되돌아가지 않는다', () => {
315
315
  const src = readFileSync(fileURLToPath(new URL('../server/engine/local-declarations.ts', import.meta.url)), 'utf-8')
@@ -430,7 +430,7 @@ test('구조 투영이 중복 id 를 이름으로 말한다 — 제약 위반
430
430
  const src = readFileSync(fileURLToPath(new URL('../server/service/twin-model/project-structure.ts', import.meta.url)), 'utf-8')
431
431
  assert.match(src, /has two \$\{what\} with id/, '무엇이 겹쳤는지 이름으로 말한다')
432
432
  assert.match(src, /ids must be unique within a twin/, '규칙도 함께 말한다')
433
- /* 조용히 하나만 남기지 않는다 — 어느 쪽이 맞는지 우리는 모른다. */
433
+ /* 알리지 않고 하나만 남기지 않는다 — 어느 쪽이 맞는지 우리는 모른다. */
434
434
  assert.equal(/dedupe|distinctBy/.test(src), false, '중복을 소리 없이 지우지 않는다')
435
435
  })
436
436
 
@@ -508,7 +508,7 @@ test('지게차에 계약전력을 넣던 구멍이 막혔다 — 읽는 코드
508
508
  assert.equal(rejected[0]?.reason, 'wrong-target')
509
509
  })
510
510
 
511
- test('확인할 수 있으면 오타 공정 이름을 거절한다 — 조용히 아무 일도 안 하지 않게', () => {
511
+ test('확인할 수 있으면 오타 공정 이름을 거절한다 — 알리지 않고 아무 일도 안 하지 않게', () => {
512
512
  const decl = [{ target: 'operation' as const, id: 'put-away', properties: [{ id: 'operation.duration', value: '4', uom: 'MIN' }] }]
513
513
  const checked = validateLocalDeclarations('wms', decl, { knownOperations: ['putaway', 'pick'] })
514
514
  assert.deepEqual(checked.accepted, [])
@@ -534,7 +534,7 @@ test('철회하면 표에서 빠진다 — 원천 명세나 커널 상수가 다
534
534
  assert.deepEqual(back.restored.length, 1)
535
535
  })
536
536
 
537
- test('없는 공정을 철회했다고 말하지 않는다 — 조용한 성공이 가장 비싸다', () => {
537
+ test('없는 공정을 철회했다고 말하지 않는다 — 성공이라 답하면서 아무 일도 하지 않는 것이 가장 비싸다', () => {
538
538
  const back = restoreRevoked({ locations: [], equipment: [], localDurations: { pick: 'PT60S' } }, [
539
539
  { target: 'operation', id: 'putaway', propertyId: 'operation.duration', replaced: null }
540
540
  ])
@@ -642,7 +642,7 @@ test('모수 철회는 모수 표에서 뺀다 — 시간 선언은 그대로
642
642
  assert.deepEqual((back.model as any).localDurations, { weld: 'PT180S' }, '시간은 그대로')
643
643
  })
644
644
 
645
- test('없는 모수를 철회했다고 말하지 않는다 — 조용한 성공이 가장 비싸다', () => {
645
+ test('없는 모수를 철회했다고 말하지 않는다 — 성공이라 답하면서 아무 일도 하지 않는 것이 가장 비싸다', () => {
646
646
  const back = restoreRevoked({ locations: [], equipment: [], localParams: { weld: { yield: '0.9' } } }, [
647
647
  { target: 'operation', id: 'weld', propertyId: 'operation.setupDuration', replaced: null }
648
648
  ])
@@ -34,7 +34,7 @@ test('오류는 스택까지 남는다 — 「어디서 났나」를 잃지 않
34
34
  assert.ok(out.includes('log-stamp.test'), '스택이 있어야 어느 자리에서 났는지 알 수 있다')
35
35
  })
36
36
 
37
- test('값이 없어도 있었다는 사실은 남긴다 — 조용히 지우지 않는다', () => {
37
+ test('값이 없어도 있었다는 사실은 남긴다 — 알리지 않고 지우지 않는다', () => {
38
38
  assert.equal(line(['a', undefined, null, 0]), 'a undefined null 0')
39
39
  const circular: any = { name: 'ㄱ' }
40
40
  circular.self = circular
@@ -11,7 +11,7 @@
11
11
  * ② 표본이 모자라면 **배우지 않는다** — 비율은 드문 사건을 재는 값이라 작은 표본이 잡음을 만든다.
12
12
  * ③ 0 도 사실이다 — 전량 불량이면 0 을 배운다(「값이 없다」와 다르다).
13
13
  * ④ 모르는 종류에는 `undefined` — 그러면 커널이 선언값·상수로 내려간다.
14
- * ⑤ 배우지 못한 것을 **밝힌다** — 조용히 일부만 반영하지 않는다.
14
+ * ⑤ 배우지 못한 것을 **밝힌다** — 알리지 않고 일부만 반영하지 않는다.
15
15
  */
16
16
  import { test } from 'node:test'
17
17
  import assert from 'node:assert/strict'
@@ -84,7 +84,7 @@ test('사슬 넷이 이어져 있다 — 판정→축→집계→비율', () =>
84
84
  assert.match(hostSrc('engine/kpi-fold.ts'), /facets\.outcome/, 'KPI 가 판정 축을 읽는다')
85
85
  assert.match(hostSrc('engine/kpi-fold.ts'), /quality\?: \{ good: number; scrap: number \}/, '종류별로 센다')
86
86
  const engine = hostSrc('engine/twin-engine.ts')
87
- assert.match(engine, /buildYieldEstimator\(kpi\?\.groups\?\.items/, '같은 폴드에서 배운다(두 번 접지 않는다)')
87
+ assert.match(engine, /buildYieldEstimator\(kpi\?\.groups\?\.items/, '같은 폴드에서 배운다(두 번 계산하지 않는다)')
88
88
  assert.match(engine, /kernel\.yieldEstimator = yields\.estimator/, '커널 시임에 넣는다')
89
89
  assert.match(engine, /yieldOf missing/, '옛 커널이면 그 사실을 말한다')
90
90
  })