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
+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)));