@things-factory/station 10.1.33

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 (185) hide show
  1. package/README.md +124 -0
  2. package/dist-client/index.d.ts +11 -0
  3. package/dist-client/index.js +13 -0
  4. package/dist-client/index.js.map +1 -0
  5. package/dist-client/station/scene/index.d.ts +11 -0
  6. package/dist-client/station/scene/index.js +12 -0
  7. package/dist-client/station/scene/index.js.map +1 -0
  8. package/dist-client/station/scene/station-context-binding.d.ts +25 -0
  9. package/dist-client/station/scene/station-context-binding.js +39 -0
  10. package/dist-client/station/scene/station-context-binding.js.map +1 -0
  11. package/dist-client/station/scene/station-context-scene.d.ts +28 -0
  12. package/dist-client/station/scene/station-context-scene.js +138 -0
  13. package/dist-client/station/scene/station-context-scene.js.map +1 -0
  14. package/dist-client/station/scene/station-counts-scene.d.ts +6 -0
  15. package/dist-client/station/scene/station-counts-scene.js +51 -0
  16. package/dist-client/station/scene/station-counts-scene.js.map +1 -0
  17. package/dist-client/station/scene/station-field-scene.d.ts +6 -0
  18. package/dist-client/station/scene/station-field-scene.js +89 -0
  19. package/dist-client/station/scene/station-field-scene.js.map +1 -0
  20. package/dist-client/station/scene/station-icons.d.ts +82 -0
  21. package/dist-client/station/scene/station-icons.js +131 -0
  22. package/dist-client/station/scene/station-icons.js.map +1 -0
  23. package/dist-client/station/scene/station-numpad-scene.d.ts +9 -0
  24. package/dist-client/station/scene/station-numpad-scene.js +144 -0
  25. package/dist-client/station/scene/station-numpad-scene.js.map +1 -0
  26. package/dist-client/station/scene/station-scene-style.d.ts +51 -0
  27. package/dist-client/station/scene/station-scene-style.js +108 -0
  28. package/dist-client/station/scene/station-scene-style.js.map +1 -0
  29. package/dist-client/station/scene/station-scene.d.ts +295 -0
  30. package/dist-client/station/scene/station-scene.js +68 -0
  31. package/dist-client/station/scene/station-scene.js.map +1 -0
  32. package/dist-client/station/scene/station-who-scene.d.ts +6 -0
  33. package/dist-client/station/scene/station-who-scene.js +48 -0
  34. package/dist-client/station/scene/station-who-scene.js.map +1 -0
  35. package/dist-client/station/scene/station-work-panel-scene.d.ts +28 -0
  36. package/dist-client/station/scene/station-work-panel-scene.js +37 -0
  37. package/dist-client/station/scene/station-work-panel-scene.js.map +1 -0
  38. package/dist-client/station/station-board-binding.d.ts +16 -0
  39. package/dist-client/station/station-board-binding.js +20 -0
  40. package/dist-client/station/station-board-binding.js.map +1 -0
  41. package/dist-client/station/station-context.d.ts +178 -0
  42. package/dist-client/station/station-context.js +30 -0
  43. package/dist-client/station/station-context.js.map +1 -0
  44. package/dist-client/station/station-palette.d.ts +34 -0
  45. package/dist-client/station/station-palette.js +119 -0
  46. package/dist-client/station/station-palette.js.map +1 -0
  47. package/dist-client/station/station-runtime.d.ts +47 -0
  48. package/dist-client/station/station-runtime.js +47 -0
  49. package/dist-client/station/station-runtime.js.map +1 -0
  50. package/dist-client/station/station-scene-context.d.ts +68 -0
  51. package/dist-client/station/station-scene-context.js +62 -0
  52. package/dist-client/station/station-scene-context.js.map +1 -0
  53. package/dist-client/station/station-screen-flow.d.ts +16 -0
  54. package/dist-client/station/station-screen-flow.js +58 -0
  55. package/dist-client/station/station-screen-flow.js.map +1 -0
  56. package/dist-client/station/station-tile-registry.d.ts +89 -0
  57. package/dist-client/station/station-tile-registry.js +86 -0
  58. package/dist-client/station/station-tile-registry.js.map +1 -0
  59. package/dist-client/station/station-work-panel-view.d.ts +23 -0
  60. package/dist-client/station/station-work-panel-view.js +125 -0
  61. package/dist-client/station/station-work-panel-view.js.map +1 -0
  62. package/dist-client/station/station-work-runtime.d.ts +47 -0
  63. package/dist-client/station/station-work-runtime.js +161 -0
  64. package/dist-client/station/station-work-runtime.js.map +1 -0
  65. package/dist-client/station/station-work-scenarios.d.ts +41 -0
  66. package/dist-client/station/station-work-scenarios.js +59 -0
  67. package/dist-client/station/station-work-scenarios.js.map +1 -0
  68. package/dist-client/station/task-counts.d.ts +32 -0
  69. package/dist-client/station/task-counts.js +26 -0
  70. package/dist-client/station/task-counts.js.map +1 -0
  71. package/dist-client/tsconfig.tsbuildinfo +1 -0
  72. package/dist-server/activities/activity-station-screen-control-withdrawal.d.ts +62 -0
  73. package/dist-server/activities/activity-station-screen-control-withdrawal.js +147 -0
  74. package/dist-server/activities/activity-station-screen-control-withdrawal.js.map +1 -0
  75. package/dist-server/activities/activity-station-screen-release.d.ts +81 -0
  76. package/dist-server/activities/activity-station-screen-release.js +147 -0
  77. package/dist-server/activities/activity-station-screen-release.js.map +1 -0
  78. package/dist-server/activities/index.d.ts +49 -0
  79. package/dist-server/activities/index.js +21 -0
  80. package/dist-server/activities/index.js.map +1 -0
  81. package/dist-server/index.d.ts +3 -0
  82. package/dist-server/index.js +8 -0
  83. package/dist-server/index.js.map +1 -0
  84. package/dist-server/service/index.d.ts +23 -0
  85. package/dist-server/service/index.js +41 -0
  86. package/dist-server/service/index.js.map +1 -0
  87. package/dist-server/service/station/deletion-changes.d.ts +14 -0
  88. package/dist-server/service/station/deletion-changes.js +25 -0
  89. package/dist-server/service/station/deletion-changes.js.map +1 -0
  90. package/dist-server/service/station/index.d.ts +13 -0
  91. package/dist-server/service/station/index.js +25 -0
  92. package/dist-server/service/station/index.js.map +1 -0
  93. package/dist-server/service/station/reversibility.d.ts +21 -0
  94. package/dist-server/service/station/reversibility.js +24 -0
  95. package/dist-server/service/station/reversibility.js.map +1 -0
  96. package/dist-server/service/station/screen-model-digest.d.ts +60 -0
  97. package/dist-server/service/station/screen-model-digest.js +97 -0
  98. package/dist-server/service/station/screen-model-digest.js.map +1 -0
  99. package/dist-server/service/station/staged-material.d.ts +62 -0
  100. package/dist-server/service/station/staged-material.js +44 -0
  101. package/dist-server/service/station/staged-material.js.map +1 -0
  102. package/dist-server/service/station/station-argument-sources.d.ts +31 -0
  103. package/dist-server/service/station/station-argument-sources.js +66 -0
  104. package/dist-server/service/station/station-argument-sources.js.map +1 -0
  105. package/dist-server/service/station/station-assignment-choice.d.ts +81 -0
  106. package/dist-server/service/station/station-assignment-choice.js +105 -0
  107. package/dist-server/service/station/station-assignment-choice.js.map +1 -0
  108. package/dist-server/service/station/station-assignment-resolver.d.ts +104 -0
  109. package/dist-server/service/station/station-assignment-resolver.js +426 -0
  110. package/dist-server/service/station/station-assignment-resolver.js.map +1 -0
  111. package/dist-server/service/station/station-assignment-service.d.ts +33 -0
  112. package/dist-server/service/station/station-assignment-service.js +64 -0
  113. package/dist-server/service/station/station-assignment-service.js.map +1 -0
  114. package/dist-server/service/station/station-axes.d.ts +9 -0
  115. package/dist-server/service/station/station-axes.js +16 -0
  116. package/dist-server/service/station/station-axes.js.map +1 -0
  117. package/dist-server/service/station/station-flow.d.ts +156 -0
  118. package/dist-server/service/station/station-flow.js +255 -0
  119. package/dist-server/service/station/station-flow.js.map +1 -0
  120. package/dist-server/service/station/station-generate.d.ts +76 -0
  121. package/dist-server/service/station/station-generate.js +178 -0
  122. package/dist-server/service/station/station-generate.js.map +1 -0
  123. package/dist-server/service/station/station-product.d.ts +123 -0
  124. package/dist-server/service/station/station-product.js +77 -0
  125. package/dist-server/service/station/station-product.js.map +1 -0
  126. package/dist-server/service/station/station-runtime-binding.d.ts +6 -0
  127. package/dist-server/service/station/station-runtime-binding.js +46 -0
  128. package/dist-server/service/station/station-runtime-binding.js.map +1 -0
  129. package/dist-server/service/station/station-screen-assignment.d.ts +94 -0
  130. package/dist-server/service/station/station-screen-assignment.js +154 -0
  131. package/dist-server/service/station/station-screen-assignment.js.map +1 -0
  132. package/dist-server/service/station/station-screen-control-withdrawal-resolver.d.ts +37 -0
  133. package/dist-server/service/station/station-screen-control-withdrawal-resolver.js +130 -0
  134. package/dist-server/service/station/station-screen-control-withdrawal-resolver.js.map +1 -0
  135. package/dist-server/service/station/station-screen-control-withdrawal.d.ts +104 -0
  136. package/dist-server/service/station/station-screen-control-withdrawal.js +128 -0
  137. package/dist-server/service/station/station-screen-control-withdrawal.js.map +1 -0
  138. package/dist-server/service/station/station-screen-draft-resolver.d.ts +47 -0
  139. package/dist-server/service/station/station-screen-draft-resolver.js +153 -0
  140. package/dist-server/service/station/station-screen-draft-resolver.js.map +1 -0
  141. package/dist-server/service/station/station-screen-layout.d.ts +119 -0
  142. package/dist-server/service/station/station-screen-layout.js +202 -0
  143. package/dist-server/service/station/station-screen-layout.js.map +1 -0
  144. package/dist-server/service/station/station-screen-list-resolver.d.ts +69 -0
  145. package/dist-server/service/station/station-screen-list-resolver.js +215 -0
  146. package/dist-server/service/station/station-screen-list-resolver.js.map +1 -0
  147. package/dist-server/service/station/station-screen-publish-subscriber.d.ts +22 -0
  148. package/dist-server/service/station/station-screen-publish-subscriber.js +90 -0
  149. package/dist-server/service/station/station-screen-publish-subscriber.js.map +1 -0
  150. package/dist-server/service/station/station-screen-release-gate.d.ts +66 -0
  151. package/dist-server/service/station/station-screen-release-gate.js +67 -0
  152. package/dist-server/service/station/station-screen-release-gate.js.map +1 -0
  153. package/dist-server/service/station/station-screen-release-record.d.ts +62 -0
  154. package/dist-server/service/station/station-screen-release-record.js +139 -0
  155. package/dist-server/service/station/station-screen-release-record.js.map +1 -0
  156. package/dist-server/service/station/station-screen-release-signature.d.ts +23 -0
  157. package/dist-server/service/station/station-screen-release-signature.js +32 -0
  158. package/dist-server/service/station/station-screen-release-signature.js.map +1 -0
  159. package/dist-server/service/station/station-screen-scorecard.d.ts +15 -0
  160. package/dist-server/service/station/station-screen-scorecard.js +35 -0
  161. package/dist-server/service/station/station-screen-scorecard.js.map +1 -0
  162. package/dist-server/service/station/station-screen-spec-resolver.d.ts +36 -0
  163. package/dist-server/service/station/station-screen-spec-resolver.js +202 -0
  164. package/dist-server/service/station/station-screen-spec-resolver.js.map +1 -0
  165. package/dist-server/service/station/station-screen-spec.d.ts +104 -0
  166. package/dist-server/service/station/station-screen-spec.js +114 -0
  167. package/dist-server/service/station/station-screen-spec.js.map +1 -0
  168. package/dist-server/service/station/station-screen-thumbnail.d.ts +11 -0
  169. package/dist-server/service/station/station-screen-thumbnail.js +36 -0
  170. package/dist-server/service/station/station-screen-thumbnail.js.map +1 -0
  171. package/dist-server/service/station/station-service-resolver.d.ts +27 -0
  172. package/dist-server/service/station/station-service-resolver.js +95 -0
  173. package/dist-server/service/station/station-service-resolver.js.map +1 -0
  174. package/dist-server/tsconfig.tsbuildinfo +1 -0
  175. package/package.json +44 -0
  176. package/shared/station-axes.d.ts +4 -0
  177. package/shared/station-axes.js +155 -0
  178. package/shared/station-screen-flow-check.d.ts +12 -0
  179. package/shared/station-screen-flow-check.js +74 -0
  180. package/things-scene.config.js +15 -0
  181. package/translations/en.json +42 -0
  182. package/translations/ja.json +42 -0
  183. package/translations/ko.json +42 -0
  184. package/translations/ms.json +42 -0
  185. package/translations/zh.json +42 -0
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stationSituationOf = stationSituationOf;
4
+ exports.stationAssignmentFor = stationAssignmentFor;
5
+ const typeorm_1 = require("typeorm");
6
+ const ops_master_1 = require("@things-factory/ops-master");
7
+ const station_screen_assignment_1 = require("./station-screen-assignment");
8
+ const station_assignment_choice_1 = require("./station-assignment-choice");
9
+ const station_product_js_1 = require("./station-product.js");
10
+ /**
11
+ * What this station is, in the words the assignment selects on.
12
+ *
13
+ * ── Read the situation from the same place the screen reads it ─────────────
14
+ * It comes from the product's declared ground reader, which is the same function the screen's own
15
+ * context is built on. One vocabulary answers both "which screen shows" and "what does it show" —
16
+ * gathered separately they would drift, and a factory could select on something the screen cannot
17
+ * display and never work out why the board is blank.
18
+ *
19
+ * Declared rather than imported so the machinery does not name this product (ADR-0083 decision 2).
20
+ */
21
+ async function stationSituationOf(manager, domain, equipmentName, at) {
22
+ const ground = await (0, station_product_js_1.stationProduct)().readGround(manager, domain, equipmentName, at);
23
+ const equipment = await manager.findOne(ops_master_1.Equipment, {
24
+ where: { domain: { id: domain.id }, name: equipmentName }
25
+ });
26
+ return {
27
+ equipmentName,
28
+ /*
29
+ * 소속은 `equipmentClassIds ∪ {kind}` 다 — 계약이 그렇게 본다(`equipmentClassMembership`).
30
+ * `kind` 를 빼면 등급 칸이 열리기 전에 만든 설비가 「용접설비」 배정에서 조용히 빠진다.
31
+ */
32
+ equipmentClassIds: [...(equipment?.equipmentClassIds ?? []), ...(equipment?.kind ? [equipment.kind] : [])],
33
+ workCenterName: ground.workCenterName,
34
+ operationKey: at?.operationKey,
35
+ routeKey: ground.routeKey,
36
+ materialClassIds: ground.materialClassIds,
37
+ personnelClassIds: ground.personnelClassIds
38
+ };
39
+ }
40
+ /**
41
+ * Which board this station shows right now.
42
+ *
43
+ * ── Effectiveness is filtered here, matching is not ────────────────────────
44
+ * A row that does not apply today is not a candidate at all, so it is left out before the rule runs
45
+ * — keeping the pure rule about specificity and nothing else. The dates are the standard
46
+ * `EffectiveStartDate` / `EffectiveEndDate` pair the masters already use, and they are what lets a
47
+ * changeover be prepared in advance rather than switched by hand on the morning.
48
+ */
49
+ async function stationAssignmentFor(manager, domain, equipmentName, at, now = new Date()) {
50
+ const rows = await manager.find(station_screen_assignment_1.StationScreenAssignment, {
51
+ where: [
52
+ { domain: { id: domain.id }, effectiveStart: (0, typeorm_1.IsNull)(), effectiveEnd: (0, typeorm_1.IsNull)() },
53
+ { domain: { id: domain.id }, effectiveStart: (0, typeorm_1.LessThanOrEqual)(now), effectiveEnd: (0, typeorm_1.IsNull)() },
54
+ { domain: { id: domain.id }, effectiveStart: (0, typeorm_1.IsNull)(), effectiveEnd: (0, typeorm_1.MoreThan)(now) },
55
+ { domain: { id: domain.id }, effectiveStart: (0, typeorm_1.LessThanOrEqual)(now), effectiveEnd: (0, typeorm_1.MoreThan)(now) }
56
+ ]
57
+ });
58
+ if (!rows.length) {
59
+ return undefined;
60
+ }
61
+ const situation = await stationSituationOf(manager, domain, equipmentName, at);
62
+ return (0, station_assignment_choice_1.chooseStationAssignment)(rows, situation);
63
+ }
64
+ //# sourceMappingURL=station-assignment-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"station-assignment-service.js","sourceRoot":"","sources":["../../../server/service/station/station-assignment-service.ts"],"names":[],"mappings":";;AAqBA,gDAyBC;AAWD,oDAuBC;AAhFD,qCAA0E;AAG1E,2DAAsD;AAEtD,2EAAqE;AACrE,2EAAqE;AAErE,6DAAqD;AAErD;;;;;;;;;;GAUG;AACI,KAAK,UAAU,kBAAkB,CACtC,OAAsB,EACtB,MAAc,EACd,aAAqB,EACrB,EAA4E;IAE5E,MAAM,MAAM,GAAG,MAAM,IAAA,mCAAc,GAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAC,CAAA;IAEpF,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,sBAAS,EAAE;QACjD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;KAC1D,CAAC,CAAA;IAEF,OAAO;QACL,aAAa;QACb;;;WAGG;QACH,iBAAiB,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,iBAAiB,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1G,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,YAAY,EAAE,EAAE,EAAE,YAAY;QAC9B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;QACzC,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;KAC5C,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACI,KAAK,UAAU,oBAAoB,CACxC,OAAsB,EACtB,MAAc,EACd,aAAqB,EACrB,EAA4E,EAC5E,MAAY,IAAI,IAAI,EAAE;IAEtB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,mDAAuB,EAAE;QACvD,KAAK,EAAE;YACL,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,IAAA,gBAAM,GAAE,EAAE,YAAY,EAAE,IAAA,gBAAM,GAAE,EAAE;YAC/E,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,IAAA,yBAAe,EAAC,GAAG,CAAC,EAAE,YAAY,EAAE,IAAA,gBAAM,GAAE,EAAE;YAC3F,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,IAAA,gBAAM,GAAE,EAAE,YAAY,EAAE,IAAA,kBAAQ,EAAC,GAAG,CAAC,EAAE;YACpF,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,IAAA,yBAAe,EAAC,GAAG,CAAC,EAAE,YAAY,EAAE,IAAA,kBAAQ,EAAC,GAAG,CAAC,EAAE;SACjG;KACF,CAAC,CAAA;IAEF,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAC,CAAA;IAE9E,OAAO,IAAA,mDAAuB,EAAC,IAA+B,EAAE,SAAS,CAAC,CAAA;AAC5E,CAAC","sourcesContent":["import { EntityManager, IsNull, LessThanOrEqual, MoreThan } from 'typeorm'\n\nimport { Domain } from '@things-factory/shell'\nimport { Equipment } from '@things-factory/ops-master'\n\nimport { StationScreenAssignment } from './station-screen-assignment'\nimport { chooseStationAssignment } from './station-assignment-choice'\nimport type { StationAssignmentChoice, StationAssignmentRule, StationAssignmentSituation } from './station-assignment-choice'\nimport { stationProduct } from './station-product.js'\n\n/**\n * What this station is, in the words the assignment selects on.\n *\n * ── Read the situation from the same place the screen reads it ─────────────\n * It comes from the product's declared ground reader, which is the same function the screen's own\n * context is built on. One vocabulary answers both \"which screen shows\" and \"what does it show\" —\n * gathered separately they would drift, and a factory could select on something the screen cannot\n * display and never work out why the board is blank.\n *\n * Declared rather than imported so the machinery does not name this product (ADR-0083 decision 2).\n */\nexport async function stationSituationOf(\n manager: EntityManager,\n domain: Domain,\n equipmentName: string,\n at?: { operationKey?: string; operatorName?: string; jobOrderName?: string }\n): Promise<StationAssignmentSituation> {\n const ground = await stationProduct().readGround(manager, domain, equipmentName, at)\n\n const equipment = await manager.findOne(Equipment, {\n where: { domain: { id: domain.id }, name: equipmentName }\n })\n\n return {\n equipmentName,\n /*\n * 소속은 `equipmentClassIds ∪ {kind}` 다 — 계약이 그렇게 본다(`equipmentClassMembership`).\n * `kind` 를 빼면 등급 칸이 열리기 전에 만든 설비가 「용접설비」 배정에서 조용히 빠진다.\n */\n equipmentClassIds: [...(equipment?.equipmentClassIds ?? []), ...(equipment?.kind ? [equipment.kind] : [])],\n workCenterName: ground.workCenterName,\n operationKey: at?.operationKey,\n routeKey: ground.routeKey,\n materialClassIds: ground.materialClassIds,\n personnelClassIds: ground.personnelClassIds\n }\n}\n\n/**\n * Which board this station shows right now.\n *\n * ── Effectiveness is filtered here, matching is not ────────────────────────\n * A row that does not apply today is not a candidate at all, so it is left out before the rule runs\n * — keeping the pure rule about specificity and nothing else. The dates are the standard\n * `EffectiveStartDate` / `EffectiveEndDate` pair the masters already use, and they are what lets a\n * changeover be prepared in advance rather than switched by hand on the morning.\n */\nexport async function stationAssignmentFor(\n manager: EntityManager,\n domain: Domain,\n equipmentName: string,\n at?: { operationKey?: string; operatorName?: string; jobOrderName?: string },\n now: Date = new Date()\n): Promise<StationAssignmentChoice | undefined> {\n const rows = await manager.find(StationScreenAssignment, {\n where: [\n { domain: { id: domain.id }, effectiveStart: IsNull(), effectiveEnd: IsNull() },\n { domain: { id: domain.id }, effectiveStart: LessThanOrEqual(now), effectiveEnd: IsNull() },\n { domain: { id: domain.id }, effectiveStart: IsNull(), effectiveEnd: MoreThan(now) },\n { domain: { id: domain.id }, effectiveStart: LessThanOrEqual(now), effectiveEnd: MoreThan(now) }\n ]\n })\n\n if (!rows.length) {\n return undefined\n }\n\n const situation = await stationSituationOf(manager, domain, equipmentName, at)\n\n return chooseStationAssignment(rows as StationAssignmentRule[], situation)\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The closed set of context axes, as the server sees it.
3
+ *
4
+ * The names themselves are declared once, in `shared/station-axes.js`, so that the browser's
5
+ * authoring surface and this gate cannot hold two lists that drift apart. Read that file for why
6
+ * the set is closed at all and why it lives there; this one exists so the server keeps importing
7
+ * the axes by the name it always did, and so `checkFlow` has a local place to reach for them.
8
+ */
9
+ export { STATION_AXES, isStationAxis, nearestAxes } from '../../../shared/station-axes.js';
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nearestAxes = exports.isStationAxis = exports.STATION_AXES = void 0;
4
+ /**
5
+ * The closed set of context axes, as the server sees it.
6
+ *
7
+ * The names themselves are declared once, in `shared/station-axes.js`, so that the browser's
8
+ * authoring surface and this gate cannot hold two lists that drift apart. Read that file for why
9
+ * the set is closed at all and why it lives there; this one exists so the server keeps importing
10
+ * the axes by the name it always did, and so `checkFlow` has a local place to reach for them.
11
+ */
12
+ var station_axes_js_1 = require("../../../shared/station-axes.js");
13
+ Object.defineProperty(exports, "STATION_AXES", { enumerable: true, get: function () { return station_axes_js_1.STATION_AXES; } });
14
+ Object.defineProperty(exports, "isStationAxis", { enumerable: true, get: function () { return station_axes_js_1.isStationAxis; } });
15
+ Object.defineProperty(exports, "nearestAxes", { enumerable: true, get: function () { return station_axes_js_1.nearestAxes; } });
16
+ //# sourceMappingURL=station-axes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"station-axes.js","sourceRoot":"","sources":["../../../server/service/station/station-axes.ts"],"names":[],"mappings":";;;AAAA;;;;;;;GAOG;AACH,mEAA0F;AAAjF,+GAAA,YAAY,OAAA;AAAE,gHAAA,aAAa,OAAA;AAAE,8GAAA,WAAW,OAAA","sourcesContent":["/**\n * The closed set of context axes, as the server sees it.\n *\n * The names themselves are declared once, in `shared/station-axes.js`, so that the browser's\n * authoring surface and this gate cannot hold two lists that drift apart. Read that file for why\n * the set is closed at all and why it lives there; this one exists so the server keeps importing\n * the axes by the name it always did, and so `checkFlow` has a local place to reach for them.\n */\nexport { STATION_AXES, isStationAxis, nearestAxes } from '../../../shared/station-axes.js'\n"]}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * What a station screen does — **the flow, and the checks that read it.**
3
+ *
4
+ * Pure on purpose. The value this authoring surface sells is catching a mistake before the screen
5
+ * hangs on a bench, and a check nobody can run without a database and a logged-in browser is a
6
+ * check nobody runs.
7
+ *
8
+ * ── Why a flow and not a list of fields ────────────────────────────────────
9
+ * Half of a bench screen is not display. The operator scans, starts, counts, records a stop reason,
10
+ * finishes — each of those reads something and most of them **write** something. A design that only
11
+ * describes what is shown cannot say whether the finish button will produce a usable record.
12
+ *
13
+ * ── Where the safety comes from ────────────────────────────────────────────
14
+ * Not from the prompt, and not from care. From three closed sets — the step kinds below, the
15
+ * context axes (`stationBindingNames()` is their only source), and the roles — plus the shape of
16
+ * the service being called, which comes from the schema rather than from anybody's memory.
17
+ */
18
+ /**
19
+ * What a step is, as the operator experiences it.
20
+ *
21
+ * Five, and deliberately not one per scene component. These are **the unit the checks ask about** —
22
+ * "does the run step have a source for every required argument", "is there an enter step feeding
23
+ * it" — and those questions cannot be asked of ten drawing parts. How a step is drawn is a separate
24
+ * matter that the layout decides.
25
+ */
26
+ export type StationStepKind =
27
+ /** Reads a badge or a label and changes what the station knows. Writes nothing. */
28
+ 'scan'
29
+ /** Shows values. Reads only. */
30
+ | 'show'
31
+ /** The operator puts a number in. This is what an argument can take its value from. */
32
+ | 'enter'
33
+ /** Calls a mutation. Decides `status` and the clock fields from its own meaning. */
34
+ | 'run'
35
+ /** Not drawn. How often this screen reads, and what it reads. */
36
+ | 'settings'
37
+ /** A standard display block retained while a pre-Composer screen is migrated. */
38
+ | 'legacy';
39
+ /**
40
+ * Where an argument's value comes from. **Three, and no others.**
41
+ *
42
+ * A field with no source is the defect this module exists to name.
43
+ */
44
+ export type StationArgumentSource =
45
+ /** The harness already knows it — equipment, operation, the scanned person. */
46
+ {
47
+ from: 'context';
48
+ axis: string;
49
+ }
50
+ /** The step's own meaning decides it — `status`, `endTime`. */
51
+ | {
52
+ from: 'step';
53
+ }
54
+ /** A person put it in, at the named step. */
55
+ | {
56
+ from: 'input';
57
+ stepId: string;
58
+ }
59
+ /** Written down here on purpose. Rare, and visible when it happens. */
60
+ | {
61
+ from: 'fixed';
62
+ value: string | number | boolean;
63
+ };
64
+ /** One argument of the service a step calls, as the schema describes it. */
65
+ export interface StationServiceField {
66
+ name: string;
67
+ /** What the field's own owner wrote about it, when they wrote anything. Shown instead of the name. */
68
+ describes?: string;
69
+ /** `!` in the schema. Without a source the call fails. */
70
+ required: boolean;
71
+ /**
72
+ * Absent means "nobody wrote it down", not zero.
73
+ *
74
+ * `ProductionReportPatch.goodCount` is the case this exists for: it is not required, so a finish
75
+ * button with no number pad is syntactically fine and produces a report that reads as if nothing
76
+ * was made. Which fields are like that is domain knowledge, so it is **told to this module**
77
+ * rather than guessed by it.
78
+ */
79
+ notable?: boolean;
80
+ }
81
+ /** The mutation a step calls, in the shape the schema gives. */
82
+ export interface StationService {
83
+ /** The mutation name, exactly as in the schema. */
84
+ name: string;
85
+ fields: StationServiceField[];
86
+ }
87
+ export interface StationStep {
88
+ id: string;
89
+ kind: StationStepKind;
90
+ /** 실행 의미. 이름표를 바꾸어도 수행 업무는 바뀌지 않는다. */
91
+ action?: 'start' | 'finish' | 'pause' | 'resume';
92
+ /** What the operator sees on it. Absent means the axis name is used — never nothing. */
93
+ label?: string;
94
+ /** Context axes this step displays. Names come from `stationBindingNames()`. */
95
+ reads?: string[];
96
+ /**
97
+ * For `settings` steps — how often the screen re-reads.
98
+ *
99
+ * Nothing is drawn for it, but it has to survive into the model: without a home here the value
100
+ * a generator computes from the operation's duration is dropped on the way to the board, and
101
+ * every screen ends up on the same default with nothing saying why.
102
+ */
103
+ refreshSeconds?: number;
104
+ /** For `run` steps. */
105
+ service?: StationService;
106
+ /** Field name → where its value comes from. Only meaningful with a service. */
107
+ arguments?: Record<string, StationArgumentSource>;
108
+ /** A rendered Station block not yet expressible by the five authoring step kinds. */
109
+ legacyPart?: Record<string, unknown>;
110
+ }
111
+ export interface StationFlow {
112
+ steps: StationStep[];
113
+ }
114
+ export type StationProblemLevel = 'error' | 'warning';
115
+ export interface StationProblem {
116
+ level: StationProblemLevel;
117
+ /** Which step, when it is about one. */
118
+ stepId?: string;
119
+ /** Which argument, when it is about one. */
120
+ field?: string;
121
+ /**
122
+ * Which check this is — the screen says it in the reader's language.
123
+ *
124
+ * ── Why a code after all ────────────────────────────────────────────────
125
+ * This used to be prose only, on the reasoning that an author reads it and nothing matches on
126
+ * it. That held right up until the composer put it on screen: the panel was in English and
127
+ * these sentences came out in Korean, because the server had written them. A sentence the
128
+ * server composes can only ever be in one language.
129
+ *
130
+ * So the server states **which** check failed and **about what**; the screen says it.
131
+ */
132
+ code: string;
133
+ /** Values to fill into that sentence — a field name, a step's name, a service name. */
134
+ params?: Record<string, string>;
135
+ /**
136
+ * The same thing in Korean, composed here.
137
+ *
138
+ * Not for the screen. This is what `npm run test:station` and the logs print, where there is
139
+ * no i18n and no reader to ask.
140
+ */
141
+ says: string;
142
+ }
143
+ /**
144
+ * Every context axis this flow reads.
145
+ *
146
+ * This is what `StationScreenSpec.requires` is filled from — derived, never typed. An author who
147
+ * has to keep a list in step with the screen will not, and then the assignment check is checking
148
+ * something that used to be true.
149
+ */
150
+ export declare function axesRead(flow: StationFlow): string[];
151
+ /**
152
+ * What is wrong with this flow — **from the flow and the schema alone.**
153
+ *
154
+ * No database, no running station. Everything here is answerable at the moment somebody is drawing.
155
+ */
156
+ export declare function checkFlow(flow: StationFlow): StationProblem[];
@@ -0,0 +1,255 @@
1
+ "use strict";
2
+ /**
3
+ * What a station screen does — **the flow, and the checks that read it.**
4
+ *
5
+ * Pure on purpose. The value this authoring surface sells is catching a mistake before the screen
6
+ * hangs on a bench, and a check nobody can run without a database and a logged-in browser is a
7
+ * check nobody runs.
8
+ *
9
+ * ── Why a flow and not a list of fields ────────────────────────────────────
10
+ * Half of a bench screen is not display. The operator scans, starts, counts, records a stop reason,
11
+ * finishes — each of those reads something and most of them **write** something. A design that only
12
+ * describes what is shown cannot say whether the finish button will produce a usable record.
13
+ *
14
+ * ── Where the safety comes from ────────────────────────────────────────────
15
+ * Not from the prompt, and not from care. From three closed sets — the step kinds below, the
16
+ * context axes (`stationBindingNames()` is their only source), and the roles — plus the shape of
17
+ * the service being called, which comes from the schema rather than from anybody's memory.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.axesRead = axesRead;
21
+ exports.checkFlow = checkFlow;
22
+ const station_axes_js_1 = require("./station-axes.js");
23
+ /**
24
+ * Every context axis this flow reads.
25
+ *
26
+ * This is what `StationScreenSpec.requires` is filled from — derived, never typed. An author who
27
+ * has to keep a list in step with the screen will not, and then the assignment check is checking
28
+ * something that used to be true.
29
+ */
30
+ function axesRead(flow) {
31
+ const seen = new Set();
32
+ for (const step of flow.steps) {
33
+ step.reads?.forEach(axis => seen.add(axis));
34
+ for (const source of Object.values(step.arguments ?? {})) {
35
+ if (source.from === 'context') {
36
+ seen.add(source.axis);
37
+ }
38
+ }
39
+ }
40
+ return [...seen].sort();
41
+ }
42
+ /**
43
+ * What is wrong with this flow — **from the flow and the schema alone.**
44
+ *
45
+ * No database, no running station. Everything here is answerable at the moment somebody is drawing.
46
+ */
47
+ function checkFlow(flow) {
48
+ const problems = [];
49
+ /*
50
+ * ── 축 이름부터 본다 ──────────────────────────────────────────────────
51
+ * 이것이 없던 동안 오타 축이 모든 검사를 지나 **영원히 빈 칸**으로 현장에 섰다. 미리보기에서도
52
+ * 「지금 값이 없는 정상 축」과 똑같이 보여서, 벤치에 걸기 전에 알 방법이 없었다 — 이 저작면이
53
+ * 없애려던 바로 그 혼동이다.
54
+ */
55
+ for (const step of flow.steps) {
56
+ for (const axis of step.reads ?? []) {
57
+ if (!(0, station_axes_js_1.isStationAxis)(axis)) {
58
+ problems.push(unknownAxis(step, axis));
59
+ }
60
+ }
61
+ for (const [field, source] of Object.entries(step.arguments ?? {})) {
62
+ if (source.from === 'context' && !(0, station_axes_js_1.isStationAxis)(source.axis)) {
63
+ problems.push({ ...unknownAxis(step, source.axis), field });
64
+ }
65
+ }
66
+ }
67
+ const byId = new Map(flow.steps.map(step => [step.id, step]));
68
+ /* 차례가 뜻을 갖는다 — 값을 받는 자리는 그 값을 쓰는 자리보다 앞에 있어야 한다. */
69
+ const order = flow.steps.map(step => step.id);
70
+ for (const step of flow.steps) {
71
+ if (!step.service) {
72
+ /*
73
+ * A step that writes nothing has nothing to check here. It is not an error to have one —
74
+ * a screen that only shows is a perfectly good screen.
75
+ */
76
+ continue;
77
+ }
78
+ const sources = step.arguments ?? {};
79
+ for (const field of step.service.fields) {
80
+ const source = sources[field.name];
81
+ if (!source) {
82
+ if (field.required) {
83
+ problems.push({
84
+ level: 'error',
85
+ stepId: step.id,
86
+ field: field.name,
87
+ code: 'text.check-required-has-no-source',
88
+ params: { step: label(step), field: field.name },
89
+ says: `「${label(step)}」의 필수 칸 ${field.name} 에 출처가 없습니다 — 누르면 실패합니다`
90
+ });
91
+ }
92
+ else if (field.notable && field.name === 'goodCount') {
93
+ /*
94
+ * 양품 수는 **결과가 구체적이다.** 이 작업대가 지시의 마지막 공정이면 서버가 수 없는 완료를 거절한다
95
+ * (`report-completed-without-counts`). 저작 시점에는 어느 공정의 작업대인지 모르므로 경고로 두되,
96
+ * 막연한 말 대신 그 결과를 말한다(아키텍트 판정 2026-09-17).
97
+ */
98
+ problems.push({
99
+ level: 'warning',
100
+ stepId: step.id,
101
+ field: field.name,
102
+ code: 'text.check-final-count-has-no-source',
103
+ params: { step: label(step) },
104
+ says: `「${label(step)}」을 눌러도 양품 수 없이 기록됩니다. 이 작업대가 지시의 마지막 공정이면 서버가 완료를 거절합니다. 양품 수를 받는 단계를 연결하십시오.`
105
+ });
106
+ }
107
+ else if (field.notable) {
108
+ problems.push({
109
+ level: 'warning',
110
+ stepId: step.id,
111
+ field: field.name,
112
+ code: 'text.check-notable-has-no-source',
113
+ params: { step: label(step), field: field.name },
114
+ says: `「${label(step)}」을 눌러도 ${field.name} 없이 기록됩니다. 비워 두면 0 이 아니라 적지 않은 것으로 남습니다.`
115
+ });
116
+ }
117
+ continue;
118
+ }
119
+ /*
120
+ * An argument can name a step that is not there, or one that never asks anybody for anything.
121
+ * Both look fine in the editor and both fail on the bench.
122
+ */
123
+ if (source.from === 'input') {
124
+ const feeding = byId.get(source.stepId);
125
+ if (!feeding) {
126
+ problems.push({
127
+ level: 'error',
128
+ stepId: step.id,
129
+ field: field.name,
130
+ code: 'text.check-source-step-gone',
131
+ params: { field: field.name },
132
+ says: `${field.name} 이 지워진 단계에서 값을 받게 되어 있습니다 — 그 단계를 다시 세우거나 출처를 바꾸십시오`
133
+ });
134
+ }
135
+ else if (feeding.kind === 'enter' && order.indexOf(feeding.id) > order.indexOf(step.id)) {
136
+ /*
137
+ * The pad comes **after** the button that reads it.
138
+ *
139
+ * Nothing about this is wrong to the type system and the argument does point at a real
140
+ * entry step, so every other check here passes. On the bench the operator presses finish
141
+ * and only then reaches the box, and the count they type lands nowhere — the report is
142
+ * already filed. Reordering steps is what makes this reachable, which is why it is here.
143
+ */
144
+ problems.push({
145
+ level: 'error',
146
+ stepId: step.id,
147
+ field: field.name,
148
+ code: 'text.check-source-comes-later',
149
+ params: { field: field.name, from: label(feeding) },
150
+ says: `${field.name} 이 「${label(feeding)}」에서 값을 받는데 그 단계가 이 단계보다 뒤에 있습니다. 값을 주는 단계를 앞으로 옮기십시오.`
151
+ });
152
+ }
153
+ else if (feeding.kind !== 'enter') {
154
+ problems.push({
155
+ level: 'error',
156
+ stepId: step.id,
157
+ field: field.name,
158
+ code: 'text.check-source-does-not-ask',
159
+ params: { field: field.name, from: label(feeding) },
160
+ says: `${field.name} 이 「${label(feeding)}」에서 값을 받는데, 그 단계는 값을 받는 자리가 아닙니다`
161
+ });
162
+ }
163
+ }
164
+ }
165
+ /* An argument mapped to a field the service does not have — usually a renamed service. */
166
+ const known = new Set(step.service.fields.map(field => field.name));
167
+ for (const name of Object.keys(sources)) {
168
+ if (!known.has(name)) {
169
+ problems.push({
170
+ level: 'error',
171
+ stepId: step.id,
172
+ field: name,
173
+ code: 'text.check-service-has-no-such-field',
174
+ params: { service: step.service.name, field: name },
175
+ says: `${step.service.name} 에 ${name} 칸이 없습니다 — 서비스가 바뀌었는지 보십시오`
176
+ });
177
+ }
178
+ }
179
+ }
180
+ /*
181
+ * A number pad nobody reads.
182
+ *
183
+ * Not an error — the screen works. But somebody put it there meaning it to go somewhere, and
184
+ * on the bench an operator will type into it and watch it do nothing.
185
+ */
186
+ const consumed = new Set();
187
+ for (const step of flow.steps) {
188
+ for (const source of Object.values(step.arguments ?? {})) {
189
+ if (source.from === 'input') {
190
+ consumed.add(source.stepId);
191
+ }
192
+ }
193
+ }
194
+ for (const step of flow.steps) {
195
+ if (step.kind === 'enter' && !consumed.has(step.id)) {
196
+ problems.push({
197
+ level: 'warning',
198
+ stepId: step.id,
199
+ code: 'text.check-nobody-reads-this-entry',
200
+ params: { step: label(step) },
201
+ says: `「${label(step)}」에 넣은 값을 쓰는 단계가 없습니다 — 작업자가 적어도 아무 데도 안 갑니다`
202
+ });
203
+ }
204
+ }
205
+ return problems;
206
+ }
207
+ /**
208
+ * 이 단계를 사람에게 뭐라고 부르나.
209
+ *
210
+ * ── 만들어진 id 를 문장에 넣지 않는다 ────────────────────────────────────
211
+ * 예전에는 이름표가 없으면 `step.id` 로 떨어졌다. 그래서 저작자가 「수량 입력」을 막 더한 순간
212
+ * 「step-1789052869630-2 에 넣은 값을 쓰는 단계가 없습니다」를 읽었다 — 자기가 방금 누른 것이
213
+ * 그것인 줄 알 방법이 없다. 이름표를 아직 안 적은 것은 흔한 일이고, 잘못도 아니다.
214
+ *
215
+ * 이름표가 없으면 **그 단계가 무슨 종류인지**로 부른다. 화면의 목록에도 같은 낱말이 서 있어서
216
+ * 저작자가 눈으로 짚을 수 있다. 몇 번째인지는 화면이 앞에 붙인다 — 차례는 화면이 알고 있다.
217
+ */
218
+ const KIND_SAID = {
219
+ scan: '사번 스캔',
220
+ show: '값 보여주기',
221
+ enter: '수량 입력',
222
+ run: '실행 단추',
223
+ settings: '화면 설정',
224
+ legacy: '보존된 표준 블록'
225
+ };
226
+ function label(step) {
227
+ return step.label?.trim() || KIND_SAID[step.kind];
228
+ }
229
+ /**
230
+ * 모르는 축 하나 — **무엇이 맞는지까지 말한다.**
231
+ *
232
+ * 「그런 축은 없습니다」에서 멈추면 저작자에게 틀렸다는 것만 알리고 남은 일은 그대로 남긴다. 이름이
233
+ * 쉰다섯이라 그 남은 일이 대부분이다. 한 글자 틀린 것이 흔한 경우이고, 그것이 답할 값어치가 있는
234
+ * 경우다.
235
+ */
236
+ function unknownAxis(step, axis) {
237
+ const near = (0, station_axes_js_1.nearestAxes)(axis);
238
+ const said = axis.trim() ? axis : '(빈 이름)';
239
+ return near.length
240
+ ? {
241
+ level: 'error',
242
+ stepId: step.id,
243
+ code: 'text.check-unknown-axis-did-you-mean',
244
+ params: { axis: said, near: near.join(' · ') },
245
+ says: `${said} 라는 값은 없습니다 — 혹시 ${near.join(' · ')} 입니까`
246
+ }
247
+ : {
248
+ level: 'error',
249
+ stepId: step.id,
250
+ code: 'text.check-unknown-axis',
251
+ params: { axis: said },
252
+ says: `${said} 라는 값은 없습니다 — 고를 수 있는 것에서 골라 주십시오`
253
+ };
254
+ }
255
+ //# sourceMappingURL=station-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"station-flow.js","sourceRoot":"","sources":["../../../server/service/station/station-flow.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a station screen does — **the flow, and the checks that read it.**\n *\n * Pure on purpose. The value this authoring surface sells is catching a mistake before the screen\n * hangs on a bench, and a check nobody can run without a database and a logged-in browser is a\n * check nobody runs.\n *\n * ── Why a flow and not a list of fields ────────────────────────────────────\n * Half of a bench screen is not display. The operator scans, starts, counts, records a stop reason,\n * finishes — each of those reads something and most of them **write** something. A design that only\n * describes what is shown cannot say whether the finish button will produce a usable record.\n *\n * ── Where the safety comes from ────────────────────────────────────────────\n * Not from the prompt, and not from care. From three closed sets — the step kinds below, the\n * context axes (`stationBindingNames()` is their only source), and the roles — plus the shape of\n * the service being called, which comes from the schema rather than from anybody's memory.\n */\n\nimport { isStationAxis, nearestAxes } from './station-axes.js'\n\n/**\n * What a step is, as the operator experiences it.\n *\n * Five, and deliberately not one per scene component. These are **the unit the checks ask about** —\n * \"does the run step have a source for every required argument\", \"is there an enter step feeding\n * it\" — and those questions cannot be asked of ten drawing parts. How a step is drawn is a separate\n * matter that the layout decides.\n */\nexport type StationStepKind =\n /** Reads a badge or a label and changes what the station knows. Writes nothing. */\n | 'scan'\n /** Shows values. Reads only. */\n | 'show'\n /** The operator puts a number in. This is what an argument can take its value from. */\n | 'enter'\n /** Calls a mutation. Decides `status` and the clock fields from its own meaning. */\n | 'run'\n /** Not drawn. How often this screen reads, and what it reads. */\n | 'settings'\n /** A standard display block retained while a pre-Composer screen is migrated. */\n | 'legacy'\n\n/**\n * Where an argument's value comes from. **Three, and no others.**\n *\n * A field with no source is the defect this module exists to name.\n */\nexport type StationArgumentSource =\n /** The harness already knows it — equipment, operation, the scanned person. */\n | { from: 'context'; axis: string }\n /** The step's own meaning decides it — `status`, `endTime`. */\n | { from: 'step' }\n /** A person put it in, at the named step. */\n | { from: 'input'; stepId: string }\n /** Written down here on purpose. Rare, and visible when it happens. */\n | { from: 'fixed'; value: string | number | boolean }\n\n/** One argument of the service a step calls, as the schema describes it. */\nexport interface StationServiceField {\n name: string\n /** What the field's own owner wrote about it, when they wrote anything. Shown instead of the name. */\n describes?: string\n /** `!` in the schema. Without a source the call fails. */\n required: boolean\n /**\n * Absent means \"nobody wrote it down\", not zero.\n *\n * `ProductionReportPatch.goodCount` is the case this exists for: it is not required, so a finish\n * button with no number pad is syntactically fine and produces a report that reads as if nothing\n * was made. Which fields are like that is domain knowledge, so it is **told to this module**\n * rather than guessed by it.\n */\n notable?: boolean\n}\n\n/** The mutation a step calls, in the shape the schema gives. */\nexport interface StationService {\n /** The mutation name, exactly as in the schema. */\n name: string\n fields: StationServiceField[]\n}\n\nexport interface StationStep {\n id: string\n kind: StationStepKind\n /** 실행 의미. 이름표를 바꾸어도 수행 업무는 바뀌지 않는다. */\n action?: 'start' | 'finish' | 'pause' | 'resume'\n /** What the operator sees on it. Absent means the axis name is used — never nothing. */\n label?: string\n /** Context axes this step displays. Names come from `stationBindingNames()`. */\n reads?: string[]\n /**\n * For `settings` steps — how often the screen re-reads.\n *\n * Nothing is drawn for it, but it has to survive into the model: without a home here the value\n * a generator computes from the operation's duration is dropped on the way to the board, and\n * every screen ends up on the same default with nothing saying why.\n */\n refreshSeconds?: number\n /** For `run` steps. */\n service?: StationService\n /** Field name → where its value comes from. Only meaningful with a service. */\n arguments?: Record<string, StationArgumentSource>\n /** A rendered Station block not yet expressible by the five authoring step kinds. */\n legacyPart?: Record<string, unknown>\n}\n\nexport interface StationFlow {\n steps: StationStep[]\n}\n\nexport type StationProblemLevel = 'error' | 'warning'\n\nexport interface StationProblem {\n level: StationProblemLevel\n /** Which step, when it is about one. */\n stepId?: string\n /** Which argument, when it is about one. */\n field?: string\n /**\n * Which check this is — the screen says it in the reader's language.\n *\n * ── Why a code after all ────────────────────────────────────────────────\n * This used to be prose only, on the reasoning that an author reads it and nothing matches on\n * it. That held right up until the composer put it on screen: the panel was in English and\n * these sentences came out in Korean, because the server had written them. A sentence the\n * server composes can only ever be in one language.\n *\n * So the server states **which** check failed and **about what**; the screen says it.\n */\n code: string\n /** Values to fill into that sentence — a field name, a step's name, a service name. */\n params?: Record<string, string>\n /**\n * The same thing in Korean, composed here.\n *\n * Not for the screen. This is what `npm run test:station` and the logs print, where there is\n * no i18n and no reader to ask.\n */\n says: string\n}\n\n/**\n * Every context axis this flow reads.\n *\n * This is what `StationScreenSpec.requires` is filled from — derived, never typed. An author who\n * has to keep a list in step with the screen will not, and then the assignment check is checking\n * something that used to be true.\n */\nexport function axesRead(flow: StationFlow): string[] {\n const seen = new Set<string>()\n\n for (const step of flow.steps) {\n step.reads?.forEach(axis => seen.add(axis))\n\n for (const source of Object.values(step.arguments ?? {})) {\n if (source.from === 'context') {\n seen.add(source.axis)\n }\n }\n }\n\n return [...seen].sort()\n}\n\n/**\n * What is wrong with this flow — **from the flow and the schema alone.**\n *\n * No database, no running station. Everything here is answerable at the moment somebody is drawing.\n */\nexport function checkFlow(flow: StationFlow): StationProblem[] {\n const problems: StationProblem[] = []\n\n /*\n * ── 축 이름부터 본다 ──────────────────────────────────────────────────\n * 이것이 없던 동안 오타 축이 모든 검사를 지나 **영원히 빈 칸**으로 현장에 섰다. 미리보기에서도\n * 「지금 값이 없는 정상 축」과 똑같이 보여서, 벤치에 걸기 전에 알 방법이 없었다 — 이 저작면이\n * 없애려던 바로 그 혼동이다.\n */\n for (const step of flow.steps) {\n for (const axis of step.reads ?? []) {\n if (!isStationAxis(axis)) {\n problems.push(unknownAxis(step, axis))\n }\n }\n\n for (const [field, source] of Object.entries(step.arguments ?? {})) {\n if (source.from === 'context' && !isStationAxis(source.axis)) {\n problems.push({ ...unknownAxis(step, source.axis), field })\n }\n }\n }\n const byId = new Map(flow.steps.map(step => [step.id, step]))\n /* 차례가 뜻을 갖는다 — 값을 받는 자리는 그 값을 쓰는 자리보다 앞에 있어야 한다. */\n const order = flow.steps.map(step => step.id)\n\n for (const step of flow.steps) {\n if (!step.service) {\n /*\n * A step that writes nothing has nothing to check here. It is not an error to have one —\n * a screen that only shows is a perfectly good screen.\n */\n continue\n }\n\n const sources = step.arguments ?? {}\n\n for (const field of step.service.fields) {\n const source = sources[field.name]\n\n if (!source) {\n if (field.required) {\n problems.push({\n level: 'error',\n stepId: step.id,\n field: field.name,\n code: 'text.check-required-has-no-source',\n params: { step: label(step), field: field.name },\n says: `「${label(step)}」의 필수 칸 ${field.name} 에 출처가 없습니다 — 누르면 실패합니다`\n })\n } else if (field.notable && field.name === 'goodCount') {\n /*\n * 양품 수는 **결과가 구체적이다.** 이 작업대가 지시의 마지막 공정이면 서버가 수 없는 완료를 거절한다\n * (`report-completed-without-counts`). 저작 시점에는 어느 공정의 작업대인지 모르므로 경고로 두되,\n * 막연한 말 대신 그 결과를 말한다(아키텍트 판정 2026-09-17).\n */\n problems.push({\n level: 'warning',\n stepId: step.id,\n field: field.name,\n code: 'text.check-final-count-has-no-source',\n params: { step: label(step) },\n says: `「${label(step)}」을 눌러도 양품 수 없이 기록됩니다. 이 작업대가 지시의 마지막 공정이면 서버가 완료를 거절합니다. 양품 수를 받는 단계를 연결하십시오.`\n })\n } else if (field.notable) {\n problems.push({\n level: 'warning',\n stepId: step.id,\n field: field.name,\n code: 'text.check-notable-has-no-source',\n params: { step: label(step), field: field.name },\n says: `「${label(step)}」을 눌러도 ${field.name} 없이 기록됩니다. 비워 두면 0 이 아니라 적지 않은 것으로 남습니다.`\n })\n }\n\n continue\n }\n\n /*\n * An argument can name a step that is not there, or one that never asks anybody for anything.\n * Both look fine in the editor and both fail on the bench.\n */\n if (source.from === 'input') {\n const feeding = byId.get(source.stepId)\n\n if (!feeding) {\n problems.push({\n level: 'error',\n stepId: step.id,\n field: field.name,\n code: 'text.check-source-step-gone',\n params: { field: field.name },\n says: `${field.name} 이 지워진 단계에서 값을 받게 되어 있습니다 — 그 단계를 다시 세우거나 출처를 바꾸십시오`\n })\n } else if (feeding.kind === 'enter' && order.indexOf(feeding.id) > order.indexOf(step.id)) {\n /*\n * The pad comes **after** the button that reads it.\n *\n * Nothing about this is wrong to the type system and the argument does point at a real\n * entry step, so every other check here passes. On the bench the operator presses finish\n * and only then reaches the box, and the count they type lands nowhere — the report is\n * already filed. Reordering steps is what makes this reachable, which is why it is here.\n */\n problems.push({\n level: 'error',\n stepId: step.id,\n field: field.name,\n code: 'text.check-source-comes-later',\n params: { field: field.name, from: label(feeding) },\n says: `${field.name} 이 「${label(feeding)}」에서 값을 받는데 그 단계가 이 단계보다 뒤에 있습니다. 값을 주는 단계를 앞으로 옮기십시오.`\n })\n } else if (feeding.kind !== 'enter') {\n problems.push({\n level: 'error',\n stepId: step.id,\n field: field.name,\n code: 'text.check-source-does-not-ask',\n params: { field: field.name, from: label(feeding) },\n says: `${field.name} 이 「${label(feeding)}」에서 값을 받는데, 그 단계는 값을 받는 자리가 아닙니다`\n })\n }\n }\n }\n\n /* An argument mapped to a field the service does not have — usually a renamed service. */\n const known = new Set(step.service.fields.map(field => field.name))\n\n for (const name of Object.keys(sources)) {\n if (!known.has(name)) {\n problems.push({\n level: 'error',\n stepId: step.id,\n field: name,\n code: 'text.check-service-has-no-such-field',\n params: { service: step.service.name, field: name },\n says: `${step.service.name} 에 ${name} 칸이 없습니다 — 서비스가 바뀌었는지 보십시오`\n })\n }\n }\n }\n\n /*\n * A number pad nobody reads.\n *\n * Not an error — the screen works. But somebody put it there meaning it to go somewhere, and\n * on the bench an operator will type into it and watch it do nothing.\n */\n const consumed = new Set<string>()\n\n for (const step of flow.steps) {\n for (const source of Object.values(step.arguments ?? {})) {\n if (source.from === 'input') {\n consumed.add(source.stepId)\n }\n }\n }\n\n for (const step of flow.steps) {\n if (step.kind === 'enter' && !consumed.has(step.id)) {\n problems.push({\n level: 'warning',\n stepId: step.id,\n code: 'text.check-nobody-reads-this-entry',\n params: { step: label(step) },\n says: `「${label(step)}」에 넣은 값을 쓰는 단계가 없습니다 — 작업자가 적어도 아무 데도 안 갑니다`\n })\n }\n }\n\n return problems\n}\n\n/**\n * 이 단계를 사람에게 뭐라고 부르나.\n *\n * ── 만들어진 id 를 문장에 넣지 않는다 ────────────────────────────────────\n * 예전에는 이름표가 없으면 `step.id` 로 떨어졌다. 그래서 저작자가 「수량 입력」을 막 더한 순간\n * 「step-1789052869630-2 에 넣은 값을 쓰는 단계가 없습니다」를 읽었다 — 자기가 방금 누른 것이\n * 그것인 줄 알 방법이 없다. 이름표를 아직 안 적은 것은 흔한 일이고, 잘못도 아니다.\n *\n * 이름표가 없으면 **그 단계가 무슨 종류인지**로 부른다. 화면의 목록에도 같은 낱말이 서 있어서\n * 저작자가 눈으로 짚을 수 있다. 몇 번째인지는 화면이 앞에 붙인다 — 차례는 화면이 알고 있다.\n */\nconst KIND_SAID: Record<StationStepKind, string> = {\n scan: '사번 스캔',\n show: '값 보여주기',\n enter: '수량 입력',\n run: '실행 단추',\n settings: '화면 설정',\n legacy: '보존된 표준 블록'\n}\n\nfunction label(step: StationStep): string {\n return step.label?.trim() || KIND_SAID[step.kind]\n}\n\n/**\n * 모르는 축 하나 — **무엇이 맞는지까지 말한다.**\n *\n * 「그런 축은 없습니다」에서 멈추면 저작자에게 틀렸다는 것만 알리고 남은 일은 그대로 남긴다. 이름이\n * 쉰다섯이라 그 남은 일이 대부분이다. 한 글자 틀린 것이 흔한 경우이고, 그것이 답할 값어치가 있는\n * 경우다.\n */\nfunction unknownAxis(step: StationStep, axis: string): StationProblem {\n const near = nearestAxes(axis)\n const said = axis.trim() ? axis : '(빈 이름)'\n\n return near.length\n ? {\n level: 'error',\n stepId: step.id,\n code: 'text.check-unknown-axis-did-you-mean',\n params: { axis: said, near: near.join(' · ') },\n says: `${said} 라는 값은 없습니다 — 혹시 ${near.join(' · ')} 입니까`\n }\n : {\n level: 'error',\n stepId: step.id,\n code: 'text.check-unknown-axis',\n params: { axis: said },\n says: `${said} 라는 값은 없습니다 — 고를 수 있는 것에서 골라 주십시오`\n }\n}\n"]}