Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_2
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
"""흐름도 적용(PLAN 36-4 · 정함 13) — 마스터 사본 폴더 길 시험.
|
||||
|
||||
· 환경 변수 AISLO_MASTER_DIR 가 있으면 M01 저장소 마스터 폴더 = 그 폴더 · 없으면 정본
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
PROBE = "from M01_MasterData import M01_MasterData_Store as s; print(s.FOLDER)"
|
||||
|
||||
|
||||
def _folder(env_dir: str | None) -> Path:
|
||||
env = {k: v for k, v in os.environ.items() if k != "AISLO_MASTER_DIR"}
|
||||
if env_dir:
|
||||
env["AISLO_MASTER_DIR"] = env_dir
|
||||
got = subprocess.run(
|
||||
[sys.executable, "-c", PROBE],
|
||||
cwd=ROOT,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
assert got.returncode == 0, got.stderr
|
||||
return Path(got.stdout.strip().splitlines()[-1])
|
||||
|
||||
|
||||
def test_사본_폴더_변수가_있으면_그_폴더(tmp_path: Path) -> None:
|
||||
assert _folder(str(tmp_path)) == tmp_path.resolve()
|
||||
|
||||
|
||||
def test_변수가_없으면_정본() -> None:
|
||||
assert _folder(None) == (ROOT / "resources" / "master_data").resolve()
|
||||
@@ -680,3 +680,107 @@ def test_만나는_상자는_살아_있는_선이_있으면_셈_서버와_같음
|
||||
k: float(v) for k, v in screen_out.items()
|
||||
}
|
||||
assert [[b["kind"], b["level"], b["link"]] for b in srv["breaks"]] == screen_breaks
|
||||
|
||||
|
||||
# ── 36-0b 선 조건 여러 줄(그리고 · 또는) · 값 목록 ─────────────────────────────
|
||||
def test_조건_여러_줄_그리고_또는_글_식_서버같음() -> None:
|
||||
lg = _ground()
|
||||
two = {
|
||||
"kind": "gate",
|
||||
"conds": [
|
||||
{"from": {"box": "in-ground", "name": "지반"}, "op": "=", "value": "토사"},
|
||||
{"from": {"box": "in-ground", "name": "굴착량"}, "op": "≥", "value": 50},
|
||||
],
|
||||
"join": "그리고",
|
||||
"from": {"box": "in-ground", "name": "지반"},
|
||||
"op": "=",
|
||||
"value": "토사",
|
||||
}
|
||||
either = {**two, "join": "또는"}
|
||||
cases = [
|
||||
(two, {"in-ground": {"지반": "토사", "굴착량": 100}}),
|
||||
(two, {"in-ground": {"지반": "토사", "굴착량": 10}}),
|
||||
(either, {"in-ground": {"지반": "암", "굴착량": 100}}),
|
||||
(either, {"in-ground": {"지반": "암", "굴착량": 10}}),
|
||||
]
|
||||
got = _run(
|
||||
RULE
|
||||
+ f"const base = {json.dumps(lg, ensure_ascii=False)};\n"
|
||||
+ f"const cases = {json.dumps(cases, ensure_ascii=False)};\n"
|
||||
"out(cases.map(([rule, vals]) => {\n"
|
||||
" const lg = { ...base, links: base.links.map(l => l.id === 'l1' ? { ...l, rule } : l) };\n"
|
||||
" const ev = E.evalLogic(lg, vals);\n"
|
||||
" return [R.ruleLabel(rule), R.applyRule(rule, 1, { 지반: vals['in-ground'].지반, 굴착량: vals['in-ground'].굴착량 }).formula,"
|
||||
" ev.ports['fn-exc'], ev.breaks.filter(b => b.link === 'l1').map(b => b.kind)];\n"
|
||||
"}));"
|
||||
)
|
||||
labels = [g[0] for g in got]
|
||||
assert labels[0] == "지반 = 토사 그리고 굴착량 ≥ 50 일 때만"
|
||||
assert labels[2] == "지반 = 토사 또는 굴착량 ≥ 50 일 때만"
|
||||
assert got[0][1] == 'IF(AND(C1="토사",D1>=50),A1,NA())'
|
||||
assert got[2][1] == 'IF(OR(C1="토사",D1>=50),A1,NA())'
|
||||
passed = [g[3] == [] for g in got]
|
||||
assert passed == [True, False, True, False]
|
||||
# 서버(Compute) = 화면(Eval)
|
||||
for (rule, values), (_, _, ports, breaks) in zip(cases, got):
|
||||
srv_lg = {
|
||||
**lg,
|
||||
"links": [{**ln, "rule": rule} if ln["id"] == "l1" else ln for ln in lg["links"]],
|
||||
}
|
||||
srv = _server(srv_lg, values)
|
||||
exc = srv["ports"].get("fn-exc") or {}
|
||||
assert {k: (None if v is None else float(v)) for k, v in exc.items()} == {
|
||||
k: (None if v is None else float(v)) for k, v in ports.items()
|
||||
}
|
||||
assert [b["kind"] for b in srv["breaks"] if b.get("link") == "l1"] == breaks
|
||||
|
||||
|
||||
def test_옛_한_줄_꼴과_makeGate() -> None:
|
||||
got = _run(
|
||||
RULE
|
||||
+ "const old = { kind: 'gate', from: { box: 'b1', name: '지반' }, op: '=', value: '토사' };\n"
|
||||
"const made = M.makeGate([{ from: { box: 'b1', name: '지반' }, op: '≠', value: '암' }, { from: { box: 'b1', name: '높이' }, op: '<', value: 3 }], '또는');\n"
|
||||
"out([M.gateConds(old), R.ruleLabel(old), made.from, made.op, made.value, M.ruleSources(made).map(p => p.name),"
|
||||
" R.applyRule(made, 7, { 지반: '암', 높이: 2 }).value, R.applyRule(made, 7, { 지반: '암', 높이: 5 }).why]);"
|
||||
)
|
||||
conds, label, first_from, first_op, first_value, sources, ok, blocked = got
|
||||
assert conds == {
|
||||
"conds": [{"from": {"box": "b1", "name": "지반"}, "op": "=", "value": "토사"}],
|
||||
"join": "그리고",
|
||||
}
|
||||
assert label == "지반 = 토사 일 때만"
|
||||
assert (first_from, first_op, first_value) == ({"box": "b1", "name": "지반"}, "≠", "암")
|
||||
assert sources == ["지반", "높이"]
|
||||
assert (ok, blocked) == (7, "통과 안 됨")
|
||||
|
||||
|
||||
def test_값_목록() -> None:
|
||||
got = _run(
|
||||
PIPE
|
||||
+ "lg = M.updateBox(lg, dia.id, { input: { ...lg.boxes[0].input, variables: [...lg.boxes[0].input.variables,"
|
||||
" { name: '지반', type: 'choice', unit: '-', choices: ['토사', '암'], example: '토사' }] } });\n"
|
||||
"lg = M.updateBox(lg, mat.id, { input: { ...lg.boxes[1].input, elements: lg.boxes[1].input.elements.map((e, i) => i === 1 ? { ...e, spec: '12mm' } : e) } });\n"
|
||||
"r = add(lg, 'input'); lg = r.lg; const tb = r.box;\n"
|
||||
"lg = M.updateBox(lg, tb.id, { input: { mode: 'group', group: '소요량', elements: [], variables: [], table: { key: 'QF1' } } });\n"
|
||||
"const vals = { [tb.id]: { 표: [['돌', '석공'], ['깬돌', 0.2], ['견치돌', 0.3], ['깬돌', 0.25]] } };\n"
|
||||
"const v = (box, name, opts) => M.valueChoices(lg, { box, name }, opts);\n"
|
||||
"out([v(dia.id, '지반'), v(dia.id, '관경'), v(mat.id, '거푸집 자리'), v(mat.id, '흄관 단가'),"
|
||||
" v(tb.id, '표', { col: '돌', values: vals }), v(tb.id, '표', { col: '없음', values: vals }), v(tb.id, '표'),"
|
||||
" v(f.id, '재료값'), v('없는 상자', '지반')]);"
|
||||
)
|
||||
assert got == [["토사", "암"], None, ["12mm"], None, ["깬돌", "견치돌"], None, None, None, None]
|
||||
|
||||
|
||||
def test_조건_여러_줄_겹침_안맞음() -> None:
|
||||
got = _run(
|
||||
SPLIT
|
||||
+ "const both = (op1, v1) => M.makeGate([{ from: { box: v.id, name: '지반' }, op: op1, value: v1 }, { from: { box: v.id, name: '높이' }, op: '≥', value: 3 }]);\n"
|
||||
"let g = M.setRule(lg, lg.links[0].id, both('=', '토사')).lg;\n"
|
||||
"g = M.setRule(g, lg.links[1].id, both('≠', '암')).lg;\n"
|
||||
"const over = E.breaks(g, V('토사')).filter(b => b.level === '문제').map(b => b.kind);\n"
|
||||
"const none = E.breaks(g, V('암', 2)).filter(b => b.level === '문제').map(b => b.kind);\n"
|
||||
"out([over, none]);"
|
||||
)
|
||||
over, none = got
|
||||
assert "조건 겹침" in over
|
||||
assert "조건 안 맞음" in none
|
||||
|
||||
Reference in New Issue
Block a user