- 원본 낙찰율 창처럼 전체·선택한 줄·선택한 공종 하위로 범위 지정, 적용된 줄 제외 - 전체낙찰율 한 비율 또는 비목별 선택, 편집 메뉴에 낙찰률 적용·당초단가 복원 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
3.5 KiB
Python
89 lines
3.5 KiB
Python
"""EST 테스트 앱 낙찰률 적용 범위(원본 TFRMBIMOGBIUL 전체·선택·공종별·적용된 줄 제외)."""
|
|
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
FILES = [
|
|
"native-math.js",
|
|
"native-formula.js",
|
|
"native-machine.js",
|
|
"native-rows.js",
|
|
"native-text.js",
|
|
"native-quantity.cjs",
|
|
"native-wonga.js",
|
|
"native-engine.js",
|
|
"native-row-match.js",
|
|
"native-escalation.js",
|
|
"native-progress.js",
|
|
"native-business.js",
|
|
"native-review.js",
|
|
"native-resources.js",
|
|
"native-schedule.js",
|
|
"native-model.js",
|
|
"native-verify.js",
|
|
"native-bid.js",
|
|
]
|
|
|
|
|
|
def run_js(code: str):
|
|
runner = f"""
|
|
const fs = require('fs'), vm = require('vm');
|
|
const ctx = vm.createContext({{ structuredClone, console }});
|
|
for (const f of {json.dumps(FILES)})
|
|
vm.runInContext(fs.readFileSync('tools/est-test/' + f, 'utf8'), ctx, {{ filename: f }});
|
|
ctx.source = JSON.parse(fs.readFileSync('tools/est-test/sample.json', 'utf8'));
|
|
process.stdout.write(JSON.stringify(vm.runInContext({json.dumps(code)}, ctx)));
|
|
"""
|
|
out = subprocess.check_output(["node", "-e", runner], cwd=ROOT)
|
|
return json.loads(out.decode("utf-8"))
|
|
|
|
|
|
SETUP = """
|
|
const p = EstNative.calculate(EstModel.validate(structuredClone(source)));
|
|
const rows = p.tables.EstMainDat;
|
|
const heading = rows.findIndex((r) => Number(r.Level) > 0 && EstBid.subtree(rows, rows.indexOf(r)).length >= 2);
|
|
const kids = EstBid.subtree(rows, heading);
|
|
const outside = rows.findIndex((r, i) => i > kids.at(-1) && !Number(r.Level) && Number(r.NU) > 0 && Number(r.LinkIndex) !== 54);
|
|
const unit = (q, i) => Number(q.tables.EstMainDat[i].NU);
|
|
"""
|
|
|
|
|
|
def test_group_scope_changes_only_rows_under_selected_heading():
|
|
result = run_js(
|
|
"(() => {"
|
|
+ SETUP
|
|
+ """
|
|
const only = EstBid.scope(p, 'EstMainDat', new Set([heading]), 'groups', false);
|
|
const q = EstBusiness.bid(p, EstBid.rates({method:'total', T:80}), only);
|
|
const inside = kids.find((i) => unit(p, i) > 0);
|
|
const base = Number(p.tables.EstMainDat[inside].ONU ?? unit(p, inside));
|
|
return { inside: Math.abs(unit(q, inside) - base * 0.8) < 1 && q.tables.EstMainDat[inside].ApplyNacChal === 1,
|
|
outside: unit(q, outside) === unit(p, outside), size: only.EstMainDat.size === kids.length };
|
|
})()"""
|
|
)
|
|
assert result == {"inside": True, "outside": True, "size": True}
|
|
|
|
|
|
def test_selected_rows_skip_applied_and_errors():
|
|
result = run_js(
|
|
"(() => {"
|
|
+ SETUP
|
|
+ """
|
|
const first = kids.find((i) => unit(p, i) > 0);
|
|
const once = EstBusiness.bid(p, [90, 90, 90, 90], { EstMainDat: new Set([first]) });
|
|
const errors = [];
|
|
const t = (fn) => { try { fn(); errors.push('ok'); } catch (e) { errors.push(e.message.slice(0, 6)); } };
|
|
t(() => EstBid.scope(once, 'EstMainDat', new Set([first]), 'rows', true));
|
|
t(() => EstBid.scope(once, 'EstMainDat', new Set(), 'rows', false));
|
|
t(() => EstBid.scope(once, 'EstMainDat', new Set([first]), 'groups', false));
|
|
t(() => EstBid.rates({method:'bimog', N:80, G:-1, J:1, S:1}));
|
|
const kept = EstBid.scope(once, 'EstMainDat', new Set([first, outside]), 'rows', true);
|
|
return { errors, kept: [...kept.EstMainDat], all: EstBid.scope(once, 'EstMainDat', new Set(), 'all', false) };
|
|
})()"""
|
|
)
|
|
assert [e[:4] for e in result["errors"]] == ["적용할 ", "낙찰률을", "낙찰률을", "낙찰률은"]
|
|
assert len(result["kept"]) == 1
|
|
assert result["all"] is None
|