@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,64 @@
1
+ import { Domain } from '@things-factory/shell';
2
+ import { TwinInstance } from '../twin-instance/twin-instance.js';
3
+ import { TwinLocation } from './twin-location.js';
4
+ export declare class TwinEquipment {
5
+ readonly id: string;
6
+ domain?: Domain;
7
+ domainId?: string;
8
+ instance?: TwinInstance;
9
+ instanceRowId?: string;
10
+ /** 커널 계약의 `TwinModelDef.equipment[].id` 그대로. */
11
+ equipmentId: string;
12
+ /**
13
+ * **사람이 부르는 이름** — 프레임워크 엔티티 규약의 `name`/`description`.
14
+ *
15
+ * ── 왜 이것이 빠져 있었고, 무엇이 망가졌나 ────────────────────────────────────
16
+ * 투영이 마스터의 `name` 을 버리고 있었다. 그래서 행에는 id 만 남고, 화면은 이름을 만들 재료가
17
+ * 없어 `cut-station` 을 그대로 찍었다 — 그 결함이 목록·표·관계 그림마다 따로 나타나서 표면을
18
+ * 하나씩 고치게 만들었다. 표면의 문제가 아니라 **행에 이름이 없던 문제**다.
19
+ *
20
+ * 원본이 이름을 말하지 않으면 비운다(id 를 이름으로 베끼지 않는다 — 그러면 "이름이 있다" 는
21
+ * 거짓이 되고, 화면이 이름 없음을 알아볼 수 없게 된다).
22
+ */
23
+ name?: string;
24
+ description?: string;
25
+ /** 커널 카탈로그의 설비 종류 키(`forklift`·`welder`…). FK 아님 — 카탈로그는 커널 SSOT. */
26
+ kind: string;
27
+ /**
28
+ * 자원 속성 — **ISA-95 `Property`**(값·데이터형·단위·하위 속성).
29
+ *
30
+ * 모델에는 있는데 행에 자리가 없어 투영이 흘리고 있었다. 그러면 조회 화면은 "원본이 말한 값" 이라
31
+ * 적고도 속성을 보이지 못한다 — 이름이 같은 식으로 사라졌던 것과 같은 부류다(2026-08-11).
32
+ *
33
+ * 소비처가 실제로 읽는 사실이다: 주행 추정기가 `speed` 로 이동 시간을 만든다. 단위가 값 옆에 붙어
34
+ * 있어야 뜻이 서므로 **모양 그대로** 담는다(문자열 값 + `uom` + `dataType`).
35
+ */
36
+ properties?: any;
37
+ /**
38
+ * 이 설비의 적격을 검증한 **시험 명세들** — 표준 `TestSpecificationID`(`maxOccurs="unbounded"`).
39
+ *
40
+ * 참조만 담는다(대상은 `testSpecifications` 축). 속성과 **같은 부류의 결손**이었다: 모델에는 있는데
41
+ * 행에 자리가 없어 투영이 흘렸고, 그래서 명세 쪽에서 "누가 나를 가리키나" 를 물으면 설비가 빠졌다.
42
+ */
43
+ testSpecificationIds?: string[];
44
+ /**
45
+ * 그 시험들의 **결과** — 자격이 성립하는지는 이것이 말한다.
46
+ *
47
+ * 참조만 있으면 "무엇으로 검증한다" 까지다. 오늘 이 저장소에서 네 번 나온 결손과 같은 부류이므로
48
+ * 행에 자리를 함께 만든다 — 모델에는 있는데 행에 없으면 화면이 조용히 잃는다.
49
+ */
50
+ testResults?: any;
51
+ /**
52
+ * 기준 자리 — **실제 FK**. 예전에는 `homeLocation` 문자열이라 끊어진 참조를 화면이 경고했고
53
+ * (`brokenHomeRefs`), 다른 경로로 들어오는 데이터는 아무도 안 봤다. 이제 DB 가 막는다.
54
+ */
55
+ homeLocation?: TwinLocation;
56
+ homeLocationRowId?: string;
57
+ mtbfMs?: number;
58
+ mttrMs?: number;
59
+ gs1Id?: string;
60
+ sourceRef: string;
61
+ ingestedAt: Date;
62
+ createdAt?: Date;
63
+ updatedAt?: Date;
64
+ }
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TwinEquipment = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const typeorm_1 = require("typeorm");
6
+ const type_graphql_1 = require("type-graphql");
7
+ const shell_1 = require("@things-factory/shell");
8
+ const twin_instance_js_1 = require("../twin-instance/twin-instance.js");
9
+ const twin_location_js_1 = require("./twin-location.js");
10
+ /*
11
+ * TwinEquipment — 이 트윈이 반영하는 **설비**. ISA-95 `Equipment` / ISO 55000 `Asset` / EPCIS `GIAI`.
12
+ *
13
+ * 자리와 같은 규율을 따른다(`twin-location.ts` 머리말) — 이것도 **원본에서 온 캐시**이고,
14
+ * `sourceRef`·`ingestedAt` 이 필수이며, 쓰기 경로는 인제스트 하나다.
15
+ *
16
+ * ── 테이블 이름을 명시한다 ──────────────────────────────────────────────────
17
+ * `equipment` 는 영어에서 **불가산**이라 자동 복수화가 다른 표들과 규칙이 어긋난다
18
+ * (`twin_locations` 인데 `twin_equipment`). 조용한 비일관은 나중에 "왜 이것만 다르지" 를
19
+ * 만들므로 이름을 고정한다(ADR-0032-I).
20
+ */
21
+ let TwinEquipment = class TwinEquipment {
22
+ };
23
+ exports.TwinEquipment = TwinEquipment;
24
+ tslib_1.__decorate([
25
+ (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
26
+ (0, type_graphql_1.Field)(type => type_graphql_1.ID),
27
+ tslib_1.__metadata("design:type", String)
28
+ ], TwinEquipment.prototype, "id", void 0);
29
+ tslib_1.__decorate([
30
+ (0, typeorm_1.ManyToOne)(type => shell_1.Domain),
31
+ (0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true, description: 'Owning tenant domain.' }),
32
+ tslib_1.__metadata("design:type", shell_1.Domain)
33
+ ], TwinEquipment.prototype, "domain", void 0);
34
+ tslib_1.__decorate([
35
+ (0, typeorm_1.RelationId)((e) => e.domain),
36
+ tslib_1.__metadata("design:type", String)
37
+ ], TwinEquipment.prototype, "domainId", void 0);
38
+ tslib_1.__decorate([
39
+ (0, typeorm_1.ManyToOne)(type => twin_instance_js_1.TwinInstance, { onDelete: 'CASCADE' }),
40
+ (0, type_graphql_1.Field)(type => twin_instance_js_1.TwinInstance, { nullable: true, description: 'Owning twin instance.' }),
41
+ tslib_1.__metadata("design:type", twin_instance_js_1.TwinInstance)
42
+ ], TwinEquipment.prototype, "instance", void 0);
43
+ tslib_1.__decorate([
44
+ (0, typeorm_1.RelationId)((e) => e.instance),
45
+ tslib_1.__metadata("design:type", String)
46
+ ], TwinEquipment.prototype, "instanceRowId", void 0);
47
+ tslib_1.__decorate([
48
+ (0, typeorm_1.Column)(),
49
+ (0, type_graphql_1.Field)({ description: 'Equipment id as declared by the source (kernel TwinModelDef.equipment[].id).' }),
50
+ tslib_1.__metadata("design:type", String)
51
+ ], TwinEquipment.prototype, "equipmentId", void 0);
52
+ tslib_1.__decorate([
53
+ (0, typeorm_1.Column)({ nullable: true }),
54
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Human name as declared by the source. Empty when the source does not name it (never copied from the id).' }),
55
+ tslib_1.__metadata("design:type", String)
56
+ ], TwinEquipment.prototype, "name", void 0);
57
+ tslib_1.__decorate([
58
+ (0, typeorm_1.Column)({ nullable: true }),
59
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Longer description when the source provides one.' }),
60
+ tslib_1.__metadata("design:type", String)
61
+ ], TwinEquipment.prototype, "description", void 0);
62
+ tslib_1.__decorate([
63
+ (0, typeorm_1.Column)(),
64
+ (0, type_graphql_1.Field)({ description: 'Equipment kind key from the kernel catalog (not an FK — the catalog is the kernel SSOT).' }),
65
+ tslib_1.__metadata("design:type", String)
66
+ ], TwinEquipment.prototype, "kind", void 0);
67
+ tslib_1.__decorate([
68
+ (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
69
+ (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'Resource properties as declared by the source (ISA-95 Property: value + dataType + unit of measure, optional children).' }),
70
+ tslib_1.__metadata("design:type", Object)
71
+ ], TwinEquipment.prototype, "properties", void 0);
72
+ tslib_1.__decorate([
73
+ (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
74
+ (0, type_graphql_1.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.' }),
75
+ tslib_1.__metadata("design:type", Array)
76
+ ], TwinEquipment.prototype, "testSpecificationIds", void 0);
77
+ tslib_1.__decorate([
78
+ (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
79
+ (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'Results of those tests (pass/fail with dates). Declared by the source — the twin does not produce them.' }),
80
+ tslib_1.__metadata("design:type", Object)
81
+ ], TwinEquipment.prototype, "testResults", void 0);
82
+ tslib_1.__decorate([
83
+ (0, typeorm_1.ManyToOne)(type => twin_location_js_1.TwinLocation, { nullable: true, onDelete: 'SET NULL' }),
84
+ (0, type_graphql_1.Field)(type => twin_location_js_1.TwinLocation, { nullable: true, description: 'Home location this equipment belongs to.' }),
85
+ tslib_1.__metadata("design:type", twin_location_js_1.TwinLocation)
86
+ ], TwinEquipment.prototype, "homeLocation", void 0);
87
+ tslib_1.__decorate([
88
+ (0, typeorm_1.RelationId)((e) => e.homeLocation),
89
+ tslib_1.__metadata("design:type", String)
90
+ ], TwinEquipment.prototype, "homeLocationRowId", void 0);
91
+ tslib_1.__decorate([
92
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
93
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'Declared mean time between failures in ms. Absent = no failure model.' }),
94
+ tslib_1.__metadata("design:type", Number)
95
+ ], TwinEquipment.prototype, "mtbfMs", void 0);
96
+ tslib_1.__decorate([
97
+ (0, typeorm_1.Column)({ type: 'int', nullable: true }),
98
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'Declared mean time to repair in ms.' }),
99
+ tslib_1.__metadata("design:type", Number)
100
+ ], TwinEquipment.prototype, "mttrMs", void 0);
101
+ tslib_1.__decorate([
102
+ (0, typeorm_1.Column)({ nullable: true }),
103
+ (0, type_graphql_1.Field)({ nullable: true, description: 'GS1 identifier supplied by the source (e.g. GIAI).' }),
104
+ tslib_1.__metadata("design:type", String)
105
+ ], TwinEquipment.prototype, "gs1Id", void 0);
106
+ tslib_1.__decorate([
107
+ (0, typeorm_1.Column)(),
108
+ (0, type_graphql_1.Field)({ description: 'Which reference (source system) this row came from. Required.' }),
109
+ tslib_1.__metadata("design:type", String)
110
+ ], TwinEquipment.prototype, "sourceRef", void 0);
111
+ tslib_1.__decorate([
112
+ (0, typeorm_1.Column)({ type: 'datetime' }),
113
+ (0, type_graphql_1.Field)({ description: 'When this row was projected from the source.' }),
114
+ tslib_1.__metadata("design:type", Date)
115
+ ], TwinEquipment.prototype, "ingestedAt", void 0);
116
+ tslib_1.__decorate([
117
+ (0, typeorm_1.CreateDateColumn)(),
118
+ (0, type_graphql_1.Field)({ nullable: true }),
119
+ tslib_1.__metadata("design:type", Date)
120
+ ], TwinEquipment.prototype, "createdAt", void 0);
121
+ tslib_1.__decorate([
122
+ (0, typeorm_1.UpdateDateColumn)(),
123
+ (0, type_graphql_1.Field)({ nullable: true }),
124
+ tslib_1.__metadata("design:type", Date)
125
+ ], TwinEquipment.prototype, "updatedAt", void 0);
126
+ exports.TwinEquipment = TwinEquipment = tslib_1.__decorate([
127
+ (0, typeorm_1.Entity)({ name: 'twin_equipment' }),
128
+ (0, typeorm_1.Index)('ix_twin_equipment_0', (e) => [e.domain, e.instance, e.equipmentId], { unique: true })
129
+ /* 종류로 좁혀 보기 — 공정이 `resourceType` 으로 종류를 가리키므로 잦은 질의다. */
130
+ ,
131
+ (0, typeorm_1.Index)('ix_twin_equipment_1', (e) => [e.domain, e.instance, e.kind]),
132
+ (0, type_graphql_1.ObjectType)({ description: 'Equipment reflected into the twin (ISA-95 Equipment / ISO 55000 Asset). Projected from the source; never authored here.' })
133
+ ], TwinEquipment);
134
+ //# sourceMappingURL=twin-equipment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"twin-equipment.js","sourceRoot":"","sources":["../../../server/service/twin-model/twin-equipment.ts"],"names":[],"mappings":";;;;AAAA,qCAAkI;AAClI,+CAAyD;AAEzD,iDAA4D;AAE5D,wEAAgE;AAChE,yDAAiD;AAEjD;;;;;;;;;;GAUG;AAMI,IAAM,aAAa,GAAnB,MAAM,aAAa;CA6HzB,CAAA;AA7HY,sCAAa;AAGf;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,CAAC;;yCACC;AAInB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IACzB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC;sCACvE,cAAM;6CAAA;AAGf;IADC,IAAA,oBAAU,EAAC,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;;+CAC1B;AAIjB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,+BAAY,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACxD,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,+BAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC;sCAC3E,+BAAY;+CAAA;AAGvB;IADC,IAAA,oBAAU,EAAC,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;;oDACvB;AAKtB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,8EAA8E,EAAE,CAAC;;kDACpF;AAenB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,0GAA0G,EAAE,CAAC;;2CACtI;AAIb;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kDAAkD,EAAE,CAAC;;kDACvE;AAKpB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,0FAA0F,EAAE,CAAC;;2CACvG;AAaZ;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,yHAAyH,EAAE,CAAC;;iDACxK;AAUhB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,gLAAgL,EAAE,CAAC;;2DAC5M;AAU/B;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,yGAAyG,EAAE,CAAC;;kDACvJ;AAQjB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,+BAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;IACzE,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,+BAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,0CAA0C,EAAE,CAAC;sCAC1F,+BAAY;mDAAA;AAG3B;IADC,IAAA,oBAAU,EAAC,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;;wDACvB;AAS1B;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uEAAuE,EAAE,CAAC;;6CAC9G;AAIf;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,qCAAqC,EAAE,CAAC;;6CAC5E;AAIf;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,oDAAoD,EAAE,CAAC;;4CAC/E;AAKd;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,+DAA+D,EAAE,CAAC;;gDACvE;AAIjB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAC5B,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,8CAA8C,EAAE,CAAC;sCAC3D,IAAI;iDAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;gDAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;gDAAA;wBA5HL,aAAa;IALzB,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;IAClC,IAAA,eAAK,EAAC,qBAAqB,EAAE,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC5G,yDAAyD;;IACxD,IAAA,eAAK,EAAC,qBAAqB,EAAE,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAClF,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,yHAAyH,EAAE,CAAC;GAC1I,aAAa,CA6HzB","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'\nimport { ObjectType, Field, ID, Int } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinLocation } from './twin-location.js'\n\n/*\n * TwinEquipment — 이 트윈이 반영하는 **설비**. ISA-95 `Equipment` / ISO 55000 `Asset` / EPCIS `GIAI`.\n *\n * 자리와 같은 규율을 따른다(`twin-location.ts` 머리말) — 이것도 **원본에서 온 캐시**이고,\n * `sourceRef`·`ingestedAt` 이 필수이며, 쓰기 경로는 인제스트 하나다.\n *\n * ── 테이블 이름을 명시한다 ──────────────────────────────────────────────────\n * `equipment` 는 영어에서 **불가산**이라 자동 복수화가 다른 표들과 규칙이 어긋난다\n * (`twin_locations` 인데 `twin_equipment`). 조용한 비일관은 나중에 \"왜 이것만 다르지\" 를\n * 만들므로 이름을 고정한다(ADR-0032-I).\n */\n@Entity({ name: 'twin_equipment' })\n@Index('ix_twin_equipment_0', (e: TwinEquipment) => [e.domain, e.instance, e.equipmentId], { unique: true })\n/* 종류로 좁혀 보기 — 공정이 `resourceType` 으로 종류를 가리키므로 잦은 질의다. */\n@Index('ix_twin_equipment_1', (e: TwinEquipment) => [e.domain, e.instance, e.kind])\n@ObjectType({ description: 'Equipment reflected into the twin (ISA-95 Equipment / ISO 55000 Asset). Projected from the source; never authored here.' })\nexport class TwinEquipment {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinEquipment) => e.domain)\n domainId?: string\n\n @ManyToOne(type => TwinInstance, { onDelete: 'CASCADE' })\n @Field(type => TwinInstance, { nullable: true, description: 'Owning twin instance.' })\n instance?: TwinInstance\n\n @RelationId((e: TwinEquipment) => e.instance)\n instanceRowId?: string\n\n /** 커널 계약의 `TwinModelDef.equipment[].id` 그대로. */\n @Column()\n @Field({ description: 'Equipment id as declared by the source (kernel TwinModelDef.equipment[].id).' })\n equipmentId: string\n\n /**\n * **사람이 부르는 이름** — 프레임워크 엔티티 규약의 `name`/`description`.\n *\n * ── 왜 이것이 빠져 있었고, 무엇이 망가졌나 ────────────────────────────────────\n * 투영이 마스터의 `name` 을 버리고 있었다. 그래서 행에는 id 만 남고, 화면은 이름을 만들 재료가\n * 없어 `cut-station` 을 그대로 찍었다 — 그 결함이 목록·표·관계 그림마다 따로 나타나서 표면을\n * 하나씩 고치게 만들었다. 표면의 문제가 아니라 **행에 이름이 없던 문제**다.\n *\n * 원본이 이름을 말하지 않으면 비운다(id 를 이름으로 베끼지 않는다 — 그러면 \"이름이 있다\" 는\n * 거짓이 되고, 화면이 이름 없음을 알아볼 수 없게 된다).\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Human name as declared by the source. Empty when the source does not name it (never copied from the id).' })\n name?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Longer description when the source provides one.' })\n description?: string\n\n /** 커널 카탈로그의 설비 종류 키(`forklift`·`welder`…). FK 아님 — 카탈로그는 커널 SSOT. */\n @Column()\n @Field({ description: 'Equipment kind key from the kernel catalog (not an FK — the catalog is the kernel SSOT).' })\n kind: string\n\n /**\n * 자원 속성 — **ISA-95 `Property`**(값·데이터형·단위·하위 속성).\n *\n * 모델에는 있는데 행에 자리가 없어 투영이 흘리고 있었다. 그러면 조회 화면은 \"원본이 말한 값\" 이라\n * 적고도 속성을 보이지 못한다 — 이름이 같은 식으로 사라졌던 것과 같은 부류다(2026-08-11).\n *\n * 소비처가 실제로 읽는 사실이다: 주행 추정기가 `speed` 로 이동 시간을 만든다. 단위가 값 옆에 붙어\n * 있어야 뜻이 서므로 **모양 그대로** 담는다(문자열 값 + `uom` + `dataType`).\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Resource properties as declared by the source (ISA-95 Property: value + dataType + unit of measure, optional children).' })\n properties?: any\n\n /**\n * 이 설비의 적격을 검증한 **시험 명세들** — 표준 `TestSpecificationID`(`maxOccurs=\"unbounded\"`).\n *\n * 참조만 담는다(대상은 `testSpecifications` 축). 속성과 **같은 부류의 결손**이었다: 모델에는 있는데\n * 행에 자리가 없어 투영이 흘렸고, 그래서 명세 쪽에서 \"누가 나를 가리키나\" 를 물으면 설비가 빠졌다.\n */\n @Column({ type: 'simple-json', nullable: true })\n @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.' })\n testSpecificationIds?: string[]\n\n /**\n * 그 시험들의 **결과** — 자격이 성립하는지는 이것이 말한다.\n *\n * 참조만 있으면 \"무엇으로 검증한다\" 까지다. 오늘 이 저장소에서 네 번 나온 결손과 같은 부류이므로\n * 행에 자리를 함께 만든다 — 모델에는 있는데 행에 없으면 화면이 조용히 잃는다.\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Results of those tests (pass/fail with dates). Declared by the source — the twin does not produce them.' })\n testResults?: any\n\n /**\n * 기준 자리 — **실제 FK**. 예전에는 `homeLocation` 문자열이라 끊어진 참조를 화면이 경고했고\n * (`brokenHomeRefs`), 다른 경로로 들어오는 데이터는 아무도 안 봤다. 이제 DB 가 막는다.\n */\n @ManyToOne(type => TwinLocation, { nullable: true, onDelete: 'SET NULL' })\n @Field(type => TwinLocation, { nullable: true, description: 'Home location this equipment belongs to.' })\n homeLocation?: TwinLocation\n\n @RelationId((e: TwinEquipment) => e.homeLocation)\n homeLocationRowId?: string\n\n /*\n * 고장 모델 — 원본이 선언할 때만 있다. 없으면 고장 없는 설비로 동작한다(커널 계약).\n * **관측(가동률)은 여기 두지 않는다** — 그건 트윈이 저널에서 만든 것이라 성질이 다르고,\n * 이 표는 원본에서 재생성 가능해야 한다(ADR-0032-D).\n */\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'Declared mean time between failures in ms. Absent = no failure model.' })\n mtbfMs?: number\n\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'Declared mean time to repair in ms.' })\n mttrMs?: number\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'GS1 identifier supplied by the source (e.g. GIAI).' })\n gs1Id?: string\n\n /* ── 캐시라는 사실을 구조로 (ADR-0032-D) ── */\n @Column()\n @Field({ description: 'Which reference (source system) this row came from. Required.' })\n sourceRef: string\n\n @Column({ type: 'datetime' })\n @Field({ description: 'When this row was projected from the source.' })\n ingestedAt: Date\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export declare class TwinLineageQuery {
2
+ twinItemLineage(instanceId: string, epc: string, context: ResolverContext, maxDepth?: number, window?: number): Promise<any | null>;
3
+ }
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TwinLineageQuery = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ const shell_1 = require("@things-factory/shell");
7
+ const twin_instance_js_1 = require("../twin-instance/twin-instance.js");
8
+ const twin_event_js_1 = require("../twin-event/twin-event.js");
9
+ /*
10
+ * 계보 — **이 개체는 무엇에서 나왔나**, 그리고 **설계대로 만들어졌나.**
11
+ *
12
+ * ── 구성과 계보는 주어가 다르다 ────────────────────────────────────────────────
13
+ * 구성(BOM)의 주어는 품목 **정의**다 — "트레일러는 프레임 1개로 만든다"(선언).
14
+ * 계보의 주어는 품목 **개체**다 — "이 트레일러는 저 프레임 로트에서 나왔다"(관측).
15
+ * 둘은 같은 모양이라 겹쳐 볼 수 있고, **그 차이가 곧 진단**이다: "설계는 1개인데 이건 2개를 먹었다".
16
+ * 트윈이 아니면 못 하는 답이고, 이 질의가 그 답의 재료다.
17
+ *
18
+ * ── 어디서 읽나 ────────────────────────────────────────────────────────────────
19
+ * 계보는 상태가 아니라 **저널**에 있다(EPCIS `TransformationEvent` 의 입력·산출 목록). 상태는
20
+ * 지금 무엇이 있는지만 알고, 없어진 원자재가 무엇이었는지는 이벤트만 안다.
21
+ *
22
+ * ── 규모 ───────────────────────────────────────────────────────────────────────
23
+ * 입력 목록은 **컬럼이 아니라 봉투 안**에 있어서 인덱스로 좁힐 수 없다. 그래서 변환 이벤트만
24
+ * 창으로 끊어 읽고, **끊었으면 끊었다고 말한다.** 그 사실을 감추면 "이 개체는 원자재가 없다" 로
25
+ * 읽히고, 계보에서 그 오해는 리콜 범위를 좁게 잡는 결과로 이어진다.
26
+ */
27
+ /** GS1 SGTIN 의 **품목 참조** — 개체(serial)를 떼고 품목만 남긴다(`…sgtin:0614141.077101.42` → `077101`). */
28
+ function itemRefOf(epc) {
29
+ const m = /sgtin:[^.]+\.([^.]+)\./.exec(epc);
30
+ return m ? m[1] : null;
31
+ }
32
+ let TwinLineageQuery = class TwinLineageQuery {
33
+ async twinItemLineage(instanceId, epc, context, maxDepth, window) {
34
+ const domainId = context.state.domain.id;
35
+ const inst = await (0, shell_1.getRepository)(twin_instance_js_1.TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } });
36
+ if (!inst)
37
+ return null;
38
+ const depthCap = Math.min(Math.max(maxDepth ?? 6, 1), 12);
39
+ const windowCap = Math.min(Math.max(window ?? 5000, 1), 50000);
40
+ /*
41
+ * 변환 이벤트만 읽는다. **최신부터** 읽는다 — 계보를 묻는 개체는 대개 최근에 만들어진 것이고,
42
+ * 창을 넘겨 잘릴 때 잃는 쪽이 오래된 것이어야 덜 아프다.
43
+ */
44
+ const rows = await (0, shell_1.getRepository)(twin_event_js_1.TwinEvent).find({
45
+ where: { domain: { id: domainId }, instanceId, eventType: 'epcis.TransformationEvent' },
46
+ order: { eventTime: 'DESC' },
47
+ take: windowCap
48
+ });
49
+ const truncated = rows.length >= windowCap;
50
+ /* 산출 → 그 변환. 한 개체는 한 번만 만들어지지만, 데이터가 그렇지 않을 수 있으므로 목록으로 든다. */
51
+ const madeBy = new Map();
52
+ for (const r of rows) {
53
+ const d = r.payload?.data ?? {};
54
+ const outs = d.outputEPCList ?? [];
55
+ const ins = d.inputEPCList ?? [];
56
+ const step = { inputs: ins, at: r.eventTime, tx: d.bizTransactionList?.[0]?.bizTransaction };
57
+ for (const o of outs) {
58
+ const list = madeBy.get(o) ?? [];
59
+ list.push(step);
60
+ madeBy.set(o, list);
61
+ }
62
+ }
63
+ /* 거슬러 올라간다 — 너비 우선, 지나온 것은 다시 파지 않는다(사이클·공유 부품). */
64
+ const items = [{ epc, itemRef: itemRefOf(epc), depth: 0 }];
65
+ const edges = [];
66
+ const seen = new Set([epc]);
67
+ let depthTruncated = false;
68
+ let frontier = [{ epc, depth: 0 }];
69
+ while (frontier.length) {
70
+ const next = [];
71
+ for (const cur of frontier) {
72
+ if (cur.depth >= depthCap) {
73
+ depthTruncated = true;
74
+ continue;
75
+ }
76
+ for (const step of madeBy.get(cur.epc) ?? []) {
77
+ for (const input of step.inputs) {
78
+ edges.push({ from: cur.epc, to: input, at: step.at, tx: step.tx, depth: cur.depth + 1 });
79
+ if (seen.has(input))
80
+ continue;
81
+ seen.add(input);
82
+ items.push({ epc: input, itemRef: itemRefOf(input), depth: cur.depth + 1 });
83
+ next.push({ epc: input, depth: cur.depth + 1 });
84
+ }
85
+ }
86
+ }
87
+ frontier = next;
88
+ }
89
+ /*
90
+ * **겹쳐 본다.** 이 개체의 품목이 어느 레시피의 산출이면, 그 레시피가 선언한 투입과 실제로
91
+ * 소비된 것을 자재별로 나란히 센다.
92
+ *
93
+ * 개체(EPC)와 자재 키를 잇는 것은 `productionSpec.binding`(자재 키 → GS1 품목 참조)이다.
94
+ * 바인딩이 없으면 **비교하지 않는다** — 짐작으로 이으면 "설계와 다르다" 는 거짓 진단이 나오고,
95
+ * 그건 아무 말도 안 하는 것보다 나쁘다.
96
+ */
97
+ const spec = inst.model?.productionSpec;
98
+ const binding = spec?.binding ?? {};
99
+ const refToMaterial = new Map(Object.entries(binding).map(([mat, ref]) => [String(ref), mat]));
100
+ const materialOf = (e) => {
101
+ const ref = itemRefOf(e);
102
+ return ref ? refToMaterial.get(ref) ?? null : null;
103
+ };
104
+ const myMaterial = materialOf(epc);
105
+ const recipes = spec?.definition?.recipes ?? [];
106
+ const recipe = myMaterial ? recipes.find((r) => (r.outputs ?? []).some((o) => o.material === myMaterial)) : undefined;
107
+ let conformance = null;
108
+ if (!Object.keys(binding).length) {
109
+ conformance = { comparable: false, reason: 'no-binding' };
110
+ }
111
+ else if (!myMaterial) {
112
+ conformance = { comparable: false, reason: 'item-not-bound' };
113
+ }
114
+ else if (!recipe) {
115
+ conformance = { comparable: false, reason: 'no-recipe-for-material' };
116
+ }
117
+ else {
118
+ /*
119
+ * 관측: **바인딩된 자재까지 내려가 센다** — 중간 산물(WIP)은 통과한다.
120
+ *
121
+ * 다단 라인에서 완제품의 직접 투입은 원자재가 아니라 **WIP** 다: 원자재는 첫 공정에서
122
+ * 소비되고 완제품은 마지막 공정에서 나온다. 그래서 직접 투입만 세면 레시피가 선언한 자재와
123
+ * **영원히 안 맞는다**(검사가 이것을 잡았다).
124
+ *
125
+ * WIP 은 라우트의 내부 산물이고 선언된 자재가 아니다 — 레시피는 자재로 말하므로 비교도
126
+ * 자재로 해야 한다. 바인딩에 있으면 거기서 멈추고, 없으면 그 위로 계속 따라간다.
127
+ *
128
+ * 다만 **다른 완제품까지 넘어가지 않는다**: 바인딩된 것을 만나면 멈추므로, 하위조립도
129
+ * 자재로 선언돼 있으면 그 자리에서 세어진다(그 아래는 그 자재 자신의 레시피가 볼 일이다).
130
+ */
131
+ const observed = new Map();
132
+ let unbound = 0;
133
+ const parentsOf = (e) => edges.filter(x => x.from === e).map(x => x.to);
134
+ const walked = new Set([epc]);
135
+ let layer = parentsOf(epc);
136
+ while (layer.length) {
137
+ const deeper = [];
138
+ for (const e of layer) {
139
+ if (walked.has(e))
140
+ continue;
141
+ walked.add(e);
142
+ const mat = materialOf(e);
143
+ if (mat) {
144
+ observed.set(mat, (observed.get(mat) ?? 0) + 1);
145
+ continue;
146
+ }
147
+ const up = parentsOf(e);
148
+ /* 바인딩에도 없고 더 올라갈 곳도 없다 — 정말 모르는 투입이다(수만 낸다). */
149
+ if (!up.length) {
150
+ unbound++;
151
+ continue;
152
+ }
153
+ deeper.push(...up);
154
+ }
155
+ layer = deeper;
156
+ }
157
+ const declared = new Map((recipe.inputs ?? []).map((i) => [i.material, i.qty ?? null]));
158
+ const keys = [...new Set([...declared.keys(), ...observed.keys()])];
159
+ conformance = {
160
+ comparable: true,
161
+ material: myMaterial,
162
+ recipe: recipe.key,
163
+ /* 자재마다 선언·관측을 나란히. 어느 한쪽에만 있는 것도 사실이다(빠뜨리지 않는다). */
164
+ lines: keys.map(m => ({
165
+ material: m,
166
+ declared: declared.has(m) ? declared.get(m) : null,
167
+ observed: observed.get(m) ?? 0,
168
+ matches: declared.get(m) === (observed.get(m) ?? 0)
169
+ })),
170
+ /* 바인딩에 없는 투입 — 설계에 없는 것이 들어갔을 수도, 바인딩이 덜 선언됐을 수도 있다.
171
+ 어느 쪽인지 우리는 모르므로 **수만 낸다.** */
172
+ unboundInputs: unbound
173
+ };
174
+ }
175
+ return {
176
+ instanceId,
177
+ epc,
178
+ itemRef: itemRefOf(epc),
179
+ material: myMaterial,
180
+ items,
181
+ edges,
182
+ conformance,
183
+ /* 무엇을 근거로 답했나 — 창을 넘겨 끊겼으면 계보가 실제보다 얕게 보인다. */
184
+ basis: { source: 'journal', transformationsRead: rows.length, truncated, depthTruncated, depthCap, windowCap }
185
+ };
186
+ }
187
+ };
188
+ exports.TwinLineageQuery = TwinLineageQuery;
189
+ tslib_1.__decorate([
190
+ (0, type_graphql_1.Query)(returns => shell_1.ScalarObject, {
191
+ nullable: true,
192
+ description: 'Where one item came from (observed) laid next to what its type declares (BOM). Walks TransformationEvent input/output lists in the journal; reports the window it read and whether it was truncated. The declared|observed difference is the diagnosis: "the recipe says one frame, this one consumed two".'
193
+ }),
194
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('instanceId')),
195
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('epc')),
196
+ tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
197
+ tslib_1.__param(3, (0, type_graphql_1.Arg)('maxDepth', type => type_graphql_1.Int, { nullable: true })),
198
+ tslib_1.__param(4, (0, type_graphql_1.Arg)('window', type => type_graphql_1.Int, { nullable: true })),
199
+ tslib_1.__metadata("design:type", Function),
200
+ tslib_1.__metadata("design:paramtypes", [String, String, Object, Number, Number]),
201
+ tslib_1.__metadata("design:returntype", Promise)
202
+ ], TwinLineageQuery.prototype, "twinItemLineage", null);
203
+ exports.TwinLineageQuery = TwinLineageQuery = tslib_1.__decorate([
204
+ (0, type_graphql_1.Resolver)()
205
+ ], TwinLineageQuery);
206
+ //# sourceMappingURL=twin-lineage-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"twin-lineage-query.js","sourceRoot":"","sources":["../../../server/service/twin-model/twin-lineage-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Int, Query, Resolver } from 'type-graphql'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\n\n/*\n * 계보 — **이 개체는 무엇에서 나왔나**, 그리고 **설계대로 만들어졌나.**\n *\n * ── 구성과 계보는 주어가 다르다 ────────────────────────────────────────────────\n * 구성(BOM)의 주어는 품목 **정의**다 — \"트레일러는 프레임 1개로 만든다\"(선언).\n * 계보의 주어는 품목 **개체**다 — \"이 트레일러는 저 프레임 로트에서 나왔다\"(관측).\n * 둘은 같은 모양이라 겹쳐 볼 수 있고, **그 차이가 곧 진단**이다: \"설계는 1개인데 이건 2개를 먹었다\".\n * 트윈이 아니면 못 하는 답이고, 이 질의가 그 답의 재료다.\n *\n * ── 어디서 읽나 ────────────────────────────────────────────────────────────────\n * 계보는 상태가 아니라 **저널**에 있다(EPCIS `TransformationEvent` 의 입력·산출 목록). 상태는\n * 지금 무엇이 있는지만 알고, 없어진 원자재가 무엇이었는지는 이벤트만 안다.\n *\n * ── 규모 ───────────────────────────────────────────────────────────────────────\n * 입력 목록은 **컬럼이 아니라 봉투 안**에 있어서 인덱스로 좁힐 수 없다. 그래서 변환 이벤트만\n * 창으로 끊어 읽고, **끊었으면 끊었다고 말한다.** 그 사실을 감추면 \"이 개체는 원자재가 없다\" 로\n * 읽히고, 계보에서 그 오해는 리콜 범위를 좁게 잡는 결과로 이어진다.\n */\n\n/** GS1 SGTIN 의 **품목 참조** — 개체(serial)를 떼고 품목만 남긴다(`…sgtin:0614141.077101.42` → `077101`). */\nfunction itemRefOf(epc: string): string | null {\n const m = /sgtin:[^.]+\\.([^.]+)\\./.exec(epc)\n return m ? m[1] : null\n}\n\n@Resolver()\nexport class TwinLineageQuery {\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Where one item came from (observed) laid next to what its type declares (BOM). Walks TransformationEvent input/output lists in the journal; reports the window it read and whether it was truncated. The declared|observed difference is the diagnosis: \"the recipe says one frame, this one consumed two\".'\n })\n async twinItemLineage(\n @Arg('instanceId') instanceId: string,\n @Arg('epc') epc: string,\n @Ctx() context: ResolverContext,\n @Arg('maxDepth', type => Int, { nullable: true }) maxDepth?: number,\n @Arg('window', type => Int, { nullable: true }) window?: number\n ): Promise<any | null> {\n const domainId = context.state.domain.id\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return null\n\n const depthCap = Math.min(Math.max(maxDepth ?? 6, 1), 12)\n const windowCap = Math.min(Math.max(window ?? 5000, 1), 50000)\n\n /*\n * 변환 이벤트만 읽는다. **최신부터** 읽는다 — 계보를 묻는 개체는 대개 최근에 만들어진 것이고,\n * 창을 넘겨 잘릴 때 잃는 쪽이 오래된 것이어야 덜 아프다.\n */\n const rows: any[] = await getRepository(TwinEvent).find({\n where: { domain: { id: domainId } as any, instanceId, eventType: 'epcis.TransformationEvent' } as any,\n order: { eventTime: 'DESC' } as any,\n take: windowCap\n })\n const truncated = rows.length >= windowCap\n\n /* 산출 → 그 변환. 한 개체는 한 번만 만들어지지만, 데이터가 그렇지 않을 수 있으므로 목록으로 든다. */\n const madeBy = new Map<string, { inputs: string[]; at?: string; tx?: string }[]>()\n for (const r of rows) {\n const d = r.payload?.data ?? {}\n const outs: string[] = d.outputEPCList ?? []\n const ins: string[] = d.inputEPCList ?? []\n const step = { inputs: ins, at: r.eventTime, tx: d.bizTransactionList?.[0]?.bizTransaction }\n for (const o of outs) {\n const list = madeBy.get(o) ?? []\n list.push(step)\n madeBy.set(o, list)\n }\n }\n\n /* 거슬러 올라간다 — 너비 우선, 지나온 것은 다시 파지 않는다(사이클·공유 부품). */\n const items: any[] = [{ epc, itemRef: itemRefOf(epc), depth: 0 }]\n const edges: any[] = []\n const seen = new Set<string>([epc])\n let depthTruncated = false\n let frontier = [{ epc, depth: 0 }]\n while (frontier.length) {\n const next: typeof frontier = []\n for (const cur of frontier) {\n if (cur.depth >= depthCap) { depthTruncated = true; continue }\n for (const step of madeBy.get(cur.epc) ?? []) {\n for (const input of step.inputs) {\n edges.push({ from: cur.epc, to: input, at: step.at, tx: step.tx, depth: cur.depth + 1 })\n if (seen.has(input)) continue\n seen.add(input)\n items.push({ epc: input, itemRef: itemRefOf(input), depth: cur.depth + 1 })\n next.push({ epc: input, depth: cur.depth + 1 })\n }\n }\n }\n frontier = next\n }\n\n /*\n * **겹쳐 본다.** 이 개체의 품목이 어느 레시피의 산출이면, 그 레시피가 선언한 투입과 실제로\n * 소비된 것을 자재별로 나란히 센다.\n *\n * 개체(EPC)와 자재 키를 잇는 것은 `productionSpec.binding`(자재 키 → GS1 품목 참조)이다.\n * 바인딩이 없으면 **비교하지 않는다** — 짐작으로 이으면 \"설계와 다르다\" 는 거짓 진단이 나오고,\n * 그건 아무 말도 안 하는 것보다 나쁘다.\n */\n const spec: any = (inst.model as any)?.productionSpec\n const binding: Record<string, string> = spec?.binding ?? {}\n const refToMaterial = new Map(Object.entries(binding).map(([mat, ref]) => [String(ref), mat]))\n const materialOf = (e: string) => {\n const ref = itemRefOf(e)\n return ref ? refToMaterial.get(ref) ?? null : null\n }\n\n const myMaterial = materialOf(epc)\n const recipes: any[] = spec?.definition?.recipes ?? []\n const recipe = myMaterial ? recipes.find((r: any) => (r.outputs ?? []).some((o: any) => o.material === myMaterial)) : undefined\n\n let conformance: any = null\n if (!Object.keys(binding).length) {\n conformance = { comparable: false, reason: 'no-binding' }\n } else if (!myMaterial) {\n conformance = { comparable: false, reason: 'item-not-bound' }\n } else if (!recipe) {\n conformance = { comparable: false, reason: 'no-recipe-for-material' }\n } else {\n /*\n * 관측: **바인딩된 자재까지 내려가 센다** — 중간 산물(WIP)은 통과한다.\n *\n * 다단 라인에서 완제품의 직접 투입은 원자재가 아니라 **WIP** 다: 원자재는 첫 공정에서\n * 소비되고 완제품은 마지막 공정에서 나온다. 그래서 직접 투입만 세면 레시피가 선언한 자재와\n * **영원히 안 맞는다**(검사가 이것을 잡았다).\n *\n * WIP 은 라우트의 내부 산물이고 선언된 자재가 아니다 — 레시피는 자재로 말하므로 비교도\n * 자재로 해야 한다. 바인딩에 있으면 거기서 멈추고, 없으면 그 위로 계속 따라간다.\n *\n * 다만 **다른 완제품까지 넘어가지 않는다**: 바인딩된 것을 만나면 멈추므로, 하위조립도\n * 자재로 선언돼 있으면 그 자리에서 세어진다(그 아래는 그 자재 자신의 레시피가 볼 일이다).\n */\n const observed = new Map<string, number>()\n let unbound = 0\n const parentsOf = (e: string) => edges.filter(x => x.from === e).map(x => x.to)\n const walked = new Set<string>([epc])\n let layer = parentsOf(epc)\n while (layer.length) {\n const deeper: string[] = []\n for (const e of layer) {\n if (walked.has(e)) continue\n walked.add(e)\n const mat = materialOf(e)\n if (mat) { observed.set(mat, (observed.get(mat) ?? 0) + 1); continue }\n const up = parentsOf(e)\n /* 바인딩에도 없고 더 올라갈 곳도 없다 — 정말 모르는 투입이다(수만 낸다). */\n if (!up.length) { unbound++; continue }\n deeper.push(...up)\n }\n layer = deeper\n }\n const declared = new Map<string, number>((recipe.inputs ?? []).map((i: any) => [i.material, i.qty ?? null]))\n const keys = [...new Set([...declared.keys(), ...observed.keys()])]\n conformance = {\n comparable: true,\n material: myMaterial,\n recipe: recipe.key,\n /* 자재마다 선언·관측을 나란히. 어느 한쪽에만 있는 것도 사실이다(빠뜨리지 않는다). */\n lines: keys.map(m => ({\n material: m,\n declared: declared.has(m) ? declared.get(m) : null,\n observed: observed.get(m) ?? 0,\n matches: declared.get(m) === (observed.get(m) ?? 0)\n })),\n /* 바인딩에 없는 투입 — 설계에 없는 것이 들어갔을 수도, 바인딩이 덜 선언됐을 수도 있다.\n 어느 쪽인지 우리는 모르므로 **수만 낸다.** */\n unboundInputs: unbound\n }\n }\n\n return {\n instanceId,\n epc,\n itemRef: itemRefOf(epc),\n material: myMaterial,\n items,\n edges,\n conformance,\n /* 무엇을 근거로 답했나 — 창을 넘겨 끊겼으면 계보가 실제보다 얕게 보인다. */\n basis: { source: 'journal', transformationsRead: rows.length, truncated, depthTruncated, depthCap, windowCap }\n }\n }\n}\n"]}
@@ -0,0 +1,58 @@
1
+ import { Domain } from '@things-factory/shell';
2
+ import { TwinInstance } from '../twin-instance/twin-instance.js';
3
+ import { TwinArea } from '../twin-space/twin-area.js';
4
+ export declare class TwinLocation {
5
+ readonly id: string;
6
+ domain?: Domain;
7
+ domainId?: string;
8
+ instance?: TwinInstance;
9
+ instanceRowId?: string;
10
+ /** 커널 계약의 `TwinModelDef.locations[].id` **그대로**. 바인딩은 id===id 다(ADR-0014). */
11
+ locationId: string;
12
+ /**
13
+ * **사람이 부르는 이름** — 프레임워크 엔티티 규약의 `name`/`description`.
14
+ *
15
+ * ── 왜 이것이 빠져 있었고, 무엇이 망가졌나 ────────────────────────────────────
16
+ * 투영이 마스터의 `name` 을 버리고 있었다. 그래서 행에는 id 만 남고, 화면은 이름을 만들 재료가
17
+ * 없어 `cut-station` 을 그대로 찍었다 — 그 결함이 목록·표·관계 그림마다 따로 나타나서 표면을
18
+ * 하나씩 고치게 만들었다. 표면의 문제가 아니라 **행에 이름이 없던 문제**다.
19
+ *
20
+ * 원본이 이름을 말하지 않으면 비운다(id 를 이름으로 베끼지 않는다 — 그러면 "이름이 있다" 는
21
+ * 거짓이 되고, 화면이 이름 없음을 알아볼 수 없게 된다).
22
+ */
23
+ name?: string;
24
+ description?: string;
25
+ /**
26
+ * 커널 카탈로그의 자리 타입 키(`storage`·`dock`·`yard-slot`…).
27
+ *
28
+ * **FK 를 걸지 않는다** — 타입 카탈로그는 커널이 SSOT 이고(ADR-0018) DB 로 내리면
29
+ * **커널 어휘의 사본**이 생긴다. 문자열 + 부팅 시 검증이 맞다(ADR-0032-F).
30
+ */
31
+ type: string;
32
+ capacity?: number;
33
+ parallelism?: number;
34
+ /**
35
+ * 속한 구역 — **실제 FK**.
36
+ *
37
+ * 커널 계약에서는 `locations[].parentId` 가 model 안에 대상이 없는 참조였다(마스터에서 구역도
38
+ * 자리와 한 목록이었다가 인제스트가 갈라 `TwinArea` 로 보내기 때문). 커널은 그것을
39
+ * `external: 'space.area'` 로 선언했고, **호스트에서는 평범한 FK 가 된다.**
40
+ */
41
+ area?: TwinArea;
42
+ areaRowId?: string;
43
+ /** GS1 식별자(SGLN 등) — 원본이 주면 싣는다. */
44
+ gs1Id?: string;
45
+ /**
46
+ * 이 자리의 **선언 속성** — 계약전력·요금 단가처럼 자리에 얹히는 수(ISA-95 Property).
47
+ *
48
+ * ── 왜 늦게 생겼나 (2026-08-18) ────────────────────────────────────────────
49
+ * 설비 행에는 이 칸이 있었는데 자리 행에는 없었다. 그래서 수전 지점에 계약전력·요금을 넣어도 **항목
50
+ * 패널의 「원본이 말한 값」에 그 줄이 아예 없었다** — 사용자는 값을 넣고도 화면에서 찾을 수 없었고,
51
+ * 「이 값이 무엇을 바꾸나」는 문장도 붙을 자리가 없었다(조용한 누락).
52
+ */
53
+ properties?: any;
54
+ sourceRef: string;
55
+ ingestedAt: Date;
56
+ createdAt?: Date;
57
+ updatedAt?: Date;
58
+ }