@things-factory/headless-twin 10.0.9 → 10.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +2 -2
  2. package/dist-server/engine/canonical-ingest.js +24 -2
  3. package/dist-server/engine/canonical-ingest.js.map +1 -1
  4. package/dist-server/engine/command-routing.js +1 -1
  5. package/dist-server/engine/command-routing.js.map +1 -1
  6. package/dist-server/engine/energy-topology.d.ts +86 -0
  7. package/dist-server/engine/energy-topology.js +144 -0
  8. package/dist-server/engine/energy-topology.js.map +1 -0
  9. package/dist-server/engine/index.d.ts +8 -3
  10. package/dist-server/engine/index.js +10 -3
  11. package/dist-server/engine/index.js.map +1 -1
  12. package/dist-server/engine/kpi-baseline.d.ts +1 -1
  13. package/dist-server/engine/kpi-baseline.js.map +1 -1
  14. package/dist-server/engine/kpi-fold.d.ts +137 -1
  15. package/dist-server/engine/kpi-fold.js +225 -0
  16. package/dist-server/engine/kpi-fold.js.map +1 -1
  17. package/dist-server/engine/kpi-query.d.ts +1 -1
  18. package/dist-server/engine/kpi-query.js +212 -18
  19. package/dist-server/engine/kpi-query.js.map +1 -1
  20. package/dist-server/engine/kpi-target.d.ts +17 -0
  21. package/dist-server/engine/kpi-target.js +24 -2
  22. package/dist-server/engine/kpi-target.js.map +1 -1
  23. package/dist-server/engine/live-feed-registry.d.ts +25 -0
  24. package/dist-server/engine/live-feed-registry.js +51 -0
  25. package/dist-server/engine/live-feed-registry.js.map +1 -0
  26. package/dist-server/engine/load-meter.d.ts +181 -0
  27. package/dist-server/engine/load-meter.js +267 -0
  28. package/dist-server/engine/load-meter.js.map +1 -0
  29. package/dist-server/engine/local-declarations.d.ts +262 -0
  30. package/dist-server/engine/local-declarations.js +528 -0
  31. package/dist-server/engine/local-declarations.js.map +1 -0
  32. package/dist-server/engine/model-basis.d.ts +39 -6
  33. package/dist-server/engine/model-basis.js +69 -9
  34. package/dist-server/engine/model-basis.js.map +1 -1
  35. package/dist-server/engine/{board-vocabulary.d.ts → model-vocabulary.d.ts} +4 -4
  36. package/dist-server/engine/{board-vocabulary.js → model-vocabulary.js} +37 -19
  37. package/dist-server/engine/model-vocabulary.js.map +1 -0
  38. package/dist-server/engine/oee-accumulator.d.ts +28 -0
  39. package/dist-server/engine/oee-accumulator.js +26 -1
  40. package/dist-server/engine/oee-accumulator.js.map +1 -1
  41. package/dist-server/engine/operation-basis.d.ts +32 -0
  42. package/dist-server/engine/operation-basis.js +87 -0
  43. package/dist-server/engine/operation-basis.js.map +1 -0
  44. package/dist-server/engine/property-effects.d.ts +30 -0
  45. package/dist-server/engine/property-effects.js +192 -0
  46. package/dist-server/engine/property-effects.js.map +1 -0
  47. package/dist-server/engine/runtime-key.d.ts +1 -1
  48. package/dist-server/engine/runtime-key.js +1 -1
  49. package/dist-server/engine/runtime-key.js.map +1 -1
  50. package/dist-server/engine/spec-coverage.js +3 -3
  51. package/dist-server/engine/spec-coverage.js.map +1 -1
  52. package/dist-server/engine/state-axes.d.ts +7 -2
  53. package/dist-server/engine/state-axes.js +8 -3
  54. package/dist-server/engine/state-axes.js.map +1 -1
  55. package/dist-server/engine/structure-diff.js +1 -1
  56. package/dist-server/engine/structure-diff.js.map +1 -1
  57. package/dist-server/engine/travel-estimator.d.ts +9 -2
  58. package/dist-server/engine/travel-estimator.js +11 -6
  59. package/dist-server/engine/travel-estimator.js.map +1 -1
  60. package/dist-server/engine/twin-engine.d.ts +332 -47
  61. package/dist-server/engine/twin-engine.js +1314 -279
  62. package/dist-server/engine/twin-engine.js.map +1 -1
  63. package/dist-server/engine/warm-start.d.ts +38 -4
  64. package/dist-server/engine/warm-start.js +38 -9
  65. package/dist-server/engine/warm-start.js.map +1 -1
  66. package/dist-server/index.d.ts +1 -0
  67. package/dist-server/index.js +17 -11
  68. package/dist-server/index.js.map +1 -1
  69. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.d.ts +5 -0
  70. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +48 -0
  71. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -0
  72. package/dist-server/migrations/index.d.ts +2 -0
  73. package/dist-server/migrations/index.js +10 -0
  74. package/dist-server/migrations/index.js.map +1 -0
  75. package/dist-server/service/index.d.ts +3 -2
  76. package/dist-server/service/index.js +25 -17
  77. package/dist-server/service/index.js.map +1 -1
  78. package/dist-server/service/reference/discovery-result.d.ts +1 -1
  79. package/dist-server/service/reference/discovery-result.js +2 -2
  80. package/dist-server/service/reference/discovery-result.js.map +1 -1
  81. package/dist-server/service/reference/ingest-space.d.ts +24 -1
  82. package/dist-server/service/reference/ingest-space.js +17 -1
  83. package/dist-server/service/reference/ingest-space.js.map +1 -1
  84. package/dist-server/service/reference/reference-live.d.ts +23 -0
  85. package/dist-server/service/reference/reference-live.js +121 -4
  86. package/dist-server/service/reference/reference-live.js.map +1 -1
  87. package/dist-server/service/reference/reference-master.d.ts +194 -13
  88. package/dist-server/service/reference/reference-master.js +159 -28
  89. package/dist-server/service/reference/reference-master.js.map +1 -1
  90. package/dist-server/service/reference/reference-resolver.d.ts +31 -1
  91. package/dist-server/service/reference/reference-resolver.js +223 -12
  92. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  93. package/dist-server/service/reference/template-registry.d.ts +19 -3
  94. package/dist-server/service/reference/template-registry.js.map +1 -1
  95. package/dist-server/service/twin-audit/command-audit.d.ts +37 -0
  96. package/dist-server/service/twin-audit/command-audit.js +53 -0
  97. package/dist-server/service/twin-audit/command-audit.js.map +1 -0
  98. package/dist-server/service/twin-audit/index.d.ts +4 -0
  99. package/dist-server/service/twin-audit/index.js +8 -0
  100. package/dist-server/service/twin-audit/index.js.map +1 -0
  101. package/dist-server/service/twin-audit/twin-audit-event.d.ts +24 -0
  102. package/dist-server/service/twin-audit/twin-audit-event.js +125 -0
  103. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -0
  104. package/dist-server/service/twin-audit/twin-audit-query.d.ts +4 -0
  105. package/dist-server/service/twin-audit/twin-audit-query.js +76 -0
  106. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -0
  107. package/dist-server/service/twin-control/twin-control-mutation.js +21 -1
  108. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  109. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  110. package/dist-server/service/twin-event/twin-event-keys.js +1 -1
  111. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  112. package/dist-server/service/twin-event/twin-event.d.ts +14 -5
  113. package/dist-server/service/twin-event/twin-event.js +46 -14
  114. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  115. package/dist-server/service/twin-forecast/forecast-metrics.d.ts +13 -0
  116. package/dist-server/service/twin-forecast/forecast-metrics.js +57 -0
  117. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -0
  118. package/dist-server/service/twin-forecast/forecast-tuning.d.ts +12 -0
  119. package/dist-server/service/twin-forecast/forecast-tuning.js +44 -0
  120. package/dist-server/service/twin-forecast/forecast-tuning.js.map +1 -0
  121. package/dist-server/service/twin-forecast/gap-analytics.d.ts +28 -2
  122. package/dist-server/service/twin-forecast/gap-analytics.js +43 -17
  123. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  124. package/dist-server/service/twin-forecast/twin-forecast-query.js +217 -38
  125. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  126. package/dist-server/service/twin-instance/twin-instance.d.ts +38 -5
  127. package/dist-server/service/twin-instance/twin-instance.js +66 -20
  128. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  129. package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -2
  130. package/dist-server/service/twin-journal/twin-journal-query.js +71 -9
  131. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  132. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +3 -5
  133. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +24 -27
  134. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  135. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  136. package/dist-server/service/twin-metrics/twin-metrics-query.js +62 -0
  137. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  138. package/dist-server/service/twin-model/epcis-coverage.d.ts +14 -0
  139. package/dist-server/service/twin-model/epcis-coverage.js +174 -0
  140. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -0
  141. package/dist-server/service/twin-model/iec61850-coverage.d.ts +14 -0
  142. package/dist-server/service/twin-model/iec61850-coverage.js +98 -0
  143. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -0
  144. package/dist-server/service/twin-model/index.d.ts +13 -0
  145. package/dist-server/service/twin-model/index.js +29 -0
  146. package/dist-server/service/twin-model/index.js.map +1 -0
  147. package/dist-server/service/twin-model/isa95-coverage.d.ts +48 -0
  148. package/dist-server/service/twin-model/isa95-coverage.js +155 -0
  149. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -0
  150. package/dist-server/service/twin-model/project-structure.d.ts +16 -0
  151. package/dist-server/service/twin-model/project-structure.js +173 -0
  152. package/dist-server/service/twin-model/project-structure.js.map +1 -0
  153. package/dist-server/service/twin-model/standard-coverage.d.ts +9 -0
  154. package/dist-server/service/twin-model/standard-coverage.js +37 -0
  155. package/dist-server/service/twin-model/standard-coverage.js.map +1 -0
  156. package/dist-server/service/twin-model/twin-equipment.d.ts +64 -0
  157. package/dist-server/service/twin-model/twin-equipment.js +134 -0
  158. package/dist-server/service/twin-model/twin-equipment.js.map +1 -0
  159. package/dist-server/service/twin-model/twin-lineage-query.d.ts +3 -0
  160. package/dist-server/service/twin-model/twin-lineage-query.js +206 -0
  161. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -0
  162. package/dist-server/service/twin-model/twin-location.d.ts +58 -0
  163. package/dist-server/service/twin-model/twin-location.js +130 -0
  164. package/dist-server/service/twin-model/twin-location.js.map +1 -0
  165. package/dist-server/service/twin-model/twin-model-item-query.d.ts +141 -0
  166. package/dist-server/service/twin-model/twin-model-item-query.js +852 -0
  167. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -0
  168. package/dist-server/service/twin-model/twin-model-mutation.d.ts +57 -0
  169. package/dist-server/service/twin-model/twin-model-mutation.js +490 -0
  170. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -0
  171. package/dist-server/service/twin-model/twin-model-query.d.ts +43 -0
  172. package/dist-server/service/twin-model/twin-model-query.js +551 -0
  173. package/dist-server/service/twin-model/twin-model-query.js.map +1 -0
  174. package/dist-server/service/twin-model/twin-model-tree-query.d.ts +3 -0
  175. package/dist-server/service/twin-model/twin-model-tree-query.js +158 -0
  176. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -0
  177. package/dist-server/service/twin-model/twin-operation.d.ts +61 -0
  178. package/dist-server/service/twin-model/twin-operation.js +137 -0
  179. package/dist-server/service/twin-model/twin-operation.js.map +1 -0
  180. package/dist-server/service/twin-space/move-space.d.ts +65 -0
  181. package/dist-server/service/twin-space/move-space.js +166 -0
  182. package/dist-server/service/twin-space/move-space.js.map +1 -0
  183. package/dist-server/service/twin-space/space-integrity.d.ts +97 -0
  184. package/dist-server/service/twin-space/space-integrity.js +182 -0
  185. package/dist-server/service/twin-space/space-integrity.js.map +1 -0
  186. package/dist-server/service/twin-space/twin-space-area.js +4 -4
  187. package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
  188. package/dist-server/service/twin-space/twin-space-representation.js +6 -6
  189. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  190. package/dist-server/service/twin-space/twin-space-resolver.d.ts +63 -1
  191. package/dist-server/service/twin-space/twin-space-resolver.js +356 -18
  192. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  193. package/dist-server/service/twin-space/twin-space.d.ts +17 -1
  194. package/dist-server/service/twin-space/twin-space.js +15 -5
  195. package/dist-server/service/twin-space/twin-space.js.map +1 -1
  196. package/dist-server/service/twin-structure/twin-structure.d.ts +1 -1
  197. package/dist-server/service/twin-structure/twin-structure.js +8 -8
  198. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  199. package/dist-shared/axis-read.d.ts +12 -0
  200. package/dist-shared/axis-read.js +32 -0
  201. package/dist-shared/axis-read.js.map +1 -0
  202. package/{dist-server/engine → dist-shared}/entity-delta.d.ts +3 -0
  203. package/{dist-server/engine → dist-shared}/entity-delta.js +102 -2
  204. package/dist-shared/entity-delta.js.map +1 -0
  205. package/dist-shared/kpi-broadcast.js.map +1 -0
  206. package/dist-shared/twin-level.js.map +1 -0
  207. package/package.json +14 -12
  208. package/server/engine/canonical-ingest.ts +36 -4
  209. package/server/engine/command-routing.ts +1 -1
  210. package/server/engine/energy-topology.ts +189 -0
  211. package/server/engine/index.ts +10 -3
  212. package/server/engine/kpi-baseline.ts +1 -1
  213. package/server/engine/kpi-fold.ts +389 -1
  214. package/server/engine/kpi-query.ts +214 -19
  215. package/server/engine/kpi-target.ts +24 -2
  216. package/server/engine/live-feed-registry.ts +58 -0
  217. package/server/engine/load-meter.ts +384 -0
  218. package/server/engine/local-declarations.ts +700 -0
  219. package/server/engine/model-basis.ts +78 -10
  220. package/server/engine/{board-vocabulary.ts → model-vocabulary.ts} +36 -15
  221. package/server/engine/oee-accumulator.ts +34 -1
  222. package/server/engine/operation-basis.ts +100 -0
  223. package/server/engine/property-effects.ts +199 -0
  224. package/server/engine/runtime-key.ts +1 -1
  225. package/server/engine/spec-coverage.ts +3 -3
  226. package/server/engine/state-axes.ts +8 -3
  227. package/server/engine/structure-diff.ts +1 -1
  228. package/server/engine/travel-estimator.ts +11 -6
  229. package/server/engine/twin-engine.ts +1414 -274
  230. package/server/engine/warm-start.ts +83 -12
  231. package/server/index.ts +13 -10
  232. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +43 -0
  233. package/server/migrations/index.ts +7 -0
  234. package/server/service/index.ts +9 -1
  235. package/server/service/reference/discovery-result.ts +2 -2
  236. package/server/service/reference/ingest-space.ts +35 -1
  237. package/server/service/reference/reference-live.ts +119 -4
  238. package/server/service/reference/reference-master.ts +276 -35
  239. package/server/service/reference/reference-resolver.ts +232 -12
  240. package/server/service/reference/template-registry.ts +20 -5
  241. package/server/service/twin-audit/command-audit.ts +81 -0
  242. package/server/service/twin-audit/index.ts +5 -0
  243. package/server/service/twin-audit/twin-audit-event.ts +112 -0
  244. package/server/service/twin-audit/twin-audit-query.ts +72 -0
  245. package/server/service/twin-control/twin-control-mutation.ts +22 -1
  246. package/server/service/twin-event/twin-event-keys.ts +1 -1
  247. package/server/service/twin-event/twin-event.ts +52 -15
  248. package/server/service/twin-forecast/forecast-metrics.ts +60 -0
  249. package/server/service/twin-forecast/forecast-tuning.ts +40 -0
  250. package/server/service/twin-forecast/gap-analytics.ts +48 -9
  251. package/server/service/twin-forecast/twin-forecast-query.ts +208 -36
  252. package/server/service/twin-instance/twin-instance.ts +126 -25
  253. package/server/service/twin-journal/twin-journal-query.ts +74 -10
  254. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +23 -22
  255. package/server/service/twin-metrics/twin-metrics-query.ts +59 -2
  256. package/server/service/twin-model/epcis-coverage.ts +195 -0
  257. package/server/service/twin-model/iec61850-coverage.ts +117 -0
  258. package/server/service/twin-model/index.ts +25 -0
  259. package/server/service/twin-model/isa95-coverage.ts +190 -0
  260. package/server/service/twin-model/project-structure.ts +199 -0
  261. package/server/service/twin-model/standard-coverage.ts +35 -0
  262. package/server/service/twin-model/twin-equipment.ts +150 -0
  263. package/server/service/twin-model/twin-lineage-query.ts +193 -0
  264. package/server/service/twin-model/twin-location.ts +140 -0
  265. package/server/service/twin-model/twin-model-item-query.ts +848 -0
  266. package/server/service/twin-model/twin-model-mutation.ts +503 -0
  267. package/server/service/twin-model/twin-model-query.ts +537 -0
  268. package/server/service/twin-model/twin-model-tree-query.ts +180 -0
  269. package/server/service/twin-model/twin-operation.ts +150 -0
  270. package/server/service/twin-space/move-space.ts +262 -0
  271. package/server/service/twin-space/space-integrity.ts +293 -0
  272. package/server/service/twin-space/twin-space-area.ts +4 -4
  273. package/server/service/twin-space/twin-space-representation.ts +6 -6
  274. package/server/service/twin-space/twin-space-resolver.ts +355 -21
  275. package/server/service/twin-space/twin-space.ts +40 -6
  276. package/server/service/twin-structure/twin-structure.ts +10 -10
  277. package/shared/axis-read.ts +28 -0
  278. package/{server/engine → shared}/entity-delta.ts +96 -1
  279. package/test/adopt-structure-live.test.ts +133 -0
  280. package/test/attention-digest.test.ts +3 -3
  281. package/test/axis-read.test.ts +87 -0
  282. package/test/boot-resume.test.ts +205 -0
  283. package/test/canonical-ingest-vocabularies.test.ts +118 -0
  284. package/test/capability-mapping.test.ts +1 -1
  285. package/test/command-routing.test.ts +1 -1
  286. package/test/declaration-reaches-model.test.ts +178 -0
  287. package/test/discovery-result.test.ts +1 -1
  288. package/test/energy-topology.test.ts +113 -0
  289. package/test/entity-delta.test.ts +171 -1
  290. package/test/event-time-column.test.ts +67 -0
  291. package/test/forecast-metrics.test.ts +71 -0
  292. package/test/forecast-tuning.test.ts +42 -0
  293. package/test/gap-analytics.test.ts +34 -7
  294. package/test/ingest-bench.test.ts +6 -6
  295. package/test/ingest-running-guard.test.ts +136 -0
  296. package/test/ingest-space.test.ts +29 -1
  297. package/test/instance-cache-lifecycle.test.ts +110 -0
  298. package/test/kernel-kind-guard.test.ts +96 -0
  299. package/test/kpi-baseline-db.test.ts +3 -1
  300. package/test/kpi-baseline.test.ts +3 -3
  301. package/test/kpi-fold.test.ts +393 -2
  302. package/test/kpi-query-bench.test.ts +5 -3
  303. package/test/lineage-survives-restart.test.ts +203 -0
  304. package/test/live-feed-registry.test.ts +67 -0
  305. package/test/live-kernel-facts.test.ts +161 -0
  306. package/test/live-mirror-parity.test.ts +22 -15
  307. package/test/load-meter.test.ts +314 -0
  308. package/test/local-declarations.test.ts +554 -0
  309. package/test/master-to-twin.test.ts +121 -33
  310. package/test/model-basis.test.ts +56 -1
  311. package/test/{board-vocabulary.test.ts → model-vocabulary.test.ts} +25 -25
  312. package/test/move-space.test.ts +149 -0
  313. package/test/mutation-gate.test.ts +19 -5
  314. package/test/oee-accumulator.test.ts +26 -7
  315. package/test/operation-basis.test.ts +100 -0
  316. package/test/operations-capability-db.test.ts +165 -0
  317. package/test/project-structure-db.test.ts +226 -0
  318. package/test/projection-reaches-screen.test.ts +272 -0
  319. package/test/property-effects.test.ts +96 -0
  320. package/test/resync-origin-site.test.ts +77 -0
  321. package/test/scale-twin-bench.test.ts +5 -5
  322. package/test/snapshot-freshness.test.ts +60 -0
  323. package/test/space-integrity.test.ts +223 -0
  324. package/test/standard-coverage.test.ts +94 -0
  325. package/test/state-axes.test.ts +1 -1
  326. package/test/streamline-e2e.test.ts +14 -14
  327. package/test/structure-revision-db.test.ts +11 -11
  328. package/test/tenant-registry-db.test.ts +5 -5
  329. package/test/twin-audit.test.ts +83 -0
  330. package/test/twin-model-item-db.test.ts +275 -0
  331. package/test/twin-model-tree-db.test.ts +176 -0
  332. package/test/twin-origin-resync.test.ts +114 -0
  333. package/test/warm-start-seam.test.ts +6 -6
  334. package/test/warm-start.test.ts +88 -3
  335. package/tsconfig.json +6 -1
  336. package/tsconfig.shared.json +23 -0
  337. package/tsconfig.shared.tsbuildinfo +1 -0
  338. package/tsconfig.tsbuildinfo +1 -0
  339. package/dist-server/engine/board-vocabulary.js.map +0 -1
  340. package/dist-server/engine/entity-delta.js.map +0 -1
  341. package/dist-server/engine/kpi-broadcast.js.map +0 -1
  342. package/dist-server/engine/twin-level.js.map +0 -1
  343. package/dist-server/service/twin-event/backfill-keys.d.ts +0 -11
  344. package/dist-server/service/twin-event/backfill-keys.js +0 -63
  345. package/dist-server/service/twin-event/backfill-keys.js.map +0 -1
  346. package/dist-server/tsconfig.tsbuildinfo +0 -1
  347. package/server/service/twin-event/backfill-keys.ts +0 -72
  348. /package/{dist-server/engine → dist-shared}/kpi-broadcast.d.ts +0 -0
  349. /package/{dist-server/engine → dist-shared}/kpi-broadcast.js +0 -0
  350. /package/{dist-server/engine → dist-shared}/twin-level.d.ts +0 -0
  351. /package/{dist-server/engine → dist-shared}/twin-level.js +0 -0
  352. /package/{server/engine → shared}/kpi-broadcast.ts +0 -0
  353. /package/{server/engine → shared}/twin-level.ts +0 -0
@@ -0,0 +1,25 @@
1
+ /*
2
+ * 트윈 구조 모델 — `TwinInstance.model`(JSON 덩어리) 에서 풀어낸 **표준 엔티티**(ADR-0032).
3
+ *
4
+ * 여기 있는 것은 전부 **원본에서 온 캐시**다. 정본은 원본 시스템이고(ADR-0028), 이 행들은
5
+ * 인제스트가 만들며 언제든 전부 지우고 다시 만들 수 있어야 한다.
6
+ *
7
+ * 오늘은 **실제로 채워지는 세 축**만 있다 — 자리·설비·공정. 사람·자산·등급·속성·생산 정의는
8
+ * 지금 0건이라 만들지 않는다("설계와 착수는 다르다", ADR-0032-H). 모델은 그때 옳게 세운다.
9
+ */
10
+ export { TwinLocation } from './twin-location.js'
11
+ export { TwinEquipment } from './twin-equipment.js'
12
+ export { TwinOperation } from './twin-operation.js'
13
+
14
+ import { TwinLocation } from './twin-location.js'
15
+ import { TwinEquipment } from './twin-equipment.js'
16
+ import { TwinOperation } from './twin-operation.js'
17
+
18
+ import { TwinModelQuery } from './twin-model-query.js'
19
+ import { TwinModelItemQuery } from './twin-model-item-query.js'
20
+ import { TwinModelTreeQuery } from './twin-model-tree-query.js'
21
+ import { TwinModelMutation } from './twin-model-mutation.js'
22
+ import { TwinLineageQuery } from './twin-lineage-query.js'
23
+
24
+ export const entities = [TwinLocation, TwinEquipment, TwinOperation]
25
+ export const resolvers = [TwinModelQuery, TwinModelItemQuery, TwinModelTreeQuery, TwinModelMutation, TwinLineageQuery]
@@ -0,0 +1,190 @@
1
+ /*
2
+ * ISA-95 적합성 기준 — **표준이 요구하는 것의 목록.**
3
+ *
4
+ * ── 왜 이 파일이 필요한가 ──────────────────────────────────────────────────────
5
+ * 지금까지 화면은 "우리가 가진 축" 만 보였다. 그러면 채운 것은 세어지지만 **빠진 것은 셀 수 없다** —
6
+ * 없는 것은 화면에 없으므로 아무도 그것을 묻지 않는다. 표준 준수는 "우리 축에 표준 이름을 붙였다"
7
+ * 가 아니라 **표준의 목록을 기준으로 빠진 것이 없다**는 말이어야 한다. 기준이 코드로 있어야 그
8
+ * 문장을 검사할 수 있다.
9
+ *
10
+ * ── 세 층에서 본다 ────────────────────────────────────────────────────────────
11
+ * 표준 준수는 한 층의 일이 아니다. 개념마다 셋을 따로 본다:
12
+ *
13
+ * · `structure` — **데이터구조**가 있나(커널 축·호스트 표에 이 개념의 자리가 있나)
14
+ * · `behavior` — **기능**이 그것을 쓰나(커널이 계획·집행·집계에서 실제로 다루나)
15
+ * · `surface` — **UI 노출**이 있나(사용자가 그것을 볼 수 있나)
16
+ *
17
+ * 셋을 합치면 "구조는 있는데 아무도 안 쓰는 컬럼" 과 "커널은 쓰는데 화면에 없는 사실" 이 같은
18
+ * 값으로 보인다. 실제로 이 프로젝트에서 둘 다 나왔다(인력 명세는 구조·기능이 있었지만 화면에
19
+ * 없었고, `properties` 는 구조만 있다).
20
+ *
21
+ * ── 값은 관측이 아니라 선언이다 ────────────────────────────────────────────────
22
+ * 여기 적힌 것은 **사람이 판단해 적은 사실**이다(자동 판정이 아니다). 그래서 틀릴 수 있고, 틀리면
23
+ * 여기를 고쳐야 한다 — 화면은 이 선언을 그대로 보이고 지어내지 않는다. 자동으로 알 수 있는 것
24
+ * 하나는 자동으로 한다: `axis` 가 커널 축 목록에 없으면 그것은 **구조가 없는 것**이다(오타 방지).
25
+ *
26
+ * ── 이 표가 커널에 없는 이유 ──────────────────────────────────────────────────
27
+ * 커널은 **런타임 모델의 SSOT** 다(무엇이 있는가). 이 표는 **참조 모델의 목록**이다(무엇이 있어야
28
+ * 하는가). 둘은 다른 종류의 사실이고, 후자는 커널을 배포하지 않고도 늘어야 한다.
29
+ */
30
+
31
+ /** 한 층의 상태 — 있음 · 부분 · 없음. "부분" 을 두는 이유: 절반 있는 것을 있다고 하면 기준이 죽는다. */
32
+ export type CoverageLevel = 'full' | 'partial' | 'none'
33
+
34
+ export interface Isa95Concept {
35
+ /** 표준이 부르는 이름 — 이 표의 키다(우리 어휘가 아니라 표준 어휘). */
36
+ std: string
37
+ /** 표준의 어느 부분인가 — Part 2(자원 모델) · Part 4(일정·실적) 등. */
38
+ part: string
39
+ /** 사람이 읽는 이름(i18n 키). 화면이 번역한다. */
40
+ label: string
41
+ /** 우리 커널 축 — 있으면 데이터구조가 있는 것이다. 없으면 `null`. */
42
+ axis: string | null
43
+ /** 세 층의 상태. `structure` 는 `axis` 로도 검증된다. */
44
+ structure: CoverageLevel
45
+ behavior: CoverageLevel
46
+ surface: CoverageLevel
47
+ /** 왜 그 수준인지 — **모르는 것을 감추지 않기 위해** 한 줄로 적는다(i18n 키). */
48
+ note?: string
49
+ /**
50
+ * 자기 축이 없을 때 **어디서 보이나** — 그 사실을 들고 있는 축들.
51
+ *
52
+ * 「어디에」칸이 두 가지를 합쳐고 있었다: *아직 축이 없다(부채)* 와 *축일 수 없다(설계)*. 가용 능력은
53
+ * 개체가 아니라 자원의 지금 상태이므로 축이 될 수 없는데, 세 층이 다 완전한 줄이 "개념 축이 없습니다"
54
+ * 라고만 말하면 **표가 스스로 모순된 말을 한다**(다 됐다면서 없다고 한다).
55
+ *
56
+ * 그래서 축일 수 없는 것은 갈 곳을 가리킨다. 여기가 비어 있고 축도 없으면 그것은 **진짜 빈칸**이다.
57
+ */
58
+ shownOn?: string[]
59
+ }
60
+
61
+ /*
62
+ * 가용 능력(2026-08-12, 커널 0.6.8) — **판정과 이유가 상태에 실려 온다**(`capability {available, reason}`).
63
+ *
64
+ * 셋 다 완전인 근거: 상태가 판정을 들고 오고(시뮬·미러가 같은 함수로 파생한다), 배정이 같은 판정을
65
+ * 쓰고, 두 화면이 같은 낱말로 그 이유를 보인다(자원 탭 배지 · 인스펙션 관측 열).
66
+ *
67
+ * 왜 축이 없는가 — 가용 능력은 개체가 아니라 **자원의 지금 상태**다. 개념 카드로 세우면 사람·설비와
68
+ * 나란히 놓여 "능력이 몇 개인가" 를 세게 되는데, 그건 물을 일이 아니다.
69
+ */
70
+
71
+ /*
72
+ * 생산 능력(2026-08-13, 커널 0.6.12) — **구간의 양을 표준 종류로 구분한다.**
73
+ *
74
+ * `structure` 완전: 계약이 보고서를 낸다(`operationsCapabilityOf`) — 구간(StartTime·EndTime) · 표준
75
+ * 열거값(Committed·Available·Unattainable) · 공정별 분해 · 제약하는 축. 낱말은 B2MML 대조 후 표준 것을
76
+ * 그대로 쓴다(우리 낱말을 만들지 않았다).
77
+ * `behavior` 완전: 약정은 오더에서, 달성불가는 자원 판정(`capabilityOf`)에서 온다 — 그리고 그 판정은
78
+ * 배정이 쓰는 것과 같은 한 벌이다. 표준의 `Unattainable` 정의가 그 판정과 겹친다.
79
+ * `surface` 완전: 두 자리에서 보인다 — 성과 화면이 현장 전체의 "앞으로 더 받을 수 있는 몫" 을, 공정
80
+ * 상세가 그 공정 하나의 분해(상한·약정·달성불가·가용)와 제약하는 축을 보인다. 두 화면은 **같은 낱말**을
81
+ * 쓴다(같은 i18n 키) — 같은 사실을 다르게 부르면 어느 쪽이 맞는지 아무도 모른다.
82
+ *
83
+ * 남은 것 — 표준은 **게시된(약속된) 능력**도 담는다(계산이 아니라 선언). 값을 실을 소스가 아직
84
+ * 침묵하므로 빈 축을 먼저 만들지 않는다(상태 쪽 `workCalendar` 를 지운 이유와 같다).
85
+ *
86
+ * 과거 분해(Used·Unused)는 이 보고서에 없다 — 그것은 실적이고 이미 두 곳에 있다(설비 OEE 의 가동/유휴,
87
+ * 성과 화면의 구간 가동률). 표준이 그 둘을 다른 종류로 부르므로 **합치지 않는다.**
88
+ */
89
+
90
+ /*
91
+ * Part 2 — 자원 모델. 네 자원 각각이 **등급/정의 ↔ 개체** 이중구조를 갖는 것이 이 부분의 핵심이고,
92
+ * 그 이중성이 이 트윈의 뼈대이기도 하다.
93
+ */
94
+ const PART2: Isa95Concept[] = [
95
+ { std: 'PersonnelClass', part: '2', label: 'twin.isa95.PersonnelClass', axis: 'personnelClasses', structure: 'full', behavior: 'full', surface: 'full' },
96
+ { std: 'Person', part: '2', label: 'twin.isa95.Person', axis: 'persons', structure: 'full', behavior: 'full', surface: 'full' },
97
+ { std: 'PersonnelCapability', part: '2', label: 'twin.isa95.PersonnelCapability', axis: null, shownOn: ['persons'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.capability' },
98
+ { std: 'EquipmentClass', part: '2', label: 'twin.isa95.EquipmentClass', axis: 'equipmentClasses', structure: 'full', behavior: 'full', surface: 'full' },
99
+ { std: 'Equipment', part: '2', label: 'twin.isa95.Equipment', axis: 'equipment', structure: 'full', behavior: 'full', surface: 'full' },
100
+ { std: 'EquipmentCapability', part: '2', label: 'twin.isa95.EquipmentCapability', axis: null, shownOn: ['equipment'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.capability' },
101
+ { std: 'PhysicalAssetClass', part: '2', label: 'twin.isa95.PhysicalAssetClass', axis: 'assetClasses', structure: 'full', behavior: 'full', surface: 'full' },
102
+ { std: 'PhysicalAsset', part: '2', label: 'twin.isa95.PhysicalAsset', axis: 'assets', structure: 'full', behavior: 'full', surface: 'full' },
103
+ { std: 'MaterialClass', part: '2', label: 'twin.isa95.MaterialClass', axis: 'materialClasses', structure: 'full', behavior: 'partial', surface: 'full' },
104
+ { std: 'MaterialDefinition', part: '2', label: 'twin.isa95.MaterialDefinition', axis: 'materialDefinitions', structure: 'full', behavior: 'full', surface: 'full' },
105
+ /* 로트·서브로트는 개체 축이 아니라 **관측**으로 존재한다(EPCIS 개체) — 구조는 저널이 갖는다. */
106
+ { std: 'MaterialLot', part: '2', label: 'twin.isa95.MaterialLot', axis: null, structure: 'partial', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.lot' },
107
+ { std: 'MaterialSublot', part: '2', label: 'twin.isa95.MaterialSublot', axis: null, structure: 'none', behavior: 'none', surface: 'none' },
108
+ { std: 'ProcessSegment', part: '2', label: 'twin.isa95.ProcessSegment', axis: 'operations', structure: 'full', behavior: 'full', surface: 'full' },
109
+ /*
110
+ * 속성은 자원마다 붙는 확장이라 **자기 축이 아니다** — 그래서 `axis` 는 없지만 구조는 완전하다:
111
+ * 계약(`ResourceProperty`: 값·데이터형·단위·하위·등급속성)이 표준을 그대로 담고, 인제스트·상태·
112
+ * 화면이 그것을 통과시킨다.
113
+ *
114
+ * 기능은 **부분**이다: 호스트의 주행 추정기가 `speed` 를 읽어 이동 시간을 만드는 것이 유일한 소비처다
115
+ * (다른 속성은 아직 아무도 읽지 않는다). 그 사실을 `full` 로 올리면 표가 거짓이 된다.
116
+ */
117
+ { std: 'Property', part: '2', label: 'twin.isa95.Property', axis: null, structure: 'full', behavior: 'partial', surface: 'full', note: 'twin.isa95.note.property' }
118
+ ]
119
+
120
+ /*
121
+ * Part 4 — 일정과 실적.
122
+ *
123
+ * **2026-08-11 이전에는 이 부분이 거의 비어 있었다.** 오더·작업은 런타임과 저널에 실재하는데 조회
124
+ * 모델의 축이 아니었고, 그래서 "이 트윈이 표준의 어디를 채우나" 라는 질문에 절반만 답했다.
125
+ *
126
+ * 커널이 계약을 넓혀(`source: 'state'`) 두 축이 추가되었다 — 이제 요구(`orders`)와 응답(`tasks`)을 개념으로
127
+ * 가리켜 걸어 들어갈 수 있다. 남은 것은 **과거로 다시 계산하는 길**(저널 커서 페이징)과 능력·검사 명세다.
128
+ */
129
+ const PART4: Isa95Concept[] = [
130
+ { std: 'OperationsDefinition', part: '4', label: 'twin.isa95.OperationsDefinition', axis: 'productionSpec', structure: 'full', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.opsdef' },
131
+ /*
132
+ * 2026-08-11 — **축이 추가되었다**(커널 0.6.3 · `source: 'state'`). 요구는 `orders`, 응답은 `tasks` 다.
133
+ *
134
+ * `structure` 를 `full` 로 올린다: 담을 자리가 계약에 있다(상태 계약 + 축 선언). **테이블 투영이
135
+ * 없는 것은 결손이 아니다** — 이 축의 정본은 저널이고, 투영하지 않는 것이 결정이다(ADR-0032-D).
136
+ *
137
+ * `surface` 도 `full` 이다(같은 날 두 번째 단계): 목록·상세·관계에 더해 **과거로 다시 계산하는 길**이 화면에
138
+ * 있다 — 시각 커서를 옮기면 그 시점의 사실이 나오고, 무엇을 근거로 답했는지도 화면이 말한다
139
+ * (라이브=권위 · 저널=파생 · 접은 리비전). 실측: 이른 시각 0건 · 하루 뒤 504건.
140
+ */
141
+ { std: 'OperationsSchedule', part: '4', label: 'twin.isa95.OperationsSchedule', axis: 'orders', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.schedule' },
142
+ { std: 'OperationsPerformance', part: '4', label: 'twin.isa95.OperationsPerformance', axis: 'tasks', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.performance' },
143
+ { std: 'OperationsCapability', part: '4', label: 'twin.isa95.OperationsCapability', axis: null, shownOn: ['operations'], structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.opscapability' },
144
+ /*
145
+ * 2026-08-12 — **축이 추가되었다**(커널 0.6.5). 자원 아홉이 가리키던 참조에 대상이 생겼고, 없는 명세를
146
+ * 가리키면 선 채움이 끊어진 참조로 센다.
147
+ *
148
+ * 기능이 **`partial` 로 살아났다**(커널 0.6.6): 결과(`TestResult`)가 계약에 들어오면서 커널이 배정에서
149
+ * 그것을 본다 — 만료·불합격이면 그 자격은 성립하지 않는다(실측: 만료된 용접사는 용접 작업 5건 중 한
150
+ * 번도 배정되지 않았다).
151
+ *
152
+ * `full` 이 아닌 이유는 둘이다: **사람만 판정한다**(설비·자산의 검사 결과는 아직 배정에 쓰지 않는다),
153
+ * 그리고 결과가 없으면 판정하지 않는다(선언한 것만 제약이 된다 — 그건 결손이 아니라 규율이다).
154
+ */
155
+ { std: 'TestSpecification', part: '4', label: 'twin.isa95.TestSpecification', axis: 'testSpecifications', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.test' },
156
+ { std: 'WorkMaster', part: '4', label: 'twin.isa95.WorkMaster', axis: 'recipes', structure: 'full', behavior: 'full', surface: 'full' },
157
+ { std: 'WorkDirective', part: '4', label: 'twin.isa95.WorkDirective', axis: null, structure: 'none', behavior: 'none', surface: 'none' }
158
+ ]
159
+
160
+ /** 표준이 요구하는 것 전부 — 이 배열이 곧 기준이다. */
161
+ export const ISA95_CONCEPTS: Isa95Concept[] = [...PART2, ...PART4]
162
+
163
+ /**
164
+ * 적합성 보고 — **채운 것과 빠진 것을 함께.**
165
+ *
166
+ * `axis` 를 커널 축 목록과 맞춰 본다: 축이 있다고 적혀 있는데 커널에 없으면 그 선언이 낡은 것이므로
167
+ * **구조 없음으로 낮추고 그 사실을 말한다**(조용히 통과시키면 이 표가 거짓이 된다).
168
+ */
169
+ export function isa95Coverage(axes: string[]): {
170
+ model: string
171
+ concepts: (Isa95Concept & { axisMissing?: boolean })[]
172
+ totals: { concepts: number; structure: number; behavior: number; surface: number }
173
+ } {
174
+ const known = new Set(axes)
175
+ const concepts = ISA95_CONCEPTS.map(c => {
176
+ const axisMissing = !!c.axis && !known.has(c.axis)
177
+ /* 가리키는 곳이 없어진 축은 **가리키지 않는다** — 죽은 문으로 보내면 화면이 거짓말을 한다.
178
+ `axis` 와 같은 규율이고, 여기가 비면 그 줄은 다시 "축이 없다" 로 정직하게 돌아간다. */
179
+ const shownOn = c.shownOn?.filter(a => known.has(a))
180
+ const fixed = shownOn && shownOn.length !== c.shownOn?.length ? { ...c, shownOn } : c
181
+ return axisMissing ? { ...fixed, structure: 'none' as CoverageLevel, axisMissing } : fixed
182
+ })
183
+ /* 채운 수는 `full` 만 센다 — 부분을 채운 것으로 세면 기준이 스스로를 속인다. */
184
+ const count = (k: 'structure' | 'behavior' | 'surface') => concepts.filter(c => c[k] === 'full').length
185
+ return {
186
+ model: 'ISA-95',
187
+ concepts,
188
+ totals: { concepts: concepts.length, structure: count('structure'), behavior: count('behavior'), surface: count('surface') }
189
+ }
190
+ }
@@ -0,0 +1,199 @@
1
+ import { getRepository } from '@things-factory/shell'
2
+
3
+ import { TwinInstance } from '../twin-instance/twin-instance.js'
4
+ import { TwinArea } from '../twin-space/twin-area.js'
5
+ import { TwinLocation } from './twin-location.js'
6
+ import { TwinEquipment } from './twin-equipment.js'
7
+ import { TwinOperation } from './twin-operation.js'
8
+ import { readBoardEquipment } from '@operato/twin-kernel'
9
+
10
+ /*
11
+ * 구조 투영 — `model`(커널 입력 문서) → **표준 엔티티 행**(ADR-0032).
12
+ *
13
+ * ── 왜 전량 교체인가 ────────────────────────────────────────────────────────
14
+ * 이 행들은 **원본에서 온 캐시**다(ADR-0032-D). 원본이 진실이므로 diff 로 아끼는 것보다
15
+ * **매번 그대로 다시 그리는 것**이 맞다 — diff 는 "우리 쪽에 남겨야 할 것이 있다" 는 전제를
16
+ * 깔고, 그 전제가 소유 침범의 시작이다.
17
+ *
18
+ * 규모가 커지면 서명(`TwinStructure.signature`)이 같을 때 건너뛰는 장치가 이미 있으므로
19
+ * 그것을 앞에 둔다. 여기서 diff 를 발명하지 않는다.
20
+ *
21
+ * ── 순서가 중요하다 ────────────────────────────────────────────────────────
22
+ * 설비가 자리를 FK 로 가리키므로 **자리를 먼저** 넣는다. 지우는 것은 반대 순서다.
23
+ * (자리 삭제 시 설비의 `homeLocation` 은 `SET NULL` 이지만, 전량 교체에서는 둘 다 지운다.)
24
+ */
25
+ export interface ProjectionReport {
26
+ locations: number
27
+ equipment: number
28
+ operations: number
29
+ /** 자리를 못 찾아 기준 자리를 비운 설비 — **조용히 넘기지 않는다.** */
30
+ unresolvedHome: string[]
31
+ /** 구역을 못 찾아 비운 자리. 구역은 model 밖(호스트 개념)이라 어긋날 수 있다. */
32
+ unresolvedArea: string[]
33
+ }
34
+
35
+ /**
36
+ * 이 트윈의 구조 행을 **원본 그대로 다시 그린다.**
37
+ *
38
+ * `sourceRef` 는 어느 원본에서 왔는지다(`ReferenceMaster.source` = instanceId). 없는 행은
39
+ * 만들 수 없다 — 캐시라는 사실을 구조로 지킨다.
40
+ */
41
+ export async function projectStructure(domainId: string, instanceId: string, model: any, sourceRef: string): Promise<ProjectionReport> {
42
+ const instance = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })
43
+ if (!instance) throw new Error(`cannot project structure: twin instance "${instanceId}" is not registered`)
44
+
45
+ /*
46
+ * **같은 id 가 둘이면 그 사실을 말한다** — 제약 위반 문구를 사용자에게 던지지 않는다.
47
+ *
48
+ * 실측(2026-08-18): 현장 값을 그 현장의 트윈 전부에 얹다가 한 트윈에서 투영이 터졌다. 올라온 것은
49
+ * `SQLITE_CONSTRAINT: UNIQUE constraint failed: twin_equipment...` 뿐이라 **무엇이 잘못됐는지 아무도
50
+ * 알 수 없었다.** 원인은 그 모델에 설비 `load-1` 이 두 번 들어 있던 것이다(id 는 트윈 안에서 유일해야
51
+ * 한다). 조용히 하나만 남기지 않는다 — 그것은 모델링 오류를 감추는 일이고, 어느 쪽이 맞는지 우리는
52
+ * 모른다.
53
+ */
54
+ const dupOf = (list: any[] | undefined, what: string): string | undefined => {
55
+ const seen = new Set<string>()
56
+ for (const e of list ?? []) {
57
+ const id = String(e?.id ?? '')
58
+ if (!id) continue
59
+ if (seen.has(id)) return `twin "${instanceId}" has two ${what} with id "${id}" — ids must be unique within a twin, so the model cannot be projected`
60
+ seen.add(id)
61
+ }
62
+ return undefined
63
+ }
64
+ const dupMsg = dupOf(model?.locations, 'locations') ?? dupOf(readBoardEquipment(model), 'equipment')
65
+ if (dupMsg) throw new Error(dupMsg)
66
+
67
+ const locRepo = getRepository(TwinLocation)
68
+ const eqRepo = getRepository(TwinEquipment)
69
+ const opRepo = getRepository(TwinOperation)
70
+ const scope = { domain: { id: domainId } as any, instance: { id: instance.id } as any }
71
+ const ingestedAt = new Date()
72
+
73
+ /*
74
+ * 전량 교체 — 이 행들은 **원본에서 온 캐시**이므로 매번 그대로 다시 그린다.
75
+ * diff 로 아끼는 것은 "우리 쪽에 남겨야 할 것이 있다" 는 전제를 깔고, 그 전제가 소유 침범의 시작이다.
76
+ *
77
+ * **관측으로 자란 자원은 여기 쓰지 않는다.** 커널은 마스터에 없던 설비도 이벤트로 알게 되지만
78
+ * (`observed-reducer` 의 `origin: 'observed'`), 그것을 이 표에 밀어 넣으면 **쓰기 경로가 둘**이 되고
79
+ * 틱 루프에서 DB 로 쓰는 길이 생긴다(ADR-0032-D 위반). 그 차이는 **조회 시점에 비교해 보고**한다 —
80
+ * 숨겨야 할 불일치가 아니라 "원본이 모르는 설비가 실행 중이다" 는 사실이다.
81
+ */
82
+ await eqRepo.delete(scope)
83
+ await opRepo.delete(scope)
84
+ await locRepo.delete(scope)
85
+
86
+ /* 구역은 model 밖에 산다(호스트의 TwinArea). 이름으로 찾아 FK 를 잇는다. */
87
+ const areas = await getRepository(TwinArea).find({ where: { domain: { id: domainId } as any } })
88
+ const areaOf = new Map(areas.map(a => [a.areaId, a]))
89
+ const unresolvedArea: string[] = []
90
+
91
+ /*
92
+ * ── 전기 상류를 「구역」으로 읽지 않는다 (2026-08-18) ────────────────────────
93
+ *
94
+ * 자리의 부모가 **다른 자리**일 수 있다(분기의 상류는 수전이다). 그 부모를 구역 표에서 찾으면 당연히
95
+ * 없고, 예전에는 그것을 매번 「구역이 이 트윈에 없다」로 경고했다 — 에너지 트윈을 인제스트할 때마다
96
+ * 분기 수만큼 거짓 경보가 났다. 경보가 소음이 되면 **사람이 경보 자체를 읽지 않게 된다.**
97
+ *
98
+ * 자리를 가리키는 부모는 계층(자리 ⊃ 자리)이고, 구역은 model 밖의 다른 표다. 그래서 부모가 이 모델의
99
+ * 자리이면 구역 해석을 **시도하지 않는다**(없는 것을 못 찾았다고 말하지 않는다).
100
+ */
101
+ const locationIds = new Set((model?.locations ?? []).filter((l: any) => l?.id).map((l: any) => String(l.id)))
102
+
103
+ const locations: TwinLocation[] = (model?.locations ?? [])
104
+ .filter((l: any) => l?.id)
105
+ .map((l: any) => {
106
+ const parentIsLocation = l.parentId ? locationIds.has(String(l.parentId)) : false
107
+ const area = l.parentId && !parentIsLocation ? areaOf.get(l.parentId) : undefined
108
+ if (l.parentId && !parentIsLocation && !area) unresolvedArea.push(`${l.id}→${l.parentId}`)
109
+ return locRepo.create({
110
+ ...scope,
111
+ locationId: l.id,
112
+ /* **이름을 옮긴다.** 버리고 있었다 — 그래서 화면에 자리 id 만 남았다(§name 주석 참조). */
113
+ name: l.name ?? null,
114
+ description: l.description ?? null,
115
+ type: l.type,
116
+ capacity: l.capacity ?? null,
117
+ parallelism: l.parallelism ?? null,
118
+ area: area ? ({ id: area.id } as any) : null,
119
+ gs1Id: l.gs1Id ?? null,
120
+ /* 자리의 속성도 옮긴다 — 옮기지 않으면 계약전력·요금이 항목 패널에서 사라진다(조용한 누락). */
121
+ properties: l.properties ?? null,
122
+ sourceRef,
123
+ ingestedAt
124
+ } as any)
125
+ })
126
+ const savedLocations = locations.length ? await locRepo.save(locations, { chunk: 500 }) : []
127
+ const locOf = new Map(savedLocations.map(l => [l.locationId, l]))
128
+
129
+ const unresolvedHome: string[] = []
130
+ const equipment: TwinEquipment[] = (model?.equipment ?? [])
131
+ .filter((m: any) => m?.id)
132
+ .map((m: any) => {
133
+ const home = m.homeLocation ? locOf.get(m.homeLocation) : undefined
134
+ /* 끊어진 기준 자리는 **이름을 대고** 보고한다. 예전에는 화면이 경고할 뿐이었고
135
+ 다른 경로로 들어온 데이터는 아무도 안 봤다. */
136
+ if (m.homeLocation && !home) unresolvedHome.push(`${m.id}→${m.homeLocation}`)
137
+ return eqRepo.create({
138
+ ...scope,
139
+ equipmentId: m.id,
140
+ name: m.name ?? null,
141
+ description: m.description ?? null,
142
+ kind: m.kind,
143
+ /* 속성도 옮긴다 — 행에 자리가 없어 흘리고 있었다(소비처가 실제로 읽는 사실이다: 주행 속도). */
144
+ properties: m.properties ?? null,
145
+ /* 시험 참조도 함께 — 흘리면 명세 쪽에서 "누가 나를 가리키나" 에 이 설비가 빠진다. */
146
+ testSpecificationIds: m.testSpecificationIds ?? null,
147
+ /* 결과까지 — 참조만 있으면 "무엇으로 검증한다" 까지고, 자격 성립은 결과가 말한다. */
148
+ testResults: m.testResults ?? null,
149
+ homeLocation: home ? ({ id: home.id } as any) : null,
150
+ mtbfMs: m.mtbfMs ?? null,
151
+ mttrMs: m.mttrMs ?? null,
152
+ gs1Id: m.gs1Id ?? null,
153
+ sourceRef,
154
+ ingestedAt
155
+ } as any)
156
+ })
157
+ if (equipment.length) await eqRepo.save(equipment, { chunk: 500 })
158
+
159
+ const operations: TwinOperation[] = (model?.operations ?? [])
160
+ .filter((o: any) => o?.key)
161
+ .map((o: any, i: number) =>
162
+ opRepo.create({
163
+ ...scope,
164
+ operationKey: o.key,
165
+ /*
166
+ * 공정은 마스터가 `label` 로 말한다 — 행에서는 **규약의 이름 하나만** 둔다.
167
+ *
168
+ * 한동안 `label` 컬럼도 함께 남겨 뒀더니 조회 화면에 "이름" 열이 **두 개** 떴다(같은 값이
169
+ * 두 번). 어휘를 하나로 한다고 하면서 둘을 남기면 반드시 이렇게 새어 나온다.
170
+ */
171
+ name: o.name ?? o.label ?? null,
172
+ description: o.description ?? null,
173
+ intent: o.intent ?? null,
174
+ locationType: o.locationType ?? null,
175
+ resourceType: o.resourceType ?? null,
176
+ /* 소요시간은 ISO 8601 문자열이 정본 — 분으로 접으면 초가 조용히 사라진다. */
177
+ duration: o.duration ?? null,
178
+ variability: o.variability ?? null,
179
+ parameters: o.parameters ?? null,
180
+ /* **네 자원 명세를 함께 옮긴다.** 셋이 빠져 있어서 조회 화면이 "원본이 말한 값" 이라 적고도
181
+ 넷 중 하나만 보였다 — 사람이 필요한 공정을 보고 사람이 필요 없다고 결론 내게 된다. */
182
+ personnelSpecification: o.personnelSpecification ?? null,
183
+ physicalAssetSpecification: o.physicalAssetSpecification ?? null,
184
+ materialSpecification: o.materialSpecification ?? null,
185
+ seq: i,
186
+ sourceRef,
187
+ ingestedAt
188
+ } as any)
189
+ )
190
+ if (operations.length) await opRepo.save(operations, { chunk: 500 })
191
+
192
+ return {
193
+ locations: savedLocations.length,
194
+ equipment: equipment.length,
195
+ operations: operations.length,
196
+ unresolvedHome,
197
+ unresolvedArea
198
+ }
199
+ }
@@ -0,0 +1,35 @@
1
+ /*
2
+ * 이 트윈이 기대는 **표준들** — 종류에 따라 다르다.
3
+ *
4
+ * 적합성 패널이 트윈 종류와 무관하게 ISA-95 하나만 말하고 있었다. 에너지 트윈에서 그것은 거짓은
5
+ * 아니지만 부족하다: 계량기·차단기·태양광·축전지의 어휘를 주는 것은 IEC 61850 이고, 무엇이 비었는지도
6
+ * 그 목록으로만 셀 수 있다.
7
+ *
8
+ * ── 여기 넣지 않는 것 ───────────────────────────────────────────────────────
9
+ * ISO 50001·ISO 55000 은 **경영 시스템**이다. 데이터 스키마가 아니라 요구사항에 답해야 하는 대상이라
10
+ * 「필드 커버리지」로 말할 수 없다 — 배우기 문서가 같은 구분을 지키고 있고, 화면도 그래야 한다.
11
+ *
12
+ * ── 왜 표를 여기서 부르지 않나 ──────────────────────────────────────────────
13
+ * 이 파일은 **정책**만 담는다(어느 종류가 어느 표준을 기대하나). 표를 직접 불러오면 그 순간 이 규칙을
14
+ * 단위 시험이 불러올 수 없게 된다(모듈 사슬이 런타임 해석을 탄다). 조립은 질의가 한 줄로 한다.
15
+ */
16
+
17
+ /** 우리가 적합성 표를 가진 표준들. */
18
+ export type StandardId = 'isa95' | 'epcis' | 'iec61850'
19
+
20
+ /**
21
+ * 이 종류의 트윈이 기대는 표준 — **순서 = 화면에 놓이는 순서.**
22
+ *
23
+ * ISA-95 는 종류와 상관없이 낸다: 자원과 계층은 어느 도메인에서나 그 표준의 것이다.
24
+ * 종류가 더하는 표준만 뒤에 붙인다.
25
+ */
26
+ export function standardsForKind(kind?: string): StandardId[] {
27
+ /*
28
+ * 물류·제조는 **EPCIS** 로 「무슨 일이 있었나」와 「이 번호가 무엇을 가리키나」를 정한다.
29
+ * 에너지에는 붙이지 않는다 — 그 트윈에는 오가는 물건이 없고, 계측은 EPCIS 사건이 아니라 표본으로
30
+ * 들어온다(사건과 시계열은 다른 층이다).
31
+ */
32
+ if (kind === 'ems') return ['isa95', 'iec61850']
33
+ if (kind === 'wms' || kind === 'yms' || kind === 'mes') return ['isa95', 'epcis']
34
+ return ['isa95']
35
+ }
@@ -0,0 +1,150 @@
1
+ import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
2
+ import { ObjectType, Field, ID, Int } from 'type-graphql'
3
+
4
+ import { Domain, ScalarObject } from '@things-factory/shell'
5
+
6
+ import { TwinInstance } from '../twin-instance/twin-instance.js'
7
+ import { TwinLocation } from './twin-location.js'
8
+
9
+ /*
10
+ * TwinEquipment — 이 트윈이 반영하는 **설비**. ISA-95 `Equipment` / ISO 55000 `Asset` / EPCIS `GIAI`.
11
+ *
12
+ * 자리와 같은 규율을 따른다(`twin-location.ts` 머리말) — 이것도 **원본에서 온 캐시**이고,
13
+ * `sourceRef`·`ingestedAt` 이 필수이며, 쓰기 경로는 인제스트 하나다.
14
+ *
15
+ * ── 테이블 이름을 명시한다 ──────────────────────────────────────────────────
16
+ * `equipment` 는 영어에서 **불가산**이라 자동 복수화가 다른 표들과 규칙이 어긋난다
17
+ * (`twin_locations` 인데 `twin_equipment`). 조용한 비일관은 나중에 "왜 이것만 다르지" 를
18
+ * 만들므로 이름을 고정한다(ADR-0032-I).
19
+ */
20
+ @Entity({ name: 'twin_equipment' })
21
+ @Index('ix_twin_equipment_0', (e: TwinEquipment) => [e.domain, e.instance, e.equipmentId], { unique: true })
22
+ /* 종류로 좁혀 보기 — 공정이 `resourceType` 으로 종류를 가리키므로 잦은 질의다. */
23
+ @Index('ix_twin_equipment_1', (e: TwinEquipment) => [e.domain, e.instance, e.kind])
24
+ @ObjectType({ description: 'Equipment reflected into the twin (ISA-95 Equipment / ISO 55000 Asset). Projected from the source; never authored here.' })
25
+ export class TwinEquipment {
26
+ @PrimaryGeneratedColumn('uuid')
27
+ @Field(type => ID)
28
+ readonly id: string
29
+
30
+ @ManyToOne(type => Domain)
31
+ @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })
32
+ domain?: Domain
33
+
34
+ @RelationId((e: TwinEquipment) => e.domain)
35
+ domainId?: string
36
+
37
+ @ManyToOne(type => TwinInstance, { onDelete: 'CASCADE' })
38
+ @Field(type => TwinInstance, { nullable: true, description: 'Owning twin instance.' })
39
+ instance?: TwinInstance
40
+
41
+ @RelationId((e: TwinEquipment) => e.instance)
42
+ instanceRowId?: string
43
+
44
+ /** 커널 계약의 `TwinModelDef.equipment[].id` 그대로. */
45
+ @Column()
46
+ @Field({ description: 'Equipment id as declared by the source (kernel TwinModelDef.equipment[].id).' })
47
+ equipmentId: string
48
+
49
+ /**
50
+ * **사람이 부르는 이름** — 프레임워크 엔티티 규약의 `name`/`description`.
51
+ *
52
+ * ── 왜 이것이 빠져 있었고, 무엇이 망가졌나 ────────────────────────────────────
53
+ * 투영이 마스터의 `name` 을 버리고 있었다. 그래서 행에는 id 만 남고, 화면은 이름을 만들 재료가
54
+ * 없어 `cut-station` 을 그대로 찍었다 — 그 결함이 목록·표·관계 그림마다 따로 나타나서 표면을
55
+ * 하나씩 고치게 만들었다. 표면의 문제가 아니라 **행에 이름이 없던 문제**다.
56
+ *
57
+ * 원본이 이름을 말하지 않으면 비운다(id 를 이름으로 베끼지 않는다 — 그러면 "이름이 있다" 는
58
+ * 거짓이 되고, 화면이 이름 없음을 알아볼 수 없게 된다).
59
+ */
60
+ @Column({ nullable: true })
61
+ @Field({ nullable: true, description: 'Human name as declared by the source. Empty when the source does not name it (never copied from the id).' })
62
+ name?: string
63
+
64
+ @Column({ nullable: true })
65
+ @Field({ nullable: true, description: 'Longer description when the source provides one.' })
66
+ description?: string
67
+
68
+ /** 커널 카탈로그의 설비 종류 키(`forklift`·`welder`…). FK 아님 — 카탈로그는 커널 SSOT. */
69
+ @Column()
70
+ @Field({ description: 'Equipment kind key from the kernel catalog (not an FK — the catalog is the kernel SSOT).' })
71
+ kind: string
72
+
73
+ /**
74
+ * 자원 속성 — **ISA-95 `Property`**(값·데이터형·단위·하위 속성).
75
+ *
76
+ * 모델에는 있는데 행에 자리가 없어 투영이 흘리고 있었다. 그러면 조회 화면은 "원본이 말한 값" 이라
77
+ * 적고도 속성을 보이지 못한다 — 이름이 같은 식으로 사라졌던 것과 같은 부류다(2026-08-11).
78
+ *
79
+ * 소비처가 실제로 읽는 사실이다: 주행 추정기가 `speed` 로 이동 시간을 만든다. 단위가 값 옆에 붙어
80
+ * 있어야 뜻이 서므로 **모양 그대로** 담는다(문자열 값 + `uom` + `dataType`).
81
+ */
82
+ @Column({ type: 'simple-json', nullable: true })
83
+ @Field(type => ScalarObject, { nullable: true, description: 'Resource properties as declared by the source (ISA-95 Property: value + dataType + unit of measure, optional children).' })
84
+ properties?: any
85
+
86
+ /**
87
+ * 이 설비의 적격을 검증한 **시험 명세들** — 표준 `TestSpecificationID`(`maxOccurs="unbounded"`).
88
+ *
89
+ * 참조만 담는다(대상은 `testSpecifications` 축). 속성과 **같은 부류의 결손**이었다: 모델에는 있는데
90
+ * 행에 자리가 없어 투영이 흘렸고, 그래서 명세 쪽에서 "누가 나를 가리키나" 를 물으면 설비가 빠졌다.
91
+ */
92
+ @Column({ type: 'simple-json', nullable: true })
93
+ @Field(type => [String], { nullable: true, description: 'Test specifications that verified this equipment (ISA-95 TestSpecificationID, unbounded). References only — the specifications themselves live on the testSpecifications axis.' })
94
+ testSpecificationIds?: string[]
95
+
96
+ /**
97
+ * 그 시험들의 **결과** — 자격이 성립하는지는 이것이 말한다.
98
+ *
99
+ * 참조만 있으면 "무엇으로 검증한다" 까지다. 오늘 이 저장소에서 네 번 나온 결손과 같은 부류이므로
100
+ * 행에 자리를 함께 만든다 — 모델에는 있는데 행에 없으면 화면이 조용히 잃는다.
101
+ */
102
+ @Column({ type: 'simple-json', nullable: true })
103
+ @Field(type => ScalarObject, { nullable: true, description: 'Results of those tests (pass/fail with dates). Declared by the source — the twin does not produce them.' })
104
+ testResults?: any
105
+
106
+ /**
107
+ * 기준 자리 — **실제 FK**. 예전에는 `homeLocation` 문자열이라 끊어진 참조를 화면이 경고했고
108
+ * (`brokenHomeRefs`), 다른 경로로 들어오는 데이터는 아무도 안 봤다. 이제 DB 가 막는다.
109
+ */
110
+ @ManyToOne(type => TwinLocation, { nullable: true, onDelete: 'SET NULL' })
111
+ @Field(type => TwinLocation, { nullable: true, description: 'Home location this equipment belongs to.' })
112
+ homeLocation?: TwinLocation
113
+
114
+ @RelationId((e: TwinEquipment) => e.homeLocation)
115
+ homeLocationRowId?: string
116
+
117
+ /*
118
+ * 고장 모델 — 원본이 선언할 때만 있다. 없으면 고장 없는 설비로 동작한다(커널 계약).
119
+ * **관측(가동률)은 여기 두지 않는다** — 그건 트윈이 저널에서 만든 것이라 성질이 다르고,
120
+ * 이 표는 원본에서 재생성 가능해야 한다(ADR-0032-D).
121
+ */
122
+ @Column({ type: 'int', nullable: true })
123
+ @Field(type => Int, { nullable: true, description: 'Declared mean time between failures in ms. Absent = no failure model.' })
124
+ mtbfMs?: number
125
+
126
+ @Column({ type: 'int', nullable: true })
127
+ @Field(type => Int, { nullable: true, description: 'Declared mean time to repair in ms.' })
128
+ mttrMs?: number
129
+
130
+ @Column({ nullable: true })
131
+ @Field({ nullable: true, description: 'GS1 identifier supplied by the source (e.g. GIAI).' })
132
+ gs1Id?: string
133
+
134
+ /* ── 캐시라는 사실을 구조로 (ADR-0032-D) ── */
135
+ @Column()
136
+ @Field({ description: 'Which reference (source system) this row came from. Required.' })
137
+ sourceRef: string
138
+
139
+ @Column({ type: 'datetime' })
140
+ @Field({ description: 'When this row was projected from the source.' })
141
+ ingestedAt: Date
142
+
143
+ @CreateDateColumn()
144
+ @Field({ nullable: true })
145
+ createdAt?: Date
146
+
147
+ @UpdateDateColumn()
148
+ @Field({ nullable: true })
149
+ updatedAt?: Date
150
+ }