v0.5.3: elektra-buizen min. 50 mm uit elkaar (gebundeld alleen naar de meterkast), vloer-wcd met leiding door de vloer, elektra op de PDF-bladen per wand/vloer/dakvlak, kosten elektra (buis + draad per meter, boorgaten 90 s geteld op stijlen/regels/balken, dozen, groepenkast, keuring), dakbeschot OSB 18 mm bovenop de sporen, felsplaten als dakbedekking

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Machiel
2026-10-06 20:54:49 +02:00
co-authored by Claude Opus 5.5
parent e1a688728c
commit b5510d56c7
13 changed files with 668 additions and 64 deletions
+53 -1
View File
@@ -122,6 +122,10 @@ window.COST_DEFAULTS = {
dakleerMontage: { label: 'Montage dakleer', unit: 's/m²', value: 600 },
rietM2: { label: 'Riet', unit: '€/m²', value: 90 },
rietMontage: { label: 'Montage riet', unit: 's/m²', value: 3600 },
beschotM2: { label: 'Dakbeschot OSB 18 mm (bovenop de sporen)', unit: '€/m²', value: 14 },
beschotMontage: { label: 'Montage dakbeschot', unit: 's/m²', value: 150 },
felsM2: { label: 'Felsplaten staande naad (zink/staal, klangen, scheidingslaag)', unit: '€/m²', value: 65 },
felsMontage: { label: 'Montage felsplaten', unit: 's/m²', value: 1800 },
boeideelM: { label: 'Boeideel', unit: '€/m', value: 25 },
boeideelMontage: { label: 'Montage boeideel', unit: 's/m', value: 600 },
onderplaatM2: { label: 'Onderplaat boeideel', unit: '€/m²', value: 30 },
@@ -157,7 +161,55 @@ window.COST_DEFAULTS = {
gevellamp: { label: 'Gevellamp', unit: '€/st.', value: 45 },
boeideelSpot: { label: 'Boeideel-spot', unit: '€/st.', value: 25 },
binnenspot: { label: 'Binnenspot', unit: '€/st.', value: 20 },
montage: { label: 'Montage per lamp/spot (incl. bekabeling)', unit: 's', value: 900 }
montage: { label: 'Montage per lamp/spot (armatuur plaatsen en aansluiten; leidingen en dozen onder Elektra)', unit: 's', value: 300 }
}
},
// Indicatieve groothandelsprijzen (excl. btw) en tijden; zie ElektraModel
// voor de hoeveelheden (strekkende meters per leidingtype, boorgaten, dozen).
elektra: {
label: 'Elektra',
items: {
personeel: { label: 'Personeel bij elektra (i.p.v. de bezetting)', unit: 'man', value: 1 },
boorgat: { label: 'Boorgat door stijl, regel of balk (aftekenen, boren, ontbramen)', unit: 's', value: 90 },
buisLeggen: { label: 'Installatiebuis leggen en bevestigen', unit: 's/m', value: 60 },
draadTrekken: { label: 'Draad trekken (per meter buis, alle aders)', unit: 's/m', value: 20 },
kabelLeggen: { label: 'XMvK-kabel leggen en bevestigen (buiten)', unit: 's/m', value: 40 },
doosPlaatsen: { label: 'Inbouwdoos plaatsen (luchtdicht, hollewand)', unit: 's', value: 240 },
verdeeldoos: { label: 'Verdeeldoos plaatsen en verbinden', unit: 's', value: 600 },
spotdoos: { label: 'Spotdoos plaatsen (luchtdicht plafond)', unit: 's', value: 300 },
vloerdoos: { label: 'Vloerdoos plaatsen (gat in vloer, aansluiten)', unit: 's', value: 1800 },
afmonteren: { label: 'Afmonteren schakelaar/wcd/apparaataansluiting', unit: 's', value: 300 },
steekverbinderMontage: { label: 'Steekverbinder module-naad monteren (set m/v)', unit: 's', value: 300 },
kastMontage: { label: 'Groepenkast plaatsen (kast, hoofdschakelaar, aardlek)', unit: 's', value: 3600 },
groepMontage: { label: 'Per groep: automaat plaatsen, bedraden, labelen', unit: 's', value: 900 },
keuring: { label: 'Meten, testen en opleveren (NEN 1010 / NEN 3140)', unit: 's', value: 10800 },
buis16M: { label: 'Installatiebuis flexibel Ø16', unit: '€/m', value: 0.35 },
buis19M: { label: 'Installatiebuis flexibel Ø19', unit: '€/m', value: 0.45 },
buis25M: { label: 'Installatiebuis flexibel Ø25', unit: '€/m', value: 0.8 },
draad15M: { label: 'VD-draad 1,5 mm² (per ader)', unit: '€/m', value: 0.28 },
draad25M: { label: 'VD-draad 2,5 mm² (per ader)', unit: '€/m', value: 0.45 },
xmvkM: { label: 'XMvK 3×1,5 mm²', unit: '€/m', value: 1.25 },
inbouwdoos: { label: 'Luchtdichte hollewanddoos', unit: '€/st.', value: 1.6 },
verdeeldoosSt: { label: 'Luchtdichte verdeeldoos', unit: '€/st.', value: 4.5 },
spotdoosSt: { label: 'Luchtdichte spotdoos', unit: '€/st.', value: 7.5 },
vloerdoosSt: { label: 'Vloerdoos met dubbele wcd', unit: '€/st.', value: 95 },
schakelaar: { label: 'Schakelaar enkelpolig (inbouw)', unit: '€/st.', value: 9 },
afdekraam1: { label: 'Afdekraam 1-voudig', unit: '€/st.', value: 3 },
afdekraam2: { label: 'Afdekraam 2-voudig', unit: '€/st.', value: 5 },
wcdDubbel: { label: 'Wandcontactdoos dubbel met randaarde', unit: '€/st.', value: 10 },
wcdEnkel: { label: 'Wandcontactdoos enkel met randaarde', unit: '€/st.', value: 6 },
perilex: { label: 'Perilex-wandcontactdoos (kookgroep)', unit: '€/st.', value: 18 },
werkschakelaar: { label: 'Werkschakelaar (warmtepomp)', unit: '€/st.', value: 28 },
lasklem: { label: 'Lasklem 3-voudig', unit: '€/st.', value: 0.35 },
steekverbinder3: { label: 'Steekverbinder 3-polig (set m/v)', unit: '€/set', value: 9 },
steekverbinder5: { label: 'Steekverbinder 5-polig (set m/v)', unit: '€/set', value: 14 },
doorvoer: { label: 'Luchtdichte kabeldoorvoer naar buiten', unit: '€/st.', value: 3.5 },
kast: { label: 'Groepenkast 3-fase leeg (behuizing, kammen, aansluitmateriaal)', unit: '€/st.', value: 180 },
hoofdschakelaar: { label: 'Hoofdschakelaar 4-polig 40 A', unit: '€/st.', value: 55 },
aardlek: { label: 'Aardlekschakelaar 2-polig 40 A / 30 mA', unit: '€/st.', value: 40 },
automaat: { label: 'Installatieautomaat 1P+N (B10/B16)', unit: '€/st.', value: 14 },
automaat2f: { label: 'Installatieautomaat 2-fase (kookgroep)', unit: '€/st.', value: 30 }
}
},
+71 -2
View File
@@ -77,8 +77,8 @@ window.CostModel = (() => {
// ------------------------------------------------------------
// Berekening
// ------------------------------------------------------------
const CAT_ORDER = ['Hout', 'Schroeven', 'Plaatwerk', 'Isolatie', 'Gevel', 'Dak', 'Vloer', 'Kozijnen', 'Verlichting'];
const ACT_ORDER = ['Zagen', 'Regelwerk monteren', 'Wanden plaatsen', 'Plaatwerk', 'Isoleren', 'Gevel', 'Latwerk', 'Dak', 'Vloer', 'Kozijnen', 'Verlichting'];
const CAT_ORDER = ['Hout', 'Schroeven', 'Plaatwerk', 'Isolatie', 'Gevel', 'Dak', 'Vloer', 'Kozijnen', 'Verlichting', 'Elektra'];
const ACT_ORDER = ['Zagen', 'Regelwerk monteren', 'Wanden plaatsen', 'Plaatwerk', 'Isoleren', 'Gevel', 'Latwerk', 'Dak', 'Vloer', 'Kozijnen', 'Verlichting', 'Elektra'];
const VERTICAL_KINDS = ['verticale balk', 'kreupele stijl'];
const DORPEL_KINDS = ['bovendorpel', 'onderdorpel'];
@@ -233,9 +233,11 @@ window.CostModel = (() => {
addJoints(rf, n * V.verbindingenPerVloerbalk, 'wolfseindbalken', true);
}
addSheet(rf, 'Plaatwerk', 'OSB plafond', rs.osbAreaM2, P.osbM2, P.osbMontage);
addSheet(rf, 'Plaatwerk', `Dakbeschot OSB ${RoofModel.BESCHOT_MM} mm`, rs.beschotM2 || 0, D.beschotM2, D.beschotMontage);
add(rf, 'Isolatie', 'Isoleren', 'Glaswol dak', rs.voidM3, 'm³', rs.voidM3 * S.isolatie.glaswolM3, rs.voidM3 * S.isolatie.glaswolMontage);
add(rf, 'Dak', 'Dak', 'Dakleer (EPDM)', rs.dakleerM2 || 0, 'm²', (rs.dakleerM2 || 0) * D.dakleerM2, (rs.dakleerM2 || 0) * D.dakleerMontage);
add(rf, 'Dak', 'Dak', 'Riet', rs.rietM2 || 0, 'm²', (rs.rietM2 || 0) * D.rietM2, (rs.rietM2 || 0) * D.rietMontage);
add(rf, 'Dak', 'Dak', 'Felsplaten (staande naad)', rs.felsM2 || 0, 'm²', (rs.felsM2 || 0) * D.felsM2, (rs.felsM2 || 0) * D.felsMontage);
});
// --- Woning-breed: dakrand, sandwichpanelen, verlichting ---
@@ -257,6 +259,73 @@ window.CostModel = (() => {
add(wo, 'Verlichting', 'Verlichting', 'Boeideel-spots', spots, 'st.', spots * L.boeideelSpot, spots * L.montage);
add(wo, 'Verlichting', 'Verlichting', 'Binnenspots', lm.indoor, 'st.', lm.indoor * L.binnenspot, lm.indoor * L.montage);
// --- Elektra (ElektraModel): per unit de leidingen (strekkende meters
// per type: buis + draad per ader, of XMvK), boorgaten, steekverbinders,
// dozen en aansluitingen; meterkast, groepen en keuring bij de unit waar
// de meterkast hangt. Arbeid met E.personeel man.
const elk = parts.elektra, E = S.elektra;
if (elk && elk.enabled) {
const unitIds = new Set(parts.units.map(u => u.id)), elOf = {};
const elFor = (uid) => {
const id = unitIds.has(uid) ? uid : null, k = id || '-';
return elOf[k] || (elOf[k] = newEl(id ? `${id}-EL` : 'EL', id ? `Unit ${id} elektra` : 'Elektra', id));
};
const ea = (uid, label, qty, unit, mat, sec) => add(elFor(uid), 'Elektra', 'Elektra', label, qty, unit, mat, sec, E.personeel);
const mkUnit = elk.meterkast ? elk.meterkast.unitId : null;
const groupUnit = (nr) => (elk.groups.find(g => g.nr === nr) || {}).unitId || mkUnit;
const buisPrijs = (d) => d >= 25 ? E.buis25M : d >= 19 ? E.buis19M : E.buis16M;
const agg = {};
elk.runs.forEach(r => {
const uid = groupUnit(r.group), a = agg[uid] = agg[uid] || { types: {}, holes: 0, cross: {} };
a.types[r.type] = (a.types[r.type] || 0) + r.lengthMM / 1000;
a.holes += r.holes || 0;
if (r.crossings) { const n = ElektraModel.cableInfo(r.type).cores.length; a.cross[n] = (a.cross[n] || 0) + r.crossings; }
});
Object.keys(agg).forEach(uid => {
const a = agg[uid];
Object.keys(a.types).sort().forEach(type => {
const c = ElektraModel.cableInfo(type), m = a.types[type];
if (c.conduit) {
const draad = c.mm2 >= 2.5 ? E.draad25M : E.draad15M;
ea(uid, `${c.label}: ${c.use.toLowerCase()} (buis + ${c.cores.length} aders)`, m, 'm', m * (buisPrijs(c.conduit) + c.cores.length * draad), m * (E.buisLeggen + E.draadTrekken));
} else ea(uid, `${c.label}: ${c.use.toLowerCase()}`, m, 'm', m * E.xmvkM, m * E.kabelLeggen);
});
ea(uid, `Boorgaten stijlen/regels/balken (${E.boorgat} s per gat)`, a.holes, 'st.', 0, a.holes * E.boorgat);
Object.keys(a.cross).forEach(n => {
const k = a.cross[n];
ea(uid, `Steekverbinder module-naad ${n}-polig`, k, 'set', k * (Number(n) >= 5 ? E.steekverbinder5 : E.steekverbinder3), k * E.steekverbinderMontage);
});
});
const count = (list, uid) => list.filter(x => x.unitId === uid).length;
parts.units.forEach(u => {
const uid = u.id;
const nSw = count(elk.switches, uid), combos = elk.combos.filter(c => c.unitId === uid);
const raam1 = combos.filter(c => c.switches.length === 1).length, raam2 = combos.length - raam1;
ea(uid, `Schakelaars (${raam1}× 1-voudig, ${raam2}× 2-voudig raam)`, nSw, 'st.',
nSw * (E.schakelaar + E.inbouwdoos) + raam1 * E.afdekraam1 + raam2 * E.afdekraam2, nSw * (E.doosPlaatsen + E.afmonteren));
const nW = count(elk.wcds, uid);
ea(uid, 'Wandcontactdozen dubbel (2 inbouwdozen)', nW, 'st.', nW * (E.wcdDubbel + 2 * E.inbouwdoos), nW * (2 * E.doosPlaatsen + E.afmonteren));
elk.apps.filter(a => a.unitId === uid).forEach(a => {
const price = a.app === 'koken' ? E.perilex : a.app === 'warmtepomp' ? E.werkschakelaar : E.wcdEnkel;
ea(uid, `Aansluitpunt ${a.label.toLowerCase()}`, 1, 'st.', price + E.inbouwdoos, E.doosPlaatsen + E.afmonteren);
});
const nV = count(elk.floorWcds, uid);
ea(uid, 'Vloerdoos met dubbele wcd', nV, 'st.', nV * E.vloerdoosSt, nV * (E.vloerdoos + E.afmonteren));
const nB = count(elk.boxes, uid), nKl = nB * 3 + nSw + nW;
ea(uid, `Verdeeldozen plafond (+ ${nKl} lasklemmen)`, nB, 'st.', nB * E.verdeeldoosSt + nKl * E.lasklem, nB * E.verdeeldoos);
const nSpot = elk.switches.filter(sw => sw.unitId === uid).reduce((t, sw) => t + sw.lamps.filter(l => l.kind === 'binnenspot').length, 0);
ea(uid, 'Spotdozen plafond (luchtdicht)', nSpot, 'st.', nSpot * E.spotdoosSt, nSpot * E.spotdoos);
const nDv = elk.runs.filter(r => r.type === 'buiten' && r.from.startsWith('VD') && groupUnit(r.group) === uid).length;
ea(uid, 'Kabeldoorvoer naar buiten', nDv, 'st.', nDv * E.doorvoer, 0);
});
if (elk.meterkast) {
const g1 = elk.groups.filter(g => (g.poles || 1) === 1).length, g2 = elk.groups.length - g1;
ea(mkUnit, `Groepenkast 3×25 A: kast, hoofdschakelaar, ${elk.rcdCount} aardlek`, 1, 'st.', E.kast + E.hoofdschakelaar + elk.rcdCount * E.aardlek, E.kastMontage);
ea(mkUnit, `Groepen: ${g1}× automaat 1P+N${g2 ? `, ${g2}× 2-fase` : ''}`, elk.groups.length, 'groep', g1 * E.automaat + g2 * E.automaat2f, elk.groups.length * E.groepMontage);
ea(mkUnit, 'Meten, testen en opleveren', 1, 'woning', 0, E.keuring);
}
}
// --- Optellen ---
const sumLines = (lines) => lines.reduce((a, l) => ({ mat: a.mat + l.mat, sec: a.sec + l.sec, clock: a.clock + l.clock }), { mat: 0, sec: 0, clock: 0 });
elements.forEach(el => Object.assign(el, sumLines(el.lines)));