@things-factory/headless-twin 10.0.12 → 10.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +57 -2
  2. package/dist-server/engine/canonical-ingest.js +43 -2
  3. package/dist-server/engine/canonical-ingest.js.map +1 -1
  4. package/dist-server/engine/command-routing.js +1 -1
  5. package/dist-server/engine/command-routing.js.map +1 -1
  6. package/dist-server/engine/index.d.ts +3 -0
  7. package/dist-server/engine/index.js +4 -0
  8. package/dist-server/engine/index.js.map +1 -1
  9. package/dist-server/engine/ingest-health.d.ts +335 -0
  10. package/dist-server/engine/ingest-health.js +434 -0
  11. package/dist-server/engine/ingest-health.js.map +1 -0
  12. package/dist-server/engine/integration-coverage.d.ts +76 -0
  13. package/dist-server/engine/integration-coverage.js +73 -0
  14. package/dist-server/engine/integration-coverage.js.map +1 -0
  15. package/dist-server/engine/integration-probes.d.ts +65 -0
  16. package/dist-server/engine/integration-probes.js +100 -0
  17. package/dist-server/engine/integration-probes.js.map +1 -0
  18. package/dist-server/engine/integration-runner.d.ts +45 -0
  19. package/dist-server/engine/integration-runner.js +59 -0
  20. package/dist-server/engine/integration-runner.js.map +1 -0
  21. package/dist-server/engine/integration-target-profile.d.ts +57 -0
  22. package/dist-server/engine/integration-target-profile.js +79 -0
  23. package/dist-server/engine/integration-target-profile.js.map +1 -0
  24. package/dist-server/engine/kpi-fold.d.ts +27 -0
  25. package/dist-server/engine/kpi-fold.js +47 -4
  26. package/dist-server/engine/kpi-fold.js.map +1 -1
  27. package/dist-server/engine/kpi-query.js +128 -14
  28. package/dist-server/engine/kpi-query.js.map +1 -1
  29. package/dist-server/engine/live-feed-registry.js +2 -16
  30. package/dist-server/engine/live-feed-registry.js.map +1 -1
  31. package/dist-server/engine/load-meter.d.ts +1 -1
  32. package/dist-server/engine/load-meter.js +12 -3
  33. package/dist-server/engine/load-meter.js.map +1 -1
  34. package/dist-server/engine/log.d.ts +18 -0
  35. package/dist-server/engine/log.js +80 -0
  36. package/dist-server/engine/log.js.map +1 -0
  37. package/dist-server/engine/loop-lag.d.ts +54 -0
  38. package/dist-server/engine/loop-lag.js +87 -0
  39. package/dist-server/engine/loop-lag.js.map +1 -0
  40. package/dist-server/engine/model-gap.d.ts +108 -0
  41. package/dist-server/engine/model-gap.js +95 -0
  42. package/dist-server/engine/model-gap.js.map +1 -0
  43. package/dist-server/engine/restart-policy.d.ts +3 -3
  44. package/dist-server/engine/restart-policy.js +4 -4
  45. package/dist-server/engine/restart-policy.js.map +1 -1
  46. package/dist-server/engine/runtime-key.js +1 -1
  47. package/dist-server/engine/runtime-key.js.map +1 -1
  48. package/dist-server/engine/stage-path.d.ts +83 -0
  49. package/dist-server/engine/stage-path.js +118 -0
  50. package/dist-server/engine/stage-path.js.map +1 -0
  51. package/dist-server/engine/twin-engine.d.ts +352 -16
  52. package/dist-server/engine/twin-engine.js +1037 -166
  53. package/dist-server/engine/twin-engine.js.map +1 -1
  54. package/dist-server/index.js +34 -2
  55. package/dist-server/index.js.map +1 -1
  56. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.d.ts +5 -0
  57. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +58 -0
  58. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -0
  59. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.d.ts +5 -0
  60. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +55 -0
  61. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -0
  62. package/dist-server/migrations/index.js +7 -1
  63. package/dist-server/migrations/index.js.map +1 -1
  64. package/dist-server/service/index.d.ts +5 -2
  65. package/dist-server/service/index.js +18 -7
  66. package/dist-server/service/index.js.map +1 -1
  67. package/dist-server/service/reference/discovery-result.d.ts +1 -1
  68. package/dist-server/service/reference/discovery-result.js +1 -1
  69. package/dist-server/service/reference/discovery-result.js.map +1 -1
  70. package/dist-server/service/reference/index.d.ts +4 -1
  71. package/dist-server/service/reference/index.js +6 -1
  72. package/dist-server/service/reference/index.js.map +1 -1
  73. package/dist-server/service/reference/reference-adapter.d.ts +184 -3
  74. package/dist-server/service/reference/reference-adapter.js +28 -4
  75. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  76. package/dist-server/service/reference/reference-assessment.d.ts +68 -0
  77. package/dist-server/service/reference/reference-assessment.js +136 -0
  78. package/dist-server/service/reference/reference-assessment.js.map +1 -0
  79. package/dist-server/service/reference/reference-live.d.ts +12 -1
  80. package/dist-server/service/reference/reference-live.js +116 -11
  81. package/dist-server/service/reference/reference-live.js.map +1 -1
  82. package/dist-server/service/reference/reference-master.d.ts +47 -1
  83. package/dist-server/service/reference/reference-master.js +34 -4
  84. package/dist-server/service/reference/reference-master.js.map +1 -1
  85. package/dist-server/service/reference/reference-probe.d.ts +92 -0
  86. package/dist-server/service/reference/reference-probe.js +186 -0
  87. package/dist-server/service/reference/reference-probe.js.map +1 -0
  88. package/dist-server/service/reference/reference-progress-subscription.d.ts +17 -0
  89. package/dist-server/service/reference/reference-progress-subscription.js +94 -0
  90. package/dist-server/service/reference/reference-progress-subscription.js.map +1 -0
  91. package/dist-server/service/reference/reference-progress.d.ts +38 -0
  92. package/dist-server/service/reference/reference-progress.js +71 -0
  93. package/dist-server/service/reference/reference-progress.js.map +1 -0
  94. package/dist-server/service/reference/reference-resolver.js +210 -43
  95. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  96. package/dist-server/service/reference/twin-reference.d.ts +32 -0
  97. package/dist-server/service/reference/twin-reference.js +10 -0
  98. package/dist-server/service/reference/twin-reference.js.map +1 -1
  99. package/dist-server/service/twin-control/twin-control-mutation.js +4 -3
  100. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  101. package/dist-server/service/twin-event/journal-count.d.ts +21 -0
  102. package/dist-server/service/twin-event/journal-count.js +26 -0
  103. package/dist-server/service/twin-event/journal-count.js.map +1 -0
  104. package/dist-server/service/twin-event/twin-event-keys.d.ts +9 -0
  105. package/dist-server/service/twin-event/twin-event-keys.js +15 -18
  106. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  107. package/dist-server/service/twin-event/twin-event-type.d.ts +3 -1
  108. package/dist-server/service/twin-event/twin-event-type.js +18 -1
  109. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  110. package/dist-server/service/twin-event/twin-event.d.ts +1 -0
  111. package/dist-server/service/twin-event/twin-event.js +9 -1
  112. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  113. package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
  114. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  115. package/dist-server/service/twin-ingest-window/index.d.ts +5 -0
  116. package/dist-server/service/twin-ingest-window/index.js +10 -0
  117. package/dist-server/service/twin-ingest-window/index.js.map +1 -0
  118. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +12 -0
  119. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +123 -0
  120. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -0
  121. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.d.ts +1 -0
  122. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +51 -0
  123. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -0
  124. package/dist-server/service/twin-ingest-window/twin-ingest-window.d.ts +16 -0
  125. package/dist-server/service/twin-ingest-window/twin-ingest-window.js +108 -0
  126. package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +1 -0
  127. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  128. package/dist-server/service/twin-journal/twin-journal-query.js +58 -5
  129. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  130. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +14 -0
  131. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +72 -2
  132. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  133. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  134. package/dist-server/service/twin-metrics/twin-metrics-query.js +78 -3
  135. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  136. package/dist-server/service/twin-model/axis-journal-evidence.d.ts +2 -0
  137. package/dist-server/service/twin-model/axis-journal-evidence.js +89 -0
  138. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -0
  139. package/dist-server/service/twin-model/epcis-coverage.d.ts +1 -1
  140. package/dist-server/service/twin-model/epcis-coverage.js +19 -7
  141. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  142. package/dist-server/service/twin-model/iec61850-coverage.d.ts +1 -1
  143. package/dist-server/service/twin-model/iec61850-coverage.js +18 -7
  144. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  145. package/dist-server/service/twin-model/isa95-coverage.d.ts +14 -0
  146. package/dist-server/service/twin-model/isa95-coverage.js +23 -9
  147. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  148. package/dist-server/service/twin-model/item-ref.d.ts +24 -0
  149. package/dist-server/service/twin-model/item-ref.js +88 -0
  150. package/dist-server/service/twin-model/item-ref.js.map +1 -0
  151. package/dist-server/service/twin-model/name-index.d.ts +36 -0
  152. package/dist-server/service/twin-model/name-index.js +116 -0
  153. package/dist-server/service/twin-model/name-index.js.map +1 -0
  154. package/dist-server/service/twin-model/project-structure.js +1 -1
  155. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  156. package/dist-server/service/twin-model/status-tally.d.ts +9 -0
  157. package/dist-server/service/twin-model/status-tally.js +37 -0
  158. package/dist-server/service/twin-model/status-tally.js.map +1 -0
  159. package/dist-server/service/twin-model/twin-lineage-query.js +40 -12
  160. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  161. package/dist-server/service/twin-model/twin-model-item-query.js +38 -39
  162. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  163. package/dist-server/service/twin-model/twin-model-query.js +156 -4
  164. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  165. package/dist-server/service/twin-model/twin-model-tree-query.js +7 -0
  166. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  167. package/dist-server/service/twin-readiness/index.d.ts +2 -0
  168. package/dist-server/service/twin-readiness/index.js +6 -0
  169. package/dist-server/service/twin-readiness/index.js.map +1 -0
  170. package/dist-server/service/twin-readiness/twin-readiness-query.d.ts +3 -0
  171. package/dist-server/service/twin-readiness/twin-readiness-query.js +103 -0
  172. package/dist-server/service/twin-readiness/twin-readiness-query.js.map +1 -0
  173. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  174. package/dist-server/service/twin-space/twin-space-resolver.js +5 -4
  175. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  176. package/dist-shared/entity-delta.d.ts +23 -3
  177. package/dist-shared/entity-delta.js +12 -8
  178. package/dist-shared/entity-delta.js.map +1 -1
  179. package/dist-shared/kpi-broadcast.js +1 -1
  180. package/dist-shared/kpi-broadcast.js.map +1 -1
  181. package/dist-shared/touched-items.d.ts +7 -0
  182. package/dist-shared/touched-items.js +80 -0
  183. package/dist-shared/touched-items.js.map +1 -0
  184. package/package.json +7 -7
  185. package/server/engine/canonical-ingest.ts +92 -7
  186. package/server/engine/command-routing.ts +1 -1
  187. package/server/engine/index.ts +4 -0
  188. package/server/engine/ingest-health.ts +704 -0
  189. package/server/engine/integration-coverage.ts +147 -0
  190. package/server/engine/integration-probes.ts +144 -0
  191. package/server/engine/integration-runner.ts +95 -0
  192. package/server/engine/integration-target-profile.ts +103 -0
  193. package/server/engine/kpi-fold.ts +72 -4
  194. package/server/engine/kpi-query.ts +129 -15
  195. package/server/engine/live-feed-registry.ts +2 -1
  196. package/server/engine/load-meter.ts +12 -3
  197. package/server/engine/log.ts +72 -0
  198. package/server/engine/loop-lag.ts +120 -0
  199. package/server/engine/model-gap.ts +168 -0
  200. package/server/engine/restart-policy.ts +4 -4
  201. package/server/engine/runtime-key.ts +1 -1
  202. package/server/engine/stage-path.ts +172 -0
  203. package/server/engine/twin-engine.ts +1135 -162
  204. package/server/index.ts +35 -2
  205. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +54 -0
  206. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +53 -0
  207. package/server/migrations/index.ts +7 -1
  208. package/server/service/index.ts +11 -0
  209. package/server/service/reference/discovery-result.ts +1 -1
  210. package/server/service/reference/index.ts +6 -1
  211. package/server/service/reference/reference-adapter.ts +197 -5
  212. package/server/service/reference/reference-assessment.ts +215 -0
  213. package/server/service/reference/reference-live.ts +123 -11
  214. package/server/service/reference/reference-master.ts +64 -4
  215. package/server/service/reference/reference-probe.ts +264 -0
  216. package/server/service/reference/reference-progress-subscription.ts +73 -0
  217. package/server/service/reference/reference-progress.ts +95 -0
  218. package/server/service/reference/reference-resolver.ts +200 -12
  219. package/server/service/reference/twin-reference.ts +39 -1
  220. package/server/service/twin-control/twin-control-mutation.ts +4 -3
  221. package/server/service/twin-event/journal-count.ts +53 -0
  222. package/server/service/twin-event/twin-event-keys.ts +16 -1
  223. package/server/service/twin-event/twin-event-type.ts +26 -2
  224. package/server/service/twin-event/twin-event.ts +7 -0
  225. package/server/service/twin-forecast/twin-forecast-query.ts +2 -1
  226. package/server/service/twin-ingest-window/index.ts +7 -0
  227. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +125 -0
  228. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +57 -0
  229. package/server/service/twin-ingest-window/twin-ingest-window.ts +113 -0
  230. package/server/service/twin-instance/twin-instance.ts +1 -1
  231. package/server/service/twin-journal/twin-journal-query.ts +59 -5
  232. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +77 -3
  233. package/server/service/twin-metrics/twin-metrics-query.ts +81 -3
  234. package/server/service/twin-model/axis-journal-evidence.ts +57 -0
  235. package/server/service/twin-model/epcis-coverage.ts +2 -8
  236. package/server/service/twin-model/iec61850-coverage.ts +2 -8
  237. package/server/service/twin-model/isa95-coverage.ts +22 -9
  238. package/server/service/twin-model/item-ref.ts +80 -0
  239. package/server/service/twin-model/name-index.ts +94 -0
  240. package/server/service/twin-model/project-structure.ts +1 -1
  241. package/server/service/twin-model/status-tally.ts +37 -0
  242. package/server/service/twin-model/twin-lineage-query.ts +38 -9
  243. package/server/service/twin-model/twin-model-item-query.ts +26 -27
  244. package/server/service/twin-model/twin-model-query.ts +155 -4
  245. package/server/service/twin-model/twin-model-tree-query.ts +7 -0
  246. package/server/service/twin-readiness/index.ts +3 -0
  247. package/server/service/twin-readiness/twin-readiness-query.ts +96 -0
  248. package/server/service/twin-space/twin-space-resolver.ts +5 -4
  249. package/shared/entity-delta.ts +31 -7
  250. package/shared/kpi-broadcast.ts +1 -1
  251. package/shared/touched-items.ts +73 -0
  252. package/test/axis-journal-evidence.test.ts +71 -0
  253. package/test/axis-read.test.ts +101 -1
  254. package/test/boot-resume.test.ts +92 -26
  255. package/test/broadcast-cost-baseline.test.ts +200 -0
  256. package/test/broadcast-period.test.ts +58 -0
  257. package/test/canonical-ingest-vocabularies.test.ts +36 -1
  258. package/test/canonical-quantity-door.test.ts +61 -0
  259. package/test/command-routing.test.ts +1 -1
  260. package/test/declared-location-types.test.ts +89 -0
  261. package/test/discovery-result.test.ts +1 -1
  262. package/test/duration-estimators.test.ts +1 -1
  263. package/test/entity-delta.test.ts +2 -2
  264. package/test/ingest-bench.test.ts +3 -3
  265. package/test/ingest-health-engine.test.ts +248 -0
  266. package/test/ingest-health-wiring.test.ts +119 -0
  267. package/test/ingest-health.test.ts +306 -0
  268. package/test/ingest-history.test.ts +247 -0
  269. package/test/integration-probes.test.ts +103 -0
  270. package/test/integration-runner.test.ts +95 -0
  271. package/test/item-ref.test.ts +78 -0
  272. package/test/journal-read-discipline.test.ts +177 -0
  273. package/test/journal-retention.test.ts +133 -0
  274. package/test/journal-sort-axis.test.ts +142 -0
  275. package/test/journal-write-door.test.ts +110 -0
  276. package/test/journal-write-trend.test.ts +115 -0
  277. package/test/kernel-kind-guard.test.ts +4 -4
  278. package/test/kpi-fold.test.ts +90 -3
  279. package/test/kpi-query-bench.test.ts +2 -2
  280. package/test/lineage-survives-restart.test.ts +17 -1
  281. package/test/live-cursor-wiring.test.ts +87 -0
  282. package/test/live-feed-registry.test.ts +9 -1
  283. package/test/live-kernel-facts.test.ts +7 -7
  284. package/test/live-mirror-parity.test.ts +6 -0
  285. package/test/load-meter.test.ts +29 -15
  286. package/test/local-declarations.test.ts +1 -1
  287. package/test/log-stamp.test.ts +59 -0
  288. package/test/loop-lag.test.ts +82 -0
  289. package/test/model-gap.test.ts +153 -0
  290. package/test/oee-accumulator.test.ts +4 -0
  291. package/test/operational-vocabulary.test.ts +4 -4
  292. package/test/read-failure-visible.test.ts +145 -0
  293. package/test/reference-grounding.test.ts +70 -0
  294. package/test/resolve-ts-siblings.mjs +52 -0
  295. package/test/restart-policy.test.ts +7 -7
  296. package/test/revision-axis.test.ts +93 -0
  297. package/test/runtime-key.test.ts +2 -2
  298. package/test/scale-twin-bench.test.ts +2 -2
  299. package/test/spec-coverage.test.ts +1 -1
  300. package/test/stage-path.test.ts +95 -0
  301. package/test/standard-coverage.test.ts +15 -3
  302. package/test/status-tally.test.ts +55 -0
  303. package/test/structure-revision-db.test.ts +4 -0
  304. package/test/tenant-registry-db.test.ts +1 -1
  305. package/test/time-range.test.ts +152 -0
  306. package/test/touched-items.test.ts +61 -0
  307. package/test/twin-event-keys.test.ts +10 -2
  308. package/test/twin-model-item-db.test.ts +2 -0
  309. package/test/warm-start-seam.test.ts +4 -0
  310. package/test/yield-loop.test.ts +57 -2
  311. package/tsconfig.shared.tsbuildinfo +1 -1
  312. package/tsconfig.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"kpi-query.js","sourceRoot":"","sources":["../../server/engine/kpi-query.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 업무 KPI 조회 — **창 해소 + 저널 조회 + 폴드**를 한 곳에.\n *\n * 왜 한 곳인가: 처음에 GraphQL 질의와 AI 도구가 각각 같은 조회를 적었다. 그러면 창 규칙이 두 곳에서\n * 갈라지고(한쪽만 고치면 화면과 AI 가 다른 숫자를 말한다) 조회 상한·테넌트 조건도 복제된다.\n * 계산은 `kpi-fold`(순수)가 소유하고, 이 파일은 **무엇을 읽을지**만 정한다.\n *\n * ── 창의 기준 시각은 \"트윈의 시계\" 다 ──────────────────────────────────────\n * 시뮬 트윈은 자기 클록으로 산다(예: 저널의 eventTime 이 2026-01-01T00:25Z 인데 실제 지금은 7월).\n * 그런 트윈에 \"최근 1시간\" 을 **실제 시각**으로 물으면 그 구간에는 아무 기록이 없어 항상\n * \"측정된 기록 없음\" 이 된다 — 폴드는 정상인데 답이 쓸모없다.\n *\n * 그래서 호출부가 끝 시각을 주지 않으면 **그 트윈의 마지막 기록 시각**을 끝으로 잡는다. 커널의 시각\n * 상수를 앱에서 재현하지 않고(무방언), 라이브 트윈에서도 자연스럽다(마지막 기록 ≈ 지금).\n * 어떤 기준을 썼는지는 결과의 `window.basis` 로 밝힌다 — 숨기면 사용자가 숫자를 오해한다.\n */\nimport { Between, In } from 'typeorm'\n\nimport { hierarchyOf, levelOfLocationType, electricalUpstreamOf, EMS_LOCATION_TYPES, EMS_PROPERTY, type TariffDeclaration } from '@operato/twin-kernel'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinEvent } from '../service/twin-event/twin-event.js'\nimport { TwinInstance } from '../service/twin-instance/twin-instance.js'\nimport { TwinArea } from '../service/twin-space/twin-area.js'\nimport { TwinSpace } from '../service/twin-space/twin-space.js'\nimport { TwinTarget } from '../service/twin-target/twin-target.js'\nimport { TWIN_METRIC, judgeTargets, judgeGroupTargets, scopedTargets, type ScopedTargets, type TargetVerdict, type TwinMetricId } from './kpi-target.js'\nimport { TwinEngine } from './twin-engine.js'\nimport { buildEnergyTopology, type EnergyTopology } from './energy-topology.js'\nimport {\n crossGroups,\n deltaP50,\n foldKpi,\n foldKpiBuckets,\n shiftWindow,\n type CrossedGroup,\n type KpiEvent,\n type KpiGroupBy,\n type KpiResult\n} from './kpi-fold.js'\nimport { MAX_BASELINE_PERIODS, baselineWindows, summarizeBaseline, type BaselineStride, type MetricBaseline } from './kpi-baseline.js'\n\n/**\n * 업무 전이만 읽는다 — 설비 상태(equipment.status)는 OEE 누적기의 몫이고 여기선 쓰지 않는다.\n *\n * **에너지 구간 마감**은 함께 읽는다(2026-08-14): 원단위는 분자(전력량)와 분모(산출)를 **같은 창**에서\n * 나눈 값이라, 두 사실이 한 폴드에 들어와야 창이 어긋날 수 없다. 표본(`energy.measured`)은 읽지 않는다 —\n * 저널에 없고(§4.1 인제스트 계약) 여기서 필요한 것은 마감된 구간뿐이다.\n */\nconst BUSINESS_EVENTS = ['task.status', 'order.status', 'energy.demand.window']\n\n/** 한 번에 접을 이벤트 상한 — 큰 구간이 서버를 붙잡지 않게. */\nconst MAX_EVENTS = 20000\n\nexport interface TwinKpiInput {\n domainId: string\n /** 한 트윈. `spaceId` 와 둘 중 하나는 있어야 한다. */\n instanceId?: string\n /**\n * 한 **공간** — 그 공간에서 가동 중인 트윈 전부를 **한 번에 접는다**.\n *\n * 트윈별로 접어 평균을 다시 평균하면 분위수가 거짓이 된다(p90 의 평균은 p90 이 아니다).\n * 그래서 이벤트를 모아 **한 번** 접는다. 사용자는 \"부산 창고\" 를 보고, 그 안에 트윈이 몇 개든\n * 합쳐서 본다(헤더 트윈 셀렉터를 없앤 결정과 같은 규율).\n */\n spaceId?: string\n /** 창의 끝(ISO). 미지정이면 **그 트윈의 마지막 기록 시각**. */\n toTime?: string\n /** 창의 시작(ISO). 미지정이면 끝에서 windowMinutes 만큼 뒤로. */\n fromTime?: string\n /** 창 길이(분). fromTime 이 있으면 무시. 기본 60, 1~1440. */\n windowMinutes?: number\n /** 시작 이벤트를 찾기 위해 창보다 앞으로 더 읽는 시간(분). 기본 60, 0~1440. */\n lookbackMinutes?: number\n /**\n * 창을 N 등분해 **구간별 값도** 함께 낸다(추세용). 1~48, 기본 0(쪼개지 않음).\n * 조회는 한 번이고 접기만 N 번 반복한다 — 폴드가 순수하므로 값싸다.\n */\n buckets?: number\n /**\n * 같은 사실을 **다른 관점으로** 쪼갠다: 자원별·작업종류별·도착지점별·오더별·구역별.\n *\n * 고정된 한 렌즈만 낼 수 있으면 \"구역별로는 어떤가\" 라는 질문에 화면도 AI 도 답할 수 없다.\n * 축은 조회 인자 하나이고, 접기는 순수 함수라 축을 하나 더 보는 값이 거의 없다.\n */\n groupBy?: KpiGroupBy\n /** 축의 상한(기본 20, 최대 100). 넘치면 잘라내고 잘린 수를 함께 알린다. */\n groupLimit?: number\n /**\n * **무엇과 비교할 것인가** — 같은 길이의 앞 구간(previous)이나 하루 전 같은 시간(yesterday).\n *\n * 숫자 하나로는 좋아졌는지 나빠졌는지 알 수 없다(\"29초\" 가 개선인지 악화인지). 창만 옮겨 한 번 더\n * 접으면 되므로(폴드는 순수) 값싸다. 비교 구간에 기록이 없으면 **delta 를 만들지 않는다** —\n * 없는 것을 0% 로 그리면 \"변화 없음\" 이라는 거짓이 된다.\n */\n compareTo?: 'previous' | 'yesterday'\n /**\n * **여러 구간 동시 비교** — 최근 N 구간의 같은 시간대를 함께 접어 **분포**와 그 안의 자리를 낸다.\n *\n * `compareTo` 는 한 구간만 봐서 판단이 \"어제보다 나아졌다\" 에서 멈춘다 — 어제도 나빴으면 쓸모가 없다.\n * 최근 며칠의 같은 시간대를 보면 목표 없이도 \"지난 7일 중 가장 낮다\" 를 말할 수 있다.\n *\n * `stride: 'week'` 는 같은 요일을 본다(주말·평일이 섞이지 않게). `periods` 는 1~14\n * (창마다 조회가 한 번 더 붙는다 — 값을 공짜로 말하지 않는다).\n *\n * `compareTo` 와 함께 쓸 수 있다: 다른 질문이기 때문이다(직전 한 구간 vs 최근 분포).\n */\n baseline?: { periods: number; stride?: BaselineStride }\n}\n\nexport interface TwinKpiOutput extends Omit<Partial<KpiResult>, 'window'> {\n /** 사람이 읽는 창(ISO) + 기준. 폴드의 raw 창(ms)은 여기에 흡수한다. */\n window: {\n fromTime: string\n toTime: string\n minutes: number\n /** 끝 시각을 어디서 얻었나 — 'given'(호출부 지정) | 'latest-event'(트윈의 마지막 기록). */\n basis: 'given' | 'latest-event'\n }\n /**\n * 조회가 **상한에 걸렸는가**(구간이 너무 커서 일부만 읽었을 수 있다).\n *\n * true 면 이 구간의 숫자는 **하한**이다 — 실제로는 더 있었을 수 있다. 조용히 잘라내면 숫자가 조용히\n * 작아지고 사용자는 그것을 성과 하락으로 읽는다. 구간을 좁혀 다시 물어야 한다.\n */\n eventsCapped?: boolean\n /**\n * 이 구간에 **측정할 기록이 있었는가**. false 는 \"처리량 0\" 이 아니라 **\"기록이 없다\"** 는 뜻이다 —\n * 두 상황은 완전히 다르고, 섞으면 사용자가 잘못된 결론을 낸다.\n */\n measured: boolean\n note?: string\n /** 무엇을 집계했나 — 공간이면 합쳐 접은 트윈 목록을 밝힌다(숫자의 출처를 숨기지 않는다). */\n /**\n * 무엇을 집계했나 — 공간이면 합쳐 접은 트윈 목록.\n *\n * `restartPolicies` 는 **이 숫자가 무엇의 성과인지**를 말한다: mirror(실제 시스템을 반영) ·\n * `resume`(이어지는 가상 세계) · `reset`(재현 실험 사본). 같은 \"345건\" 이 현실의 실적일 수도, 시뮬레이션의\n * 산출일 수도 있다 — 그 구별 없이 숫자만 보여주면 사용자가 시뮬 결과를 실적으로 착각한다.\n */\n scope: { spaceId?: string; instanceIds: string[]; restartPolicies: string[] }\n /** 구간별 값(요청했을 때만) — 추세를 그리기 위한 최소 정보. */\n buckets?: { fromTime: string; toTime: string; tasks: number; orders: number; workP50Ms: number }[]\n /**\n * **목표 대비 판정** — 숫자를 판단으로 바꾸는 값(§kpi-target).\n *\n * 목표가 걸린 지표만 들어온다. 목표가 없거나 그 구간에 값이 없는 지표는 **여기 나타나지 않는다** —\n * 전부 \"목표 없음\" 으로 채우면 화면이 회색 노이즈가 되고, 없는 값을 0 으로 놓으면 \"목표 크게 미달\"\n * 이라는 없는 사실이 만들어진다.\n */\n targets?: TargetVerdict[]\n /**\n * 비교 구간(요청했을 때만) — 같은 규칙·같은 길이로 접은 앞 구간과의 차이.\n *\n * `measured:false` 면 그 구간에 기록이 없다는 뜻이고 `delta` 는 없다(0 이 아니다).\n * delta 는 **현재 − 비교구간** 이다. 좋고 나쁨은 판단하지 않는다 — 처리량은 클수록 좋고 소요는\n * 작을수록 좋다는 해석은 소비처(화면·AI)의 몫이고, 계산 층이 판단을 섞으면 목표 없이 판정하게 된다.\n */\n comparison?: {\n basis: 'previous' | 'yesterday'\n window: { fromTime: string; toTime: string }\n measured: boolean\n throughput?: { tasks: number; orders: number }\n workTime?: KpiResult['workTime']\n leadTime?: KpiResult['leadTime']\n waitTime?: KpiResult['waitTime']\n delta?: { tasks: number; orders: number; workP50Ms?: number; leadP50Ms?: number; waitP50Ms?: number }\n }\n /**\n * **여러 구간 분포**(요청했을 때만) — 지표별로 최근 N 구간의 분포와 지금의 자리.\n *\n * `samples` 는 **요청한 창 전부**다(기록 없는 창도 `measured:false` 로 남는다) — 빼고 말하면\n * \"지난 7일\" 이 실제로는 3일이었다는 것을 아무도 모른다. 자리를 말할 수 없으면 `noPosition` 이\n * 이유를 밝힌다(표본 부족·현재 값 없음). 방향은 지표의 성질이라 `standing` 이 그것을 적용한 읽기다.\n */\n baseline?: {\n stride: BaselineStride\n /** 요청한 과거 구간 수(상한 적용 후). */\n periods: number\n metrics: MetricBaseline[]\n /** 과거 창 조회 중 하나라도 상한에 걸렸나 — 그 창의 값은 하한이라 분포가 낮게 보일 수 있다. */\n capped?: boolean\n }\n /**\n * 관점별 값(요청했을 때만). `key` 는 식별자, `label` 은 사람이 읽는 이름(있을 때만 — 구역은 이름이\n * 따로 저장되어 있다). `truncated` 는 상한 때문에 빠진 축의 수다.\n */\n groups?: {\n by: KpiGroupBy\n items: (KpiGroupOut & { label?: string })[]\n /**\n * 비교 구간에는 일이 있었는데 **지금은 없는** 축. 표에서 그냥 빠지면 멈춘 구역·대기 중인 자원이\n * 눈에 보이지 않는다 — 운영에서는 이것이 가장 중요한 신호일 수 있다.\n */\n disappeared?: { key: string; label?: string; tasks: number }[]\n /** 상한 때문에 빠진 축의 수와, 그 축들이 안고 있던 건수(둘 다 알려야 꼬리를 놓치지 않는다). */\n truncated?: number\n truncatedTasks?: number\n note?: string\n }\n}\n\n/** 축 하나의 값 — 폴드의 KpiGroup 과 같은 모양(시각 변환이 없어 그대로 통과한다). */\ntype KpiGroupOut = NonNullable<KpiResult['groups']>['items'][number] &\n Partial<Pick<CrossedGroup, 'prevTasks' | 'deltaTasks' | 'deltaWorkP50Ms' | 'isNew'>>\n\n/**\n * 노드 → 구역 지도. **저널에는 구역이 없다**(노드까지만 있다) — 모델 정의의 `locations[].parentId` 가\n * 마스터 계층에서 온 소속이다. 기동하지 않은 트윈도 모델은 남아 있어 이 경로가 가장 튼튼하다.\n *\n * **한 홉만 보면 안 된다.** `parentId` 는 구역일 수도 있고 중간 단(라인·셀)일 수도 있다. 라인이 끼면\n * 한 홉짜리 코드는 \"구역이 LINE-1\" 이라고 답하고, 그 id 는 구역 목록에 없으므로 그 아래 스테이션이\n * **집계에서 조용히 빠진다**(에러 없이 틀린 숫자). 그래서 커널 `hierarchyOf` 로 사슬을 끝까지 걷는다 —\n * 걷는 규칙은 커널에 한 벌뿐이고 소비처는 그것만 부른다.\n */\n/**\n * 교대 축의 재료 — **교대 선언과 현장의 시각 기준.**\n *\n * 저널에는 시각만 있고 교대가 없다(일부러 그렇게 뒀다 — 교대는 파생이라 찍어 두면 나중에 교대 시간을\n * 고쳤을 때 과거가 옛 구분으로 굳는다). 그래서 폴드가 계산할 재료를 여기서 모은다.\n *\n * 선언의 출처는 **모델의 설비**다. 여러 설비가 서로 다른 교대를 가질 수 있지만, 축은 하나여야 하므로\n * **가장 많이 쓰이는 선언 한 벌**을 쓴다 — 3교대 공장은 전 설비가 같은 교대이므로 이것이 곧 정답이고,\n * 자원마다 교대가 다른 현장에서는 이 축이 거칠다는 뜻이다(그 사실을 여기 적어 둔다).\n *\n * 시각 기준은 모델에 실려 있다(`utcOffsetMinutes` — 공간이 갖는 값을 기동 시 호스트가 풀어 넣는다).\n * 없으면 UTC 로 읽고, 그 기본값은 커널 계약에 밝혀져 있다.\n */\n/**\n * 이 범위에 걸린 운영 목표 — **좁은 선언이 넓은 선언을 덮는다.**\n *\n * 공간 전체 목표 위에 인스턴스 목표를 얹을 수 있어야 예외를 표현할 수 있다(\"이 라인만 다르다\").\n * 같은 지표에 둘 다 있으면 인스턴스가 이긴다.\n */\nasync function loadTargets(\n domainId: string,\n spaceId: string | undefined,\n instanceIds: string[],\n axis?: string\n): Promise<ScopedTargets> {\n /* 조회는 **가져오기만** 한다 — 어느 목표가 이기는지는 순수 함수(`scopedTargets`)가 정한다.\n 그 규칙이 DB 접근 뒤에 있으면 화면을 눌러 봐야만 확인된다(그리고 실제로 확인되지 않았다). */\n const rows = await getRepository(TwinTarget).find({ where: { domain: { id: domainId } } }).catch(() => [])\n return scopedTargets(rows, { spaceId, instanceIds, axis })\n}\n\nasync function shiftContext(\n domainId: string,\n instanceIds: string[]\n): Promise<{ shifts?: any[]; utcOffsetMinutes?: number }> {\n if (!instanceIds.length) return {}\n const rows = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, instanceId: In(instanceIds) } })\n const tally = new Map<string, { decl: any[]; n: number }>()\n let utcOffsetMinutes: number | undefined\n for (const row of rows) {\n const model = row.model as any\n if (utcOffsetMinutes === undefined && typeof model?.utcOffsetMinutes === 'number') utcOffsetMinutes = model.utcOffsetMinutes\n for (const e of (model?.equipment ?? []) as any[]) {\n const decl = e?.workCalendar\n if (!Array.isArray(decl) || !decl.length) continue\n const key = JSON.stringify(decl)\n const hit = tally.get(key)\n if (hit) hit.n++\n else tally.set(key, { decl, n: 1 })\n }\n }\n let best: { decl: any[]; n: number } | undefined\n for (const v of tally.values()) if (!best || v.n > best.n) best = v\n return { ...(best ? { shifts: best.decl } : {}), ...(utcOffsetMinutes !== undefined ? { utcOffsetMinutes } : {}) }\n}\n\nasync function locationAreaMap(\n domainId: string,\n instanceIds: string[],\n spaceId?: string\n): Promise<{ locationArea: Record<string, string>; labels: Record<string, string> }> {\n const locationArea: Record<string, string> = {}\n if (instanceIds.length) {\n const rows = await getRepository(TwinInstance).find({\n where: { domain: { id: domainId }, instanceId: In(instanceIds) }\n })\n for (const row of rows) {\n const locations = (((row.model as any)?.locations ?? []) as any[]).filter(n => n?.id)\n if (!locations.length) continue\n /* 순환은 hierarchyOf 가 던진다 — 한 트윈의 잘못된 계층이 다른 트윈의 집계까지 죽이지 않게 여기서 가둔다. */\n let h\n try {\n /*\n * 단 해석기를 함께 넘긴다 — 자리가 `level` 을 적지 않으면 **타입이 답한다**(카탈로그 선언).\n * 이 집계는 지금 구역 롤업(`rollupOf`)만 쓰므로 결과가 달라지지 않는다. 그래도 여기서\n * 잇는다: 단으로 묻는 질의(`ancestorOfLevel`)가 생길 때 배선을 다시 찾지 않게 한다.\n */\n h = hierarchyOf({ locations }, t => levelOfLocationType(t, row.kind))\n } catch (e: any) {\n console.warn(`[twin-kpi] \"${row.instanceId}\": location hierarchy is broken — area rollup skipped for this twin: ${e?.message}`)\n continue\n }\n for (const n of locations) {\n const area = h.rollupOf(String(n.id))\n if (area) locationArea[String(n.id)] = area // 구역 미상은 넣지 않는다 — 없는 구역을 만들지 않는다\n }\n }\n }\n\n const labels: Record<string, string> = {}\n const sid = spaceId\n if (sid) {\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId: sid } })\n if (space) {\n const areas = await getRepository(TwinArea).find({\n where: { domain: { id: domainId } as any, space: { id: space.id } as any }\n })\n for (const a of areas) if (a.areaId && a.name) labels[a.areaId] = a.name\n }\n }\n return { locationArea, labels }\n}\n\n/**\n * 자원 → **배전 계통의 자리**(분기·수전) 지도와 그 자리의 이름.\n *\n * ── 무엇을 읽나 ─────────────────────────────────────────────────────────────\n * 에너지 트윈의 모델이 「이 설비가 어느 자리에 매달렸나」를 선언한다(`homeLocation`). 그 자리가 분기가\n * 아니라 하위 자리라면 부모를 타고 올라가 **전기 계통의 자리**를 찾는다 — 전기의 계통은 물리 구역과\n * 다른 나무이므로 구역 롤업(`locationAreaMap`)을 쓸 수 없다.\n *\n * ── 왜 에너지 트윈만 읽나 ───────────────────────────────────────────────────\n * 같은 설비가 공장 트윈에서는 자원(작업을 한다)이고 에너지 트윈에서는 부하(전기를 먹는다)다 — 사실 둘,\n * id 하나. 가동시간은 공장 트윈의 저널에서 오고, 전기 계통의 소속은 이 모델에서 온다. 그 둘을 id 로\n * 잇는 것이 이 지도의 일이다.\n *\n * 자리를 말하지 않는 설비는 **넣지 않는다** — 없는 소속을 만들면 그 몫이 엉뚱한 분기의 것이 된다.\n */\n/**\n * 선언된 **전기요금 단가** — 에너지 트윈의 자리 속성에서 읽는다(계약전력과 같은 자리).\n *\n * 여러 에너지 트윈이 한 현장에 있으면 **처음 선언된 것 하나**를 쓴다. 평균을 내거나 합치지 않는다:\n * 요금은 계약이고, 두 계약을 섞은 단가는 어느 계약도 아니다. 서로 다른 단가가 선언돼 있으면 그것을\n * 밝힌다(조용히 하나를 고르면 왜 그 금액인지 아무도 설명할 수 없다).\n */\nasync function tariffOf(\n domainId: string,\n instanceIds: string[]\n): Promise<{ tariff?: TariffDeclaration; conflict?: boolean }> {\n if (!instanceIds.length) return {}\n const rows = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, instanceId: In(instanceIds) } })\n const found: TariffDeclaration[] = []\n for (const row of rows) {\n if (row.kind !== 'ems') continue\n for (const loc of ((row.model as any)?.locations ?? []) as any[]) {\n const props = new Map<string, any>(((loc?.properties ?? []) as any[]).map(p => [String(p?.id), p?.value]))\n const num = (id: string): number | undefined => {\n const v = Number(props.get(id))\n return Number.isFinite(v) && v > 0 ? v : undefined\n }\n const d = num(EMS_PROPERTY.demandChargePerKW)\n const e = num(EMS_PROPERTY.energyChargePerKWh)\n const cur = props.get(EMS_PROPERTY.currency)\n if (d === undefined && e === undefined) continue\n found.push({\n ...(d !== undefined ? { demandChargePerKW: d } : {}),\n ...(e !== undefined ? { energyChargePerKWh: e } : {}),\n ...(cur ? { currency: String(cur) } : {})\n })\n }\n }\n if (!found.length) return {}\n const first = found[0]\n const same = found.every(\n t => t.demandChargePerKW === first.demandChargePerKW && t.energyChargePerKWh === first.energyChargePerKWh && t.currency === first.currency\n )\n return { tariff: first, ...(same ? {} : { conflict: true }) }\n}\n\nasync function resourceFeederMap(\n domainId: string,\n instanceIds: string[]\n): Promise<{ resourceFeeder: Record<string, string>; meterFeeder: Record<string, string>; labels: Record<string, string> }> {\n const resourceFeeder: Record<string, string> = {}\n /** 계량 지점 → 그것이 재는 분기. 있으면 그 분기 값은 **측정**이다(배분보다 강한 근거). */\n const meterFeeder: Record<string, string> = {}\n const labels: Record<string, string> = {}\n if (!instanceIds.length) return { resourceFeeder, meterFeeder, labels }\n\n const rows = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, instanceId: In(instanceIds) } })\n for (const row of rows) {\n if (row.kind !== 'ems') continue // 전기 계통을 선언하는 것은 에너지 트윈이다\n const model: any = row.model ?? {}\n const locations: any[] = (model.locations ?? []).filter((n: any) => n?.id)\n const byId = new Map(locations.map((n: any) => [String(n.id), n]))\n const electrical = new Set(EMS_LOCATION_TYPES as readonly string[])\n\n /**\n * 이 자리에서 **전기 상류**로 올라가 처음 만나는 계통의 자리. 없으면 undefined.\n *\n * 상류의 뜻은 커널이 읽는다(`electricalUpstreamOf`) — 공간 부모(공장·구역)를 따라 올라가면 전기적으로\n * 뜻이 없는 길을 걷게 되고, 옛 세대(분기의 `parentId` 에 수전)와 새 세대(`upstreamId`)가 섞여 있으므로\n * 그 판정을 여기서 다시 쓰면 두 벌이 된다.\n */\n const feederOf = (startId: string): string | undefined => {\n const seen = new Set<string>()\n let cur = byId.get(startId)\n while (cur && !seen.has(String(cur.id))) {\n seen.add(String(cur.id)) // 잘못된 모델의 순환이 여기서 멈춘다\n if (electrical.has(String(cur.type))) return String(cur.id)\n const up = electricalUpstreamOf(locations as any, String(cur.id))\n cur = up ? byId.get(up) : cur.parentId ? byId.get(String(cur.parentId)) : undefined\n }\n return undefined\n }\n\n for (const e of (model.equipment ?? []) as any[]) {\n const home = e?.homeLocation ?? e?.homeLocationId\n if (!e?.id || !home) continue\n /*\n * 계량기는 부하가 아니다 — 그것의 「몫」을 세면 총량이 이중으로 나뉜다. 대신 **어디를 재는지**를\n * 따로 모은다: 계량기가 분기에 있으면 그 분기 값은 배분이 아니라 **측정**이다(가장 강한 근거).\n */\n if (e.kind === 'meter') {\n const at = feederOf(String(home))\n /* 뿌리(수전)를 재는 계량기는 분기의 것이 아니다 — 그 값은 총량이고, 나머지는 뺄셈으로 나온다. */\n if (at && String(byId.get(at)?.type) !== 'incoming') meterFeeder[String(e.id)] = at\n continue\n }\n /* 차단기·태양광·축전지도 부하가 아니다. */\n if (e.kind === 'breaker' || e.kind === 'pv-array' || e.kind === 'battery') continue\n const feeder = feederOf(String(home))\n if (feeder) resourceFeeder[String(e.id)] = feeder\n }\n for (const n of locations) if (electrical.has(String(n.type)) && n.name) labels[String(n.id)] = String(n.name)\n }\n return { resourceFeeder, meterFeeder, labels }\n}\n\n/**\n * 이 공간의 에너지 트윈들의 계통도 — 모델을 읽어 트리로 접는다.\n *\n * 여러 트윈이 한 공간에 있으면 **각 트윈의 뿌리를 나란히** 둔다(하나로 합치면 없는 결선을 그리게 된다).\n * 에너지 트윈이 없으면 만들지 않는다 — 빈 그림은 고장으로 읽힌다.\n */\nasync function energyTopologyOf(\n domainId: string,\n instanceIds: string[],\n energy: { byFeeder?: { key: string; kWh: number; basis?: 'measured' | 'apportioned'; peakKW?: number }[] } | undefined\n): Promise<EnergyTopology | undefined> {\n if (!instanceIds.length) return undefined\n const rows = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, instanceId: In(instanceIds) } })\n const ems = rows.filter(r => r.kind === 'ems' && r.model)\n if (!ems.length) return undefined\n const merged: EnergyTopology = { roots: [], unplaced: [], unmetered: [], loadsOnIncoming: 0 }\n for (const row of ems) {\n /*\n * 지금의 설비 상태를 함께 넘긴다 — 발전·저장의 **지금 값**은 구간 폴드가 아니라 스냅샷이 안다.\n * 없으면(정지된 트윈) 구조만 그린다.\n */\n const snap = TwinEngine.snapshot(domainId, row.instanceId)\n const t = buildEnergyTopology(row.model, energy, snap?.equipment)\n merged.roots.push(...t.roots)\n merged.unplaced.push(...t.unplaced)\n merged.unmetered.push(...t.unmetered)\n merged.loadsOnIncoming += t.loadsOnIncoming\n }\n return merged.roots.length ? merged : undefined\n}\n\n/** 이 대상들의 마지막 업무 기록 시각(ISO). 기록이 없으면 null. */\nasync function latestEventTime(domainId: string, instanceIds: string[]): Promise<string | null> {\n if (instanceIds.length === 0) return null\n const row = await getRepository(TwinEvent).findOne({\n where: { domain: { id: domainId }, instanceId: In(instanceIds), eventType: In(BUSINESS_EVENTS) },\n order: { revision: 'DESC' }\n })\n /* 소비처는 ISO 문자열을 기대한다(시각 축의 공용 표기) — 컬럼이 날짜가 됐으므로 여기서 옮긴다. */\n return row?.eventTime ? row.eventTime.toISOString() : null\n}\n\n/**\n * 창 구간의 업무 이벤트 — 창보다 `lookbackMs` 만큼 앞까지 읽는다(완료된 작업의 시작이 창 밖일 수 있다).\n *\n * eventTime 은 날짜 컬럼이라 DB 가 시각으로 비교한다 — 예전 문자열 컬럼 시절의 \"UTC ISO 만 들어오면\n * 사전식 비교가 맞는다\" 는 전제가 필요 없다.\n */\nasync function fetchEvents(domainId: string, instanceIds: string[], fromMs: number, toMs: number, lookbackMs: number) {\n if (instanceIds.length === 0) return { events: [] as KpiEvent[], capped: false }\n const rows = await getRepository(TwinEvent).find({\n where: {\n domain: { id: domainId },\n instanceId: In(instanceIds),\n eventType: In(BUSINESS_EVENTS),\n eventTime: Between(new Date(fromMs - lookbackMs), new Date(toMs))\n },\n order: { revision: 'ASC' },\n take: MAX_EVENTS\n })\n /* 상한에 정확히 닿으면 **더 있었을 수 있다** — 그 사실을 돌려준다. 조용히 잘라내면 숫자가 조용히\n * 작아지고, 사용자는 그것을 성과 하락으로 읽는다(가장 나쁜 종류의 거짓이다). */\n return {\n /* 폴드는 ISO 문자열 축으로 수행된다(kpi-fold) — 경계에서 한 번만 옮긴다. */\n events: rows.map(r => ({ eventType: r.eventType, eventTime: r.eventTime?.toISOString(), payload: r.payload })),\n capped: rows.length >= MAX_EVENTS\n }\n}\n\n/**\n * 집계 대상 트윈 목록.\n *\n * 공간이 주어지면 **가동 중이고 운영 목적인** 것만 모은다 — 벤치 사본은 사용자가 보는 현장이 아니다.\n * 트윈이 지정되면 그것 하나. 둘 다 없으면 무엇을 재야 할지 알 수 없으므로 명시 실패한다.\n */\nasync function resolveTargets(input: TwinKpiInput): Promise<{ instanceIds: string[]; restartPolicies: string[] }> {\n if (!input.instanceId && !input.spaceId) throw new Error('either instanceId or spaceId is required')\n const list = await TwinEngine.list(input.domainId)\n const rows = input.instanceId\n ? list.filter((x: any) => x?.instanceId === input.instanceId)\n : list.filter((x: any) => x?.running && x.spaceId === input.spaceId && x.purpose !== 'bench' && x.instanceId)\n /* 목록에서 못 찾아도(정지·비등록) 지정된 트윈은 그대로 조회한다 — 저널은 남아 있을 수 있다. */\n const instanceIds = rows.length ? rows.map((x: any) => x.instanceId as string) : input.instanceId ? [input.instanceId] : []\n /* 컬럼이 NOT NULL 이라 'unknown' 으로 메울 일이 없다 — 비면 계약이 깨진 것이지 모르는 상태가 아니다. */\n const restartPolicies = [...new Set(rows.map((x: any) => String(x.restartPolicy)))]\n return { instanceIds, restartPolicies }\n}\n\n/**\n * 대상 해소 공개 창구 — **저널 목록 질의도 KPI 와 같은 규칙을 써야 한다.**\n *\n * 화면에서 \"이 공간\" 은 하나의 뜻이어야 한다. 성과 화면과 원장 화면이 각자 co-located 트윈을\n * 추리면 같은 공간을 보면서 다른 대상 집합을 세게 되고, 두 숫자가 어긋나는 이유를 아무도 설명하지 못한다.\n */\nexport async function resolveTwinTargets(domainId: string, instanceId?: string, spaceId?: string): Promise<string[]> {\n const { instanceIds } = await resolveTargets({ domainId, instanceId, spaceId } as TwinKpiInput)\n return instanceIds\n}\n\n/**\n * 시간창 업무 KPI. 창 해소 → 저널 조회 → 순수 폴드.\n *\n * 테넌트 조건은 호출부가 준 `domainId` 로만 좁힌다(클라이언트 값을 그대로 쓰지 않는 것은 호출부 책임).\n */\nexport async function computeTwinKpi(input: TwinKpiInput): Promise<TwinKpiOutput> {\n const minutes = Math.max(1, Math.min(24 * 60, Number(input.windowMinutes) || 60))\n const lookbackMs = Math.max(0, Math.min(24 * 60, input.lookbackMinutes ?? 60)) * 60_000\n\n const { instanceIds, restartPolicies } = await resolveTargets(input)\n const scope = { spaceId: input.spaceId, instanceIds, restartPolicies }\n\n /* 끝 시각 — 지정이 없으면 대상들의 마지막 기록. 그것도 없으면 측정할 것이 없다. */\n let basis: 'given' | 'latest-event' = 'given'\n let toMs = input.toTime ? Date.parse(input.toTime) : NaN\n if (!Number.isFinite(toMs)) {\n const latest = await latestEventTime(input.domainId, instanceIds)\n if (!latest) {\n const now = Date.now()\n return {\n window: {\n fromTime: new Date(now - minutes * 60_000).toISOString(),\n toTime: new Date(now).toISOString(),\n minutes,\n basis: 'latest-event'\n },\n measured: false,\n scope,\n note:\n instanceIds.length === 0\n ? 'no running twin in this space — nothing to measure.'\n : 'no operational events in the journal yet — there is nothing to measure (this is NOT \"zero throughput\").'\n }\n }\n toMs = Date.parse(latest)\n basis = 'latest-event'\n }\n\n const fromMs = input.fromTime ? Date.parse(input.fromTime) : toMs - minutes * 60_000\n if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || fromMs >= toMs) {\n throw new Error('invalid window: fromTime must be a valid time before toTime')\n }\n\n const { events, capped } = await fetchEvents(input.domainId, instanceIds, fromMs, toMs, lookbackMs)\n\n /* 구역·교대 축은 **이벤트 밖의 지식**을 필요로 한다 — 다른 축은 payload 에서 바로 나온다. */\n const areaMap =\n input.groupBy === 'area' ? await locationAreaMap(input.domainId, instanceIds, input.spaceId) : { locationArea: {}, labels: {} }\n const shiftCtx = input.groupBy === 'shift' ? await shiftContext(input.domainId, instanceIds) : {}\n /*\n * 전기 계통 소속은 **축과 무관하게** 읽는다 — 분기별 몫은 관점 축이 아니라 에너지 사실의 일부라\n * (같은 창의 총량을 나눈 것) 어느 축을 보고 있든 함께 나가야 한다.\n */\n const feederMap = await resourceFeederMap(input.domainId, instanceIds)\n /* 요금 단가도 축과 무관하게 읽는다 — 금액은 같은 창의 전력량·최대수요에서 나오는 사실이다. */\n const tariffFound = await tariffOf(input.domainId, instanceIds)\n const kpi = foldKpi(events, { fromMs, toMs }, {\n groupBy: input.groupBy,\n locationArea: areaMap.locationArea,\n resourceFeeder: feederMap.resourceFeeder,\n meterFeeder: feederMap.meterFeeder,\n ...(tariffFound.tariff ? { tariff: tariffFound.tariff } : {}),\n ...shiftCtx,\n groupLimit: input.groupLimit\n })\n /*\n * 목표를 **폴드 뒤, 그룹 조립 앞**에 읽는다 — 전체 판정과 축별 판정이 같은 목록에서 나와야\n * 두 곳의 임계가 갈라지지 않는다. 폴드 자체는 목표를 모른다(사실 층과 판정 층의 분리).\n */\n const scoped = await loadTargets(input.domainId, input.spaceId, instanceIds, input.groupBy)\n const verdicts = judgeTargets(kpi, scoped.overall)\n const groupTargets = scoped.byAxisKey\n\n const window = {\n fromTime: new Date(fromMs).toISOString(),\n toTime: new Date(toMs).toISOString(),\n minutes: Math.round((toMs - fromMs) / 60_000),\n basis\n }\n /**\n * 여러 구간 분포를 만든다 — **현재 창에 기록이 없어도 낸다.**\n *\n * 그때가 오히려 가장 알고 싶은 순간이다(\"지금 아무 기록이 없는데 평소엔 60건이었다\" = 멈춘 신호).\n * 그래서 조기 반환보다 앞에 두고 두 경로가 함께 쓴다.\n */\n const computeBaseline = async (): Promise<TwinKpiOutput['baseline']> => {\n if (!input.baseline || !(input.baseline.periods > 0)) return undefined\n const stride = input.baseline.stride ?? 'day'\n const windows = baselineWindows(fromMs, toMs, input.baseline.periods, stride)\n let baseCapped = false\n const samples: { fromMs: number; toMs: number; values: Partial<Record<TwinMetricId, number | undefined>> }[] = []\n\n for (const w of windows) {\n const { events: pastEvents, capped: pastCapped } = await fetchEvents(input.domainId, instanceIds, w.fromMs, w.toMs, lookbackMs)\n baseCapped = baseCapped || pastCapped\n /* 축 없이, **같은 교대 정의**로 접는다(정의가 다르면 같은 시간대가 아니게 된다). */\n const past = foldKpi(pastEvents, w, { locationArea: areaMap.locationArea, ...shiftCtx })\n const values: Partial<Record<TwinMetricId, number | undefined>> = {}\n for (const metric of Object.keys(TWIN_METRIC) as TwinMetricId[]) values[metric] = TWIN_METRIC[metric].of(past)\n samples.push({ fromMs: w.fromMs, toMs: w.toMs, values })\n }\n\n const current: Partial<Record<TwinMetricId, number | undefined>> = {}\n for (const metric of Object.keys(TWIN_METRIC) as TwinMetricId[]) current[metric] = TWIN_METRIC[metric].of(kpi)\n\n return {\n stride,\n periods: windows.length,\n metrics: summarizeBaseline(TWIN_METRIC, current, samples),\n ...(baseCapped ? { capped: true } : {})\n }\n }\n\n\n if (kpi.eventsInWindow === 0) {\n const baseline = await computeBaseline()\n return {\n window,\n measured: false,\n scope,\n note: 'no operational events in this window — nothing to measure (this is NOT \"zero throughput\").',\n ...(baseline ? { baseline } : {})\n }\n }\n\n /* 구간별 값 — 쪼개기 규칙은 순수 모듈이 소유한다(테스트 가능). 여기서는 시각을 ISO 로 옮기기만. */\n const rawBuckets = foldKpiBuckets(events, { fromMs, toMs }, Number(input.buckets) || 0)\n const buckets = rawBuckets.length\n ? rawBuckets.map(b => ({\n fromTime: new Date(b.fromMs).toISOString(),\n toTime: new Date(b.toMs).toISOString(),\n tasks: b.tasks,\n orders: b.orders,\n workP50Ms: b.workP50Ms\n }))\n : undefined\n\n /* 비교 구간 — **같은 길이·같은 규칙**으로 한 번 더 접는다. 기록이 없으면 delta 를 만들지 않는다\n * (없는 것을 0 으로 그리면 \"변화 없음\" 이라는 거짓이 된다). */\n let comparison: TwinKpiOutput['comparison']\n /* 관점 × 비교 교차 — 축별 delta 와 **사라진 축**(전에 있었고 지금 없는 것). */\n let crossed: ReturnType<typeof crossGroups> | undefined\n /* 비교 구간 조회도 상한에 걸릴 수 있다 — 그 경우 delta 가 실제보다 작아 보인다. */\n let prevCappedFlag = false\n if (input.compareTo === 'previous' || input.compareTo === 'yesterday') {\n const w = shiftWindow(fromMs, toMs, input.compareTo)\n const { events: prevEvents, capped: prevCapped } = await fetchEvents(\n input.domainId,\n instanceIds,\n w.fromMs,\n w.toMs,\n lookbackMs\n )\n /* 축이 요청됐으면 비교 구간도 **같은 축**으로 접는다 — 교차(\"구역별로 어제 대비\")를 내기 위해.\n * 상한은 축이 서로 어긋나지 않게 넉넉히 둔다(현재 축이 상한 안에 있는데 비교 축에서 밀려나면\n * 있는데 없다고 말하게 된다). */\n const prev = foldKpi(prevEvents, w, {\n groupBy: input.groupBy,\n locationArea: areaMap.locationArea,\n ...shiftCtx, // 비교 구간도 **같은 교대 정의**로 접는다(정의가 다르면 축이 어긋난다)\n groupLimit: input.groupBy ? 100 : undefined\n })\n prevCappedFlag = prevCapped\n if (input.groupBy && kpi.groups && prev.groups && prev.eventsInWindow > 0) {\n crossed = crossGroups(kpi.groups.items, prev.groups.items)\n }\n const base = {\n basis: input.compareTo,\n window: { fromTime: new Date(w.fromMs).toISOString(), toTime: new Date(w.toMs).toISOString() }\n }\n comparison =\n prev.eventsInWindow === 0\n ? { ...base, measured: false }\n : {\n ...base,\n measured: true,\n throughput: prev.throughput,\n workTime: prev.workTime,\n leadTime: prev.leadTime,\n waitTime: prev.waitTime,\n delta: {\n tasks: kpi.throughput.tasks - prev.throughput.tasks,\n orders: kpi.throughput.orders - prev.throughput.orders,\n workP50Ms: deltaP50(kpi.workTime, prev.workTime),\n leadP50Ms: deltaP50(kpi.leadTime, prev.leadTime),\n waitP50Ms: deltaP50(kpi.waitTime, prev.waitTime)\n }\n }\n }\n\n /*\n * 여러 구간 동시 비교 — **같은 시간대를 며칠 뒤로** 옮겨 분포를 만든다(성과 맵 ③).\n *\n * 창마다 조회가 한 번 더 붙지만 각 창은 현재 창과 같은 길이라(보통 1~24h) 값싸다. 축(groupBy)은\n * 넘기지 않는다 — 분포는 **전체 지표**의 질문이고, 축별 분포까지 내면 응답이 축 수만큼 부푼다\n * (필요해지면 그때 축을 지정해 다시 묻는 것이 정직하다).\n *\n * 지표 어휘는 `TWIN_METRIC` 이 계속 소유하고, 여기서는 **값만 꺼내** 분포 모듈에 넘긴다.\n */\n /* 관점 값은 시각 변환이 없어 그대로 통과한다. 붙이는 것은 ① 사람이 읽는 이름과 ② 축이 통째로\n * 비었을 때의 이유다 — \"구역별 0건\" 과 \"노드에 구역이 안 붙어 있다\" 는 완전히 다른 상황이다. */\n const groups = kpi.groups\n ? {\n by: kpi.groups.by,\n items: (crossed?.items ?? kpi.groups.items).map(g => {\n /* 축의 이 값에 목표가 걸려 있으면 판정을 함께 낸다 — \"이 구역만 목표가 다르다\" 를\n 표에서 바로 볼 수 있게. 목표가 없는 축값에는 필드를 만들지 않는다. */\n const gt = judgeGroupTargets(g as any, groupTargets[g.key])\n return {\n ...g,\n ...(areaMap.labels[g.key] ? { label: areaMap.labels[g.key] } : {}),\n ...(gt.length ? { targets: gt } : {})\n }\n }),\n ...(crossed?.disappeared.length\n ? {\n disappeared: crossed.disappeared.map(d => ({\n ...d,\n ...(areaMap.labels[d.key] ? { label: areaMap.labels[d.key] } : {})\n }))\n }\n : {}),\n ...(kpi.groups.truncated\n ? { truncated: kpi.groups.truncated, truncatedTasks: kpi.groups.truncatedTasks ?? 0 }\n : {}),\n ...(input.groupBy === 'area' &&\n Object.keys(areaMap.locationArea).length === 0 &&\n kpi.groups.items.some(g => g.key === 'unknown')\n ? { note: 'locations carry no area (parentId) in this twin — cannot break down by area. this is NOT \"no work in areas\".' }\n : {})\n }\n : undefined\n\n /*\n * 목표 대비 판정 — **사실 계산 뒤에 얹는다.**\n *\n * 폴드는 목표를 모른다(사실만 낸다). 목표를 아는 순간 \"세는 규칙\" 과 \"잘했나 판정하는 규칙\" 이\n * 한 함수에 섞이고, 목표가 바뀔 때마다 사실 계산을 건드리게 된다. 그래서 조회 층에서 목표를\n * 실어 오고, 판정은 순수 함수(`judgeTargets`)가 한다.\n *\n * **목표가 없으면 판정하지 않는다** — 필드 자체를 만들지 않는다. 전부 \"목표 없음\" 으로 채우면\n * 화면이 회색 노이즈가 되고, 목표가 있는 지표가 묻힌다.\n */\n /* 분포는 **한 번만** 계산한다 — 창마다 조회가 붙으므로 두 번 부르면 조회가 두 배가 된다. */\n const baseline = await computeBaseline()\n\n /* 폴드가 돌려준 raw 창(ms)은 버린다 — 위의 ISO 창이 사람이 읽을 정본이다. */\n const { window: _rawWindow, groups: _rawGroups, ...rest } = kpi\n /*\n * 자리에 **부르는 이름**을 붙인다 — `feeder-2` 는 id 이고 사람은 「가공 라인」으로 부른다.\n * 이름을 모르는 자리는 id 그대로 둔다(지어내지 않는다).\n */\n /* 단가가 갈려 있으면 밝힌다 — 우리가 고른 하나로 낸 금액이라는 사실을 화면이 말할 수 있게. */\n if (rest.energy?.cost && tariffFound.conflict) rest.energy = { ...rest.energy, tariffConflict: true } as any\n if (rest.energy?.byFeeder?.length) {\n rest.energy = {\n ...rest.energy,\n byFeeder: rest.energy.byFeeder.map(row => ({ ...row, ...(feederMap.labels[row.key] ? { label: feederMap.labels[row.key] } : {}) }))\n }\n }\n /*\n * 계통도 — **선언된 관계와 계량이 덮는 범위**를 함께 낸다.\n *\n * 값은 여기서 다시 계산하지 않는다(폴드가 낸 분기별 몫을 그대로 노드에 얹는다). 계통은 모델이 아는\n * 사실이므로 저널만 보는 폴드가 아니라 여기서 조립한다 — 화면이 모델을 따로 또 조회하지 않아도 되고,\n * AI 도 같은 값을 읽는다(규칙 한 벌).\n */\n if (rest.energy) {\n const topo = await energyTopologyOf(input.domainId, instanceIds, rest.energy)\n if (topo) rest.energy = { ...rest.energy, topology: topo } as any\n }\n return {\n window,\n measured: true,\n scope,\n ...(capped || prevCappedFlag ? { eventsCapped: true } : {}),\n ...rest,\n ...(buckets ? { buckets } : {}),\n ...(groups ? { groups } : {}),\n ...(verdicts.length ? { targets: verdicts } : {}),\n ...(comparison ? { comparison } : {}),\n ...(baseline ? { baseline } : {})\n }\n}\n"]}
1
+ {"version":3,"file":"kpi-query.js","sourceRoot":"","sources":["../../server/engine/kpi-query.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 업무 KPI 조회 — **창 해소 + 저널 조회 + 폴드**를 한 곳에.\n *\n * 왜 한 곳인가: 처음에 GraphQL 질의와 AI 도구가 각각 같은 조회를 적었다. 그러면 창 규칙이 두 곳에서\n * 갈라지고(한쪽만 고치면 화면과 AI 가 다른 숫자를 말한다) 조회 상한·테넌트 조건도 복제된다.\n * 계산은 `kpi-fold`(순수)가 소유하고, 이 파일은 **무엇을 읽을지**만 정한다.\n *\n * ── 창의 기준 시각은 \"트윈의 시계\" 다 ──────────────────────────────────────\n * 시뮬 트윈은 자기 클록으로 산다(예: 저널의 eventTime 이 2026-01-01T00:25Z 인데 실제 지금은 7월).\n * 그런 트윈에 \"최근 1시간\" 을 **실제 시각**으로 물으면 그 구간에는 아무 기록이 없어 항상\n * \"측정된 기록 없음\" 이 된다 — 폴드는 정상인데 답이 쓸모없다.\n *\n * 그래서 호출부가 끝 시각을 주지 않으면 **그 트윈의 마지막 기록 시각**을 끝으로 잡는다. 커널의 시각\n * 상수를 앱에서 재현하지 않고(무방언), 라이브 트윈에서도 자연스럽다(마지막 기록 ≈ 지금).\n * 어떤 기준을 썼는지는 결과의 `window.basis` 로 밝힌다 — 숨기면 사용자가 숫자를 오해한다.\n */\nimport { twinWarn } from './log.js'\nimport { Between, In } from 'typeorm'\n\nimport { hierarchyOf, levelOfLocationType, electricalUpstreamOf, EMS_LOCATION_TYPES, EMS_PROPERTY, type TariffDeclaration } from '@operato/twin-kernel'\n\nimport { getReadRepository, getRepository } from '@things-factory/shell'\n\nimport { TwinEvent } from '../service/twin-event/twin-event.js'\nimport { TwinInstance } from '../service/twin-instance/twin-instance.js'\nimport { TwinArea } from '../service/twin-space/twin-area.js'\nimport { TwinSpace } from '../service/twin-space/twin-space.js'\nimport { TwinTarget } from '../service/twin-target/twin-target.js'\nimport { TWIN_METRIC, judgeTargets, judgeGroupTargets, scopedTargets, type ScopedTargets, type TargetVerdict, type TwinMetricId } from './kpi-target.js'\nimport { TwinEngine } from './twin-engine.js'\nimport { buildEnergyTopology, type EnergyTopology } from './energy-topology.js'\nimport {\n crossGroups,\n deltaP50,\n foldKpi,\n foldKpiBuckets,\n shiftWindow,\n type CrossedGroup,\n type KpiEvent,\n type KpiGroupBy,\n type KpiResult\n} from './kpi-fold.js'\nimport { MAX_BASELINE_PERIODS, baselineWindows, summarizeBaseline, type BaselineStride, type MetricBaseline } from './kpi-baseline.js'\n\n/**\n * 업무 전이만 읽는다 — 설비 상태(equipment.status)는 OEE 누적기의 몫이고 여기선 쓰지 않는다.\n *\n * **에너지 구간 마감**은 함께 읽는다(2026-08-14): 원단위는 분자(전력량)와 분모(산출)를 **같은 창**에서\n * 나눈 값이라, 두 사실이 한 폴드에 들어와야 창이 어긋날 수 없다. 표본(`energy.measured`)은 읽지 않는다 —\n * 저널에 없고(§4.1 인제스트 계약) 여기서 필요한 것은 마감된 구간뿐이다.\n */\nconst BUSINESS_EVENTS = ['task.status', 'order.status', 'energy.demand.window']\n\n/** 한 번에 접을 이벤트 상한 — 큰 구간이 서버를 붙잡지 않게. */\nconst MAX_EVENTS = 20000\n\nexport interface TwinKpiInput {\n domainId: string\n /** 한 트윈. `spaceId` 와 둘 중 하나는 있어야 한다. */\n instanceId?: string\n /**\n * 한 **공간** — 그 공간에서 가동 중인 트윈 전부를 **한 번에 접는다**.\n *\n * 트윈별로 접어 평균을 다시 평균하면 분위수가 거짓이 된다(p90 의 평균은 p90 이 아니다).\n * 그래서 이벤트를 모아 **한 번** 접는다. 사용자는 \"부산 창고\" 를 보고, 그 안에 트윈이 몇 개든\n * 합쳐서 본다(헤더 트윈 셀렉터를 없앤 결정과 같은 규율).\n */\n spaceId?: string\n /** 창의 끝(ISO). 미지정이면 **그 트윈의 마지막 기록 시각**. */\n toTime?: string\n /** 창의 시작(ISO). 미지정이면 끝에서 windowMinutes 만큼 뒤로. */\n fromTime?: string\n /** 창 길이(분). fromTime 이 있으면 무시. 기본 60, 1~1440. */\n windowMinutes?: number\n /** 시작 이벤트를 찾기 위해 창보다 앞으로 더 읽는 시간(분). 기본 60, 0~1440. */\n lookbackMinutes?: number\n /**\n * 창을 N 등분해 **구간별 값도** 함께 낸다(추세용). 1~48, 기본 0(쪼개지 않음).\n * 조회는 한 번이고 접기만 N 번 반복한다 — 폴드가 순수하므로 값싸다.\n */\n buckets?: number\n /**\n * 같은 사실을 **다른 관점으로** 쪼갠다: 자원별·작업종류별·도착지점별·오더별·구역별.\n *\n * 고정된 한 렌즈만 낼 수 있으면 \"구역별로는 어떤가\" 라는 질문에 화면도 AI 도 답할 수 없다.\n * 축은 조회 인자 하나이고, 접기는 순수 함수라 축을 하나 더 보는 값이 거의 없다.\n */\n groupBy?: KpiGroupBy\n /** 축의 상한(기본 20, 최대 100). 넘치면 잘라내고 잘린 수를 함께 알린다. */\n groupLimit?: number\n /**\n * **무엇과 비교할 것인가** — 같은 길이의 앞 구간(previous)이나 하루 전 같은 시간(yesterday).\n *\n * 숫자 하나로는 좋아졌는지 나빠졌는지 알 수 없다(\"29초\" 가 개선인지 악화인지). 창만 옮겨 한 번 더\n * 접으면 되므로(폴드는 순수) 값싸다. 비교 구간에 기록이 없으면 **delta 를 만들지 않는다** —\n * 없는 것을 0% 로 그리면 \"변화 없음\" 이라는 거짓이 된다.\n */\n compareTo?: 'previous' | 'yesterday'\n /**\n * **여러 구간 동시 비교** — 최근 N 구간의 같은 시간대를 함께 접어 **분포**와 그 안의 자리를 낸다.\n *\n * `compareTo` 는 한 구간만 봐서 판단이 \"어제보다 나아졌다\" 에서 멈춘다 — 어제도 나빴으면 쓸모가 없다.\n * 최근 며칠의 같은 시간대를 보면 목표 없이도 \"지난 7일 중 가장 낮다\" 를 말할 수 있다.\n *\n * `stride: 'week'` 는 같은 요일을 본다(주말·평일이 섞이지 않게). `periods` 는 1~14\n * (창마다 조회가 한 번 더 붙는다 — 값을 공짜로 말하지 않는다).\n *\n * `compareTo` 와 함께 쓸 수 있다: 다른 질문이기 때문이다(직전 한 구간 vs 최근 분포).\n */\n baseline?: { periods: number; stride?: BaselineStride }\n}\n\nexport interface TwinKpiOutput extends Omit<Partial<KpiResult>, 'window'> {\n /** 사람이 읽는 창(ISO) + 기준. 폴드의 raw 창(ms)은 여기에 흡수한다. */\n window: {\n fromTime: string\n toTime: string\n minutes: number\n /** 끝 시각을 어디서 얻었나 — 'given'(호출부 지정) | 'latest-event'(트윈의 마지막 기록). */\n basis: 'given' | 'latest-event'\n }\n /**\n * 조회가 **상한에 걸렸는가**(구간이 너무 커서 일부만 읽었을 수 있다).\n *\n * true 면 이 구간의 숫자는 **하한**이다 — 실제로는 더 있었을 수 있다. 조용히 잘라내면 숫자가 조용히\n * 작아지고 사용자는 그것을 성과 하락으로 읽는다. 구간을 좁혀 다시 물어야 한다.\n */\n eventsCapped?: boolean\n /**\n * 이 구간에 **측정할 기록이 있었는가**. false 는 \"처리량 0\" 이 아니라 **\"기록이 없다\"** 는 뜻이다 —\n * 두 상황은 완전히 다르고, 섞으면 사용자가 잘못된 결론을 낸다.\n */\n measured: boolean\n note?: string\n /** 무엇을 집계했나 — 공간이면 합쳐 접은 트윈 목록을 밝힌다(숫자의 출처를 숨기지 않는다). */\n /**\n * 무엇을 집계했나 — 공간이면 합쳐 접은 트윈 목록.\n *\n * `restartPolicies` 는 **이 숫자가 무엇의 성과인지**를 말한다: mirror(실제 시스템을 반영) ·\n * `resume`(이어지는 가상 세계) · `reset`(재현 실험 사본). 같은 \"345건\" 이 현실의 실적일 수도, 시뮬레이션의\n * 산출일 수도 있다 — 그 구별 없이 숫자만 보여주면 사용자가 시뮬 결과를 실적으로 착각한다.\n */\n scope: { spaceId?: string; instanceIds: string[]; restartPolicies: string[] }\n /** 구간별 값(요청했을 때만) — 추세를 그리기 위한 최소 정보. */\n buckets?: { fromTime: string; toTime: string; tasks: number; orders: number; workP50Ms: number }[]\n /**\n * **목표 대비 판정** — 숫자를 판단으로 바꾸는 값(§kpi-target).\n *\n * 목표가 걸린 지표만 들어온다. 목표가 없거나 그 구간에 값이 없는 지표는 **여기 나타나지 않는다** —\n * 전부 \"목표 없음\" 으로 채우면 화면이 회색 노이즈가 되고, 없는 값을 0 으로 놓으면 \"목표 크게 미달\"\n * 이라는 없는 사실이 만들어진다.\n */\n targets?: TargetVerdict[]\n /**\n * 비교 구간(요청했을 때만) — 같은 규칙·같은 길이로 접은 앞 구간과의 차이.\n *\n * `measured:false` 면 그 구간에 기록이 없다는 뜻이고 `delta` 는 없다(0 이 아니다).\n * delta 는 **현재 − 비교구간** 이다. 좋고 나쁨은 판단하지 않는다 — 처리량은 클수록 좋고 소요는\n * 작을수록 좋다는 해석은 소비처(화면·AI)의 몫이고, 계산 층이 판단을 섞으면 목표 없이 판정하게 된다.\n */\n comparison?: {\n basis: 'previous' | 'yesterday'\n window: { fromTime: string; toTime: string }\n measured: boolean\n throughput?: { tasks: number; orders: number }\n workTime?: KpiResult['workTime']\n leadTime?: KpiResult['leadTime']\n waitTime?: KpiResult['waitTime']\n delta?: { tasks: number; orders: number; workP50Ms?: number; leadP50Ms?: number; waitP50Ms?: number }\n }\n /**\n * **여러 구간 분포**(요청했을 때만) — 지표별로 최근 N 구간의 분포와 지금의 자리.\n *\n * `samples` 는 **요청한 창 전부**다(기록 없는 창도 `measured:false` 로 남는다) — 빼고 말하면\n * \"지난 7일\" 이 실제로는 3일이었다는 것을 아무도 모른다. 자리를 말할 수 없으면 `noPosition` 이\n * 이유를 밝힌다(표본 부족·현재 값 없음). 방향은 지표의 성질이라 `standing` 이 그것을 적용한 읽기다.\n */\n baseline?: {\n stride: BaselineStride\n /** 요청한 과거 구간 수(상한 적용 후). */\n periods: number\n metrics: MetricBaseline[]\n /** 과거 창 조회 중 하나라도 상한에 걸렸나 — 그 창의 값은 하한이라 분포가 낮게 보일 수 있다. */\n capped?: boolean\n }\n /**\n * 관점별 값(요청했을 때만). `key` 는 식별자, `label` 은 사람이 읽는 이름(있을 때만 — 구역은 이름이\n * 따로 저장되어 있다). `truncated` 는 상한 때문에 빠진 축의 수다.\n */\n groups?: {\n by: KpiGroupBy\n items: (KpiGroupOut & { label?: string })[]\n /**\n * 비교 구간에는 일이 있었는데 **지금은 없는** 축. 표에서 그냥 빠지면 멈춘 구역·대기 중인 자원이\n * 눈에 보이지 않는다 — 운영에서는 이것이 가장 중요한 신호일 수 있다.\n */\n disappeared?: { key: string; label?: string; tasks: number }[]\n /** 상한 때문에 빠진 축의 수와, 그 축들이 안고 있던 건수(둘 다 알려야 꼬리를 놓치지 않는다). */\n truncated?: number\n truncatedTasks?: number\n note?: string\n }\n}\n\n/** 축 하나의 값 — 폴드의 KpiGroup 과 같은 모양(시각 변환이 없어 그대로 통과한다). */\ntype KpiGroupOut = NonNullable<KpiResult['groups']>['items'][number] &\n Partial<Pick<CrossedGroup, 'prevTasks' | 'deltaTasks' | 'deltaWorkP50Ms' | 'isNew'>>\n\n/**\n * 노드 → 구역 지도. **저널에는 구역이 없다**(노드까지만 있다) — 모델 정의의 `locations[].parentId` 가\n * 마스터 계층에서 온 소속이다. 기동하지 않은 트윈도 모델은 남아 있어 이 경로가 가장 튼튼하다.\n *\n * **한 홉만 보면 안 된다.** `parentId` 는 구역일 수도 있고 중간 단(라인·셀)일 수도 있다. 라인이 끼면\n * 한 홉짜리 코드는 \"구역이 LINE-1\" 이라고 답하고, 그 id 는 구역 목록에 없으므로 그 아래 스테이션이\n * **집계에서 조용히 빠진다**(에러 없이 틀린 숫자). 그래서 커널 `hierarchyOf` 로 사슬을 끝까지 걷는다 —\n * 걷는 규칙은 커널에 한 벌뿐이고 소비처는 그것만 부른다.\n */\n/**\n * 교대 축의 재료 — **교대 선언과 현장의 시각 기준.**\n *\n * 저널에는 시각만 있고 교대가 없다(일부러 그렇게 뒀다 — 교대는 파생이라 찍어 두면 나중에 교대 시간을\n * 고쳤을 때 과거가 옛 구분으로 굳는다). 그래서 폴드가 계산할 재료를 여기서 모은다.\n *\n * 선언의 출처는 **모델의 설비**다. 여러 설비가 서로 다른 교대를 가질 수 있지만, 축은 하나여야 하므로\n * **가장 많이 쓰이는 선언 한 벌**을 쓴다 — 3교대 공장은 전 설비가 같은 교대이므로 이것이 곧 정답이고,\n * 자원마다 교대가 다른 현장에서는 이 축이 거칠다는 뜻이다(그 사실을 여기 적어 둔다).\n *\n * 시각 기준은 모델에 실려 있다(`utcOffsetMinutes` — 공간이 갖는 값을 기동 시 호스트가 풀어 넣는다).\n * 없으면 UTC 로 읽고, 그 기본값은 커널 계약에 밝혀져 있다.\n */\n/**\n * 이 범위에 걸린 운영 목표 — **좁은 선언이 넓은 선언을 덮는다.**\n *\n * 공간 전체 목표 위에 인스턴스 목표를 얹을 수 있어야 예외를 표현할 수 있다(\"이 라인만 다르다\").\n * 같은 지표에 둘 다 있으면 인스턴스가 이긴다.\n */\nasync function loadTargets(\n domainId: string,\n spaceId: string | undefined,\n instanceIds: string[],\n axis?: string\n): Promise<ScopedTargets> {\n /* 조회는 **가져오기만** 한다 — 어느 목표가 이기는지는 순수 함수(`scopedTargets`)가 정한다.\n 그 규칙이 DB 접근 뒤에 있으면 화면을 눌러 봐야만 확인된다(그리고 실제로 확인되지 않았다). */\n const rows = await getRepository(TwinTarget).find({ where: { domain: { id: domainId } } }).catch(() => [])\n return scopedTargets(rows, { spaceId, instanceIds, axis })\n}\n\nasync function shiftContext(\n domainId: string,\n instanceIds: string[]\n): Promise<{ shifts?: any[]; utcOffsetMinutes?: number }> {\n if (!instanceIds.length) return {}\n const rows = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, instanceId: In(instanceIds) } })\n const tally = new Map<string, { decl: any[]; n: number }>()\n let utcOffsetMinutes: number | undefined\n for (const row of rows) {\n const model = row.model as any\n if (utcOffsetMinutes === undefined && typeof model?.utcOffsetMinutes === 'number') utcOffsetMinutes = model.utcOffsetMinutes\n for (const e of (model?.equipment ?? []) as any[]) {\n const decl = e?.workCalendar\n if (!Array.isArray(decl) || !decl.length) continue\n const key = JSON.stringify(decl)\n const hit = tally.get(key)\n if (hit) hit.n++\n else tally.set(key, { decl, n: 1 })\n }\n }\n let best: { decl: any[]; n: number } | undefined\n for (const v of tally.values()) if (!best || v.n > best.n) best = v\n return { ...(best ? { shifts: best.decl } : {}), ...(utcOffsetMinutes !== undefined ? { utcOffsetMinutes } : {}) }\n}\n\nasync function locationAreaMap(\n domainId: string,\n instanceIds: string[],\n spaceId?: string\n): Promise<{ locationArea: Record<string, string>; labels: Record<string, string> }> {\n const locationArea: Record<string, string> = {}\n if (instanceIds.length) {\n const rows = await getRepository(TwinInstance).find({\n where: { domain: { id: domainId }, instanceId: In(instanceIds) }\n })\n for (const row of rows) {\n const locations = (((row.model as any)?.locations ?? []) as any[]).filter(n => n?.id)\n if (!locations.length) continue\n /* 순환은 hierarchyOf 가 오류를 낸다 — 한 트윈의 잘못된 계층이 다른 트윈의 집계까지 죽이지 않게 여기서 가둔다. */\n let h\n try {\n /*\n * 단 해석기를 함께 넘긴다 — 자리가 `level` 을 적지 않으면 **타입이 답한다**(카탈로그 선언).\n * 이 집계는 지금 구역 롤업(`rollupOf`)만 쓰므로 결과가 달라지지 않는다. 그래도 여기서\n * 잇는다: 단으로 묻는 질의(`ancestorOfLevel`)가 생길 때 배선을 다시 찾지 않게 한다.\n */\n h = hierarchyOf({ locations }, t => levelOfLocationType(t, row.kind))\n } catch (e: any) {\n twinWarn(`[twin-kpi] \"${row.instanceId}\": location hierarchy is broken — area rollup skipped for this twin: ${e?.message}`)\n continue\n }\n for (const n of locations) {\n const area = h.rollupOf(String(n.id))\n if (area) locationArea[String(n.id)] = area // 구역 미상은 넣지 않는다 — 없는 구역을 만들지 않는다\n }\n }\n }\n\n const labels: Record<string, string> = {}\n const sid = spaceId\n if (sid) {\n const space = await getRepository(TwinSpace).findOne({ where: { domain: { id: domainId }, spaceId: sid } })\n if (space) {\n const areas = await getRepository(TwinArea).find({\n where: { domain: { id: domainId } as any, space: { id: space.id } as any }\n })\n for (const a of areas) if (a.areaId && a.name) labels[a.areaId] = a.name\n }\n }\n return { locationArea, labels }\n}\n\n/**\n * 자원 → **배전 계통의 자리**(분기·수전) 지도와 그 자리의 이름.\n *\n * ── 무엇을 읽나 ─────────────────────────────────────────────────────────────\n * 에너지 트윈의 모델이 「이 설비가 어느 자리에 매달렸나」를 선언한다(`homeLocation`). 그 자리가 분기가\n * 아니라 하위 자리라면 부모를 타고 올라가 **전기 계통의 자리**를 찾는다 — 전기의 계통은 물리 구역과\n * 다른 나무이므로 구역 롤업(`locationAreaMap`)을 쓸 수 없다.\n *\n * ── 왜 에너지 트윈만 읽나 ───────────────────────────────────────────────────\n * 같은 설비가 공장 트윈에서는 자원(작업을 한다)이고 에너지 트윈에서는 부하(전기를 먹는다)다 — 사실 둘,\n * id 하나. 가동시간은 공장 트윈의 저널에서 오고, 전기 계통의 소속은 이 모델에서 온다. 그 둘을 id 로\n * 잇는 것이 이 지도의 일이다.\n *\n * 자리를 말하지 않는 설비는 **넣지 않는다** — 없는 소속을 만들면 그 몫이 엉뚱한 분기의 것이 된다.\n */\n/**\n * 선언된 **전기요금 단가** — 에너지 트윈의 자리 속성에서 읽는다(계약전력과 같은 자리).\n *\n * 여러 에너지 트윈이 한 현장에 있으면 **처음 선언된 것 하나**를 쓴다. 평균을 내거나 합치지 않는다:\n * 요금은 계약이고, 두 계약을 섞은 단가는 어느 계약도 아니다. 서로 다른 단가가 선언돼 있으면 그것을\n * 밝힌다(조용히 하나를 고르면 왜 그 금액인지 아무도 설명할 수 없다).\n */\nasync function tariffOf(\n domainId: string,\n instanceIds: string[]\n): Promise<{ tariff?: TariffDeclaration; conflict?: boolean }> {\n if (!instanceIds.length) return {}\n const rows = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, instanceId: In(instanceIds) } })\n const found: TariffDeclaration[] = []\n for (const row of rows) {\n if (row.kind !== 'ems') continue\n for (const loc of ((row.model as any)?.locations ?? []) as any[]) {\n const props = new Map<string, any>(((loc?.properties ?? []) as any[]).map(p => [String(p?.id), p?.value]))\n const num = (id: string): number | undefined => {\n const v = Number(props.get(id))\n return Number.isFinite(v) && v > 0 ? v : undefined\n }\n const d = num(EMS_PROPERTY.demandChargePerKW)\n const e = num(EMS_PROPERTY.energyChargePerKWh)\n const cur = props.get(EMS_PROPERTY.currency)\n if (d === undefined && e === undefined) continue\n found.push({\n ...(d !== undefined ? { demandChargePerKW: d } : {}),\n ...(e !== undefined ? { energyChargePerKWh: e } : {}),\n ...(cur ? { currency: String(cur) } : {})\n })\n }\n }\n if (!found.length) return {}\n const first = found[0]\n const same = found.every(\n t => t.demandChargePerKW === first.demandChargePerKW && t.energyChargePerKWh === first.energyChargePerKWh && t.currency === first.currency\n )\n return { tariff: first, ...(same ? {} : { conflict: true }) }\n}\n\nasync function resourceFeederMap(\n domainId: string,\n instanceIds: string[]\n): Promise<{ resourceFeeder: Record<string, string>; meterFeeder: Record<string, string>; labels: Record<string, string> }> {\n const resourceFeeder: Record<string, string> = {}\n /** 계량 지점 → 그것이 재는 분기. 있으면 그 분기 값은 **측정**이다(배분보다 강한 근거). */\n const meterFeeder: Record<string, string> = {}\n const labels: Record<string, string> = {}\n if (!instanceIds.length) return { resourceFeeder, meterFeeder, labels }\n\n const rows = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, instanceId: In(instanceIds) } })\n for (const row of rows) {\n if (row.kind !== 'ems') continue // 전기 계통을 선언하는 것은 에너지 트윈이다\n const model: any = row.model ?? {}\n const locations: any[] = (model.locations ?? []).filter((n: any) => n?.id)\n const byId = new Map(locations.map((n: any) => [String(n.id), n]))\n const electrical = new Set(EMS_LOCATION_TYPES as readonly string[])\n\n /**\n * 이 자리에서 **전기 상류**로 올라가 처음 만나는 계통의 자리. 없으면 undefined.\n *\n * 상류의 뜻은 커널이 읽는다(`electricalUpstreamOf`) — 공간 부모(공장·구역)를 따라 올라가면 전기적으로\n * 뜻이 없는 길을 걷게 되고, 옛 세대(분기의 `parentId` 에 수전)와 새 세대(`upstreamId`)가 섞여 있으므로\n * 그 판정을 여기서 다시 쓰면 두 벌이 된다.\n */\n const feederOf = (startId: string): string | undefined => {\n const seen = new Set<string>()\n let cur = byId.get(startId)\n while (cur && !seen.has(String(cur.id))) {\n seen.add(String(cur.id)) // 잘못된 모델의 순환이 여기서 멈춘다\n if (electrical.has(String(cur.type))) return String(cur.id)\n const up = electricalUpstreamOf(locations as any, String(cur.id))\n cur = up ? byId.get(up) : cur.parentId ? byId.get(String(cur.parentId)) : undefined\n }\n return undefined\n }\n\n for (const e of (model.equipment ?? []) as any[]) {\n const home = e?.homeLocation ?? e?.homeLocationId\n if (!e?.id || !home) continue\n /*\n * 계량기는 부하가 아니다 — 그것의 「몫」을 세면 총량이 이중으로 나뉜다. 대신 **어디를 재는지**를\n * 따로 모은다: 계량기가 분기에 있으면 그 분기 값은 배분이 아니라 **측정**이다(가장 강한 근거).\n */\n if (e.kind === 'meter') {\n const at = feederOf(String(home))\n /* 뿌리(수전)를 재는 계량기는 분기의 것이 아니다 — 그 값은 총량이고, 나머지는 뺄셈으로 나온다. */\n if (at && String(byId.get(at)?.type) !== 'incoming') meterFeeder[String(e.id)] = at\n continue\n }\n /* 차단기·태양광·축전지도 부하가 아니다. */\n if (e.kind === 'breaker' || e.kind === 'pv-array' || e.kind === 'battery') continue\n const feeder = feederOf(String(home))\n if (feeder) resourceFeeder[String(e.id)] = feeder\n }\n for (const n of locations) if (electrical.has(String(n.type)) && n.name) labels[String(n.id)] = String(n.name)\n }\n return { resourceFeeder, meterFeeder, labels }\n}\n\n/**\n * 이 공간의 에너지 트윈들의 계통도 — 모델을 읽어 트리로 접는다.\n *\n * 여러 트윈이 한 공간에 있으면 **각 트윈의 뿌리를 나란히** 둔다(하나로 합치면 없는 결선을 그리게 된다).\n * 에너지 트윈이 없으면 만들지 않는다 — 빈 그림은 고장으로 읽힌다.\n */\nasync function energyTopologyOf(\n domainId: string,\n instanceIds: string[],\n energy: { byFeeder?: { key: string; kWh: number; basis?: 'measured' | 'apportioned'; peakKW?: number }[] } | undefined\n): Promise<EnergyTopology | undefined> {\n if (!instanceIds.length) return undefined\n const rows = await getRepository(TwinInstance).find({ where: { domain: { id: domainId }, instanceId: In(instanceIds) } })\n const ems = rows.filter(r => r.kind === 'ems' && r.model)\n if (!ems.length) return undefined\n const merged: EnergyTopology = { roots: [], unplaced: [], unmetered: [], loadsOnIncoming: 0 }\n for (const row of ems) {\n /*\n * 지금의 설비 상태를 함께 넘긴다 — 발전·저장의 **지금 값**은 구간 폴드가 아니라 스냅샷이 안다.\n * 없으면(정지된 트윈) 구조만 그린다.\n */\n const snap = TwinEngine.snapshot(domainId, row.instanceId)\n const t = buildEnergyTopology(row.model, energy, snap?.equipment)\n merged.roots.push(...t.roots)\n merged.unplaced.push(...t.unplaced)\n merged.unmetered.push(...t.unmetered)\n merged.loadsOnIncoming += t.loadsOnIncoming\n }\n return merged.roots.length ? merged : undefined\n}\n\n/** 이 대상들의 마지막 업무 기록 시각(ISO). 기록이 없으면 null. */\nasync function latestEventTime(domainId: string, instanceIds: string[]): Promise<string | null> {\n if (instanceIds.length === 0) return null\n const row = await getRepository(TwinEvent).findOne({\n where: { domain: { id: domainId }, instanceId: In(instanceIds), eventType: In(BUSINESS_EVENTS) },\n order: { revision: 'DESC' }\n })\n /* 소비처는 ISO 문자열을 기대한다(시각 축의 공용 표기) — 컬럼이 날짜가 됐으므로 여기서 옮긴다. */\n return row?.eventTime ? row.eventTime.toISOString() : null\n}\n\n/**\n * 창 구간의 업무 이벤트 — 창보다 `lookbackMs` 만큼 앞까지 읽는다(완료된 작업의 시작이 창 밖일 수 있다).\n *\n * eventTime 은 날짜 컬럼이라 DB 가 시각으로 비교한다 — 예전 문자열 컬럼 시절의 \"UTC ISO 만 들어오면\n * 사전식 비교가 맞는다\" 는 전제가 필요 없다.\n */\nasync function fetchEvents(domainId: string, instanceIds: string[], fromMs: number, toMs: number, lookbackMs: number) {\n if (instanceIds.length === 0) return { events: [] as KpiEvent[], capped: false }\n /*\n * ── 필요한 세 컬럼만, 그리고 **읽기 연결로** (2026-08-22 실측) ─────────────\n * 이 읽기가 서버를 붙잡고 있었다. 실측(개발 DB 16.3GB · 저널 1,134만 행): 2만 행을 실어 오는 동안\n * 다른 가벼운 읽기가 **47.6초** 기다렸다. 로그에서 이 자리 뒤로 사용자·도메인 조회까지 30.9초로\n * 찍혔다 — TypeORM sqlite 드라이버는 연결을 하나만 들기 때문이다.\n *\n * **행 단위로 읽는 것 자체는 정당하다**: 이 폴드는 `p10·p50·p90` 을 낸다(§`kpi-fold`). 분위수는\n * 카운터로 만들 수 없고 트윈별 사전집계로도 만들 수 없다(p90 의 평균은 p90 이 아니다). 개별 값이\n * 있어야 한다.\n *\n * 정당하지 않은 것은 **읽는 방식**이었다. 세 가지를 고친다.\n *\n * ① **엔티티를 만들지 않는다.** 폴드가 쓰는 것은 `eventType`·`eventTime`·`payload` 뿐인데\n * `find()` 는 열여섯 컬럼을 실어 엔티티로 만들고 관계 로더를 지난다. CPU 프로파일에서\n * `RawSqlResultsToEntityTransformer` 와 `stringToSimpleJson` 이 상위였다.\n * ② **읽기 연결로 보낸다**(`getReadRepository`). 순수 읽기이므로 쓰기와 순서를 다툴 것이 없다.\n * 실측: 같은 2만 행 읽기에서 다른 읽기의 대기가 47.6초 → 2.8초.\n * ③ **정렬 축을 거르는 축과 맞춘다.** 여기가 이 자리의 진짜 비용이었다.\n *\n * ── `ORDER BY revision` 이 `LIMIT` 을 무력화했다 (실측) ────────────────────\n * 거르는 축은 `event_time`(창)이고 정렬 축은 `revision` 이었다. 그러면 sqlite 는 창에 맞는 행을\n * **전부** 찾아 `revision` 으로 **전부 정렬한 뒤** 상한을 적용한다 — 상한이 정렬 뒤에 걸리므로\n * 아무것도 줄여 주지 않는다. 실측이 그것을 그대로 보였다(인스턴스 하나가 375만 행):\n *\n * ORDER BY revision LIMIT 20000 → 32.2초 · LIMIT 5000 → 28.3초 · LIMIT 2000 → 28.2초\n * ORDER BY eventTime, revision LIMIT 20000 → 0.41초 (78배)\n *\n * 계획도 그렇게 말한다: 앞은 `USE TEMP B-TREE FOR ORDER BY`(전체 정렬), 뒤는\n * `USE TEMP B-TREE FOR LAST TERM OF ORDER BY` — 시각은 인덱스(`ix_twin_event_1`)에서 순서대로\n * 나오고 **동시각만** 임시 정렬한다. 즉 범위를 훑다가 상한에서 멈춘다.\n *\n * ── 순서의 뜻은 **완전히 같지는 않다** (측정해서 고친 문장) ────────────────\n * 처음에 「순서의 뜻은 같다」고 적었다. 재 보니 아니었다 — `revision` 순서와 `eventTime` 순서가\n * 어긋나는 자리가 실재한다(hatio-mx2: 426만 행 중 **7건** · rosarito-mes: 11만 중 8건). 미러는\n * 원본의 시각을 싣고 도착 순서로 리비전을 받으므로, 늦게 도착한 사건이 그 어긋남을 만든다.\n *\n * **그러나 폴드의 수치는 같다.** `foldTaskRecords`(커널 `task-fold.ts`)가 스트림 위치가 아니라\n * **시각으로** 판정한다:\n *\n * startedMs = Math.min(startedMs, at)\n * completedMs = Math.max(completedMs, at)\n *\n * `min`·`max` 이므로 행이 어떤 순서로 와도 답이 같다. 소요시간·분위수·처리량은 순서와 무관하다.\n *\n * 순서에 민감한 자리는 **하나**다: 그 폴드의 `location` 이 「마지막에 본 것」이다. 같은 밀리초에 같은\n * 작업의 자리가 두 번 바뀌면 어느 것이 남는지가 순서로 갈린다(라이브 미러가 같은 밀리초에 여러 행을\n * 보내는 경우다). 수치가 아니라 **표시되는 자리 하나**이고, 그래서 이 바꿈의 값이 그 위험보다 크다.\n *\n * `revision` 을 둘째 축으로 남기는 이유도 그것이다 — 동시각의 순서가 실행마다 달라지지 않게 한다.\n *\n * **상한을 낮추는 것으로는 고쳐지지 않는다**(위 숫자가 그것을 말한다). 정렬 축이 원인이다.\n *\n * 남는 비용을 감추지 않는다: `payload` 파싱은 여전히 메인 스레드 일이고 연결을 나눠도 나뉘지 않는다\n * (실측 평균 1.5초). 그것을 더 줄이려면 폴드가 payload 에서 무엇을 쓰는지 좁혀야 한다 — 별 건이다.\n */\n const raw = await getReadRepository(TwinEvent)\n .createQueryBuilder('e')\n .select(['e.eventType AS \"eventType\"', 'e.eventTime AS \"eventTime\"', 'e.payload AS \"payload\"'])\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId IN (:...instanceIds)', { instanceIds })\n .andWhere('e.eventType IN (:...types)', { types: BUSINESS_EVENTS })\n .andWhere('e.eventTime BETWEEN :from AND :to', { from: new Date(fromMs - lookbackMs), to: new Date(toMs) })\n .orderBy('e.eventTime', 'ASC')\n .addOrderBy('e.revision', 'ASC')\n .limit(MAX_EVENTS)\n .getRawMany<{ eventType: string; eventTime: unknown; payload: unknown }>()\n\n /* 상한에 정확히 닿으면 **더 있었을 수 있다** — 그 사실을 돌려준다. 조용히 잘라내면 숫자가 조용히\n * 작아지고, 사용자는 그것을 성과 하락으로 읽는다(가장 나쁜 종류의 거짓이다). */\n return {\n /* 폴드는 ISO 문자열 축으로 수행된다(kpi-fold) — 경계에서 한 번만 옮긴다. */\n events: raw.map(r => ({ eventType: r.eventType, eventTime: isoOf(r.eventTime), payload: jsonOf(r.payload) })),\n capped: raw.length >= MAX_EVENTS\n }\n}\n\n/**\n * 원시 결과의 시각을 ISO 로 — **드라이버마다 모양이 다르다.**\n *\n * 엔티티 경로에서는 TypeORM 이 `Date` 로 바꿔 주지만, 원시 결과는 드라이버가 준 그대로다(sqlite 는\n * `'2026-08-07 11:00:00.000'`, pg 는 `Date`). 경계에서 한 번 정규화한다 — 폴드는 ISO 문자열만 안다.\n */\nfunction isoOf(v: unknown): string | undefined {\n if (v == null) return undefined\n if (v instanceof Date) return v.toISOString()\n const s = String(v)\n /* sqlite 의 공백 구분 형식을 ISO 로 — 그대로 `Date` 에 주면 시간대 해석이 갈린다. */\n const d = new Date(s.includes('T') ? s : s.replace(' ', 'T') + (s.endsWith('Z') ? '' : 'Z'))\n return Number.isNaN(d.getTime()) ? undefined : d.toISOString()\n}\n\n/**\n * 원시 결과의 `payload` 를 객체로 — **엔티티 경로의 `simple-json` 변환을 여기서 한다.**\n *\n * 깨진 JSON 은 **버리지 않고 비운다**: 그 사건이 있었다는 사실은 남고, 안을 읽을 수 없다는 것만\n * 폴드에 전해진다(폴드는 없는 필드를 건너뛴다). 던지면 창 하나가 통째로 사라진다.\n */\nfunction jsonOf(v: unknown): any {\n if (v == null) return undefined\n if (typeof v !== 'string') return v\n try {\n return JSON.parse(v)\n } catch {\n return undefined\n }\n}\n\n/**\n * 집계 대상 트윈 목록.\n *\n * 공간이 주어지면 **가동 중이고 운영 목적인** 것만 모은다 — 벤치 사본은 사용자가 보는 현장이 아니다.\n * 트윈이 지정되면 그것 하나. 둘 다 없으면 무엇을 재야 할지 알 수 없으므로 명시 실패한다.\n */\nasync function resolveTargets(input: TwinKpiInput): Promise<{ instanceIds: string[]; restartPolicies: string[] }> {\n if (!input.instanceId && !input.spaceId) throw new Error('either instanceId or spaceId is required')\n const list = await TwinEngine.list(input.domainId)\n const rows = input.instanceId\n ? list.filter((x: any) => x?.instanceId === input.instanceId)\n : list.filter((x: any) => x?.running && x.spaceId === input.spaceId && x.purpose !== 'bench' && x.instanceId)\n /* 목록에서 못 찾아도(정지·비등록) 지정된 트윈은 그대로 조회한다 — 저널은 남아 있을 수 있다. */\n const instanceIds = rows.length ? rows.map((x: any) => x.instanceId as string) : input.instanceId ? [input.instanceId] : []\n /* 컬럼이 NOT NULL 이라 'unknown' 으로 메울 일이 없다 — 비면 계약이 깨진 것이지 모르는 상태가 아니다. */\n const restartPolicies = [...new Set(rows.map((x: any) => String(x.restartPolicy)))]\n return { instanceIds, restartPolicies }\n}\n\n/**\n * 대상 해소 공개 창구 — **저널 목록 질의도 KPI 와 같은 규칙을 써야 한다.**\n *\n * 화면에서 \"이 공간\" 은 하나의 뜻이어야 한다. 성과 화면과 원장 화면이 각자 co-located 트윈을\n * 추리면 같은 공간을 보면서 다른 대상 집합을 세게 되고, 두 숫자가 어긋나는 이유를 아무도 설명하지 못한다.\n */\nexport async function resolveTwinTargets(domainId: string, instanceId?: string, spaceId?: string): Promise<string[]> {\n const { instanceIds } = await resolveTargets({ domainId, instanceId, spaceId } as TwinKpiInput)\n return instanceIds\n}\n\n/**\n * 시간창 업무 KPI. 창 해소 → 저널 조회 → 순수 폴드.\n *\n * 테넌트 조건은 호출부가 준 `domainId` 로만 좁힌다(클라이언트 값을 그대로 쓰지 않는 것은 호출부 책임).\n */\nexport async function computeTwinKpi(input: TwinKpiInput): Promise<TwinKpiOutput> {\n const minutes = Math.max(1, Math.min(24 * 60, Number(input.windowMinutes) || 60))\n const lookbackMs = Math.max(0, Math.min(24 * 60, input.lookbackMinutes ?? 60)) * 60_000\n\n const { instanceIds, restartPolicies } = await resolveTargets(input)\n const scope = { spaceId: input.spaceId, instanceIds, restartPolicies }\n\n /* 끝 시각 — 지정이 없으면 대상들의 마지막 기록. 그것도 없으면 측정할 것이 없다. */\n let basis: 'given' | 'latest-event' = 'given'\n let toMs = input.toTime ? Date.parse(input.toTime) : NaN\n if (!Number.isFinite(toMs)) {\n const latest = await latestEventTime(input.domainId, instanceIds)\n if (!latest) {\n const now = Date.now()\n return {\n window: {\n fromTime: new Date(now - minutes * 60_000).toISOString(),\n toTime: new Date(now).toISOString(),\n minutes,\n basis: 'latest-event'\n },\n measured: false,\n scope,\n note:\n instanceIds.length === 0\n ? 'no running twin in this space — nothing to measure.'\n : 'no operational events in the journal yet — there is nothing to measure (this is NOT \"zero throughput\").'\n }\n }\n toMs = Date.parse(latest)\n basis = 'latest-event'\n }\n\n const fromMs = input.fromTime ? Date.parse(input.fromTime) : toMs - minutes * 60_000\n if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || fromMs >= toMs) {\n throw new Error('invalid window: fromTime must be a valid time before toTime')\n }\n\n const { events, capped } = await fetchEvents(input.domainId, instanceIds, fromMs, toMs, lookbackMs)\n\n /* 구역·교대 축은 **이벤트 밖의 지식**을 필요로 한다 — 다른 축은 payload 에서 바로 나온다. */\n const areaMap =\n input.groupBy === 'area' ? await locationAreaMap(input.domainId, instanceIds, input.spaceId) : { locationArea: {}, labels: {} }\n const shiftCtx = input.groupBy === 'shift' ? await shiftContext(input.domainId, instanceIds) : {}\n /*\n * 전기 계통 소속은 **축과 무관하게** 읽는다 — 분기별 몫은 관점 축이 아니라 에너지 사실의 일부라\n * (같은 창의 총량을 나눈 것) 어느 축을 보고 있든 함께 나가야 한다.\n */\n const feederMap = await resourceFeederMap(input.domainId, instanceIds)\n /* 요금 단가도 축과 무관하게 읽는다 — 금액은 같은 창의 전력량·최대수요에서 나오는 사실이다. */\n const tariffFound = await tariffOf(input.domainId, instanceIds)\n const kpi = foldKpi(events, { fromMs, toMs }, {\n groupBy: input.groupBy,\n locationArea: areaMap.locationArea,\n resourceFeeder: feederMap.resourceFeeder,\n meterFeeder: feederMap.meterFeeder,\n ...(tariffFound.tariff ? { tariff: tariffFound.tariff } : {}),\n ...shiftCtx,\n groupLimit: input.groupLimit\n })\n /*\n * **완료로 세지 않은 오더 상태 낱말을 말한다** (2026-08-23).\n *\n * 폴드는 순수하다(로그를 모른다) — 그래서 부르는 쪽이 말한다. 왜 말해야 하나: 이 폴드는 오더의\n * 완료를 **동의어 목록**으로 판정하고, 그것은 원본의 낱말에서 뜻을 추론하는 것이다. 커넥터가\n * 자기 표로 옮기지 않으면 그 낱말이 목록에 없어 **완료가 조용히 0 으로 세어진다.**\n *\n * 실제로 그랬다: 첫 실 원본의 `FINISHED` 가 목록에 없어 완료 오더가 전부 0 이었고, 사람이 화면을\n * 보고 의심해서야 드러났다(그 커넥터에는 상태 표가 정의돼 **있으면서** 오더 경로에서 쓰이지 않았다).\n * 그때 목록에 낱말을 더한 것은 그 사실을 가린 땜빵이었다.\n *\n * 그러니 이 로그는 「목록을 늘려라」가 아니라 **「그 커넥터가 옮기지 않았다」**를 뜻한다. 화면을 보지\n * 않는 사람도 알아야 하므로 로그에 남긴다 — 조용한 0 이 이 시스템에서 가장 비싼 침묵이다.\n *\n * 완료가 하나도 없을 때만 말한다: `picking` 처럼 완료가 아닌 낱말이 섞여 있는 것은 정상이고,\n * 그것까지 매번 경고하면 진짜 신호가 묻힌다.\n */\n const notCounted = Object.entries(kpi.orderStatusNotCounted ?? {})\n if (notCounted.length && kpi.throughput.orders === 0) {\n twinWarn(\n `[twin-kpi] no order counted as completed in this window, but these order status words were seen: ` +\n `${notCounted.map(([w, n]) => `${w}×${n}`).join(' · ')} — the connector may not be translating them ` +\n `to the kernel's vocabulary (see its own status table).`\n )\n }\n /*\n * 목표를 **폴드 뒤, 그룹 조립 앞**에 읽는다 — 전체 판정과 축별 판정이 같은 목록에서 나와야\n * 두 곳의 임계가 갈라지지 않는다. 폴드 자체는 목표를 모른다(사실 층과 판정 층의 분리).\n */\n const scoped = await loadTargets(input.domainId, input.spaceId, instanceIds, input.groupBy)\n const verdicts = judgeTargets(kpi, scoped.overall)\n const groupTargets = scoped.byAxisKey\n\n const window = {\n fromTime: new Date(fromMs).toISOString(),\n toTime: new Date(toMs).toISOString(),\n minutes: Math.round((toMs - fromMs) / 60_000),\n basis\n }\n /**\n * 여러 구간 분포를 만든다 — **현재 창에 기록이 없어도 낸다.**\n *\n * 그때가 오히려 가장 알고 싶은 순간이다(\"지금 아무 기록이 없는데 평소엔 60건이었다\" = 멈춘 신호).\n * 그래서 조기 반환보다 앞에 두고 두 경로가 함께 쓴다.\n */\n const computeBaseline = async (): Promise<TwinKpiOutput['baseline']> => {\n if (!input.baseline || !(input.baseline.periods > 0)) return undefined\n const stride = input.baseline.stride ?? 'day'\n const windows = baselineWindows(fromMs, toMs, input.baseline.periods, stride)\n let baseCapped = false\n const samples: { fromMs: number; toMs: number; values: Partial<Record<TwinMetricId, number | undefined>> }[] = []\n\n for (const w of windows) {\n const { events: pastEvents, capped: pastCapped } = await fetchEvents(input.domainId, instanceIds, w.fromMs, w.toMs, lookbackMs)\n baseCapped = baseCapped || pastCapped\n /* 축 없이, **같은 교대 정의**로 접는다(정의가 다르면 같은 시간대가 아니게 된다). */\n const past = foldKpi(pastEvents, w, { locationArea: areaMap.locationArea, ...shiftCtx })\n const values: Partial<Record<TwinMetricId, number | undefined>> = {}\n for (const metric of Object.keys(TWIN_METRIC) as TwinMetricId[]) values[metric] = TWIN_METRIC[metric].of(past)\n samples.push({ fromMs: w.fromMs, toMs: w.toMs, values })\n }\n\n const current: Partial<Record<TwinMetricId, number | undefined>> = {}\n for (const metric of Object.keys(TWIN_METRIC) as TwinMetricId[]) current[metric] = TWIN_METRIC[metric].of(kpi)\n\n return {\n stride,\n periods: windows.length,\n metrics: summarizeBaseline(TWIN_METRIC, current, samples),\n ...(baseCapped ? { capped: true } : {})\n }\n }\n\n\n if (kpi.eventsInWindow === 0) {\n const baseline = await computeBaseline()\n return {\n window,\n measured: false,\n scope,\n note: 'no operational events in this window — nothing to measure (this is NOT \"zero throughput\").',\n ...(baseline ? { baseline } : {})\n }\n }\n\n /* 구간별 값 — 쪼개기 규칙은 순수 모듈이 소유한다(테스트 가능). 여기서는 시각을 ISO 로 옮기기만. */\n const rawBuckets = foldKpiBuckets(events, { fromMs, toMs }, Number(input.buckets) || 0)\n const buckets = rawBuckets.length\n ? rawBuckets.map(b => ({\n fromTime: new Date(b.fromMs).toISOString(),\n toTime: new Date(b.toMs).toISOString(),\n tasks: b.tasks,\n orders: b.orders,\n workP50Ms: b.workP50Ms\n }))\n : undefined\n\n /* 비교 구간 — **같은 길이·같은 규칙**으로 한 번 더 접는다. 기록이 없으면 delta 를 만들지 않는다\n * (없는 것을 0 으로 그리면 \"변화 없음\" 이라는 거짓이 된다). */\n let comparison: TwinKpiOutput['comparison']\n /* 관점 × 비교 교차 — 축별 delta 와 **사라진 축**(전에 있었고 지금 없는 것). */\n let crossed: ReturnType<typeof crossGroups> | undefined\n /* 비교 구간 조회도 상한에 걸릴 수 있다 — 그 경우 delta 가 실제보다 작아 보인다. */\n let prevCappedFlag = false\n if (input.compareTo === 'previous' || input.compareTo === 'yesterday') {\n const w = shiftWindow(fromMs, toMs, input.compareTo)\n const { events: prevEvents, capped: prevCapped } = await fetchEvents(\n input.domainId,\n instanceIds,\n w.fromMs,\n w.toMs,\n lookbackMs\n )\n /* 축이 요청됐으면 비교 구간도 **같은 축**으로 접는다 — 교차(\"구역별로 어제 대비\")를 내기 위해.\n * 상한은 축이 서로 어긋나지 않게 넉넉히 둔다(현재 축이 상한 안에 있는데 비교 축에서 밀려나면\n * 있는데 없다고 말하게 된다). */\n const prev = foldKpi(prevEvents, w, {\n groupBy: input.groupBy,\n locationArea: areaMap.locationArea,\n ...shiftCtx, // 비교 구간도 **같은 교대 정의**로 접는다(정의가 다르면 축이 어긋난다)\n groupLimit: input.groupBy ? 100 : undefined\n })\n prevCappedFlag = prevCapped\n if (input.groupBy && kpi.groups && prev.groups && prev.eventsInWindow > 0) {\n crossed = crossGroups(kpi.groups.items, prev.groups.items)\n }\n const base = {\n basis: input.compareTo,\n window: { fromTime: new Date(w.fromMs).toISOString(), toTime: new Date(w.toMs).toISOString() }\n }\n comparison =\n prev.eventsInWindow === 0\n ? { ...base, measured: false }\n : {\n ...base,\n measured: true,\n throughput: prev.throughput,\n workTime: prev.workTime,\n leadTime: prev.leadTime,\n waitTime: prev.waitTime,\n delta: {\n tasks: kpi.throughput.tasks - prev.throughput.tasks,\n orders: kpi.throughput.orders - prev.throughput.orders,\n workP50Ms: deltaP50(kpi.workTime, prev.workTime),\n leadP50Ms: deltaP50(kpi.leadTime, prev.leadTime),\n waitP50Ms: deltaP50(kpi.waitTime, prev.waitTime)\n }\n }\n }\n\n /*\n * 여러 구간 동시 비교 — **같은 시간대를 며칠 뒤로** 옮겨 분포를 만든다(성과 맵 ③).\n *\n * 창마다 조회가 한 번 더 붙지만 각 창은 현재 창과 같은 길이라(보통 1~24h) 값싸다. 축(groupBy)은\n * 넘기지 않는다 — 분포는 **전체 지표**의 질문이고, 축별 분포까지 내면 응답이 축 수만큼 부푼다\n * (필요해지면 그때 축을 지정해 다시 묻는 것이 정직하다).\n *\n * 지표 어휘는 `TWIN_METRIC` 이 계속 소유하고, 여기서는 **값만 꺼내** 분포 모듈에 넘긴다.\n */\n /* 관점 값은 시각 변환이 없어 그대로 통과한다. 붙이는 것은 ① 사람이 읽는 이름과 ② 축이 통째로\n * 비었을 때의 이유다 — \"구역별 0건\" 과 \"노드에 구역이 안 붙어 있다\" 는 완전히 다른 상황이다. */\n const groups = kpi.groups\n ? {\n by: kpi.groups.by,\n items: (crossed?.items ?? kpi.groups.items).map(g => {\n /* 축의 이 값에 목표가 걸려 있으면 판정을 함께 낸다 — \"이 구역만 목표가 다르다\" 를\n 표에서 바로 볼 수 있게. 목표가 없는 축값에는 필드를 만들지 않는다. */\n const gt = judgeGroupTargets(g as any, groupTargets[g.key])\n return {\n ...g,\n ...(areaMap.labels[g.key] ? { label: areaMap.labels[g.key] } : {}),\n ...(gt.length ? { targets: gt } : {})\n }\n }),\n ...(crossed?.disappeared.length\n ? {\n disappeared: crossed.disappeared.map(d => ({\n ...d,\n ...(areaMap.labels[d.key] ? { label: areaMap.labels[d.key] } : {})\n }))\n }\n : {}),\n ...(kpi.groups.truncated\n ? { truncated: kpi.groups.truncated, truncatedTasks: kpi.groups.truncatedTasks ?? 0 }\n : {}),\n ...(input.groupBy === 'area' &&\n Object.keys(areaMap.locationArea).length === 0 &&\n kpi.groups.items.some(g => g.key === 'unknown')\n ? { note: 'locations carry no area (parentId) in this twin — cannot break down by area. this is NOT \"no work in areas\".' }\n : {})\n }\n : undefined\n\n /*\n * 목표 대비 판정 — **사실 계산 뒤에 얹는다.**\n *\n * 폴드는 목표를 모른다(사실만 낸다). 목표를 아는 순간 \"세는 규칙\" 과 \"잘했나 판정하는 규칙\" 이\n * 한 함수에 섞이고, 목표가 바뀔 때마다 사실 계산을 건드리게 된다. 그래서 조회 층에서 목표를\n * 실어 오고, 판정은 순수 함수(`judgeTargets`)가 한다.\n *\n * **목표가 없으면 판정하지 않는다** — 필드 자체를 만들지 않는다. 전부 \"목표 없음\" 으로 채우면\n * 화면이 회색 노이즈가 되고, 목표가 있는 지표가 묻힌다.\n */\n /* 분포는 **한 번만** 계산한다 — 창마다 조회가 붙으므로 두 번 부르면 조회가 두 배가 된다. */\n const baseline = await computeBaseline()\n\n /* 폴드가 돌려준 raw 창(ms)은 버린다 — 위의 ISO 창이 사람이 읽을 정본이다. */\n const { window: _rawWindow, groups: _rawGroups, ...rest } = kpi\n /*\n * 자리에 **부르는 이름**을 붙인다 — `feeder-2` 는 id 이고 사람은 「가공 라인」으로 부른다.\n * 이름을 모르는 자리는 id 그대로 둔다(지어내지 않는다).\n */\n /* 단가가 갈려 있으면 밝힌다 — 우리가 고른 하나로 낸 금액이라는 사실을 화면이 말할 수 있게. */\n if (rest.energy?.cost && tariffFound.conflict) rest.energy = { ...rest.energy, tariffConflict: true } as any\n if (rest.energy?.byFeeder?.length) {\n rest.energy = {\n ...rest.energy,\n byFeeder: rest.energy.byFeeder.map(row => ({ ...row, ...(feederMap.labels[row.key] ? { label: feederMap.labels[row.key] } : {}) }))\n }\n }\n /*\n * 계통도 — **선언된 관계와 계량이 덮는 범위**를 함께 낸다.\n *\n * 값은 여기서 다시 계산하지 않는다(폴드가 낸 분기별 몫을 그대로 노드에 얹는다). 계통은 모델이 아는\n * 사실이므로 저널만 보는 폴드가 아니라 여기서 조립한다 — 화면이 모델을 따로 또 조회하지 않아도 되고,\n * AI 도 같은 값을 읽는다(규칙 한 벌).\n */\n if (rest.energy) {\n const topo = await energyTopologyOf(input.domainId, instanceIds, rest.energy)\n if (topo) rest.energy = { ...rest.energy, topology: topo } as any\n }\n return {\n window,\n measured: true,\n scope,\n ...(capped || prevCappedFlag ? { eventsCapped: true } : {}),\n ...rest,\n ...(buckets ? { buckets } : {}),\n ...(groups ? { groups } : {}),\n ...(verdicts.length ? { targets: verdicts } : {}),\n ...(comparison ? { comparison } : {}),\n ...(baseline ? { baseline } : {})\n }\n}\n"]}
@@ -1,23 +1,9 @@
1
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
2
  Object.defineProperty(exports, "__esModule", { value: true });
18
3
  exports.registerLiveFeedProbe = registerLiveFeedProbe;
19
4
  exports.liveFeedAttached = liveFeedAttached;
20
5
  exports.liveFeedStateOf = liveFeedStateOf;
6
+ const log_js_1 = require("./log.js");
21
7
  const probes = [];
22
8
  /** 피드 장부를 등록한다(모듈 로드 시 한 번). */
23
9
  function registerLiveFeedProbe(probe) {
@@ -36,7 +22,7 @@ function liveFeedAttached(instanceId) {
36
22
  return true;
37
23
  }
38
24
  catch (e) {
39
- console.warn('[twin-live] a feed registry could not answer —', e?.message ?? e);
25
+ (0, log_js_1.twinWarn)('[twin-live] a feed registry could not answer —', e?.message ?? e);
40
26
  }
41
27
  }
42
28
  return false;
@@ -1 +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,IAAuE;IACrG,IAAI,IAAI,CAAC,aAAa,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAA;IAC5D,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: { restartPolicy?: string; running?: boolean; instanceId: string }): LiveFeedState {\n if (args.restartPolicy !== 'resync') return 'not-applicable'\n if (!args.running) return 'not-applicable'\n return liveFeedAttached(args.instanceId) ? 'attached' : 'detached'\n}\n"]}
1
+ {"version":3,"file":"live-feed-registry.js","sourceRoot":"","sources":["../../server/engine/live-feed-registry.ts"],"names":[],"mappings":";;AAuBA,sDAEC;AAQD,4CASC;AAYD,0CAIC;AA1DD,qCAAmC;AAoBnC,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,IAAA,iBAAQ,EAAC,gDAAgD,EAAE,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAA;QAC7E,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAYD,SAAgB,eAAe,CAAC,IAAuE;IACrG,IAAI,IAAI,CAAC,aAAa,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAA;IAC5D,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":["import { twinWarn } from './log.js'\n/*\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 twinWarn('[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: { restartPolicy?: string; running?: boolean; instanceId: string }): LiveFeedState {\n if (args.restartPolicy !== 'resync') return 'not-applicable'\n if (!args.running) return 'not-applicable'\n return liveFeedAttached(args.instanceId) ? 'attached' : 'detached'\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  /** 작업 이름 — 새 작업을 재기 시작하면 여기 늘린다(문자열을 자유롭게 쓰지 않는다: 오타가 새 칸이 된다). */
2
- export declare const LOAD_PHASES: readonly ["tick", "snapshot", "deltas", "publish", "journal", "ingest", "forecast", "forkTick"];
2
+ export declare const LOAD_PHASES: readonly ["tick", "snapshot", "deltas", "publish", "journal", "ingest", "forecast", "forkTick", "warmStart", "stimulus"];
3
3
  export type LoadPhase = (typeof LOAD_PHASES)[number];
4
4
  export interface PhaseStats {
5
5
  /** 잰 횟수. */
@@ -9,11 +9,11 @@
9
9
  *
10
10
  * 실제로 그런 상태를 만났다: 포트는 LISTEN 이고 webpack 은 `compiled successfully` 인데 요청이 하나도
11
11
  * 처리되지 않고 CPU 만 99% 였다. **터지면 알지만 느려지면 몰랐다** — `tickGuarded` 는 예외만 잡고
12
- * 시간은 재지 않았고, `TwinMetrics` 에는 라이브 지표(유입·방송·저널)뿐이라 시뮬 틱은 아무도 안 봤다.
12
+ * 시간은 재지 않았고, `TwinMetrics` 에는 라이브 지표(유입·브로드캐스팅·저널)뿐이라 시뮬 틱은 아무도 안 봤다.
13
13
  *
14
14
  * ── 총량만으로는 부족하다 ─────────────────────────────────────────────────
15
15
  * "틱이 800ms 걸렸다" 는 어디를 고칠지 알려 주지 않는다. 한 주기는 여러 작업으로 이뤄진다 —
16
- * 커널 틱 · 상태 투영(snapshot) · 방송 payload 만들기 · 발행 · 저널 기록 · 유입 흡수. 그래서
16
+ * 커널 틱 · 상태 투영(snapshot) · 브로드캐스팅 payload 만들기 · 발행 · 저널 기록 · 유입 흡수. 그래서
17
17
  * **작업마다 따로** 잰다. 어느 작업이 총 시간의 몇 %인지 보이면 손댈 곳이 정해진다.
18
18
  *
19
19
  * 재고 말하는 것이 먼저다. 워커 스레드로 옮기는 것은 그다음이다 — 무엇이 느린지 모른 채 옮기면
@@ -32,7 +32,16 @@ exports.fleetLoad = fleetLoad;
32
32
  exports.newCpuSampler = newCpuSampler;
33
33
  exports.sampleCpu = sampleCpu;
34
34
  /** 작업 이름 — 새 작업을 재기 시작하면 여기 늘린다(문자열을 자유롭게 쓰지 않는다: 오타가 새 칸이 된다). */
35
- exports.LOAD_PHASES = ['tick', 'snapshot', 'deltas', 'publish', 'journal', 'ingest', 'forecast', 'forkTick'];
35
+ /*
36
+ * ── 부팅 구간을 재는 두 이름 (2026-08-22) ───────────────────────────────────
37
+ * 실측으로 부팅이 620~717초였고 트윈 하나를 세우는 데 15~93초였다. 그런데 **어느 작업이 그 시간을
38
+ * 쓰는지 아무 계기도 답하지 못했다** — 재는 이름이 도는 중의 작업(틱·브로드캐스팅·저널)뿐이었다.
39
+ * 짐작으로 원인을 적으면 그것이 사실로 읽히므로, 그 자리에 이름을 둔다.
40
+ *
41
+ * · `warmStart` — 저장된 상태를 커널에 심는다(물품·오더·작업·주목 시각).
42
+ * · `stimulus` — 선언된 자극을 싣고 시작한다(실측: 큰 트윈에서 이 뒤가 29~72초였다).
43
+ */
44
+ exports.LOAD_PHASES = ['tick', 'snapshot', 'deltas', 'publish', 'journal', 'ingest', 'forecast', 'forkTick', 'warmStart', 'stimulus'];
36
45
  function newLoadMeter() {
37
46
  return { phases: {}, window: { startMs: -1, phases: {} }, recent: null, forksCreated: 0, overBudget: 0, lastWarnMs: -Infinity };
38
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"load-meter.js","sourceRoot":"","sources":["../../server/engine/load-meter.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 부하 계기판 — **어떤 작업이 이벤트 루프를 얼마나 쓰는지** 작업별로 잰다. 순수(import 없음).\n *\n * ── 왜 필요한가 (2026-08-07) ───────────────────────────────────────────────\n * 시뮬 틱은 트윈마다 `setInterval(1000ms)` 로 **메인 이벤트 루프에서 동기로** 수행된다. 부팅 시 16개가\n * 자동 프로비저닝되므로 초당 16번이 같은 루프에서 차례로 실행된다. 한 주기가 예산을 넘기면 타이머가\n * 밀려 쌓이고, 같은 루프에 있는 HTTP·GraphQL·웹소켓이 **함께 멈춘다.**\n *\n * 실제로 그런 상태를 만났다: 포트는 LISTEN 이고 webpack 은 `compiled successfully` 인데 요청이 하나도\n * 처리되지 않고 CPU 만 99% 였다. **터지면 알지만 느려지면 몰랐다** — `tickGuarded` 는 예외만 잡고\n * 시간은 재지 않았고, `TwinMetrics` 에는 라이브 지표(유입·방송·저널)뿐이라 시뮬 틱은 아무도 안 봤다.\n *\n * ── 총량만으로는 부족하다 ─────────────────────────────────────────────────\n * \"틱이 800ms 걸렸다\" 는 어디를 고칠지 알려 주지 않는다. 한 주기는 여러 작업으로 이뤄진다 —\n * 커널 틱 · 상태 투영(snapshot) · 방송 payload 만들기 · 발행 · 저널 기록 · 유입 흡수. 그래서\n * **작업마다 따로** 잰다. 어느 작업이 총 시간의 몇 %인지 보이면 손댈 곳이 정해진다.\n *\n * 재고 말하는 것이 먼저다. 워커 스레드로 옮기는 것은 그다음이다 — 무엇이 느린지 모른 채 옮기면\n * 느린 채로 옮겨진다.\n */\n\n/** 작업 이름 — 새 작업을 재기 시작하면 여기 늘린다(문자열을 자유롭게 쓰지 않는다: 오타가 새 칸이 된다). */\nexport const LOAD_PHASES = ['tick', 'snapshot', 'deltas', 'publish', 'journal', 'ingest', 'forecast', 'forkTick'] as const\nexport type LoadPhase = (typeof LOAD_PHASES)[number]\n\nexport interface PhaseStats {\n /** 잰 횟수. */\n count: number\n /** 마지막 소요(ms). */\n lastMs: number\n /** 가장 오래 걸린 것(ms) — 평균은 순간 정체를 가린다. */\n maxMs: number\n /** 소요 합(ms). 나눗셈은 읽는 쪽에서 한다. */\n totalMs: number\n}\n\n/** 창(window) 하나의 누적 — \"지금 얼마나 쓰고 있나\" 를 답한다. */\ninterface Window {\n /** 창이 열린 시각. **아직 안 열렸으면 `-1`** — 0 으로 두면 시각 0 과 구별이 안 된다(검사가 잡았다). */\n startMs: number\n phases: Partial<Record<LoadPhase, PhaseStats>>\n}\n\nexport interface LoadMeter {\n /** **누적** — 프로세스가 뜬 뒤 전부. 총량과 최악값을 본다. */\n phases: Partial<Record<LoadPhase, PhaseStats>>\n /** 지금 쌓고 있는 창. */\n window: Window\n /** **마지막으로 닫힌 창** — 이것이 \"실시간\" 이다. 없으면 아직 한 창도 안 지났다. */\n recent: (Window & { endMs: number }) | null\n /** 이 트윈에서 만들어진 fork 수(누적) — 예측 한 번이 수십 개를 만든다. */\n forksCreated: number\n /** 한 주기(틱)가 예산을 넘긴 횟수. */\n overBudget: number\n /**\n * 마지막 경고 시각(ms). **한 번도 안 했으면 `-Infinity`** — 0 으로 두면 \"0 시점에 경고했다\" 가 되어\n * 첫 느린 틱이 조용히 지나간다(검사가 그것을 잡았다).\n */\n lastWarnMs: number\n}\n\nexport function newLoadMeter(): LoadMeter {\n return { phases: {}, window: { startMs: -1, phases: {} }, recent: null, forksCreated: 0, overBudget: 0, lastWarnMs: -Infinity }\n}\n\n/**\n * 예산 — 틱 간격의 절반. 넘으면 루프의 절반을 이 트윈 하나가 쓰고 있다는 뜻이다.\n *\n * 절반인 이유: 트윈이 여럿이라 각자 간격을 다 쓰면 이미 포화다. 절반에서 알아야 손 쓸 틈이 있다.\n */\nexport const TICK_BUDGET_RATIO = 0.5\n\n/** 경고를 다시 낼 때까지의 최소 간격(ms) — 느린 트윈은 매 틱 느리므로, 그대로 두면 로그가 원인을 덮는다. */\nexport const WARN_COOLDOWN_MS = 30_000\n\n/**\n * 한 작업의 소요를 기록한다. **누적만 하고 판단하지 않는다.**\n *\n * 음수·NaN 은 기록하지 않는다(시계가 뒤로 간 경우). 없는 값을 0 으로 세면 평균이 거짓이 된다.\n */\nexport function recordPhase(meter: LoadMeter, phase: LoadPhase, tookMs: number, nowMs = Date.now()): void {\n if (!Number.isFinite(tookMs) || tookMs < 0) return\n\n rollWindow(meter, nowMs)\n for (const bucket of [meter.phases, meter.window.phases]) {\n const s = bucket[phase] ?? (bucket[phase] = { count: 0, lastMs: 0, maxMs: 0, totalMs: 0 })\n s.count += 1\n s.lastMs = tookMs\n s.totalMs += tookMs\n if (tookMs > s.maxMs) s.maxMs = tookMs\n }\n}\n\n/** fork 하나가 만들어졌다 — 예측 한 번이 수십 개를 만든다. 수를 알아야 부하의 출처를 짚는다. */\nexport function recordFork(meter: LoadMeter, count = 1): void {\n meter.forksCreated += count\n}\n\n/** 실시간 창의 길이(ms) — 이보다 길게 보려면 누적을 본다. */\nexport const WINDOW_MS = 10_000\n\n/** 창이 다 찼으면 닫고 새로 연다. **닫힌 창만 \"최근\" 으로 읽는다** — 채우는 중인 창은 과소 집계다. */\nexport function rollWindow(meter: LoadMeter, nowMs: number, windowMs = WINDOW_MS): void {\n if (meter.window.startMs < 0) {\n meter.window.startMs = nowMs\n return\n }\n if (nowMs - meter.window.startMs < windowMs) return\n\n meter.recent = { ...meter.window, endMs: nowMs }\n meter.window = { startMs: nowMs, phases: {} }\n}\n\n/**\n * 이 주기가 예산을 넘었나 — 넘었으면 세고, **말할 때인지**까지 답한다.\n */\nexport function judgeCycle(\n meter: LoadMeter,\n tookMs: number,\n tickMs: number,\n nowMs: number,\n budgetRatio = TICK_BUDGET_RATIO,\n cooldownMs = WARN_COOLDOWN_MS\n): { over: boolean; warn: boolean; budgetMs: number } {\n const budgetMs = tickMs * budgetRatio\n const over = Number.isFinite(tookMs) && tookMs > budgetMs\n if (!over) return { over: false, warn: false, budgetMs }\n\n meter.overBudget += 1\n const warn = nowMs - meter.lastWarnMs >= cooldownMs\n if (warn) meter.lastWarnMs = nowMs\n\n return { over: true, warn, budgetMs }\n}\n\nconst round2 = (v: number) => Math.round(v * 100) / 100\n/**\n * 비율은 자릿수를 더 준다 — **작은 값이 0 이 되면 \"안 쓴다\" 로 읽힌다.**\n *\n * 24ms/11초 = 0.0022 인데 소수 둘째 자리로 반올림하면 0 이 되어, 재서 나온 값과 없는 값이 같아진다.\n * 오늘 겪은 착각(0 과 없음을 구별 못 함)과 같은 종류라 여기서 막는다.\n */\nconst round4 = (v: number) => Math.round(v * 10000) / 10000\n\nexport interface PhaseRow {\n phase: LoadPhase\n count: number\n lastMs: number\n maxMs: number\n avgMs: number\n totalMs: number\n /** 잰 작업 전체 시간 중 이 작업의 몫(0~1). 어디를 고칠지 이 값이 정한다. */\n share: number\n}\n\nexport interface LoadView {\n phases: PhaseRow[]\n measuredTotalMs: number\n /** 이 구간의 길이(ms). 누적은 프로세스 기동 이후라 알 수 없으므로 `null`. */\n spanMs: number | null\n /** 구간 중 몇 %를 이 트윈이 썼나(0~1+). 구간을 모르면 `null` — **0 이 아니다.** */\n busyRatio: number | null\n}\n\nfunction toRows(phases: Partial<Record<LoadPhase, PhaseStats>>): { rows: PhaseRow[]; totalMs: number } {\n const entries = (Object.entries(phases) as [LoadPhase, PhaseStats][]).filter(([, s]) => s.count > 0)\n const totalMs = entries.reduce((sum, [, s]) => sum + s.totalMs, 0)\n const rows = entries\n .map(([phase, s]) => ({\n phase,\n count: s.count,\n lastMs: round2(s.lastMs),\n maxMs: round2(s.maxMs),\n avgMs: round2(s.totalMs / s.count),\n totalMs: round2(s.totalMs),\n share: totalMs > 0 ? round4(s.totalMs / totalMs) : 0\n }))\n .sort((a, b) => b.totalMs - a.totalMs)\n\n return { rows, totalMs }\n}\n\nfunction toView(phases: Partial<Record<LoadPhase, PhaseStats>>, spanMs: number | null): LoadView | null {\n const { rows, totalMs } = toRows(phases)\n if (!rows.length) return null\n\n return {\n phases: rows,\n measuredTotalMs: round2(totalMs),\n spanMs,\n busyRatio: spanMs && spanMs > 0 ? round4(totalMs / spanMs) : null\n }\n}\n\n/**\n * 읽기용 요약 — **실시간과 누적을 함께** 낸다.\n *\n * · `recent` — 마지막으로 **닫힌** 창. 지금 이 트윈이 루프를 얼마나 쓰고 있나. 채우는 중인 창은\n * 과소 집계라 쓰지 않는다.\n * · `total` — 기동 이후 전부. 총량과 최악값.\n *\n * 잰 적이 없으면 **`null` 이다.** 0 으로 채우면 \"0ms 다\"(= 아주 빠르다)로 읽혀, 재지 않은 것과 정반대의\n * 뜻이 된다. 몫(`share`)도 잰 작업들 사이의 비율일 뿐 — **재지 않은 작업은 분모에 없다.**\n */\nexport function loadSummary(meter: LoadMeter | undefined, tickMs: number): {\n recent: LoadView | null\n total: LoadView | null\n forksCreated: number\n overBudget: number\n budgetMs: number\n /** 마지막 틱이 간격을 얼마나 썼나(0~1+). 1 이면 간격을 통째로 쓴다. */\n loadRatio: number | null\n} | null {\n if (!meter) return null\n\n const total = toView(meter.phases, null)\n const recent = meter.recent ? toView(meter.recent.phases, meter.recent.endMs - meter.recent.startMs) : null\n if (!total && !recent && !meter.forksCreated) return null\n\n const tick = meter.phases.tick\n return {\n recent,\n total,\n forksCreated: meter.forksCreated,\n overBudget: meter.overBudget,\n budgetMs: tickMs * TICK_BUDGET_RATIO,\n loadRatio: tick && tick.count > 0 ? round4(tick.lastMs / tickMs) : null\n }\n}\n\n/**\n * 경고 한 줄 — **무엇이 느린지까지 말한다.** \"틱이 800ms\" 만으로는 어디를 볼지 알 수 없다.\n *\n * 가장 무거운 작업 셋을 몫과 함께 적는다.\n */\nexport function slowTickMessage(instanceId: string, tookMs: number, budgetMs: number, meter: LoadMeter): string {\n const { rows } = toRows(meter.recent?.phases ?? meter.phases)\n const top = rows\n .slice(0, 3)\n .map(p => `${p.phase} ${p.avgMs}ms(${Math.round(p.share * 100)}%)`)\n .join(' · ')\n\n return (\n `[twin-engine] \"${instanceId}\" 틱 ${round2(tookMs)}ms — 예산 ${round2(budgetMs)}ms 초과. ` +\n (top ? `무거운 작업: ${top}. ` : '') +\n `시뮬 틱이 메인 이벤트 루프에서 수행됩니다 — 이 상태가 이어지면 HTTP·구독이 함께 느려집니다.`\n )\n}\n\n/**\n * 전체 한눈에 — **누가 루프를 점유하고 있나.**\n *\n * 인스턴스 목록을 받아 대수·모드별 수·fork 총수를 세고, **최근 창의 부하가 큰 순서**로 줄을 세운다.\n * 누적이 아니라 최근으로 세우는 이유: 지금 아픈 것을 먼저 봐야 한다(누적 1위는 오래 떠 있던 것일 뿐일\n * 수 있다).\n */\nexport function fleetLoad(\n rows: { instanceId: string; mode: string; domainId?: string; domainLabel?: string; meter?: LoadMeter }[],\n tickMs: number\n): {\n instances: number\n byMode: Record<string, number>\n /**\n * **테넌트별 몫** — 운영자가 먼저 묻는 것은 \"어느 테넌트가 루프를 먹나\" 다.\n *\n * 실측으로 필요해졌다: 한 도메인 트윈 22개가 CPU 를 110% 쓰는 동안 다른 도메인 화면은 0 이었다.\n * 무거운 순으로 낸다. 몫은 **잰 트윈들 사이의 비율**이다(안 잰 트윈은 분모에 없다).\n */\n byDomain: { domainId: string; label: string; instances: number; recentBusyMs: number; share: number }[]\n forksCreated: number\n /** 최근 창에서 트윈들이 쓴 시간의 합(ms) — 창 길이를 넘으면 루프가 이미 밀리고 있다. */\n recentBusyMs: number\n /** 그 창의 길이(ms). 창이 하나도 안 닫혔으면 `null`. */\n recentSpanMs: number | null\n /** 전체가 루프를 얼마나 쓰나(0~1+). 1 을 넘으면 **한 틱 안에 다 못 끝낸다.** */\n fleetBusyRatio: number | null\n slowest: { instanceId: string; mode: string; domainId?: string; domainLabel?: string; recentBusyMs: number; topPhase: LoadPhase | null; overBudget: number }[]\n} {\n const byMode: Record<string, number> = {}\n const domains = new Map<string, { label: string; instances: number; recentBusyMs: number }>()\n let forksCreated = 0\n let recentBusyMs = 0\n let recentSpanMs: number | null = null\n\n const slowest = rows.map(r => {\n byMode[r.mode] = (byMode[r.mode] ?? 0) + 1\n forksCreated += r.meter?.forksCreated ?? 0\n\n const recent = r.meter?.recent\n const { rows: phaseRows, totalMs } = toRows(recent?.phases ?? {})\n /*\n * **잎에서 한 번만 반올림한다.** 도메인 합계와 전체 합계는 이 값을 더해서 만든다.\n *\n * 전에는 트윈·도메인·전체를 각자 원값에서 반올림했다. 그래서 화면의 숫자가 서로 안 맞았다:\n * system 도메인 0.08ms 인데 그 도메인의 트윈 둘은 0.08 + 0.01 = 0.09ms (2026-08-07 지적).\n * 보이는 부분들의 합이 보이는 합계와 달라지면 계기판을 믿을 수 없다.\n */\n const busyMs = round2(totalMs)\n if (recent) {\n recentBusyMs += busyMs\n const span = recent.endMs - recent.startMs\n /* 창 길이는 트윈마다 조금씩 다를 수 있다 — 가장 긴 것을 쓴다(짧은 것을 쓰면 비율이 부풀려진다). */\n if (span > 0 && (recentSpanMs === null || span > recentSpanMs)) recentSpanMs = span\n }\n\n /* 테넌트별 합산 — 라벨을 모르면 id 를 그대로 쓴다(지어내지 않는다). */\n const key = r.domainId ?? '(unknown)'\n const dom = domains.get(key) ?? { label: r.domainLabel || key, instances: 0, recentBusyMs: 0 }\n dom.instances += 1\n dom.recentBusyMs += busyMs\n domains.set(key, dom)\n\n return {\n instanceId: r.instanceId,\n mode: r.mode,\n domainId: r.domainId,\n domainLabel: r.domainLabel,\n recentBusyMs: busyMs,\n topPhase: phaseRows[0]?.phase ?? null,\n overBudget: r.meter?.overBudget ?? 0\n }\n })\n\n slowest.sort((a, b) => b.recentBusyMs - a.recentBusyMs)\n\n const byDomain = [...domains.entries()]\n .map(([domainId, d]) => ({\n domainId,\n label: d.label,\n instances: d.instances,\n recentBusyMs: round2(d.recentBusyMs),\n share: recentBusyMs > 0 ? round4(d.recentBusyMs / recentBusyMs) : 0\n }))\n .sort((a, b) => b.recentBusyMs - a.recentBusyMs)\n\n return {\n instances: rows.length,\n byMode,\n byDomain,\n forksCreated,\n recentBusyMs: round2(recentBusyMs),\n recentSpanMs,\n fleetBusyRatio: recentSpanMs && recentSpanMs > 0 ? round4(recentBusyMs / recentSpanMs) : null,\n slowest\n }\n}\n\n/**\n * 프로세스가 실제로 쓴 CPU — **계기판의 다른 숫자들과 뜻이 다르다.**\n *\n * 이 파일의 다른 값들은 트윈의 **계측된 8개 작업**만 잰 것이다. 그런데 프로세스는 그 밖에도 일한다:\n * GraphQL·TypeORM·GC·webpack·HTTP. 그래서 \"루프 점유 <1%\" 인데 CPU 는 110% 일 수 있고, 실제로 그런\n * 상태를 겪었다. 두 숫자를 나란히 두면 **트윈이 범인인지 아닌지**를 바로 가릴 수 있다.\n *\n * 코어 여러 개를 쓰면 1 을 넘을 수 있다(운영체제의 `%CPU` 와 같은 뜻).\n */\nexport type CpuSampler = { lastMicros: number; lastMs: number }\n\n/** 첫 표본은 비교 대상이 없다 — `-1` 로 두어 \"아직 모른다\" 와 0 을 구별한다. */\nexport function newCpuSampler(): CpuSampler {\n return { lastMicros: -1, lastMs: -1 }\n}\n\n/**\n * 두 표본 사이의 CPU 점유율. 처음 부르면 `null`(0 이 아니다 — 잰 구간이 없다).\n *\n * @param totalMicros `process.cpuUsage()` 의 user + system (마이크로초 누적)\n */\nexport function sampleCpu(s: CpuSampler, totalMicros: number, nowMs: number): { ratio: number; spanMs: number } | null {\n const prevMicros = s.lastMicros\n const prevMs = s.lastMs\n s.lastMicros = totalMicros\n s.lastMs = nowMs\n\n if (prevMicros < 0 || prevMs < 0) return null\n\n const spanMs = nowMs - prevMs\n if (spanMs <= 0) return null\n\n const usedMs = (totalMicros - prevMicros) / 1000\n if (usedMs < 0) return null\n\n return { ratio: round4(usedMs / spanMs), spanMs: Math.round(spanMs) }\n}\n"]}
1
+ {"version":3,"file":"load-meter.js","sourceRoot":"","sources":["../../server/engine/load-meter.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 부하 계기판 — **어떤 작업이 이벤트 루프를 얼마나 쓰는지** 작업별로 잰다. 순수(import 없음).\n *\n * ── 왜 필요한가 (2026-08-07) ───────────────────────────────────────────────\n * 시뮬 틱은 트윈마다 `setInterval(1000ms)` 로 **메인 이벤트 루프에서 동기로** 수행된다. 부팅 시 16개가\n * 자동 프로비저닝되므로 초당 16번이 같은 루프에서 차례로 실행된다. 한 주기가 예산을 넘기면 타이머가\n * 밀려 쌓이고, 같은 루프에 있는 HTTP·GraphQL·웹소켓이 **함께 멈춘다.**\n *\n * 실제로 그런 상태를 만났다: 포트는 LISTEN 이고 webpack 은 `compiled successfully` 인데 요청이 하나도\n * 처리되지 않고 CPU 만 99% 였다. **터지면 알지만 느려지면 몰랐다** — `tickGuarded` 는 예외만 잡고\n * 시간은 재지 않았고, `TwinMetrics` 에는 라이브 지표(유입·브로드캐스팅·저널)뿐이라 시뮬 틱은 아무도 안 봤다.\n *\n * ── 총량만으로는 부족하다 ─────────────────────────────────────────────────\n * \"틱이 800ms 걸렸다\" 는 어디를 고칠지 알려 주지 않는다. 한 주기는 여러 작업으로 이뤄진다 —\n * 커널 틱 · 상태 투영(snapshot) · 브로드캐스팅 payload 만들기 · 발행 · 저널 기록 · 유입 흡수. 그래서\n * **작업마다 따로** 잰다. 어느 작업이 총 시간의 몇 %인지 보이면 손댈 곳이 정해진다.\n *\n * 재고 말하는 것이 먼저다. 워커 스레드로 옮기는 것은 그다음이다 — 무엇이 느린지 모른 채 옮기면\n * 느린 채로 옮겨진다.\n */\n\n/** 작업 이름 — 새 작업을 재기 시작하면 여기 늘린다(문자열을 자유롭게 쓰지 않는다: 오타가 새 칸이 된다). */\n/*\n * ── 부팅 구간을 재는 두 이름 (2026-08-22) ───────────────────────────────────\n * 실측으로 부팅이 620~717초였고 트윈 하나를 세우는 데 15~93초였다. 그런데 **어느 작업이 그 시간을\n * 쓰는지 아무 계기도 답하지 못했다** — 재는 이름이 도는 중의 작업(틱·브로드캐스팅·저널)뿐이었다.\n * 짐작으로 원인을 적으면 그것이 사실로 읽히므로, 그 자리에 이름을 둔다.\n *\n * · `warmStart` — 저장된 상태를 커널에 심는다(물품·오더·작업·주목 시각).\n * · `stimulus` — 선언된 자극을 싣고 시작한다(실측: 큰 트윈에서 이 뒤가 29~72초였다).\n */\nexport const LOAD_PHASES = ['tick', 'snapshot', 'deltas', 'publish', 'journal', 'ingest', 'forecast', 'forkTick', 'warmStart', 'stimulus'] as const\nexport type LoadPhase = (typeof LOAD_PHASES)[number]\n\nexport interface PhaseStats {\n /** 잰 횟수. */\n count: number\n /** 마지막 소요(ms). */\n lastMs: number\n /** 가장 오래 걸린 것(ms) — 평균은 순간 정체를 가린다. */\n maxMs: number\n /** 소요 합(ms). 나눗셈은 읽는 쪽에서 한다. */\n totalMs: number\n}\n\n/** 창(window) 하나의 누적 — \"지금 얼마나 쓰고 있나\" 를 답한다. */\ninterface Window {\n /** 창이 열린 시각. **아직 안 열렸으면 `-1`** — 0 으로 두면 시각 0 과 구별이 안 된다(검사가 잡았다). */\n startMs: number\n phases: Partial<Record<LoadPhase, PhaseStats>>\n}\n\nexport interface LoadMeter {\n /** **누적** — 프로세스가 뜬 뒤 전부. 총량과 최악값을 본다. */\n phases: Partial<Record<LoadPhase, PhaseStats>>\n /** 지금 쌓고 있는 창. */\n window: Window\n /** **마지막으로 닫힌 창** — 이것이 \"실시간\" 이다. 없으면 아직 한 창도 안 지났다. */\n recent: (Window & { endMs: number }) | null\n /** 이 트윈에서 만들어진 fork 수(누적) — 예측 한 번이 수십 개를 만든다. */\n forksCreated: number\n /** 한 주기(틱)가 예산을 넘긴 횟수. */\n overBudget: number\n /**\n * 마지막 경고 시각(ms). **한 번도 안 했으면 `-Infinity`** — 0 으로 두면 \"0 시점에 경고했다\" 가 되어\n * 첫 느린 틱이 조용히 지나간다(검사가 그것을 잡았다).\n */\n lastWarnMs: number\n}\n\nexport function newLoadMeter(): LoadMeter {\n return { phases: {}, window: { startMs: -1, phases: {} }, recent: null, forksCreated: 0, overBudget: 0, lastWarnMs: -Infinity }\n}\n\n/**\n * 예산 — 틱 간격의 절반. 넘으면 루프의 절반을 이 트윈 하나가 쓰고 있다는 뜻이다.\n *\n * 절반인 이유: 트윈이 여럿이라 각자 간격을 다 쓰면 이미 포화다. 절반에서 알아야 손 쓸 틈이 있다.\n */\nexport const TICK_BUDGET_RATIO = 0.5\n\n/** 경고를 다시 낼 때까지의 최소 간격(ms) — 느린 트윈은 매 틱 느리므로, 그대로 두면 로그가 원인을 덮는다. */\nexport const WARN_COOLDOWN_MS = 30_000\n\n/**\n * 한 작업의 소요를 기록한다. **누적만 하고 판단하지 않는다.**\n *\n * 음수·NaN 은 기록하지 않는다(시계가 뒤로 간 경우). 없는 값을 0 으로 세면 평균이 거짓이 된다.\n */\nexport function recordPhase(meter: LoadMeter, phase: LoadPhase, tookMs: number, nowMs = Date.now()): void {\n if (!Number.isFinite(tookMs) || tookMs < 0) return\n\n rollWindow(meter, nowMs)\n for (const bucket of [meter.phases, meter.window.phases]) {\n const s = bucket[phase] ?? (bucket[phase] = { count: 0, lastMs: 0, maxMs: 0, totalMs: 0 })\n s.count += 1\n s.lastMs = tookMs\n s.totalMs += tookMs\n if (tookMs > s.maxMs) s.maxMs = tookMs\n }\n}\n\n/** fork 하나가 만들어졌다 — 예측 한 번이 수십 개를 만든다. 수를 알아야 부하의 출처를 짚는다. */\nexport function recordFork(meter: LoadMeter, count = 1): void {\n meter.forksCreated += count\n}\n\n/** 실시간 창의 길이(ms) — 이보다 길게 보려면 누적을 본다. */\nexport const WINDOW_MS = 10_000\n\n/** 창이 다 찼으면 닫고 새로 연다. **닫힌 창만 \"최근\" 으로 읽는다** — 채우는 중인 창은 과소 집계다. */\nexport function rollWindow(meter: LoadMeter, nowMs: number, windowMs = WINDOW_MS): void {\n if (meter.window.startMs < 0) {\n meter.window.startMs = nowMs\n return\n }\n if (nowMs - meter.window.startMs < windowMs) return\n\n meter.recent = { ...meter.window, endMs: nowMs }\n meter.window = { startMs: nowMs, phases: {} }\n}\n\n/**\n * 이 주기가 예산을 넘었나 — 넘었으면 세고, **말할 때인지**까지 답한다.\n */\nexport function judgeCycle(\n meter: LoadMeter,\n tookMs: number,\n tickMs: number,\n nowMs: number,\n budgetRatio = TICK_BUDGET_RATIO,\n cooldownMs = WARN_COOLDOWN_MS\n): { over: boolean; warn: boolean; budgetMs: number } {\n const budgetMs = tickMs * budgetRatio\n const over = Number.isFinite(tookMs) && tookMs > budgetMs\n if (!over) return { over: false, warn: false, budgetMs }\n\n meter.overBudget += 1\n const warn = nowMs - meter.lastWarnMs >= cooldownMs\n if (warn) meter.lastWarnMs = nowMs\n\n return { over: true, warn, budgetMs }\n}\n\nconst round2 = (v: number) => Math.round(v * 100) / 100\n/**\n * 비율은 자릿수를 더 준다 — **작은 값이 0 이 되면 \"안 쓴다\" 로 읽힌다.**\n *\n * 24ms/11초 = 0.0022 인데 소수 둘째 자리로 반올림하면 0 이 되어, 재서 나온 값과 없는 값이 같아진다.\n * 오늘 겪은 착각(0 과 없음을 구별 못 함)과 같은 종류라 여기서 막는다.\n */\nconst round4 = (v: number) => Math.round(v * 10000) / 10000\n\nexport interface PhaseRow {\n phase: LoadPhase\n count: number\n lastMs: number\n maxMs: number\n avgMs: number\n totalMs: number\n /** 잰 작업 전체 시간 중 이 작업의 몫(0~1). 어디를 고칠지 이 값이 정한다. */\n share: number\n}\n\nexport interface LoadView {\n phases: PhaseRow[]\n measuredTotalMs: number\n /** 이 구간의 길이(ms). 누적은 프로세스 기동 이후라 알 수 없으므로 `null`. */\n spanMs: number | null\n /** 구간 중 몇 %를 이 트윈이 썼나(0~1+). 구간을 모르면 `null` — **0 이 아니다.** */\n busyRatio: number | null\n}\n\nfunction toRows(phases: Partial<Record<LoadPhase, PhaseStats>>): { rows: PhaseRow[]; totalMs: number } {\n const entries = (Object.entries(phases) as [LoadPhase, PhaseStats][]).filter(([, s]) => s.count > 0)\n const totalMs = entries.reduce((sum, [, s]) => sum + s.totalMs, 0)\n const rows = entries\n .map(([phase, s]) => ({\n phase,\n count: s.count,\n lastMs: round2(s.lastMs),\n maxMs: round2(s.maxMs),\n avgMs: round2(s.totalMs / s.count),\n totalMs: round2(s.totalMs),\n share: totalMs > 0 ? round4(s.totalMs / totalMs) : 0\n }))\n .sort((a, b) => b.totalMs - a.totalMs)\n\n return { rows, totalMs }\n}\n\nfunction toView(phases: Partial<Record<LoadPhase, PhaseStats>>, spanMs: number | null): LoadView | null {\n const { rows, totalMs } = toRows(phases)\n if (!rows.length) return null\n\n return {\n phases: rows,\n measuredTotalMs: round2(totalMs),\n spanMs,\n busyRatio: spanMs && spanMs > 0 ? round4(totalMs / spanMs) : null\n }\n}\n\n/**\n * 읽기용 요약 — **실시간과 누적을 함께** 낸다.\n *\n * · `recent` — 마지막으로 **닫힌** 창. 지금 이 트윈이 루프를 얼마나 쓰고 있나. 채우는 중인 창은\n * 과소 집계라 쓰지 않는다.\n * · `total` — 기동 이후 전부. 총량과 최악값.\n *\n * 잰 적이 없으면 **`null` 이다.** 0 으로 채우면 \"0ms 다\"(= 아주 빠르다)로 읽혀, 재지 않은 것과 정반대의\n * 뜻이 된다. 몫(`share`)도 잰 작업들 사이의 비율일 뿐 — **재지 않은 작업은 분모에 없다.**\n */\nexport function loadSummary(meter: LoadMeter | undefined, tickMs: number): {\n recent: LoadView | null\n total: LoadView | null\n forksCreated: number\n overBudget: number\n budgetMs: number\n /** 마지막 틱이 간격을 얼마나 썼나(0~1+). 1 이면 간격을 통째로 쓴다. */\n loadRatio: number | null\n} | null {\n if (!meter) return null\n\n const total = toView(meter.phases, null)\n const recent = meter.recent ? toView(meter.recent.phases, meter.recent.endMs - meter.recent.startMs) : null\n if (!total && !recent && !meter.forksCreated) return null\n\n const tick = meter.phases.tick\n return {\n recent,\n total,\n forksCreated: meter.forksCreated,\n overBudget: meter.overBudget,\n budgetMs: tickMs * TICK_BUDGET_RATIO,\n loadRatio: tick && tick.count > 0 ? round4(tick.lastMs / tickMs) : null\n }\n}\n\n/**\n * 경고 한 줄 — **무엇이 느린지까지 말한다.** \"틱이 800ms\" 만으로는 어디를 볼지 알 수 없다.\n *\n * 가장 무거운 작업 셋을 몫과 함께 적는다.\n */\nexport function slowTickMessage(instanceId: string, tookMs: number, budgetMs: number, meter: LoadMeter): string {\n const { rows } = toRows(meter.recent?.phases ?? meter.phases)\n const top = rows\n .slice(0, 3)\n .map(p => `${p.phase} ${p.avgMs}ms(${Math.round(p.share * 100)}%)`)\n .join(' · ')\n\n return (\n `[twin-engine] \"${instanceId}\" 틱 ${round2(tookMs)}ms — 예산 ${round2(budgetMs)}ms 초과. ` +\n (top ? `무거운 작업: ${top}. ` : '') +\n `시뮬 틱이 메인 이벤트 루프에서 수행됩니다 — 이 상태가 이어지면 HTTP·구독이 함께 느려집니다.`\n )\n}\n\n/**\n * 전체 한눈에 — **누가 루프를 점유하고 있나.**\n *\n * 인스턴스 목록을 받아 대수·모드별 수·fork 총수를 세고, **최근 창의 부하가 큰 순서**로 줄을 세운다.\n * 누적이 아니라 최근으로 세우는 이유: 지금 아픈 것을 먼저 봐야 한다(누적 1위는 오래 떠 있던 것일 뿐일\n * 수 있다).\n */\nexport function fleetLoad(\n rows: { instanceId: string; mode: string; domainId?: string; domainLabel?: string; meter?: LoadMeter }[],\n tickMs: number\n): {\n instances: number\n byMode: Record<string, number>\n /**\n * **테넌트별 몫** — 운영자가 먼저 묻는 것은 \"어느 테넌트가 루프를 먹나\" 다.\n *\n * 실측으로 필요해졌다: 한 도메인 트윈 22개가 CPU 를 110% 쓰는 동안 다른 도메인 화면은 0 이었다.\n * 무거운 순으로 낸다. 몫은 **잰 트윈들 사이의 비율**이다(안 잰 트윈은 분모에 없다).\n */\n byDomain: { domainId: string; label: string; instances: number; recentBusyMs: number; share: number }[]\n forksCreated: number\n /** 최근 창에서 트윈들이 쓴 시간의 합(ms) — 창 길이를 넘으면 루프가 이미 밀리고 있다. */\n recentBusyMs: number\n /** 그 창의 길이(ms). 창이 하나도 안 닫혔으면 `null`. */\n recentSpanMs: number | null\n /** 전체가 루프를 얼마나 쓰나(0~1+). 1 을 넘으면 **한 틱 안에 다 못 끝낸다.** */\n fleetBusyRatio: number | null\n slowest: { instanceId: string; mode: string; domainId?: string; domainLabel?: string; recentBusyMs: number; topPhase: LoadPhase | null; overBudget: number }[]\n} {\n const byMode: Record<string, number> = {}\n const domains = new Map<string, { label: string; instances: number; recentBusyMs: number }>()\n let forksCreated = 0\n let recentBusyMs = 0\n let recentSpanMs: number | null = null\n\n const slowest = rows.map(r => {\n byMode[r.mode] = (byMode[r.mode] ?? 0) + 1\n forksCreated += r.meter?.forksCreated ?? 0\n\n const recent = r.meter?.recent\n const { rows: phaseRows, totalMs } = toRows(recent?.phases ?? {})\n /*\n * **잎에서 한 번만 반올림한다.** 도메인 합계와 전체 합계는 이 값을 더해서 만든다.\n *\n * 전에는 트윈·도메인·전체를 각자 원값에서 반올림했다. 그래서 화면의 숫자가 서로 안 맞았다:\n * system 도메인 0.08ms 인데 그 도메인의 트윈 둘은 0.08 + 0.01 = 0.09ms (2026-08-07 지적).\n * 보이는 부분들의 합이 보이는 합계와 달라지면 계기판을 믿을 수 없다.\n */\n const busyMs = round2(totalMs)\n if (recent) {\n recentBusyMs += busyMs\n const span = recent.endMs - recent.startMs\n /* 창 길이는 트윈마다 조금씩 다를 수 있다 — 가장 긴 것을 쓴다(짧은 것을 쓰면 비율이 부풀려진다). */\n if (span > 0 && (recentSpanMs === null || span > recentSpanMs)) recentSpanMs = span\n }\n\n /* 테넌트별 합산 — 라벨을 모르면 id 를 그대로 쓴다(지어내지 않는다). */\n const key = r.domainId ?? '(unknown)'\n const dom = domains.get(key) ?? { label: r.domainLabel || key, instances: 0, recentBusyMs: 0 }\n dom.instances += 1\n dom.recentBusyMs += busyMs\n domains.set(key, dom)\n\n return {\n instanceId: r.instanceId,\n mode: r.mode,\n domainId: r.domainId,\n domainLabel: r.domainLabel,\n recentBusyMs: busyMs,\n topPhase: phaseRows[0]?.phase ?? null,\n overBudget: r.meter?.overBudget ?? 0\n }\n })\n\n slowest.sort((a, b) => b.recentBusyMs - a.recentBusyMs)\n\n const byDomain = [...domains.entries()]\n .map(([domainId, d]) => ({\n domainId,\n label: d.label,\n instances: d.instances,\n recentBusyMs: round2(d.recentBusyMs),\n share: recentBusyMs > 0 ? round4(d.recentBusyMs / recentBusyMs) : 0\n }))\n .sort((a, b) => b.recentBusyMs - a.recentBusyMs)\n\n return {\n instances: rows.length,\n byMode,\n byDomain,\n forksCreated,\n recentBusyMs: round2(recentBusyMs),\n recentSpanMs,\n fleetBusyRatio: recentSpanMs && recentSpanMs > 0 ? round4(recentBusyMs / recentSpanMs) : null,\n slowest\n }\n}\n\n/**\n * 프로세스가 실제로 쓴 CPU — **계기판의 다른 숫자들과 뜻이 다르다.**\n *\n * 이 파일의 다른 값들은 트윈의 **계측된 8개 작업**만 잰 것이다. 그런데 프로세스는 그 밖에도 일한다:\n * GraphQL·TypeORM·GC·webpack·HTTP. 그래서 \"루프 점유 <1%\" 인데 CPU 는 110% 일 수 있고, 실제로 그런\n * 상태를 겪었다. 두 숫자를 나란히 두면 **트윈이 범인인지 아닌지**를 바로 가릴 수 있다.\n *\n * 코어 여러 개를 쓰면 1 을 넘을 수 있다(운영체제의 `%CPU` 와 같은 뜻).\n */\nexport type CpuSampler = { lastMicros: number; lastMs: number }\n\n/** 첫 표본은 비교 대상이 없다 — `-1` 로 두어 \"아직 모른다\" 와 0 을 구별한다. */\nexport function newCpuSampler(): CpuSampler {\n return { lastMicros: -1, lastMs: -1 }\n}\n\n/**\n * 두 표본 사이의 CPU 점유율. 처음 부르면 `null`(0 이 아니다 — 잰 구간이 없다).\n *\n * @param totalMicros `process.cpuUsage()` 의 user + system (마이크로초 누적)\n */\nexport function sampleCpu(s: CpuSampler, totalMicros: number, nowMs: number): { ratio: number; spanMs: number } | null {\n const prevMicros = s.lastMicros\n const prevMs = s.lastMs\n s.lastMicros = totalMicros\n s.lastMs = nowMs\n\n if (prevMicros < 0 || prevMs < 0) return null\n\n const spanMs = nowMs - prevMs\n if (spanMs <= 0) return null\n\n const usedMs = (totalMicros - prevMicros) / 1000\n if (usedMs < 0) return null\n\n return { ratio: round4(usedMs / spanMs), spanMs: Math.round(spanMs) }\n}\n"]}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 지역 시각 + 오프셋 — `2026-08-22T05:54:12.345+09:00`.
3
+ *
4
+ * UTC 로 찍지 않는다: 이 로그를 읽는 사람은 현장의 시각으로 이야기하고, 프레임워크의 다른 줄도
5
+ * 지역 시각이다. 두 줄이 다른 기준이면 나란히 놓고 읽을 수 없다.
6
+ */
7
+ export declare function stamp(at?: Date): string;
8
+ /**
9
+ * 인자 여럿을 한 줄로 — **어떤 인자도 버리지 않는다.**
10
+ *
11
+ * `console.error(msg, err)` 처럼 쓰던 자리가 많다. 프레임워크 로거에 그대로 넘기면 두 번째 인자는
12
+ * meta 로 들어가 우리 형식(`message` 만 찍는다)에서 **조용히 사라진다.** 그래서 여기서 문자열로 편다.
13
+ * 오류는 스택까지 — 스택이 없으면 「어디서 났나」를 잃는다.
14
+ */
15
+ export declare function line(args: unknown[]): string;
16
+ export declare const twinLog: (...args: unknown[]) => void;
17
+ export declare const twinWarn: (...args: unknown[]) => void;
18
+ export declare const twinError: (...args: unknown[]) => void;