- 보고서: 원본 STMATE 엑셀과 셀 대조(목록표·자재단가대비·중기·일위대가·산근 목록·설계내역서·원가계산서) · 조달청 XML 공식 XSD 검증 · PM_Prt 화면 - 제잡비: 원본 계산 순서 4형식(T/S/TA/SA) · 실무 7건 저장 결과키 전수 1원 · 요율표 조회 · 선택지 계산 · 제잡비·제비율 화면 - 식언어·수량: 함수·조건식·금액변수·문법 검사·수량변수·QCODE · 산근·구성·중기공식 편집 화면 · 순환 알림 - 화면: 원본 메뉴 체계·단축키 · 설계내역서 편집(행 넣기·지우기·이동·레벨·보관함) · 초기값 · 단가목록표 - 저장: 초기값(Start8x.Ini) 바뀐 키 저장 · 노임 환산 직종 = 초기값 CONLD · 산근·구성 결과에 적용단가 칸 채우지 않음(원본 0) - DBF 글 칸 앞 빈칸 보존 · 시험 도우미 표준입력 utf8(한글 조각 깨짐 원인) - 실무 5건 재계산 후 저장 = 바뀐 칸 0 · 시험 332건 통과 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
3.8 KiB
JavaScript
100 lines
3.8 KiB
JavaScript
// STMATE 재현 앱 식 언어·수량산출서 순수 계산 호출 — stdin {calls:[{fn, args}]} → stdout [결과]
|
|
// evaluate·check 의 lookup = 단가표 {코드: {M,L,S,unit}} · qty = 수량코드 행([{CODE, QTY0~9, XCODE}])
|
|
globalThis.window = globalThis;
|
|
const T = "../../tools/stmate-test/";
|
|
// smt_engine 이 쓰는 수치 도우미만(calc.js 원가 계산 묶음과 떨어뜨림)
|
|
globalThis.STMATE_CALC = globalThis.STMATE_CALC || {
|
|
num: (v, d = 0) => {
|
|
const n = parseFloat(String(v ?? "").replace(/,/g, ""));
|
|
return Number.isFinite(n) ? n : d;
|
|
},
|
|
fmt: (v) => String(v),
|
|
};
|
|
const E = require(T + "smt_eval.js");
|
|
const X = require(T + "scr_edit_expr.js");
|
|
const L = require(T + "scr_edit_lang.js");
|
|
const Q = require(T + "scr_qty_sheet.js");
|
|
const PR = require(T + "scr_edit_price.js");
|
|
require(T + "smt_engine.js");
|
|
const ENG = globalThis.STMATE_ENGINE;
|
|
|
|
const lookupOf = (table) => (c) => (table && table[c] ? table[c] : null);
|
|
const qtyOf = (rows) => ENG.qtyResolver({ dbfData: { qcode_rows: rows || [] } });
|
|
|
|
const FN = {
|
|
expr: (src, vars) => X.evaluate(src, vars || {}),
|
|
evaluate: (text, table, opts = {}) => {
|
|
const o = { ...opts, qty: opts.qcode ? qtyOf(opts.qcode) : undefined };
|
|
const ev = E.evaluate(text, lookupOf(table), o);
|
|
return {
|
|
M: ev.M,
|
|
L: ev.L,
|
|
S: ev.S,
|
|
T: ev.T,
|
|
vars: ev.vars,
|
|
labels: ev.labels,
|
|
errors: ev.errors,
|
|
lines: ev.lines,
|
|
};
|
|
},
|
|
composition: (rows, table, opts = {}) => {
|
|
const o = { ...opts };
|
|
if (o.laborMap) o.labor = new Map(Object.entries(o.laborMap));
|
|
return E.evaluateComposition(rows, lookupOf(table), o);
|
|
},
|
|
devd: (q, unit) => E.devdToOrdinary(q, unit),
|
|
normQCode: (c) => E.normQCode(c),
|
|
codeLines: (code, row, opts) => L.codeLines(code, row, opts),
|
|
stripResults: (t) => L.stripResults(t),
|
|
marker: (lv, ex) => L.marker(lv, ex),
|
|
laborMap: (ini, sel, fb) => Object.fromEntries(ENG.laborMap(ini, sel, fb)),
|
|
cycles: (graph) => ENG.findCycles((c) => graph[c] || [], new Set(Object.keys(graph))),
|
|
smtAll: (load) => {
|
|
// 실무 STC 의 산근 전부 — 오류 목록과 금액(금액결과 삭제 뒤 금액 포함)
|
|
const ctx = { dbfData: load, uiState: {}, state: load.sim_state };
|
|
const out = {};
|
|
for (const [code, m] of Object.entries(load.costn_memos || {})) {
|
|
const a = ENG.evaluateSmtFormulaText(m.text, ctx, code);
|
|
const b = ENG.evaluateSmtFormulaText(L.stripResults(m.text), ctx, code);
|
|
out[code] = {
|
|
errors: a.errors,
|
|
sum: [a.sumM, a.sumL, a.sumS],
|
|
stripped: [b.sumM, b.sumL, b.sumS],
|
|
};
|
|
}
|
|
return out;
|
|
},
|
|
cascadeCycles: (load) => {
|
|
globalThis.STMATE_VIEW_PRJ_BOQ = globalThis.STMATE_VIEW_PRJ_BOQ || {
|
|
syncCostnUnitPrice: () => {},
|
|
};
|
|
const ctx = { dbfData: load, uiState: {}, state: load.sim_state };
|
|
return ENG.cascadeRecalculateAll(ctx).cycles;
|
|
},
|
|
adopt: (rows, opts) => PR.adoptCandidates(rows, opts),
|
|
evalSheet: (sheet) => Q.evalSheet(sheet),
|
|
assignFromSheet: (rows, sheet) => Q.assignFromSheet(rows, sheet),
|
|
sheetToBoq: (sheet, mode) => Q.sheetToBoq(sheet, mode),
|
|
checkQcodes: (rows, codes) => Q.checkQcodes(rows, codes),
|
|
nextQcode: (rows) => Q.nextQcode(rows),
|
|
qtyByCode: (rows, kind, field) => Q.qtyByCode(rows, kind, field),
|
|
rollup: (boq, bd, field) => Q.rollup(boq, bd, field),
|
|
roundTrip: (sheet, rows) => Q.deserialize(Q.serialize(sheet, rows)),
|
|
deserialize: (t) => Q.deserialize(t),
|
|
};
|
|
|
|
let buf = "";
|
|
process.stdin.setEncoding("utf8");
|
|
process.stdin.on("data", (d) => (buf += d));
|
|
process.stdin.on("end", () => {
|
|
const { calls } = JSON.parse(buf);
|
|
const out = calls.map(({ fn, args }) => {
|
|
try {
|
|
return FN[fn](...(args || []));
|
|
} catch (e) {
|
|
return { __error: String(e && e.stack ? e.stack : e) };
|
|
}
|
|
});
|
|
process.stdout.write(JSON.stringify(out));
|
|
});
|