Files
GPI/src/server/controllers/analysisController.ts
T

154 lines
6.3 KiB
TypeScript

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 };
}