@things-factory/headless-twin 10.0.9 → 10.0.10

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 (353) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +2 -2
  2. package/dist-server/engine/canonical-ingest.js +24 -2
  3. package/dist-server/engine/canonical-ingest.js.map +1 -1
  4. package/dist-server/engine/command-routing.js +1 -1
  5. package/dist-server/engine/command-routing.js.map +1 -1
  6. package/dist-server/engine/energy-topology.d.ts +86 -0
  7. package/dist-server/engine/energy-topology.js +144 -0
  8. package/dist-server/engine/energy-topology.js.map +1 -0
  9. package/dist-server/engine/index.d.ts +8 -3
  10. package/dist-server/engine/index.js +10 -3
  11. package/dist-server/engine/index.js.map +1 -1
  12. package/dist-server/engine/kpi-baseline.d.ts +1 -1
  13. package/dist-server/engine/kpi-baseline.js.map +1 -1
  14. package/dist-server/engine/kpi-fold.d.ts +137 -1
  15. package/dist-server/engine/kpi-fold.js +225 -0
  16. package/dist-server/engine/kpi-fold.js.map +1 -1
  17. package/dist-server/engine/kpi-query.d.ts +1 -1
  18. package/dist-server/engine/kpi-query.js +212 -18
  19. package/dist-server/engine/kpi-query.js.map +1 -1
  20. package/dist-server/engine/kpi-target.d.ts +17 -0
  21. package/dist-server/engine/kpi-target.js +24 -2
  22. package/dist-server/engine/kpi-target.js.map +1 -1
  23. package/dist-server/engine/live-feed-registry.d.ts +25 -0
  24. package/dist-server/engine/live-feed-registry.js +51 -0
  25. package/dist-server/engine/live-feed-registry.js.map +1 -0
  26. package/dist-server/engine/load-meter.d.ts +181 -0
  27. package/dist-server/engine/load-meter.js +267 -0
  28. package/dist-server/engine/load-meter.js.map +1 -0
  29. package/dist-server/engine/local-declarations.d.ts +262 -0
  30. package/dist-server/engine/local-declarations.js +528 -0
  31. package/dist-server/engine/local-declarations.js.map +1 -0
  32. package/dist-server/engine/model-basis.d.ts +39 -6
  33. package/dist-server/engine/model-basis.js +69 -9
  34. package/dist-server/engine/model-basis.js.map +1 -1
  35. package/dist-server/engine/{board-vocabulary.d.ts → model-vocabulary.d.ts} +4 -4
  36. package/dist-server/engine/{board-vocabulary.js → model-vocabulary.js} +37 -19
  37. package/dist-server/engine/model-vocabulary.js.map +1 -0
  38. package/dist-server/engine/oee-accumulator.d.ts +28 -0
  39. package/dist-server/engine/oee-accumulator.js +26 -1
  40. package/dist-server/engine/oee-accumulator.js.map +1 -1
  41. package/dist-server/engine/operation-basis.d.ts +32 -0
  42. package/dist-server/engine/operation-basis.js +87 -0
  43. package/dist-server/engine/operation-basis.js.map +1 -0
  44. package/dist-server/engine/property-effects.d.ts +30 -0
  45. package/dist-server/engine/property-effects.js +192 -0
  46. package/dist-server/engine/property-effects.js.map +1 -0
  47. package/dist-server/engine/runtime-key.d.ts +1 -1
  48. package/dist-server/engine/runtime-key.js +1 -1
  49. package/dist-server/engine/runtime-key.js.map +1 -1
  50. package/dist-server/engine/spec-coverage.js +3 -3
  51. package/dist-server/engine/spec-coverage.js.map +1 -1
  52. package/dist-server/engine/state-axes.d.ts +7 -2
  53. package/dist-server/engine/state-axes.js +8 -3
  54. package/dist-server/engine/state-axes.js.map +1 -1
  55. package/dist-server/engine/structure-diff.js +1 -1
  56. package/dist-server/engine/structure-diff.js.map +1 -1
  57. package/dist-server/engine/travel-estimator.d.ts +9 -2
  58. package/dist-server/engine/travel-estimator.js +11 -6
  59. package/dist-server/engine/travel-estimator.js.map +1 -1
  60. package/dist-server/engine/twin-engine.d.ts +332 -47
  61. package/dist-server/engine/twin-engine.js +1314 -279
  62. package/dist-server/engine/twin-engine.js.map +1 -1
  63. package/dist-server/engine/warm-start.d.ts +38 -4
  64. package/dist-server/engine/warm-start.js +38 -9
  65. package/dist-server/engine/warm-start.js.map +1 -1
  66. package/dist-server/index.d.ts +1 -0
  67. package/dist-server/index.js +17 -11
  68. package/dist-server/index.js.map +1 -1
  69. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.d.ts +5 -0
  70. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +48 -0
  71. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -0
  72. package/dist-server/migrations/index.d.ts +2 -0
  73. package/dist-server/migrations/index.js +10 -0
  74. package/dist-server/migrations/index.js.map +1 -0
  75. package/dist-server/service/index.d.ts +3 -2
  76. package/dist-server/service/index.js +25 -17
  77. package/dist-server/service/index.js.map +1 -1
  78. package/dist-server/service/reference/discovery-result.d.ts +1 -1
  79. package/dist-server/service/reference/discovery-result.js +2 -2
  80. package/dist-server/service/reference/discovery-result.js.map +1 -1
  81. package/dist-server/service/reference/ingest-space.d.ts +24 -1
  82. package/dist-server/service/reference/ingest-space.js +17 -1
  83. package/dist-server/service/reference/ingest-space.js.map +1 -1
  84. package/dist-server/service/reference/reference-live.d.ts +23 -0
  85. package/dist-server/service/reference/reference-live.js +121 -4
  86. package/dist-server/service/reference/reference-live.js.map +1 -1
  87. package/dist-server/service/reference/reference-master.d.ts +194 -13
  88. package/dist-server/service/reference/reference-master.js +159 -28
  89. package/dist-server/service/reference/reference-master.js.map +1 -1
  90. package/dist-server/service/reference/reference-resolver.d.ts +31 -1
  91. package/dist-server/service/reference/reference-resolver.js +223 -12
  92. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  93. package/dist-server/service/reference/template-registry.d.ts +19 -3
  94. package/dist-server/service/reference/template-registry.js.map +1 -1
  95. package/dist-server/service/twin-audit/command-audit.d.ts +37 -0
  96. package/dist-server/service/twin-audit/command-audit.js +53 -0
  97. package/dist-server/service/twin-audit/command-audit.js.map +1 -0
  98. package/dist-server/service/twin-audit/index.d.ts +4 -0
  99. package/dist-server/service/twin-audit/index.js +8 -0
  100. package/dist-server/service/twin-audit/index.js.map +1 -0
  101. package/dist-server/service/twin-audit/twin-audit-event.d.ts +24 -0
  102. package/dist-server/service/twin-audit/twin-audit-event.js +125 -0
  103. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -0
  104. package/dist-server/service/twin-audit/twin-audit-query.d.ts +4 -0
  105. package/dist-server/service/twin-audit/twin-audit-query.js +76 -0
  106. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -0
  107. package/dist-server/service/twin-control/twin-control-mutation.js +21 -1
  108. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  109. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  110. package/dist-server/service/twin-event/twin-event-keys.js +1 -1
  111. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  112. package/dist-server/service/twin-event/twin-event.d.ts +14 -5
  113. package/dist-server/service/twin-event/twin-event.js +46 -14
  114. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  115. package/dist-server/service/twin-forecast/forecast-metrics.d.ts +13 -0
  116. package/dist-server/service/twin-forecast/forecast-metrics.js +57 -0
  117. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -0
  118. package/dist-server/service/twin-forecast/forecast-tuning.d.ts +12 -0
  119. package/dist-server/service/twin-forecast/forecast-tuning.js +44 -0
  120. package/dist-server/service/twin-forecast/forecast-tuning.js.map +1 -0
  121. package/dist-server/service/twin-forecast/gap-analytics.d.ts +28 -2
  122. package/dist-server/service/twin-forecast/gap-analytics.js +43 -17
  123. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  124. package/dist-server/service/twin-forecast/twin-forecast-query.js +217 -38
  125. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  126. package/dist-server/service/twin-instance/twin-instance.d.ts +38 -5
  127. package/dist-server/service/twin-instance/twin-instance.js +66 -20
  128. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  129. package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -2
  130. package/dist-server/service/twin-journal/twin-journal-query.js +71 -9
  131. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  132. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +3 -5
  133. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +24 -27
  134. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  135. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  136. package/dist-server/service/twin-metrics/twin-metrics-query.js +62 -0
  137. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  138. package/dist-server/service/twin-model/epcis-coverage.d.ts +14 -0
  139. package/dist-server/service/twin-model/epcis-coverage.js +174 -0
  140. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -0
  141. package/dist-server/service/twin-model/iec61850-coverage.d.ts +14 -0
  142. package/dist-server/service/twin-model/iec61850-coverage.js +98 -0
  143. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -0
  144. package/dist-server/service/twin-model/index.d.ts +13 -0
  145. package/dist-server/service/twin-model/index.js +29 -0
  146. package/dist-server/service/twin-model/index.js.map +1 -0
  147. package/dist-server/service/twin-model/isa95-coverage.d.ts +48 -0
  148. package/dist-server/service/twin-model/isa95-coverage.js +155 -0
  149. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -0
  150. package/dist-server/service/twin-model/project-structure.d.ts +16 -0
  151. package/dist-server/service/twin-model/project-structure.js +173 -0
  152. package/dist-server/service/twin-model/project-structure.js.map +1 -0
  153. package/dist-server/service/twin-model/standard-coverage.d.ts +9 -0
  154. package/dist-server/service/twin-model/standard-coverage.js +37 -0
  155. package/dist-server/service/twin-model/standard-coverage.js.map +1 -0
  156. package/dist-server/service/twin-model/twin-equipment.d.ts +64 -0
  157. package/dist-server/service/twin-model/twin-equipment.js +134 -0
  158. package/dist-server/service/twin-model/twin-equipment.js.map +1 -0
  159. package/dist-server/service/twin-model/twin-lineage-query.d.ts +3 -0
  160. package/dist-server/service/twin-model/twin-lineage-query.js +206 -0
  161. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -0
  162. package/dist-server/service/twin-model/twin-location.d.ts +58 -0
  163. package/dist-server/service/twin-model/twin-location.js +130 -0
  164. package/dist-server/service/twin-model/twin-location.js.map +1 -0
  165. package/dist-server/service/twin-model/twin-model-item-query.d.ts +141 -0
  166. package/dist-server/service/twin-model/twin-model-item-query.js +852 -0
  167. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -0
  168. package/dist-server/service/twin-model/twin-model-mutation.d.ts +57 -0
  169. package/dist-server/service/twin-model/twin-model-mutation.js +490 -0
  170. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -0
  171. package/dist-server/service/twin-model/twin-model-query.d.ts +43 -0
  172. package/dist-server/service/twin-model/twin-model-query.js +551 -0
  173. package/dist-server/service/twin-model/twin-model-query.js.map +1 -0
  174. package/dist-server/service/twin-model/twin-model-tree-query.d.ts +3 -0
  175. package/dist-server/service/twin-model/twin-model-tree-query.js +158 -0
  176. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -0
  177. package/dist-server/service/twin-model/twin-operation.d.ts +61 -0
  178. package/dist-server/service/twin-model/twin-operation.js +137 -0
  179. package/dist-server/service/twin-model/twin-operation.js.map +1 -0
  180. package/dist-server/service/twin-space/move-space.d.ts +65 -0
  181. package/dist-server/service/twin-space/move-space.js +166 -0
  182. package/dist-server/service/twin-space/move-space.js.map +1 -0
  183. package/dist-server/service/twin-space/space-integrity.d.ts +97 -0
  184. package/dist-server/service/twin-space/space-integrity.js +182 -0
  185. package/dist-server/service/twin-space/space-integrity.js.map +1 -0
  186. package/dist-server/service/twin-space/twin-space-area.js +4 -4
  187. package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
  188. package/dist-server/service/twin-space/twin-space-representation.js +6 -6
  189. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  190. package/dist-server/service/twin-space/twin-space-resolver.d.ts +63 -1
  191. package/dist-server/service/twin-space/twin-space-resolver.js +356 -18
  192. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  193. package/dist-server/service/twin-space/twin-space.d.ts +17 -1
  194. package/dist-server/service/twin-space/twin-space.js +15 -5
  195. package/dist-server/service/twin-space/twin-space.js.map +1 -1
  196. package/dist-server/service/twin-structure/twin-structure.d.ts +1 -1
  197. package/dist-server/service/twin-structure/twin-structure.js +8 -8
  198. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  199. package/dist-shared/axis-read.d.ts +12 -0
  200. package/dist-shared/axis-read.js +32 -0
  201. package/dist-shared/axis-read.js.map +1 -0
  202. package/{dist-server/engine → dist-shared}/entity-delta.d.ts +3 -0
  203. package/{dist-server/engine → dist-shared}/entity-delta.js +102 -2
  204. package/dist-shared/entity-delta.js.map +1 -0
  205. package/dist-shared/kpi-broadcast.js.map +1 -0
  206. package/dist-shared/twin-level.js.map +1 -0
  207. package/package.json +14 -12
  208. package/server/engine/canonical-ingest.ts +36 -4
  209. package/server/engine/command-routing.ts +1 -1
  210. package/server/engine/energy-topology.ts +189 -0
  211. package/server/engine/index.ts +10 -3
  212. package/server/engine/kpi-baseline.ts +1 -1
  213. package/server/engine/kpi-fold.ts +389 -1
  214. package/server/engine/kpi-query.ts +214 -19
  215. package/server/engine/kpi-target.ts +24 -2
  216. package/server/engine/live-feed-registry.ts +58 -0
  217. package/server/engine/load-meter.ts +384 -0
  218. package/server/engine/local-declarations.ts +700 -0
  219. package/server/engine/model-basis.ts +78 -10
  220. package/server/engine/{board-vocabulary.ts → model-vocabulary.ts} +36 -15
  221. package/server/engine/oee-accumulator.ts +34 -1
  222. package/server/engine/operation-basis.ts +100 -0
  223. package/server/engine/property-effects.ts +199 -0
  224. package/server/engine/runtime-key.ts +1 -1
  225. package/server/engine/spec-coverage.ts +3 -3
  226. package/server/engine/state-axes.ts +8 -3
  227. package/server/engine/structure-diff.ts +1 -1
  228. package/server/engine/travel-estimator.ts +11 -6
  229. package/server/engine/twin-engine.ts +1414 -274
  230. package/server/engine/warm-start.ts +83 -12
  231. package/server/index.ts +13 -10
  232. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +43 -0
  233. package/server/migrations/index.ts +7 -0
  234. package/server/service/index.ts +9 -1
  235. package/server/service/reference/discovery-result.ts +2 -2
  236. package/server/service/reference/ingest-space.ts +35 -1
  237. package/server/service/reference/reference-live.ts +119 -4
  238. package/server/service/reference/reference-master.ts +276 -35
  239. package/server/service/reference/reference-resolver.ts +232 -12
  240. package/server/service/reference/template-registry.ts +20 -5
  241. package/server/service/twin-audit/command-audit.ts +81 -0
  242. package/server/service/twin-audit/index.ts +5 -0
  243. package/server/service/twin-audit/twin-audit-event.ts +112 -0
  244. package/server/service/twin-audit/twin-audit-query.ts +72 -0
  245. package/server/service/twin-control/twin-control-mutation.ts +22 -1
  246. package/server/service/twin-event/twin-event-keys.ts +1 -1
  247. package/server/service/twin-event/twin-event.ts +52 -15
  248. package/server/service/twin-forecast/forecast-metrics.ts +60 -0
  249. package/server/service/twin-forecast/forecast-tuning.ts +40 -0
  250. package/server/service/twin-forecast/gap-analytics.ts +48 -9
  251. package/server/service/twin-forecast/twin-forecast-query.ts +208 -36
  252. package/server/service/twin-instance/twin-instance.ts +126 -25
  253. package/server/service/twin-journal/twin-journal-query.ts +74 -10
  254. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +23 -22
  255. package/server/service/twin-metrics/twin-metrics-query.ts +59 -2
  256. package/server/service/twin-model/epcis-coverage.ts +195 -0
  257. package/server/service/twin-model/iec61850-coverage.ts +117 -0
  258. package/server/service/twin-model/index.ts +25 -0
  259. package/server/service/twin-model/isa95-coverage.ts +190 -0
  260. package/server/service/twin-model/project-structure.ts +199 -0
  261. package/server/service/twin-model/standard-coverage.ts +35 -0
  262. package/server/service/twin-model/twin-equipment.ts +150 -0
  263. package/server/service/twin-model/twin-lineage-query.ts +193 -0
  264. package/server/service/twin-model/twin-location.ts +140 -0
  265. package/server/service/twin-model/twin-model-item-query.ts +848 -0
  266. package/server/service/twin-model/twin-model-mutation.ts +503 -0
  267. package/server/service/twin-model/twin-model-query.ts +537 -0
  268. package/server/service/twin-model/twin-model-tree-query.ts +180 -0
  269. package/server/service/twin-model/twin-operation.ts +150 -0
  270. package/server/service/twin-space/move-space.ts +262 -0
  271. package/server/service/twin-space/space-integrity.ts +293 -0
  272. package/server/service/twin-space/twin-space-area.ts +4 -4
  273. package/server/service/twin-space/twin-space-representation.ts +6 -6
  274. package/server/service/twin-space/twin-space-resolver.ts +355 -21
  275. package/server/service/twin-space/twin-space.ts +40 -6
  276. package/server/service/twin-structure/twin-structure.ts +10 -10
  277. package/shared/axis-read.ts +28 -0
  278. package/{server/engine → shared}/entity-delta.ts +96 -1
  279. package/test/adopt-structure-live.test.ts +133 -0
  280. package/test/attention-digest.test.ts +3 -3
  281. package/test/axis-read.test.ts +87 -0
  282. package/test/boot-resume.test.ts +205 -0
  283. package/test/canonical-ingest-vocabularies.test.ts +118 -0
  284. package/test/capability-mapping.test.ts +1 -1
  285. package/test/command-routing.test.ts +1 -1
  286. package/test/declaration-reaches-model.test.ts +178 -0
  287. package/test/discovery-result.test.ts +1 -1
  288. package/test/energy-topology.test.ts +113 -0
  289. package/test/entity-delta.test.ts +171 -1
  290. package/test/event-time-column.test.ts +67 -0
  291. package/test/forecast-metrics.test.ts +71 -0
  292. package/test/forecast-tuning.test.ts +42 -0
  293. package/test/gap-analytics.test.ts +34 -7
  294. package/test/ingest-bench.test.ts +6 -6
  295. package/test/ingest-running-guard.test.ts +136 -0
  296. package/test/ingest-space.test.ts +29 -1
  297. package/test/instance-cache-lifecycle.test.ts +110 -0
  298. package/test/kernel-kind-guard.test.ts +96 -0
  299. package/test/kpi-baseline-db.test.ts +3 -1
  300. package/test/kpi-baseline.test.ts +3 -3
  301. package/test/kpi-fold.test.ts +393 -2
  302. package/test/kpi-query-bench.test.ts +5 -3
  303. package/test/lineage-survives-restart.test.ts +203 -0
  304. package/test/live-feed-registry.test.ts +67 -0
  305. package/test/live-kernel-facts.test.ts +161 -0
  306. package/test/live-mirror-parity.test.ts +22 -15
  307. package/test/load-meter.test.ts +314 -0
  308. package/test/local-declarations.test.ts +554 -0
  309. package/test/master-to-twin.test.ts +121 -33
  310. package/test/model-basis.test.ts +56 -1
  311. package/test/{board-vocabulary.test.ts → model-vocabulary.test.ts} +25 -25
  312. package/test/move-space.test.ts +149 -0
  313. package/test/mutation-gate.test.ts +19 -5
  314. package/test/oee-accumulator.test.ts +26 -7
  315. package/test/operation-basis.test.ts +100 -0
  316. package/test/operations-capability-db.test.ts +165 -0
  317. package/test/project-structure-db.test.ts +226 -0
  318. package/test/projection-reaches-screen.test.ts +272 -0
  319. package/test/property-effects.test.ts +96 -0
  320. package/test/resync-origin-site.test.ts +77 -0
  321. package/test/scale-twin-bench.test.ts +5 -5
  322. package/test/snapshot-freshness.test.ts +60 -0
  323. package/test/space-integrity.test.ts +223 -0
  324. package/test/standard-coverage.test.ts +94 -0
  325. package/test/state-axes.test.ts +1 -1
  326. package/test/streamline-e2e.test.ts +14 -14
  327. package/test/structure-revision-db.test.ts +11 -11
  328. package/test/tenant-registry-db.test.ts +5 -5
  329. package/test/twin-audit.test.ts +83 -0
  330. package/test/twin-model-item-db.test.ts +275 -0
  331. package/test/twin-model-tree-db.test.ts +176 -0
  332. package/test/twin-origin-resync.test.ts +114 -0
  333. package/test/warm-start-seam.test.ts +6 -6
  334. package/test/warm-start.test.ts +88 -3
  335. package/tsconfig.json +6 -1
  336. package/tsconfig.shared.json +23 -0
  337. package/tsconfig.shared.tsbuildinfo +1 -0
  338. package/tsconfig.tsbuildinfo +1 -0
  339. package/dist-server/engine/board-vocabulary.js.map +0 -1
  340. package/dist-server/engine/entity-delta.js.map +0 -1
  341. package/dist-server/engine/kpi-broadcast.js.map +0 -1
  342. package/dist-server/engine/twin-level.js.map +0 -1
  343. package/dist-server/service/twin-event/backfill-keys.d.ts +0 -11
  344. package/dist-server/service/twin-event/backfill-keys.js +0 -63
  345. package/dist-server/service/twin-event/backfill-keys.js.map +0 -1
  346. package/dist-server/tsconfig.tsbuildinfo +0 -1
  347. package/server/service/twin-event/backfill-keys.ts +0 -72
  348. /package/{dist-server/engine → dist-shared}/kpi-broadcast.d.ts +0 -0
  349. /package/{dist-server/engine → dist-shared}/kpi-broadcast.js +0 -0
  350. /package/{dist-server/engine → dist-shared}/twin-level.d.ts +0 -0
  351. /package/{dist-server/engine → dist-shared}/twin-level.js +0 -0
  352. /package/{server/engine → shared}/kpi-broadcast.ts +0 -0
  353. /package/{server/engine → shared}/twin-level.ts +0 -0
@@ -39,6 +39,23 @@ export declare const TWIN_METRIC: {
39
39
  readonly unit: "ms";
40
40
  readonly of: (k: KpiResult) => number;
41
41
  };
42
+ readonly 'energy.kWh': {
43
+ readonly direction: "lower";
44
+ readonly unit: "kWh";
45
+ readonly of: (k: KpiResult) => number;
46
+ };
47
+ /** 최대수요 — 계약 상한을 목표로 적는 자리다(요금의 기본요금 부분이 여기서 정해진다). */
48
+ readonly 'energy.peakKW': {
49
+ readonly direction: "lower";
50
+ readonly unit: "kW";
51
+ readonly of: (k: KpiResult) => number;
52
+ };
53
+ /** 원단위 — 완료 오더당 kWh. 답할 수 없으면(`null`) 판정하지 않는다. */
54
+ readonly 'energy.intensity': {
55
+ readonly direction: "lower";
56
+ readonly unit: "kWh/order";
57
+ readonly of: (k: KpiResult) => number;
58
+ };
42
59
  /** 자원 점유 — 개별 자원이 아니라 **창 전체의 평균 비율**(자원별은 관점 축의 일이다). */
43
60
  readonly 'utilization.ratio': {
44
61
  readonly direction: "higher";
@@ -19,7 +19,7 @@ const statOf = (s, pick) => s.count > 0 ? pick(s) : undefined;
19
19
  * 좋다 — 이것을 입력으로 받으면 반대로 적은 목표가 만들어지고, 그 뒤 판정이 전부 뒤집힌다.
20
20
  */
21
21
  exports.TWIN_METRIC = {
22
- /* 기록이 없는 구간은 처리량 0 이 아니라 **모름**이다 — `eventsInWindow` 가 그것을 가른다.
22
+ /* 기록이 없는 구간은 처리량 0 이 아니라 **모름**이다 — `eventsInWindow` 가 그것을 구분한다.
23
23
  0 으로 판정하면 "측정 못 한 구간" 이 "일을 안 한 구간" 으로 보고된다. */
24
24
  'throughput.tasks': { direction: 'higher', unit: 'count', of: (k) => measured(k, k.throughput.tasks) },
25
25
  'throughput.orders': { direction: 'higher', unit: 'count', of: (k) => measured(k, k.throughput.orders) },
@@ -30,6 +30,28 @@ exports.TWIN_METRIC = {
30
30
  'leadTime.p90Ms': { direction: 'lower', unit: 'ms', of: (k) => statOf(k.leadTime, d => d.p90Ms) },
31
31
  'workTime.p50Ms': { direction: 'lower', unit: 'ms', of: (k) => statOf(k.workTime, d => d.p50Ms) },
32
32
  'waitTime.p50Ms': { direction: 'lower', unit: 'ms', of: (k) => statOf(k.waitTime, d => d.p50Ms) },
33
+ /*
34
+ * ── 에너지 (2026-08-17) ────────────────────────────────────────────────────
35
+ *
36
+ * 값은 폴드가 이미 내고 있었는데 **목표를 걸 수 있는 지표 목록에 없었다.** 그래서 화면은 「이번 창에
37
+ * 1,240kWh」를 보여 주면서도 그것이 좋은지 나쁜지는 말하지 못했다 — 에너지야말로 계약이라는 딱딱한
38
+ * 기준선이 있는 영역인데도 그랬다.
39
+ *
40
+ * 셋 다 방향은 **낮을수록 좋다**로 못 박는다. 「많이 쓸수록 좋다」는 에너지 목표는 없다. 산출이 늘어
41
+ * 전력량이 느는 경우는 원단위(`energy.intensity`)가 답할 자리이지, 방향을 뒤집을 이유가 아니다.
42
+ *
43
+ * 값이 없는 창(에너지 트윈이 아니거나 계측이 끊긴 창)은 `undefined` 로 남긴다 — 0 으로 읽으면
44
+ * 시간 지표에서 이미 겪은 그 결함이 그대로 재현된다(안 쓴 것이 아니라 못 잰 것인데 만점이 된다).
45
+ */
46
+ 'energy.kWh': { direction: 'lower', unit: 'kWh', of: (k) => k.energy?.kWh },
47
+ /** 최대수요 — 계약 상한을 목표로 적는 자리다(요금의 기본요금 부분이 여기서 정해진다). */
48
+ 'energy.peakKW': { direction: 'lower', unit: 'kW', of: (k) => k.energy?.peakKW },
49
+ /** 원단위 — 완료 오더당 kWh. 답할 수 없으면(`null`) 판정하지 않는다. */
50
+ 'energy.intensity': {
51
+ direction: 'lower',
52
+ unit: 'kWh/order',
53
+ of: (k) => (typeof k.energy?.intensity?.value === 'number' ? k.energy.intensity.value : undefined)
54
+ },
33
55
  /** 자원 점유 — 개별 자원이 아니라 **창 전체의 평균 비율**(자원별은 관점 축의 일이다). */
34
56
  'utilization.ratio': {
35
57
  direction: 'higher',
@@ -128,7 +150,7 @@ function judgeTargets(kpi, targets) {
128
150
  }
129
151
  return out;
130
152
  }
131
- /** 판정 산수 한 곳 — 전체 판정과 축 판정이 **같은 규칙**을 쓴다(두 벌이면 갈라진다). */
153
+ /** 판정 산수 한 곳 — 전체 판정과 축 판정이 **같은 규칙**을 쓴다(두 벌이면 어긋난다). */
132
154
  function verdictOf(metric, actual, target, direction) {
133
155
  const met = direction === 'higher' ? actual >= target : actual <= target;
134
156
  const ratio = direction === 'higher' ? (target > 0 ? actual / target : Number.NaN) : actual > 0 ? target / actual : Number.NaN;
@@ -1 +1 @@
1
- {"version":3,"file":"kpi-target.js","sourceRoot":"","sources":["../../server/engine/kpi-target.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 성과 목표 — **숫자를 판단으로 바꾸는 층.**\n *\n * ── 왜 필요한가 ─────────────────────────────────────────────────────────────\n * 화면과 AI 는 \"처리량 345건 · 작업시간 29초\" 를 정확히 말한다. 그런데 **그것이 좋은지 나쁜지는 말할 수\n * 없다.** 판단 근거가 앞 구간 비교뿐이라 \"어제보다 나아졌다\" 까지가 한계이고, 어제도 나빴으면 그 말은\n * 쓸모가 없다. 목표가 붙는 순간 같은 숫자가 **정보에서 판단으로** 바뀐다.\n *\n * ── 층을 섞지 않는다 ────────────────────────────────────────────────────────\n * 폴드(`kpi-fold`)는 **사실만** 낸다 — 목표를 모른다. 목표를 아는 순간 \"사실을 세는 규칙\" 과 \"잘했나\n * 판정하는 규칙\" 이 한 함수에 섞이고, 목표가 바뀔 때마다 사실 계산을 건드리게 된다. 그래서 판정은\n * 여기, **순수 함수**로 따로 둔다(폴드처럼 테스트가 값싸다).\n *\n * ── 목표의 집 (사용자 결정 2026-08-02) ──────────────────────────────────────\n * **트윈은 운영 목표를 갖고, `@things-factory/kpi` 는 경영 목표를 갖는다.** 경계:\n *\n * 트윈(TwinTarget) 이 구간·이 교대·이 공간의 **즉시 판단**. 현장 관리자가 트윈 화면에서 고친다.\n * 값은 저널 폴드에서 **그때그때 계산**된다(저장된 값을 읽지 않는다).\n * packages/kpi **기간 평가**(조직·분기·점수·가중치·알림). 값은 저장돼 있어야 한다.\n *\n * 이 경계를 지키는 규칙 하나: **트윈은 목표를 복제하지 않는다.** 경영 목표가 필요하면 트윈의 사실을\n * 내보내 그쪽이 소비하고, 그쪽 목표를 여기로 베껴 오지 않는다 — 베끼는 순간 두 개의 진실이 된다.\n */\n/*\n * ── 운영 임계(커널 attention)와의 경계 ─────────────────────────────────────\n *\n * **attention 은 개체 하나의 지금 상태**다 — 앵커(설비·자리·오더)와 할 수 있는 조치를 갖는다.\n * **target(여기)은 구간의 집계**다 — 앵커도 조치도 없고 추세를 판단한다.\n *\n * 그래서 창 단위 집계 판정을 attention 으로 넣지 않는다: \"가동률 저하\"(창 평균) attention 을 만들면\n * `utilization.ratio` 목표와 **같은 사실을 두 층이 다른 임계로 판정**하게 된다.\n * (design/map/performance.md §5.0-4 — 현재 다섯 attention 과 이 어휘는 겹치지 않는다.)\n */\nimport type { DurationStats, KpiResult } from './kpi-fold.js'\n\n/** 측정된 기록이 있을 때만 값으로 인정한다 — 없으면 모름(0 이 아니다). */\nconst measured = (k: KpiResult, v: number): number | undefined => (k.eventsInWindow > 0 ? v : undefined)\n\n/** 분위수는 표본이 있을 때만 값이다 — `count === 0` 의 `p50Ms: 0` 은 \"0 밀리초\" 가 아니라 \"없음\" 이다. */\nconst statOf = (s: DurationStats, pick: (d: DurationStats) => number): number | undefined =>\n s.count > 0 ? pick(s) : undefined\n\n/**\n * 지표 어휘 — **이름을 한 곳에서 정한다.**\n *\n * 목표와 사실이 서로 다른 이름을 쓰면 그 사이에 매핑 표가 생기고, 그 표가 곧 방언이 된다. 그래서\n * 목표가 가리킬 수 있는 지표는 여기 열거된 것뿐이고, 값을 꺼내는 방법도 여기 함께 있다.\n *\n * **`direction` 은 지표의 성질이지 사용자의 선택이 아니다.** 처리량은 높을수록 좋고 대기시간은 낮을수록\n * 좋다 — 이것을 입력으로 받으면 반대로 적은 목표가 만들어지고, 그 뒤 판정이 전부 뒤집힌다.\n */\nexport const TWIN_METRIC = {\n /* 기록이 없는 구간은 처리량 0 이 아니라 **모름**이다 — `eventsInWindow` 가 그것을 가른다.\n 0 으로 판정하면 \"측정 못 한 구간\" 이 \"일을 안 한 구간\" 으로 보고된다. */\n 'throughput.tasks': { direction: 'higher', unit: 'count', of: (k: KpiResult) => measured(k, k.throughput.tasks) },\n 'throughput.orders': { direction: 'higher', unit: 'count', of: (k: KpiResult) => measured(k, k.throughput.orders) },\n /* 소요시간 분위수는 `count === 0` 이면 **0 이 아니라 없다.** 이것을 0 으로 읽으면 시간 지표는\n \"낮을수록 좋다\" 라서 **일이 없던 구간이 목표를 완벽히 달성한 것으로** 보고된다 — 미달을 놓치는\n 것보다 나쁘다(고칠 곳이 있는데 잘하고 있다고 말한다). 테스트가 이것을 잡았다. */\n 'leadTime.p50Ms': { direction: 'lower', unit: 'ms', of: (k: KpiResult) => statOf(k.leadTime, d => d.p50Ms) },\n 'leadTime.p90Ms': { direction: 'lower', unit: 'ms', of: (k: KpiResult) => statOf(k.leadTime, d => d.p90Ms) },\n 'workTime.p50Ms': { direction: 'lower', unit: 'ms', of: (k: KpiResult) => statOf(k.workTime, d => d.p50Ms) },\n 'waitTime.p50Ms': { direction: 'lower', unit: 'ms', of: (k: KpiResult) => statOf(k.waitTime, d => d.p50Ms) },\n /** 자원 점유 — 개별 자원이 아니라 **창 전체의 평균 비율**(자원별은 관점 축의 일이다). */\n 'utilization.ratio': {\n direction: 'higher',\n unit: 'ratio',\n of: (k: KpiResult) =>\n k.utilization.length ? k.utilization.reduce((s, u) => s + u.ratio, 0) / k.utilization.length : undefined\n }\n} as const\n\nexport type TwinMetricId = keyof typeof TWIN_METRIC\nexport const isTwinMetric = (id: string): id is TwinMetricId => id in TWIN_METRIC\n\n/**\n * 저장된 목표 행에서 **이 범위에 걸린 목표**를 고른다 — 좁은 선언이 넓은 선언을 덮는다.\n *\n * **왜 순수 함수인가**: 이 규칙(공간 목표 위에 인스턴스 목표를 얹는다)은 지금까지 DB 접근 뒤에 숨어\n * 있어서 **화면을 눌러 봐야만** 확인할 수 있었다. 그런데 이 프로젝트는 로그인 뒤 화면을 자동으로\n * 검증할 방법이 없어, 실제로 여러 기능이 \"실화면 미검증\" 으로 쌓였다. 규칙을 밖으로 빼면 테스트가\n * 값싸진다 — 조회는 행을 가져오기만 하고, **판단은 여기서** 한다.\n */\n/**\n * 축 목표까지 담은 목록 — 전체 목표와 **축의 한 값**에 걸린 목표를 함께 든다.\n *\n * 축 목표는 전체 목표를 **덮지 않는다.** 다른 질문이기 때문이다: \"현장 전체가 100건\" 과\n * \"용접 구역이 30건\" 은 동시에 참일 수 있다. 덮게 만들면 축 하나에 목표를 거는 순간 전체 판정이\n * 사라져 사용자가 \"목표를 지웠나?\" 를 묻게 된다.\n */\nexport interface ScopedTargets {\n /** 창 전체에 걸린 목표. */\n overall: TwinTargetSpec[]\n /** 축 → (그 축의 값 → 목표들). 요청한 축만 채운다. */\n byAxisKey: Record<string, TwinTargetSpec[]>\n}\n\n/**\n * 축의 각 값에 대해 목표 대비 판정 — **그룹 항목에 그대로 붙일 형태.**\n *\n * 그룹의 지표는 전체보다 좁다(주문 수·자원 점유는 축 단위로 뜻이 다르거나 없다). 없는 지표에 목표가\n * 걸려 있으면 **판정하지 않는다** — 여기서 0 으로 읽으면 시간 지표가 \"완벽 달성\" 으로 뒤집힌다\n * (전체 판정에서 이미 겪은 결함이다).\n */\nexport function judgeGroupTargets(\n group: { key: string; tasks: number; leadTime: { count: number; p50Ms: number }; workTime: { count: number; p50Ms: number }; waitTime: { count: number; p50Ms: number } },\n targets: readonly TwinTargetSpec[] | undefined\n): TargetVerdict[] {\n const value = (metric: TwinMetricId): number | undefined => {\n switch (metric) {\n case 'throughput.tasks':\n return group.tasks\n case 'leadTime.p50Ms':\n return group.leadTime.count > 0 ? group.leadTime.p50Ms : undefined\n case 'workTime.p50Ms':\n return group.workTime.count > 0 ? group.workTime.p50Ms : undefined\n case 'waitTime.p50Ms':\n return group.waitTime.count > 0 ? group.waitTime.p50Ms : undefined\n default:\n return undefined // 축 단위로 뜻이 없는 지표(주문 수·자원 점유) — 지어내지 않는다\n }\n }\n const out: TargetVerdict[] = []\n for (const t of targets ?? []) {\n const spec = TWIN_METRIC[t.metric]\n const actual = value(t.metric)\n if (!spec || typeof actual !== 'number' || !Number.isFinite(actual) || !Number.isFinite(t.target)) continue\n out.push(verdictOf(t.metric, actual, t.target, spec.direction))\n }\n return out\n}\n\nexport function mergeTargets(\n rows: readonly TargetRow[],\n scope: { spaceId?: string; instanceIds: readonly string[]; axis?: string }\n): TwinTargetSpec[] {\n return scopedTargets(rows, scope).overall\n}\n\n/** 저장된 목표 행 — 판단에 필요한 것만(엔티티 전체를 요구하지 않는다: 테스트가 값싸진다). */\nexport interface TargetRow {\n metric: string\n target: number\n spaceId?: string | null\n instanceId?: string | null\n axis?: string | null\n axisKey?: string | null\n}\n\n/** 전체 목표와 축 목표를 함께 고른다 — 좁은 선언(인스턴스)이 넓은 선언(공간)을 덮는 규칙은 양쪽 같다. */\nexport function scopedTargets(\n rows: readonly TargetRow[],\n scope: { spaceId?: string; instanceIds: readonly string[]; axis?: string }\n): ScopedTargets {\n /* 같은 자리(지표 또는 축값+지표)에 공간·인스턴스 목표가 함께 있으면 인스턴스가 이긴다. */\n const pick = new Map<string, { target: number; fromInstance: boolean; axisKey?: string; metric: TwinMetricId }>()\n for (const r of rows) {\n if (!isTwinMetric(r.metric) || typeof r.target !== 'number' || !Number.isFinite(r.target)) continue\n const inSpace = !!scope.spaceId && r.spaceId === scope.spaceId\n const inInstance = !!r.instanceId && scope.instanceIds.includes(r.instanceId)\n if (!inSpace && !inInstance) continue\n if (r.axis) {\n if (!scope.axis || r.axis !== scope.axis || !r.axisKey) continue // 지금 보는 축만\n }\n const slot = `${r.axis ?? ''}\\u0000${r.axisKey ?? ''}\\u0000${r.metric}`\n const prev = pick.get(slot)\n if (prev?.fromInstance && !inInstance) continue // 인스턴스 선언을 공간 선언이 덮지 않는다\n pick.set(slot, { target: r.target, fromInstance: inInstance, axisKey: r.axisKey ?? undefined, metric: r.metric })\n }\n const overall: TwinTargetSpec[] = []\n const byAxisKey: Record<string, TwinTargetSpec[]> = {}\n for (const [slot, v] of pick) {\n const spec = { metric: v.metric, target: v.target }\n if (slot.startsWith('\\u0000')) overall.push(spec)\n else (byAxisKey[v.axisKey as string] ??= []).push(spec)\n }\n return { overall, byAxisKey }\n}\n\n/** 목표 하나 — 어느 지표를, 얼마로. 방향은 지표가 안다(§TWIN_METRIC). */\nexport interface TwinTargetSpec {\n metric: TwinMetricId\n target: number\n}\n\nexport interface TargetVerdict {\n metric: TwinMetricId\n /** 이 구간의 실제 값. */\n actual: number\n target: number\n direction: 'higher' | 'lower'\n /** 목표를 만족했나. */\n met: boolean\n /**\n * 목표 대비 비율 — **항상 \"1 이상이면 좋다\"** 로 정규화한다(방향이 다른 지표를 한 화면에서 비교할 수\n * 있게). 처리량은 `actual/target`, 시간은 `target/actual`.\n */\n ratio: number\n}\n\n/**\n * 목표 대비 판정 — **목표가 없는 지표는 판정하지 않는다.**\n *\n * 값이 없는 지표(그 구간에 기록이 없어 `undefined`)도 판정하지 않는다. 없는 것을 0 으로 놓으면\n * \"목표 크게 미달\" 이라는 **없는 사실**이 만들어지고, 사용자는 멈춘 적 없는 라인을 고치러 간다.\n * 목표를 0 으로 준 경우도 비율을 내지 않는다(0 으로 나누지 않는다) — 만족 여부만 답한다.\n */\nexport function judgeTargets(kpi: KpiResult, targets: readonly TwinTargetSpec[] | undefined): TargetVerdict[] {\n const out: TargetVerdict[] = []\n for (const t of targets ?? []) {\n const spec = TWIN_METRIC[t.metric]\n if (!spec) continue // 모르는 지표 이름 — 짐작해 매핑하지 않는다\n const actual = spec.of(kpi)\n if (typeof actual !== 'number' || !Number.isFinite(actual)) continue // 값이 없다 ≠ 값이 0\n if (!Number.isFinite(t.target)) continue\n out.push(verdictOf(t.metric, actual, t.target, spec.direction))\n }\n return out\n}\n\n/** 판정 산수 한 곳 — 전체 판정과 축 판정이 **같은 규칙**을 쓴다(두 벌이면 갈라진다). */\nfunction verdictOf(metric: TwinMetricId, actual: number, target: number, direction: 'higher' | 'lower'): TargetVerdict {\n const met = direction === 'higher' ? actual >= target : actual <= target\n const ratio = direction === 'higher' ? (target > 0 ? actual / target : Number.NaN) : actual > 0 ? target / actual : Number.NaN\n return { metric, actual, target, direction, met, ratio }\n}\n"]}
1
+ {"version":3,"file":"kpi-target.js","sourceRoot":"","sources":["../../server/engine/kpi-target.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 성과 목표 — **숫자를 판단으로 바꾸는 층.**\n *\n * ── 왜 필요한가 ─────────────────────────────────────────────────────────────\n * 화면과 AI 는 \"처리량 345건 · 작업시간 29초\" 를 정확히 말한다. 그런데 **그것이 좋은지 나쁜지는 말할 수\n * 없다.** 판단 근거가 앞 구간 비교뿐이라 \"어제보다 나아졌다\" 까지가 한계이고, 어제도 나빴으면 그 말은\n * 쓸모가 없다. 목표가 붙는 순간 같은 숫자가 **정보에서 판단으로** 바뀐다.\n *\n * ── 층을 섞지 않는다 ────────────────────────────────────────────────────────\n * 폴드(`kpi-fold`)는 **사실만** 낸다 — 목표를 모른다. 목표를 아는 순간 \"사실을 세는 규칙\" 과 \"잘했나\n * 판정하는 규칙\" 이 한 함수에 섞이고, 목표가 바뀔 때마다 사실 계산을 건드리게 된다. 그래서 판정은\n * 여기, **순수 함수**로 따로 둔다(폴드처럼 테스트가 값싸다).\n *\n * ── 목표의 집 (사용자 결정 2026-08-02) ──────────────────────────────────────\n * **트윈은 운영 목표를 갖고, `@things-factory/kpi` 는 경영 목표를 갖는다.** 경계:\n *\n * 트윈(TwinTarget) 이 구간·이 교대·이 공간의 **즉시 판단**. 현장 관리자가 트윈 화면에서 고친다.\n * 값은 저널 폴드에서 **그때그때 계산**된다(저장된 값을 읽지 않는다).\n * packages/kpi **기간 평가**(조직·분기·점수·가중치·알림). 값은 저장돼 있어야 한다.\n *\n * 이 경계를 지키는 규칙 하나: **트윈은 목표를 복제하지 않는다.** 경영 목표가 필요하면 트윈의 사실을\n * 내보내 그쪽이 소비하고, 그쪽 목표를 여기로 베껴 오지 않는다 — 베끼는 순간 두 개의 진실이 된다.\n */\n/*\n * ── 운영 임계(커널 attention)와의 경계 ─────────────────────────────────────\n *\n * **attention 은 개체 하나의 지금 상태**다 — 앵커(설비·자리·오더)와 할 수 있는 조치를 갖는다.\n * **target(여기)은 구간의 집계**다 — 앵커도 조치도 없고 추세를 판단한다.\n *\n * 그래서 창 단위 집계 판정을 attention 으로 넣지 않는다: \"가동률 저하\"(창 평균) attention 을 만들면\n * `utilization.ratio` 목표와 **같은 사실을 두 층이 다른 임계로 판정**하게 된다.\n * (design/map/performance.md §5.0-4 — 현재 다섯 attention 과 이 어휘는 겹치지 않는다.)\n */\nimport type { DurationStats, KpiResult } from './kpi-fold.js'\n\n/** 측정된 기록이 있을 때만 값으로 인정한다 — 없으면 모름(0 이 아니다). */\nconst measured = (k: KpiResult, v: number): number | undefined => (k.eventsInWindow > 0 ? v : undefined)\n\n/** 분위수는 표본이 있을 때만 값이다 — `count === 0` 의 `p50Ms: 0` 은 \"0 밀리초\" 가 아니라 \"없음\" 이다. */\nconst statOf = (s: DurationStats, pick: (d: DurationStats) => number): number | undefined =>\n s.count > 0 ? pick(s) : undefined\n\n/**\n * 지표 어휘 — **이름을 한 곳에서 정한다.**\n *\n * 목표와 사실이 서로 다른 이름을 쓰면 그 사이에 매핑 표가 생기고, 그 표가 곧 방언이 된다. 그래서\n * 목표가 가리킬 수 있는 지표는 여기 열거된 것뿐이고, 값을 꺼내는 방법도 여기 함께 있다.\n *\n * **`direction` 은 지표의 성질이지 사용자의 선택이 아니다.** 처리량은 높을수록 좋고 대기시간은 낮을수록\n * 좋다 — 이것을 입력으로 받으면 반대로 적은 목표가 만들어지고, 그 뒤 판정이 전부 뒤집힌다.\n */\nexport const TWIN_METRIC = {\n /* 기록이 없는 구간은 처리량 0 이 아니라 **모름**이다 — `eventsInWindow` 가 그것을 구분한다.\n 0 으로 판정하면 \"측정 못 한 구간\" 이 \"일을 안 한 구간\" 으로 보고된다. */\n 'throughput.tasks': { direction: 'higher', unit: 'count', of: (k: KpiResult) => measured(k, k.throughput.tasks) },\n 'throughput.orders': { direction: 'higher', unit: 'count', of: (k: KpiResult) => measured(k, k.throughput.orders) },\n /* 소요시간 분위수는 `count === 0` 이면 **0 이 아니라 없다.** 이것을 0 으로 읽으면 시간 지표는\n \"낮을수록 좋다\" 라서 **일이 없던 구간이 목표를 완벽히 달성한 것으로** 보고된다 — 미달을 놓치는\n 것보다 나쁘다(고칠 곳이 있는데 잘하고 있다고 말한다). 테스트가 이것을 잡았다. */\n 'leadTime.p50Ms': { direction: 'lower', unit: 'ms', of: (k: KpiResult) => statOf(k.leadTime, d => d.p50Ms) },\n 'leadTime.p90Ms': { direction: 'lower', unit: 'ms', of: (k: KpiResult) => statOf(k.leadTime, d => d.p90Ms) },\n 'workTime.p50Ms': { direction: 'lower', unit: 'ms', of: (k: KpiResult) => statOf(k.workTime, d => d.p50Ms) },\n 'waitTime.p50Ms': { direction: 'lower', unit: 'ms', of: (k: KpiResult) => statOf(k.waitTime, d => d.p50Ms) },\n /*\n * ── 에너지 (2026-08-17) ────────────────────────────────────────────────────\n *\n * 값은 폴드가 이미 내고 있었는데 **목표를 걸 수 있는 지표 목록에 없었다.** 그래서 화면은 「이번 창에\n * 1,240kWh」를 보여 주면서도 그것이 좋은지 나쁜지는 말하지 못했다 — 에너지야말로 계약이라는 딱딱한\n * 기준선이 있는 영역인데도 그랬다.\n *\n * 셋 다 방향은 **낮을수록 좋다**로 못 박는다. 「많이 쓸수록 좋다」는 에너지 목표는 없다. 산출이 늘어\n * 전력량이 느는 경우는 원단위(`energy.intensity`)가 답할 자리이지, 방향을 뒤집을 이유가 아니다.\n *\n * 값이 없는 창(에너지 트윈이 아니거나 계측이 끊긴 창)은 `undefined` 로 남긴다 — 0 으로 읽으면\n * 시간 지표에서 이미 겪은 그 결함이 그대로 재현된다(안 쓴 것이 아니라 못 잰 것인데 만점이 된다).\n */\n 'energy.kWh': { direction: 'lower', unit: 'kWh', of: (k: KpiResult) => k.energy?.kWh },\n /** 최대수요 — 계약 상한을 목표로 적는 자리다(요금의 기본요금 부분이 여기서 정해진다). */\n 'energy.peakKW': { direction: 'lower', unit: 'kW', of: (k: KpiResult) => k.energy?.peakKW },\n /** 원단위 — 완료 오더당 kWh. 답할 수 없으면(`null`) 판정하지 않는다. */\n 'energy.intensity': {\n direction: 'lower',\n unit: 'kWh/order',\n of: (k: KpiResult) => (typeof k.energy?.intensity?.value === 'number' ? k.energy.intensity.value : undefined)\n },\n /** 자원 점유 — 개별 자원이 아니라 **창 전체의 평균 비율**(자원별은 관점 축의 일이다). */\n 'utilization.ratio': {\n direction: 'higher',\n unit: 'ratio',\n of: (k: KpiResult) =>\n k.utilization.length ? k.utilization.reduce((s, u) => s + u.ratio, 0) / k.utilization.length : undefined\n }\n} as const\n\nexport type TwinMetricId = keyof typeof TWIN_METRIC\nexport const isTwinMetric = (id: string): id is TwinMetricId => id in TWIN_METRIC\n\n/**\n * 저장된 목표 행에서 **이 범위에 걸린 목표**를 고른다 — 좁은 선언이 넓은 선언을 덮는다.\n *\n * **왜 순수 함수인가**: 이 규칙(공간 목표 위에 인스턴스 목표를 얹는다)은 지금까지 DB 접근 뒤에 숨어\n * 있어서 **화면을 눌러 봐야만** 확인할 수 있었다. 그런데 이 프로젝트는 로그인 뒤 화면을 자동으로\n * 검증할 방법이 없어, 실제로 여러 기능이 \"실화면 미검증\" 으로 쌓였다. 규칙을 밖으로 빼면 테스트가\n * 값싸진다 — 조회는 행을 가져오기만 하고, **판단은 여기서** 한다.\n */\n/**\n * 축 목표까지 담은 목록 — 전체 목표와 **축의 한 값**에 걸린 목표를 함께 든다.\n *\n * 축 목표는 전체 목표를 **덮지 않는다.** 다른 질문이기 때문이다: \"현장 전체가 100건\" 과\n * \"용접 구역이 30건\" 은 동시에 참일 수 있다. 덮게 만들면 축 하나에 목표를 거는 순간 전체 판정이\n * 사라져 사용자가 \"목표를 지웠나?\" 를 묻게 된다.\n */\nexport interface ScopedTargets {\n /** 창 전체에 걸린 목표. */\n overall: TwinTargetSpec[]\n /** 축 → (그 축의 값 → 목표들). 요청한 축만 채운다. */\n byAxisKey: Record<string, TwinTargetSpec[]>\n}\n\n/**\n * 축의 각 값에 대해 목표 대비 판정 — **그룹 항목에 그대로 붙일 형태.**\n *\n * 그룹의 지표는 전체보다 좁다(주문 수·자원 점유는 축 단위로 뜻이 다르거나 없다). 없는 지표에 목표가\n * 걸려 있으면 **판정하지 않는다** — 여기서 0 으로 읽으면 시간 지표가 \"완벽 달성\" 으로 뒤집힌다\n * (전체 판정에서 이미 겪은 결함이다).\n */\nexport function judgeGroupTargets(\n group: { key: string; tasks: number; leadTime: { count: number; p50Ms: number }; workTime: { count: number; p50Ms: number }; waitTime: { count: number; p50Ms: number } },\n targets: readonly TwinTargetSpec[] | undefined\n): TargetVerdict[] {\n const value = (metric: TwinMetricId): number | undefined => {\n switch (metric) {\n case 'throughput.tasks':\n return group.tasks\n case 'leadTime.p50Ms':\n return group.leadTime.count > 0 ? group.leadTime.p50Ms : undefined\n case 'workTime.p50Ms':\n return group.workTime.count > 0 ? group.workTime.p50Ms : undefined\n case 'waitTime.p50Ms':\n return group.waitTime.count > 0 ? group.waitTime.p50Ms : undefined\n default:\n return undefined // 축 단위로 뜻이 없는 지표(주문 수·자원 점유) — 지어내지 않는다\n }\n }\n const out: TargetVerdict[] = []\n for (const t of targets ?? []) {\n const spec = TWIN_METRIC[t.metric]\n const actual = value(t.metric)\n if (!spec || typeof actual !== 'number' || !Number.isFinite(actual) || !Number.isFinite(t.target)) continue\n out.push(verdictOf(t.metric, actual, t.target, spec.direction))\n }\n return out\n}\n\nexport function mergeTargets(\n rows: readonly TargetRow[],\n scope: { spaceId?: string; instanceIds: readonly string[]; axis?: string }\n): TwinTargetSpec[] {\n return scopedTargets(rows, scope).overall\n}\n\n/** 저장된 목표 행 — 판단에 필요한 것만(엔티티 전체를 요구하지 않는다: 테스트가 값싸진다). */\nexport interface TargetRow {\n metric: string\n target: number\n spaceId?: string | null\n instanceId?: string | null\n axis?: string | null\n axisKey?: string | null\n}\n\n/** 전체 목표와 축 목표를 함께 고른다 — 좁은 선언(인스턴스)이 넓은 선언(공간)을 덮는 규칙은 양쪽 같다. */\nexport function scopedTargets(\n rows: readonly TargetRow[],\n scope: { spaceId?: string; instanceIds: readonly string[]; axis?: string }\n): ScopedTargets {\n /* 같은 자리(지표 또는 축값+지표)에 공간·인스턴스 목표가 함께 있으면 인스턴스가 이긴다. */\n const pick = new Map<string, { target: number; fromInstance: boolean; axisKey?: string; metric: TwinMetricId }>()\n for (const r of rows) {\n if (!isTwinMetric(r.metric) || typeof r.target !== 'number' || !Number.isFinite(r.target)) continue\n const inSpace = !!scope.spaceId && r.spaceId === scope.spaceId\n const inInstance = !!r.instanceId && scope.instanceIds.includes(r.instanceId)\n if (!inSpace && !inInstance) continue\n if (r.axis) {\n if (!scope.axis || r.axis !== scope.axis || !r.axisKey) continue // 지금 보는 축만\n }\n const slot = `${r.axis ?? ''}\\u0000${r.axisKey ?? ''}\\u0000${r.metric}`\n const prev = pick.get(slot)\n if (prev?.fromInstance && !inInstance) continue // 인스턴스 선언을 공간 선언이 덮지 않는다\n pick.set(slot, { target: r.target, fromInstance: inInstance, axisKey: r.axisKey ?? undefined, metric: r.metric })\n }\n const overall: TwinTargetSpec[] = []\n const byAxisKey: Record<string, TwinTargetSpec[]> = {}\n for (const [slot, v] of pick) {\n const spec = { metric: v.metric, target: v.target }\n if (slot.startsWith('\\u0000')) overall.push(spec)\n else (byAxisKey[v.axisKey as string] ??= []).push(spec)\n }\n return { overall, byAxisKey }\n}\n\n/** 목표 하나 — 어느 지표를, 얼마로. 방향은 지표가 안다(§TWIN_METRIC). */\nexport interface TwinTargetSpec {\n metric: TwinMetricId\n target: number\n}\n\nexport interface TargetVerdict {\n metric: TwinMetricId\n /** 이 구간의 실제 값. */\n actual: number\n target: number\n direction: 'higher' | 'lower'\n /** 목표를 만족했나. */\n met: boolean\n /**\n * 목표 대비 비율 — **항상 \"1 이상이면 좋다\"** 로 정규화한다(방향이 다른 지표를 한 화면에서 비교할 수\n * 있게). 처리량은 `actual/target`, 시간은 `target/actual`.\n */\n ratio: number\n}\n\n/**\n * 목표 대비 판정 — **목표가 없는 지표는 판정하지 않는다.**\n *\n * 값이 없는 지표(그 구간에 기록이 없어 `undefined`)도 판정하지 않는다. 없는 것을 0 으로 놓으면\n * \"목표 크게 미달\" 이라는 **없는 사실**이 만들어지고, 사용자는 멈춘 적 없는 라인을 고치러 간다.\n * 목표를 0 으로 준 경우도 비율을 내지 않는다(0 으로 나누지 않는다) — 만족 여부만 답한다.\n */\nexport function judgeTargets(kpi: KpiResult, targets: readonly TwinTargetSpec[] | undefined): TargetVerdict[] {\n const out: TargetVerdict[] = []\n for (const t of targets ?? []) {\n const spec = TWIN_METRIC[t.metric]\n if (!spec) continue // 모르는 지표 이름 — 짐작해 매핑하지 않는다\n const actual = spec.of(kpi)\n if (typeof actual !== 'number' || !Number.isFinite(actual)) continue // 값이 없다 ≠ 값이 0\n if (!Number.isFinite(t.target)) continue\n out.push(verdictOf(t.metric, actual, t.target, spec.direction))\n }\n return out\n}\n\n/** 판정 산수 한 곳 — 전체 판정과 축 판정이 **같은 규칙**을 쓴다(두 벌이면 어긋난다). */\nfunction verdictOf(metric: TwinMetricId, actual: number, target: number, direction: 'higher' | 'lower'): TargetVerdict {\n const met = direction === 'higher' ? actual >= target : actual <= target\n const ratio = direction === 'higher' ? (target > 0 ? actual / target : Number.NaN) : actual > 0 ? target / actual : Number.NaN\n return { metric, actual, target, direction, met, ratio }\n}\n"]}
@@ -0,0 +1,25 @@
1
+ /** 지금 피드가 붙어 있는 인스턴스 id 들 — 그 계층의 장부가 답한다. */
2
+ export type LiveFeedProbe = () => string[];
3
+ /** 피드 장부를 등록한다(모듈 로드 시 한 번). */
4
+ export declare function registerLiveFeedProbe(probe: LiveFeedProbe): void;
5
+ /**
6
+ * 피드가 붙어 있나 — 어느 장부든 그렇다고 하면 그렇다.
7
+ *
8
+ * 장부가 터지는 것을 이 판정 전체의 실패로 만들지 않는다: 한 계층이 답하지 못해도 다른 계층의 사실은
9
+ * 그대로 참이다. 다만 **조용히 넘기지 않는다** — 답하지 못한 장부는 로그에 남긴다.
10
+ */
11
+ export declare function liveFeedAttached(instanceId: string): boolean;
12
+ /**
13
+ * 이 트윈의 **피드 사실** — 화면이 그대로 말할 수 있는 세 값.
14
+ *
15
+ * `not-applicable` 을 `detached` 와 섞지 않는다(이 시스템이 내내 지켜 온 「없음 ≠ 해당 없음」):
16
+ * · 시뮬 트윈에는 나를 피드가 **없어야 한다** — 그건 결함이 아니라 그 트윈의 성질이다.
17
+ * · 멈춘 트윈에게 피드를 묻는 것은 뜻이 없다 — 사람이 멈춰 둔 것이다.
18
+ * · 남는 것 하나가 우리가 보이려던 사실이다: **도는 미러인데 아무것도 오지 않는다.**
19
+ */
20
+ export type LiveFeedState = 'attached' | 'detached' | 'not-applicable';
21
+ export declare function liveFeedStateOf(args: {
22
+ realityMode?: string;
23
+ running?: boolean;
24
+ instanceId: string;
25
+ }): LiveFeedState;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /*
3
+ * 「이 트윈에 계측이 들어오고 있나」 — **그 사실을 한 자리에서 묻는다.**
4
+ *
5
+ * ── 무엇이 보이지 않았나 (2026-08-14) ───────────────────────────────────────
6
+ * 미러 트윈은 커널이 돌기만 하면 「도는 중」이라 보고했다. 그런데 계측을 나르는 피드가 떨어져 있으면
7
+ * 그 트윈은 **아무것도 받지 않으면서 도는 중**이다 — 화면에서 두 상태가 똑같이 보였고, 사람은
8
+ * 「값이 안 변하네」로 뒤늦게 알게 됐다(재기동 뒤 실제로 그랬다).
9
+ *
10
+ * ── 왜 장부가 하나여야 하나 ────────────────────────────────────────────────
11
+ * 피드를 여는 곳이 둘이다: 커넥터 기반(`reference-live`)과 프리셋 기반(앱의 `provision-from-preset`).
12
+ * 한쪽만 보고 「끊김」이라 말하면 **멀쩡히 흐르는 트윈을 끊겼다고 부르게 된다** — 거짓 경보는 참 경보를
13
+ * 죽인다. 그래서 각 계층이 자기 장부를 여기 **등록**하고, 묻는 쪽은 이 한 자리에만 묻는다.
14
+ *
15
+ * 호스트는 어떤 어댑터·프리셋이 있는지 몰라도 된다(계층을 거꾸로 잇지 않는다).
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.registerLiveFeedProbe = registerLiveFeedProbe;
19
+ exports.liveFeedAttached = liveFeedAttached;
20
+ exports.liveFeedStateOf = liveFeedStateOf;
21
+ const probes = [];
22
+ /** 피드 장부를 등록한다(모듈 로드 시 한 번). */
23
+ function registerLiveFeedProbe(probe) {
24
+ probes.push(probe);
25
+ }
26
+ /**
27
+ * 피드가 붙어 있나 — 어느 장부든 그렇다고 하면 그렇다.
28
+ *
29
+ * 장부가 터지는 것을 이 판정 전체의 실패로 만들지 않는다: 한 계층이 답하지 못해도 다른 계층의 사실은
30
+ * 그대로 참이다. 다만 **조용히 넘기지 않는다** — 답하지 못한 장부는 로그에 남긴다.
31
+ */
32
+ function liveFeedAttached(instanceId) {
33
+ for (const probe of probes) {
34
+ try {
35
+ if (probe().includes(instanceId))
36
+ return true;
37
+ }
38
+ catch (e) {
39
+ console.warn('[twin-live] a feed registry could not answer —', e?.message ?? e);
40
+ }
41
+ }
42
+ return false;
43
+ }
44
+ function liveFeedStateOf(args) {
45
+ if (args.realityMode !== 'mirror')
46
+ return 'not-applicable';
47
+ if (!args.running)
48
+ return 'not-applicable';
49
+ return liveFeedAttached(args.instanceId) ? 'attached' : 'detached';
50
+ }
51
+ //# sourceMappingURL=live-feed-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-feed-registry.js","sourceRoot":"","sources":["../../server/engine/live-feed-registry.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AAQH,sDAEC;AAQD,4CASC;AAYD,0CAIC;AAtCD,MAAM,MAAM,GAAoB,EAAE,CAAA;AAElC,gCAAgC;AAChC,SAAgB,qBAAqB,CAAC,KAAoB;IACxD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACpB,CAAC;AAED;;;;;GAKG;AACH,SAAgB,gBAAgB,CAAC,UAAkB;IACjD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAAE,OAAO,IAAI,CAAA;QAC/C,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,gDAAgD,EAAE,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAA;QACjF,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAYD,SAAgB,eAAe,CAAC,IAAqE;IACnG,IAAI,IAAI,CAAC,WAAW,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAA;IAC1D,IAAI,CAAC,IAAI,CAAC,OAAO;QAAE,OAAO,gBAAgB,CAAA;IAC1C,OAAO,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAA;AACpE,CAAC","sourcesContent":["/*\n * 「이 트윈에 계측이 들어오고 있나」 — **그 사실을 한 자리에서 묻는다.**\n *\n * ── 무엇이 보이지 않았나 (2026-08-14) ───────────────────────────────────────\n * 미러 트윈은 커널이 돌기만 하면 「도는 중」이라 보고했다. 그런데 계측을 나르는 피드가 떨어져 있으면\n * 그 트윈은 **아무것도 받지 않으면서 도는 중**이다 — 화면에서 두 상태가 똑같이 보였고, 사람은\n * 「값이 안 변하네」로 뒤늦게 알게 됐다(재기동 뒤 실제로 그랬다).\n *\n * ── 왜 장부가 하나여야 하나 ────────────────────────────────────────────────\n * 피드를 여는 곳이 둘이다: 커넥터 기반(`reference-live`)과 프리셋 기반(앱의 `provision-from-preset`).\n * 한쪽만 보고 「끊김」이라 말하면 **멀쩡히 흐르는 트윈을 끊겼다고 부르게 된다** — 거짓 경보는 참 경보를\n * 죽인다. 그래서 각 계층이 자기 장부를 여기 **등록**하고, 묻는 쪽은 이 한 자리에만 묻는다.\n *\n * 호스트는 어떤 어댑터·프리셋이 있는지 몰라도 된다(계층을 거꾸로 잇지 않는다).\n */\n\n/** 지금 피드가 붙어 있는 인스턴스 id 들 — 그 계층의 장부가 답한다. */\nexport type LiveFeedProbe = () => string[]\n\nconst probes: LiveFeedProbe[] = []\n\n/** 피드 장부를 등록한다(모듈 로드 시 한 번). */\nexport function registerLiveFeedProbe(probe: LiveFeedProbe): void {\n probes.push(probe)\n}\n\n/**\n * 피드가 붙어 있나 — 어느 장부든 그렇다고 하면 그렇다.\n *\n * 장부가 터지는 것을 이 판정 전체의 실패로 만들지 않는다: 한 계층이 답하지 못해도 다른 계층의 사실은\n * 그대로 참이다. 다만 **조용히 넘기지 않는다** — 답하지 못한 장부는 로그에 남긴다.\n */\nexport function liveFeedAttached(instanceId: string): boolean {\n for (const probe of probes) {\n try {\n if (probe().includes(instanceId)) return true\n } catch (e: any) {\n console.warn('[twin-live] a feed registry could not answer —', e?.message ?? e)\n }\n }\n return false\n}\n\n/**\n * 이 트윈의 **피드 사실** — 화면이 그대로 말할 수 있는 세 값.\n *\n * `not-applicable` 을 `detached` 와 섞지 않는다(이 시스템이 내내 지켜 온 「없음 ≠ 해당 없음」):\n * · 시뮬 트윈에는 나를 피드가 **없어야 한다** — 그건 결함이 아니라 그 트윈의 성질이다.\n * · 멈춘 트윈에게 피드를 묻는 것은 뜻이 없다 — 사람이 멈춰 둔 것이다.\n * · 남는 것 하나가 우리가 보이려던 사실이다: **도는 미러인데 아무것도 오지 않는다.**\n */\nexport type LiveFeedState = 'attached' | 'detached' | 'not-applicable'\n\nexport function liveFeedStateOf(args: { realityMode?: string; running?: boolean; instanceId: string }): LiveFeedState {\n if (args.realityMode !== 'mirror') return 'not-applicable'\n if (!args.running) return 'not-applicable'\n return liveFeedAttached(args.instanceId) ? 'attached' : 'detached'\n}\n"]}
@@ -0,0 +1,181 @@
1
+ /** 작업 이름 — 새 작업을 재기 시작하면 여기 늘린다(문자열을 자유롭게 쓰지 않는다: 오타가 새 칸이 된다). */
2
+ export declare const LOAD_PHASES: readonly ["tick", "snapshot", "deltas", "publish", "journal", "ingest", "forecast", "forkTick"];
3
+ export type LoadPhase = (typeof LOAD_PHASES)[number];
4
+ export interface PhaseStats {
5
+ /** 잰 횟수. */
6
+ count: number;
7
+ /** 마지막 소요(ms). */
8
+ lastMs: number;
9
+ /** 가장 오래 걸린 것(ms) — 평균은 순간 정체를 가린다. */
10
+ maxMs: number;
11
+ /** 소요 합(ms). 나눗셈은 읽는 쪽에서 한다. */
12
+ totalMs: number;
13
+ }
14
+ /** 창(window) 하나의 누적 — "지금 얼마나 쓰고 있나" 를 답한다. */
15
+ interface Window {
16
+ /** 창이 열린 시각. **아직 안 열렸으면 `-1`** — 0 으로 두면 시각 0 과 구별이 안 된다(검사가 잡았다). */
17
+ startMs: number;
18
+ phases: Partial<Record<LoadPhase, PhaseStats>>;
19
+ }
20
+ export interface LoadMeter {
21
+ /** **누적** — 프로세스가 뜬 뒤 전부. 총량과 최악값을 본다. */
22
+ phases: Partial<Record<LoadPhase, PhaseStats>>;
23
+ /** 지금 쌓고 있는 창. */
24
+ window: Window;
25
+ /** **마지막으로 닫힌 창** — 이것이 "실시간" 이다. 없으면 아직 한 창도 안 지났다. */
26
+ recent: (Window & {
27
+ endMs: number;
28
+ }) | null;
29
+ /** 이 트윈에서 만들어진 fork 수(누적) — 예측 한 번이 수십 개를 만든다. */
30
+ forksCreated: number;
31
+ /** 한 주기(틱)가 예산을 넘긴 횟수. */
32
+ overBudget: number;
33
+ /**
34
+ * 마지막 경고 시각(ms). **한 번도 안 했으면 `-Infinity`** — 0 으로 두면 "0 시점에 경고했다" 가 되어
35
+ * 첫 느린 틱이 조용히 지나간다(검사가 그것을 잡았다).
36
+ */
37
+ lastWarnMs: number;
38
+ }
39
+ export declare function newLoadMeter(): LoadMeter;
40
+ /**
41
+ * 예산 — 틱 간격의 절반. 넘으면 루프의 절반을 이 트윈 하나가 쓰고 있다는 뜻이다.
42
+ *
43
+ * 절반인 이유: 트윈이 여럿이라 각자 간격을 다 쓰면 이미 포화다. 절반에서 알아야 손 쓸 틈이 있다.
44
+ */
45
+ export declare const TICK_BUDGET_RATIO = 0.5;
46
+ /** 경고를 다시 낼 때까지의 최소 간격(ms) — 느린 트윈은 매 틱 느리므로, 그대로 두면 로그가 원인을 덮는다. */
47
+ export declare const WARN_COOLDOWN_MS = 30000;
48
+ /**
49
+ * 한 작업의 소요를 기록한다. **누적만 하고 판단하지 않는다.**
50
+ *
51
+ * 음수·NaN 은 기록하지 않는다(시계가 뒤로 간 경우). 없는 값을 0 으로 세면 평균이 거짓이 된다.
52
+ */
53
+ export declare function recordPhase(meter: LoadMeter, phase: LoadPhase, tookMs: number, nowMs?: number): void;
54
+ /** fork 하나가 만들어졌다 — 예측 한 번이 수십 개를 만든다. 수를 알아야 부하의 출처를 짚는다. */
55
+ export declare function recordFork(meter: LoadMeter, count?: number): void;
56
+ /** 실시간 창의 길이(ms) — 이보다 길게 보려면 누적을 본다. */
57
+ export declare const WINDOW_MS = 10000;
58
+ /** 창이 다 찼으면 닫고 새로 연다. **닫힌 창만 "최근" 으로 읽는다** — 채우는 중인 창은 과소 집계다. */
59
+ export declare function rollWindow(meter: LoadMeter, nowMs: number, windowMs?: number): void;
60
+ /**
61
+ * 이 주기가 예산을 넘었나 — 넘었으면 세고, **말할 때인지**까지 답한다.
62
+ */
63
+ export declare function judgeCycle(meter: LoadMeter, tookMs: number, tickMs: number, nowMs: number, budgetRatio?: number, cooldownMs?: number): {
64
+ over: boolean;
65
+ warn: boolean;
66
+ budgetMs: number;
67
+ };
68
+ export interface PhaseRow {
69
+ phase: LoadPhase;
70
+ count: number;
71
+ lastMs: number;
72
+ maxMs: number;
73
+ avgMs: number;
74
+ totalMs: number;
75
+ /** 잰 작업 전체 시간 중 이 작업의 몫(0~1). 어디를 고칠지 이 값이 정한다. */
76
+ share: number;
77
+ }
78
+ export interface LoadView {
79
+ phases: PhaseRow[];
80
+ measuredTotalMs: number;
81
+ /** 이 구간의 길이(ms). 누적은 프로세스 기동 이후라 알 수 없으므로 `null`. */
82
+ spanMs: number | null;
83
+ /** 구간 중 몇 %를 이 트윈이 썼나(0~1+). 구간을 모르면 `null` — **0 이 아니다.** */
84
+ busyRatio: number | null;
85
+ }
86
+ /**
87
+ * 읽기용 요약 — **실시간과 누적을 함께** 낸다.
88
+ *
89
+ * · `recent` — 마지막으로 **닫힌** 창. 지금 이 트윈이 루프를 얼마나 쓰고 있나. 채우는 중인 창은
90
+ * 과소 집계라 쓰지 않는다.
91
+ * · `total` — 기동 이후 전부. 총량과 최악값.
92
+ *
93
+ * 잰 적이 없으면 **`null` 이다.** 0 으로 채우면 "0ms 다"(= 아주 빠르다)로 읽혀, 재지 않은 것과 정반대의
94
+ * 뜻이 된다. 몫(`share`)도 잰 작업들 사이의 비율일 뿐 — **재지 않은 작업은 분모에 없다.**
95
+ */
96
+ export declare function loadSummary(meter: LoadMeter | undefined, tickMs: number): {
97
+ recent: LoadView | null;
98
+ total: LoadView | null;
99
+ forksCreated: number;
100
+ overBudget: number;
101
+ budgetMs: number;
102
+ /** 마지막 틱이 간격을 얼마나 썼나(0~1+). 1 이면 간격을 통째로 쓴다. */
103
+ loadRatio: number | null;
104
+ } | null;
105
+ /**
106
+ * 경고 한 줄 — **무엇이 느린지까지 말한다.** "틱이 800ms" 만으로는 어디를 볼지 알 수 없다.
107
+ *
108
+ * 가장 무거운 작업 셋을 몫과 함께 적는다.
109
+ */
110
+ export declare function slowTickMessage(instanceId: string, tookMs: number, budgetMs: number, meter: LoadMeter): string;
111
+ /**
112
+ * 전체 한눈에 — **누가 루프를 점유하고 있나.**
113
+ *
114
+ * 인스턴스 목록을 받아 대수·모드별 수·fork 총수를 세고, **최근 창의 부하가 큰 순서**로 줄을 세운다.
115
+ * 누적이 아니라 최근으로 세우는 이유: 지금 아픈 것을 먼저 봐야 한다(누적 1위는 오래 떠 있던 것일 뿐일
116
+ * 수 있다).
117
+ */
118
+ export declare function fleetLoad(rows: {
119
+ instanceId: string;
120
+ mode: string;
121
+ domainId?: string;
122
+ domainLabel?: string;
123
+ meter?: LoadMeter;
124
+ }[], tickMs: number): {
125
+ instances: number;
126
+ byMode: Record<string, number>;
127
+ /**
128
+ * **테넌트별 몫** — 운영자가 먼저 묻는 것은 "어느 테넌트가 루프를 먹나" 다.
129
+ *
130
+ * 실측으로 필요해졌다: 한 도메인 트윈 22개가 CPU 를 110% 쓰는 동안 다른 도메인 화면은 0 이었다.
131
+ * 무거운 순으로 낸다. 몫은 **잰 트윈들 사이의 비율**이다(안 잰 트윈은 분모에 없다).
132
+ */
133
+ byDomain: {
134
+ domainId: string;
135
+ label: string;
136
+ instances: number;
137
+ recentBusyMs: number;
138
+ share: number;
139
+ }[];
140
+ forksCreated: number;
141
+ /** 최근 창에서 트윈들이 쓴 시간의 합(ms) — 창 길이를 넘으면 루프가 이미 밀리고 있다. */
142
+ recentBusyMs: number;
143
+ /** 그 창의 길이(ms). 창이 하나도 안 닫혔으면 `null`. */
144
+ recentSpanMs: number | null;
145
+ /** 전체가 루프를 얼마나 쓰나(0~1+). 1 을 넘으면 **한 틱 안에 다 못 끝낸다.** */
146
+ fleetBusyRatio: number | null;
147
+ slowest: {
148
+ instanceId: string;
149
+ mode: string;
150
+ domainId?: string;
151
+ domainLabel?: string;
152
+ recentBusyMs: number;
153
+ topPhase: LoadPhase | null;
154
+ overBudget: number;
155
+ }[];
156
+ };
157
+ /**
158
+ * 프로세스가 실제로 쓴 CPU — **계기판의 다른 숫자들과 뜻이 다르다.**
159
+ *
160
+ * 이 파일의 다른 값들은 트윈의 **계측된 8개 작업**만 잰 것이다. 그런데 프로세스는 그 밖에도 일한다:
161
+ * GraphQL·TypeORM·GC·webpack·HTTP. 그래서 "루프 점유 <1%" 인데 CPU 는 110% 일 수 있고, 실제로 그런
162
+ * 상태를 겪었다. 두 숫자를 나란히 두면 **트윈이 범인인지 아닌지**를 바로 가릴 수 있다.
163
+ *
164
+ * 코어 여러 개를 쓰면 1 을 넘을 수 있다(운영체제의 `%CPU` 와 같은 뜻).
165
+ */
166
+ export type CpuSampler = {
167
+ lastMicros: number;
168
+ lastMs: number;
169
+ };
170
+ /** 첫 표본은 비교 대상이 없다 — `-1` 로 두어 "아직 모른다" 와 0 을 구별한다. */
171
+ export declare function newCpuSampler(): CpuSampler;
172
+ /**
173
+ * 두 표본 사이의 CPU 점유율. 처음 부르면 `null`(0 이 아니다 — 잰 구간이 없다).
174
+ *
175
+ * @param totalMicros `process.cpuUsage()` 의 user + system (마이크로초 누적)
176
+ */
177
+ export declare function sampleCpu(s: CpuSampler, totalMicros: number, nowMs: number): {
178
+ ratio: number;
179
+ spanMs: number;
180
+ } | null;
181
+ export {};