@things-factory/headless-twin 10.0.8 → 10.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/dist-server/engine/attention-digest.d.ts +52 -0
  2. package/dist-server/engine/attention-digest.js +76 -0
  3. package/dist-server/engine/attention-digest.js.map +1 -0
  4. package/dist-server/engine/canonical-ingest.d.ts +2 -2
  5. package/dist-server/engine/canonical-ingest.js +24 -2
  6. package/dist-server/engine/canonical-ingest.js.map +1 -1
  7. package/dist-server/engine/command-routing.d.ts +33 -0
  8. package/dist-server/engine/command-routing.js +53 -0
  9. package/dist-server/engine/command-routing.js.map +1 -0
  10. package/dist-server/engine/energy-topology.d.ts +86 -0
  11. package/dist-server/engine/energy-topology.js +144 -0
  12. package/dist-server/engine/energy-topology.js.map +1 -0
  13. package/dist-server/engine/index.d.ts +11 -0
  14. package/dist-server/engine/index.js +17 -0
  15. package/dist-server/engine/index.js.map +1 -1
  16. package/dist-server/engine/kpi-baseline.d.ts +78 -0
  17. package/dist-server/engine/kpi-baseline.js +123 -0
  18. package/dist-server/engine/kpi-baseline.js.map +1 -0
  19. package/dist-server/engine/kpi-fold.d.ts +137 -1
  20. package/dist-server/engine/kpi-fold.js +225 -0
  21. package/dist-server/engine/kpi-fold.js.map +1 -1
  22. package/dist-server/engine/kpi-query.d.ts +32 -1
  23. package/dist-server/engine/kpi-query.js +262 -20
  24. package/dist-server/engine/kpi-query.js.map +1 -1
  25. package/dist-server/engine/kpi-target.d.ts +17 -0
  26. package/dist-server/engine/kpi-target.js +24 -2
  27. package/dist-server/engine/kpi-target.js.map +1 -1
  28. package/dist-server/engine/live-attentions.d.ts +1 -0
  29. package/dist-server/engine/live-attentions.js +7 -1
  30. package/dist-server/engine/live-attentions.js.map +1 -1
  31. package/dist-server/engine/live-feed-registry.d.ts +25 -0
  32. package/dist-server/engine/live-feed-registry.js +51 -0
  33. package/dist-server/engine/live-feed-registry.js.map +1 -0
  34. package/dist-server/engine/load-meter.d.ts +181 -0
  35. package/dist-server/engine/load-meter.js +267 -0
  36. package/dist-server/engine/load-meter.js.map +1 -0
  37. package/dist-server/engine/local-declarations.d.ts +262 -0
  38. package/dist-server/engine/local-declarations.js +528 -0
  39. package/dist-server/engine/local-declarations.js.map +1 -0
  40. package/dist-server/engine/model-basis.d.ts +39 -6
  41. package/dist-server/engine/model-basis.js +69 -9
  42. package/dist-server/engine/model-basis.js.map +1 -1
  43. package/dist-server/engine/model-vocabulary.d.ts +17 -0
  44. package/dist-server/engine/model-vocabulary.js +81 -0
  45. package/dist-server/engine/model-vocabulary.js.map +1 -0
  46. package/dist-server/engine/oee-accumulator.d.ts +28 -0
  47. package/dist-server/engine/oee-accumulator.js +26 -1
  48. package/dist-server/engine/oee-accumulator.js.map +1 -1
  49. package/dist-server/engine/operation-basis.d.ts +32 -0
  50. package/dist-server/engine/operation-basis.js +87 -0
  51. package/dist-server/engine/operation-basis.js.map +1 -0
  52. package/dist-server/engine/property-effects.d.ts +30 -0
  53. package/dist-server/engine/property-effects.js +192 -0
  54. package/dist-server/engine/property-effects.js.map +1 -0
  55. package/dist-server/engine/runtime-key.d.ts +15 -0
  56. package/dist-server/engine/runtime-key.js +64 -0
  57. package/dist-server/engine/runtime-key.js.map +1 -0
  58. package/dist-server/engine/spec-coverage.js +3 -3
  59. package/dist-server/engine/spec-coverage.js.map +1 -1
  60. package/dist-server/engine/state-axes.d.ts +21 -0
  61. package/dist-server/engine/state-axes.js +59 -0
  62. package/dist-server/engine/state-axes.js.map +1 -0
  63. package/dist-server/engine/structure-diff.js +1 -1
  64. package/dist-server/engine/structure-diff.js.map +1 -1
  65. package/dist-server/engine/travel-estimator.d.ts +9 -2
  66. package/dist-server/engine/travel-estimator.js +11 -6
  67. package/dist-server/engine/travel-estimator.js.map +1 -1
  68. package/dist-server/engine/twin-engine.d.ts +418 -57
  69. package/dist-server/engine/twin-engine.js +1599 -306
  70. package/dist-server/engine/twin-engine.js.map +1 -1
  71. package/dist-server/engine/warm-start.d.ts +93 -13
  72. package/dist-server/engine/warm-start.js +114 -14
  73. package/dist-server/engine/warm-start.js.map +1 -1
  74. package/dist-server/index.d.ts +1 -0
  75. package/dist-server/index.js +17 -11
  76. package/dist-server/index.js.map +1 -1
  77. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.d.ts +5 -0
  78. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +48 -0
  79. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -0
  80. package/dist-server/migrations/index.d.ts +2 -0
  81. package/dist-server/migrations/index.js +10 -0
  82. package/dist-server/migrations/index.js.map +1 -0
  83. package/dist-server/service/index.d.ts +3 -2
  84. package/dist-server/service/index.js +25 -17
  85. package/dist-server/service/index.js.map +1 -1
  86. package/dist-server/service/reference/discovery-result.d.ts +34 -0
  87. package/dist-server/service/reference/discovery-result.js +84 -0
  88. package/dist-server/service/reference/discovery-result.js.map +1 -0
  89. package/dist-server/service/reference/ingest-space.d.ts +53 -0
  90. package/dist-server/service/reference/ingest-space.js +79 -0
  91. package/dist-server/service/reference/ingest-space.js.map +1 -0
  92. package/dist-server/service/reference/knob-defaults.d.ts +20 -0
  93. package/dist-server/service/reference/knob-defaults.js +59 -0
  94. package/dist-server/service/reference/knob-defaults.js.map +1 -0
  95. package/dist-server/service/reference/reference-live.d.ts +23 -0
  96. package/dist-server/service/reference/reference-live.js +123 -6
  97. package/dist-server/service/reference/reference-live.js.map +1 -1
  98. package/dist-server/service/reference/reference-master.d.ts +227 -11
  99. package/dist-server/service/reference/reference-master.js +206 -25
  100. package/dist-server/service/reference/reference-master.js.map +1 -1
  101. package/dist-server/service/reference/reference-resolver.d.ts +33 -3
  102. package/dist-server/service/reference/reference-resolver.js +290 -23
  103. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  104. package/dist-server/service/reference/template-registry.d.ts +19 -3
  105. package/dist-server/service/reference/template-registry.js.map +1 -1
  106. package/dist-server/service/twin-attention/twin-attention-query.d.ts +8 -1
  107. package/dist-server/service/twin-attention/twin-attention-query.js +39 -8
  108. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  109. package/dist-server/service/twin-audit/command-audit.d.ts +37 -0
  110. package/dist-server/service/twin-audit/command-audit.js +53 -0
  111. package/dist-server/service/twin-audit/command-audit.js.map +1 -0
  112. package/dist-server/service/twin-audit/index.d.ts +4 -0
  113. package/dist-server/service/twin-audit/index.js +8 -0
  114. package/dist-server/service/twin-audit/index.js.map +1 -0
  115. package/dist-server/service/twin-audit/twin-audit-event.d.ts +24 -0
  116. package/dist-server/service/twin-audit/twin-audit-event.js +125 -0
  117. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -0
  118. package/dist-server/service/twin-audit/twin-audit-query.d.ts +4 -0
  119. package/dist-server/service/twin-audit/twin-audit-query.js +76 -0
  120. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -0
  121. package/dist-server/service/twin-control/twin-control-mutation.d.ts +2 -0
  122. package/dist-server/service/twin-control/twin-control-mutation.js +50 -11
  123. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  124. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  125. package/dist-server/service/twin-event/twin-event-keys.js +1 -1
  126. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  127. package/dist-server/service/twin-event/twin-event.d.ts +14 -5
  128. package/dist-server/service/twin-event/twin-event.js +46 -14
  129. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  130. package/dist-server/service/twin-forecast/forecast-metrics.d.ts +13 -0
  131. package/dist-server/service/twin-forecast/forecast-metrics.js +57 -0
  132. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -0
  133. package/dist-server/service/twin-forecast/forecast-tuning.d.ts +12 -0
  134. package/dist-server/service/twin-forecast/forecast-tuning.js +44 -0
  135. package/dist-server/service/twin-forecast/forecast-tuning.js.map +1 -0
  136. package/dist-server/service/twin-forecast/gap-analytics.d.ts +28 -2
  137. package/dist-server/service/twin-forecast/gap-analytics.js +43 -17
  138. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  139. package/dist-server/service/twin-forecast/twin-forecast-query.js +219 -39
  140. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  141. package/dist-server/service/twin-instance/twin-instance.d.ts +38 -5
  142. package/dist-server/service/twin-instance/twin-instance.js +66 -18
  143. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  144. package/dist-server/service/twin-journal/twin-journal-query.d.ts +11 -4
  145. package/dist-server/service/twin-journal/twin-journal-query.js +95 -15
  146. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  147. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +11 -5
  148. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +46 -27
  149. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  150. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  151. package/dist-server/service/twin-metrics/twin-metrics-query.js +63 -1
  152. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  153. package/dist-server/service/twin-model/epcis-coverage.d.ts +14 -0
  154. package/dist-server/service/twin-model/epcis-coverage.js +174 -0
  155. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -0
  156. package/dist-server/service/twin-model/iec61850-coverage.d.ts +14 -0
  157. package/dist-server/service/twin-model/iec61850-coverage.js +98 -0
  158. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -0
  159. package/dist-server/service/twin-model/index.d.ts +13 -0
  160. package/dist-server/service/twin-model/index.js +29 -0
  161. package/dist-server/service/twin-model/index.js.map +1 -0
  162. package/dist-server/service/twin-model/isa95-coverage.d.ts +48 -0
  163. package/dist-server/service/twin-model/isa95-coverage.js +155 -0
  164. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -0
  165. package/dist-server/service/twin-model/project-structure.d.ts +16 -0
  166. package/dist-server/service/twin-model/project-structure.js +173 -0
  167. package/dist-server/service/twin-model/project-structure.js.map +1 -0
  168. package/dist-server/service/twin-model/standard-coverage.d.ts +9 -0
  169. package/dist-server/service/twin-model/standard-coverage.js +37 -0
  170. package/dist-server/service/twin-model/standard-coverage.js.map +1 -0
  171. package/dist-server/service/twin-model/twin-equipment.d.ts +64 -0
  172. package/dist-server/service/twin-model/twin-equipment.js +134 -0
  173. package/dist-server/service/twin-model/twin-equipment.js.map +1 -0
  174. package/dist-server/service/twin-model/twin-lineage-query.d.ts +3 -0
  175. package/dist-server/service/twin-model/twin-lineage-query.js +206 -0
  176. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -0
  177. package/dist-server/service/twin-model/twin-location.d.ts +58 -0
  178. package/dist-server/service/twin-model/twin-location.js +130 -0
  179. package/dist-server/service/twin-model/twin-location.js.map +1 -0
  180. package/dist-server/service/twin-model/twin-model-item-query.d.ts +141 -0
  181. package/dist-server/service/twin-model/twin-model-item-query.js +852 -0
  182. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -0
  183. package/dist-server/service/twin-model/twin-model-mutation.d.ts +57 -0
  184. package/dist-server/service/twin-model/twin-model-mutation.js +490 -0
  185. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -0
  186. package/dist-server/service/twin-model/twin-model-query.d.ts +43 -0
  187. package/dist-server/service/twin-model/twin-model-query.js +551 -0
  188. package/dist-server/service/twin-model/twin-model-query.js.map +1 -0
  189. package/dist-server/service/twin-model/twin-model-tree-query.d.ts +3 -0
  190. package/dist-server/service/twin-model/twin-model-tree-query.js +158 -0
  191. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -0
  192. package/dist-server/service/twin-model/twin-operation.d.ts +61 -0
  193. package/dist-server/service/twin-model/twin-operation.js +137 -0
  194. package/dist-server/service/twin-model/twin-operation.js.map +1 -0
  195. package/dist-server/service/twin-space/move-space.d.ts +65 -0
  196. package/dist-server/service/twin-space/move-space.js +166 -0
  197. package/dist-server/service/twin-space/move-space.js.map +1 -0
  198. package/dist-server/service/twin-space/space-integrity.d.ts +97 -0
  199. package/dist-server/service/twin-space/space-integrity.js +182 -0
  200. package/dist-server/service/twin-space/space-integrity.js.map +1 -0
  201. package/dist-server/service/twin-space/twin-space-area.js +4 -4
  202. package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
  203. package/dist-server/service/twin-space/twin-space-representation.js +6 -6
  204. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  205. package/dist-server/service/twin-space/twin-space-resolver.d.ts +63 -1
  206. package/dist-server/service/twin-space/twin-space-resolver.js +365 -18
  207. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  208. package/dist-server/service/twin-space/twin-space.d.ts +17 -1
  209. package/dist-server/service/twin-space/twin-space.js +15 -5
  210. package/dist-server/service/twin-space/twin-space.js.map +1 -1
  211. package/dist-server/service/twin-state/twin-state-subscription.js +1 -1
  212. package/dist-server/service/twin-state/twin-state-subscription.js.map +1 -1
  213. package/dist-server/service/twin-structure/twin-structure.d.ts +1 -1
  214. package/dist-server/service/twin-structure/twin-structure.js +8 -7
  215. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  216. package/dist-server/service/twin-target/twin-target-resolver.js +21 -4
  217. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  218. package/dist-shared/axis-read.d.ts +12 -0
  219. package/dist-shared/axis-read.js +32 -0
  220. package/dist-shared/axis-read.js.map +1 -0
  221. package/dist-shared/entity-delta.d.ts +29 -0
  222. package/dist-shared/entity-delta.js +287 -0
  223. package/dist-shared/entity-delta.js.map +1 -0
  224. package/dist-shared/kpi-broadcast.d.ts +4 -0
  225. package/dist-shared/kpi-broadcast.js +16 -0
  226. package/dist-shared/kpi-broadcast.js.map +1 -0
  227. package/dist-shared/twin-level.d.ts +23 -0
  228. package/dist-shared/twin-level.js +52 -0
  229. package/dist-shared/twin-level.js.map +1 -0
  230. package/package.json +14 -12
  231. package/server/engine/attention-digest.ts +102 -0
  232. package/server/engine/canonical-ingest.ts +36 -4
  233. package/server/engine/command-routing.ts +67 -0
  234. package/server/engine/energy-topology.ts +189 -0
  235. package/server/engine/index.ts +17 -0
  236. package/server/engine/kpi-baseline.ts +202 -0
  237. package/server/engine/kpi-fold.ts +389 -1
  238. package/server/engine/kpi-query.ts +295 -22
  239. package/server/engine/kpi-target.ts +24 -2
  240. package/server/engine/live-attentions.ts +7 -2
  241. package/server/engine/live-feed-registry.ts +58 -0
  242. package/server/engine/load-meter.ts +384 -0
  243. package/server/engine/local-declarations.ts +700 -0
  244. package/server/engine/model-basis.ts +78 -10
  245. package/server/engine/model-vocabulary.ts +82 -0
  246. package/server/engine/oee-accumulator.ts +34 -1
  247. package/server/engine/operation-basis.ts +100 -0
  248. package/server/engine/property-effects.ts +199 -0
  249. package/server/engine/runtime-key.ts +58 -0
  250. package/server/engine/spec-coverage.ts +3 -3
  251. package/server/engine/state-axes.ts +60 -0
  252. package/server/engine/structure-diff.ts +1 -1
  253. package/server/engine/travel-estimator.ts +11 -6
  254. package/server/engine/twin-engine.ts +1710 -297
  255. package/server/engine/warm-start.ts +207 -22
  256. package/server/index.ts +13 -10
  257. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +43 -0
  258. package/server/migrations/index.ts +7 -0
  259. package/server/service/index.ts +9 -1
  260. package/server/service/reference/discovery-result.ts +95 -0
  261. package/server/service/reference/ingest-space.ts +104 -0
  262. package/server/service/reference/knob-defaults.ts +59 -0
  263. package/server/service/reference/reference-live.ts +121 -6
  264. package/server/service/reference/reference-master.ts +362 -35
  265. package/server/service/reference/reference-resolver.ts +307 -23
  266. package/server/service/reference/template-registry.ts +20 -5
  267. package/server/service/twin-attention/twin-attention-query.ts +43 -6
  268. package/server/service/twin-audit/command-audit.ts +81 -0
  269. package/server/service/twin-audit/index.ts +5 -0
  270. package/server/service/twin-audit/twin-audit-event.ts +112 -0
  271. package/server/service/twin-audit/twin-audit-query.ts +72 -0
  272. package/server/service/twin-control/twin-control-mutation.ts +53 -13
  273. package/server/service/twin-event/twin-event-keys.ts +1 -1
  274. package/server/service/twin-event/twin-event.ts +52 -15
  275. package/server/service/twin-forecast/forecast-metrics.ts +60 -0
  276. package/server/service/twin-forecast/forecast-tuning.ts +40 -0
  277. package/server/service/twin-forecast/gap-analytics.ts +48 -9
  278. package/server/service/twin-forecast/twin-forecast-query.ts +211 -38
  279. package/server/service/twin-instance/twin-instance.ts +127 -22
  280. package/server/service/twin-journal/twin-journal-query.ts +108 -14
  281. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +40 -23
  282. package/server/service/twin-metrics/twin-metrics-query.ts +60 -3
  283. package/server/service/twin-model/epcis-coverage.ts +195 -0
  284. package/server/service/twin-model/iec61850-coverage.ts +117 -0
  285. package/server/service/twin-model/index.ts +25 -0
  286. package/server/service/twin-model/isa95-coverage.ts +190 -0
  287. package/server/service/twin-model/project-structure.ts +199 -0
  288. package/server/service/twin-model/standard-coverage.ts +35 -0
  289. package/server/service/twin-model/twin-equipment.ts +150 -0
  290. package/server/service/twin-model/twin-lineage-query.ts +193 -0
  291. package/server/service/twin-model/twin-location.ts +140 -0
  292. package/server/service/twin-model/twin-model-item-query.ts +848 -0
  293. package/server/service/twin-model/twin-model-mutation.ts +503 -0
  294. package/server/service/twin-model/twin-model-query.ts +537 -0
  295. package/server/service/twin-model/twin-model-tree-query.ts +180 -0
  296. package/server/service/twin-model/twin-operation.ts +150 -0
  297. package/server/service/twin-space/move-space.ts +262 -0
  298. package/server/service/twin-space/space-integrity.ts +293 -0
  299. package/server/service/twin-space/twin-space-area.ts +4 -4
  300. package/server/service/twin-space/twin-space-representation.ts +6 -6
  301. package/server/service/twin-space/twin-space-resolver.ts +365 -22
  302. package/server/service/twin-space/twin-space.ts +40 -6
  303. package/server/service/twin-state/twin-state-subscription.ts +1 -1
  304. package/server/service/twin-structure/twin-structure.ts +10 -7
  305. package/server/service/twin-target/twin-target-resolver.ts +22 -4
  306. package/shared/axis-read.ts +28 -0
  307. package/shared/entity-delta.ts +286 -0
  308. package/shared/kpi-broadcast.ts +13 -0
  309. package/shared/twin-level.ts +48 -0
  310. package/test/adopt-structure-live.test.ts +133 -0
  311. package/test/attention-digest.test.ts +135 -0
  312. package/test/axis-read.test.ts +87 -0
  313. package/test/boot-resume.test.ts +205 -0
  314. package/test/canonical-ingest-vocabularies.test.ts +118 -0
  315. package/test/capability-mapping.test.ts +5 -5
  316. package/test/command-routing.test.ts +61 -0
  317. package/test/declaration-reaches-model.test.ts +178 -0
  318. package/test/discovery-result.test.ts +75 -0
  319. package/test/energy-topology.test.ts +113 -0
  320. package/test/entity-delta.test.ts +195 -25
  321. package/test/event-time-column.test.ts +67 -0
  322. package/test/forecast-metrics.test.ts +71 -0
  323. package/test/forecast-tuning.test.ts +42 -0
  324. package/test/gap-analytics.test.ts +34 -7
  325. package/test/ingest-bench.test.ts +9 -9
  326. package/test/ingest-running-guard.test.ts +136 -0
  327. package/test/ingest-space.test.ts +78 -0
  328. package/test/instance-cache-lifecycle.test.ts +110 -0
  329. package/test/kernel-kind-guard.test.ts +96 -0
  330. package/test/knob-defaults.test.ts +72 -0
  331. package/test/kpi-baseline-db.test.ts +216 -0
  332. package/test/kpi-baseline.test.ts +196 -0
  333. package/test/kpi-fold.test.ts +393 -2
  334. package/test/kpi-query-bench.test.ts +130 -0
  335. package/test/lineage-survives-restart.test.ts +203 -0
  336. package/test/live-feed-registry.test.ts +67 -0
  337. package/test/live-kernel-facts.test.ts +161 -0
  338. package/test/live-mirror-parity.test.ts +54 -14
  339. package/test/load-meter.test.ts +314 -0
  340. package/test/local-declarations.test.ts +554 -0
  341. package/test/master-to-twin.test.ts +128 -36
  342. package/test/model-basis.test.ts +56 -1
  343. package/test/model-vocabulary.test.ts +114 -0
  344. package/test/move-space.test.ts +149 -0
  345. package/test/mutation-gate.test.ts +122 -0
  346. package/test/oee-accumulator.test.ts +90 -7
  347. package/test/operation-basis.test.ts +100 -0
  348. package/test/operations-capability-db.test.ts +165 -0
  349. package/test/project-structure-db.test.ts +226 -0
  350. package/test/projection-reaches-screen.test.ts +272 -0
  351. package/test/property-effects.test.ts +96 -0
  352. package/test/registry-key-guard.test.ts +80 -0
  353. package/test/resync-origin-site.test.ts +77 -0
  354. package/test/runtime-key.test.ts +66 -0
  355. package/test/scale-twin-bench.test.ts +7 -7
  356. package/test/snapshot-freshness.test.ts +60 -0
  357. package/test/space-integrity.test.ts +223 -0
  358. package/test/standard-coverage.test.ts +94 -0
  359. package/test/state-axes.test.ts +74 -0
  360. package/test/streamline-e2e.test.ts +16 -16
  361. package/test/structure-revision-db.test.ts +16 -15
  362. package/test/tenant-registry-db.test.ts +149 -0
  363. package/test/twin-audit.test.ts +83 -0
  364. package/test/twin-model-item-db.test.ts +275 -0
  365. package/test/twin-model-tree-db.test.ts +176 -0
  366. package/test/twin-origin-resync.test.ts +114 -0
  367. package/test/warm-start-seam.test.ts +140 -0
  368. package/test/warm-start.test.ts +223 -4
  369. package/tsconfig.json +6 -1
  370. package/tsconfig.shared.json +23 -0
  371. package/tsconfig.shared.tsbuildinfo +1 -0
  372. package/tsconfig.tsbuildinfo +1 -0
  373. package/dist-server/engine/entity-delta.d.ts +0 -19
  374. package/dist-server/engine/entity-delta.js +0 -161
  375. package/dist-server/engine/entity-delta.js.map +0 -1
  376. package/dist-server/service/twin-event/backfill-keys.d.ts +0 -11
  377. package/dist-server/service/twin-event/backfill-keys.js +0 -63
  378. package/dist-server/service/twin-event/backfill-keys.js.map +0 -1
  379. package/dist-server/tsconfig.tsbuildinfo +0 -1
  380. package/server/engine/entity-delta.ts +0 -169
  381. package/server/service/twin-event/backfill-keys.ts +0 -72
@@ -0,0 +1,216 @@
1
+ /*
2
+ * ★ 여러 구간 동시 비교 — **실 DB로 배관을 넘어 본다.**
3
+ *
4
+ * 분포 산수는 `kpi-baseline.test.ts`(순수)가 지킨다. 여기서 지키는 것은 **호스트 배관**이다:
5
+ * 창을 하루씩 뒤로 옮겨 **각 창을 따로 읽는가** · 그 창을 같은 규칙으로 접는가 · 지표 값을 정본 표
6
+ * (`TWIN_METRIC`)로 꺼내는가 · 기록 없는 날을 결측으로 남기는가.
7
+ *
8
+ * 이 배관은 **경계를 넘어야만** 틀리는 것이 드러난다. 실제로 앱의 시뮬 트윈은 저널이 두 시간 남짓이라
9
+ * "지난 7일" 을 물어도 과거 창이 모두 비어 있어(정직하게 자리를 거부한다) 분포 경로를 한 번도 밟지
10
+ * 못했다. 그래서 여러 날에 걸친 저널을 손으로 심어 그 경로를 통과시킨다.
11
+ *
12
+ * 실 엔티티·실 네이밍 전략의 sqlite in-memory 스키마에 **빌드 산출물**을 실행한다.
13
+ * **소스를 고친 뒤에는 `npm run build:server` 를 먼저 돌려야 반영된다.**
14
+ */
15
+ import { test, before, after } from 'node:test'
16
+ import assert from 'node:assert/strict'
17
+ import { createRequire } from 'node:module'
18
+ import type { DataSource } from 'typeorm'
19
+
20
+ const req = createRequire(import.meta.url)
21
+ const { NamingStrategy, addDataSource, entities: shellEntities, Domain } = req('@things-factory/shell')
22
+ const { entities: authEntities } = req('@things-factory/auth-base')
23
+ const { entities: twinEntities } = req('../dist-server/service/index.js')
24
+ const { computeTwinKpi } = req('../dist-server/engine/index.js')
25
+ const { TwinEvent } = req('../dist-server/service/twin-event/twin-event.js')
26
+ const { TwinInstance } = req('../dist-server/service/twin-instance/twin-instance.js')
27
+ const { DataSource: TypeOrmDataSource } = req('typeorm')
28
+
29
+ let ds: DataSource
30
+ let domainId: string
31
+
32
+ const INSTANCE = 'baseline-probe'
33
+ const MS_DAY = 24 * 60 * 60_000
34
+
35
+ /** 창의 끝 — 고정 시각을 쓴다(테스트가 벽시계에 흔들리지 않게). */
36
+ const TO = Date.parse('2026-03-10T10:00:00.000Z')
37
+ const FROM = TO - 60 * 60_000
38
+
39
+ /**
40
+ * 완료 한 건 = 시작 + 완료 두 줄. 소요시간이 짝맞춤에서 나오므로 둘 다 필요하다.
41
+ * 완료 시각이 창 안에 들면 그 창의 성과로 센다.
42
+ */
43
+ function taskRows(dayBack: number, count: number, workMs: number, revBase: number) {
44
+ const rows: any[] = []
45
+ const endBase = TO - dayBack * MS_DAY - 60_000 // 창 끝 1분 전에 완료
46
+ for (let i = 0; i < count; i++) {
47
+ const taskId = `t-${dayBack}-${i}`
48
+ const done = endBase - i * 1000
49
+ const started = done - workMs
50
+ rows.push(
51
+ {
52
+ domain: { id: domainId },
53
+ instanceId: INSTANCE,
54
+ revision: revBase + i * 2,
55
+ eventType: 'task.status',
56
+ eventTime: new Date(started).toISOString(),
57
+ payload: { data: { taskId, status: 'in-progress', resourceRef: 'fk-1' } }
58
+ },
59
+ {
60
+ domain: { id: domainId },
61
+ instanceId: INSTANCE,
62
+ revision: revBase + i * 2 + 1,
63
+ eventType: 'task.status',
64
+ eventTime: new Date(done).toISOString(),
65
+ /* 완료 어휘는 **커널이 소유한다**(`foldTaskRecords`) — 'done' 이 아니라 'completed' 다.
66
+ 처음에 'done' 으로 심었더니 창은 잡히는데 건수가 0 이었다(이 DB 스펙이 그것을 잡았다). */
67
+ payload: { data: { taskId, status: 'completed', resourceRef: 'fk-1' } }
68
+ }
69
+ )
70
+ }
71
+ return rows
72
+ }
73
+
74
+ before(async () => {
75
+ ds = new TypeOrmDataSource({
76
+ type: 'sqlite',
77
+ database: ':memory:',
78
+ synchronize: true,
79
+ dropSchema: true,
80
+ namingStrategy: new NamingStrategy(),
81
+ entities: [...shellEntities, ...authEntities, ...twinEntities],
82
+ logging: false
83
+ })
84
+ await ds.initialize()
85
+ addDataSource('default', ds)
86
+ const d = await ds.getRepository(Domain).save({ name: 'baseline-test', subdomain: 'baseline-test' } as any)
87
+ domainId = d.id
88
+
89
+ await ds.getRepository(TwinInstance).save({
90
+ domain: { id: domainId },
91
+ instanceId: INSTANCE,
92
+ kind: 'wms',
93
+ realityMode: 'sim-experiment', /* 선언은 이제 필수 — 비워 두면 읽는 자리마다 기본값을 고른다 */
94
+ purpose: 'operational',
95
+ status: 'stopped',
96
+ model: { locations: [{ id: 'dock', type: 'dock', capacity: 10 }], equipment: [{ id: 'fk-1', kind: 'forklift', homeLocation: 'dock' }] }
97
+ } as any)
98
+
99
+ /*
100
+ * 지금 창 20건 · 어제 40건 · 그제 60건 · **3일 전은 기록 없음** · 4일 전 80건.
101
+ * 3일 전을 비워 두는 것이 이 스펙의 요점이다 — 그 날이 결측으로 남고 분포에서 빠지는지 본다
102
+ * (조용히 빼면 "지난 4일" 이 실제로는 3일이었다는 것을 아무도 모른다).
103
+ */
104
+ const rows = [
105
+ ...taskRows(0, 20, 30_000, 1000),
106
+ ...taskRows(1, 40, 40_000, 3000),
107
+ ...taskRows(2, 60, 50_000, 6000),
108
+ ...taskRows(4, 80, 60_000, 9000)
109
+ ]
110
+ await ds.getRepository(TwinEvent).save(rows)
111
+ })
112
+
113
+ after(async () => {
114
+ await ds?.destroy()
115
+ })
116
+
117
+ const kpi = (periods: number, stride?: 'day' | 'week') =>
118
+ computeTwinKpi({
119
+ domainId,
120
+ instanceId: INSTANCE,
121
+ fromTime: new Date(FROM).toISOString(),
122
+ toTime: new Date(TO).toISOString(),
123
+ baseline: { periods, ...(stride ? { stride } : {}) }
124
+ })
125
+
126
+ test('과거 창을 하루씩 따로 읽어 분포를 만든다', async () => {
127
+ const out: any = await kpi(4)
128
+ const tasks = out.baseline.metrics.find((m: any) => m.metric === 'throughput.tasks')
129
+
130
+ assert.equal(out.baseline.stride, 'day')
131
+ assert.equal(out.baseline.periods, 4)
132
+ assert.equal(tasks.current, 20, '지금 창')
133
+ /* 어제 40 · 그제 60 · 3일 전 없음 · 4일 전 80. */
134
+ assert.deepEqual([tasks.min, tasks.median, tasks.max], [40, 60, 80])
135
+ })
136
+
137
+ test('기록 없는 날을 **결측으로 남긴다** — 요청한 창 수와 측정된 수를 함께 답한다', async () => {
138
+ const out: any = await kpi(4)
139
+ const tasks = out.baseline.metrics.find((m: any) => m.metric === 'throughput.tasks')
140
+
141
+ assert.equal(tasks.requested, 4)
142
+ assert.equal(tasks.measuredCount, 3, '3일 전은 기록이 없다')
143
+ assert.equal(tasks.samples.length, 4, '창 자체는 네 개 모두 돌려준다')
144
+ assert.deepEqual(tasks.samples.map((s: any) => s.measured), [true, true, false, true])
145
+ assert.deepEqual(tasks.samples.map((s: any) => s.value), [40, 60, undefined, 80])
146
+ })
147
+
148
+ test('처리량이 지난 며칠 중 가장 낮으면 worst — 이 축이 답하려던 질문', async () => {
149
+ /* 예전에는 "어제보다 나아졌나" 까지만 답할 수 있었다. 지금 창(20)은 어제(40)보다도 낮고
150
+ 최근 어느 날보다도 낮다 — 그것을 한 번에 말할 수 있어야 한다. */
151
+ const out: any = await kpi(4)
152
+ const tasks = out.baseline.metrics.find((m: any) => m.metric === 'throughput.tasks')
153
+
154
+ assert.equal(tasks.standing, 'worst')
155
+ assert.equal(tasks.rank, 1, '오름차순 첫째')
156
+ assert.equal(tasks.rankOf, 4, '표본 3 + 현재 1')
157
+ })
158
+
159
+ test('방향은 지표가 안다 — 작업시간이 가장 짧으면 best', async () => {
160
+ const out: any = await kpi(4)
161
+ const work = out.baseline.metrics.find((m: any) => m.metric === 'workTime.p50Ms')
162
+
163
+ assert.equal(work.current, 30_000)
164
+ assert.deepEqual([work.min, work.max], [40_000, 60_000])
165
+ assert.equal(work.standing, 'best', '짧을수록 좋다')
166
+ })
167
+
168
+ test('구간 수를 줄이면 그만큼만 본다 — 창을 지어내지 않는다', async () => {
169
+ const out: any = await kpi(1)
170
+ const tasks = out.baseline.metrics.find((m: any) => m.metric === 'throughput.tasks')
171
+
172
+ assert.equal(tasks.requested, 1)
173
+ assert.equal(tasks.measuredCount, 1)
174
+ /* 표본 하나는 분포가 아니다. */
175
+ assert.equal(tasks.noPosition, 'too-few-samples')
176
+ assert.equal(tasks.standing, undefined)
177
+ })
178
+
179
+ test('주 단위는 같은 요일을 본다 — 하루 전 기록에 걸리지 않는다', async () => {
180
+ const out: any = await kpi(2, 'week')
181
+ const tasks = out.baseline.metrics.find((m: any) => m.metric === 'throughput.tasks')
182
+
183
+ assert.equal(out.baseline.stride, 'week')
184
+ /* 심어 둔 기록은 최근 나흘뿐이라 지난주 같은 요일에는 아무것도 없다 — 지어내지 않는다. */
185
+ assert.equal(tasks.measuredCount, 0)
186
+ assert.equal(tasks.noPosition, 'too-few-samples')
187
+ })
188
+
189
+ test('요청하지 않으면 분포를 만들지 않는다 — 조회가 공짜가 아니다', async () => {
190
+ const out: any = await computeTwinKpi({
191
+ domainId,
192
+ instanceId: INSTANCE,
193
+ fromTime: new Date(FROM).toISOString(),
194
+ toTime: new Date(TO).toISOString()
195
+ })
196
+
197
+ assert.equal(out.baseline, undefined)
198
+ })
199
+
200
+ test('지금 창에 기록이 없어도 분포는 낸다 — 그때가 가장 알고 싶은 순간이다', async () => {
201
+ /* 심어 둔 기록이 없는 시각을 창으로 잡는다(3일 전). 평소엔 일이 있었는데 지금 없다 = 멈춘 신호. */
202
+ const quietTo = TO - 3 * MS_DAY
203
+ const out: any = await computeTwinKpi({
204
+ domainId,
205
+ instanceId: INSTANCE,
206
+ fromTime: new Date(quietTo - 60 * 60_000).toISOString(),
207
+ toTime: new Date(quietTo).toISOString(),
208
+ baseline: { periods: 2 }
209
+ })
210
+
211
+ assert.equal(out.measured, false, '지금 창에는 측정할 기록이 없다')
212
+ assert.ok(out.baseline, '그래도 분포는 낸다')
213
+ const tasks = out.baseline.metrics.find((m: any) => m.metric === 'throughput.tasks')
214
+ assert.equal(tasks.measuredCount, 1, '4일 전 하루만 걸린다(5일 전은 기록 없음)')
215
+ assert.equal(tasks.samples.find((s: any) => s.measured).value, 80)
216
+ })
@@ -0,0 +1,196 @@
1
+ /*
2
+ * 여러 구간 동시 비교 — **"어제보다 나아졌다" 를 넘어선다.**
3
+ *
4
+ * 비교 축이 한 구간만 볼 때는 판단이 한 문장에서 멈췄다: "어제보다 나아졌다." 어제도 나빴으면 쓸모가
5
+ * 없다. 최근 며칠의 같은 시간대를 함께 보면 목표 없이도 "지난 7일 중 가장 낮다" 를 말할 수 있다.
6
+ *
7
+ * 이 테스트가 지키는 것은 산수보다 **정직성**이다. 이 도메인에서 값이 없는 것을 0 으로 읽으면 시간
8
+ * 지표는 "낮을수록 좋다" 라서 **일이 없던 구간이 최고 성적으로** 보고된다 — 목표 판정에서 이미 겪은
9
+ * 결함이라 여기서 되풀이하지 않는다.
10
+ */
11
+ import { test } from 'node:test'
12
+ import assert from 'node:assert/strict'
13
+
14
+ import {
15
+ MAX_BASELINE_PERIODS,
16
+ baselineWindows,
17
+ summarizeBaseline,
18
+ type MetricSpecLike
19
+ } from '../server/engine/kpi-baseline.ts'
20
+
21
+ const MS_DAY = 24 * 60 * 60_000
22
+ const HOUR = 60 * 60_000
23
+
24
+ /* 실제 표(`TWIN_METRIC`)의 성질만 흉내 낸 최소 사양 — 어휘는 그쪽이 소유한다. */
25
+ const SPECS = {
26
+ 'throughput.tasks': { direction: 'higher', unit: 'count' },
27
+ 'workTime.p50Ms': { direction: 'lower', unit: 'ms' }
28
+ } satisfies Record<string, MetricSpecLike>
29
+
30
+ type M = keyof typeof SPECS
31
+
32
+ /** 과거 창 표본 — 값만 신경 쓰는 테스트를 짧게 쓰기 위한 도우미. */
33
+ const sample = (dayBack: number, values: Partial<Record<M, number | undefined>>) => ({
34
+ fromMs: -dayBack * MS_DAY,
35
+ toMs: -dayBack * MS_DAY + HOUR,
36
+ values
37
+ })
38
+
39
+ test('같은 시간대를 하루씩 뒤로 옮긴다 — 가까운 것부터', () => {
40
+ const from = 10 * MS_DAY
41
+ const to = from + HOUR
42
+
43
+ const windows = baselineWindows(from, to, 3)
44
+
45
+ assert.equal(windows.length, 3)
46
+ assert.deepEqual(windows[0], { fromMs: from - MS_DAY, toMs: to - MS_DAY }, '어제가 먼저')
47
+ assert.deepEqual(windows[2], { fromMs: from - 3 * MS_DAY, toMs: to - 3 * MS_DAY })
48
+ /* 길이는 그대로여야 한다 — 길이가 달라지면 처리량을 비교할 수 없다. */
49
+ for (const w of windows) assert.equal(w.toMs - w.fromMs, to - from)
50
+ })
51
+
52
+ test('주 단위는 같은 요일을 본다 — 주말·평일이 섞이지 않게', () => {
53
+ const from = 30 * MS_DAY
54
+ const windows = baselineWindows(from, from + HOUR, 2, 'week')
55
+
56
+ assert.equal(windows[0].fromMs, from - 7 * MS_DAY)
57
+ assert.equal(windows[1].fromMs, from - 14 * MS_DAY)
58
+ })
59
+
60
+ test('구간 수에 상한이 있고, 이상한 입력은 창을 만들지 않는다', () => {
61
+ assert.equal(baselineWindows(0, HOUR, 999).length, MAX_BASELINE_PERIODS)
62
+ assert.equal(baselineWindows(0, HOUR, 0).length, 0)
63
+ assert.equal(baselineWindows(0, HOUR, -3).length, 0)
64
+ /* 끝이 시작보다 앞이면 창이 아니다 — 지어내지 않는다. */
65
+ assert.equal(baselineWindows(HOUR, 0, 3).length, 0)
66
+ assert.equal(baselineWindows(Number.NaN, HOUR, 3).length, 0)
67
+ })
68
+
69
+ test('분포는 측정된 표본만으로 만들되, **결측 창도 남긴다**', () => {
70
+ /* 빼고 말하면 "지난 5일" 이 실제로는 3일이었다는 것을 아무도 모른다. */
71
+ const [row] = summarizeBaseline({ 'throughput.tasks': SPECS['throughput.tasks'] }, { 'throughput.tasks': 50 }, [
72
+ sample(1, { 'throughput.tasks': 40 }),
73
+ sample(2, {}), // 기록 없음
74
+ sample(3, { 'throughput.tasks': 60 }),
75
+ sample(4, { 'throughput.tasks': undefined }), // 기록 없음
76
+ sample(5, { 'throughput.tasks': 80 })
77
+ ])
78
+
79
+ assert.equal(row.requested, 5)
80
+ assert.equal(row.measuredCount, 3)
81
+ assert.equal(row.samples.length, 5, '요청한 창 전부를 돌려준다')
82
+ assert.deepEqual(
83
+ row.samples.map(s => s.measured),
84
+ [true, false, true, false, true]
85
+ )
86
+ assert.deepEqual([row.min, row.median, row.max], [40, 60, 80])
87
+ })
88
+
89
+ test('현재 값이 없으면 순위를 내지 않는다 — 결측을 0 으로 읽으면 최고 성적으로 뒤집힌다', () => {
90
+ /* 시간 지표는 "낮을수록 좋다" 라서 결측을 0 으로 놓으면 **일이 없던 구간이 1등**이 된다. */
91
+ const [row] = summarizeBaseline({ 'workTime.p50Ms': SPECS['workTime.p50Ms'] }, {}, [
92
+ sample(1, { 'workTime.p50Ms': 30_000 }),
93
+ sample(2, { 'workTime.p50Ms': 40_000 })
94
+ ])
95
+
96
+ assert.equal(row.current, undefined)
97
+ assert.equal(row.standing, undefined)
98
+ assert.equal(row.rank, undefined)
99
+ assert.equal(row.noPosition, 'no-current')
100
+ /* 그래도 분포 자체는 낸다 — "평소 얼마였나" 는 여전히 답할 수 있는 사실이다. */
101
+ assert.deepEqual([row.min, row.max], [30_000, 40_000])
102
+ })
103
+
104
+ test('표본이 둘 미만이면 순위를 내지 않는다 — 하나를 분포라 부를 수 없다', () => {
105
+ const one = summarizeBaseline({ 'throughput.tasks': SPECS['throughput.tasks'] }, { 'throughput.tasks': 10 }, [
106
+ sample(1, { 'throughput.tasks': 40 })
107
+ ])[0]
108
+
109
+ assert.equal(one.noPosition, 'too-few-samples')
110
+ assert.equal(one.standing, undefined)
111
+
112
+ const none = summarizeBaseline({ 'throughput.tasks': SPECS['throughput.tasks'] }, { 'throughput.tasks': 10 }, [])[0]
113
+ assert.equal(none.noPosition, 'too-few-samples')
114
+ assert.equal(none.min, undefined)
115
+ })
116
+
117
+ test('방향을 지표가 안다 — 같은 자리가 처리량과 소요시간에서 반대로 읽힌다', () => {
118
+ const past = [sample(1, { 'throughput.tasks': 40, 'workTime.p50Ms': 40_000 }), sample(2, { 'throughput.tasks': 60, 'workTime.p50Ms': 60_000 }), sample(3, { 'throughput.tasks': 80, 'workTime.p50Ms': 80_000 })]
119
+
120
+ /* 값이 표본 전부보다 크다 — 처리량은 최고, 소요시간은 최악이다. */
121
+ const rows = summarizeBaseline(SPECS, { 'throughput.tasks': 90, 'workTime.p50Ms': 90_000 }, past)
122
+ const by = Object.fromEntries(rows.map(r => [r.metric, r]))
123
+
124
+ assert.equal(by['throughput.tasks'].standing, 'best')
125
+ assert.equal(by['workTime.p50Ms'].standing, 'worst')
126
+ /* 순위는 **방향 없는 사실**이다 — 둘 다 오름차순 4번째다. */
127
+ assert.equal(by['throughput.tasks'].rank, 4)
128
+ assert.equal(by['workTime.p50Ms'].rank, 4)
129
+ assert.equal(by['workTime.p50Ms'].rankOf, 4)
130
+ })
131
+
132
+ test('전부보다 나쁘면 worst — 처리량이 지난 3일 중 가장 낮은 경우', () => {
133
+ const [row] = summarizeBaseline({ 'throughput.tasks': SPECS['throughput.tasks'] }, { 'throughput.tasks': 10 }, [
134
+ sample(1, { 'throughput.tasks': 40 }),
135
+ sample(2, { 'throughput.tasks': 60 }),
136
+ sample(3, { 'throughput.tasks': 80 })
137
+ ])
138
+
139
+ assert.equal(row.standing, 'worst')
140
+ assert.equal(row.rank, 1, '오름차순 첫째')
141
+ })
142
+
143
+ test('중앙값과 같으면 typical — 임의의 임계를 만들지 않는다', () => {
144
+ const [row] = summarizeBaseline({ 'throughput.tasks': SPECS['throughput.tasks'] }, { 'throughput.tasks': 60 }, [
145
+ sample(1, { 'throughput.tasks': 40 }),
146
+ sample(2, { 'throughput.tasks': 60 }),
147
+ sample(3, { 'throughput.tasks': 80 })
148
+ ])
149
+
150
+ assert.equal(row.standing, 'typical')
151
+ })
152
+
153
+ test('중앙값 양쪽을 방향에 따라 구분한다', () => {
154
+ const past = [sample(1, { 'throughput.tasks': 40 }), sample(2, { 'throughput.tasks': 60 }), sample(3, { 'throughput.tasks': 80 })]
155
+ const spec = { 'throughput.tasks': SPECS['throughput.tasks'] }
156
+
157
+ assert.equal(summarizeBaseline(spec, { 'throughput.tasks': 70 }, past)[0].standing, 'better-than-typical')
158
+ assert.equal(summarizeBaseline(spec, { 'throughput.tasks': 50 }, past)[0].standing, 'worse-than-typical')
159
+
160
+ /* 소요시간은 반대다 — 중앙값보다 낮은 것이 좋다. */
161
+ const lower = { 'workTime.p50Ms': SPECS['workTime.p50Ms'] }
162
+ const pastMs = [sample(1, { 'workTime.p50Ms': 40 }), sample(2, { 'workTime.p50Ms': 60 }), sample(3, { 'workTime.p50Ms': 80 })]
163
+ assert.equal(summarizeBaseline(lower, { 'workTime.p50Ms': 50 }, pastMs)[0].standing, 'better-than-typical')
164
+ assert.equal(summarizeBaseline(lower, { 'workTime.p50Ms': 70 }, pastMs)[0].standing, 'worse-than-typical')
165
+ })
166
+
167
+ test('짝수 표본의 중앙값은 가운데 둘의 평균이다', () => {
168
+ const [row] = summarizeBaseline({ 'throughput.tasks': SPECS['throughput.tasks'] }, { 'throughput.tasks': 55 }, [
169
+ sample(1, { 'throughput.tasks': 40 }),
170
+ sample(2, { 'throughput.tasks': 50 }),
171
+ sample(3, { 'throughput.tasks': 60 }),
172
+ sample(4, { 'throughput.tasks': 70 })
173
+ ])
174
+
175
+ assert.equal(row.median, 55)
176
+ assert.equal(row.standing, 'typical')
177
+ })
178
+
179
+ test('열거된 지표만 낸다 — 모르는 이름을 짐작하지 않는다', () => {
180
+ const rows = summarizeBaseline({ 'throughput.tasks': SPECS['throughput.tasks'] }, { 'throughput.tasks': 10, 'made.up': 99 } as any, [
181
+ sample(1, { 'throughput.tasks': 5 } as any)
182
+ ])
183
+
184
+ assert.deepEqual(rows.map(r => r.metric), ['throughput.tasks'])
185
+ })
186
+
187
+ test('무한·NaN 은 측정으로 세지 않는다', () => {
188
+ const [row] = summarizeBaseline({ 'throughput.tasks': SPECS['throughput.tasks'] }, { 'throughput.tasks': Number.NaN }, [
189
+ sample(1, { 'throughput.tasks': Number.POSITIVE_INFINITY }),
190
+ sample(2, { 'throughput.tasks': 40 }),
191
+ sample(3, { 'throughput.tasks': 60 })
192
+ ])
193
+
194
+ assert.equal(row.measuredCount, 2, '무한은 표본이 아니다')
195
+ assert.equal(row.noPosition, 'no-current', 'NaN 은 값이 아니다')
196
+ })