import { Request, Response } from 'express'; import * as applicationRecordService from '../services/applicationRecordService.js'; import * as paintingSchemeService from '../services/paintingSchemeService.js'; import * as inspectionService from '../services/inspectionService.js'; interface AnalysisResult { pieceDescription: string; schemeName: string; schemeType: string; // Yield (Rendimento) realYield: number; theoreticalYield: number; yieldVariance: number; // percentage yieldStatus: 'approved' | 'warning' | 'critical'; // Dilution diluentUsed: number; volumeUsed: number; realDilution: number; targetDilution: number; dilutionStatus: 'approved' | 'warning' | 'critical'; // Thickness (Espessura) realDFT: number; // Average of dryThicknessCalc or inspection points minDFT: number; maxDFT: number; dftStatus: 'approved' | 'warning' | 'critical'; notes: string[]; } export const getProjectAnalysis = async (req: Request, res: Response) => { try { const { projectId } = req.params; const records = await applicationRecordService.getApplicationRecordsByProject(projectId as string); const schemes = await paintingSchemeService.getPaintingSchemesByProject(projectId as string); const inspections = await inspectionService.getInspectionsByProject(projectId as string); const analysis: AnalysisResult[] = []; // Group records by pieceDescription to handle multiple layers/records for same piece if needed // For simple MVP, assuming 1 record per piece for calculation or analyzing individual records for (const record of records) { // Find scheme used (assuming application record doesn't store schemeId directly, we might need to match by name or context? // Checking types.ts, ApplicationRecord doesn't have schemeId. It has 'coatStage'. // PaintingScheme has 'type' which might match 'coatStage' or project context. // Without direct link, we might have to infer or maybe the user links them by name/description effectively? // Actually, in many cases there's only one scheme active per project or we check if schemes exist. // Let's try to match if possible, or use a default if only one scheme exists. // Note: In a real robust system we'd link Record -> Scheme. // Here we will try to find a scheme that matches the coatStage or just take the first one if length is 1. const scheme = schemes.find(s => s.type === record.coatStage) || schemes[0]; if (!scheme) continue; // Calculations // 1. Yield // Yield = Area / Volume const realYield = (record.areaPainted && record.volumeUsed) ? (record.areaPainted / record.volumeUsed) : 0; const theoreticalYield = scheme.yieldTheoretical || 0; const yieldData = calculateVariance(realYield, theoreticalYield); // 2. Dilution const diluent = record.diluentUsed || 0; const volume = record.volumeUsed || 1; // avoid div by zero const realDilution = (diluent / volume) * 100; const targetDilution = scheme.dilution || 0; const dilutionData = calculateVariance(realDilution, targetDilution); // true for "lower is not always better/worse", requires exact match? // Actually for dilution, usually there's a limit. Let's assume variance check. // 3. DFT (Dry Film Thickness) // Use inspection data if available for this piece, otherwise calculated const pieceInspection = inspections.find(i => i.pieceDescription === record.pieceDescription); let realDFT = 0; if (pieceInspection) { const points = (pieceInspection.epsPoints || []).filter((p: number | null | undefined): p is number => p !== undefined && p !== null); if (points.length > 0) { realDFT = points.reduce((a: number, b: number) => a + b, 0) / points.length; } } else { realDFT = record.dryThicknessCalc || 0; } const minDFT = scheme.epsMin || 0; const maxDFT = scheme.epsMax || 9999; let dftStatus: 'approved' | 'warning' | 'critical' = 'approved'; if (realDFT < minDFT) dftStatus = 'critical'; else if (realDFT > maxDFT) dftStatus = 'warning'; analysis.push({ pieceDescription: record.pieceDescription || 'Desconhecido', schemeName: scheme.name, schemeType: scheme.type || '', realYield: Number(realYield.toFixed(2)), theoreticalYield: Number(theoreticalYield.toFixed(2)), yieldVariance: Number(yieldData.variance.toFixed(2)), yieldStatus: yieldData.status, diluentUsed: diluent, volumeUsed: volume, realDilution: Number(realDilution.toFixed(1)), targetDilution: Number(targetDilution.toFixed(1)), dilutionStatus: dilutionData.status, // We might want to warn if dilution is > target realDFT: Number(realDFT.toFixed(1)), minDFT: minDFT, maxDFT: maxDFT, dftStatus, notes: [] }); } res.json(analysis); } catch (error: unknown) { const message = error instanceof Error ? error.message : 'Unknown error'; res.status(500).json({ error: message }); } }; function calculateVariance(actual: number, target: number): { variance: number, status: 'approved' | 'warning' | 'critical' } { if (!target) return { variance: 0, status: 'approved' }; // Variance % = ((Actual - Target) / Target) * 100 const variance = ((actual - target) / target) * 100; // Rules (Simplified) // Yield: Higher is usually good (efficient), but too high might mean too thin. // Lower means using more paint -> expensive. let status: 'approved' | 'warning' | 'critical' = 'approved'; // If deviation is > 20% (warning) or > 30% (critical) if (Math.abs(variance) > 30) status = 'critical'; else if (Math.abs(variance) > 20) status = 'warning'; return { variance, status }; }