/** * QualityAssuranceAgent * Verifies blockchain integrity and data quality * * Responsibilities: * - Verify blockchain integrity * - Detect data anomalies and inconsistencies * - Monitor transaction validity * - Generate data quality reports * - Ensure compliance with data standards */ import { BaseAgent } from './BaseAgent'; import { AgentConfig, AgentTask, QualityReport } from './types'; import { getBlockchain } from '../blockchain/manager'; import { getTransportChain } from '../transport/tracker'; import { PlantBlock } from '../blockchain/types'; import crypto from 'crypto'; interface IntegrityCheck { chainId: string; chainName: string; isValid: boolean; blocksChecked: number; hashMismatches: number; linkBroken: number; timestamp: string; } interface DataQualityIssue { id: string; chainId: string; blockIndex: number; issueType: 'missing_data' | 'invalid_format' | 'out_of_range' | 'duplicate' | 'inconsistent' | 'suspicious'; field: string; description: string; severity: 'low' | 'medium' | 'high' | 'critical'; autoFixable: boolean; suggestedFix?: string; } interface ComplianceStatus { standard: string; compliant: boolean; violations: string[]; score: number; } interface DataStatistics { totalRecords: number; completeRecords: number; partialRecords: number; invalidRecords: number; completenessScore: number; accuracyScore: number; consistencyScore: number; timelinessScore: number; } interface AuditLog { id: string; timestamp: string; action: 'verify' | 'fix' | 'flag' | 'report'; target: string; result: 'pass' | 'fail' | 'warning'; details: string; } export class QualityAssuranceAgent extends BaseAgent { private integrityChecks: IntegrityCheck[] = []; private qualityIssues: DataQualityIssue[] = []; private complianceStatus: ComplianceStatus[] = []; private auditLog: AuditLog[] = []; private dataStats: DataStatistics | null = null; constructor() { const config: AgentConfig = { id: 'quality-assurance-agent', name: 'Quality Assurance Agent', description: 'Verifies data integrity and quality across all chains', enabled: true, intervalMs: 120000, // Run every 2 minutes priority: 'critical', maxRetries: 5, timeoutMs: 60000 }; super(config); } /** * Main execution cycle */ async runOnce(): Promise { const startTime = Date.now(); // Verify blockchain integrity const plantChainCheck = await this.verifyPlantChain(); const transportChainCheck = await this.verifyTransportChain(); this.integrityChecks = [plantChainCheck, transportChainCheck]; // Check data quality const issues = this.checkDataQuality(); this.qualityIssues = [...this.qualityIssues, ...issues].slice(-500); // Check compliance this.complianceStatus = this.checkCompliance(); // Calculate statistics this.dataStats = this.calculateStatistics(); // Log audit this.addAuditLog('verify', 'all_chains', plantChainCheck.isValid && transportChainCheck.isValid ? 'pass' : 'fail', `Plant chain: ${plantChainCheck.isValid}, Transport chain: ${transportChainCheck.isValid}` ); // Generate alerts for critical issues this.generateQualityAlerts(); return this.createTaskResult('quality_assurance', 'completed', { integrityValid: plantChainCheck.isValid && transportChainCheck.isValid, issuesFound: issues.length, complianceScore: this.calculateOverallCompliance(), executionTimeMs: Date.now() - startTime }); } /** * Verify plant blockchain integrity */ private async verifyPlantChain(): Promise { const blockchain = getBlockchain(); const chain = blockchain.chain; let hashMismatches = 0; let linkBroken = 0; for (let i = 1; i < chain.length; i++) { const block = chain[i]; const prevBlock = chain[i - 1]; // Verify hash const expectedHash = this.calculateBlockHash(block); if (block.hash !== expectedHash) { hashMismatches++; } // Verify chain link if (block.previousHash !== prevBlock.hash) { linkBroken++; } } const isValid = hashMismatches === 0 && linkBroken === 0; if (!isValid) { this.createAlert('critical', 'Plant Chain Integrity Compromised', `Found ${hashMismatches} hash mismatches and ${linkBroken} broken links`, { actionRequired: 'Immediate investigation required' } ); } return { chainId: 'plant-chain', chainName: 'Plant Lineage Chain', isValid, blocksChecked: chain.length, hashMismatches, linkBroken, timestamp: new Date().toISOString() }; } /** * Verify transport blockchain integrity */ private async verifyTransportChain(): Promise { const transportChain = getTransportChain(); const isValid = transportChain.isChainValid(); return { chainId: 'transport-chain', chainName: 'Transport Events Chain', isValid, blocksChecked: transportChain.chain.length, hashMismatches: isValid ? 0 : 1, linkBroken: isValid ? 0 : 1, timestamp: new Date().toISOString() }; } /** * Calculate block hash (must match PlantChain implementation) */ private calculateBlockHash(block: PlantBlock): string { const data = `${block.index}${block.timestamp}${JSON.stringify(block.plant)}${block.previousHash}${block.nonce}`; return crypto.createHash('sha256').update(data).digest('hex'); } /** * Check data quality across chains */ private checkDataQuality(): DataQualityIssue[] { const issues: DataQualityIssue[] = []; const blockchain = getBlockchain(); const chain = blockchain.chain.slice(1); const seenIds = new Set(); for (let i = 0; i < chain.length; i++) { const block = chain[i]; const plant = block.plant; // Check for duplicates if (seenIds.has(plant.id)) { issues.push({ id: `issue-${Date.now()}-${i}`, chainId: 'plant-chain', blockIndex: block.index, issueType: 'duplicate', field: 'plant.id', description: `Duplicate plant ID: ${plant.id}`, severity: 'high', autoFixable: false }); } seenIds.add(plant.id); // Check for missing required fields if (!plant.commonName) { issues.push({ id: `issue-${Date.now()}-${i}-name`, chainId: 'plant-chain', blockIndex: block.index, issueType: 'missing_data', field: 'plant.commonName', description: 'Missing common name', severity: 'medium', autoFixable: false }); } // Check location validity if (plant.location) { if (Math.abs(plant.location.latitude) > 90) { issues.push({ id: `issue-${Date.now()}-${i}-lat`, chainId: 'plant-chain', blockIndex: block.index, issueType: 'out_of_range', field: 'plant.location.latitude', description: `Invalid latitude: ${plant.location.latitude}`, severity: 'high', autoFixable: false }); } if (Math.abs(plant.location.longitude) > 180) { issues.push({ id: `issue-${Date.now()}-${i}-lon`, chainId: 'plant-chain', blockIndex: block.index, issueType: 'out_of_range', field: 'plant.location.longitude', description: `Invalid longitude: ${plant.location.longitude}`, severity: 'high', autoFixable: false }); } } // Check generation consistency if (plant.parentPlantId) { const parent = chain.find(b => b.plant.id === plant.parentPlantId); if (parent && plant.generation !== parent.plant.generation + 1) { issues.push({ id: `issue-${Date.now()}-${i}-gen`, chainId: 'plant-chain', blockIndex: block.index, issueType: 'inconsistent', field: 'plant.generation', description: `Generation ${plant.generation} inconsistent with parent generation ${parent.plant.generation}`, severity: 'medium', autoFixable: true, suggestedFix: `Set generation to ${parent.plant.generation + 1}` }); } } // Check for suspicious patterns if (plant.childPlants && plant.childPlants.length > 100) { issues.push({ id: `issue-${Date.now()}-${i}-children`, chainId: 'plant-chain', blockIndex: block.index, issueType: 'suspicious', field: 'plant.childPlants', description: `Unusually high number of children: ${plant.childPlants.length}`, severity: 'low', autoFixable: false }); } // Check timestamp validity const blockDate = new Date(block.timestamp); if (blockDate > new Date()) { issues.push({ id: `issue-${Date.now()}-${i}-time`, chainId: 'plant-chain', blockIndex: block.index, issueType: 'invalid_format', field: 'timestamp', description: 'Timestamp is in the future', severity: 'high', autoFixable: false }); } } return issues; } /** * Check compliance with data standards */ private checkCompliance(): ComplianceStatus[] { const statuses: ComplianceStatus[] = []; // Blockchain Integrity Standard const integrityViolations: string[] = []; for (const check of this.integrityChecks) { if (!check.isValid) { integrityViolations.push(`${check.chainName} failed integrity check`); } } statuses.push({ standard: 'Blockchain Integrity', compliant: integrityViolations.length === 0, violations: integrityViolations, score: integrityViolations.length === 0 ? 100 : 0 }); // Data Completeness Standard (>90% complete records) const completeness = this.dataStats?.completenessScore || 0; statuses.push({ standard: 'Data Completeness (>90%)', compliant: completeness >= 90, violations: completeness < 90 ? [`Completeness at ${completeness}%`] : [], score: completeness }); // Location Accuracy Standard const locationIssues = this.qualityIssues.filter(i => i.field.includes('location') && i.severity === 'high' ); statuses.push({ standard: 'Location Data Accuracy', compliant: locationIssues.length === 0, violations: locationIssues.map(i => i.description), score: Math.max(0, 100 - locationIssues.length * 10) }); // No Duplicates Standard const duplicateIssues = this.qualityIssues.filter(i => i.issueType === 'duplicate'); statuses.push({ standard: 'No Duplicate Records', compliant: duplicateIssues.length === 0, violations: duplicateIssues.map(i => i.description), score: duplicateIssues.length === 0 ? 100 : 0 }); // Lineage Consistency Standard const lineageIssues = this.qualityIssues.filter(i => i.issueType === 'inconsistent' && i.field.includes('generation') ); statuses.push({ standard: 'Lineage Consistency', compliant: lineageIssues.length === 0, violations: lineageIssues.map(i => i.description), score: Math.max(0, 100 - lineageIssues.length * 5) }); return statuses; } /** * Calculate data statistics */ private calculateStatistics(): DataStatistics { const blockchain = getBlockchain(); const chain = blockchain.chain.slice(1); let completeRecords = 0; let partialRecords = 0; let invalidRecords = 0; for (const block of chain) { const plant = block.plant; // Check completeness const hasRequiredFields = plant.id && plant.commonName && plant.location && plant.owner; const hasOptionalFields = plant.environment && plant.growthMetrics; if (!hasRequiredFields) { invalidRecords++; } else if (hasOptionalFields) { completeRecords++; } else { partialRecords++; } } const totalRecords = chain.length; const completenessScore = totalRecords > 0 ? Math.round(((completeRecords + partialRecords * 0.5) / totalRecords) * 100) : 0; // Calculate accuracy (based on issues found) const highSeverityIssues = this.qualityIssues.filter(i => i.severity === 'high' || i.severity === 'critical' ).length; const accuracyScore = Math.max(0, 100 - highSeverityIssues * 5); // Consistency score (based on inconsistency issues) const inconsistencyIssues = this.qualityIssues.filter(i => i.issueType === 'inconsistent' ).length; const consistencyScore = Math.max(0, 100 - inconsistencyIssues * 3); // Timeliness (based on recent data) const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000; const recentRecords = chain.filter(b => new Date(b.timestamp).getTime() > oneWeekAgo ).length; const timelinessScore = totalRecords > 0 ? Math.min(100, Math.round((recentRecords / Math.max(1, totalRecords * 0.1)) * 100)) : 0; return { totalRecords, completeRecords, partialRecords, invalidRecords, completenessScore, accuracyScore, consistencyScore, timelinessScore }; } /** * Add audit log entry */ private addAuditLog( action: AuditLog['action'], target: string, result: AuditLog['result'], details: string ): void { this.auditLog.push({ id: `audit-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, timestamp: new Date().toISOString(), action, target, result, details }); // Keep last 1000 entries if (this.auditLog.length > 1000) { this.auditLog = this.auditLog.slice(-1000); } } /** * Generate alerts for quality issues */ private generateQualityAlerts(): void { const criticalIssues = this.qualityIssues.filter(i => i.severity === 'critical' ); for (const issue of criticalIssues.slice(0, 5)) { this.createAlert('critical', `Data Quality Issue: ${issue.issueType}`, issue.description, { actionRequired: issue.suggestedFix || 'Manual investigation required', relatedEntityId: `block-${issue.blockIndex}`, relatedEntityType: 'block' } ); } // Alert for low compliance for (const status of this.complianceStatus) { if (!status.compliant && status.score < 50) { this.createAlert('warning', `Low Compliance: ${status.standard}`, `Compliance score: ${status.score}%. Violations: ${status.violations.length}`, { actionRequired: 'Review and address compliance violations' } ); } } } /** * Calculate overall compliance score */ private calculateOverallCompliance(): number { if (this.complianceStatus.length === 0) return 0; return Math.round( this.complianceStatus.reduce((sum, s) => sum + s.score, 0) / this.complianceStatus.length ); } /** * Generate quality report */ generateReport(): QualityReport { const chainCheck = this.integrityChecks.find(c => c.chainId === 'plant-chain'); return { chainId: 'plant-chain', isValid: chainCheck?.isValid || false, blocksVerified: chainCheck?.blocksChecked || 0, integrityScore: this.calculateOverallCompliance(), issues: this.qualityIssues.slice(0, 10).map(i => ({ blockIndex: i.blockIndex, issueType: i.issueType, description: i.description, severity: i.severity })), lastVerifiedAt: new Date().toISOString() }; } /** * Get integrity checks */ getIntegrityChecks(): IntegrityCheck[] { return this.integrityChecks; } /** * Get quality issues */ getQualityIssues(severity?: string): DataQualityIssue[] { if (severity) { return this.qualityIssues.filter(i => i.severity === severity); } return this.qualityIssues; } /** * Get compliance status */ getComplianceStatus(): ComplianceStatus[] { return this.complianceStatus; } /** * Get data statistics */ getDataStatistics(): DataStatistics | null { return this.dataStats; } /** * Get audit log */ getAuditLog(limit: number = 100): AuditLog[] { return this.auditLog.slice(-limit); } /** * Manually trigger verification */ async triggerVerification(): Promise<{ plantChain: IntegrityCheck; transportChain: IntegrityCheck; overallValid: boolean; }> { const plantChain = await this.verifyPlantChain(); const transportChain = await this.verifyTransportChain(); this.addAuditLog('verify', 'manual_trigger', plantChain.isValid && transportChain.isValid ? 'pass' : 'fail', 'Manual verification triggered' ); return { plantChain, transportChain, overallValid: plantChain.isValid && transportChain.isValid }; } } // Singleton instance let qaAgentInstance: QualityAssuranceAgent | null = null; export function getQualityAssuranceAgent(): QualityAssuranceAgent { if (!qaAgentInstance) { qaAgentInstance = new QualityAssuranceAgent(); } return qaAgentInstance; }