@holoscript/plugin-manufacturing-qc 2.0.1
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/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/package.json +13 -0
- package/src/__tests__/runtime-integration.test.ts +165 -0
- package/src/__tests__/spc.test.ts +278 -0
- package/src/index.ts +30 -0
- package/src/runtime.ts +172 -0
- package/src/spc.ts +728 -0
- package/src/traits/BOMTrait.ts +22 -0
- package/src/traits/DefectTrackingTrait.ts +26 -0
- package/src/traits/ProductionLineTrait.ts +26 -0
- package/src/traits/QualityGateTrait.ts +27 -0
- package/src/traits/types.ts +4 -0
- package/tsconfig.json +1 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** @bom Trait — Bill of Materials management. @trait bom */
|
|
2
|
+
import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
|
|
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; }
|
|
6
|
+
|
|
7
|
+
const defaultConfig: BOMConfig = { items: [], revision: '1.0', product: '' };
|
|
8
|
+
|
|
9
|
+
export function createBOMHandler(): TraitHandler<BOMConfig> {
|
|
10
|
+
return { name: 'bom', defaultConfig,
|
|
11
|
+
onAttach(n: HSPlusNode, c: BOMConfig, ctx: TraitContext) {
|
|
12
|
+
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)) };
|
|
14
|
+
ctx.emit?.('bom:loaded', { items: c.items.length, totalCost });
|
|
15
|
+
},
|
|
16
|
+
onDetach(n: HSPlusNode, _c: BOMConfig, ctx: TraitContext) { delete n.__bomState; ctx.emit?.('bom:unloaded'); },
|
|
17
|
+
onUpdate() {},
|
|
18
|
+
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 }); }
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** @defect_tracking Trait — Defect logging and classification. @trait defect_tracking */
|
|
2
|
+
import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
|
|
3
|
+
|
|
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; }
|
|
7
|
+
|
|
8
|
+
const defaultConfig: DefectTrackingConfig = { categories: ['dimensional', 'surface', 'functional', 'material'], autoEscalateCritical: true, maxOpenDefects: 50 };
|
|
9
|
+
|
|
10
|
+
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'); },
|
|
14
|
+
onUpdate() {},
|
|
15
|
+
onEvent(n: HSPlusNode, c: DefectTrackingConfig, ctx: TraitContext, e: TraitEvent) {
|
|
16
|
+
const s = n.__defectState as { defects: Defect[]; openCount: number } | undefined; if (!s) return;
|
|
17
|
+
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++;
|
|
20
|
+
ctx.emit?.('defect:logged', { id: defect.id, severity: defect.severity });
|
|
21
|
+
if (defect.severity === 'critical' && c.autoEscalateCritical) ctx.emit?.('defect:escalated', { id: defect.id });
|
|
22
|
+
}
|
|
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
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** @production_line Trait — Assembly line management. @trait production_line */
|
|
2
|
+
import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
|
|
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; }
|
|
7
|
+
|
|
8
|
+
const defaultConfig: ProductionLineConfig = { stations: [], targetUnitsPerHour: 60, shiftDurationH: 8, product: '' };
|
|
9
|
+
|
|
10
|
+
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'); },
|
|
14
|
+
onUpdate(n: HSPlusNode, c: ProductionLineConfig, ctx: TraitContext, delta: number) {
|
|
15
|
+
const s = n.__lineState as ProductionLineState | undefined; if (!s?.isRunning) return;
|
|
16
|
+
s.unitsProduced += (c.targetUnitsPerHour / 3600) * (delta / 1000);
|
|
17
|
+
const slowest = c.stations.reduce((a, b) => a.cycleTimeS > b.cycleTimeS ? a : b, c.stations[0]);
|
|
18
|
+
s.bottleneckStation = slowest?.id ?? null;
|
|
19
|
+
},
|
|
20
|
+
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 }); }
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** @quality_gate Trait — Inspection checkpoint. @trait quality_gate */
|
|
2
|
+
import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
|
|
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; }
|
|
7
|
+
|
|
8
|
+
const defaultConfig: QualityGateConfig = { criteria: [], passThreshold: 95, autoReject: true, stationId: '' };
|
|
9
|
+
|
|
10
|
+
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'); },
|
|
14
|
+
onUpdate() {},
|
|
15
|
+
onEvent(n: HSPlusNode, c: QualityGateConfig, ctx: TraitContext, e: TraitEvent) {
|
|
16
|
+
const s = n.__qcState as QualityGateState | undefined; if (!s) return;
|
|
17
|
+
if (e.type === 'qc:inspect') {
|
|
18
|
+
s.inspected++;
|
|
19
|
+
const pass = (e.payload?.pass as boolean) ?? true;
|
|
20
|
+
if (pass) s.passed++; else s.failed++;
|
|
21
|
+
s.passRate = s.inspected > 0 ? (s.passed / s.inspected) * 100 : 100;
|
|
22
|
+
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 });
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
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; }
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "extends": "../../../tsconfig.json", "compilerOptions": { "outDir": "dist", "rootDir": "src", "declaration": true }, "include": ["src"] }
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
// resolve.alias is REQUIRED: without it, `@holoscript/core/runtime` resolves to
|
|
5
|
+
// the STALE built dist and registerPluginTraits is missing ("not a function").
|
|
6
|
+
// Point the subpath imports at core/engine source so the runtime barrel
|
|
7
|
+
// (core/src/runtime.ts) and shared registrar resolve from source. Mirrors
|
|
8
|
+
// government-civic-plugin/vitest.config.ts.
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
resolve: {
|
|
11
|
+
alias: {
|
|
12
|
+
'@holoscript/engine': resolve(__dirname, '../../engine/src'),
|
|
13
|
+
'@holoscript/core': resolve(__dirname, '../../core/src'),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
test: {
|
|
17
|
+
globals: true,
|
|
18
|
+
environment: 'node',
|
|
19
|
+
include: ['src/**/*.test.ts'],
|
|
20
|
+
passWithNoTests: true,
|
|
21
|
+
},
|
|
22
|
+
});
|