Files
tilbudgivern/archive/analysis/intelligent-quote-generator.js
alexpolo1 4a372d2e28 clean up
2025-10-30 18:28:58 +00:00

310 lines
10 KiB
JavaScript

/**
* TILBUDSGENERERING BASERET PÅ REELLE TIDSDATA
* Intelligent system til beregning af realistiske tilbud
*
* Baseret på analyse af 1093 reelle projekter fra Ordrestyring database
* Erstatter teoretiske PDF-estimater med faktiske gennemsnitsdata
*/
const RealTimeCarpenterTasks = require('./real-time-carpenter-tasks.js');
class IntelligentQuoteGenerator {
constructor() {
this.carpenterDB = new RealTimeCarpenterTasks();
this.hourlyRates = {
standard: 450, // kr/time for standard håndværk
specialist: 550, // kr/time for specialiseret arbejde
complex: 650, // kr/time for komplekst/risikofyldt arbejde
material_markup: 1.25 // 25% tillæg på materialer
};
// Kompleksitetsfaktorer baseret på reelle data analyse
this.complexityFactors = {
simple: 0.8, // Små, enkle opgaver
standard: 1.0, // Standard kompleksitet
complex: 1.3, // Komplekse forhold (højder, adgang, etc.)
very_complex: 1.6 // Meget komplekse (tilbygninger, store tag)
};
}
/**
* Genererer tilbud for specifik opgave type
* @param {string} taskType - Type opgave (tag_small, kitchen, etc.)
* @param {number} size - Størrelse (m², løbende meter, etc.)
* @param {string} complexity - simple|standard|complex|very_complex
* @param {Object} additionalFactors - Ekstra faktorer
* @returns {Object} Komplet tilbud
*/
generateQuote(taskType, size, complexity = 'standard', additionalFactors = {}) {
const taskCategory = this.carpenterDB.taskTimeCategories[taskType];
if (!taskCategory) {
throw new Error(`Ukendt opgave type: ${taskType}`);
}
const baseHours = taskCategory.avgHours;
const complexityFactor = this.complexityFactors[complexity] || 1.0;
// Juster timer baseret på størrelse og kompleksitet
const adjustedHours = this.calculateAdjustedHours(baseHours, size, complexityFactor, additionalFactors);
// Vælg timelløn baseret på opgave type
const hourlyRate = this.selectHourlyRate(taskType, complexity);
// Beregn arbejdsløn
const laborCost = adjustedHours * hourlyRate;
// Estimer materialeomkostninger
const materialCost = this.estimateMaterialCost(taskType, size, additionalFactors);
// Beregn moms og total
const subtotal = laborCost + materialCost;
const vat = subtotal * 0.25; // 25% moms
const total = subtotal + vat;
return {
taskDetails: {
type: taskType,
description: taskCategory.description,
size: size,
complexity: complexity,
projectsBasedOn: taskCategory.projects
},
timeEstimate: {
baseHours: baseHours,
adjustedHours: adjustedHours.toFixed(1),
complexityFactor: complexityFactor,
workingDays: Math.ceil(adjustedHours / 7.4) // 7.4 timer pr dag
},
costs: {
labor: {
hours: adjustedHours.toFixed(1),
hourlyRate: hourlyRate,
amount: laborCost.toFixed(0)
},
materials: {
estimated: materialCost.toFixed(0),
markup: `${((this.hourlyRates.material_markup - 1) * 100)}%`
},
subtotal: subtotal.toFixed(0),
vat: vat.toFixed(0),
total: total.toFixed(0)
},
dataSource: 'Baseret på ' + taskCategory.projects + ' reelle projekter fra database',
validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] // 30 dage
};
}
/**
* Beregner justerede timer baseret på forskellige faktorer
*/
calculateAdjustedHours(baseHours, size, complexityFactor, additionalFactors) {
let adjustedHours = baseHours * complexityFactor;
// Størrelses justeringer - større projekter har skalafordele
if (size) {
if (size > 100) {
adjustedHours *= 0.95; // 5% rabat for store projekter
} else if (size < 20) {
adjustedHours *= 1.1; // 10% tillæg for meget små projekter
}
}
// Adgangs vanskeligheder
if (additionalFactors.difficultAccess) {
adjustedHours *= 1.2;
}
// Højtarbejde
if (additionalFactors.heightWork) {
adjustedHours *= 1.15;
}
// Gammelt byggeri (kan have overraskelser)
if (additionalFactors.oldBuilding && additionalFactors.oldBuilding > 1970) {
adjustedHours *= 1.1;
} else if (additionalFactors.oldBuilding && additionalFactors.oldBuilding < 1950) {
adjustedHours *= 1.25;
}
return adjustedHours;
}
/**
* Vælger passende timeløn baseret på opgave kompleksitet
*/
selectHourlyRate(taskType, complexity) {
// Tag arbejde og tilbygninger kræver specialist kompetencer
if (taskType.includes('tag_') || taskType === 'extension') {
return complexity === 'very_complex' ?
this.hourlyRates.complex : this.hourlyRates.specialist;
}
// Vinduer/døre kræver præcision
if (taskType === 'windows_doors') {
return this.hourlyRates.specialist;
}
// Ombygning kan være uforudsigelig
if (taskType === 'renovation') {
return complexity === 'simple' ?
this.hourlyRates.standard : this.hourlyRates.specialist;
}
// Standard opgaver
return this.hourlyRates.standard;
}
/**
* Estimerer materialeomkostninger
*/
estimateMaterialCost(taskType, size, additionalFactors) {
const materialRates = {
tag_small: 300, // kr/m²
tag_medium: 400, // kr/m²
tag_large: 500, // kr/m²
tag_xlarge: 600, // kr/m²
kitchen: 8000, // kr/løbende meter
bathroom: 1200, // kr/m²
flooring: 400, // kr/m²
windows_doors: 3000, // kr/stk
outdoor: 600, // kr/m²
renovation: 300, // kr/m²
extension: 2500 // kr/m²
};
const baseMaterialCost = (materialRates[taskType] || 300) * (size || 1);
// Tillæg for premium materialer
if (additionalFactors.premiumMaterials) {
return baseMaterialCost * 1.5 * this.hourlyRates.material_markup;
}
return baseMaterialCost * this.hourlyRates.material_markup;
}
/**
* Genererer komplet tilbud for flere opgaver
*/
generateMultiTaskQuote(tasks) {
const quotes = tasks.map(task =>
this.generateQuote(task.type, task.size, task.complexity, task.additionalFactors)
);
const totalHours = quotes.reduce((sum, q) => sum + parseFloat(q.timeEstimate.adjustedHours), 0);
const totalCost = quotes.reduce((sum, q) => sum + parseFloat(q.costs.total), 0);
const totalDays = Math.ceil(totalHours / 7.4);
return {
tasks: quotes,
summary: {
totalHours: totalHours.toFixed(1),
totalDays: totalDays,
totalCost: totalCost.toFixed(0),
averageHourlyRate: (totalCost / totalHours).toFixed(0)
},
projectSchedule: this.generateProjectSchedule(quotes)
};
}
/**
* Genererer projekt tidsplan
*/
generateProjectSchedule(quotes) {
let currentDay = 1;
const schedule = [];
quotes.forEach((quote, index) => {
const taskDays = Math.ceil(parseFloat(quote.timeEstimate.adjustedHours) / 7.4);
schedule.push({
taskNumber: index + 1,
description: quote.taskDetails.description,
startDay: currentDay,
endDay: currentDay + taskDays - 1,
duration: taskDays + ' dage'
});
currentDay += taskDays;
});
return schedule;
}
/**
* Eksempel tilbud for demonstration
*/
generateExampleQuotes() {
const examples = [
{
name: 'Tag renovering - parcelhus',
quote: this.generateQuote('tag_medium', 120, 'standard', {
oldBuilding: 1985,
heightWork: true
})
},
{
name: 'Køkken installation - rækkehus',
quote: this.generateQuote('kitchen', 4, 'standard', {
premiumMaterials: false
})
},
{
name: 'Terrasse - kompleks form',
quote: this.generateQuote('outdoor', 35, 'complex', {
difficultAccess: true,
premiumMaterials: true
})
},
{
name: 'Komplet tilbygning',
quote: this.generateQuote('extension', 25, 'very_complex', {
oldBuilding: 1960
})
}
];
return examples;
}
}
// Test og demonstration
if (require.main === module) {
console.log('💼 INTELLIGENT TILBUDSGENERATOR');
console.log('='.repeat(60));
console.log('Baseret på reelle data fra 1093 projekter\n');
const quoteGen = new IntelligentQuoteGenerator();
// Generer eksempel tilbud
const examples = quoteGen.generateExampleQuotes();
examples.forEach((example, i) => {
console.log(`\n📋 TILBUD ${i+1}: ${example.name.toUpperCase()}`);
console.log('-'.repeat(50));
const q = example.quote;
console.log(`Opgave: ${q.taskDetails.description}`);
console.log(`Størrelse: ${q.taskDetails.size} (${q.taskDetails.complexity})`);
console.log(`Estimeret tid: ${q.timeEstimate.adjustedHours} timer (${q.timeEstimate.workingDays} dage)`);
console.log(`Arbejdsløn: ${parseInt(q.costs.labor.amount).toLocaleString()} kr`);
console.log(`Materialer: ${parseInt(q.costs.materials.estimated).toLocaleString()} kr`);
console.log(`TOTAL inkl. moms: ${parseInt(q.costs.total).toLocaleString()} kr`);
console.log(`Data baseret på: ${q.dataSource}`);
});
// Sammenligning med PDF estimater
console.log('\n\n🔍 SAMMENLIGNING MED PDF-ESTIMATER:');
console.log('='.repeat(60));
const tagQuote = quoteGen.generateQuote('tag_medium', 100, 'standard');
const pdfTagEstimate = 12.5 * 450; // PDF timer * standard timeløn
console.log(`Tag projekt (100m²):`);
console.log(` PDF estimat: ${pdfTagEstimate.toLocaleString()} kr (12.5 timer)`);
console.log(` Reelt estimat: ${parseInt(tagQuote.costs.labor.amount).toLocaleString()} kr (${tagQuote.timeEstimate.adjustedHours} timer)`);
console.log(` Forskel: ${(parseInt(tagQuote.costs.labor.amount) - pdfTagEstimate).toLocaleString()} kr mere`);
console.log(` Faktor: ${(parseInt(tagQuote.costs.labor.amount) / pdfTagEstimate).toFixed(1)}x længere`);
console.log('\n✅ Tilbudsgenerator klar til brug!');
console.log('💡 Brug generateQuote() metoden til specifikke tilbud');
}
module.exports = IntelligentQuoteGenerator;