154 lines
6.3 KiB
TypeScript
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 };
|
|
}
|