@things-factory/headless-twin 10.0.15 → 10.0.18

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 (513) 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 +18 -82
  5. package/dist-server/engine/canonical-ingest.js +183 -18
  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 +4 -4
  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 +90 -10
  17. package/dist-server/engine/ingest-health.js +125 -16
  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 +212 -14
  25. package/dist-server/engine/kpi-fold.js +374 -21
  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 +281 -36
  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 +15 -14
  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 +3 -3
  52. package/dist-server/engine/model-vocabulary.js.map +1 -1
  53. package/dist-server/engine/oee-accumulator.d.ts +6 -5
  54. package/dist-server/engine/oee-accumulator.js +6 -5
  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 +307 -54
  78. package/dist-server/engine/twin-engine.js +822 -196
  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 +30 -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 +116 -9
  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 +76 -13
  131. package/dist-server/service/reference/reference-live.js.map +1 -1
  132. package/dist-server/service/reference/reference-master.d.ts +25 -16
  133. package/dist-server/service/reference/reference-master.js +35 -13
  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 +12 -12
  137. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  138. package/dist-server/service/reference/template-registry.d.ts +1 -1
  139. package/dist-server/service/reference/template-registry.js.map +1 -1
  140. package/dist-server/service/reference/twin-reference.d.ts +3 -3
  141. package/dist-server/service/reference/twin-reference.js.map +1 -1
  142. package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
  143. package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
  144. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  145. package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
  146. package/dist-server/service/twin-audit/command-audit.js.map +1 -1
  147. package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
  148. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
  149. package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
  150. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
  151. package/dist-server/service/twin-backfill/backfill-period-facts.d.ts +24 -0
  152. package/dist-server/service/twin-backfill/backfill-period-facts.js +96 -0
  153. package/dist-server/service/twin-backfill/backfill-period-facts.js.map +1 -0
  154. package/dist-server/service/twin-backfill/backfill-shape.d.ts +34 -0
  155. package/dist-server/service/twin-backfill/backfill-shape.js +75 -0
  156. package/dist-server/service/twin-backfill/backfill-shape.js.map +1 -0
  157. package/dist-server/service/twin-backfill/index.d.ts +2 -0
  158. package/dist-server/service/twin-backfill/index.js +6 -0
  159. package/dist-server/service/twin-backfill/index.js.map +1 -0
  160. package/dist-server/service/twin-backfill/twin-backfill-resolver.d.ts +10 -0
  161. package/dist-server/service/twin-backfill/twin-backfill-resolver.js +44 -0
  162. package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +1 -0
  163. package/dist-server/service/twin-control/twin-control-mutation.js +3 -3
  164. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  165. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  166. package/dist-server/service/twin-event/twin-event-keys.js +2 -2
  167. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  168. package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
  169. package/dist-server/service/twin-event/twin-event-type.js +8 -1
  170. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  171. package/dist-server/service/twin-event/twin-event.d.ts +2 -2
  172. package/dist-server/service/twin-event/twin-event.js +32 -4
  173. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  174. package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
  175. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
  176. package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
  177. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  178. package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
  179. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  180. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
  181. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
  182. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
  183. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
  184. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
  185. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  186. package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
  187. package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
  188. package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
  189. package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
  190. package/dist-server/service/twin-journal/event-type-split.js +102 -0
  191. package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
  192. package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
  193. package/dist-server/service/twin-journal/journal-order.js +17 -0
  194. package/dist-server/service/twin-journal/journal-order.js.map +1 -0
  195. package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
  196. package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
  197. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  198. package/dist-server/service/twin-lifecycle/domain-catalog.js +2 -1
  199. package/dist-server/service/twin-lifecycle/domain-catalog.js.map +1 -1
  200. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
  201. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
  202. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  203. package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
  204. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  205. package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
  206. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
  207. package/dist-server/service/twin-model/epcis-coverage.js +34 -3
  208. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  209. package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
  210. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  211. package/dist-server/service/twin-model/isa95-coverage.d.ts +12 -9
  212. package/dist-server/service/twin-model/isa95-coverage.js +33 -9
  213. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  214. package/dist-server/service/twin-model/item-ref.js +2 -2
  215. package/dist-server/service/twin-model/item-ref.js.map +1 -1
  216. package/dist-server/service/twin-model/name-index.d.ts +1 -1
  217. package/dist-server/service/twin-model/name-index.js +2 -2
  218. package/dist-server/service/twin-model/name-index.js.map +1 -1
  219. package/dist-server/service/twin-model/project-structure.d.ts +1 -1
  220. package/dist-server/service/twin-model/project-structure.js +7 -6
  221. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  222. package/dist-server/service/twin-model/twin-equipment.d.ts +2 -2
  223. package/dist-server/service/twin-model/twin-equipment.js +3 -3
  224. package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
  225. package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
  226. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  227. package/dist-server/service/twin-model/twin-location.d.ts +1 -1
  228. package/dist-server/service/twin-model/twin-location.js.map +1 -1
  229. package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
  230. package/dist-server/service/twin-model/twin-model-item-query.js +15 -15
  231. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  232. package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
  233. package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
  234. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
  235. package/dist-server/service/twin-model/twin-model-query.js +51 -30
  236. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  237. package/dist-server/service/twin-model/twin-model-tree-query.js +23 -1
  238. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  239. package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
  240. package/dist-server/service/twin-model/twin-operation.js +1 -1
  241. package/dist-server/service/twin-model/twin-operation.js.map +1 -1
  242. package/dist-server/service/twin-space/move-space.js +2 -2
  243. package/dist-server/service/twin-space/move-space.js.map +1 -1
  244. package/dist-server/service/twin-space/representation-role.d.ts +2 -0
  245. package/dist-server/service/twin-space/representation-role.js +18 -0
  246. package/dist-server/service/twin-space/representation-role.js.map +1 -0
  247. package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
  248. package/dist-server/service/twin-space/space-integrity.js.map +1 -1
  249. package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
  250. package/dist-server/service/twin-space/twin-space-representation.js +16 -0
  251. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  252. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  253. package/dist-server/service/twin-space/twin-space-resolver.js +37 -8
  254. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  255. package/dist-server/service/twin-structure/twin-structure.js +3 -3
  256. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  257. package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
  258. package/dist-server/service/twin-subject/event-subjects.js +137 -0
  259. package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
  260. package/dist-server/service/twin-subject/index.d.ts +9 -0
  261. package/dist-server/service/twin-subject/index.js +16 -0
  262. package/dist-server/service/twin-subject/index.js.map +1 -0
  263. package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
  264. package/dist-server/service/twin-subject/item-resolver.js +93 -0
  265. package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
  266. package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
  267. package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
  268. package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
  269. package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
  270. package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
  271. package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
  272. package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
  273. package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
  274. package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
  275. package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
  276. package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
  277. package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
  278. package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
  279. package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
  280. package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
  281. package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
  282. package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
  283. package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
  284. package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
  285. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  286. package/dist-shared/entity-delta.d.ts +1 -1
  287. package/dist-shared/entity-delta.js +4 -4
  288. package/dist-shared/entity-delta.js.map +1 -1
  289. package/dist-shared/touched-items.js +2 -2
  290. package/dist-shared/touched-items.js.map +1 -1
  291. package/dist-shared/twin-level.js +1 -1
  292. package/dist-shared/twin-level.js.map +1 -1
  293. package/package.json +7 -6
  294. package/server/engine/attention-digest.ts +9 -9
  295. package/server/engine/canonical-ingest.ts +202 -87
  296. package/server/engine/command-routing.ts +1 -1
  297. package/server/engine/declared-stimulus.ts +1 -1
  298. package/server/engine/energy-topology.ts +2 -2
  299. package/server/engine/ingest-dedupe.ts +149 -0
  300. package/server/engine/ingest-health.ts +171 -22
  301. package/server/engine/integration-probes.ts +7 -7
  302. package/server/engine/kpi-baseline.ts +4 -4
  303. package/server/engine/kpi-fold.ts +592 -41
  304. package/server/engine/kpi-query.ts +307 -48
  305. package/server/engine/live-feed-registry.ts +1 -1
  306. package/server/engine/load-meter.ts +1 -1
  307. package/server/engine/local-declarations.ts +15 -14
  308. package/server/engine/log.ts +1 -1
  309. package/server/engine/measured-estimator.ts +1 -1
  310. package/server/engine/measured-yield.ts +1 -1
  311. package/server/engine/model-basis.ts +2 -2
  312. package/server/engine/model-gap.ts +3 -3
  313. package/server/engine/model-vocabulary.ts +2 -2
  314. package/server/engine/oee-accumulator.ts +8 -6
  315. package/server/engine/property-effects.ts +2 -2
  316. package/server/engine/read-time.ts +77 -0
  317. package/server/engine/restart-policy.ts +1 -1
  318. package/server/engine/runtime-key.ts +1 -1
  319. package/server/engine/spec-coverage.ts +3 -3
  320. package/server/engine/stage-path.ts +5 -5
  321. package/server/engine/state-axes.ts +2 -2
  322. package/server/engine/travel-estimator.ts +2 -2
  323. package/server/engine/twin-engine.ts +906 -205
  324. package/server/engine/warm-start.ts +6 -6
  325. package/server/index.ts +1 -1
  326. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
  327. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
  328. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
  329. package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
  330. package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
  331. package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
  332. package/server/migrations/index.ts +7 -1
  333. package/server/routes.ts +73 -5
  334. package/server/service/index.ts +7 -1
  335. package/server/service/reference/control-routing.ts +1 -1
  336. package/server/service/reference/discovery-result.ts +3 -3
  337. package/server/service/reference/hook-contract.ts +103 -0
  338. package/server/service/reference/ingest-space.ts +4 -4
  339. package/server/service/reference/knob-defaults.ts +4 -4
  340. package/server/service/reference/reference-adapter.ts +145 -10
  341. package/server/service/reference/reference-hook.ts +145 -0
  342. package/server/service/reference/reference-live.ts +87 -15
  343. package/server/service/reference/reference-master.ts +51 -20
  344. package/server/service/reference/reference-resolver.ts +11 -11
  345. package/server/service/reference/template-registry.ts +1 -1
  346. package/server/service/reference/twin-reference.ts +3 -3
  347. package/server/service/twin-attention/twin-attention-query.ts +1 -1
  348. package/server/service/twin-audit/command-audit.ts +1 -1
  349. package/server/service/twin-audit/twin-audit-event.ts +2 -2
  350. package/server/service/twin-audit/twin-audit-query.ts +1 -1
  351. package/server/service/twin-backfill/backfill-period-facts.ts +168 -0
  352. package/server/service/twin-backfill/backfill-shape.ts +86 -0
  353. package/server/service/twin-backfill/index.ts +3 -0
  354. package/server/service/twin-backfill/twin-backfill-resolver.ts +35 -0
  355. package/server/service/twin-control/twin-control-mutation.ts +2 -2
  356. package/server/service/twin-event/twin-event-keys.ts +2 -2
  357. package/server/service/twin-event/twin-event-type.ts +18 -1
  358. package/server/service/twin-event/twin-event.ts +32 -6
  359. package/server/service/twin-forecast/forecast-metrics.ts +1 -1
  360. package/server/service/twin-forecast/gap-analytics.ts +1 -1
  361. package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
  362. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
  363. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
  364. package/server/service/twin-instance/twin-instance.ts +4 -4
  365. package/server/service/twin-journal/event-type-split-shape.ts +112 -0
  366. package/server/service/twin-journal/event-type-split.ts +144 -0
  367. package/server/service/twin-journal/journal-order.ts +20 -0
  368. package/server/service/twin-journal/twin-journal-query.ts +224 -20
  369. package/server/service/twin-lifecycle/domain-catalog.ts +2 -1
  370. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +58 -4
  371. package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
  372. package/server/service/twin-model/axis-journal-evidence.ts +1 -1
  373. package/server/service/twin-model/epcis-coverage.ts +34 -3
  374. package/server/service/twin-model/iec61850-coverage.ts +1 -1
  375. package/server/service/twin-model/isa95-coverage.ts +43 -16
  376. package/server/service/twin-model/item-ref.ts +2 -2
  377. package/server/service/twin-model/name-index.ts +2 -2
  378. package/server/service/twin-model/project-structure.ts +7 -6
  379. package/server/service/twin-model/twin-equipment.ts +16 -4
  380. package/server/service/twin-model/twin-lineage-query.ts +16 -5
  381. package/server/service/twin-model/twin-location.ts +1 -1
  382. package/server/service/twin-model/twin-model-item-query.ts +15 -15
  383. package/server/service/twin-model/twin-model-mutation.ts +4 -4
  384. package/server/service/twin-model/twin-model-query.ts +49 -28
  385. package/server/service/twin-model/twin-model-tree-query.ts +26 -2
  386. package/server/service/twin-model/twin-operation.ts +2 -2
  387. package/server/service/twin-space/move-space.ts +3 -3
  388. package/server/service/twin-space/representation-role.ts +15 -0
  389. package/server/service/twin-space/space-integrity.ts +1 -1
  390. package/server/service/twin-space/twin-space-representation.ts +37 -0
  391. package/server/service/twin-space/twin-space-resolver.ts +40 -8
  392. package/server/service/twin-structure/twin-structure.ts +3 -3
  393. package/server/service/twin-subject/event-subjects.ts +155 -0
  394. package/server/service/twin-subject/index.ts +10 -0
  395. package/server/service/twin-subject/item-resolver.ts +92 -0
  396. package/server/service/twin-subject/subject-query-shape.ts +84 -0
  397. package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
  398. package/server/service/twin-subject/twin-subject-event.ts +101 -0
  399. package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
  400. package/server/service/twin-subject/twin-subject-query.ts +210 -0
  401. package/server/service/twin-subject/twin-subject-writer.ts +189 -0
  402. package/server/service/twin-target/twin-target-resolver.ts +1 -1
  403. package/shared/entity-delta.ts +4 -4
  404. package/shared/touched-items.ts +2 -2
  405. package/shared/twin-level.ts +1 -1
  406. package/test/adopt-structure-live.test.ts +3 -3
  407. package/test/aggregate-not-findone.test.ts +112 -0
  408. package/test/attention-digest.test.ts +6 -6
  409. package/test/axis-read.test.ts +6 -5
  410. package/test/backfill-shape.test.ts +89 -0
  411. package/test/boot-resume.test.ts +20 -7
  412. package/test/broadcast-cost-baseline.test.ts +2 -1
  413. package/test/broadcast-period.test.ts +2 -2
  414. package/test/broadcast-queue-overflow.test.ts +244 -0
  415. package/test/canonical-ingest-vocabularies.test.ts +259 -8
  416. package/test/canonical-quantity-door.test.ts +2 -2
  417. package/test/capability-mapping.test.ts +1 -1
  418. package/test/checkpoint-refuses-empty.test.ts +2 -2
  419. package/test/connector-capability-declaration.test.ts +88 -0
  420. package/test/contract-layer-guard.test.ts +48 -0
  421. package/test/control-capability.test.ts +1 -1
  422. package/test/cursor-stall-not-read-failure.test.ts +2 -2
  423. package/test/declaration-reaches-model.test.ts +7 -5
  424. package/test/declared-stimulus.test.ts +4 -4
  425. package/test/discovery-result.test.ts +2 -2
  426. package/test/entity-delta.test.ts +4 -4
  427. package/test/equipment-identity-reaches.test.ts +57 -0
  428. package/test/event-subjects.test.ts +147 -0
  429. package/test/event-time-column.test.ts +3 -3
  430. package/test/event-type-split.test.ts +155 -0
  431. package/test/fact-scope-wiring.test.ts +96 -0
  432. package/test/fold-gap-speaks.test.ts +53 -0
  433. package/test/forecast-metrics.test.ts +1 -1
  434. package/test/forecast-tuning.test.ts +1 -1
  435. package/test/generated-not-counted.test.ts +99 -0
  436. package/test/ingest-bench.test.ts +2 -1
  437. package/test/ingest-health-engine.test.ts +2 -2
  438. package/test/ingest-health-wiring.test.ts +7 -5
  439. package/test/ingest-health.test.ts +6 -6
  440. package/test/ingest-history.test.ts +2 -2
  441. package/test/ingest-idempotent.test.ts +132 -0
  442. package/test/ingest-space.test.ts +3 -3
  443. package/test/ingest-wiring-guard.test.ts +197 -0
  444. package/test/integration-probes.test.ts +3 -3
  445. package/test/integration-runner.test.ts +1 -1
  446. package/test/item-ref.test.ts +1 -1
  447. package/test/journal-order-axis.test.ts +80 -0
  448. package/test/journal-read-discipline.test.ts +16 -6
  449. package/test/journal-retention.test.ts +171 -29
  450. package/test/journal-sort-axis.test.ts +1 -1
  451. package/test/journal-write-door.test.ts +3 -3
  452. package/test/journal-write-trend.test.ts +1 -1
  453. package/test/kernel-kind-guard.test.ts +3 -3
  454. package/test/knob-defaults.test.ts +1 -1
  455. package/test/kpi-baseline-db.test.ts +2 -2
  456. package/test/kpi-fold.test.ts +266 -14
  457. package/test/kpi-query-bench.test.ts +3 -3
  458. package/test/live-cursor-wiring.test.ts +6 -6
  459. package/test/live-kernel-facts.test.ts +11 -11
  460. package/test/live-mirror-parity.test.ts +9 -6
  461. package/test/load-meter.test.ts +1 -1
  462. package/test/local-declarations.test.ts +13 -12
  463. package/test/log-stamp.test.ts +1 -1
  464. package/test/master-to-twin.test.ts +2 -2
  465. package/test/measured-yield.test.ts +2 -2
  466. package/test/merge-streams.test.ts +70 -0
  467. package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
  468. package/test/model-basis.test.ts +2 -2
  469. package/test/model-gap.test.ts +4 -4
  470. package/test/model-vocabulary.test.ts +2 -2
  471. package/test/move-space.test.ts +1 -1
  472. package/test/oee-accumulator.test.ts +20 -5
  473. package/test/operational-vocabulary.test.ts +2 -2
  474. package/test/project-structure-db.test.ts +2 -2
  475. package/test/projection-reaches-screen.test.ts +7 -2
  476. package/test/projection-reads-declared.test.ts +109 -0
  477. package/test/property-effects.test.ts +10 -6
  478. package/test/read-failure-ledger.test.ts +106 -0
  479. package/test/read-failure-visible.test.ts +7 -7
  480. package/test/read-time.test.ts +118 -0
  481. package/test/reference-grounding.test.ts +1 -1
  482. package/test/reference-hook.test.ts +86 -0
  483. package/test/registry-key-guard.test.ts +1 -1
  484. package/test/rename-twin.test.ts +81 -0
  485. package/test/restart-policy.test.ts +3 -3
  486. package/test/resync-origin-site.test.ts +3 -3
  487. package/test/revision-axis.test.ts +14 -8
  488. package/test/runtime-key.test.ts +3 -3
  489. package/test/scale-twin-bench.test.ts +2 -1
  490. package/test/snapshot-freshness.test.ts +4 -4
  491. package/test/source-outcome-audit.test.ts +1 -1
  492. package/test/space-integrity.test.ts +1 -1
  493. package/test/space-representation.test.ts +66 -0
  494. package/test/space-scope-includes-stopped.test.ts +55 -0
  495. package/test/spec-coverage.test.ts +1 -1
  496. package/test/standard-coverage.test.ts +19 -21
  497. package/test/status-tally.test.ts +1 -1
  498. package/test/structure-revision-db.test.ts +7 -7
  499. package/test/subject-fast-path.test.ts +148 -0
  500. package/test/subject-read-model.test.ts +175 -0
  501. package/test/touched-items.test.ts +2 -2
  502. package/test/twin-audit.test.ts +1 -1
  503. package/test/twin-event-keys.test.ts +1 -1
  504. package/test/twin-model-item-db.test.ts +5 -5
  505. package/test/twin-model-tree-db.test.ts +35 -2
  506. package/test/twin-origin-resync.test.ts +1 -1
  507. package/test/vocabulary-guard.test.ts +1 -1
  508. package/test/warm-start-seam.test.ts +1 -1
  509. package/test/warm-start.test.ts +6 -6
  510. package/test/withheld-door.test.ts +2 -2
  511. package/test/yield-loop.test.ts +4 -4
  512. package/tsconfig.shared.tsbuildinfo +1 -1
  513. package/tsconfig.tsbuildinfo +1 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * 업무 KPI 폴드 — **이벤트 저널을 접어** 시간창 지표를 만든다(순수).
3
+ * 업무 KPI 폴드 — **이벤트 저널을 계산해** 시간창 지표를 만든다(순수).
4
4
  *
5
5
  * ── 왜 필요한가 ─────────────────────────────────────────────────────────────
6
6
  * 지금 트윈이 답할 수 있는 것은 "이 순간의 값"(점유 92%)과 "인프라 부하"(초당 이벤트 수)뿐이다.
@@ -9,8 +9,8 @@
9
9
  *
10
10
  * ── 원료는 이미 있다 ────────────────────────────────────────────────────────
11
11
  * 저널(`TwinEvent`)에 `task.status`·`order.status`·`equipment.status` 전이가 `eventTime`(ISO)과 함께
12
- * append-only 로 쌓인다. 그래서 새 계측을 심을 필요가 없다 — **접기만 하면 된다.**
13
- * 같은 모양의 선례가 이 폴더에 있다(`oee-accumulator.ts` — equipment 전이를 접어 OEE 카운터 생산).
12
+ * append-only 로 쌓인다. 그래서 새 계측을 심을 필요가 없다 — **계산만 하면 된다.**
13
+ * 같은 모양의 선례가 이 폴더에 있다(`oee-accumulator.ts` — equipment 전이를 계산해 OEE 카운터 생산).
14
14
  *
15
15
  * ── 이 파일이 하지 않는 것 ──────────────────────────────────────────────────
16
16
  * · 목표와 비교하지 않는다. "95% 목표 대비" 는 목표를 저장하는 별개 층(경영 KPI)의 몫이고,
@@ -29,9 +29,10 @@ exports.foldKpiBuckets = foldKpiBuckets;
29
29
  * 짝맞춤은 **커널이 소유한다**(`foldTaskRecords`). 완료는 작업당 하나·나중 것이 사실, 착수는 처음 것,
30
30
  * 종류·오더는 어느 전이에서든 — 그 규칙은 커널 상태 기계에서 나오는 지식이다. 여기서 다시 적었더니
31
31
  * 공정 타임라인과 **다른 답**을 냈다(재전송된 완료를 두 건으로 세어 처리량·점유가 부풀려졌다).
32
- * 이 파일은 이제 "접힌 기록 → 창 지표" 만 한다.
32
+ * 이 파일은 이제 "계산한 기록 → 창 지표" 만 한다.
33
33
  */
34
34
  const twin_kernel_1 = require("@operato/twin-kernel");
35
+ const ops_contract_1 = require("@operato/ops-contract");
35
36
  /**
36
37
  * 창의 전력량을 **배전 계통의 자리별로 나눈다** — 근거를 함께.
37
38
  *
@@ -128,6 +129,221 @@ function feederShares(kWh, utilization, resourceFeeder) {
128
129
  * 분기 계량 합이 뿌리보다 크면(회계가 다른 두 계량기) **그 사실을 낸다** — 값을 깎아 맞추면 계량을
129
130
  * 우리가 고쳐 쓰는 것이다.
130
131
  */
132
+ /**
133
+ * 창의 **발전량** — 커널이 마감한 기간 사실을 합한다.
134
+ *
135
+ * ── 왜 합이 전부인가 ───────────────────────────────────────────────────────
136
+ * 기간 발전량은 커널이 이미 확정한 사실이다(적산의 종류마다 다른 규칙으로 얻는다). 여기서 다시
137
+ * 계산하면 규칙이 두 벌이 되고, 그때부터 두 화면이 다른 발전량을 말한다. 이 함수가 하는 일은
138
+ * **창에 든 것을 고르고, 설비별로 묶고, 근거가 얼마나 덮였는지 세는 것**이다.
139
+ *
140
+ * ── 걸친 기간은 세지 않는다 ─────────────────────────────────────────────────
141
+ * 구간 마감과 같은 규칙이다. 하루의 절반이 창에 들었다고 절반을 세면 그것은 관측이 아니라 우리가 나눈
142
+ * 값이고, 합이 창을 넘는다.
143
+ *
144
+ * ── 덜 덮인 기간을 센다 ─────────────────────────────────────────────────────
145
+ * 기간의 끝이 자정인데 마지막 표본이 저녁이었으면 그 사이는 재지 않은 것이다. 그 수를 함께 내야 화면이
146
+ * 「이 합은 이만큼 덜 잰 것」이라고 말할 수 있다. 얼마나 어긋나면 문제인지는 현장이 정한다.
147
+ */
148
+ function generatedOfPeriods(periods, range,
149
+ /** 이용률의 분모 — 없으면 이용률을 내지 않는다(0 이나 짐작을 만들지 않는다). */
150
+ rated,
151
+ /** 기간마다 적용할 단가 — 기간을 덮는 것을 쓴다. 없으면 금액을 만들지 않는다. */
152
+ prices) {
153
+ const inside = periods.filter(g => g.startMs >= range.startMs && g.endMs <= range.endMs);
154
+ /*
155
+ * ── **걸친 기간을 조용히 버리지 않는다** (2026-08-30) ──────────────────────
156
+ *
157
+ * 더하는 것은 창에 **온전히 들어온 기간**뿐이다. 그것이 맞는 셈이다 — 걸친 기간을 더하면 다른 날의
158
+ * 값이 이 창의 값인 척한다.
159
+ *
160
+ * 그런데 버린 것을 말하지 않았다. 하루 단위 기간은 「지금부터 24시간 전」 창에 온전히 들어오는 일이
161
+ * 거의 없다(창이 한낮에서 시작하므로). 그래서 어제 발전량이 저널에 멀쩡히 있는데 화면이 비었고,
162
+ * 사용자는 그것을 **「발전량이 없다」**로 읽었다. 실측으로 그 일이 났다.
163
+ *
164
+ * 이제 걸친 것을 세어 함께 낸다. 화면이 「이 창에 다 들어오지 않은 기간이 있다」를 말할 수 있다.
165
+ */
166
+ const straddling = periods.filter(g => !(g.startMs >= range.startMs && g.endMs <= range.endMs) && g.endMs > range.startMs && g.startMs < range.endMs);
167
+ const notCounted = straddling.length
168
+ ? {
169
+ periods: straddling.length,
170
+ kWh: straddling.reduce((a, g) => a + g.kWh, 0),
171
+ earliestStart: new Date(Math.min(...straddling.map(g => g.startMs))).toISOString(),
172
+ latestEnd: new Date(Math.max(...straddling.map(g => g.endMs))).toISOString()
173
+ }
174
+ : undefined;
175
+ if (!inside.length) {
176
+ /* 온전히 들어온 것이 없다 — 그래도 걸친 것이 있으면 그 사실을 낸다(빈 값과 「없음」은 다르다). */
177
+ return notCounted ? { generated: { kWh: 0, counted: 0, partial: 0, byEquipment: [], notCounted } } : {};
178
+ }
179
+ const byEquipment = new Map();
180
+ let partial = 0;
181
+ for (const g of inside) {
182
+ byEquipment.set(g.equipmentId, (byEquipment.get(g.equipmentId) ?? 0) + g.kWh);
183
+ const coveredFrom = Number.isFinite(g.observedFromMs) ? g.observedFromMs <= g.startMs : false;
184
+ const coveredTo = Number.isFinite(g.observedToMs) ? g.observedToMs >= g.endMs : false;
185
+ if (!coveredFrom || !coveredTo)
186
+ partial++;
187
+ }
188
+ const kWh = inside.reduce((a, g) => a + g.kWh, 0);
189
+ /*
190
+ * 이용률의 분모는 **센 기간들의 길이**다. 창 길이로 나누면, 하루치 하나만 센 24시간 창에서 그 하루가
191
+ * 창 전체에 걸쳐 있었던 것처럼 작아진다.
192
+ */
193
+ const hours = inside.reduce((a, g) => a + (g.endMs - g.startMs) / 3_600_000, 0);
194
+ const capacityFactor = rated && rated.kW > 0 && hours > 0
195
+ ? { value: kWh / (rated.kW * hours), ratedKW: rated.kW, basis: rated.basis, from: rated.from, hours }
196
+ : undefined;
197
+ /*
198
+ * 낸 것의 값 — 기간마다 **그 기간을 덮는 단가**로 매긴다. 걸친 단가는 쓰지 않는다: 하루 단가로
199
+ * 이틀치를 매기면 그 절반은 다른 날의 값이 된다.
200
+ *
201
+ * 모든 기간에 단가가 있을 때만 금액을 만든다. 일부만 매기고 더하면 그 합이 창 전체의 값인 척한다.
202
+ */
203
+ let amount = 0;
204
+ let priced = 0;
205
+ let currency;
206
+ for (const g of inside) {
207
+ const p = (prices ?? []).find(x => x.startMs <= g.startMs && x.endMs >= g.endMs);
208
+ if (!p)
209
+ continue;
210
+ amount += g.kWh * p.unitPrice;
211
+ priced++;
212
+ currency = currency ?? p.currency;
213
+ }
214
+ const unpriced = inside.length - priced;
215
+ const revenue = priced > 0 && unpriced === 0 && currency ? { amount, currency, priced, unpriced } : undefined;
216
+ return {
217
+ generated: {
218
+ kWh,
219
+ counted: inside.length,
220
+ partial,
221
+ byEquipment: [...byEquipment.entries()]
222
+ .map(([id, kWh]) => ({ id, kWh }))
223
+ .sort((a, b) => b.kWh - a.kWh),
224
+ ...(capacityFactor ? { capacityFactor } : {}),
225
+ ...(revenue ? { revenue } : {}),
226
+ /* 이 창에 다 들어오지 않아 더하지 못한 기간 — 있으면 화면이 그것을 말한다. */
227
+ ...(notCounted ? { notCounted } : {})
228
+ }
229
+ };
230
+ }
231
+ /**
232
+ * 밖에서 마감되어 온 사용 구간을 합한다 — **구간 마감과 더하지 않는다.**
233
+ *
234
+ * 두 회계가 같은 전기를 말한다. 더하면 두 번 세고, 둘 다 있는 현장에서는 계량 쪽이 강한 근거이므로
235
+ * 그쪽을 쓴다. 여기서는 이쪽 몫만 계산하고, 무엇을 쓸지는 부르는 쪽이 정한다.
236
+ *
237
+ * 단가는 **모든 구간이 실었을 때만** 요금을 만든다. 일부만 실었는데 있는 것만 더하면 그 합이 창의
238
+ * 요금인 척한다 — 못 매긴 구간 수를 함께 낸다.
239
+ */
240
+ function usageOfPeriods(periods, range) {
241
+ const inside = periods.filter(u => u.startMs >= range.startMs && u.endMs <= range.endMs);
242
+ if (!inside.length)
243
+ return undefined;
244
+ const byBasis = new Map();
245
+ let kWh = 0;
246
+ let charge = 0;
247
+ let unpriced = 0;
248
+ let currency;
249
+ for (const u of inside) {
250
+ kWh += u.kWh;
251
+ byBasis.set(u.basis, (byBasis.get(u.basis) ?? 0) + 1);
252
+ if (Number.isFinite(u.unitPrice)) {
253
+ charge += u.kWh * u.unitPrice;
254
+ if (!currency)
255
+ currency = u.currency;
256
+ }
257
+ else
258
+ unpriced++;
259
+ }
260
+ return {
261
+ kWh,
262
+ counted: inside.length,
263
+ ...(unpriced === 0 && currency ? { charge, currency } : {}),
264
+ unpriced,
265
+ byBasis: [...byBasis.entries()].map(([basis, counted]) => ({ basis, counted })).sort((a, b) => b.counted - a.counted)
266
+ };
267
+ }
268
+ /**
269
+ * **요금적용전력을 고른다 — 청구서가 먼저다.**
270
+ *
271
+ * 순서에 뜻이 있다. 청구서는 그 기간에 실제로 매겨진 값이고, 선언은 사람이 옮겨 적은 값이다.
272
+ * 그 값이 매달 바뀌므로 옮겨 적은 것은 곧 낡는다.
273
+ */
274
+ function billingDemandOf(billsInWindow, tariffBasis, lastBill, declaredKW,
275
+ /** 이 창에서 관측한 최고 수요와 그 값이 언제까지의 것인지 — 사용 구간의 `maxKW` 에서 온다. */
276
+ observedPeak) {
277
+ const declared = Number.isFinite(Number(declaredKW)) ? Number(declaredKW) : undefined;
278
+ const withValue = billsInWindow.filter(b => Number.isFinite(b.billingDemandKW));
279
+ const fromBill = withValue.length ? withValue.reduce((a, x) => (x.endMs > a.endMs ? x : a)).billingDemandKW : undefined;
280
+ const carried = Number.isFinite(Number(lastBill?.billingDemandKW)) ? Number(lastBill?.billingDemandKW) : undefined;
281
+ /* 이 주기에 적용되는 기준이 가장 먼저다 — 청구서는 끝난 기간의 것이다. */
282
+ const fromBasis = Number.isFinite(Number(tariffBasis?.billingDemandKW)) ? Number(tariffBasis?.billingDemandKW) : undefined;
283
+ const picked = fromBasis !== undefined
284
+ ? { kW: fromBasis, from: 'tariff-basis' }
285
+ : fromBill !== undefined
286
+ ? { kW: fromBill, from: 'bill' }
287
+ : carried !== undefined
288
+ ? { kW: carried, from: 'carried-forward' }
289
+ : declared !== undefined
290
+ ? { kW: declared, from: 'declaration' }
291
+ : undefined;
292
+ if (!picked)
293
+ return undefined;
294
+ /* 선언이 있는데 청구서와 다르면 그 사실을 낸다 — 낡은 선언은 스스로 말해야 한다. */
295
+ const stale = picked.from !== 'declaration' && declared !== undefined && declared !== picked.kW;
296
+ const exceeded = observedPeak && observedPeak.kW > picked.kW
297
+ ? {
298
+ peakKW: observedPeak.kW,
299
+ overKW: observedPeak.kW - picked.kW,
300
+ observedTo: new Date(observedPeak.toMs).toISOString()
301
+ }
302
+ : undefined;
303
+ return {
304
+ ...picked,
305
+ ...(declared !== undefined ? { declaredKW: declared } : {}),
306
+ ...(stale ? { stale: true } : {}),
307
+ ...(exceeded ? { exceeded } : {})
308
+ };
309
+ }
310
+ /**
311
+ * 창에 온전히 든 청구서 하나 — 가장 늦게 끝난 것.
312
+ *
313
+ * **창이 청구 주기와 같을 때만 견준다.** 한 달 청구서를 하루 창의 값과 빼면 그 차이는 아무것도 뜻하지
314
+ * 않는다. 그래서 같은지를 값과 함께 낸다.
315
+ */
316
+ function billOfWindow(bills, range, ours) {
317
+ const inside = bills.filter(b => b.startMs >= range.startMs && b.endMs <= range.endMs);
318
+ if (!inside.length)
319
+ return undefined;
320
+ const b = inside.reduce((a, x) => (x.endMs > a.endMs ? x : a));
321
+ const matchesWindow = b.startMs === range.startMs && b.endMs === range.endMs;
322
+ /* 양쪽에 있는 항목만 견준다 — 한쪽이 없는 것을 0 으로 두면 그 차이가 통째로 어긋남처럼 보인다. */
323
+ const gap = (mine, billed) => Number.isFinite(Number(mine)) && Number.isFinite(billed) ? Number(mine) - billed : undefined;
324
+ const diff = matchesWindow
325
+ ? {
326
+ ...(gap(ours?.total, b.total) !== undefined ? { total: gap(ours?.total, b.total) } : {}),
327
+ ...(gap(ours?.demandCharge, b.demandCharge) !== undefined
328
+ ? { demandCharge: gap(ours?.demandCharge, b.demandCharge) }
329
+ : {}),
330
+ ...(gap(ours?.energyCharge, b.energyCharge) !== undefined
331
+ ? { energyCharge: gap(ours?.energyCharge, b.energyCharge) }
332
+ : {})
333
+ }
334
+ : {};
335
+ return {
336
+ from: b.from,
337
+ to: b.to,
338
+ ...(Number.isFinite(b.total) ? { total: b.total } : {}),
339
+ ...(Number.isFinite(b.energyCharge) ? { energyCharge: b.energyCharge } : {}),
340
+ ...(Number.isFinite(b.demandCharge) ? { demandCharge: b.demandCharge } : {}),
341
+ ...(b.currency ? { currency: b.currency } : {}),
342
+ ...(Number.isFinite(b.billingDemandKW) ? { billingDemandKW: b.billingDemandKW } : {}),
343
+ matchesWindow,
344
+ ...(Object.keys(diff).length ? { diff } : {})
345
+ };
346
+ }
131
347
  function measuredFeeders(windows, range, totalKWh, meterFeeder) {
132
348
  if (!meterFeeder || !Object.keys(meterFeeder).length)
133
349
  return undefined;
@@ -197,7 +413,29 @@ function measuredFeeders(windows, range, totalKWh, meterFeeder) {
197
413
  ...(remainder < 0 ? { submeterExcessKWh: -remainder } : {})
198
414
  };
199
415
  }
200
- /** 이 기록이 요청한 축에서 어느 값에 속하는가. 값이 없으면 'unknown'(조용히 버리지 않는다). */
416
+ /** 폴드 옵션 — 관점 축과, 그 축을 만들기 위해 이벤트 밖에서 와야 하는 지도. */
417
+ /*
418
+ * ── 같은 사실이 두 벌 있어도 한 번만 센다 (2026-08-30) ──────────────────────
419
+ *
420
+ * 마감된 구간 사실은 정체성이 내용에 있다(커널 0.7.81). 그런데 저널에 이미 여러 벌 적힌 것이 있다 —
421
+ * 쓰는 자리에서 막기 전에 들어간 것들이다(실측: 어느 날의 발전 단가가 세 벌).
422
+ *
423
+ * 지난 기록을 지우지 않는다 — 저널은 일어난 일의 기록이다. 대신 **읽을 때 한 번만 센다.**
424
+ * 그러지 않으면 그 구간의 수익·사용량이 그 횟수만큼 세어진다.
425
+ */
426
+ function onceById(rows) {
427
+ const seen = new Set();
428
+ return rows.filter(r => {
429
+ const id = r?.payload?.eventId;
430
+ if (typeof id !== 'string' || !id)
431
+ return true;
432
+ if (seen.has(id))
433
+ return false;
434
+ seen.add(id);
435
+ return true;
436
+ });
437
+ }
438
+ /** 이 기록이 요청한 축에서 어느 값에 속하는가. 값이 없으면 'unknown'(알리지 않고 버리지 않는다). */
201
439
  function facetKey(rec, options) {
202
440
  const f = rec.facets;
203
441
  switch (options.groupBy) {
@@ -227,8 +465,8 @@ function facetKey(rec, options) {
227
465
  if (!options.shifts?.length)
228
466
  return 'unknown';
229
467
  /* 시각까지 넘긴다 — **휴일에 일어난 일은 어느 교대에도 속하지 않는다.** 되풀이만 보면 그날 서지도
230
- 않은 교대에 집계돼, 교대별 성과가 조용히 거짓이 된다. */
231
- return (0, twin_kernel_1.activeShiftAt)(options.shifts, rec.at, options.utcOffsetMinutes) ?? 'unknown';
468
+ 않은 교대에 집계돼, 교대별 성과가 알리지 않고 거짓이 된다. */
469
+ return (0, ops_contract_1.activeShiftAt)(options.shifts, rec.at, options.utcOffsetMinutes) ?? 'unknown';
232
470
  default:
233
471
  return 'unknown';
234
472
  }
@@ -256,7 +494,7 @@ function qualityOf(rows) {
256
494
  * 기록을 축으로 묶어 축별 통계를 낸다 — **전체와 같은 규칙**으로(같은 stats 함수, 같은 창).
257
495
  *
258
496
  * 축의 값이 아주 많을 수 있다(오더 축은 수천 개). 그래서 건수 큰 것부터 상한까지만 돌려주고
259
- * **잘라낸 수를 함께 알린다** — 조용한 절단은 "이게 전부" 라는 거짓을 만든다.
497
+ * **잘라낸 수를 함께 알린다** — 들어온 것이 없는 절단은 "이게 전부" 라는 거짓을 만든다.
260
498
  */
261
499
  function groupRecords(records, options) {
262
500
  const by = options.groupBy;
@@ -320,11 +558,11 @@ function stats(values) {
320
558
  };
321
559
  }
322
560
  /**
323
- * 이벤트를 접어 시간창 KPI 를 만든다.
561
+ * 이벤트를 계산해 시간창 KPI 를 만든다.
324
562
  *
325
563
  * **호출부 규약**: `events` 는 창보다 **넉넉히 앞까지** 담아 넘긴다(예: 창 시작 − 리드타임 상한).
326
564
  * 완료된 작업의 시작 이벤트가 창 밖에 있을 수 있기 때문이다. 못 찾은 것은 `unpaired` 로 알린다 —
327
- * 평균에서 조용히 빠지면 지표가 거짓이 된다.
565
+ * 평균에서 오류 없이 빠지면 지표가 거짓이 된다.
328
566
  *
329
567
  * 시간축은 `eventTime`(ISO) 차분이다. sim 클록이 실제 시각과 오프셋이 있어도 **차분은 정확**하다
330
568
  * (고정 오프셋은 상쇄된다). 파싱 불가한 줄은 버린다.
@@ -352,7 +590,7 @@ function foldKpi(events, window, options = {}) {
352
590
  * **끝났나는 커널이 답한다** (2026-08-23) — `isOrderTerminal`.
353
591
  *
354
592
  * 예전에는 여기서 **동의어 목록**으로 추론했다(`completed`·`fulfilled`·`done`·`finished`).
355
- * 그 방식이 낸 대가가 둘이었고 둘 다 조용했다.
593
+ * 그 방식이 낸 대가가 둘이었고 둘 다 들어온 것이 없었다.
356
594
  *
357
595
  * ① 원본의 낱말이 목록에 없으면 완료가 0 이 된다. 실제로 그랬다 — 첫 실 시스템의 `FINISHED`
358
596
  * 가 빠져 있었고, 그때 목록에 낱말을 더한 것은 진짜 빈 자리(그 커넥터가 자기 상태 표를
@@ -365,11 +603,11 @@ function foldKpi(events, window, options = {}) {
365
603
  * 판정의 1차 근거는 **양**이고 낱말은 양으로 말할 수 없는 종결(취소·중도 종료)에만 쓴다.
366
604
  */
367
605
  const status = String(d.status ?? '').toLowerCase();
368
- const done = (0, twin_kernel_1.isOrderTerminal)({ status: d.status, requested: d.requested, fulfilled: d.fulfilled });
606
+ const done = (0, ops_contract_1.isOrderTerminal)({ status: d.status, requested: d.requested, fulfilled: d.fulfilled });
369
607
  if (at >= window.fromMs && at <= window.toMs) {
370
608
  /*
371
609
  * **오더당 한 번만 센다.** 폴링하는 원본은 같은 오더의 종결을 여러 주기에 걸쳐 다시 보낼 수
372
- * 있고(그것은 결함이 아니라 폴링의 성질이다), 사건마다 세면 처리량이 조용히 부풀려진다.
610
+ * 있고(그것은 결함이 아니라 폴링의 성질이다), 사건마다 세면 처리량이 알리지 않고 부풀려진다.
373
611
  * 작업 쪽은 커널의 짝맞춤이 이미 그 규율을 갖고 있다(「완료는 작업당 하나」) — 오더만 없었다.
374
612
  */
375
613
  if (done) {
@@ -379,7 +617,7 @@ function foldKpi(events, window, options = {}) {
379
617
  completedOrders++;
380
618
  }
381
619
  else if (!id)
382
- completedOrders++; // 정체를 모르면 셈에서 빼지 않는다(조용히 줄이지 않는다)
620
+ completedOrders++; // 정체를 모르면 셈에서 빼지 않는다(알리지 않고 줄이지 않는다)
383
621
  }
384
622
  /*
385
623
  * **완료로 세지 않은 낱말의 인구조사.** 「못 읽었다」가 아니다 — `picking` 처럼 완료가 아닌
@@ -398,7 +636,7 @@ function foldKpi(events, window, options = {}) {
398
636
  * 사건이 하나도 없으면 이 트윈에는 에너지가 없는 것이므로 블록 자체를 내지 않는다.
399
637
  */
400
638
  const demandWindows = events
401
- .filter(e => e.eventType === twin_kernel_1.ENERGY_EVENT.demandWindow)
639
+ .filter(e => e.eventType === ops_contract_1.ENERGY_EVENT.demandWindow)
402
640
  .map(e => {
403
641
  const d = e.payload?.data ?? e.payload ?? {};
404
642
  /* 최대(`maxKW`)도 함께 넘긴다 — 요금이 걸리는 수는 평균이 아니라 최대다.
@@ -421,6 +659,72 @@ function foldKpi(events, window, options = {}) {
421
659
  };
422
660
  })
423
661
  .filter(w => Number.isFinite(w.startMs) && Number.isFinite(w.endMs));
662
+ /*
663
+ * 발전 기간 사실 — 커널이 기간을 마감할 때 낸 것이다. 구간 마감과 같은 규칙으로 **창에 온전히 든
664
+ * 것만** 센다(걸친 기간을 반만 세면 합이 창을 넘는다).
665
+ *
666
+ * 관측 구간(`observedFrom`·`observedTo`)을 함께 읽는다 — 값의 근거가 얼마나 덮였는지가 그 둘에 있다.
667
+ */
668
+ const generationPeriods = onceById(events.filter(e => e.eventType === ops_contract_1.ENERGY_EVENT.generationPeriod))
669
+ .map(e => {
670
+ const d = e.payload?.data ?? e.payload ?? {};
671
+ return {
672
+ equipmentId: String(d.equipmentId ?? ''),
673
+ kWh: Number(d.kWh),
674
+ startMs: Date.parse(String(d.periodStart ?? '')),
675
+ endMs: Date.parse(String(d.periodEnd ?? '')),
676
+ observedFromMs: Date.parse(String(d.observedFrom ?? '')),
677
+ observedToMs: Date.parse(String(d.observedTo ?? ''))
678
+ };
679
+ })
680
+ .filter(g => g.equipmentId && Number.isFinite(g.kWh) && Number.isFinite(g.startMs) && Number.isFinite(g.endMs));
681
+ /*
682
+ * 밖에서 마감되어 온 사용 구간 — 구간 마감과 같은 규칙으로 **창에 온전히 든 것만** 센다.
683
+ */
684
+ const usagePeriods = onceById(events.filter(e => e.eventType === ops_contract_1.ENERGY_EVENT.usagePeriod))
685
+ .map(e => {
686
+ const d = e.payload?.data ?? e.payload ?? {};
687
+ return {
688
+ meterId: String(d.meterId ?? ''),
689
+ kWh: Number(d.kWh),
690
+ startMs: Date.parse(String(d.from ?? '')),
691
+ endMs: Date.parse(String(d.to ?? '')),
692
+ unitPrice: Number(d.unitPrice),
693
+ maxKW: Number(d.maxKW),
694
+ currency: d.currency ? String(d.currency) : undefined,
695
+ basis: d.basis ? String(d.basis) : 'unstated'
696
+ };
697
+ })
698
+ .filter(u => u.meterId && Number.isFinite(u.kWh) && Number.isFinite(u.startMs) && Number.isFinite(u.endMs));
699
+ /* 청구서 — 창에 온전히 든 것 중 가장 늦게 끝난 것 하나. */
700
+ const bills = onceById(events.filter(e => e.eventType === ops_contract_1.ENERGY_EVENT.bill))
701
+ .map(e => {
702
+ const d = e.payload?.data ?? e.payload ?? {};
703
+ return {
704
+ from: String(d.from ?? ''),
705
+ to: String(d.to ?? ''),
706
+ startMs: Date.parse(String(d.from ?? '')),
707
+ endMs: Date.parse(String(d.to ?? '')),
708
+ total: Number(d.total),
709
+ energyCharge: Number(d.energyCharge),
710
+ demandCharge: Number(d.demandCharge),
711
+ currency: d.currency ? String(d.currency) : undefined,
712
+ billingDemandKW: Number(d.billingDemandKW)
713
+ };
714
+ })
715
+ .filter(b => Number.isFinite(b.startMs) && Number.isFinite(b.endMs));
716
+ /* 발전 단가 — 기간마다 그때의 값이다(날마다 바뀐다). */
717
+ const generationPrices = onceById(events.filter(e => e.eventType === ops_contract_1.ENERGY_EVENT.generationPrice))
718
+ .map(e => {
719
+ const d = e.payload?.data ?? e.payload ?? {};
720
+ return {
721
+ startMs: Date.parse(String(d.from ?? '')),
722
+ endMs: Date.parse(String(d.to ?? '')),
723
+ unitPrice: Number(d.unitPrice),
724
+ currency: String(d.currency ?? '')
725
+ };
726
+ })
727
+ .filter(p => Number.isFinite(p.startMs) && Number.isFinite(p.endMs) && Number.isFinite(p.unitPrice) && p.currency);
424
728
  const leads = [];
425
729
  const works = [];
426
730
  const waits = [];
@@ -466,18 +770,48 @@ function foldKpi(events, window, options = {}) {
466
770
  return {
467
771
  window: { fromMs: window.fromMs, toMs: window.toMs },
468
772
  throughput: { tasks: tasksInWindow, orders: completedOrders },
469
- ...(demandWindows.length
773
+ /* 발전만 있는 현장(계량 없는 발전소)도 답이 나와야 한다 — 둘 중 하나만 있어도 블록을 낸다. */
774
+ ...(demandWindows.length || generationPeriods.length || usagePeriods.length || bills.length
470
775
  ? (() => {
471
776
  const e = (0, twin_kernel_1.energyOfWindows)(demandWindows, { startMs: window.fromMs, endMs: window.toMs });
777
+ const usage = usageOfPeriods(usagePeriods, { startMs: window.fromMs, endMs: window.toMs });
778
+ /*
779
+ * 어느 회계를 쓸지 정한다 — **더하지 않는다.** 계량 표본에서 우리가 마감한 값이 있으면 그것을
780
+ * 쓰고(강한 근거), 없으면 밖에서 마감되어 온 값을 쓴다. 무엇을 썼는지 함께 낸다.
781
+ */
782
+ const usedKWh = e.kWh !== undefined ? e.kWh : usage?.kWh;
783
+ const kWhBasis = e.kWh !== undefined ? 'demand-window' : usage ? 'usage-period' : undefined;
472
784
  /* 분모는 **완료 오더**다(대당 전력). 창에 완료가 없으면 커널 규칙이 「정의되지 않음」으로 답한다. */
473
785
  const i = (0, twin_kernel_1.energyIntensity)({
474
- kWh: e.kWh,
786
+ kWh: usedKWh,
475
787
  energyWindow: e.window,
476
788
  denominator: { kind: 'output', value: completedOrders, window: e.window }
477
789
  });
790
+ /*
791
+ * 요금은 **구간마다 제 단가로** 매긴 값이 있으면 그것을 쓴다. 단일 단가로 접으면 시간대마다
792
+ * 다른 단가가 사라지고, 「피크를 깎아 얼마를 아끼나」가 그 차이 위에 선다.
793
+ */
794
+ /* 요금적용전력은 청구서가 먼저다 — 선언은 청구가 오지 않는 현장의 몫이다. */
795
+ /* 이 창의 최고 수요 — 사용 구간이 실어 온 값 중 가장 큰 것(원본 계량기가 잰 값이다). */
796
+ const peaks = usagePeriods
797
+ .filter(u => u.startMs >= window.fromMs && u.endMs <= window.toMs && Number.isFinite(u.maxKW))
798
+ .map(u => ({ kW: u.maxKW, toMs: u.endMs }));
799
+ const observedPeak = peaks.length ? peaks.reduce((a, x) => (x.kW > a.kW ? x : a)) : undefined;
800
+ const billingDemand = billingDemandOf(bills.filter(b => Number.isFinite(b.billingDemandKW)).map(b => ({ endMs: b.endMs, billingDemandKW: b.billingDemandKW })), options.tariffBasis, options.lastBill, options.tariff?.billingDemandKW, observedPeak);
801
+ const cost = options.tariff
802
+ ? (0, twin_kernel_1.electricityCost)({
803
+ kWh: usedKWh,
804
+ peakKW: e.peakKW,
805
+ tariff: { ...options.tariff, ...(billingDemand ? { billingDemandKW: billingDemand.kW } : {}) },
806
+ ...(usage?.charge !== undefined ? { energyChargeFromPeriods: usage.charge } : {})
807
+ })
808
+ : undefined;
478
809
  return {
479
810
  energy: {
480
- ...(e.kWh !== undefined ? { kWh: e.kWh } : {}),
811
+ ...(usedKWh !== undefined ? { kWh: usedKWh } : {}),
812
+ ...(kWhBasis ? { kWhBasis } : {}),
813
+ ...(billingDemand ? { billingDemand } : {}),
814
+ ...(usage ? { usage } : {}),
481
815
  /* 이 창의 최대수요 — 계약 대비 판정이 보는 수다(전력량과 같은 구간 집합에서 왔다). */
482
816
  ...(e.peakKW !== undefined ? { peakKW: e.peakKW, peakStartMs: e.peakStartMs } : {}),
483
817
  /* 깎인 양은 **같은 구간의 두 수**의 차이다 — 여기서 한 번만 계산해 두 곳이 갈라지지 않게. */
@@ -493,7 +827,26 @@ function foldKpi(events, window, options = {}) {
493
827
  ...(measuredFeeders(demandWindows, { startMs: window.fromMs, endMs: window.toMs }, e.kWh, options.meterFeeder) ??
494
828
  feederShares(e.kWh, utilization, options.resourceFeeder)),
495
829
  /* 금액은 같은 창의 전력량·최대수요에서 나온다 — 창이 하나뿐이라 어긋날 수 없다. */
496
- ...(options.tariff ? { cost: (0, twin_kernel_1.electricityCost)({ kWh: e.kWh, peakKW: e.peakKW, tariff: options.tariff }) } : {})
830
+ ...(cost ? { cost } : {}),
831
+ ...generatedOfPeriods(generationPeriods, { startMs: window.fromMs, endMs: window.toMs }, options.generationRated,
832
+ /* 창 안의 단가 사실이 먼저이고, 없으면 이어받은 마지막 단가를 쓴다. */
833
+ generationPrices.length
834
+ ? generationPrices
835
+ : options.generationPrice
836
+ ? [
837
+ {
838
+ startMs: Date.parse(options.generationPrice.from),
839
+ endMs: Date.parse(options.generationPrice.to),
840
+ unitPrice: options.generationPrice.unitPrice,
841
+ currency: options.generationPrice.currency
842
+ }
843
+ ]
844
+ : []),
845
+ /* 청구서는 사실이다 — 우리 계산을 덮지 않고 나란히 둔다(§`billOfWindow`). */
846
+ ...(() => {
847
+ const b = billOfWindow(bills, { startMs: window.fromMs, endMs: window.toMs }, cost);
848
+ return b ? { bill: b } : {};
849
+ })()
497
850
  }
498
851
  };
499
852
  })()
@@ -529,7 +882,7 @@ function crossGroups(now, prev) {
529
882
  }
530
883
  /* ── 시간 비교 ──────────────────────────────────────────────────────────────
531
884
  * 숫자 하나로는 좋아졌는지 나빠졌는지 알 수 없다("29초" 가 개선인지 악화인지). 같은 길이의 창을 옮겨
532
- * 한 번 더 접으면 되므로(폴드는 순수) 값싸다. 창 옮기기와 차이 계산은 계산 규칙이라 여기 있다 —
885
+ * 한 번 더 계산하면 되므로(폴드는 순수) 값싸다. 창 옮기기와 차이 계산은 계산 규칙이라 여기 있다 —
533
886
  * 조회 층에 두면 테스트가 DB 를 끌고 와야 한다.
534
887
  */
535
888
  /**
@@ -553,7 +906,7 @@ function deltaP50(now, prev) {
553
906
  return now.count > 0 && prev.count > 0 ? now.p50Ms - prev.p50Ms : undefined;
554
907
  }
555
908
  /**
556
- * 창을 N 등분해 구간별로 접는다 — **같은 이벤트를 창만 바꿔** 다시 접는다(조회 추가 없음).
909
+ * 창을 N 등분해 구간별로 처리한다 — **같은 이벤트를 창만 바꿔** 다시 계산한다(조회 추가 없음).
557
910
  *
558
911
  * 마지막 구간은 나머지를 흡수한다(부동소수 나눗셈으로 끝이 밀리면 마지막 완료가 빠진다).
559
912
  * `count <= 1` 이면 쪼갤 것이 없으므로 빈 배열 — 호출부가 "추세 없음" 으로 다룬다.