localgreenchain/lib/agents/QualityAssuranceAgent.ts
Claude 88244a4276
Fix QualityAssuranceAgent type errors and deploy Agent 9
- Fix PlantBlock import to use correct module path (PlantBlock.ts)
- Update crypto import to use namespace import pattern
- Fix blockchain.getChain() to use blockchain.chain property
- Add 'critical' severity to QualityReport type for consistency
2025-11-23 00:28:39 +00:00

608 lines
17 KiB
TypeScript

/**
* 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/PlantBlock';
import * as 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<AgentTask | null> {
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<IntegrityCheck> {
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<IntegrityCheck> {
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<string>();
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;
}