Files
Aislo/resources/tester/test_est_bid.py
T
eomsangdonandClaude Opus 5.5 6518238ef1 feat(est-test): 낙찰률 적용 범위와 전체낙찰율
- 원본 낙찰율 창처럼 전체·선택한 줄·선택한 공종 하위로 범위 지정, 적용된 줄 제외
- 전체낙찰율 한 비율 또는 비목별 선택, 편집 메뉴에 낙찰률 적용·당초단가 복원

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 17:16:20 +09:00

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