Files
Aislo/tools/stmate-test/exec_calc.js
T

150 lines
5.6 KiB
JavaScript

/* -*- coding: utf-8 -*- */
/**
* 실행예산 계산 (exec_calc.js) — 실행단가·실행수량 · 중기 실행단가(절사 6택 · 차액 보정 순서)
* 근거: 지식DB STmate/old/20_분석 27 §4(wM_Boq_ExecX · 1단위당 중기사용료 · 1단위=시간 · 최초단가 → 실행단가
* · 절사 1~100,000원 · 차액 보정 기본/노/재/경 · 최초수량 → 실행수량 · 실행예산서 Ⅰ/Ⅱ 0673·0674) · 35 §3
* 미확인(표본 0건, 27 §9): 성분 나눔 방식 — 여기서는 최초단가 비례 · 「기본보정」 = 가장 큰 성분에 차액
* 시험: resources/tester/test_stmate_stage_exec.py (자기 일관성 — 성분 합 = 절사 목표 · 절사 단위 배수)
*/
const StmateExec = (() => {
const Calc = (typeof window !== "undefined" && window.STMATE_CALC) || require("./calc.js");
const Change =
(typeof window !== "undefined" && window.STMATE_CHANGE) || require("./change_calc.js");
const { roundDown } = Calc;
const n0 = (v) => Number(v) || 0;
// 절사 기준 6택 — 1원 · 10원 · 100원 · 1,000원 · 10,000원 · 100,000원 미만 절사
const JEOL = [1, 10, 100, 1000, 10000, 100000];
// 차액 보정 순서 — 기본보정 · 노무비 · 재료비 · 경비
const ORDERS = [
["base", "기본보정"],
["l", "노무비"],
["m", "재료비"],
["s", "경비"],
];
const cut = (v, j) => Math.trunc(n0(v) / (n0(j) || 1) + 1e-9) * (n0(j) || 1);
// 중기 실행단가 — 1단위당 사용료 ÷ 시간 → 절사 → 최초단가 비례 나눔 → 성분 절사 → 차액 한 성분에
function machineExecUnit(p) {
const j = n0(p.jeol) || 1;
const hours = n0(p.hours) || 1;
const target = n0(p.fee) / hours;
const T = cut(target, j);
const init = {
l: n0(p.init && p.init.l),
m: n0(p.init && p.init.m),
s: n0(p.init && p.init.s),
};
const sum = init.l + init.m + init.s;
const comps = {};
for (const k of ["l", "m", "s"]) comps[k] = sum ? cut((T * init[k]) / sum, j) : 0;
if (!sum) comps.s = T;
const diff = T - (comps.l + comps.m + comps.s);
let to = p.order || "base";
if (to === "base") to = ["l", "m", "s"].reduce((a, k) => (init[k] > init[a] ? k : a), "l");
comps[to] += diff;
const ratio = n0(p.initQty) ? n0(p.execQty || p.initQty) / n0(p.initQty) : 1;
return {
target,
T,
comps,
diff,
to,
initT: sum,
execQty: n0(p.execQty || p.initQty),
qtyRatio: ratio,
};
}
// 실행예산 행 — 실행단가 = 직접 입력 또는 trunc(설계단가 × 실행율, 절사 단위) · 실행수량 = 입력 또는 설계수량
function execRow(r, o) {
const ov = (o.overrides || {})[r.id] || {};
const j = n0(o.jeol) || 1;
const rate = { m: n0(o.rateM ?? 100), l: n0(o.rateL ?? 100), s: n0(o.rateS ?? 100) };
const u = {};
["m", "l", "s"].forEach((k, i) => {
u[k] = ov.mls ? n0(ov.mls[i]) : cut((n0(r[k]) * rate[k]) / 100, j);
});
const q = ov.qty != null ? n0(ov.qty) : n0(r.qty);
const a = {
mAmt: roundDown(u.m * q, 0),
lAmt: roundDown(u.l * q, 0),
sAmt: roundDown(u.s * q, 0),
};
a.tAmt = a.mAmt + a.lAmt + a.sAmt;
return { qty: q, mUnit: u.m, lUnit: u.l, sUnit: u.s, tUnit: u.m + u.l + u.s, ...a };
}
// 평면 행 실행예산 — rows [{id, qty, m, l, s, gov}]
function budget(rows, opts = {}) {
const out = rows.map((r) => {
const x = execRow(r, opts);
const d = {
mAmt: roundDown(n0(r.m) * n0(r.qty), 0),
lAmt: roundDown(n0(r.l) * n0(r.qty), 0),
sAmt: roundDown(n0(r.s) * n0(r.qty), 0),
};
d.tAmt = d.mAmt + d.lAmt + d.sAmt;
return Object.assign({}, r, { design: d, exec: x });
});
const tot = (key) =>
out
.filter((r) => !r.gov)
.reduce(
(a, r) => ({
mAmt: a.mAmt + r[key].mAmt,
lAmt: a.lAmt + r[key].lAmt,
sAmt: a.sAmt + r[key].sAmt,
tAmt: a.tAmt + r[key].tAmt,
}),
{ mAmt: 0, lAmt: 0, sAmt: 0, tAmt: 0 },
);
const design = tot("design"),
exec = tot("exec");
return {
rows: out,
design,
exec,
diff: exec.tAmt - design.tAmt,
rate: design.tAmt ? Math.round((exec.tAmt / design.tAmt) * 1e5) / 1e3 : 0,
};
}
// 내역 구조 위 실행예산(실행예산서 Ⅰ: 공종 | 설계(계획) | 실행예산 | 구분) — 머리 합계 포함
function budgetFromTree(tree, stage, opts = {}) {
const nodes = tree.nodes.map((nd) => {
const o = Object.assign({}, nd, { st: {} });
if (nd.kind !== "leaf") return o;
const s = nd.st[stage] || {};
o.st.d = {
qty: n0(s.qty),
mUnit: n0(s.mUnit),
lUnit: n0(s.lUnit),
sUnit: n0(s.sUnit),
tUnit: n0(s.tUnit),
mAmt: n0(s.mAmt),
lAmt: n0(s.lAmt),
sAmt: n0(s.sAmt),
tAmt: n0(s.tAmt),
};
o.st.x = execRow(
{ id: nd.id, qty: s.qty, m: s.mUnit, l: s.lUnit, s: s.sUnit },
nd.gov ? Object.assign({}, opts, { rateM: 100, rateL: 100, rateS: 100 }) : opts,
);
return o;
});
Change.sumHeads(nodes, ["d", "x"]);
const tops = nodes.filter((nd) => nd.kind === "head" && !nd.zone);
const top = tops.reduce((a, h) => (!a || h.level > a.level ? h : a), null);
return {
nodes,
design: top ? top.st.d.tAmt : 0,
exec: top ? top.st.x.tAmt : 0,
};
}
return { JEOL, ORDERS, machineExecUnit, execRow, budget, budgetFromTree };
})();
if (typeof window !== "undefined") window.STMATE_EXEC = StmateExec;
if (typeof module !== "undefined" && module.exports) module.exports = StmateExec;