Tool Calibration Architecture
Overview
The Tool Calibration system is a sophisticated quality management framework built on LoopBack 4, designed to ensure tools maintain their accuracy and compliance with industry standards. This document provides a technical deep-dive into the system's architecture, data models, business logic, and extension points.
System Architecture
High-Level Components
┌─────────────────────────────────────────────────────────────┐
│ Angular Frontend │
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ Calibration Forms│ │WidgetToolCalibrationDue │ │
│ └────────┬─────────┘ └────────┬─────────────────────┘ │
│ │ │ │
└───────────┼──────────────────────┼───────────────────────────┘
│ │
│ HTTP/REST │ HTTP/REST
│ │
┌───────────▼──────────────────────▼───────────────────────────┐
│ LoopBack 4 API │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ToolController.calibrateTool() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ToolRepository.processCalibration() │ │
│ │ │ │ │
│ │ ┌────────────┴────────────┐ │ │
│ │ ▼ ▼ │ │
│ │ Validation Logic Calculation Engine │ │
│ │ - Requirements - Pull test status │ │
│ │ - Tool config - Overall status │ │
│ │ - Data completeness - Due date │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
└──────────────────────┼───────────────────────────────────────┘
│
▼
┌─────────────────────────┐
│ MySQL Database │
│ - tool │
│ - tool_calibration │
│ - tool_calibration_test│
│ - tool_calibration_* │
└─────────────────────────┘Data Model
Entity Relationship Diagram
┌──────────────────┐
│ ToolType │
│ │
│ id │────┐
│ name │ │
│ description │ │
│ calibration_ │ │
│ cycle │ │
│ calibration_ │ │
│ multipiler │ │
└──────────────────┘ │
│
│ tool_type_id
│
┌──────────────────┐ │ ┌────────────────────────────┐
│ Tool │◄───┘ │ ToolCalibrationRequirement │
│ │ │ │
│ id │ │ id │
│ name │ │ tool_type_id ─────────────┤
│ tool_type_id │ │ calibration_type_id │
│ calibration_ │ │ calibration_standard_id │
│ cycle │ └──────────┬─────────────────┘
│ calibration_ │ │
│ multipiler │ │
│ min_pull_force_n│ │
│ max_pull_force_n│ │
│ date_last_ │ │
│ calibration │ │ calibration_type_id
│ date_ │ │
│ calibration_ │ │
│ due │ ┌──────────▼─────────────────┐
│ last_ │ │ ToolCalibrationType │
│ calibration_id├─────┐ │ │
└──────────────────┘ │ │ id │
│ │ name │
│ │ description │
│ │ controller (key field) │
│ └────────────────────────────┘
│
│ last_calibration_id
│
┌────────────────────▼──────────────────┐
│ ToolCalibration │
│ │
│ id │
│ tool_id │
│ user_id │
│ date_calibration │
│ date_last_calibration │
│ date_calibration_due │
│ status (1=pass, 0=fail) │
│ calibration_cycle │
│ calibration_multipiler │
│ description │
└───────────────┬───────────────────────┘
│
│ calibration_id
│
┌───────────────▼──────────────────────────┐
│ ToolCalibrationTest │
│ │
│ id │
│ calibration_id │
│ calibration_type_id │
│ status (true=pass, false=fail) │
│ is_external │
│ notes │
└──────┬──────────────────┬────────────────┘
│ │
│ │ calibration_test_id
│ │
│ ┌───────────▼──────────────────────────┐
│ │ ToolCalibrationTestPullSimple │
│ │ │
│ │ id │
│ │ calibration_test_id │
│ │ tensile_tool_id │
│ │ part_id │
│ │ tool_calibration_standard_id │
│ │ wire_awg_value │
│ │ wire_mm_value │
│ │ min_pull_force_n │
│ │ pull_test_value_n (measured) │
│ │ status (true=pass, false=fail) │
│ └──────────────────────────────────────┘
│
│ calibration_test_id
│
┌──────▼─────────────────────────────┐
│ ToolCalibrationExternal │
│ │
│ id │
│ calibration_test_id │
│ partner_id (lab) │
│ date_external_calibration │
│ description │
└────────────────┬───────────────────┘
│
│ tool_calibration_external_id
│
┌────────────────▼──────────────────┐
│ ToolCalibrationExternalFiles │
│ │
│ id │
│ tool_calibration_external_id │
│ file_id │
└───────────────────────────────────┘Key Entities
Tool
The primary entity representing physical tools requiring calibration.
Critical Fields:
calibration_cycle: Number (1, 3, 6, 12 months)calibration_multipiler: Multiplier applied to cycle (default 1)min_pull_force_n: Minimum pull force for this specific tool (optional override)max_pull_force_n: Maximum pull force for this specific tool (optional override)date_last_calibration: Date of last successful calibrationdate_calibration_due: Calculated due date for next calibrationlast_calibration_id: Foreign key to most recent ToolCalibration
Business Rules:
- Must have
calibration_cycleandcalibration_multipilerset before calibration - Due date calculated:
last_calibration_date + (cycle × multiplier) - Tool-specific force ranges override tool type defaults
ToolType
Defines categories of tools with shared calibration requirements.
Critical Fields:
calibration_cycle: Default cycle for tools of this typecalibration_multipiler: Default multiplier for tools of this type
Relationships:
- Has many
ToolCalibrationRequirement(defines required tests) - Has many
ToolTypePartPullTest(defines wire sizes and force requirements)
ToolCalibrationRequirement
Links tool types to required calibration tests.
Purpose: Defines which calibration tests are mandatory for a given tool type.
Critical Fields:
tool_type_id: The tool type this requirement applies tocalibration_type_id: The type of calibration test requiredcalibration_standard_id: The standard to apply (e.g., IEC 60512-9-2)
Example:
-- Crimping Tool requires pull test and visual inspection
INSERT INTO tool_calibration_requirement
(tool_type_id, calibration_type_id, calibration_standard_id)
VALUES
(5, 1, 1), -- Pull test per IEC 60512-9-2
(5, 2, NULL); -- Visual inspection (no standard)ToolCalibrationType
Defines types of calibration tests.
Controller Field: The controller field is critical - it determines how the system processes the test data.
Supported Controllers:
pull_test_dellner: Complex pull test with multiple wire sizesvisual: Simple pass/fail visual inspectiongo_no_go: Gauge measurement testpressure: Pressure calibrationtemperature_test: Temperature calibrationexternal: External lab calibration
Extension Point: To add a new calibration type, create a new controller value and implement handling in ToolRepository.processCalibration().
ToolCalibration
Main calibration record representing a complete calibration event.
Critical Fields:
date_calibration: When calibration was performedstatus: 1 (pass) or 0 (fail) - derived from ALL testsuser_id: Technician who performed calibrationtool_id: Tool being calibratedcalibration_cycle: Snapshot of cycle at calibration timecalibration_multipiler: Snapshot of multiplier at calibration timedate_last_calibration: Previous calibration datedate_calibration_due: Calculated next due date
Status Calculation:
// All tests must pass for overall pass
const overallStatus = testStatusesData.every(t => t.testStatus);Date Handling:
- For internal calibrations: Uses current timestamp
- For mixed internal/external: Uses minimum external calibration date
- Enables accurate due date calculation for externally calibrated tools
ToolCalibrationTest
Individual test within a calibration.
Critical Fields:
calibration_id: Parent calibrationcalibration_type_id: Type of teststatus: true (pass) or false (fail)is_external: Whether performed by external labnotes: Technician observations
Relationships:
- Has many
ToolCalibrationTestPullSimple(for pull tests) - Has many
ToolCalibrationExternal(for external calibrations) - Has many
ToolCalibrationTestTemperature(for temperature tests)
ToolCalibrationTestPullSimple
Detailed pull test measurements for a single wire size.
Critical Fields:
calibration_test_id: Parent testtensile_tool_id: Tool used to perform pull test (tensile tester)part_id: Specific part/component testedtool_calibration_standard_id: Standard appliedwire_awg_value: Wire gauge (e.g., "20", "22", "24")wire_mm_value: Wire diameter in millimetersmin_pull_force_n: Minimum required forcepull_test_value_n: Measured force in Newtonsstatus: true ifpull_test_value_n >= min_pull_force_n
Pass Logic:
const testPassed = pull_test_value_n >= min_pull_force_n;Business Logic
processCalibration Method
Located in: /workspace/api/b3api/src/repositories/tool.repository.ts
This is the core calibration processing engine.
Method Signature
async processCalibration(
toolId: number,
userId: number,
calibrationDataMap: {
[requirementId: string]: {
testData: any;
calibration_type: { controller: string };
};
}
): Promise<any>Processing Flow
Phase 1: Validation
// 1. Validate tool exists
const tool = await this.findById(toolId);
if (!tool) throw new HttpErrors.NotFound('Tool not found');
// 2. Validate calibration configuration
if (!tool.calibration_cycle) {
throw new HttpErrors.BadRequest(
'Tool must have calibration_cycle set before calibration'
);
}
if (!tool.calibration_multipiler || tool.calibration_multipiler === 0) {
throw new HttpErrors.BadRequest(
'Tool must have calibration_multipiler set before calibration'
);
}
// 3. Validate all requirements are provided
const requirements = await toolCalibrationRequirementRepo.find({
where: { tool_type_id: tool.tool_type_id }
});
const requirementIds = requirements.map(r => r.id?.toString());
const providedRequirementIds = Object.keys(calibrationDataMap);
for (const reqId of requirementIds) {
if (!providedRequirementIds.includes(reqId!)) {
throw new HttpErrors.BadRequest(
`Missing calibration data for requirement ID: ${reqId}`
);
}
}Phase 2: Status Calculation (Pre-Creation)
This critical phase calculates ALL test statuses BEFORE creating any database records. This ensures atomicity and correct overall status calculation.
const testStatusesData: Array<{
requirement: any;
testStatus: boolean;
testData: any;
calibration_type: any;
pullTestDetails?: any;
}> = [];
for (const requirement of requirements) {
const { testData, calibration_type } = calibrationDataMap[requirement.id];
let testStatus = false;
let pullTestDetails = null;
// Calculate status based on calibration type
if (testData.externalCalibration) {
// External calibrations use testResult field
testStatus = testData.testResult === 'pass';
} else if (calibration_type.controller === 'pull_test_dellner') {
// Internal pull test - complex calculation
const pullTestResult = await this.calculatePullTestStatus(
testData,
toolTypePartPullTestRepo,
toolStandardDiameterRepo,
wireSizeRepo
);
testStatus = pullTestResult.overallStatus;
pullTestDetails = pullTestResult.testDetails;
} else if (calibration_type.controller === 'visual') {
// Visual inspection
testStatus = testData.testResult === 'pass';
} else {
// Other types use testResult
testStatus = testData.testResult === 'pass';
}
testStatusesData.push({
requirement,
testStatus,
testData,
calibration_type,
pullTestDetails
});
}
// Calculate overall status - ALL tests must pass
const overallStatus = testStatusesData.every(t => t.testStatus);Phase 3: Date Calculation
For external calibrations, use the minimum external calibration date to ensure accurate due date calculation:
let calibrationDate: string;
// Collect all external calibration dates
const externalDates: string[] = [];
for (const testStatusData of testStatusesData) {
if (
testStatusData.testData.externalCalibration &&
testStatusData.testData.externalData?.calibrationDate
) {
externalDates.push(testStatusData.testData.externalData.calibrationDate);
}
}
// Use minimum external date or current date
if (externalDates.length > 0) {
const momentDates = externalDates.map(date => moment(date, 'YYYY-MM-DD'));
const minDate = moment.min(momentDates);
calibrationDate = minDate.utc().toDate().toISOString();
} else {
calibrationDate = moment().utc().toDate().toISOString();
}Phase 4: Record Creation
Now that all statuses are calculated, create database records:
// Create main calibration record with correct status
const toolCalibration = await toolCalibrationRepo.create({
date_calibration: calibrationDate,
tool_id: toolId,
user_id: userId,
status: overallStatus ? 1 : 0, // Correct overall status
description: null,
calibration_cycle: tool.calibration_cycle,
calibration_multipiler: tool.calibration_multipiler,
date_last_calibration: tool.date_last_calibration
});
// Create all test records
for (const testStatusData of testStatusesData) {
const calibrationTest = await toolCalibrationTestRepo.create({
calibration_id: toolCalibration.id,
calibration_type_id: requirement.calibration_type_id,
status: testStatus, // Individual test status
is_external: testData.externalCalibration || false,
notes: testData.notes || null
});
// Create detailed records based on type
if (calibration_type.controller === 'pull_test_dellner' && pullTestDetails) {
await this.createPullTestRecords(
pullTestDetails,
calibrationTest.id,
toolCalibrationTestPullSimpleRepo
);
}
// Handle external calibration files
if (testData.externalCalibration) {
const externalRecord = await toolExternalCalibrationRepo.create({
calibration_test_id: calibrationTest.id,
date_external_calibration: moment(
testData.externalData.calibrationDate,
'YYYY-MM-DD'
).toDate().toISOString(),
partner_id: testData.externalData.partnerId,
description: testData.externalData.notes || null
});
// Link files to external calibration
if (testData.externalData.uploadedFiles) {
for (const file of testData.externalData.uploadedFiles) {
await fileRepo.updateById(file.id, {
status: 1, // Active
description: `$trl.external_calibration ${calibrationTest.id}`
});
await toolCalibrationExternalFilesRepo.create({
tool_calibration_external_id: externalRecord.id,
file_id: file.id
});
}
}
}
}calculatePullTestStatus Method
Handles the complex pull test status calculation.
private async calculatePullTestStatus(
testData: any,
toolTypePartPullTestRepo: ToolTypePartPullTestRepository,
toolStandardDiameterRepo: ToolStandardDiameterRepository,
wireSizeRepo: WireSizeRepository
): Promise<{ overallStatus: boolean; testDetails: any[] }>Algorithm:
For each pull test result in
testData.pullTestResults:- Retrieve
ToolTypePartPullTestrecord - Get associated
ToolStandardDiameterfor minimum force - Get
WireSizefor wire specifications - Parse measured force from result string
- Compare:
measured >= minimum - Collect individual test status
- Retrieve
Overall pull test status:
- ALL individual wire size tests must pass
- Return overall status and detailed test data
const pullTestResults = testData.pullTestResults || [];
const allTestStatuses: boolean[] = [];
const testDetails: any[] = [];
for (const pullTestResult of pullTestResults) {
const pull_test_value_n = parseFloat(pullTestResult.result) || 0;
const min_pull_force_n = toolStandardDiameter.min_pull_force_n;
// Individual test pass/fail
const testPassed = pull_test_value_n >= min_pull_force_n;
allTestStatuses.push(testPassed);
testDetails.push({
toolTypePartPullTestId: pullTestResult.id,
part_id: toolTypePartPullTest.tool_part_id,
standard_id: toolStandardDiameter.standard_id,
wire_awg_value: wireSize.awg_value,
wire_mm_value: wireSize.diameter_mm,
min_pull_force_n,
pull_test_value_n,
tensile_tool_id: testData.tensile_tool_id || null,
status: testPassed
});
}
// ALL must pass
const overallStatus = allTestStatuses.every(status => status);
return { overallStatus, testDetails };Database Schema
Key Tables
tool
CREATE TABLE tool (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
tool_type_id INT,
calibration_cycle INT,
calibration_multipiler DECIMAL(10,4),
min_pull_force_n DECIMAL(8,2),
max_pull_force_n DECIMAL(8,2),
date_last_calibration DATETIME,
date_calibration_due DATETIME,
last_calibration_id INT,
system_status_id INT,
calibration_required BOOLEAN DEFAULT FALSE,
-- ... other fields
FOREIGN KEY (tool_type_id) REFERENCES tool_type(id),
FOREIGN KEY (last_calibration_id) REFERENCES tool_calibration(id)
);tool_calibration
CREATE TABLE tool_calibration (
id INT PRIMARY KEY AUTO_INCREMENT,
tool_id INT NOT NULL,
user_id INT NOT NULL,
date_calibration DATETIME DEFAULT NOW(),
date_last_calibration DATETIME,
date_calibration_due DATETIME,
status INT, -- 1 = pass, 0 = fail
calibration_cycle INT,
calibration_multipiler DECIMAL(10,4),
description LONGTEXT,
FOREIGN KEY (tool_id) REFERENCES tool(id),
FOREIGN KEY (user_id) REFERENCES user(id)
);tool_calibration_test
CREATE TABLE tool_calibration_test (
id INT PRIMARY KEY AUTO_INCREMENT,
calibration_id INT NOT NULL,
calibration_type_id INT NOT NULL,
status BOOLEAN, -- true = pass, false = fail
is_external BOOLEAN DEFAULT FALSE,
notes LONGTEXT,
FOREIGN KEY (calibration_id) REFERENCES tool_calibration(id),
FOREIGN KEY (calibration_type_id) REFERENCES tool_calibration_type(id)
);tool_calibration_test_pull_simple
CREATE TABLE tool_calibration_test_pull_simple (
id INT PRIMARY KEY AUTO_INCREMENT,
calibration_test_id INT NOT NULL,
tensile_tool_id INT NOT NULL,
part_id INT NOT NULL,
tool_calibration_standard_id INT NOT NULL,
wire_awg_value VARCHAR(50),
wire_mm_value VARCHAR(50),
min_pull_force_n DECIMAL(8,2) NOT NULL,
pull_test_value_n DECIMAL(8,2) NOT NULL,
status BOOLEAN, -- true = pass, false = fail
FOREIGN KEY (calibration_test_id) REFERENCES tool_calibration_test(id),
FOREIGN KEY (tensile_tool_id) REFERENCES tool(id),
FOREIGN KEY (part_id) REFERENCES part(id),
FOREIGN KEY (tool_calibration_standard_id) REFERENCES tool_calibration_standard(id)
);Indexes
Critical indexes for performance:
-- Find tools due for calibration
CREATE INDEX idx_tool_calibration_due
ON tool(date_calibration_due, system_status_id);
-- Find calibrations by tool
CREATE INDEX idx_calibration_tool_id
ON tool_calibration(tool_id, date_calibration DESC);
-- Find tests by calibration
CREATE INDEX idx_calibration_test_calibration_id
ON tool_calibration_test(calibration_id);
-- Find pull test details
CREATE INDEX idx_pull_simple_test_id
ON tool_calibration_test_pull_simple(calibration_test_id);Validation Logic
Request Validation
The system enforces strict validation at multiple levels:
1. Tool Configuration Validation
if (!tool.calibration_cycle || tool.calibration_cycle === null) {
throw new HttpErrors.BadRequest(
'Tool must have calibration_cycle set before calibration'
);
}
if (!tool.calibration_multipiler || tool.calibration_multipiler === 0) {
throw new HttpErrors.BadRequest(
'Tool must have calibration_multipiler set before calibration'
);
}2. Completeness Validation
ALL requirements must be provided:
const requirements = await toolCalibrationRequirementRepo.find({
where: { tool_type_id: tool.tool_type_id }
});
for (const reqId of requirementIds) {
if (!providedRequirementIds.includes(reqId!)) {
throw new HttpErrors.BadRequest(
`Missing calibration data for requirement ID: ${reqId}`
);
}
}3. Data Format Validation
- External calibration dates must be in
YYYY-MM-DDformat - Pull test results must be parseable as numbers
- Test result values must be "pass" or "fail"
- File IDs must reference existing uploaded files
Status Calculation Algorithm
Overall Calibration Status
Overall Status = AND(all test statuses)
If ALL tests pass → Overall Status = 1 (pass)
If ANY test fails → Overall Status = 0 (fail)Pull Test Status
Pull Test Status = AND(all wire size test statuses)
For each wire size:
Wire Test Status = (measured_force >= min_required_force)
Pull Test Passes = ALL wire tests passVisual/Go-No-Go Test Status
Test Status = (testResult === "pass")External Calibration Status
Test Status = (testResult === "pass")The system trusts the external lab's determination.
Extension Points
Adding New Calibration Types
Step 1: Database Setup
Add new ToolCalibrationType record:
INSERT INTO tool_calibration_type (name, description, controller)
VALUES ('Torque Test', 'Torque wrench calibration', 'torque_test');Step 2: Implement Controller Logic
In ToolRepository.processCalibration(), add handling:
// Around line 545
if (calibration_type.controller === 'torque_test') {
// Calculate torque test status
const torqueTestResult = await this.calculateTorqueTestStatus(
testData,
/* additional repos */
);
testStatus = torqueTestResult.overallStatus;
torqueTestDetails = torqueTestResult.testDetails;
}Step 3: Implement Calculation Method
private async calculateTorqueTestStatus(
testData: any,
/* repositories */
): Promise<{ overallStatus: boolean; testDetails: any[] }> {
// Your calculation logic
const torqueValue = parseFloat(testData.torqueValue);
const minTorque = testData.minTorque;
const maxTorque = testData.maxTorque;
const testPassed = torqueValue >= minTorque && torqueValue <= maxTorque;
return {
overallStatus: testPassed,
testDetails: [{
torqueValue,
minTorque,
maxTorque,
status: testPassed
}]
};
}Step 4: Create Detail Table (if needed)
CREATE TABLE tool_calibration_test_torque (
id INT PRIMARY KEY AUTO_INCREMENT,
calibration_test_id INT NOT NULL,
min_torque_nm DECIMAL(8,2) NOT NULL,
max_torque_nm DECIMAL(8,2) NOT NULL,
measured_torque_nm DECIMAL(8,2) NOT NULL,
status BOOLEAN,
FOREIGN KEY (calibration_test_id) REFERENCES tool_calibration_test(id)
);Step 5: Implement Record Creation
In processCalibration() Phase 4:
if (calibration_type.controller === 'torque_test' && torqueTestDetails) {
await this.createTorqueTestRecords(
torqueTestDetails,
calibrationTest.id,
toolCalibrationTestTorqueRepo
);
}Custom Validation Rules
To add custom validation for specific tool types:
// In processCalibration() after tool retrieval
if (tool.tool_type_id === SPECIAL_TOOL_TYPE_ID) {
// Custom validation
if (!testData.specialField) {
throw new HttpErrors.BadRequest('Special field required for this tool type');
}
}Custom Status Calculation
To implement custom status logic (e.g., weighted scoring):
// Replace simple every() check with custom logic
const overallStatus = this.calculateCustomStatus(testStatusesData, tool);
private calculateCustomStatus(
testStatusesData: any[],
tool: Tool
): boolean {
// Example: Critical tests must pass, others are weighted
const criticalTests = testStatusesData.filter(t => t.isCritical);
const nonCriticalTests = testStatusesData.filter(t => !t.isCritical);
const criticalPass = criticalTests.every(t => t.testStatus);
const nonCriticalScore = nonCriticalTests.filter(t => t.testStatus).length;
const nonCriticalThreshold = nonCriticalTests.length * 0.8; // 80% must pass
return criticalPass && (nonCriticalScore >= nonCriticalThreshold);
}Performance Considerations
Query Optimization
Loading Calibrations with Full Details:
// Efficient single query with deep includes
const filter = {
where: { tool_id: toolId },
include: [
{
relation: 'tests',
scope: {
include: [
'calibration_type',
{
relation: 'pull_tests_simple',
scope: {
include: ['standard', 'tensile_tool', 'part']
}
},
{
relation: 'external',
scope: {
include: ['partner']
}
}
]
}
},
'user',
'tool',
'calibration_cycle_format'
]
};Transaction Handling
The processCalibration method performs multiple database writes. Consider wrapping in a transaction for atomicity:
// Pseudo-code - LoopBack 4 transaction pattern
async processCalibration(...) {
const transaction = await this.dataSource.beginTransaction();
try {
// All database operations
const toolCalibration = await toolCalibrationRepo.create(
{ ... },
{ transaction }
);
// ... more operations
await transaction.commit();
return result;
} catch (error) {
await transaction.rollback();
throw error;
}
}Caching Strategies
For frequently accessed reference data:
// Cache calibration types
private calibrationTypeCache: Map<number, ToolCalibrationType> = new Map();
async getCalibrationType(id: number): Promise<ToolCalibrationType> {
if (!this.calibrationTypeCache.has(id)) {
const type = await this.calibrationTypeRepo.findById(id);
this.calibrationTypeCache.set(id, type);
}
return this.calibrationTypeCache.get(id)!;
}Error Handling
Common Error Scenarios
Tool Not Found
if (!tool) {
throw new HttpErrors.NotFound(`Tool with ID ${toolId} not found`);
}Missing Configuration
if (!tool.calibration_cycle) {
throw new HttpErrors.BadRequest(
'Tool must have calibration_cycle set before calibration'
);
}Incomplete Data
if (!providedRequirementIds.includes(reqId)) {
throw new HttpErrors.BadRequest(
`Missing calibration data for requirement ID: ${reqId}`
);
}Invalid Data Format
const pull_test_value_n = parseFloat(pullTestResult.result);
if (isNaN(pull_test_value_n)) {
throw new HttpErrors.BadRequest(
`Invalid pull test value: ${pullTestResult.result}`
);
}Error Recovery
For partial failures during record creation, consider implementing compensating transactions or maintaining operation logs for audit trails.
Security Considerations
Authorization
Ensure proper authorization checks:
// In controller before calling processCalibration
if (!this.userHasPermission(userId, 'tool.calibrate')) {
throw new HttpErrors.Forbidden('User not authorized to calibrate tools');
}Data Integrity
- All calibration records are immutable (no updates, only creates)
- Maintains full audit trail through user_id and timestamps
- File associations prevent orphaned calibration certificates
Input Sanitization
- Validate all numeric inputs are within reasonable ranges
- Sanitize text fields (notes, descriptions) to prevent injection
- Verify file uploads are legitimate calibration certificates
Monitoring and Observability
Key Metrics to Track
- Calibration Success Rate: Percentage of calibrations passing
- Time to Calibrate: Average duration per calibration type
- Tool Downtime: Time tools spend in calibration vs. production
- Overdue Tools: Count of tools past due date
- Failure Patterns: Tools or tool types with recurring failures
Logging Recommendations
// Log calibration events
logger.info('Calibration started', {
toolId,
userId,
requirementCount: requirements.length
});
logger.info('Calibration completed', {
toolId,
calibrationId: toolCalibration.id,
overallStatus,
testCount: testStatusesData.length,
passedTests: testStatusesData.filter(t => t.testStatus).length
});
// Log failures with details
if (!overallStatus) {
logger.warn('Calibration failed', {
toolId,
calibrationId: toolCalibration.id,
failedTests: testStatusesData
.filter(t => !t.testStatus)
.map(t => t.calibration_type.name)
});
}Testing Strategy
Unit Tests
Test individual calculation methods:
describe('calculatePullTestStatus', () => {
it('should pass when all measurements exceed minimums', async () => {
const testData = {
pullTestResults: [
{ id: 1, result: '50.0' }, // min: 45
{ id: 2, result: '40.0' }, // min: 38
{ id: 3, result: '32.0' } // min: 30
]
};
const result = await repo.calculatePullTestStatus(testData, ...);
expect(result.overallStatus).toBe(true);
expect(result.testDetails).toHaveLength(3);
expect(result.testDetails.every(t => t.status)).toBe(true);
});
it('should fail when any measurement is below minimum', async () => {
const testData = {
pullTestResults: [
{ id: 1, result: '50.0' }, // min: 45 - pass
{ id: 2, result: '35.0' }, // min: 38 - FAIL
{ id: 3, result: '32.0' } // min: 30 - pass
]
};
const result = await repo.calculatePullTestStatus(testData, ...);
expect(result.overallStatus).toBe(false);
expect(result.testDetails[1].status).toBe(false);
});
});Integration Tests
Test full calibration workflow:
describe('processCalibration', () => {
it('should create complete calibration record', async () => {
const result = await toolRepo.processCalibration(
toolId,
userId,
calibrationDataMap
);
expect(result.success).toBe(true);
expect(result.calibrationId).toBeDefined();
// Verify database records
const calibration = await calibrationRepo.findById(result.calibrationId, {
include: ['tests']
});
expect(calibration.status).toBe(1);
expect(calibration.tests).toHaveLength(2);
});
});Migration Guide
Knex Migrations
Key migrations for calibration system:
Initial calibration tables:
/workspace/api/b3api/src/knex_migrations/20240527203454_add_is_external_to_tool_calibration_test.tsPull test simplification:
/workspace/api/b3api/src/knex_migrations/20251019094232_add_new_table_tool_calibration_test_pull_simple.tsCalibration controller endpoint:
/workspace/api/b3api/src/knex_migrations/20251019085223_add_new_controller_tool_calibration.tsData Migration Considerations
When migrating from legacy calibration system:
- Map Old Test Types: Create mapping from old test type IDs to new ToolCalibrationType records
- Preserve History: Migrate historical calibration records maintaining relationships
- Recalculate Due Dates: Run script to recalculate
date_calibration_duefor all tools - Validate Integrity: Ensure all foreign keys are properly linked