@fmsim/machine 2.0.1 → 3.0.0-beta.2

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 (95) hide show
  1. package/dist/agv.js +2 -3
  2. package/dist/agv.js.map +1 -1
  3. package/dist/carrier.js +53 -43
  4. package/dist/carrier.js.map +1 -1
  5. package/dist/crane.js +37 -2
  6. package/dist/crane.js.map +1 -1
  7. package/dist/editors/index.js +2 -0
  8. package/dist/editors/index.js.map +1 -1
  9. package/dist/editors/load-layout-host.js +96 -0
  10. package/dist/editors/load-layout-host.js.map +1 -0
  11. package/dist/editors/ox-load-layout-panel.js +124 -0
  12. package/dist/editors/ox-load-layout-panel.js.map +1 -0
  13. package/dist/factories/crane-3d.js +77 -18
  14. package/dist/factories/crane-3d.js.map +1 -1
  15. package/dist/factories/machine-3d.js +29 -52
  16. package/dist/factories/machine-3d.js.map +1 -1
  17. package/dist/features/capacity-gauge-mixin.js +2 -3
  18. package/dist/features/capacity-gauge-mixin.js.map +1 -1
  19. package/dist/features/load-layout-holder.js +77 -0
  20. package/dist/features/load-layout-holder.js.map +1 -0
  21. package/dist/features/mcs-status-default.js +30 -7
  22. package/dist/features/mcs-status-default.js.map +1 -1
  23. package/dist/features/mcs-status-mixin.js +3 -3
  24. package/dist/features/mcs-status-mixin.js.map +1 -1
  25. package/dist/index.js +5 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/mcs-carrier-holder.js +287 -82
  28. package/dist/mcs-carrier-holder.js.map +1 -1
  29. package/dist/oht.js +4 -0
  30. package/dist/oht.js.map +1 -1
  31. package/dist/port.js +6 -1
  32. package/dist/port.js.map +1 -1
  33. package/dist/scene/root-container-override.js +5 -11
  34. package/dist/scene/root-container-override.js.map +1 -1
  35. package/dist/shelf.js +4 -0
  36. package/dist/shelf.js.map +1 -1
  37. package/dist/shuttle.js +4 -0
  38. package/dist/shuttle.js.map +1 -1
  39. package/dist/stocker-carrier-bar.js +4 -4
  40. package/dist/stocker-carrier-bar.js.map +1 -1
  41. package/dist/tsconfig.tsbuildinfo +1 -1
  42. package/dist/utils/carrier-type-code.js +10 -0
  43. package/dist/utils/carrier-type-code.js.map +1 -0
  44. package/dist/utils/crane-forks.js +5 -0
  45. package/dist/utils/crane-forks.js.map +1 -0
  46. package/dist/utils/layout-issue-text.js +16 -0
  47. package/dist/utils/layout-issue-text.js.map +1 -0
  48. package/dist/utils/layout-issue.js +16 -0
  49. package/dist/utils/layout-issue.js.map +1 -0
  50. package/dist/utils/load-address.js +217 -0
  51. package/dist/utils/load-address.js.map +1 -0
  52. package/dist/utils/stocker-gauge.js +3 -2
  53. package/dist/utils/stocker-gauge.js.map +1 -1
  54. package/jest.config.js +9 -1
  55. package/package.json +9 -6
  56. package/src/agv.ts +2 -3
  57. package/src/carrier.ts +50 -41
  58. package/src/crane.ts +39 -2
  59. package/src/editors/index.ts +2 -0
  60. package/src/editors/load-layout-host.ts +121 -0
  61. package/src/editors/ox-load-layout-panel.ts +129 -0
  62. package/src/factories/crane-3d.ts +74 -20
  63. package/src/factories/machine-3d.ts +26 -49
  64. package/src/features/capacity-gauge-mixin.ts +2 -3
  65. package/src/features/load-layout-holder.ts +83 -0
  66. package/src/features/mcs-status-default.ts +29 -10
  67. package/src/features/mcs-status-mixin.ts +3 -7
  68. package/src/index.ts +7 -0
  69. package/src/mcs-carrier-holder.ts +319 -78
  70. package/src/oht.ts +5 -0
  71. package/src/port.ts +7 -1
  72. package/src/scene/root-container-override.ts +4 -14
  73. package/src/shelf.ts +5 -0
  74. package/src/shuttle.ts +5 -0
  75. package/src/stocker-carrier-bar.ts +4 -4
  76. package/src/utils/carrier-type-code.ts +9 -0
  77. package/src/utils/crane-forks.ts +4 -0
  78. package/src/utils/layout-issue-text.ts +18 -0
  79. package/src/utils/layout-issue.ts +41 -0
  80. package/src/utils/load-address.ts +270 -0
  81. package/src/utils/stocker-gauge.ts +3 -3
  82. package/translations/en.json +42 -1
  83. package/translations/ja.json +42 -1
  84. package/translations/ko.json +59 -18
  85. package/translations/ms.json +42 -1
  86. package/translations/zh.json +42 -1
  87. package/dist/carrier-manager.js +0 -191
  88. package/dist/carrier-manager.js.map +0 -1
  89. package/dist/features/animation-default.js +0 -6
  90. package/dist/features/animation-default.js.map +0 -1
  91. package/dist/utils/get-value-on-ranges.js +0 -21
  92. package/dist/utils/get-value-on-ranges.js.map +0 -1
  93. package/src/carrier-manager.ts +0 -231
  94. package/src/features/animation-default.ts +0 -13
  95. package/src/utils/get-value-on-ranges.ts +0 -23
@@ -0,0 +1,10 @@
1
+ /*
2
+ * How live data's CARRIERTYPE is compared (fmsim doc 01, 6.8): trimmed and upper-cased. The code it makes is looked up
3
+ * in the domain's item type table (ADR-0155), whose codes fmsim writes in that form.
4
+ */
5
+ export function normalizeTypeCode(code) {
6
+ return String(code !== null && code !== void 0 ? code : '')
7
+ .trim()
8
+ .toUpperCase();
9
+ }
10
+ //# sourceMappingURL=carrier-type-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carrier-type-code.js","sourceRoot":"","sources":["../../src/utils/carrier-type-code.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAa;IAC7C,OAAO,MAAM,CAAC,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,EAAE,CAAC;SACtB,IAAI,EAAE;SACN,WAAW,EAAE,CAAA;AAClB,CAAC","sourcesContent":["/*\n * How live data's CARRIERTYPE is compared (fmsim doc 01, 6.8): trimmed and upper-cased. The code it makes is looked up\n * in the domain's item type table (ADR-0155), whose codes fmsim writes in that form.\n */\nexport function normalizeTypeCode(code: unknown): string {\n return String(code ?? '')\n .trim()\n .toUpperCase()\n}\n"]}
@@ -0,0 +1,5 @@
1
+ /* 크레인 포크 수 (설계 02번 3.2.1절 트윈 포크). 1(단일) 또는 2(트윈). 그 밖의 값은 1 */
2
+ export function craneForkCount(value) {
3
+ return Number(value) === 2 ? 2 : 1;
4
+ }
5
+ //# sourceMappingURL=crane-forks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crane-forks.js","sourceRoot":"","sources":["../../src/utils/crane-forks.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACpC,CAAC","sourcesContent":["/* 크레인 포크 수 (설계 02번 3.2.1절 트윈 포크). 1(단일) 또는 2(트윈). 그 밖의 값은 1 */\nexport function craneForkCount(value: unknown): 1 | 2 {\n return Number(value) === 2 ? 2 : 1\n}\n"]}
@@ -0,0 +1,16 @@
1
+ /*
2
+ * fmsim 고유 오류 코드의 기본 문구 (영어). 화면은 label.load-layout.issue.{코드} 번역을 먼저 쓰고, 없으면 이 문구를 쓴다.
3
+ * 층·자리 검증 코드의 문구는 공통 편집기(@operato/slot-layout-editor)의 것을 쓴다.
4
+ */
5
+ /** 오류 코드의 기본 문구 (번역이 없을 때). 매개변수는 {name} 으로 넣는다. 자리표시자가 중괄호라서 문구에 중괄호 글자를 쓰지 않는다 */
6
+ export const ISSUE_TEXT = {
7
+ ADDRESSING_BY_INVALID: 'Must be fields or a location pattern',
8
+ FIELD_REQUIRED: 'A field name is required',
9
+ ADDRESS_PATTERN_EMPTY: 'The pattern is empty',
10
+ ADDRESS_PATTERN_SYNTAX: 'Broken token near "{text}"',
11
+ ADDRESS_PATTERN_TOKEN_REPEATED: 'The {token} token may appear only once',
12
+ ADDRESS_PATTERN_WIDTH_NOT_ALLOWED: 'Digits are allowed only on the tier and slot tokens, not on {token}',
13
+ ADDRESS_PATTERN_SLOT_REQUIRED: 'The pattern needs a slot token',
14
+ DUPLICATE_CARRIER: 'The same carrier came twice: {name}'
15
+ };
16
+ //# sourceMappingURL=layout-issue-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-issue-text.js","sourceRoot":"","sources":["../../src/utils/layout-issue-text.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,oFAAoF;AACpF,MAAM,CAAC,MAAM,UAAU,GAAoC;IACzD,qBAAqB,EAAE,sCAAsC;IAC7D,cAAc,EAAE,0BAA0B;IAC1C,qBAAqB,EAAE,sBAAsB;IAC7C,sBAAsB,EAAE,4BAA4B;IACpD,8BAA8B,EAAE,wCAAwC;IACxE,iCAAiC,EAAE,qEAAqE;IACxG,6BAA6B,EAAE,gCAAgC;IAC/D,iBAAiB,EAAE,qCAAqC;CACzD,CAAA","sourcesContent":["/*\n * fmsim 고유 오류 코드의 기본 문구 (영어). 화면은 label.load-layout.issue.{코드} 번역을 먼저 쓰고, 없으면 이 문구를 쓴다.\n * 층·자리 검증 코드의 문구는 공통 편집기(@operato/slot-layout-editor)의 것을 쓴다.\n */\n\nimport { LayoutIssueCode } from './layout-issue'\n\n/** 오류 코드의 기본 문구 (번역이 없을 때). 매개변수는 {name} 으로 넣는다. 자리표시자가 중괄호라서 문구에 중괄호 글자를 쓰지 않는다 */\nexport const ISSUE_TEXT: Record<LayoutIssueCode, string> = {\n ADDRESSING_BY_INVALID: 'Must be fields or a location pattern',\n FIELD_REQUIRED: 'A field name is required',\n ADDRESS_PATTERN_EMPTY: 'The pattern is empty',\n ADDRESS_PATTERN_SYNTAX: 'Broken token near \"{text}\"',\n ADDRESS_PATTERN_TOKEN_REPEATED: 'The {token} token may appear only once',\n ADDRESS_PATTERN_WIDTH_NOT_ALLOWED: 'Digits are allowed only on the tier and slot tokens, not on {token}',\n ADDRESS_PATTERN_SLOT_REQUIRED: 'The pattern needs a slot token',\n DUPLICATE_CARRIER: 'The same carrier came twice: {name}'\n}\n"]}
@@ -0,0 +1,16 @@
1
+ /*
2
+ * fmsim 고유 검증 결과: 실시간 주소 읽기 규칙과 점유 데이터 (설계 02번 3.6절, 4.2절)
3
+ * 층·자리 레이아웃의 검증은 figure-model 이 한다. 검증 함수는 문장을 만들지 않고 코드·필드 경로·매개변수만 돌려준다.
4
+ */
5
+ export function issue(code, path, params) {
6
+ return params ? { code, path, params } : { code, path };
7
+ }
8
+ /** 위치 문자열 패턴 문법 오류. issues 에 모든 문제가 들어 있다 */
9
+ export class AddressPatternError extends Error {
10
+ constructor(issues) {
11
+ super(issues.map(i => i.code).join(', '));
12
+ this.issues = issues;
13
+ this.name = 'AddressPatternError';
14
+ }
15
+ }
16
+ //# sourceMappingURL=layout-issue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-issue.js","sourceRoot":"","sources":["../../src/utils/layout-issue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA2BH,MAAM,UAAU,KAAK,CAAC,IAAqB,EAAE,IAAY,EAAE,MAAwC;IACjG,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AACzD,CAAC;AAED,6CAA6C;AAC7C,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,YAAqB,MAAqB;QACxC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QADtB,WAAM,GAAN,MAAM,CAAe;QAExC,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAA;IACnC,CAAC;CACF","sourcesContent":["/*\n * fmsim 고유 검증 결과: 실시간 주소 읽기 규칙과 점유 데이터 (설계 02번 3.6절, 4.2절)\n * 층·자리 레이아웃의 검증은 figure-model 이 한다. 검증 함수는 문장을 만들지 않고 코드·필드 경로·매개변수만 돌려준다.\n */\n\nexport type LayoutIssueCode =\n /** FIELDS·PATTERN 중 하나 */\n | 'ADDRESSING_BY_INVALID'\n /** 필드 이름이 필요하다 */\n | 'FIELD_REQUIRED'\n /** 위치 문자열 패턴이 비었다 */\n | 'ADDRESS_PATTERN_EMPTY'\n /** 토큰 문법 오류 (params.text) */\n | 'ADDRESS_PATTERN_SYNTAX'\n /** {tier}·{slot} 은 한 번만 (params.token) */\n | 'ADDRESS_PATTERN_TOKEN_REPEATED'\n /** {tier}·{slot} 밖의 토큰에는 자릿수를 쓰지 않는다 (params.token) */\n | 'ADDRESS_PATTERN_WIDTH_NOT_ALLOWED'\n /** {slot} 이 없다 */\n | 'ADDRESS_PATTERN_SLOT_REQUIRED'\n /** 한 홀더에 같은 Carrier 가 두 번 왔다 (params.name) */\n | 'DUPLICATE_CARRIER'\n\nexport interface LayoutIssue {\n code: LayoutIssueCode\n /** 값의 위치. 예 'addressing.pattern'. 값 전체이면 빈 문자열 */\n path: string\n params?: Record<string, string | number>\n}\n\nexport function issue(code: LayoutIssueCode, path: string, params?: Record<string, string | number>): LayoutIssue {\n return params ? { code, path, params } : { code, path }\n}\n\n/** 위치 문자열 패턴 문법 오류. issues 에 모든 문제가 들어 있다 */\nexport class AddressPatternError extends Error {\n constructor(readonly issues: LayoutIssue[]) {\n super(issues.map(i => i.code).join(', '))\n this.name = 'AddressPatternError'\n }\n}\n"]}
@@ -0,0 +1,217 @@
1
+ /*
2
+ * 실시간 데이터의 적재 주소 해석과 점유 배정 (설계 02번 3.6절, 3.6.1절)
3
+ * Carrier 는 자리 번호에 고정된다. 주소를 못 읽으면 빈 자리에 채우지 않고 예외로 둔다.
4
+ * 레이아웃(층·자리)과 자리 계산은 figure-model 의 것이다(ADR-0051 4.1절 결정 8). 여기 남는 것은 MCS 의 몫이다:
5
+ * 실시간 레코드에서 주소를 읽는 규칙(FIELDS·PATTERN)과 점유 배정·예외.
6
+ */
7
+ import { resolveSlot } from '@hatiolab/figure-model';
8
+ import { AddressPatternError, issue } from './layout-issue';
9
+ import { parseIfJsonString } from './data-value';
10
+ /** 양의 정수 또는 양의 정수 문자열만 받는다. 그 밖의 값은 null */
11
+ function positiveInt(v) {
12
+ if (typeof v === 'number')
13
+ return Number.isSafeInteger(v) && v > 0 ? v : null;
14
+ if (typeof v === 'string' && /^\d+$/.test(v.trim())) {
15
+ const n = Number(v.trim());
16
+ return Number.isSafeInteger(n) && n > 0 ? n : null;
17
+ }
18
+ return null;
19
+ }
20
+ const missing = (v) => v === undefined || v === null;
21
+ // ── PATTERN 문법 ────────────────────────────────────────────────────────────
22
+ const TOKEN = /\{([A-Za-z_][A-Za-z0-9_]*)(?::(0\d+))?\}/g;
23
+ /** 위치 문자열 패턴을 정규식으로 바꾼다. 문법이 틀리면 AddressPatternError(모든 문제 목록)를 던진다 */
24
+ export function compileAddressPattern(pattern) {
25
+ if (typeof pattern !== 'string' || !pattern)
26
+ throw new AddressPatternError([issue('ADDRESS_PATTERN_EMPTY', 'pattern')]);
27
+ const errors = [];
28
+ let source = '^';
29
+ let last = 0;
30
+ let group = 0;
31
+ let tierGroup;
32
+ let slotGroup;
33
+ const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
34
+ for (const m of pattern.matchAll(TOKEN)) {
35
+ const literal = pattern.slice(last, m.index);
36
+ if (/[{}]/.test(literal))
37
+ errors.push(issue('ADDRESS_PATTERN_SYNTAX', 'pattern', { text: literal }));
38
+ source += escape(literal);
39
+ const [, name, width] = m;
40
+ group++;
41
+ if (name === 'tier' || name === 'slot') {
42
+ if ((name === 'tier' ? tierGroup : slotGroup) !== undefined)
43
+ errors.push(issue('ADDRESS_PATTERN_TOKEN_REPEATED', 'pattern', { token: name }));
44
+ if (name === 'tier')
45
+ tierGroup = group;
46
+ else
47
+ slotGroup = group;
48
+ source += width ? `(\\d{${width.length}})` : '(\\d+)';
49
+ }
50
+ else {
51
+ if (width)
52
+ errors.push(issue('ADDRESS_PATTERN_WIDTH_NOT_ALLOWED', 'pattern', { token: name }));
53
+ source += '(.+?)';
54
+ }
55
+ last = m.index + m[0].length;
56
+ }
57
+ const tail = pattern.slice(last);
58
+ if (/[{}]/.test(tail))
59
+ errors.push(issue('ADDRESS_PATTERN_SYNTAX', 'pattern', { text: tail }));
60
+ source += escape(tail) + '$';
61
+ if (slotGroup === undefined)
62
+ errors.push(issue('ADDRESS_PATTERN_SLOT_REQUIRED', 'pattern'));
63
+ if (errors.length)
64
+ throw new AddressPatternError(errors);
65
+ return { regex: new RegExp(source), tierGroup, slotGroup };
66
+ }
67
+ /** 레이아웃의 addressing 설정 오류 목록. 경로는 'addressing.' 으로 시작한다. 빈 배열이면 올바르다 */
68
+ export function validateAddressing(addressing) {
69
+ if (!addressing)
70
+ return [];
71
+ if (addressing.by === 'FIELDS')
72
+ return addressing.slotField ? [] : [issue('FIELD_REQUIRED', 'addressing.slotField')];
73
+ if (addressing.by === 'PATTERN') {
74
+ const errors = addressing.field ? [] : [issue('FIELD_REQUIRED', 'addressing.field')];
75
+ try {
76
+ compileAddressPattern(addressing.pattern);
77
+ }
78
+ catch (e) {
79
+ if (!(e instanceof AddressPatternError))
80
+ throw e;
81
+ errors.push(...e.issues.map(i => (Object.assign(Object.assign({}, i), { path: `addressing.${i.path}` }))));
82
+ }
83
+ return errors;
84
+ }
85
+ return [issue('ADDRESSING_BY_INVALID', 'addressing.by')];
86
+ }
87
+ // ── 주소 읽기 ───────────────────────────────────────────────────────────────
88
+ /**
89
+ * 레코드에서 주소를 읽는다. 범위 검사는 하지 않는다 (resolveOccupancy 가 한다).
90
+ * 필드 누락·패턴 불일치는 UNKNOWN, 0·음수·소수·빈 문자열은 ERROR.
91
+ * {tier} 가 없는 패턴과 tierField 를 생략한 FIELDS 는 층 1로 읽는다.
92
+ */
93
+ export function readAddress(record, addressing) {
94
+ if (!addressing)
95
+ return { kind: 'UNKNOWN', raw: undefined };
96
+ if (addressing.by === 'FIELDS') {
97
+ const rawSlot = record === null || record === void 0 ? void 0 : record[addressing.slotField];
98
+ const rawTier = addressing.tierField ? record === null || record === void 0 ? void 0 : record[addressing.tierField] : 1;
99
+ const raw = addressing.tierField ? { tier: rawTier, slot: rawSlot } : { slot: rawSlot };
100
+ if (missing(rawSlot) || missing(rawTier))
101
+ return { kind: 'UNKNOWN', raw };
102
+ const tier = positiveInt(rawTier);
103
+ const slot = positiveInt(rawSlot);
104
+ if (tier === null || slot === null)
105
+ return { kind: 'ERROR', raw, reason: 'NOT_POSITIVE_INTEGER' };
106
+ return { kind: 'OK', address: { tier, slot } };
107
+ }
108
+ const raw = record === null || record === void 0 ? void 0 : record[addressing.field];
109
+ if (typeof raw !== 'string')
110
+ return { kind: 'UNKNOWN', raw };
111
+ const { regex, tierGroup, slotGroup } = compileAddressPattern(addressing.pattern);
112
+ const m = regex.exec(raw);
113
+ if (!m)
114
+ return { kind: 'UNKNOWN', raw };
115
+ const tier = tierGroup ? positiveInt(m[tierGroup]) : 1;
116
+ const slot = positiveInt(m[slotGroup]);
117
+ if (tier === null || slot === null)
118
+ return { kind: 'ERROR', raw, reason: 'NOT_POSITIVE_INTEGER' };
119
+ return { kind: 'OK', address: { tier, slot } };
120
+ }
121
+ export const slotKey = (a) => `${a.tier}:${a.slot}`;
122
+ /**
123
+ * v2 홀더 데이터 → 아래에서 위로 놓을 Carrier 목록 (3.6.1절, CR-11).
124
+ * MULTICARRIERS[] 를 배열 순서대로 아래부터, 대표 CARRIERNAME 을 맨 위에 둔다.
125
+ * MULTICARRIERS 는 JSON 문자열로 올 수 있다. CARRIERNAME 이 없는 항목은 건너뛴다.
126
+ */
127
+ export function incomingFromV2(data) {
128
+ if (!data)
129
+ return [];
130
+ const out = [];
131
+ const list = parseIfJsonString(data.MULTICARRIERS);
132
+ if (Array.isArray(list))
133
+ for (const item of list)
134
+ if (item && typeof item === 'object' && item.CARRIERNAME)
135
+ out.push({ name: String(item.CARRIERNAME), type: item.CARRIERTYPE, record: item, stackOrder: out.length });
136
+ if (data.CARRIERNAME)
137
+ out.push({ name: String(data.CARRIERNAME), type: data.CARRIERTYPE, record: data, stackOrder: out.length });
138
+ return out;
139
+ }
140
+ /**
141
+ * 홀더 점유 스냅샷 하나를 한 번에 배정한다.
142
+ * 순서: 주소 해석 → 정상 자리 배정 → 자리별 적층 한도 검사 → 예외 목록 작성.
143
+ * previous 는 직전 결과이며, 메시지에 적층 순서가 없을 때 기존 순서를 지키는 데 쓴다.
144
+ * 각 Carrier 는 slots·addressUnknown·addressError 셋 중 한 곳에만 들어간다.
145
+ */
146
+ export function resolveOccupancy(layout, carriers, previous) {
147
+ const result = { slots: new Map(), addressUnknown: [], addressError: [], dataErrors: [] };
148
+ // 같은 이름: 한 번만 그린다. 주소가 다르면 주소 오류
149
+ const byName = new Map();
150
+ const conflicted = new Set();
151
+ carriers.forEach((carrier, arrival) => {
152
+ const read = readAddress(carrier.record, layout.addressing);
153
+ const seen = byName.get(carrier.name);
154
+ if (!seen) {
155
+ byName.set(carrier.name, { carrier, read, arrival });
156
+ return;
157
+ }
158
+ result.dataErrors.push(issue('DUPLICATE_CARRIER', '', { name: carrier.name }));
159
+ const same = seen.read.kind === read.kind &&
160
+ (read.kind !== 'OK' || slotKey(read.address) === slotKey(seen.read.address));
161
+ if (!same)
162
+ conflicted.add(carrier.name);
163
+ });
164
+ const previousOrder = new Map();
165
+ previous === null || previous === void 0 ? void 0 : previous.slots.forEach(list => list.forEach((c, i) => previousOrder.set(c.name, i)));
166
+ const pending = new Map();
167
+ for (const [name, { carrier, read, arrival }] of byName) {
168
+ const exception = { name, type: carrier.type, raw: 'raw' in read ? read.raw : undefined };
169
+ if (conflicted.has(name)) {
170
+ result.addressError.push(Object.assign(Object.assign({}, exception), { raw: carrier.record, reason: 'DUPLICATE_CONFLICT' }));
171
+ }
172
+ else if (read.kind === 'UNKNOWN') {
173
+ result.addressUnknown.push(exception);
174
+ }
175
+ else if (read.kind === 'ERROR') {
176
+ result.addressError.push(Object.assign(Object.assign({}, exception), { reason: read.reason }));
177
+ }
178
+ else if (!resolveSlot(layout, read.address)) {
179
+ result.addressError.push(Object.assign(Object.assign({}, exception), { raw: read.address, reason: 'OUT_OF_RANGE' }));
180
+ }
181
+ else {
182
+ const key = slotKey(read.address);
183
+ if (!pending.has(key))
184
+ pending.set(key, []);
185
+ pending.get(key).push({ carrier, arrival });
186
+ }
187
+ }
188
+ // 적층 순서: 메시지 순서 우선. 없으면 기존 순서를 지키고 새 Carrier 는 도착 순서로 위에 얹는다
189
+ for (const [key, list] of pending) {
190
+ const [tier, slot] = key.split(':').map(Number);
191
+ const limit = resolveSlot(layout, { tier, slot }).stackLimit;
192
+ const rank = (e) => {
193
+ if (e.carrier.stackOrder !== undefined)
194
+ return [0, e.carrier.stackOrder, e.arrival];
195
+ const prev = previousOrder.get(e.carrier.name);
196
+ return prev !== undefined ? [1, prev, e.arrival] : [2, e.arrival, 0];
197
+ };
198
+ list.sort((a, b) => {
199
+ const ra = rank(a);
200
+ const rb = rank(b);
201
+ return ra[0] - rb[0] || ra[1] - rb[1] || ra[2] - rb[2];
202
+ });
203
+ const placed = list
204
+ .slice(0, limit)
205
+ .map(({ carrier }) => ({ name: carrier.name, type: carrier.type, address: { tier, slot } }));
206
+ result.slots.set(key, placed);
207
+ for (const { carrier } of list.slice(limit))
208
+ result.addressError.push({
209
+ name: carrier.name,
210
+ type: carrier.type,
211
+ raw: { tier, slot },
212
+ reason: 'STACK_LIMIT_EXCEEDED'
213
+ });
214
+ }
215
+ return result;
216
+ }
217
+ //# sourceMappingURL=load-address.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-address.js","sourceRoot":"","sources":["../../src/utils/load-address.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 실시간 데이터의 적재 주소 해석과 점유 배정 (설계 02번 3.6절, 3.6.1절)\n * Carrier 는 자리 번호에 고정된다. 주소를 못 읽으면 빈 자리에 채우지 않고 예외로 둔다.\n * 레이아웃(층·자리)과 자리 계산은 figure-model 의 것이다(ADR-0051 4.1절 결정 8). 여기 남는 것은 MCS 의 몫이다:\n * 실시간 레코드에서 주소를 읽는 규칙(FIELDS·PATTERN)과 점유 배정·예외.\n */\n\nimport { resolveSlot, type SlotAddress, type SlotLayout } from '@hatiolab/figure-model'\nimport { AddressPatternError, LayoutIssue, issue } from './layout-issue'\nimport { parseIfJsonString } from './data-value'\n\nexport type LoadAddress = SlotAddress\n\n/** 실시간 데이터에서 주소를 읽는 규칙 (3.6절) */\nexport type LoadAddressing =\n | { by: 'FIELDS'; tierField?: string; slotField: string }\n | { by: 'PATTERN'; field: string; pattern: string }\n\n/** 홀더의 적재 레이아웃: figure-model 의 층·자리에 주소 읽기 규칙을 더한 배치 값 */\nexport interface LoadLayout extends SlotLayout {\n addressing?: LoadAddressing\n}\n\nexport type AddressRead =\n | { kind: 'OK'; address: LoadAddress }\n | { kind: 'UNKNOWN'; raw: unknown }\n | { kind: 'ERROR'; raw: unknown; reason: AddressErrorReason }\n\nexport type AddressErrorReason = 'NOT_POSITIVE_INTEGER' | 'OUT_OF_RANGE' | 'STACK_LIMIT_EXCEEDED' | 'DUPLICATE_CONFLICT'\n\n/** 양의 정수 또는 양의 정수 문자열만 받는다. 그 밖의 값은 null */\nfunction positiveInt(v: unknown): number | null {\n if (typeof v === 'number') return Number.isSafeInteger(v) && v > 0 ? v : null\n if (typeof v === 'string' && /^\\d+$/.test(v.trim())) {\n const n = Number(v.trim())\n return Number.isSafeInteger(n) && n > 0 ? n : null\n }\n return null\n}\n\nconst missing = (v: unknown) => v === undefined || v === null\n\n// ── PATTERN 문법 ────────────────────────────────────────────────────────────\n\nconst TOKEN = /\\{([A-Za-z_][A-Za-z0-9_]*)(?::(0\\d+))?\\}/g\n\nexport interface CompiledPattern {\n regex: RegExp\n tierGroup?: number\n slotGroup?: number\n}\n\n/** 위치 문자열 패턴을 정규식으로 바꾼다. 문법이 틀리면 AddressPatternError(모든 문제 목록)를 던진다 */\nexport function compileAddressPattern(pattern: string): CompiledPattern {\n if (typeof pattern !== 'string' || !pattern)\n throw new AddressPatternError([issue('ADDRESS_PATTERN_EMPTY', 'pattern')])\n const errors: LayoutIssue[] = []\n let source = '^'\n let last = 0\n let group = 0\n let tierGroup: number | undefined\n let slotGroup: number | undefined\n const escape = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n for (const m of pattern.matchAll(TOKEN)) {\n const literal = pattern.slice(last, m.index)\n if (/[{}]/.test(literal)) errors.push(issue('ADDRESS_PATTERN_SYNTAX', 'pattern', { text: literal }))\n source += escape(literal)\n const [, name, width] = m\n group++\n if (name === 'tier' || name === 'slot') {\n if ((name === 'tier' ? tierGroup : slotGroup) !== undefined)\n errors.push(issue('ADDRESS_PATTERN_TOKEN_REPEATED', 'pattern', { token: name }))\n if (name === 'tier') tierGroup = group\n else slotGroup = group\n source += width ? `(\\\\d{${width.length}})` : '(\\\\d+)'\n } else {\n if (width) errors.push(issue('ADDRESS_PATTERN_WIDTH_NOT_ALLOWED', 'pattern', { token: name }))\n source += '(.+?)'\n }\n last = m.index! + m[0].length\n }\n const tail = pattern.slice(last)\n if (/[{}]/.test(tail)) errors.push(issue('ADDRESS_PATTERN_SYNTAX', 'pattern', { text: tail }))\n source += escape(tail) + '$'\n if (slotGroup === undefined) errors.push(issue('ADDRESS_PATTERN_SLOT_REQUIRED', 'pattern'))\n if (errors.length) throw new AddressPatternError(errors)\n return { regex: new RegExp(source), tierGroup, slotGroup }\n}\n\n/** 레이아웃의 addressing 설정 오류 목록. 경로는 'addressing.' 으로 시작한다. 빈 배열이면 올바르다 */\nexport function validateAddressing(addressing: LoadAddressing | undefined): LayoutIssue[] {\n if (!addressing) return []\n if (addressing.by === 'FIELDS') return addressing.slotField ? [] : [issue('FIELD_REQUIRED', 'addressing.slotField')]\n if (addressing.by === 'PATTERN') {\n const errors = addressing.field ? [] : [issue('FIELD_REQUIRED', 'addressing.field')]\n try {\n compileAddressPattern(addressing.pattern)\n } catch (e) {\n if (!(e instanceof AddressPatternError)) throw e\n errors.push(...e.issues.map(i => ({ ...i, path: `addressing.${i.path}` })))\n }\n return errors\n }\n return [issue('ADDRESSING_BY_INVALID', 'addressing.by')]\n}\n\n// ── 주소 읽기 ───────────────────────────────────────────────────────────────\n\n/**\n * 레코드에서 주소를 읽는다. 범위 검사는 하지 않는다 (resolveOccupancy 가 한다).\n * 필드 누락·패턴 불일치는 UNKNOWN, 0·음수·소수·빈 문자열은 ERROR.\n * {tier} 가 없는 패턴과 tierField 를 생략한 FIELDS 는 층 1로 읽는다.\n */\nexport function readAddress(record: Record<string, unknown>, addressing: LoadAddressing | undefined): AddressRead {\n if (!addressing) return { kind: 'UNKNOWN', raw: undefined }\n\n if (addressing.by === 'FIELDS') {\n const rawSlot = record?.[addressing.slotField]\n const rawTier = addressing.tierField ? record?.[addressing.tierField] : 1\n const raw = addressing.tierField ? { tier: rawTier, slot: rawSlot } : { slot: rawSlot }\n if (missing(rawSlot) || missing(rawTier)) return { kind: 'UNKNOWN', raw }\n const tier = positiveInt(rawTier)\n const slot = positiveInt(rawSlot)\n if (tier === null || slot === null) return { kind: 'ERROR', raw, reason: 'NOT_POSITIVE_INTEGER' }\n return { kind: 'OK', address: { tier, slot } }\n }\n\n const raw = record?.[addressing.field]\n if (typeof raw !== 'string') return { kind: 'UNKNOWN', raw }\n const { regex, tierGroup, slotGroup } = compileAddressPattern(addressing.pattern)\n const m = regex.exec(raw)\n if (!m) return { kind: 'UNKNOWN', raw }\n const tier = tierGroup ? positiveInt(m[tierGroup]) : 1\n const slot = positiveInt(m[slotGroup!])\n if (tier === null || slot === null) return { kind: 'ERROR', raw, reason: 'NOT_POSITIVE_INTEGER' }\n return { kind: 'OK', address: { tier, slot } }\n}\n\n// ── 점유 배정 ───────────────────────────────────────────────────────────────\n\nexport interface IncomingCarrier {\n name: string\n type?: string\n /** 주소를 읽을 원본 레코드 */\n record: Record<string, unknown>\n /** 메시지가 주는 적층 순서 (아래가 작다). 없으면 생략 */\n stackOrder?: number\n}\n\nexport interface PlacedCarrier {\n name: string\n type?: string\n address: LoadAddress\n}\n\nexport interface ExceptionCarrier {\n name: string\n type?: string\n raw: unknown\n reason?: AddressErrorReason\n}\n\nexport interface OccupancyResult {\n /** 'tier:slot' → 아래에서 위로 정상 점유 */\n slots: Map<string, PlacedCarrier[]>\n /** 주소를 읽지 못해 예외 표시 영역에 두는 Carrier */\n addressUnknown: ExceptionCarrier[]\n /** 범위 밖·적층 한도 초과·중복 충돌로 예외 표시 영역에 두는 Carrier */\n addressError: ExceptionCarrier[]\n /** 데이터 오류 기록 (같은 이름이 두 번 온 경우 등) */\n dataErrors: LayoutIssue[]\n}\n\nexport const slotKey = (a: LoadAddress) => `${a.tier}:${a.slot}`\n\n/**\n * v2 홀더 데이터 → 아래에서 위로 놓을 Carrier 목록 (3.6.1절, CR-11).\n * MULTICARRIERS[] 를 배열 순서대로 아래부터, 대표 CARRIERNAME 을 맨 위에 둔다.\n * MULTICARRIERS 는 JSON 문자열로 올 수 있다. CARRIERNAME 이 없는 항목은 건너뛴다.\n */\nexport function incomingFromV2(data: Record<string, any> | null | undefined): IncomingCarrier[] {\n if (!data) return []\n const out: IncomingCarrier[] = []\n const list = parseIfJsonString(data.MULTICARRIERS)\n if (Array.isArray(list))\n for (const item of list)\n if (item && typeof item === 'object' && item.CARRIERNAME)\n out.push({ name: String(item.CARRIERNAME), type: item.CARRIERTYPE, record: item, stackOrder: out.length })\n if (data.CARRIERNAME)\n out.push({ name: String(data.CARRIERNAME), type: data.CARRIERTYPE, record: data, stackOrder: out.length })\n return out\n}\n\n/**\n * 홀더 점유 스냅샷 하나를 한 번에 배정한다.\n * 순서: 주소 해석 → 정상 자리 배정 → 자리별 적층 한도 검사 → 예외 목록 작성.\n * previous 는 직전 결과이며, 메시지에 적층 순서가 없을 때 기존 순서를 지키는 데 쓴다.\n * 각 Carrier 는 slots·addressUnknown·addressError 셋 중 한 곳에만 들어간다.\n */\nexport function resolveOccupancy(layout: LoadLayout, carriers: IncomingCarrier[], previous?: OccupancyResult): OccupancyResult {\n const result: OccupancyResult = { slots: new Map(), addressUnknown: [], addressError: [], dataErrors: [] }\n\n // 같은 이름: 한 번만 그린다. 주소가 다르면 주소 오류\n const byName = new Map<string, { carrier: IncomingCarrier; read: AddressRead; arrival: number }>()\n const conflicted = new Set<string>()\n carriers.forEach((carrier, arrival) => {\n const read = readAddress(carrier.record, layout.addressing)\n const seen = byName.get(carrier.name)\n if (!seen) {\n byName.set(carrier.name, { carrier, read, arrival })\n return\n }\n result.dataErrors.push(issue('DUPLICATE_CARRIER', '', { name: carrier.name }))\n const same =\n seen.read.kind === read.kind &&\n (read.kind !== 'OK' || slotKey(read.address) === slotKey((seen.read as { address: LoadAddress }).address))\n if (!same) conflicted.add(carrier.name)\n })\n\n const previousOrder = new Map<string, number>()\n previous?.slots.forEach(list => list.forEach((c, i) => previousOrder.set(c.name, i)))\n\n const pending = new Map<string, { carrier: IncomingCarrier; arrival: number }[]>()\n for (const [name, { carrier, read, arrival }] of byName) {\n const exception = { name, type: carrier.type, raw: 'raw' in read ? read.raw : undefined }\n if (conflicted.has(name)) {\n result.addressError.push({ ...exception, raw: carrier.record, reason: 'DUPLICATE_CONFLICT' })\n } else if (read.kind === 'UNKNOWN') {\n result.addressUnknown.push(exception)\n } else if (read.kind === 'ERROR') {\n result.addressError.push({ ...exception, reason: read.reason })\n } else if (!resolveSlot(layout, read.address)) {\n result.addressError.push({ ...exception, raw: read.address, reason: 'OUT_OF_RANGE' })\n } else {\n const key = slotKey(read.address)\n if (!pending.has(key)) pending.set(key, [])\n pending.get(key)!.push({ carrier, arrival })\n }\n }\n\n // 적층 순서: 메시지 순서 우선. 없으면 기존 순서를 지키고 새 Carrier 는 도착 순서로 위에 얹는다\n for (const [key, list] of pending) {\n const [tier, slot] = key.split(':').map(Number)\n const limit = resolveSlot(layout, { tier, slot })!.stackLimit\n const rank = (e: { carrier: IncomingCarrier; arrival: number }) => {\n if (e.carrier.stackOrder !== undefined) return [0, e.carrier.stackOrder, e.arrival]\n const prev = previousOrder.get(e.carrier.name)\n return prev !== undefined ? [1, prev, e.arrival] : [2, e.arrival, 0]\n }\n list.sort((a, b) => {\n const ra = rank(a)\n const rb = rank(b)\n return ra[0] - rb[0] || ra[1] - rb[1] || ra[2] - rb[2]\n })\n const placed = list\n .slice(0, limit)\n .map(({ carrier }) => ({ name: carrier.name, type: carrier.type, address: { tier, slot } }))\n result.slots.set(key, placed)\n for (const { carrier } of list.slice(limit))\n result.addressError.push({\n name: carrier.name,\n type: carrier.type,\n raw: { tier, slot },\n reason: 'STACK_LIMIT_EXCEEDED'\n })\n }\n\n return result\n}\n"]}
@@ -10,6 +10,7 @@
10
10
  * - carrier : CARRIER_FULL / CARRIER_EMPTY / CARRIER_EMPTY_EMPTY
11
11
  * - abnormal : CARRIER_NORMAL / CARRIER_ABNORMAL
12
12
  */
13
+ import { legendColor, legendEntryOf } from '../features/mcs-status-default';
13
14
  /** carrier 세그먼트 색상 (2D/3D 공통). LEGEND_CARRIER 기본값과 동일 — 테마 미지정 시 폴백. */
14
15
  export const CARRIER_COLORS = {
15
16
  full: '#4AA7FE',
@@ -18,11 +19,11 @@ export const CARRIER_COLORS = {
18
19
  };
19
20
  /**
20
21
  * carrier legend(테마)에서 full/empty/emptyEmpty 색상 추출.
21
- * carrier 컴포넌트의 색 취득 로직(statusColor = legend[status] || legend.default)과 동일하게,
22
+ * carrier 컴포넌트의 색 취득 로직(statusColor, legendEntryOf)과 동일하게,
22
23
  * status 키(FULL/EMPTY/EMPTYEMPTY)로 직접 조회한다. 키/default 모두 없으면 CARRIER_COLORS 로 폴백.
23
24
  */
24
25
  export function carrierColorsFromLegend(legend) {
25
- const pick = (key, fallback) => (legend && (legend[key] || legend.default)) || fallback;
26
+ const pick = (key, fallback) => (legend && (legendColor(legendEntryOf(key, legend)) || legendColor(legend.default))) || fallback;
26
27
  return {
27
28
  full: pick('FULL', CARRIER_COLORS.full),
28
29
  empty: pick('EMPTY', CARRIER_COLORS.empty),
@@ -1 +1 @@
1
- {"version":3,"file":"stocker-gauge.js","sourceRoot":"","sources":["../../src/utils/stocker-gauge.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 스토커 게이지 세그먼트 계산 (2D/3D 공통 단일 소스).\n *\n * 2D(StockerCapacityBar) 와 3D(machine-3d buildGauge) 가 각자 세그먼트 비율/색상을\n * 중복 계산하면 필드/색상 drift 가 발생한다(실제로 carrier 필드명이 어긋난 전례).\n * 이 모듈이 carrier/abnormal 세그먼트 계산의 단일 소스이며 순수 함수라 단위 테스트가\n * 용이하다. 표현(라벨 포맷·watermark·테두리색)은 렌더러별로 다르므로 여기서 다루지 않는다.\n *\n * canonical 데이터 필드(2D 기준):\n * - carrier : CARRIER_FULL / CARRIER_EMPTY / CARRIER_EMPTY_EMPTY\n * - abnormal : CARRIER_NORMAL / CARRIER_ABNORMAL\n */\n\nimport { Legend } from '../features/mcs-status-default'\n\nexport type StockerGaugeMode = 'capacity' | 'carrier' | 'abnormal'\n\nexport interface GaugeSegment {\n /** 안정적 식별 토큰 (대문자, 클릭 이벤트 payload·데이터 필드 관례와 일치). */\n key: 'FULL' | 'EMPTY' | 'EMPTYEMPTY' | 'NORMAL' | 'ABNORMAL'\n /** 세그먼트 라벨(2D per-segment 표시용, '\\n' 은 줄바꿈). */\n label: string\n /** 카운트 원본값. */\n count: number\n /** 전체 대비 비율 (0~1). total 이 0 이면 0. */\n ratio: number\n /** 채움 색상. */\n color: string\n}\n\n/** carrier 세그먼트 색상 (2D/3D 공통). LEGEND_CARRIER 기본값과 동일 — 테마 미지정 시 폴백. */\nexport const CARRIER_COLORS = {\n full: '#4AA7FE',\n empty: '#8BDA5B',\n emptyEmpty: '#019D59'\n}\n\nexport interface CarrierColors {\n full: string\n empty: string\n emptyEmpty: string\n}\n\n/**\n * carrier legend(테마)에서 full/empty/emptyEmpty 색상 추출.\n * carrier 컴포넌트의 색 취득 로직(statusColor = legend[status] || legend.default)과 동일하게,\n * status 키(FULL/EMPTY/EMPTYEMPTY)로 직접 조회한다. 키/default 모두 없으면 CARRIER_COLORS 로 폴백.\n */\nexport function carrierColorsFromLegend(legend?: Legend): CarrierColors {\n const pick = (key: string, fallback: string) => (legend && (legend[key] || legend.default)) || fallback\n return {\n full: pick('FULL', CARRIER_COLORS.full),\n empty: pick('EMPTY', CARRIER_COLORS.empty),\n emptyEmpty: pick('EMPTYEMPTY', CARRIER_COLORS.emptyEmpty)\n }\n}\n\n/** abnormal 세그먼트 색상 (2D/3D 공통). */\nexport const ABNORMAL_COLORS = {\n normal: '#8BDA5B',\n abnormal: '#F4B4B4'\n}\n\nfunction num(v: unknown): number {\n const n = Number(v)\n return Number.isFinite(n) && n > 0 ? n : 0\n}\n\n/**\n * 캐리어 상태 세그먼트 (Full / Empty / EmptyEmpty). total 0 이면 모든 ratio 0.\n * colors 미지정 시 CARRIER_COLORS(기본). 테마 색은 carrierColorsFromLegend 로 만들어 전달.\n */\nexport function carrierSegments(\n full: unknown,\n empty: unknown,\n emptyEmpty: unknown,\n colors: CarrierColors = CARRIER_COLORS\n): GaugeSegment[] {\n const f = num(full)\n const e = num(empty)\n const ee = num(emptyEmpty)\n const total = f + e + ee\n const r = (c: number) => (total > 0 ? c / total : 0)\n return [\n { key: 'FULL', label: 'Full', count: f, ratio: r(f), color: colors.full },\n { key: 'EMPTY', label: 'Empty', count: e, ratio: r(e), color: colors.empty },\n { key: 'EMPTYEMPTY', label: 'Empty\\nEmpty', count: ee, ratio: r(ee), color: colors.emptyEmpty }\n ]\n}\n\n/** 캐리어 정상/비정상 세그먼트 (Normal / Abnormal). total 0 이면 모든 ratio 0. */\nexport function abnormalSegments(normal: unknown, abnormal: unknown): GaugeSegment[] {\n const n = num(normal)\n const a = num(abnormal)\n const total = n + a\n const r = (c: number) => (total > 0 ? c / total : 0)\n return [\n { key: 'NORMAL', label: 'Normal', count: n, ratio: r(n), color: ABNORMAL_COLORS.normal },\n { key: 'ABNORMAL', label: 'Abnormal', count: a, ratio: r(a), color: ABNORMAL_COLORS.abnormal }\n ]\n}\n\n/**\n * 누적 비율 위치(pos, 0..span 범위)가 속한 세그먼트를 반환한다. ratio 0 세그먼트는\n * 화면에 없으므로 건너뛴다. 경계(pos == span)·초과 클릭은 마지막 비영 세그먼트로 귀속하고,\n * 데이터가 전혀 없으면(모든 ratio 0) null.\n *\n * renderSegmentGauge 의 배치와 동일 규칙(누적 채움)이며, 2D 렌더와 클릭 판정이 이 함수를\n * 공유해 drift 를 막는다. 방향(landscape 왼→오 / portrait 아래→위)은 호출부가 pos 로 흡수한다.\n */\nexport function pickSegmentAt(segments: GaugeSegment[], pos: number, span: number): GaugeSegment | null {\n let acc = 0\n let lastNonZero: GaugeSegment | null = null\n for (const seg of segments) {\n const segLen = span * seg.ratio\n if (segLen <= 0) continue\n lastNonZero = seg\n if (pos < acc + segLen) return seg\n acc += segLen\n }\n return lastNonZero\n}\n\n/** 캐리어 세그먼트 — canonical raw data(CARRIER_*) 에서 직접. 3D 처럼 raw data 소스용. */\nexport function carrierSegmentsFromData(data: any, colors?: CarrierColors): GaugeSegment[] {\n const d = data && typeof data === 'object' ? data : {}\n return carrierSegments(d.CARRIER_FULL, d.CARRIER_EMPTY, d.CARRIER_EMPTY_EMPTY, colors)\n}\n\n/** 비정상 세그먼트 — canonical raw data(CARRIER_*) 에서 직접. */\nexport function abnormalSegmentsFromData(data: any): GaugeSegment[] {\n const d = data && typeof data === 'object' ? data : {}\n return abnormalSegments(d.CARRIER_NORMAL, d.CARRIER_ABNORMAL)\n}\n"]}
1
+ {"version":3,"file":"stocker-gauge.js","sourceRoot":"","sources":["../../src/utils/stocker-gauge.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 스토커 게이지 세그먼트 계산 (2D/3D 공통 단일 소스).\n *\n * 2D(StockerCapacityBar) 와 3D(machine-3d buildGauge) 가 각자 세그먼트 비율/색상을\n * 중복 계산하면 필드/색상 drift 가 발생한다(실제로 carrier 필드명이 어긋난 전례).\n * 이 모듈이 carrier/abnormal 세그먼트 계산의 단일 소스이며 순수 함수라 단위 테스트가\n * 용이하다. 표현(라벨 포맷·watermark·테두리색)은 렌더러별로 다르므로 여기서 다루지 않는다.\n *\n * canonical 데이터 필드(2D 기준):\n * - carrier : CARRIER_FULL / CARRIER_EMPTY / CARRIER_EMPTY_EMPTY\n * - abnormal : CARRIER_NORMAL / CARRIER_ABNORMAL\n */\n\nimport { Legend, legendColor, legendEntryOf } from '../features/mcs-status-default'\n\nexport type StockerGaugeMode = 'capacity' | 'carrier' | 'abnormal'\n\nexport interface GaugeSegment {\n /** 안정적 식별 토큰 (대문자, 클릭 이벤트 payload·데이터 필드 관례와 일치). */\n key: 'FULL' | 'EMPTY' | 'EMPTYEMPTY' | 'NORMAL' | 'ABNORMAL'\n /** 세그먼트 라벨(2D per-segment 표시용, '\\n' 은 줄바꿈). */\n label: string\n /** 카운트 원본값. */\n count: number\n /** 전체 대비 비율 (0~1). total 이 0 이면 0. */\n ratio: number\n /** 채움 색상. */\n color: string\n}\n\n/** carrier 세그먼트 색상 (2D/3D 공통). LEGEND_CARRIER 기본값과 동일 — 테마 미지정 시 폴백. */\nexport const CARRIER_COLORS = {\n full: '#4AA7FE',\n empty: '#8BDA5B',\n emptyEmpty: '#019D59'\n}\n\nexport interface CarrierColors {\n full: string\n empty: string\n emptyEmpty: string\n}\n\n/**\n * carrier legend(테마)에서 full/empty/emptyEmpty 색상 추출.\n * carrier 컴포넌트의 색 취득 로직(statusColor, legendEntryOf)과 동일하게,\n * status 키(FULL/EMPTY/EMPTYEMPTY)로 직접 조회한다. 키/default 모두 없으면 CARRIER_COLORS 로 폴백.\n */\nexport function carrierColorsFromLegend(legend?: Legend): CarrierColors {\n const pick = (key: string, fallback: string) => (legend && (legendColor(legendEntryOf(key, legend)) || legendColor(legend.default))) || fallback\n return {\n full: pick('FULL', CARRIER_COLORS.full),\n empty: pick('EMPTY', CARRIER_COLORS.empty),\n emptyEmpty: pick('EMPTYEMPTY', CARRIER_COLORS.emptyEmpty)\n }\n}\n\n/** abnormal 세그먼트 색상 (2D/3D 공통). */\nexport const ABNORMAL_COLORS = {\n normal: '#8BDA5B',\n abnormal: '#F4B4B4'\n}\n\nfunction num(v: unknown): number {\n const n = Number(v)\n return Number.isFinite(n) && n > 0 ? n : 0\n}\n\n/**\n * 캐리어 상태 세그먼트 (Full / Empty / EmptyEmpty). total 0 이면 모든 ratio 0.\n * colors 미지정 시 CARRIER_COLORS(기본). 테마 색은 carrierColorsFromLegend 로 만들어 전달.\n */\nexport function carrierSegments(\n full: unknown,\n empty: unknown,\n emptyEmpty: unknown,\n colors: CarrierColors = CARRIER_COLORS\n): GaugeSegment[] {\n const f = num(full)\n const e = num(empty)\n const ee = num(emptyEmpty)\n const total = f + e + ee\n const r = (c: number) => (total > 0 ? c / total : 0)\n return [\n { key: 'FULL', label: 'Full', count: f, ratio: r(f), color: colors.full },\n { key: 'EMPTY', label: 'Empty', count: e, ratio: r(e), color: colors.empty },\n { key: 'EMPTYEMPTY', label: 'Empty\\nEmpty', count: ee, ratio: r(ee), color: colors.emptyEmpty }\n ]\n}\n\n/** 캐리어 정상/비정상 세그먼트 (Normal / Abnormal). total 0 이면 모든 ratio 0. */\nexport function abnormalSegments(normal: unknown, abnormal: unknown): GaugeSegment[] {\n const n = num(normal)\n const a = num(abnormal)\n const total = n + a\n const r = (c: number) => (total > 0 ? c / total : 0)\n return [\n { key: 'NORMAL', label: 'Normal', count: n, ratio: r(n), color: ABNORMAL_COLORS.normal },\n { key: 'ABNORMAL', label: 'Abnormal', count: a, ratio: r(a), color: ABNORMAL_COLORS.abnormal }\n ]\n}\n\n/**\n * 누적 비율 위치(pos, 0..span 범위)가 속한 세그먼트를 반환한다. ratio 0 세그먼트는\n * 화면에 없으므로 건너뛴다. 경계(pos == span)·초과 클릭은 마지막 비영 세그먼트로 귀속하고,\n * 데이터가 전혀 없으면(모든 ratio 0) null.\n *\n * renderSegmentGauge 의 배치와 동일 규칙(누적 채움)이며, 2D 렌더와 클릭 판정이 이 함수를\n * 공유해 drift 를 막는다. 방향(landscape 왼→오 / portrait 아래→위)은 호출부가 pos 로 흡수한다.\n */\nexport function pickSegmentAt(segments: GaugeSegment[], pos: number, span: number): GaugeSegment | null {\n let acc = 0\n let lastNonZero: GaugeSegment | null = null\n for (const seg of segments) {\n const segLen = span * seg.ratio\n if (segLen <= 0) continue\n lastNonZero = seg\n if (pos < acc + segLen) return seg\n acc += segLen\n }\n return lastNonZero\n}\n\n/** 캐리어 세그먼트 — canonical raw data(CARRIER_*) 에서 직접. 3D 처럼 raw data 소스용. */\nexport function carrierSegmentsFromData(data: any, colors?: CarrierColors): GaugeSegment[] {\n const d = data && typeof data === 'object' ? data : {}\n return carrierSegments(d.CARRIER_FULL, d.CARRIER_EMPTY, d.CARRIER_EMPTY_EMPTY, colors)\n}\n\n/** 비정상 세그먼트 — canonical raw data(CARRIER_*) 에서 직접. */\nexport function abnormalSegmentsFromData(data: any): GaugeSegment[] {\n const d = data && typeof data === 'object' ? data : {}\n return abnormalSegments(d.CARRIER_NORMAL, d.CARRIER_ABNORMAL)\n}\n"]}
package/jest.config.js CHANGED
@@ -3,8 +3,16 @@ module.exports = {
3
3
  roots: ['<rootDir>/packages/machine'],
4
4
  moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
5
5
  transform: {
6
- '^.+\\.tsx?$': 'babel-jest'
6
+ '^.+\\.tsx?$': 'babel-jest',
7
+ // 공통 계산(@hatiolab/figure-model)과 things-scene 의 node 판(공통 아이템 믹스인)은 ESM 이라 babel 로 바꿔 읽는다
8
+ '^.+\\.m?js$': 'babel-jest'
7
9
  },
10
+ transformIgnorePatterns: ['/node_modules/(?!@hatiolab/(figure-model|things-scene)/)'],
8
11
  testRegex: 'packages/machine/.*\\.test\\.tsx?$',
12
+ // 소스가 ESM 식으로 쓴 './x.js' 와 tsconfig baseUrl 별칭 'src/x' 를 ts 파일로 푼다
13
+ moduleNameMapper: {
14
+ '^(\\.{1,2}/.*)\\.js$': '$1',
15
+ '^src/(.*)$': '<rootDir>/packages/machine/src/$1'
16
+ },
9
17
  testEnvironment: 'node'
10
18
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@fmsim/machine",
3
3
  "description": "Layout View를 구성하는 설비(machine)들과 유닛을 구현한 모듈입니다.",
4
4
  "author": "heartyoh",
5
- "version": "2.0.1",
5
+ "version": "3.0.0-beta.2",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
8
8
  "things-scene": true,
@@ -18,16 +18,19 @@
18
18
  "scripts": {
19
19
  "serve": "tsc && things-factory-dev",
20
20
  "start": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wds\"",
21
- "build": "tsc; exit 0",
22
- "prepublish": "tsc; exit 0",
21
+ "build": "tsc",
22
+ "prepublish": "tsc",
23
23
  "lint": "eslint --ext .ts,.html . --ignore-path .gitignore && prettier \"**/*.ts\" --check --ignore-path .gitignore",
24
24
  "format": "eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --write --ignore-path .gitignore",
25
25
  "test": "jest --config jest.config.js",
26
26
  "migration": "things-factory-migration"
27
27
  },
28
28
  "dependencies": {
29
- "@fmsim/api": "^2.0.1",
30
- "@hatiolab/things-scene": "^10.0.0"
29
+ "@fmsim/api": "^3.0.0-beta.2",
30
+ "@fmsim/figure-capabilities": "^3.0.0-beta.2",
31
+ "@hatiolab/figure-model": "^0.12.0",
32
+ "@hatiolab/things-scene": "^10.1.90",
33
+ "@operato/slot-layout-editor": "^10.29.33"
31
34
  },
32
35
  "devDependencies": {
33
36
  "@hatiolab/prettier-config": "^1.0.0",
@@ -55,5 +58,5 @@
55
58
  "prettier --write"
56
59
  ]
57
60
  },
58
- "gitHead": "a5336bd252e398b021c4a025fad0eb3b501f35f2"
61
+ "gitHead": "23b7fedf8bb2b70b1150b963bd0f75d77a26052d"
59
62
  }
package/src/agv.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { ComponentNature, sceneComponent } from '@hatiolab/things-scene'
2
2
  import { themesColorRange } from '@fmsim/api'
3
3
  import MCSVehicle from './mcs-vehicle'
4
- import { getValueOnRanges } from './utils/get-value-on-ranges'
5
- import { BATTERY_RATE_LEGEND } from './features/mcs-status-default'
4
+ import { BATTERY_RATE_LEGEND, legendColor, legendRangeEntryOf } from './features/mcs-status-default'
6
5
 
7
6
  const NATURE: ComponentNature = {
8
7
  mutable: false,
@@ -55,7 +54,7 @@ export default class AGV extends MCSVehicle {
55
54
  }
56
55
 
57
56
  getBatteryColor(rate: number) {
58
- return getValueOnRanges(rate, this.getBatteryLegendTheme() || this.getBatteryLegendFallback())
57
+ return legendColor(legendRangeEntryOf(rate, this.getBatteryLegendTheme() || this.getBatteryLegendFallback()))
59
58
  }
60
59
 
61
60
  render(ctx: CanvasRenderingContext2D) {