@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.
- package/README.md +124 -0
- package/dist-client/index.d.ts +11 -0
- package/dist-client/index.js +13 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/station/scene/index.d.ts +11 -0
- package/dist-client/station/scene/index.js +12 -0
- package/dist-client/station/scene/index.js.map +1 -0
- package/dist-client/station/scene/station-context-binding.d.ts +25 -0
- package/dist-client/station/scene/station-context-binding.js +39 -0
- package/dist-client/station/scene/station-context-binding.js.map +1 -0
- package/dist-client/station/scene/station-context-scene.d.ts +28 -0
- package/dist-client/station/scene/station-context-scene.js +138 -0
- package/dist-client/station/scene/station-context-scene.js.map +1 -0
- package/dist-client/station/scene/station-counts-scene.d.ts +6 -0
- package/dist-client/station/scene/station-counts-scene.js +51 -0
- package/dist-client/station/scene/station-counts-scene.js.map +1 -0
- package/dist-client/station/scene/station-field-scene.d.ts +6 -0
- package/dist-client/station/scene/station-field-scene.js +89 -0
- package/dist-client/station/scene/station-field-scene.js.map +1 -0
- package/dist-client/station/scene/station-icons.d.ts +82 -0
- package/dist-client/station/scene/station-icons.js +131 -0
- package/dist-client/station/scene/station-icons.js.map +1 -0
- package/dist-client/station/scene/station-numpad-scene.d.ts +9 -0
- package/dist-client/station/scene/station-numpad-scene.js +144 -0
- package/dist-client/station/scene/station-numpad-scene.js.map +1 -0
- package/dist-client/station/scene/station-scene-style.d.ts +51 -0
- package/dist-client/station/scene/station-scene-style.js +108 -0
- package/dist-client/station/scene/station-scene-style.js.map +1 -0
- package/dist-client/station/scene/station-scene.d.ts +295 -0
- package/dist-client/station/scene/station-scene.js +68 -0
- package/dist-client/station/scene/station-scene.js.map +1 -0
- package/dist-client/station/scene/station-who-scene.d.ts +6 -0
- package/dist-client/station/scene/station-who-scene.js +48 -0
- package/dist-client/station/scene/station-who-scene.js.map +1 -0
- package/dist-client/station/scene/station-work-panel-scene.d.ts +28 -0
- package/dist-client/station/scene/station-work-panel-scene.js +37 -0
- package/dist-client/station/scene/station-work-panel-scene.js.map +1 -0
- package/dist-client/station/station-board-binding.d.ts +16 -0
- package/dist-client/station/station-board-binding.js +20 -0
- package/dist-client/station/station-board-binding.js.map +1 -0
- package/dist-client/station/station-context.d.ts +178 -0
- package/dist-client/station/station-context.js +30 -0
- package/dist-client/station/station-context.js.map +1 -0
- package/dist-client/station/station-palette.d.ts +34 -0
- package/dist-client/station/station-palette.js +119 -0
- package/dist-client/station/station-palette.js.map +1 -0
- package/dist-client/station/station-runtime.d.ts +47 -0
- package/dist-client/station/station-runtime.js +47 -0
- package/dist-client/station/station-runtime.js.map +1 -0
- package/dist-client/station/station-scene-context.d.ts +68 -0
- package/dist-client/station/station-scene-context.js +62 -0
- package/dist-client/station/station-scene-context.js.map +1 -0
- package/dist-client/station/station-screen-flow.d.ts +16 -0
- package/dist-client/station/station-screen-flow.js +58 -0
- package/dist-client/station/station-screen-flow.js.map +1 -0
- package/dist-client/station/station-tile-registry.d.ts +89 -0
- package/dist-client/station/station-tile-registry.js +86 -0
- package/dist-client/station/station-tile-registry.js.map +1 -0
- package/dist-client/station/station-work-panel-view.d.ts +23 -0
- package/dist-client/station/station-work-panel-view.js +125 -0
- package/dist-client/station/station-work-panel-view.js.map +1 -0
- package/dist-client/station/station-work-runtime.d.ts +47 -0
- package/dist-client/station/station-work-runtime.js +161 -0
- package/dist-client/station/station-work-runtime.js.map +1 -0
- package/dist-client/station/station-work-scenarios.d.ts +41 -0
- package/dist-client/station/station-work-scenarios.js +59 -0
- package/dist-client/station/station-work-scenarios.js.map +1 -0
- package/dist-client/station/task-counts.d.ts +32 -0
- package/dist-client/station/task-counts.js +26 -0
- package/dist-client/station/task-counts.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/activities/activity-station-screen-control-withdrawal.d.ts +62 -0
- package/dist-server/activities/activity-station-screen-control-withdrawal.js +147 -0
- package/dist-server/activities/activity-station-screen-control-withdrawal.js.map +1 -0
- package/dist-server/activities/activity-station-screen-release.d.ts +81 -0
- package/dist-server/activities/activity-station-screen-release.js +147 -0
- package/dist-server/activities/activity-station-screen-release.js.map +1 -0
- package/dist-server/activities/index.d.ts +49 -0
- package/dist-server/activities/index.js +21 -0
- package/dist-server/activities/index.js.map +1 -0
- package/dist-server/index.d.ts +3 -0
- package/dist-server/index.js +8 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/service/index.d.ts +23 -0
- package/dist-server/service/index.js +41 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/station/deletion-changes.d.ts +14 -0
- package/dist-server/service/station/deletion-changes.js +25 -0
- package/dist-server/service/station/deletion-changes.js.map +1 -0
- package/dist-server/service/station/index.d.ts +13 -0
- package/dist-server/service/station/index.js +25 -0
- package/dist-server/service/station/index.js.map +1 -0
- package/dist-server/service/station/reversibility.d.ts +21 -0
- package/dist-server/service/station/reversibility.js +24 -0
- package/dist-server/service/station/reversibility.js.map +1 -0
- package/dist-server/service/station/screen-model-digest.d.ts +60 -0
- package/dist-server/service/station/screen-model-digest.js +97 -0
- package/dist-server/service/station/screen-model-digest.js.map +1 -0
- package/dist-server/service/station/staged-material.d.ts +62 -0
- package/dist-server/service/station/staged-material.js +44 -0
- package/dist-server/service/station/staged-material.js.map +1 -0
- package/dist-server/service/station/station-argument-sources.d.ts +31 -0
- package/dist-server/service/station/station-argument-sources.js +66 -0
- package/dist-server/service/station/station-argument-sources.js.map +1 -0
- package/dist-server/service/station/station-assignment-choice.d.ts +81 -0
- package/dist-server/service/station/station-assignment-choice.js +105 -0
- package/dist-server/service/station/station-assignment-choice.js.map +1 -0
- package/dist-server/service/station/station-assignment-resolver.d.ts +104 -0
- package/dist-server/service/station/station-assignment-resolver.js +426 -0
- package/dist-server/service/station/station-assignment-resolver.js.map +1 -0
- package/dist-server/service/station/station-assignment-service.d.ts +33 -0
- package/dist-server/service/station/station-assignment-service.js +64 -0
- package/dist-server/service/station/station-assignment-service.js.map +1 -0
- package/dist-server/service/station/station-axes.d.ts +9 -0
- package/dist-server/service/station/station-axes.js +16 -0
- package/dist-server/service/station/station-axes.js.map +1 -0
- package/dist-server/service/station/station-flow.d.ts +156 -0
- package/dist-server/service/station/station-flow.js +255 -0
- package/dist-server/service/station/station-flow.js.map +1 -0
- package/dist-server/service/station/station-generate.d.ts +76 -0
- package/dist-server/service/station/station-generate.js +178 -0
- package/dist-server/service/station/station-generate.js.map +1 -0
- package/dist-server/service/station/station-product.d.ts +123 -0
- package/dist-server/service/station/station-product.js +77 -0
- package/dist-server/service/station/station-product.js.map +1 -0
- package/dist-server/service/station/station-runtime-binding.d.ts +6 -0
- package/dist-server/service/station/station-runtime-binding.js +46 -0
- package/dist-server/service/station/station-runtime-binding.js.map +1 -0
- package/dist-server/service/station/station-screen-assignment.d.ts +94 -0
- package/dist-server/service/station/station-screen-assignment.js +154 -0
- package/dist-server/service/station/station-screen-assignment.js.map +1 -0
- package/dist-server/service/station/station-screen-control-withdrawal-resolver.d.ts +37 -0
- package/dist-server/service/station/station-screen-control-withdrawal-resolver.js +130 -0
- package/dist-server/service/station/station-screen-control-withdrawal-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-control-withdrawal.d.ts +104 -0
- package/dist-server/service/station/station-screen-control-withdrawal.js +128 -0
- package/dist-server/service/station/station-screen-control-withdrawal.js.map +1 -0
- package/dist-server/service/station/station-screen-draft-resolver.d.ts +47 -0
- package/dist-server/service/station/station-screen-draft-resolver.js +153 -0
- package/dist-server/service/station/station-screen-draft-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-layout.d.ts +119 -0
- package/dist-server/service/station/station-screen-layout.js +202 -0
- package/dist-server/service/station/station-screen-layout.js.map +1 -0
- package/dist-server/service/station/station-screen-list-resolver.d.ts +69 -0
- package/dist-server/service/station/station-screen-list-resolver.js +215 -0
- package/dist-server/service/station/station-screen-list-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-publish-subscriber.d.ts +22 -0
- package/dist-server/service/station/station-screen-publish-subscriber.js +90 -0
- package/dist-server/service/station/station-screen-publish-subscriber.js.map +1 -0
- package/dist-server/service/station/station-screen-release-gate.d.ts +66 -0
- package/dist-server/service/station/station-screen-release-gate.js +67 -0
- package/dist-server/service/station/station-screen-release-gate.js.map +1 -0
- package/dist-server/service/station/station-screen-release-record.d.ts +62 -0
- package/dist-server/service/station/station-screen-release-record.js +139 -0
- package/dist-server/service/station/station-screen-release-record.js.map +1 -0
- package/dist-server/service/station/station-screen-release-signature.d.ts +23 -0
- package/dist-server/service/station/station-screen-release-signature.js +32 -0
- package/dist-server/service/station/station-screen-release-signature.js.map +1 -0
- package/dist-server/service/station/station-screen-scorecard.d.ts +15 -0
- package/dist-server/service/station/station-screen-scorecard.js +35 -0
- package/dist-server/service/station/station-screen-scorecard.js.map +1 -0
- package/dist-server/service/station/station-screen-spec-resolver.d.ts +36 -0
- package/dist-server/service/station/station-screen-spec-resolver.js +202 -0
- package/dist-server/service/station/station-screen-spec-resolver.js.map +1 -0
- package/dist-server/service/station/station-screen-spec.d.ts +104 -0
- package/dist-server/service/station/station-screen-spec.js +114 -0
- package/dist-server/service/station/station-screen-spec.js.map +1 -0
- package/dist-server/service/station/station-screen-thumbnail.d.ts +11 -0
- package/dist-server/service/station/station-screen-thumbnail.js +36 -0
- package/dist-server/service/station/station-screen-thumbnail.js.map +1 -0
- package/dist-server/service/station/station-service-resolver.d.ts +27 -0
- package/dist-server/service/station/station-service-resolver.js +95 -0
- package/dist-server/service/station/station-service-resolver.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +44 -0
- package/shared/station-axes.d.ts +4 -0
- package/shared/station-axes.js +155 -0
- package/shared/station-screen-flow-check.d.ts +12 -0
- package/shared/station-screen-flow-check.js +74 -0
- package/things-scene.config.js +15 -0
- package/translations/en.json +42 -0
- package/translations/ja.json +42 -0
- package/translations/ko.json +42 -0
- package/translations/ms.json +42 -0
- package/translations/zh.json +42 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { StationFlow, StationServiceField } from './station-flow.js';
|
|
2
|
+
/**
|
|
3
|
+
* A station screen from what the factory already declares — **rules, not inference.**
|
|
4
|
+
*
|
|
5
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
* ## Why a rule generator comes before an AI one
|
|
7
|
+
*
|
|
8
|
+
* Filling in a service's required fields is **arithmetic, not judgement**. The equipment is the
|
|
9
|
+
* station's equipment; the operation is the operation; `status` is what the button means. There is
|
|
10
|
+
* exactly one right answer and it is derivable, so a model that guesses it can only be right by
|
|
11
|
+
* accident and wrong without saying so.
|
|
12
|
+
*
|
|
13
|
+
* What is left for a sentence in plain language — «검사도 같이 하는 자리다» — is which *services*
|
|
14
|
+
* belong here. That is the part worth asking a person, or eventually a model, and it is a small
|
|
15
|
+
* closed choice rather than an open one.
|
|
16
|
+
*
|
|
17
|
+
* ## What it leaves out is part of the output
|
|
18
|
+
*
|
|
19
|
+
* A generator that quietly omits the inspection step leaves the author thinking they were given
|
|
20
|
+
* everything. They add it by hand, and it goes to the floor as a box that never fills, because the
|
|
21
|
+
* equipment has no test specification behind it. So every omission is returned with its reason.
|
|
22
|
+
*
|
|
23
|
+
* ## It closes the loop it opens
|
|
24
|
+
*
|
|
25
|
+
* `goodCount` has no automatic source — a person types it. The generator is the one creating the
|
|
26
|
+
* number pad, so it is also the one that can point the argument at it. A generator that produced
|
|
27
|
+
* the pad and left the wiring undone would hand the author the exact defect this module was built
|
|
28
|
+
* to warn about.
|
|
29
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
30
|
+
*/
|
|
31
|
+
/** What the factory says about this station. Gathered by the resolver; this module reads only. */
|
|
32
|
+
export interface StationFacts {
|
|
33
|
+
equipmentName: string;
|
|
34
|
+
/** Where it stands. Drawn so an operator can tell one bench from the next. */
|
|
35
|
+
locationName?: string;
|
|
36
|
+
/** The operation this bench runs, when the assignment names one. */
|
|
37
|
+
operationKey?: string;
|
|
38
|
+
/** ISO 8601, e.g. `PT392S`. Decides how often the screen re-reads. */
|
|
39
|
+
operationDuration?: string;
|
|
40
|
+
/** Test specifications the equipment carries. Empty means no inspection step. */
|
|
41
|
+
testSpecifications: string[];
|
|
42
|
+
/** Stop reasons this factory declares. Empty means a pause button with no reason to give. */
|
|
43
|
+
stopReasons: string[];
|
|
44
|
+
/** Whether any job order names this operation. Decides whether order fields are worth drawing. */
|
|
45
|
+
hasJobOrders: boolean;
|
|
46
|
+
/** Every service a step could call, with its fields — the schema's own list. */
|
|
47
|
+
services: {
|
|
48
|
+
name: string;
|
|
49
|
+
fields: StationServiceField[];
|
|
50
|
+
}[];
|
|
51
|
+
}
|
|
52
|
+
/** Something the generator did not put in, and why. The screen says it in the reader's language. */
|
|
53
|
+
export interface StationOmission {
|
|
54
|
+
code: string;
|
|
55
|
+
params?: Record<string, string>;
|
|
56
|
+
/** The same thing in Korean, for logs and the scenario tool. */
|
|
57
|
+
says: string;
|
|
58
|
+
}
|
|
59
|
+
export interface StationGenerated {
|
|
60
|
+
flow: StationFlow;
|
|
61
|
+
omitted: StationOmission[];
|
|
62
|
+
}
|
|
63
|
+
export declare function generateFlow(facts: StationFacts): StationGenerated;
|
|
64
|
+
/**
|
|
65
|
+
* How often to re-read, from how long the work takes.
|
|
66
|
+
*
|
|
67
|
+
* ── Why not one number for everybody ─────────────────────────────────────
|
|
68
|
+
* A 30-second cut and a 392-second assembly are different screens. Reading every 5 seconds at the
|
|
69
|
+
* assembly bench is forty pointless round trips per piece across a plant of twelve benches; reading
|
|
70
|
+
* every 30 at the cutter means the count is a whole piece behind for most of the cycle.
|
|
71
|
+
*
|
|
72
|
+
* Roughly forty reads across one cycle, held between 5 and 60 seconds. The bounds matter more than
|
|
73
|
+
* the ratio: below 5 the screen is a load generator, above 60 it looks frozen to somebody standing
|
|
74
|
+
* in front of it.
|
|
75
|
+
*/
|
|
76
|
+
export declare function refreshFor(duration?: string): number;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateFlow = generateFlow;
|
|
4
|
+
exports.refreshFor = refreshFor;
|
|
5
|
+
const station_argument_sources_js_1 = require("./station-argument-sources.js");
|
|
6
|
+
const GOOD = 'good';
|
|
7
|
+
const SCRAP = 'scrap';
|
|
8
|
+
function generateFlow(facts) {
|
|
9
|
+
const steps = [];
|
|
10
|
+
const omitted = [];
|
|
11
|
+
/*
|
|
12
|
+
* Who is at the bench, first and always.
|
|
13
|
+
*
|
|
14
|
+
* Without it every later fact is unattributed — the counts belong to nobody, and a report filed
|
|
15
|
+
* that way cannot answer the one question anybody asks it later.
|
|
16
|
+
*/
|
|
17
|
+
steps.push({ id: 'who', kind: 'scan', label: '작업자' });
|
|
18
|
+
/* Where am I, and on what. Only axes the facts actually support. */
|
|
19
|
+
const reads = ['station.equipment'];
|
|
20
|
+
if (facts.locationName) {
|
|
21
|
+
reads.push('location.name');
|
|
22
|
+
}
|
|
23
|
+
if (facts.hasJobOrders) {
|
|
24
|
+
reads.push('order.name', 'materialDefinition.name');
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
omitted.push({
|
|
28
|
+
code: 'text.omitted-order-fields',
|
|
29
|
+
says: '작업지시 칸을 넣지 않았습니다 — 이 공정에 걸린 작업지시가 아직 없습니다'
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
steps.push({ id: 'here', kind: 'show', label: '지금 이 자리', reads });
|
|
33
|
+
/* The two counts. Both are `notable` on a production report, and neither has an automatic source. */
|
|
34
|
+
steps.push({ id: GOOD, kind: 'enter', label: '양품 수' });
|
|
35
|
+
steps.push({ id: SCRAP, kind: 'enter', label: '불량 수' });
|
|
36
|
+
const report = facts.services.find(one => one.name === 'reportProduction');
|
|
37
|
+
steps.push({ id: 'start', kind: 'run', label: '시작', action: 'start' });
|
|
38
|
+
if (report) {
|
|
39
|
+
steps.push({
|
|
40
|
+
id: 'finish',
|
|
41
|
+
action: 'finish',
|
|
42
|
+
kind: 'run',
|
|
43
|
+
label: '완료',
|
|
44
|
+
service: report,
|
|
45
|
+
arguments: wire(report.fields, { goodCount: GOOD, nonconformingCount: SCRAP })
|
|
46
|
+
});
|
|
47
|
+
/*
|
|
48
|
+
* `materialActual` is left alone on purpose. What went in and how much is a recipe's business,
|
|
49
|
+
* and guessing it from an equipment declaration would put a box on the screen that an operator
|
|
50
|
+
* has to fill from memory.
|
|
51
|
+
*/
|
|
52
|
+
if (report.fields.some(field => field.name === 'materialActual')) {
|
|
53
|
+
omitted.push({
|
|
54
|
+
code: 'text.omitted-material-actual',
|
|
55
|
+
says: '자재 실적을 넣지 않았습니다 — 무엇이 얼마나 들어갔는지는 레시피가 정하는 것이라 설비 선언에서 끌어올 수 없습니다'
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
/* No production report service at all is a broken catalogue, not an ordinary omission. */
|
|
61
|
+
omitted.push({
|
|
62
|
+
code: 'text.omitted-no-report-service',
|
|
63
|
+
says: '완료 단추가 아무것도 기록하지 않습니다 — reportProduction 을 카탈로그에서 못 찾았습니다'
|
|
64
|
+
});
|
|
65
|
+
steps.push({ id: 'finish', kind: 'run', label: '완료', action: 'finish' });
|
|
66
|
+
}
|
|
67
|
+
/* Stopping, and saying why. A pause button with no reason to give records nothing useful. */
|
|
68
|
+
const stopping = facts.services.find(one => one.name === 'recordEquipmentStatePeriod');
|
|
69
|
+
if (stopping && facts.stopReasons.length) {
|
|
70
|
+
steps.push({
|
|
71
|
+
id: 'pause',
|
|
72
|
+
action: 'pause',
|
|
73
|
+
kind: 'run',
|
|
74
|
+
label: '일시정지',
|
|
75
|
+
service: stopping,
|
|
76
|
+
arguments: wire(stopping.fields, {})
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else if (!facts.stopReasons.length) {
|
|
80
|
+
omitted.push({
|
|
81
|
+
code: 'text.omitted-no-stop-reasons',
|
|
82
|
+
says: '일시정지 단추를 넣지 않았습니다 — 이 공장에 정지 사유가 하나도 선언되어 있지 않습니다'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/*
|
|
86
|
+
* Inspection, and **only when there is one answer to which specification.**
|
|
87
|
+
*
|
|
88
|
+
* `specId` is required and there is no context axis for it — a bench does not carry "the
|
|
89
|
+
* current test spec" the way it carries its own name. It comes from the equipment declaration,
|
|
90
|
+
* which is known here and nowhere later, so it is written down as a fixed value.
|
|
91
|
+
*
|
|
92
|
+
* With two or more declared, no rule can pick. Generating the step anyway would hand over a
|
|
93
|
+
* button that fails when pressed; generating it without `specId` would hand over the exact
|
|
94
|
+
* defect this module exists to warn about. So it is left out and said out loud.
|
|
95
|
+
*
|
|
96
|
+
* This is why the generator is run end to end and not only unit tested: the first fixture here
|
|
97
|
+
* had no `specId` in it, and every test was green while the real thing produced a broken step.
|
|
98
|
+
*/
|
|
99
|
+
const inspection = facts.services.find(one => one.name === 'recordInspectionResult');
|
|
100
|
+
const specs = facts.testSpecifications;
|
|
101
|
+
if (!specs.length) {
|
|
102
|
+
omitted.push({
|
|
103
|
+
code: 'text.omitted-no-test-spec',
|
|
104
|
+
params: { equipment: facts.equipmentName },
|
|
105
|
+
says: `검사 단계를 넣지 않았습니다 — ${facts.equipmentName} 에 검사 명세가 붙어 있지 않습니다`
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else if (specs.length > 1) {
|
|
109
|
+
omitted.push({
|
|
110
|
+
code: 'text.omitted-many-test-specs',
|
|
111
|
+
params: { equipment: facts.equipmentName, specs: specs.join(' · ') },
|
|
112
|
+
says: `검사 단계를 넣지 않았습니다 — ${facts.equipmentName} 에 검사 명세가 ${specs.length}개(${specs.join(' · ')})라 어느 것인지 규칙으로 정할 수 없습니다`
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else if (inspection) {
|
|
116
|
+
/* 검사 기록 mutation을 보내는 런타임은 아직 없다. 실적으로 대신 기록하지 않는다. */
|
|
117
|
+
omitted.push({
|
|
118
|
+
code: 'text.omitted-no-inspection-runtime',
|
|
119
|
+
says: '검사 단계를 넣지 않았습니다 — 검사 기록 실행 연결이 아직 구현되지 않았습니다'
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/* Not drawn. How often the screen re-reads — computed from how long the work takes. */
|
|
123
|
+
steps.push({ id: 'settings', kind: 'settings', refreshSeconds: refreshFor(facts.operationDuration) });
|
|
124
|
+
return { flow: { steps }, omitted };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Fill in every argument that has one right answer, and point the rest at the steps we just made.
|
|
128
|
+
*
|
|
129
|
+
* A field with no suggestion and no step to take it from is left unset **on purpose** — that is
|
|
130
|
+
* exactly the state `checkFlow` names, and inventing a value here would silence a warning by
|
|
131
|
+
* making it untrue rather than by fixing it.
|
|
132
|
+
*/
|
|
133
|
+
function wire(fields, fromSteps) {
|
|
134
|
+
const wired = {};
|
|
135
|
+
for (const field of fields) {
|
|
136
|
+
const entry = fromSteps[field.name];
|
|
137
|
+
if (entry) {
|
|
138
|
+
wired[field.name] = { from: 'input', stepId: entry };
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const suggested = (0, station_argument_sources_js_1.suggestSource)(field.name);
|
|
142
|
+
if (suggested) {
|
|
143
|
+
wired[field.name] = suggested;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return wired;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* How often to re-read, from how long the work takes.
|
|
150
|
+
*
|
|
151
|
+
* ── Why not one number for everybody ─────────────────────────────────────
|
|
152
|
+
* A 30-second cut and a 392-second assembly are different screens. Reading every 5 seconds at the
|
|
153
|
+
* assembly bench is forty pointless round trips per piece across a plant of twelve benches; reading
|
|
154
|
+
* every 30 at the cutter means the count is a whole piece behind for most of the cycle.
|
|
155
|
+
*
|
|
156
|
+
* Roughly forty reads across one cycle, held between 5 and 60 seconds. The bounds matter more than
|
|
157
|
+
* the ratio: below 5 the screen is a load generator, above 60 it looks frozen to somebody standing
|
|
158
|
+
* in front of it.
|
|
159
|
+
*/
|
|
160
|
+
function refreshFor(duration) {
|
|
161
|
+
const seconds = secondsOf(duration);
|
|
162
|
+
if (!seconds) {
|
|
163
|
+
return 10;
|
|
164
|
+
}
|
|
165
|
+
return Math.min(60, Math.max(5, Math.round(seconds / 40)));
|
|
166
|
+
}
|
|
167
|
+
/** `PT392S` · `PT7M30S` · `PT1H` → seconds. Anything else → nothing, and the caller uses its default. */
|
|
168
|
+
function secondsOf(duration) {
|
|
169
|
+
if (!duration) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
const said = /^PT(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?$/.exec(duration.trim().toUpperCase());
|
|
173
|
+
if (!said || (!said[1] && !said[2] && !said[3])) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
return Number(said[1] ?? 0) * 3600 + Number(said[2] ?? 0) * 60 + Number(said[3] ?? 0);
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=station-generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"station-generate.js","sourceRoot":"","sources":["../../../server/service/station/station-generate.ts"],"names":[],"mappings":"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","sourcesContent":["import { suggestSource } from './station-argument-sources.js'\nimport type { StationArgumentSource, StationFlow, StationServiceField, StationStep } from './station-flow.js'\n\n/**\n * A station screen from what the factory already declares — **rules, not inference.**\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ## Why a rule generator comes before an AI one\n *\n * Filling in a service's required fields is **arithmetic, not judgement**. The equipment is the\n * station's equipment; the operation is the operation; `status` is what the button means. There is\n * exactly one right answer and it is derivable, so a model that guesses it can only be right by\n * accident and wrong without saying so.\n *\n * What is left for a sentence in plain language — «검사도 같이 하는 자리다» — is which *services*\n * belong here. That is the part worth asking a person, or eventually a model, and it is a small\n * closed choice rather than an open one.\n *\n * ## What it leaves out is part of the output\n *\n * A generator that quietly omits the inspection step leaves the author thinking they were given\n * everything. They add it by hand, and it goes to the floor as a box that never fills, because the\n * equipment has no test specification behind it. So every omission is returned with its reason.\n *\n * ## It closes the loop it opens\n *\n * `goodCount` has no automatic source — a person types it. The generator is the one creating the\n * number pad, so it is also the one that can point the argument at it. A generator that produced\n * the pad and left the wiring undone would hand the author the exact defect this module was built\n * to warn about.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** What the factory says about this station. Gathered by the resolver; this module reads only. */\nexport interface StationFacts {\n equipmentName: string\n /** Where it stands. Drawn so an operator can tell one bench from the next. */\n locationName?: string\n /** The operation this bench runs, when the assignment names one. */\n operationKey?: string\n /** ISO 8601, e.g. `PT392S`. Decides how often the screen re-reads. */\n operationDuration?: string\n /** Test specifications the equipment carries. Empty means no inspection step. */\n testSpecifications: string[]\n /** Stop reasons this factory declares. Empty means a pause button with no reason to give. */\n stopReasons: string[]\n /** Whether any job order names this operation. Decides whether order fields are worth drawing. */\n hasJobOrders: boolean\n /** Every service a step could call, with its fields — the schema's own list. */\n services: { name: string; fields: StationServiceField[] }[]\n}\n\n/** Something the generator did not put in, and why. The screen says it in the reader's language. */\nexport interface StationOmission {\n code: string\n params?: Record<string, string>\n /** The same thing in Korean, for logs and the scenario tool. */\n says: string\n}\n\nexport interface StationGenerated {\n flow: StationFlow\n omitted: StationOmission[]\n}\n\nconst GOOD = 'good'\nconst SCRAP = 'scrap'\n\nexport function generateFlow(facts: StationFacts): StationGenerated {\n const steps: StationStep[] = []\n const omitted: StationOmission[] = []\n\n /*\n * Who is at the bench, first and always.\n *\n * Without it every later fact is unattributed — the counts belong to nobody, and a report filed\n * that way cannot answer the one question anybody asks it later.\n */\n steps.push({ id: 'who', kind: 'scan', label: '작업자' })\n\n /* Where am I, and on what. Only axes the facts actually support. */\n const reads = ['station.equipment']\n\n if (facts.locationName) {\n reads.push('location.name')\n }\n\n if (facts.hasJobOrders) {\n reads.push('order.name', 'materialDefinition.name')\n } else {\n omitted.push({\n code: 'text.omitted-order-fields',\n says: '작업지시 칸을 넣지 않았습니다 — 이 공정에 걸린 작업지시가 아직 없습니다'\n })\n }\n\n steps.push({ id: 'here', kind: 'show', label: '지금 이 자리', reads })\n\n /* The two counts. Both are `notable` on a production report, and neither has an automatic source. */\n steps.push({ id: GOOD, kind: 'enter', label: '양품 수' })\n steps.push({ id: SCRAP, kind: 'enter', label: '불량 수' })\n\n const report = facts.services.find(one => one.name === 'reportProduction')\n\n steps.push({ id: 'start', kind: 'run', label: '시작', action: 'start' })\n\n if (report) {\n steps.push({\n id: 'finish',\n action: 'finish',\n kind: 'run',\n label: '완료',\n service: report,\n arguments: wire(report.fields, { goodCount: GOOD, nonconformingCount: SCRAP })\n })\n\n /*\n * `materialActual` is left alone on purpose. What went in and how much is a recipe's business,\n * and guessing it from an equipment declaration would put a box on the screen that an operator\n * has to fill from memory.\n */\n if (report.fields.some(field => field.name === 'materialActual')) {\n omitted.push({\n code: 'text.omitted-material-actual',\n says: '자재 실적을 넣지 않았습니다 — 무엇이 얼마나 들어갔는지는 레시피가 정하는 것이라 설비 선언에서 끌어올 수 없습니다'\n })\n }\n } else {\n /* No production report service at all is a broken catalogue, not an ordinary omission. */\n omitted.push({\n code: 'text.omitted-no-report-service',\n says: '완료 단추가 아무것도 기록하지 않습니다 — reportProduction 을 카탈로그에서 못 찾았습니다'\n })\n\n steps.push({ id: 'finish', kind: 'run', label: '완료', action: 'finish' })\n }\n\n /* Stopping, and saying why. A pause button with no reason to give records nothing useful. */\n const stopping = facts.services.find(one => one.name === 'recordEquipmentStatePeriod')\n\n if (stopping && facts.stopReasons.length) {\n steps.push({\n id: 'pause',\n action: 'pause',\n kind: 'run',\n label: '일시정지',\n service: stopping,\n arguments: wire(stopping.fields, {})\n })\n } else if (!facts.stopReasons.length) {\n omitted.push({\n code: 'text.omitted-no-stop-reasons',\n says: '일시정지 단추를 넣지 않았습니다 — 이 공장에 정지 사유가 하나도 선언되어 있지 않습니다'\n })\n }\n\n /*\n * Inspection, and **only when there is one answer to which specification.**\n *\n * `specId` is required and there is no context axis for it — a bench does not carry \"the\n * current test spec\" the way it carries its own name. It comes from the equipment declaration,\n * which is known here and nowhere later, so it is written down as a fixed value.\n *\n * With two or more declared, no rule can pick. Generating the step anyway would hand over a\n * button that fails when pressed; generating it without `specId` would hand over the exact\n * defect this module exists to warn about. So it is left out and said out loud.\n *\n * This is why the generator is run end to end and not only unit tested: the first fixture here\n * had no `specId` in it, and every test was green while the real thing produced a broken step.\n */\n const inspection = facts.services.find(one => one.name === 'recordInspectionResult')\n const specs = facts.testSpecifications\n\n if (!specs.length) {\n omitted.push({\n code: 'text.omitted-no-test-spec',\n params: { equipment: facts.equipmentName },\n says: `검사 단계를 넣지 않았습니다 — ${facts.equipmentName} 에 검사 명세가 붙어 있지 않습니다`\n })\n } else if (specs.length > 1) {\n omitted.push({\n code: 'text.omitted-many-test-specs',\n params: { equipment: facts.equipmentName, specs: specs.join(' · ') },\n says: `검사 단계를 넣지 않았습니다 — ${facts.equipmentName} 에 검사 명세가 ${specs.length}개(${specs.join(' · ')})라 어느 것인지 규칙으로 정할 수 없습니다`\n })\n } else if (inspection) {\n /* 검사 기록 mutation을 보내는 런타임은 아직 없다. 실적으로 대신 기록하지 않는다. */\n omitted.push({\n code: 'text.omitted-no-inspection-runtime',\n says: '검사 단계를 넣지 않았습니다 — 검사 기록 실행 연결이 아직 구현되지 않았습니다'\n })\n }\n\n /* Not drawn. How often the screen re-reads — computed from how long the work takes. */\n steps.push({ id: 'settings', kind: 'settings', refreshSeconds: refreshFor(facts.operationDuration) })\n\n return { flow: { steps }, omitted }\n}\n\n/**\n * Fill in every argument that has one right answer, and point the rest at the steps we just made.\n *\n * A field with no suggestion and no step to take it from is left unset **on purpose** — that is\n * exactly the state `checkFlow` names, and inventing a value here would silence a warning by\n * making it untrue rather than by fixing it.\n */\nfunction wire(fields: StationServiceField[], fromSteps: Record<string, string>): Record<string, StationArgumentSource> {\n const wired: Record<string, StationArgumentSource> = {}\n\n for (const field of fields) {\n const entry = fromSteps[field.name]\n\n if (entry) {\n wired[field.name] = { from: 'input', stepId: entry }\n continue\n }\n\n const suggested = suggestSource(field.name)\n\n if (suggested) {\n wired[field.name] = suggested\n }\n }\n\n return wired\n}\n\n/**\n * How often to re-read, from how long the work takes.\n *\n * ── Why not one number for everybody ─────────────────────────────────────\n * A 30-second cut and a 392-second assembly are different screens. Reading every 5 seconds at the\n * assembly bench is forty pointless round trips per piece across a plant of twelve benches; reading\n * every 30 at the cutter means the count is a whole piece behind for most of the cycle.\n *\n * Roughly forty reads across one cycle, held between 5 and 60 seconds. The bounds matter more than\n * the ratio: below 5 the screen is a load generator, above 60 it looks frozen to somebody standing\n * in front of it.\n */\nexport function refreshFor(duration?: string): number {\n const seconds = secondsOf(duration)\n\n if (!seconds) {\n return 10\n }\n\n return Math.min(60, Math.max(5, Math.round(seconds / 40)))\n}\n\n/** `PT392S` · `PT7M30S` · `PT1H` → seconds. Anything else → nothing, and the caller uses its default. */\nfunction secondsOf(duration?: string): number | undefined {\n if (!duration) {\n return undefined\n }\n\n const said = /^PT(?:(\\d+(?:\\.\\d+)?)H)?(?:(\\d+(?:\\.\\d+)?)M)?(?:(\\d+(?:\\.\\d+)?)S)?$/.exec(duration.trim().toUpperCase())\n\n if (!said || (!said[1] && !said[2] && !said[3])) {\n return undefined\n }\n\n return Number(said[1] ?? 0) * 3600 + Number(said[2] ?? 0) * 60 + Number(said[3] ?? 0)\n}\n"]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { EntityManager } from 'typeorm';
|
|
2
|
+
import type { Domain } from '@things-factory/shell';
|
|
3
|
+
import type { StationAssignmentSituation } from './station-assignment-choice.js';
|
|
4
|
+
import type { StationServiceField } from './station-flow.js';
|
|
5
|
+
/**
|
|
6
|
+
* What a product has to say before the station machinery will run for it (ADR-0083 decision 2).
|
|
7
|
+
*
|
|
8
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
9
|
+
* ## Why this file exists
|
|
10
|
+
*
|
|
11
|
+
* The station machinery is going to `@things-factory/station`, where operato-warehouse, and later
|
|
12
|
+
* a yard and an energy product, will use the same authoring, the same flow model and the same
|
|
13
|
+
* release approval. Nothing of that is about a factory. What *is* about a factory is small: which
|
|
14
|
+
* values a screen may read, which mutations a step may call, who signs a release, and where those
|
|
15
|
+
* values actually come from.
|
|
16
|
+
*
|
|
17
|
+
* This is that boundary, drawn **before** anything moves. Splitting while moving would leave the
|
|
18
|
+
* first failure unattributable — was it the split or the move? — which is the order ADR-0078 used
|
|
19
|
+
* when it lifted the outbox judgement out one layer at a time.
|
|
20
|
+
*
|
|
21
|
+
* ## Declared, never defaulted
|
|
22
|
+
*
|
|
23
|
+
* A product that has not declared gets an error naming what is missing, not an empty list. A
|
|
24
|
+
* default here would mean a warehouse screen quietly offering a factory's mutations, or offering
|
|
25
|
+
* none and reading as "this product has no services" — the silent empty this codebase keeps
|
|
26
|
+
* finding. The same reason `registerWorkDoors` has no default door list.
|
|
27
|
+
*
|
|
28
|
+
* ## The assignment columns are the standard's general words (ADR-0083 decision 5)
|
|
29
|
+
*
|
|
30
|
+
* They used to be a factory's: `productionLineName` became `workCenterName` because the contract
|
|
31
|
+
* already says `WorkCenter` is the general term over `ProcessCell` · `ProductionLine` ·
|
|
32
|
+
* `ProductionUnit` · `StorageZone`. A warehouse zone and a yard block have somewhere to stand now.
|
|
33
|
+
* `routeKey` and `operationKey` stay — a warehouse has a picking route too. No union of both
|
|
34
|
+
* spellings: that would grow four products every time an axis is added.
|
|
35
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
36
|
+
*/
|
|
37
|
+
/** Something a station step can call. The product owns the list; the machinery reads the shape. */
|
|
38
|
+
export interface StationServiceDeclaration {
|
|
39
|
+
/** The mutation name, exactly as the schema spells it. */
|
|
40
|
+
name: string;
|
|
41
|
+
/** What an author is doing when they pick this — their words, not the mutation's. */
|
|
42
|
+
describes: string;
|
|
43
|
+
/** The `@InputType` whose fields this call takes. */
|
|
44
|
+
input: Function;
|
|
45
|
+
/**
|
|
46
|
+
* Nullable fields that still must not be left empty.
|
|
47
|
+
*
|
|
48
|
+
* Absent means "nobody wrote it down", not zero — and the two are different facts on a
|
|
49
|
+
* production report.
|
|
50
|
+
*/
|
|
51
|
+
notable?: string[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The facts this product knows about one station, in the words assignment selects on.
|
|
55
|
+
*
|
|
56
|
+
* The machinery reads what the platform owns (the equipment and its classes) itself. This answers
|
|
57
|
+
* only what the product's own domain knows — for a factory, which line it stands on and what the
|
|
58
|
+
* order running there is made of.
|
|
59
|
+
*/
|
|
60
|
+
export type StationGroundReader = (manager: EntityManager, domain: Domain, stationName: string, at?: {
|
|
61
|
+
operationKey?: string;
|
|
62
|
+
operatorName?: string;
|
|
63
|
+
jobOrderName?: string;
|
|
64
|
+
}) => Promise<Pick<StationAssignmentSituation, 'workCenterName' | 'routeKey' | 'materialClassIds' | 'personnelClassIds'>>;
|
|
65
|
+
/**
|
|
66
|
+
* 기계가 만든 변경 목록을 제품의 이력으로 보내는 자리 (ADR-0083 결정 7, 선언 다섯).
|
|
67
|
+
*
|
|
68
|
+
* **무엇이 바뀌었는지는 기계가 만든다** — 자기 엔티티를 자기가 안다. 화면 정의가 지워질 때 어느 칸이
|
|
69
|
+
* 무엇이었는지는 `deletionChanges` 가 이 모듈 안에서 만든다.
|
|
70
|
+
*
|
|
71
|
+
* **그것이 어느 이력으로 가는지는 제품이 정한다.** plant 는 `fact-change` 로 보내고, 창고는 자기
|
|
72
|
+
* 이력을 갖는다. 그 표와 그 낱말(`source` 같은 것)은 제품의 것이라 모듈이 알 까닭이 없다.
|
|
73
|
+
*
|
|
74
|
+
* 필수다. 선언하지 않으면 통제 문서를 지운 이력이 **오류 없이 안 남는다** — 통제 문서에서 그것은
|
|
75
|
+
* 「누가 언제 무엇을 지웠나」를 못 답하는 것이고, 감사에서 드러날 때는 이미 늦다.
|
|
76
|
+
*/
|
|
77
|
+
export type StationChangeRecorder = (manager: EntityManager, domain: Domain, what: {
|
|
78
|
+
factKind: string;
|
|
79
|
+
factName: string;
|
|
80
|
+
changes: {
|
|
81
|
+
field: string;
|
|
82
|
+
fromValue?: string;
|
|
83
|
+
toValue?: string;
|
|
84
|
+
}[];
|
|
85
|
+
at?: Date;
|
|
86
|
+
}, by: {
|
|
87
|
+
user?: unknown;
|
|
88
|
+
/** 결재를 지나 온 것이면 그 건. 누가 승인했는지는 워크리스트가 들고 이것으로 찾아간다. */
|
|
89
|
+
activityInstanceId?: string;
|
|
90
|
+
}) => Promise<unknown>;
|
|
91
|
+
export interface StationProduct {
|
|
92
|
+
/** For the message when something is missing — the product says its own name. */
|
|
93
|
+
product: string;
|
|
94
|
+
services: StationServiceDeclaration[];
|
|
95
|
+
readGround: StationGroundReader;
|
|
96
|
+
recordChange: StationChangeRecorder;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Called once at boot, from the product's own declaration file.
|
|
100
|
+
*
|
|
101
|
+
* 빠진 자리를 여기서 잡는다. 타입이 이미 다섯을 다 요구하지만 서버 쪽은 `strict: false` 로 돌고
|
|
102
|
+
* 선언 파일에 `as any` 가 한 번 끼면 타입이 못 막는다. 그때 빠진 자리는 **부를 때**에야 드러나고,
|
|
103
|
+
* 그 자리가 `recordChange` 면 통제 문서를 지운 이력이 오류 없이 안 남는다 — 기동 때 말하는 쪽이
|
|
104
|
+
* 낫다(ADR-0083 결정 2 「선언은 필수이고 모듈에 기본값은 없다」).
|
|
105
|
+
*/
|
|
106
|
+
export declare function declareStationProduct(product: StationProduct): void;
|
|
107
|
+
/**
|
|
108
|
+
* What the product declared — **or an error saying it has not.**
|
|
109
|
+
*
|
|
110
|
+
* Reading this before the declaration is loaded is a wiring mistake and shows as one. Answering
|
|
111
|
+
* with an empty product instead would draw a screen with no services on it and no reason given.
|
|
112
|
+
*/
|
|
113
|
+
export declare function stationProduct(): StationProduct;
|
|
114
|
+
/** Tests only — a declaration is meant to be made once, at boot. */
|
|
115
|
+
export declare function forgetStationProduct(): void;
|
|
116
|
+
/**
|
|
117
|
+
* The fields of one service, as the running schema has them.
|
|
118
|
+
*
|
|
119
|
+
* Reads type-graphql's own metadata rather than issuing an introspection query: it is the same
|
|
120
|
+
* declaration the schema is built from, it needs no round trip, and it works before the server
|
|
121
|
+
* has finished starting.
|
|
122
|
+
*/
|
|
123
|
+
export declare function fieldsOfService(declared: StationServiceDeclaration): StationServiceField[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.declareStationProduct = declareStationProduct;
|
|
4
|
+
exports.stationProduct = stationProduct;
|
|
5
|
+
exports.forgetStationProduct = forgetStationProduct;
|
|
6
|
+
exports.fieldsOfService = fieldsOfService;
|
|
7
|
+
const type_graphql_1 = require("type-graphql");
|
|
8
|
+
let declared;
|
|
9
|
+
/**
|
|
10
|
+
* Called once at boot, from the product's own declaration file.
|
|
11
|
+
*
|
|
12
|
+
* 빠진 자리를 여기서 잡는다. 타입이 이미 다섯을 다 요구하지만 서버 쪽은 `strict: false` 로 돌고
|
|
13
|
+
* 선언 파일에 `as any` 가 한 번 끼면 타입이 못 막는다. 그때 빠진 자리는 **부를 때**에야 드러나고,
|
|
14
|
+
* 그 자리가 `recordChange` 면 통제 문서를 지운 이력이 오류 없이 안 남는다 — 기동 때 말하는 쪽이
|
|
15
|
+
* 낫다(ADR-0083 결정 2 「선언은 필수이고 모듈에 기본값은 없다」).
|
|
16
|
+
*/
|
|
17
|
+
function declareStationProduct(product) {
|
|
18
|
+
const missing = ['services', 'readGround', 'recordChange'].filter(one => !product?.[one]);
|
|
19
|
+
if (missing.length) {
|
|
20
|
+
throw new Error(`station: ${product?.product ?? '이 제품'} 의 선언에 ${missing.join(' · ')} 이(가) 없습니다 — 현장 화면이 서려면 다 채워야 합니다.`);
|
|
21
|
+
}
|
|
22
|
+
declared = product;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* What the product declared — **or an error saying it has not.**
|
|
26
|
+
*
|
|
27
|
+
* Reading this before the declaration is loaded is a wiring mistake and shows as one. Answering
|
|
28
|
+
* with an empty product instead would draw a screen with no services on it and no reason given.
|
|
29
|
+
*/
|
|
30
|
+
function stationProduct() {
|
|
31
|
+
if (!declared) {
|
|
32
|
+
throw new Error('station: 이 제품이 선언을 하지 않았습니다 — 서비스 목록 · 바탕을 읽는 자리 · 기록을 남기는 자리를 declareStationProduct 로 등록해야 현장 화면이 섭니다.');
|
|
33
|
+
}
|
|
34
|
+
return declared;
|
|
35
|
+
}
|
|
36
|
+
/** Tests only — a declaration is meant to be made once, at boot. */
|
|
37
|
+
function forgetStationProduct() {
|
|
38
|
+
declared = undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The fields of one service, as the running schema has them.
|
|
42
|
+
*
|
|
43
|
+
* Reads type-graphql's own metadata rather than issuing an introspection query: it is the same
|
|
44
|
+
* declaration the schema is built from, it needs no round trip, and it works before the server
|
|
45
|
+
* has finished starting.
|
|
46
|
+
*/
|
|
47
|
+
function fieldsOfService(declared) {
|
|
48
|
+
const storage = (0, type_graphql_1.getMetadataStorage)();
|
|
49
|
+
const notable = new Set(declared.notable ?? []);
|
|
50
|
+
/*
|
|
51
|
+
* Fields are stored against the class, and a class that extends another has its parent's fields
|
|
52
|
+
* under the parent's target. Walking the prototype chain keeps an inherited field from going
|
|
53
|
+
* missing — which would look exactly like the schema not having it.
|
|
54
|
+
*/
|
|
55
|
+
const targets = new Set();
|
|
56
|
+
for (let at = declared.input; at && at !== Function.prototype; at = Object.getPrototypeOf(at)) {
|
|
57
|
+
targets.add(at);
|
|
58
|
+
}
|
|
59
|
+
return storage.fields
|
|
60
|
+
.filter(field => targets.has(field.target))
|
|
61
|
+
.map(field => ({
|
|
62
|
+
name: field.name,
|
|
63
|
+
required: !field.typeOptions?.nullable,
|
|
64
|
+
/*
|
|
65
|
+
* The description the field's own owner wrote.
|
|
66
|
+
*
|
|
67
|
+
* `goodCount` on a screen is a raw schema name to a plant manager. The input type already
|
|
68
|
+
* carries `@Field({ description })` for many of them — the person who added the field is the
|
|
69
|
+
* one who can say what it means, and they already did. Carrying it through beats a second
|
|
70
|
+
* table here that goes stale the day a field is added: a field with no description shows its
|
|
71
|
+
* name, which is what happened anyway before.
|
|
72
|
+
*/
|
|
73
|
+
...(field.description ? { describes: field.description } : {}),
|
|
74
|
+
...(notable.has(field.name) ? { notable: true } : {})
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=station-product.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"station-product.js","sourceRoot":"","sources":["../../../server/service/station/station-product.ts"],"names":[],"mappings":";;AAqHA,sDAUC;AAQD,wCAQC;AAGD,oDAEC;AASD,0CAgCC;AA7LD,+CAAiD;AA2GjD,IAAI,QAAoC,CAAA;AAExC;;;;;;;GAOG;AACH,SAAgB,qBAAqB,CAAC,OAAuB;IAC3D,MAAM,OAAO,GAAI,CAAC,UAAU,EAAE,YAAY,EAAE,cAAc,CAAW,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IAEpG,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,YAAY,OAAO,EAAE,OAAO,IAAI,MAAM,UAAU,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,oCAAoC,CACxG,CAAA;IACH,CAAC;IAED,QAAQ,GAAG,OAAO,CAAA;AACpB,CAAC;AAED;;;;;GAKG;AACH,SAAgB,cAAc;IAC5B,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,yGAAyG,CAC1G,CAAA;IACH,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,oEAAoE;AACpE,SAAgB,oBAAoB;IAClC,QAAQ,GAAG,SAAS,CAAA;AACtB,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,QAAmC;IACjE,MAAM,OAAO,GAAG,IAAA,iCAAkB,GAAE,CAAA;IACpC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAA;IAE/C;;;;OAIG;IACH,MAAM,OAAO,GAAG,IAAI,GAAG,EAAY,CAAA;IAEnC,KAAK,IAAI,EAAE,GAAoB,QAAQ,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,QAAQ,CAAC,SAAS,EAAE,EAAE,GAAG,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,EAAE,CAAC;QAC/G,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACjB,CAAC;IAED,OAAO,OAAO,CAAC,MAAM;SAClB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAkB,CAAC,CAAC;SACtD,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACb,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ;QACtC;;;;;;;;WAQG;QACH,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC,CAAC,CAAA;AACP,CAAC","sourcesContent":["import { getMetadataStorage } from 'type-graphql'\nimport type { EntityManager } from 'typeorm'\nimport type { Domain } from '@things-factory/shell'\n\nimport type { StationAssignmentSituation } from './station-assignment-choice.js'\nimport type { StationServiceField } from './station-flow.js'\n\n/**\n * What a product has to say before the station machinery will run for it (ADR-0083 decision 2).\n *\n * ═══════════════════════════════════════════════════════════════════════════\n * ## Why this file exists\n *\n * The station machinery is going to `@things-factory/station`, where operato-warehouse, and later\n * a yard and an energy product, will use the same authoring, the same flow model and the same\n * release approval. Nothing of that is about a factory. What *is* about a factory is small: which\n * values a screen may read, which mutations a step may call, who signs a release, and where those\n * values actually come from.\n *\n * This is that boundary, drawn **before** anything moves. Splitting while moving would leave the\n * first failure unattributable — was it the split or the move? — which is the order ADR-0078 used\n * when it lifted the outbox judgement out one layer at a time.\n *\n * ## Declared, never defaulted\n *\n * A product that has not declared gets an error naming what is missing, not an empty list. A\n * default here would mean a warehouse screen quietly offering a factory's mutations, or offering\n * none and reading as \"this product has no services\" — the silent empty this codebase keeps\n * finding. The same reason `registerWorkDoors` has no default door list.\n *\n * ## The assignment columns are the standard's general words (ADR-0083 decision 5)\n *\n * They used to be a factory's: `productionLineName` became `workCenterName` because the contract\n * already says `WorkCenter` is the general term over `ProcessCell` · `ProductionLine` ·\n * `ProductionUnit` · `StorageZone`. A warehouse zone and a yard block have somewhere to stand now.\n * `routeKey` and `operationKey` stay — a warehouse has a picking route too. No union of both\n * spellings: that would grow four products every time an axis is added.\n * ═══════════════════════════════════════════════════════════════════════════\n */\n\n/** Something a station step can call. The product owns the list; the machinery reads the shape. */\nexport interface StationServiceDeclaration {\n /** The mutation name, exactly as the schema spells it. */\n name: string\n /** What an author is doing when they pick this — their words, not the mutation's. */\n describes: string\n /** The `@InputType` whose fields this call takes. */\n input: Function\n /**\n * Nullable fields that still must not be left empty.\n *\n * Absent means \"nobody wrote it down\", not zero — and the two are different facts on a\n * production report.\n */\n notable?: string[]\n}\n\n/**\n * The facts this product knows about one station, in the words assignment selects on.\n *\n * The machinery reads what the platform owns (the equipment and its classes) itself. This answers\n * only what the product's own domain knows — for a factory, which line it stands on and what the\n * order running there is made of.\n */\nexport type StationGroundReader = (\n manager: EntityManager,\n domain: Domain,\n stationName: string,\n at?: { operationKey?: string; operatorName?: string; jobOrderName?: string }\n) => Promise<Pick<StationAssignmentSituation, 'workCenterName' | 'routeKey' | 'materialClassIds' | 'personnelClassIds'>>\n\n/**\n * 기계가 만든 변경 목록을 제품의 이력으로 보내는 자리 (ADR-0083 결정 7, 선언 다섯).\n *\n * **무엇이 바뀌었는지는 기계가 만든다** — 자기 엔티티를 자기가 안다. 화면 정의가 지워질 때 어느 칸이\n * 무엇이었는지는 `deletionChanges` 가 이 모듈 안에서 만든다.\n *\n * **그것이 어느 이력으로 가는지는 제품이 정한다.** plant 는 `fact-change` 로 보내고, 창고는 자기\n * 이력을 갖는다. 그 표와 그 낱말(`source` 같은 것)은 제품의 것이라 모듈이 알 까닭이 없다.\n *\n * 필수다. 선언하지 않으면 통제 문서를 지운 이력이 **오류 없이 안 남는다** — 통제 문서에서 그것은\n * 「누가 언제 무엇을 지웠나」를 못 답하는 것이고, 감사에서 드러날 때는 이미 늦다.\n */\nexport type StationChangeRecorder = (\n manager: EntityManager,\n domain: Domain,\n what: {\n factKind: string\n factName: string\n changes: { field: string; fromValue?: string; toValue?: string }[]\n at?: Date\n },\n by: {\n user?: unknown\n /** 결재를 지나 온 것이면 그 건. 누가 승인했는지는 워크리스트가 들고 이것으로 찾아간다. */\n activityInstanceId?: string\n }\n) => Promise<unknown>\n\nexport interface StationProduct {\n /** For the message when something is missing — the product says its own name. */\n product: string\n services: StationServiceDeclaration[]\n readGround: StationGroundReader\n recordChange: StationChangeRecorder\n}\n\nlet declared: StationProduct | undefined\n\n/**\n * Called once at boot, from the product's own declaration file.\n *\n * 빠진 자리를 여기서 잡는다. 타입이 이미 다섯을 다 요구하지만 서버 쪽은 `strict: false` 로 돌고\n * 선언 파일에 `as any` 가 한 번 끼면 타입이 못 막는다. 그때 빠진 자리는 **부를 때**에야 드러나고,\n * 그 자리가 `recordChange` 면 통제 문서를 지운 이력이 오류 없이 안 남는다 — 기동 때 말하는 쪽이\n * 낫다(ADR-0083 결정 2 「선언은 필수이고 모듈에 기본값은 없다」).\n */\nexport function declareStationProduct(product: StationProduct): void {\n const missing = (['services', 'readGround', 'recordChange'] as const).filter(one => !product?.[one])\n\n if (missing.length) {\n throw new Error(\n `station: ${product?.product ?? '이 제품'} 의 선언에 ${missing.join(' · ')} 이(가) 없습니다 — 현장 화면이 서려면 다 채워야 합니다.`\n )\n }\n\n declared = product\n}\n\n/**\n * What the product declared — **or an error saying it has not.**\n *\n * Reading this before the declaration is loaded is a wiring mistake and shows as one. Answering\n * with an empty product instead would draw a screen with no services on it and no reason given.\n */\nexport function stationProduct(): StationProduct {\n if (!declared) {\n throw new Error(\n 'station: 이 제품이 선언을 하지 않았습니다 — 서비스 목록 · 바탕을 읽는 자리 · 기록을 남기는 자리를 declareStationProduct 로 등록해야 현장 화면이 섭니다.'\n )\n }\n\n return declared\n}\n\n/** Tests only — a declaration is meant to be made once, at boot. */\nexport function forgetStationProduct(): void {\n declared = undefined\n}\n\n/**\n * The fields of one service, as the running schema has them.\n *\n * Reads type-graphql's own metadata rather than issuing an introspection query: it is the same\n * declaration the schema is built from, it needs no round trip, and it works before the server\n * has finished starting.\n */\nexport function fieldsOfService(declared: StationServiceDeclaration): StationServiceField[] {\n const storage = getMetadataStorage()\n const notable = new Set(declared.notable ?? [])\n\n /*\n * Fields are stored against the class, and a class that extends another has its parent's fields\n * under the parent's target. Walking the prototype chain keeps an inherited field from going\n * missing — which would look exactly like the schema not having it.\n */\n const targets = new Set<Function>()\n\n for (let at: Function | null = declared.input; at && at !== Function.prototype; at = Object.getPrototypeOf(at)) {\n targets.add(at)\n }\n\n return storage.fields\n .filter(field => targets.has(field.target as Function))\n .map(field => ({\n name: field.name,\n required: !field.typeOptions?.nullable,\n /*\n * The description the field's own owner wrote.\n *\n * `goodCount` on a screen is a raw schema name to a plant manager. The input type already\n * carries `@Field({ description })` for many of them — the person who added the field is the\n * one who can say what it means, and they already did. Carrying it through beats a second\n * table here that goes stale the day a field is added: a field with no description shows its\n * name, which is what happened anyway before.\n */\n ...(field.description ? { describes: field.description } : {}),\n ...(notable.has(field.name) ? { notable: true } : {})\n }))\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { StationFlow, StationStep } from './station-flow';
|
|
2
|
+
export type StationRunAction = 'start' | 'finish' | 'pause' | 'resume';
|
|
3
|
+
/** 실행 의미는 표시 이름과 분리한다. 옛 흐름의 명확한 이름만 호환한다. */
|
|
4
|
+
export declare function runAction(step: StationStep): StationRunAction | undefined;
|
|
5
|
+
/** 현 런타임은 good/scrap 두 수량만 가진다. 임의 입력을 양품으로 바꾸지 않는다. */
|
|
6
|
+
export declare function entryCounts(flow: StationFlow, stepId: string): 'good' | 'scrap' | undefined;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAction = runAction;
|
|
4
|
+
exports.entryCounts = entryCounts;
|
|
5
|
+
/** 실행 의미는 표시 이름과 분리한다. 옛 흐름의 명확한 이름만 호환한다. */
|
|
6
|
+
function runAction(step) {
|
|
7
|
+
if (step.service && !['reportProduction', 'recordEquipmentStatePeriod'].includes(step.service.name))
|
|
8
|
+
return undefined;
|
|
9
|
+
const action = step.action ?? legacyAction(step.label);
|
|
10
|
+
if (step.service?.name === 'recordEquipmentStatePeriod' && action !== 'pause' && action !== 'resume')
|
|
11
|
+
return undefined;
|
|
12
|
+
if (step.service?.name === 'reportProduction' && action !== 'start' && action !== 'finish')
|
|
13
|
+
return undefined;
|
|
14
|
+
return action;
|
|
15
|
+
}
|
|
16
|
+
function legacyAction(label) {
|
|
17
|
+
const text = (label ?? '').trim().toLowerCase();
|
|
18
|
+
if (text === '완료' || text === 'finish')
|
|
19
|
+
return 'finish';
|
|
20
|
+
if (text === '일시정지' || text === 'pause')
|
|
21
|
+
return 'pause';
|
|
22
|
+
if (text === '재개' || text === 'resume')
|
|
23
|
+
return 'resume';
|
|
24
|
+
if (text === '시작' || text === 'start')
|
|
25
|
+
return 'start';
|
|
26
|
+
}
|
|
27
|
+
/** 현 런타임은 good/scrap 두 수량만 가진다. 임의 입력을 양품으로 바꾸지 않는다. */
|
|
28
|
+
function entryCounts(flow, stepId) {
|
|
29
|
+
const targets = new Set();
|
|
30
|
+
for (const step of flow.steps) {
|
|
31
|
+
for (const [field, source] of Object.entries(step.arguments ?? {})) {
|
|
32
|
+
if (source.from !== 'input' || source.stepId !== stepId)
|
|
33
|
+
continue;
|
|
34
|
+
if (step.service?.name !== 'reportProduction')
|
|
35
|
+
return undefined;
|
|
36
|
+
targets.add(field);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (targets.size !== 1)
|
|
40
|
+
return undefined;
|
|
41
|
+
if (targets.has('goodCount'))
|
|
42
|
+
return 'good';
|
|
43
|
+
if (targets.has('nonconformingCount'))
|
|
44
|
+
return 'scrap';
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=station-runtime-binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"station-runtime-binding.js","sourceRoot":"","sources":["../../../server/service/station/station-runtime-binding.ts"],"names":[],"mappings":";;AAKA,8BAMC;AAWD,kCAYC;AA9BD,8CAA8C;AAC9C,SAAgB,SAAS,CAAC,IAAiB;IACzC,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,kBAAkB,EAAE,4BAA4B,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IACrH,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACtD,IAAI,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,4BAA4B,IAAI,MAAM,KAAK,OAAO,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IACtH,IAAI,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,kBAAkB,IAAI,MAAM,KAAK,OAAO,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IAC5G,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,MAAM,IAAI,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC/C,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAA;IACvD,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,OAAO,CAAA;IACvD,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAA;IACvD,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,OAAO,CAAA;AACvD,CAAC;AAED,wDAAwD;AACxD,SAAgB,WAAW,CAAC,IAAiB,EAAE,MAAc;IAC3D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAA;IACjC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,CAAC;YACnE,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM;gBAAE,SAAQ;YACjE,IAAI,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,kBAAkB;gBAAE,OAAO,SAAS,CAAA;YAC/D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACpB,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IACxC,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;QAAE,OAAO,MAAM,CAAA;IAC3C,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;QAAE,OAAO,OAAO,CAAA;AACvD,CAAC","sourcesContent":["import type { StationFlow, StationStep } from './station-flow'\n\nexport type StationRunAction = 'start' | 'finish' | 'pause' | 'resume'\n\n/** 실행 의미는 표시 이름과 분리한다. 옛 흐름의 명확한 이름만 호환한다. */\nexport function runAction(step: StationStep): StationRunAction | undefined {\n if (step.service && !['reportProduction', 'recordEquipmentStatePeriod'].includes(step.service.name)) return undefined\n const action = step.action ?? legacyAction(step.label)\n if (step.service?.name === 'recordEquipmentStatePeriod' && action !== 'pause' && action !== 'resume') return undefined\n if (step.service?.name === 'reportProduction' && action !== 'start' && action !== 'finish') return undefined\n return action\n}\n\nfunction legacyAction(label?: string): StationRunAction | undefined {\n const text = (label ?? '').trim().toLowerCase()\n if (text === '완료' || text === 'finish') return 'finish'\n if (text === '일시정지' || text === 'pause') return 'pause'\n if (text === '재개' || text === 'resume') return 'resume'\n if (text === '시작' || text === 'start') return 'start'\n}\n\n/** 현 런타임은 good/scrap 두 수량만 가진다. 임의 입력을 양품으로 바꾸지 않는다. */\nexport function entryCounts(flow: StationFlow, stepId: string): 'good' | 'scrap' | undefined {\n const targets = new Set<string>()\n for (const step of flow.steps) {\n for (const [field, source] of Object.entries(step.arguments ?? {})) {\n if (source.from !== 'input' || source.stepId !== stepId) continue\n if (step.service?.name !== 'reportProduction') return undefined\n targets.add(field)\n }\n }\n if (targets.size !== 1) return undefined\n if (targets.has('goodCount')) return 'good'\n if (targets.has('nonconformingCount')) return 'scrap'\n}\n"]}
|