@holoscript/plugin-manufacturing-qc 2.0.1 → 2.0.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.
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@holoscript/plugin-manufacturing-qc",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "main": "src/index.ts",
5
5
  "peerDependencies": {
6
- "@holoscript/core": "8.0.6"
6
+ "@holoscript/core": ">=8.0.0"
7
7
  },
8
8
  "license": "MIT",
9
9
  "scripts": {
10
10
  "test": "vitest run --passWithNoTests",
11
11
  "test:coverage": "vitest run --coverage --passWithNoTests"
12
12
  }
13
- }
13
+ }
@@ -80,7 +80,7 @@ describe('manufacturing-qc -> HoloScript runtime integration (spc)', () => {
80
80
  subgroups: HAND_SUBGROUPS,
81
81
  lsl: HAND_LSL,
82
82
  usl: HAND_USL,
83
- }) as never,
83
+ }) as never
84
84
  );
85
85
  await flush();
86
86
 
@@ -107,7 +107,7 @@ describe('manufacturing-qc -> HoloScript runtime integration (spc)', () => {
107
107
  subgroups: HAND_SUBGROUPS,
108
108
  lsl: HAND_LSL,
109
109
  usl: HAND_USL,
110
- }) as never,
110
+ }) as never
111
111
  );
112
112
  await flush();
113
113
 
@@ -124,7 +124,7 @@ describe('manufacturing-qc -> HoloScript runtime integration (spc)', () => {
124
124
  subgroups: HAND_SUBGROUPS,
125
125
  lsl: HAND_LSL,
126
126
  usl: HAND_USL,
127
- }) as never,
127
+ }) as never
128
128
  );
129
129
  await flush();
130
130
 
@@ -155,7 +155,7 @@ describe('manufacturing-qc -> HoloScript runtime integration (spc)', () => {
155
155
  subgroups: HAND_SUBGROUPS,
156
156
  lsl: 10,
157
157
  usl: 4,
158
- }) as never,
158
+ }) as never
159
159
  );
160
160
  await flush();
161
161
 
@@ -7,12 +7,7 @@
7
7
  */
8
8
 
9
9
  import { describe, it, expect } from 'vitest';
10
- import {
11
- buildSPCChart,
12
- computeCapability,
13
- buildSPCReceipt,
14
- type Subgroup,
15
- } from '../spc';
10
+ import { buildSPCChart, computeCapability, buildSPCReceipt, type Subgroup } from '../spc';
16
11
 
17
12
  // ─── Helpers ──────────────────────────────────────────────────────────────────
18
13
 
@@ -87,8 +82,7 @@ describe('buildSPCChart — xbar_r', () => {
87
82
  expect(result.primaryChart.ucl).toBeGreaterThan(result.primaryChart.centerLine);
88
83
  expect(result.primaryChart.lcl).toBeLessThan(result.primaryChart.centerLine);
89
84
  // At least 80% of subgroups should be in control for a stable reference process
90
- const inControlFraction =
91
- 1 - result.outOfControlCount / result.subgroupCount;
85
+ const inControlFraction = 1 - result.outOfControlCount / result.subgroupCount;
92
86
  expect(inControlFraction).toBeGreaterThanOrEqual(0.8);
93
87
  });
94
88
 
@@ -158,7 +152,7 @@ describe('buildSPCChart — p', () => {
158
152
  buildSPCChart('p', [
159
153
  { index: 1, values: [1, 2, 3] },
160
154
  { index: 2, values: [1, 2, 3] },
161
- ]),
155
+ ])
162
156
  ).toThrow();
163
157
  });
164
158
  });
@@ -191,11 +185,12 @@ describe('buildSPCChart — c', () => {
191
185
 
192
186
  describe('computeCapability', () => {
193
187
  // Centred process: mean=50, σ≈1, LSL=44, USL=56 → Cpk≈2.0
194
- const centredValues: number[] = Array.from({ length: 100 }, (_, i) =>
195
- 50 + Math.sin(i * 0.7) * 0.8,
188
+ const centredValues: number[] = Array.from(
189
+ { length: 100 },
190
+ (_, i) => 50 + Math.sin(i * 0.7) * 0.8
196
191
  );
197
192
  const centredSubgroups = makeSubgroups(
198
- Array.from({ length: 20 }, (_, i) => centredValues.slice(i * 5, i * 5 + 5)),
193
+ Array.from({ length: 20 }, (_, i) => centredValues.slice(i * 5, i * 5 + 5))
199
194
  );
200
195
 
201
196
  it('computes Cp, Cpk, Pp, Ppk with correct ordering (Cpk ≤ Cp)', () => {
package/src/index.ts CHANGED
@@ -1,7 +1,20 @@
1
1
  export * from './spc';
2
- export { createProductionLineHandler, type ProductionLineConfig, type Station } from './traits/ProductionLineTrait';
3
- export { createQualityGateHandler, type QualityGateConfig, type InspectionCriteria } from './traits/QualityGateTrait';
4
- export { createDefectTrackingHandler, type DefectTrackingConfig, type Defect, type DefectSeverity } from './traits/DefectTrackingTrait';
2
+ export {
3
+ createProductionLineHandler,
4
+ type ProductionLineConfig,
5
+ type Station,
6
+ } from './traits/ProductionLineTrait';
7
+ export {
8
+ createQualityGateHandler,
9
+ type QualityGateConfig,
10
+ type InspectionCriteria,
11
+ } from './traits/QualityGateTrait';
12
+ export {
13
+ createDefectTrackingHandler,
14
+ type DefectTrackingConfig,
15
+ type Defect,
16
+ type DefectSeverity,
17
+ } from './traits/DefectTrackingTrait';
5
18
  export { createBOMHandler, type BOMConfig, type BOMItem } from './traits/BOMTrait';
6
19
  export * from './traits/types';
7
20
 
@@ -26,5 +39,14 @@ export {
26
39
  type TraitRegistrar,
27
40
  } from './runtime';
28
41
 
29
- export const pluginMeta = { name: '@holoscript/plugin-manufacturing-qc', version: '1.0.0', traits: ['production_line', 'quality_gate', 'defect_tracking', 'bom', 'spc'] };
30
- export const traitHandlers = [createProductionLineHandler(), createQualityGateHandler(), createDefectTrackingHandler(), createBOMHandler()];
42
+ export const pluginMeta = {
43
+ name: '@holoscript/plugin-manufacturing-qc',
44
+ version: '1.0.0',
45
+ traits: ['production_line', 'quality_gate', 'defect_tracking', 'bom', 'spc'],
46
+ };
47
+ export const traitHandlers = [
48
+ createProductionLineHandler(),
49
+ createQualityGateHandler(),
50
+ createDefectTrackingHandler(),
51
+ createBOMHandler(),
52
+ ];
package/src/runtime.ts CHANGED
@@ -15,11 +15,7 @@
15
15
  * traits follow the same registrar shape.
16
16
  */
17
17
  import { registerPluginTraits } from '@holoscript/core/runtime';
18
- import {
19
- computeCapability,
20
- type Subgroup,
21
- type ProcessCapability,
22
- } from './spc';
18
+ import { computeCapability, type Subgroup, type ProcessCapability } from './spc';
23
19
 
24
20
  /** Stable id for this plugin's trait ownership tagging. */
25
21
  export const MANUFACTURING_QC_PLUGIN_ID = 'manufacturing-qc' as const;
@@ -76,7 +72,7 @@ export interface RuntimeTraitHandler {
76
72
  node: unknown,
77
73
  config: SpcTraitConfig,
78
74
  context: TraitDispatchContext,
79
- delta: number,
75
+ delta: number
80
76
  ) => void;
81
77
  }
82
78
 
@@ -91,7 +87,7 @@ interface SpcNode {
91
87
  function solveOntoNode(
92
88
  node: unknown,
93
89
  config: SpcTraitConfig | undefined,
94
- context: TraitDispatchContext,
90
+ context: TraitDispatchContext
95
91
  ): void {
96
92
  const carrier = node as SpcNode;
97
93
  const nodeId = carrier.id ?? carrier.name ?? 'unknown';
package/src/spc.ts CHANGED
@@ -58,8 +58,8 @@ export interface SPCChartResult {
58
58
  chartType: ChartType;
59
59
  subgroupCount: number;
60
60
  totalObservations: number;
61
- primaryChart: ControlLimits; // X̅ or p or c/u
62
- secondaryChart?: ControlLimits; // R or s (only for variables charts)
61
+ primaryChart: ControlLimits; // X̅ or p or c/u
62
+ secondaryChart?: ControlLimits; // R or s (only for variables charts)
63
63
  subgroupStats: SubgroupStat[];
64
64
  outOfControlCount: number;
65
65
  processInControl: boolean;
@@ -131,48 +131,104 @@ export interface SPCReceipt {
131
131
 
132
132
  /** d2 unbiasing constants for range → σ̂ */
133
133
  const D2: Record<number, number> = {
134
- 2: 1.128, 3: 1.693, 4: 2.059, 5: 2.326,
135
- 6: 2.534, 7: 2.704, 8: 2.847, 9: 2.970, 10: 3.078,
134
+ 2: 1.128,
135
+ 3: 1.693,
136
+ 4: 2.059,
137
+ 5: 2.326,
138
+ 6: 2.534,
139
+ 7: 2.704,
140
+ 8: 2.847,
141
+ 9: 2.97,
142
+ 10: 3.078,
136
143
  };
137
144
 
138
145
  /** d3 for range chart LCL */
139
146
  const D3: Record<number, number> = {
140
- 2: 0, 3: 0, 4: 0, 5: 0,
141
- 6: 0, 7: 0.076, 8: 0.136, 9: 0.184, 10: 0.223,
147
+ 2: 0,
148
+ 3: 0,
149
+ 4: 0,
150
+ 5: 0,
151
+ 6: 0,
152
+ 7: 0.076,
153
+ 8: 0.136,
154
+ 9: 0.184,
155
+ 10: 0.223,
142
156
  };
143
157
 
144
158
  /** d4 for range chart UCL */
145
159
  const D4: Record<number, number> = {
146
- 2: 3.267, 3: 2.575, 4: 2.282, 5: 2.115,
147
- 6: 2.004, 7: 1.924, 8: 1.864, 9: 1.816, 10: 1.777,
160
+ 2: 3.267,
161
+ 3: 2.575,
162
+ 4: 2.282,
163
+ 5: 2.115,
164
+ 6: 2.004,
165
+ 7: 1.924,
166
+ 8: 1.864,
167
+ 9: 1.816,
168
+ 10: 1.777,
148
169
  };
149
170
 
150
171
  /** c4 unbiasing constants for s → σ̂ */
151
172
  const C4: Record<number, number> = {
152
- 2: 0.7979, 3: 0.8862, 4: 0.9213, 5: 0.9400,
153
- 6: 0.9515, 7: 0.9594, 8: 0.9650, 9: 0.9693, 10: 0.9727,
173
+ 2: 0.7979,
174
+ 3: 0.8862,
175
+ 4: 0.9213,
176
+ 5: 0.94,
177
+ 6: 0.9515,
178
+ 7: 0.9594,
179
+ 8: 0.965,
180
+ 9: 0.9693,
181
+ 10: 0.9727,
154
182
  };
155
183
 
156
184
  /** A2 constants for X̅-R chart (3σ limits via R̅) */
157
185
  const A2: Record<number, number> = {
158
- 2: 1.880, 3: 1.023, 4: 0.729, 5: 0.577,
159
- 6: 0.483, 7: 0.419, 8: 0.373, 9: 0.337, 10: 0.308,
186
+ 2: 1.88,
187
+ 3: 1.023,
188
+ 4: 0.729,
189
+ 5: 0.577,
190
+ 6: 0.483,
191
+ 7: 0.419,
192
+ 8: 0.373,
193
+ 9: 0.337,
194
+ 10: 0.308,
160
195
  };
161
196
 
162
197
  /** A3 constants for X̅-s chart (3σ limits via s̅) */
163
198
  const A3: Record<number, number> = {
164
- 2: 2.659, 3: 1.954, 4: 1.628, 5: 1.427,
165
- 6: 1.287, 7: 1.182, 8: 1.099, 9: 1.032, 10: 0.975,
199
+ 2: 2.659,
200
+ 3: 1.954,
201
+ 4: 1.628,
202
+ 5: 1.427,
203
+ 6: 1.287,
204
+ 7: 1.182,
205
+ 8: 1.099,
206
+ 9: 1.032,
207
+ 10: 0.975,
166
208
  };
167
209
 
168
210
  /** B3 / B4 for s-chart limits */
169
211
  const B3: Record<number, number> = {
170
- 2: 0, 3: 0, 4: 0, 5: 0,
171
- 6: 0.030, 7: 0.118, 8: 0.185, 9: 0.239, 10: 0.284,
212
+ 2: 0,
213
+ 3: 0,
214
+ 4: 0,
215
+ 5: 0,
216
+ 6: 0.03,
217
+ 7: 0.118,
218
+ 8: 0.185,
219
+ 9: 0.239,
220
+ 10: 0.284,
172
221
  };
173
222
  const B4: Record<number, number> = {
174
- 2: 3.267, 3: 2.568, 4: 2.266, 5: 2.089,
175
- 6: 1.970, 7: 1.882, 8: 1.815, 9: 1.761, 10: 1.716,
223
+ 2: 3.267,
224
+ 3: 2.568,
225
+ 4: 2.266,
226
+ 5: 2.089,
227
+ 6: 1.97,
228
+ 7: 1.882,
229
+ 8: 1.815,
230
+ 9: 1.761,
231
+ 10: 1.716,
176
232
  };
177
233
 
178
234
  // ─── Statistics helpers ────────────────────────────────────────────────────────────────────
@@ -266,7 +322,8 @@ function westernElectricViolations(zScores: number[]): string[][] {
266
322
  // Rule 5: 6 consecutive monotone
267
323
  if (i >= 5) {
268
324
  const window = zScores.slice(i - 5, i + 1);
269
- let inc = true; let dec = true;
325
+ let inc = true;
326
+ let dec = true;
270
327
  for (let j = 1; j < window.length; j++) {
271
328
  if (window[j] <= window[j - 1]) inc = false;
272
329
  if (window[j] >= window[j - 1]) dec = false;
@@ -291,7 +348,10 @@ function westernElectricViolations(zScores: number[]): string[][] {
291
348
  for (let j = 2; j < window.length; j++) {
292
349
  const prevDir = window[j - 1] > window[j - 2];
293
350
  const currDir = window[j] > window[j - 1];
294
- if (currDir === prevDir) { alternating = false; break; }
351
+ if (currDir === prevDir) {
352
+ alternating = false;
353
+ break;
354
+ }
295
355
  }
296
356
  if (alternating) violations[i].push('WE7:alternating14');
297
357
  }
@@ -396,14 +456,15 @@ function buildXbarSChart(subgroups: Subgroup[]): SPCChartResult {
396
456
  lclFloor: 0,
397
457
  };
398
458
 
399
- const c4val = C4[n] ?? 0.9400;
459
+ const c4val = C4[n] ?? 0.94;
400
460
  const sigma = sBar / c4val;
401
461
  const primaryZ = means.map((m) => (sigma > 0 ? (m - xbarBar) / sigma : 0));
402
462
  const primaryViolations = westernElectricViolations(primaryZ);
403
463
 
404
464
  const subgroupStats: SubgroupStat[] = subgroups.map((sg, i) => {
405
465
  const violations = [...primaryViolations[i]];
406
- if (stds[i] > secondaryLimits.ucl || stds[i] < secondaryLimits.lcl) violations.push('s:outOfControl');
466
+ if (stds[i] > secondaryLimits.ucl || stds[i] < secondaryLimits.lcl)
467
+ violations.push('s:outOfControl');
407
468
  return {
408
469
  index: sg.index,
409
470
  n,
@@ -485,7 +546,8 @@ function buildPChart(subgroups: Subgroup[]): SPCChartResult {
485
546
  function buildCChart(subgroups: Subgroup[]): SPCChartResult {
486
547
  if (subgroups.length < 2) throw new Error('[spc] c-chart requires ≥ 2 subgroups');
487
548
  for (const sg of subgroups) {
488
- if (sg.defects === undefined) throw new Error('[spc] c-chart requires .defects on every subgroup');
549
+ if (sg.defects === undefined)
550
+ throw new Error('[spc] c-chart requires .defects on every subgroup');
489
551
  }
490
552
 
491
553
  const counts = subgroups.map((sg) => sg.defects ?? 0);
@@ -537,9 +599,12 @@ function buildCChart(subgroups: Subgroup[]): SPCChartResult {
537
599
  */
538
600
  export function buildSPCChart(type: ChartType, subgroups: Subgroup[]): SPCChartResult {
539
601
  switch (type) {
540
- case 'xbar_r': return buildXbarRChart(subgroups);
541
- case 'xbar_s': return buildXbarSChart(subgroups);
542
- case 'p': return buildPChart(subgroups);
602
+ case 'xbar_r':
603
+ return buildXbarRChart(subgroups);
604
+ case 'xbar_s':
605
+ return buildXbarSChart(subgroups);
606
+ case 'p':
607
+ return buildPChart(subgroups);
543
608
  case 'np': {
544
609
  // np-chart: same as p-chart but plot count instead of proportion
545
610
  const result = buildPChart(subgroups);
@@ -555,7 +620,8 @@ export function buildSPCChart(type: ChartType, subgroups: Subgroup[]): SPCChartR
555
620
  },
556
621
  };
557
622
  }
558
- case 'c': return buildCChart(subgroups);
623
+ case 'c':
624
+ return buildCChart(subgroups);
559
625
  case 'u': {
560
626
  // u-chart: defects per unit (c-chart normalised by n)
561
627
  for (const sg of subgroups) {
@@ -612,7 +678,7 @@ export function computeCapability(
612
678
  subgroups: Subgroup[],
613
679
  lsl: number,
614
680
  usl: number,
615
- target?: number,
681
+ target?: number
616
682
  ): ProcessCapability {
617
683
  if (usl <= lsl) throw new Error('[spc] usl must be > lsl');
618
684
  if (allValues.length < 2) throw new Error('[spc] need ≥ 2 values for capability');
@@ -633,11 +699,11 @@ export function computeCapability(
633
699
  }
634
700
 
635
701
  const specWidth = usl - lsl;
636
- const Cp = withinSigma > 0 ? specWidth / (6 * withinSigma) : Infinity;
637
- const Pp = overallSigma > 0 ? specWidth / (6 * overallSigma) : Infinity;
702
+ const Cp = withinSigma > 0 ? specWidth / (6 * withinSigma) : Infinity;
703
+ const Pp = overallSigma > 0 ? specWidth / (6 * overallSigma) : Infinity;
638
704
 
639
- const CpkUpper = withinSigma > 0 ? (usl - processMean) / (3 * withinSigma) : Infinity;
640
- const CpkLower = withinSigma > 0 ? (processMean - lsl) / (3 * withinSigma) : Infinity;
705
+ const CpkUpper = withinSigma > 0 ? (usl - processMean) / (3 * withinSigma) : Infinity;
706
+ const CpkLower = withinSigma > 0 ? (processMean - lsl) / (3 * withinSigma) : Infinity;
641
707
  const Cpk = Math.min(CpkUpper, CpkLower);
642
708
 
643
709
  const PpkUpper = overallSigma > 0 ? (usl - processMean) / (3 * overallSigma) : Infinity;
@@ -656,11 +722,17 @@ export function computeCapability(
656
722
  const ppmBelowLSL = ppm(zBelowLSL);
657
723
 
658
724
  return {
659
- lsl, usl, target,
725
+ lsl,
726
+ usl,
727
+ target,
660
728
  processMean,
661
729
  processStdDev: overallSigma,
662
- Cp, Cpk, CpkLower, CpkUpper,
663
- Pp, Ppk,
730
+ Cp,
731
+ Cpk,
732
+ CpkLower,
733
+ CpkUpper,
734
+ Pp,
735
+ Ppk,
664
736
  Cpm,
665
737
  ppmAboveUSL,
666
738
  ppmBelowLSL,
@@ -676,7 +748,7 @@ export function buildSPCReceipt(
676
748
  modelId: string,
677
749
  chartResult: SPCChartResult,
678
750
  capability?: ProcessCapability,
679
- options: SPCReceiptOptions = {},
751
+ options: SPCReceiptOptions = {}
680
752
  ): SPCReceipt {
681
753
  const violations: Array<{ criterion: string; message: string }> = [];
682
754
 
@@ -1,22 +1,50 @@
1
1
  /** @bom Trait — Bill of Materials management. @trait bom */
2
2
  import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
3
3
 
4
- export interface BOMItem { partNumber: string; name: string; quantity: number; unit: string; supplier?: string; leadTimeDays: number; costPerUnit: number; }
5
- export interface BOMConfig { items: BOMItem[]; revision: string; product: string; approvedBy?: string; }
4
+ export interface BOMItem {
5
+ partNumber: string;
6
+ name: string;
7
+ quantity: number;
8
+ unit: string;
9
+ supplier?: string;
10
+ leadTimeDays: number;
11
+ costPerUnit: number;
12
+ }
13
+ export interface BOMConfig {
14
+ items: BOMItem[];
15
+ revision: string;
16
+ product: string;
17
+ approvedBy?: string;
18
+ }
6
19
 
7
20
  const defaultConfig: BOMConfig = { items: [], revision: '1.0', product: '' };
8
21
 
9
22
  export function createBOMHandler(): TraitHandler<BOMConfig> {
10
- return { name: 'bom', defaultConfig,
23
+ return {
24
+ name: 'bom',
25
+ defaultConfig,
11
26
  onAttach(n: HSPlusNode, c: BOMConfig, ctx: TraitContext) {
12
27
  const totalCost = c.items.reduce((sum, i) => sum + i.costPerUnit * i.quantity, 0);
13
- n.__bomState = { totalCost, itemCount: c.items.length, longestLeadTime: Math.max(0, ...c.items.map(i => i.leadTimeDays)) };
28
+ n.__bomState = {
29
+ totalCost,
30
+ itemCount: c.items.length,
31
+ longestLeadTime: Math.max(0, ...c.items.map((i) => i.leadTimeDays)),
32
+ };
14
33
  ctx.emit?.('bom:loaded', { items: c.items.length, totalCost });
15
34
  },
16
- onDetach(n: HSPlusNode, _c: BOMConfig, ctx: TraitContext) { delete n.__bomState; ctx.emit?.('bom:unloaded'); },
35
+ onDetach(n: HSPlusNode, _c: BOMConfig, ctx: TraitContext) {
36
+ delete n.__bomState;
37
+ ctx.emit?.('bom:unloaded');
38
+ },
17
39
  onUpdate() {},
18
40
  onEvent(_n: HSPlusNode, c: BOMConfig, ctx: TraitContext, e: TraitEvent) {
19
- if (e.type === 'bom:check_availability') { const missing = c.items.filter(i => i.leadTimeDays > 14); ctx.emit?.('bom:availability', { available: c.items.length - missing.length, delayed: missing.length }); }
41
+ if (e.type === 'bom:check_availability') {
42
+ const missing = c.items.filter((i) => i.leadTimeDays > 14);
43
+ ctx.emit?.('bom:availability', {
44
+ available: c.items.length - missing.length,
45
+ delayed: missing.length,
46
+ });
47
+ }
20
48
  },
21
49
  };
22
50
  }
@@ -2,25 +2,65 @@
2
2
  import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
3
3
 
4
4
  export type DefectSeverity = 'critical' | 'major' | 'minor' | 'cosmetic';
5
- export interface Defect { id: string; severity: DefectSeverity; description: string; stationId: string; timestamp: number; resolved: boolean; }
6
- export interface DefectTrackingConfig { categories: string[]; autoEscalateCritical: boolean; maxOpenDefects: number; }
5
+ export interface Defect {
6
+ id: string;
7
+ severity: DefectSeverity;
8
+ description: string;
9
+ stationId: string;
10
+ timestamp: number;
11
+ resolved: boolean;
12
+ }
13
+ export interface DefectTrackingConfig {
14
+ categories: string[];
15
+ autoEscalateCritical: boolean;
16
+ maxOpenDefects: number;
17
+ }
7
18
 
8
- const defaultConfig: DefectTrackingConfig = { categories: ['dimensional', 'surface', 'functional', 'material'], autoEscalateCritical: true, maxOpenDefects: 50 };
19
+ const defaultConfig: DefectTrackingConfig = {
20
+ categories: ['dimensional', 'surface', 'functional', 'material'],
21
+ autoEscalateCritical: true,
22
+ maxOpenDefects: 50,
23
+ };
9
24
 
10
25
  export function createDefectTrackingHandler(): TraitHandler<DefectTrackingConfig> {
11
- return { name: 'defect_tracking', defaultConfig,
12
- onAttach(n: HSPlusNode, _c: DefectTrackingConfig, ctx: TraitContext) { n.__defectState = { defects: [], openCount: 0 }; ctx.emit?.('defect:tracker_ready'); },
13
- onDetach(n: HSPlusNode, _c: DefectTrackingConfig, ctx: TraitContext) { delete n.__defectState; ctx.emit?.('defect:tracker_removed'); },
26
+ return {
27
+ name: 'defect_tracking',
28
+ defaultConfig,
29
+ onAttach(n: HSPlusNode, _c: DefectTrackingConfig, ctx: TraitContext) {
30
+ n.__defectState = { defects: [], openCount: 0 };
31
+ ctx.emit?.('defect:tracker_ready');
32
+ },
33
+ onDetach(n: HSPlusNode, _c: DefectTrackingConfig, ctx: TraitContext) {
34
+ delete n.__defectState;
35
+ ctx.emit?.('defect:tracker_removed');
36
+ },
14
37
  onUpdate() {},
15
38
  onEvent(n: HSPlusNode, c: DefectTrackingConfig, ctx: TraitContext, e: TraitEvent) {
16
- const s = n.__defectState as { defects: Defect[]; openCount: number } | undefined; if (!s) return;
39
+ const s = n.__defectState as { defects: Defect[]; openCount: number } | undefined;
40
+ if (!s) return;
17
41
  if (e.type === 'defect:log') {
18
- const defect: Defect = { id: `DEF-${s.defects.length + 1}`, severity: (e.payload?.severity as DefectSeverity) || 'minor', description: (e.payload?.description as string) || '', stationId: (e.payload?.stationId as string) || '', timestamp: Date.now(), resolved: false };
19
- s.defects.push(defect); s.openCount++;
42
+ const defect: Defect = {
43
+ id: `DEF-${s.defects.length + 1}`,
44
+ severity: (e.payload?.severity as DefectSeverity) || 'minor',
45
+ description: (e.payload?.description as string) || '',
46
+ stationId: (e.payload?.stationId as string) || '',
47
+ timestamp: Date.now(),
48
+ resolved: false,
49
+ };
50
+ s.defects.push(defect);
51
+ s.openCount++;
20
52
  ctx.emit?.('defect:logged', { id: defect.id, severity: defect.severity });
21
- if (defect.severity === 'critical' && c.autoEscalateCritical) ctx.emit?.('defect:escalated', { id: defect.id });
53
+ if (defect.severity === 'critical' && c.autoEscalateCritical)
54
+ ctx.emit?.('defect:escalated', { id: defect.id });
55
+ }
56
+ if (e.type === 'defect:resolve') {
57
+ const d = s.defects.find((d) => d.id === e.payload?.id);
58
+ if (d && !d.resolved) {
59
+ d.resolved = true;
60
+ s.openCount--;
61
+ ctx.emit?.('defect:resolved', { id: d.id });
62
+ }
22
63
  }
23
- if (e.type === 'defect:resolve') { const d = s.defects.find(d => d.id === e.payload?.id); if (d && !d.resolved) { d.resolved = true; s.openCount--; ctx.emit?.('defect:resolved', { id: d.id }); } }
24
64
  },
25
65
  };
26
66
  }
@@ -1,26 +1,70 @@
1
1
  /** @production_line Trait — Assembly line management. @trait production_line */
2
2
  import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
3
3
 
4
- export interface Station { id: string; name: string; cycleTimeS: number; status: 'running' | 'idle' | 'maintenance' | 'error'; }
5
- export interface ProductionLineConfig { stations: Station[]; targetUnitsPerHour: number; shiftDurationH: number; product: string; }
6
- export interface ProductionLineState { unitsProduced: number; currentThroughput: number; bottleneckStation: string | null; isRunning: boolean; }
4
+ export interface Station {
5
+ id: string;
6
+ name: string;
7
+ cycleTimeS: number;
8
+ status: 'running' | 'idle' | 'maintenance' | 'error';
9
+ }
10
+ export interface ProductionLineConfig {
11
+ stations: Station[];
12
+ targetUnitsPerHour: number;
13
+ shiftDurationH: number;
14
+ product: string;
15
+ }
16
+ export interface ProductionLineState {
17
+ unitsProduced: number;
18
+ currentThroughput: number;
19
+ bottleneckStation: string | null;
20
+ isRunning: boolean;
21
+ }
7
22
 
8
- const defaultConfig: ProductionLineConfig = { stations: [], targetUnitsPerHour: 60, shiftDurationH: 8, product: '' };
23
+ const defaultConfig: ProductionLineConfig = {
24
+ stations: [],
25
+ targetUnitsPerHour: 60,
26
+ shiftDurationH: 8,
27
+ product: '',
28
+ };
9
29
 
10
30
  export function createProductionLineHandler(): TraitHandler<ProductionLineConfig> {
11
- return { name: 'production_line', defaultConfig,
12
- onAttach(n: HSPlusNode, c: ProductionLineConfig, ctx: TraitContext) { n.__lineState = { unitsProduced: 0, currentThroughput: 0, bottleneckStation: null, isRunning: false }; ctx.emit?.('line:created', { stations: c.stations.length }); },
13
- onDetach(n: HSPlusNode, _c: ProductionLineConfig, ctx: TraitContext) { delete n.__lineState; ctx.emit?.('line:shutdown'); },
31
+ return {
32
+ name: 'production_line',
33
+ defaultConfig,
34
+ onAttach(n: HSPlusNode, c: ProductionLineConfig, ctx: TraitContext) {
35
+ n.__lineState = {
36
+ unitsProduced: 0,
37
+ currentThroughput: 0,
38
+ bottleneckStation: null,
39
+ isRunning: false,
40
+ };
41
+ ctx.emit?.('line:created', { stations: c.stations.length });
42
+ },
43
+ onDetach(n: HSPlusNode, _c: ProductionLineConfig, ctx: TraitContext) {
44
+ delete n.__lineState;
45
+ ctx.emit?.('line:shutdown');
46
+ },
14
47
  onUpdate(n: HSPlusNode, c: ProductionLineConfig, ctx: TraitContext, delta: number) {
15
- const s = n.__lineState as ProductionLineState | undefined; if (!s?.isRunning) return;
48
+ const s = n.__lineState as ProductionLineState | undefined;
49
+ if (!s?.isRunning) return;
16
50
  s.unitsProduced += (c.targetUnitsPerHour / 3600) * (delta / 1000);
17
- const slowest = c.stations.reduce((a, b) => a.cycleTimeS > b.cycleTimeS ? a : b, c.stations[0]);
51
+ const slowest = c.stations.reduce(
52
+ (a, b) => (a.cycleTimeS > b.cycleTimeS ? a : b),
53
+ c.stations[0]
54
+ );
18
55
  s.bottleneckStation = slowest?.id ?? null;
19
56
  },
20
57
  onEvent(n: HSPlusNode, _c: ProductionLineConfig, ctx: TraitContext, e: TraitEvent) {
21
- const s = n.__lineState as ProductionLineState | undefined; if (!s) return;
22
- if (e.type === 'line:start') { s.isRunning = true; ctx.emit?.('line:started'); }
23
- if (e.type === 'line:stop') { s.isRunning = false; ctx.emit?.('line:stopped', { produced: s.unitsProduced }); }
58
+ const s = n.__lineState as ProductionLineState | undefined;
59
+ if (!s) return;
60
+ if (e.type === 'line:start') {
61
+ s.isRunning = true;
62
+ ctx.emit?.('line:started');
63
+ }
64
+ if (e.type === 'line:stop') {
65
+ s.isRunning = false;
66
+ ctx.emit?.('line:stopped', { produced: s.unitsProduced });
67
+ }
24
68
  },
25
69
  };
26
70
  }
@@ -1,26 +1,58 @@
1
1
  /** @quality_gate Trait — Inspection checkpoint. @trait quality_gate */
2
2
  import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
3
3
 
4
- export interface InspectionCriteria { id: string; name: string; type: 'visual' | 'dimensional' | 'functional' | 'electrical'; tolerance: number; unit: string; }
5
- export interface QualityGateConfig { criteria: InspectionCriteria[]; passThreshold: number; autoReject: boolean; stationId: string; }
6
- export interface QualityGateState { inspected: number; passed: number; failed: number; passRate: number; }
4
+ export interface InspectionCriteria {
5
+ id: string;
6
+ name: string;
7
+ type: 'visual' | 'dimensional' | 'functional' | 'electrical';
8
+ tolerance: number;
9
+ unit: string;
10
+ }
11
+ export interface QualityGateConfig {
12
+ criteria: InspectionCriteria[];
13
+ passThreshold: number;
14
+ autoReject: boolean;
15
+ stationId: string;
16
+ }
17
+ export interface QualityGateState {
18
+ inspected: number;
19
+ passed: number;
20
+ failed: number;
21
+ passRate: number;
22
+ }
7
23
 
8
- const defaultConfig: QualityGateConfig = { criteria: [], passThreshold: 95, autoReject: true, stationId: '' };
24
+ const defaultConfig: QualityGateConfig = {
25
+ criteria: [],
26
+ passThreshold: 95,
27
+ autoReject: true,
28
+ stationId: '',
29
+ };
9
30
 
10
31
  export function createQualityGateHandler(): TraitHandler<QualityGateConfig> {
11
- return { name: 'quality_gate', defaultConfig,
12
- onAttach(n: HSPlusNode, _c: QualityGateConfig, ctx: TraitContext) { n.__qcState = { inspected: 0, passed: 0, failed: 0, passRate: 100 }; ctx.emit?.('qc:ready'); },
13
- onDetach(n: HSPlusNode, _c: QualityGateConfig, ctx: TraitContext) { delete n.__qcState; ctx.emit?.('qc:removed'); },
32
+ return {
33
+ name: 'quality_gate',
34
+ defaultConfig,
35
+ onAttach(n: HSPlusNode, _c: QualityGateConfig, ctx: TraitContext) {
36
+ n.__qcState = { inspected: 0, passed: 0, failed: 0, passRate: 100 };
37
+ ctx.emit?.('qc:ready');
38
+ },
39
+ onDetach(n: HSPlusNode, _c: QualityGateConfig, ctx: TraitContext) {
40
+ delete n.__qcState;
41
+ ctx.emit?.('qc:removed');
42
+ },
14
43
  onUpdate() {},
15
44
  onEvent(n: HSPlusNode, c: QualityGateConfig, ctx: TraitContext, e: TraitEvent) {
16
- const s = n.__qcState as QualityGateState | undefined; if (!s) return;
45
+ const s = n.__qcState as QualityGateState | undefined;
46
+ if (!s) return;
17
47
  if (e.type === 'qc:inspect') {
18
48
  s.inspected++;
19
49
  const pass = (e.payload?.pass as boolean) ?? true;
20
- if (pass) s.passed++; else s.failed++;
50
+ if (pass) s.passed++;
51
+ else s.failed++;
21
52
  s.passRate = s.inspected > 0 ? (s.passed / s.inspected) * 100 : 100;
22
53
  ctx.emit?.('qc:inspected', { pass, passRate: s.passRate });
23
- if (s.passRate < c.passThreshold) ctx.emit?.('qc:threshold_breach', { passRate: s.passRate, threshold: c.passThreshold });
54
+ if (s.passRate < c.passThreshold)
55
+ ctx.emit?.('qc:threshold_breach', { passRate: s.passRate, threshold: c.passThreshold });
24
56
  }
25
57
  },
26
58
  };
@@ -1,4 +1,22 @@
1
- export interface HSPlusNode { id?: string; properties?: Record<string, unknown>; [key: string]: unknown; }
2
- export interface TraitContext { emit?: (event: string, payload?: unknown) => void; [key: string]: unknown; }
3
- export interface TraitEvent { type: string; payload?: Record<string, unknown>; [key: string]: unknown; }
4
- export interface TraitHandler<T = unknown> { name: string; defaultConfig: T; onAttach(n: HSPlusNode, c: T, ctx: TraitContext): void; onDetach(n: HSPlusNode, c: T, ctx: TraitContext): void; onUpdate(n: HSPlusNode, c: T, ctx: TraitContext, d: number): void; onEvent(n: HSPlusNode, c: T, ctx: TraitContext, e: TraitEvent): void; }
1
+ export interface HSPlusNode {
2
+ id?: string;
3
+ properties?: Record<string, unknown>;
4
+ [key: string]: unknown;
5
+ }
6
+ export interface TraitContext {
7
+ emit?: (event: string, payload?: unknown) => void;
8
+ [key: string]: unknown;
9
+ }
10
+ export interface TraitEvent {
11
+ type: string;
12
+ payload?: Record<string, unknown>;
13
+ [key: string]: unknown;
14
+ }
15
+ export interface TraitHandler<T = unknown> {
16
+ name: string;
17
+ defaultConfig: T;
18
+ onAttach(n: HSPlusNode, c: T, ctx: TraitContext): void;
19
+ onDetach(n: HSPlusNode, c: T, ctx: TraitContext): void;
20
+ onUpdate(n: HSPlusNode, c: T, ctx: TraitContext, d: number): void;
21
+ onEvent(n: HSPlusNode, c: T, ctx: TraitContext, e: TraitEvent): void;
22
+ }
package/tsconfig.json CHANGED
@@ -1 +1,5 @@
1
- { "extends": "../../../tsconfig.json", "compilerOptions": { "outDir": "dist", "rootDir": "src", "declaration": true }, "include": ["src"] }
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": { "outDir": "dist", "rootDir": "src", "declaration": true },
4
+ "include": ["src"]
5
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025-2026 HoloScript Contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.