- 새 M01_MasterData_UI_LogicLab_Copy.ts — [본떠 만들기] → GET /logic/formula(별칭 폼) 편집 → 표 바꾸기 모달(소요량·계수 검색) · 로직 부르기 그대로/같이 복사 갈래 · 식 줄 고치기·덧줄 더하기 → POST /logic/formula/preview 로 되돌린 실제 식을 buildCalc 로 재사용해 시험 계산 → POST /logic/formula/save 로 자체 로직(GX) 저장 · afterSave 로 LogicLab 이 새 키를 바로 엶 - 서버: POST /logic/formula/preview 새로 더함(저장 없이 별칭만 원문으로 되돌림) · 계약 7장 반영 - LogicLab.ts 는 「본떠 만들기」 단추 한 줄만 붙임(정본 로직 화면·New 화면은 안 건드림) - ORCA 로 13-4-1 을 본떠 저장 — 안 고치면 79,279.668 로 원본과 같음 · 석공 줄 곱 2배로 고치면 131,716.728 로 바뀜 확인 · 시험 흔적(GX000001·로직_자체.json·키대장 GX 번호) 지움·되돌림 - 시험 `test_m01_copy.py` 에 미리보기 시험 둘 더함 · M01 시험 130 통과 · typecheck 통과
234 lines
10 KiB
Python
234 lines
10 KiB
Python
"""M01 복사해서 만들기(PLAN 3-4) — 별칭 꼴 식 펴기 · 되돌려 자체 로직으로 저장.
|
|
|
|
계약 `resources/master_data/_화면_계약.md` 7장 · 규칙 `resources/master_data/ref/_설계_식_복사.md`.
|
|
못박는 것
|
|
- 13-4-1 을 식으로 펴 아무것도 안 고치고 되돌려 저장하면 원본과 계산 결과가 같음.
|
|
- 12-4 처럼 같은 표를 다른 인자로 쓰는 로직은 별칭이 번호로 갈림.
|
|
- 로직 전부(1,354줄)가 펴고 되돌리면 원래 식 글자 그대로 돌아옴.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
import sys
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from M01_MasterData import M01_MasterData_Router as router_module
|
|
from M01_MasterData import M01_MasterData_Store as store
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "master_data/scripts"))
|
|
|
|
import master_copy as mcp # noqa: E402
|
|
|
|
REAL = store.FOLDER
|
|
STACK = "GF000219" # 13-4-1 메쌓기(인력) — 같은 표를 같은 인자로 두 번(별칭 하나로 합침)
|
|
FORM = "GF000160" # 12-4 합판거푸집 — 같은 표를 다른 인자로 일곱 번(번호로 갈림)
|
|
|
|
|
|
@pytest.fixture
|
|
def client(tmp_path: Path, monkeypatch) -> TestClient:
|
|
for path in REAL.glob("*.json"):
|
|
if not path.name.startswith("_") or path.name == store.mk.BOOK.name:
|
|
shutil.copy(path, tmp_path / path.name)
|
|
monkeypatch.setattr(store, "FOLDER", tmp_path)
|
|
app = FastAPI()
|
|
app.include_router(router_module.router)
|
|
return TestClient(app)
|
|
|
|
|
|
def _get(client: TestClient, url: str, **params) -> dict:
|
|
res = client.get(url, params=params)
|
|
assert res.status_code == 200, res.text
|
|
return res.json()
|
|
|
|
|
|
def _post(client: TestClient, url: str, body: dict, want: int = 200) -> dict:
|
|
res = client.post(url, json=body)
|
|
assert res.status_code == want, res.text
|
|
return res.json()
|
|
|
|
|
|
def _inputs(key: str) -> dict:
|
|
"""그 로직의 일괄 시험 입력 한 벌(`check_master.시험입력`)."""
|
|
row = store.cm.master(REAL).logic(key)
|
|
return {k: str(v) for k, v in store.cm.시험입력(row).items()}
|
|
|
|
|
|
def _sums(client: TestClient, key: str, inputs: dict) -> dict:
|
|
got = _post(client, "/api/m01/calc", {"key": key, "inputs": inputs})
|
|
assert got["ok"] is True, got
|
|
return got["sums"]
|
|
|
|
|
|
# ── 펴기 ──────────────────────────────────────────────────────────────
|
|
def test_별칭이_식에서_이름_하나로_보이고_원문은_뒤에_남는다(client: TestClient) -> None:
|
|
got = _get(client, "/api/m01/logic/formula", key=STACK)
|
|
names = {one["이름"]: one for one in got["별칭"]}
|
|
assert set(names) == {"메쌓기인력", "높이에대한증가율표"}
|
|
|
|
hit = names["메쌓기인력"]
|
|
assert hit["참조"] == "QF000421"
|
|
assert hit["원문"] == "찾기(QF000421, 뒷길이=뒷길이, 돌=돌, 쌓기=쌓기)"
|
|
assert hit["자리"] == ["호표[0].수량", "호표[1].수량"] # 완전히 같은 부르기는 하나로 합침
|
|
assert "석공" in hit["값칸"]
|
|
|
|
가로 = {one["자리"]: one["식"] for one in got["줄"]}
|
|
assert 가로["호표[0].수량"] == "메쌓기인력.석공 * (1 + 증가율 / 100)" # 값 칸은 그대로 둠
|
|
assert "찾기(" not in 가로["중간[0].식"]
|
|
assert "높이에대한증가율표.증가율" in 가로["중간[0].식"]
|
|
|
|
|
|
def test_같은_표를_다른_인자로_쓰면_별칭이_번호로_갈린다(client: TestClient) -> None:
|
|
got = _get(client, "/api/m01/logic/formula", key=FORM)
|
|
형틀 = [one for one in got["별칭"] if one["참조"] == "QF000357"]
|
|
assert [one["이름"] for one in 형틀] == [f"합판거푸집{n}" for n in range(1, 8)]
|
|
assert [one["원문"] for one in 형틀][:2] == [
|
|
"찾기(QF000357, 구분='합판')",
|
|
"찾기(QF000357, 구분='각재')",
|
|
]
|
|
# 같은 표를 같은 인자로 두 번 쓰는 자리(QF000358)는 번호 없이 하나
|
|
한벌 = [one for one in got["별칭"] if one["참조"] == "QF000358"]
|
|
assert len(한벌) == 1 and 한벌[0]["자리"] == ["중간[0].식", "중간[1].식"]
|
|
|
|
가로 = {one["자리"]: one["식"] for one in got["줄"]}
|
|
assert 가로["호표[0].수량"] == "합판거푸집1.기준수량_1회사용 * 재료율"
|
|
assert 가로["호표[1].수량"] == "합판거푸집2.기준수량_1회사용 * 재료율"
|
|
|
|
|
|
def test_로직_부르기도_별칭으로_보인다(client: TestClient) -> None:
|
|
got = _get(client, "/api/m01/logic/formula", key="GC000994")
|
|
부름 = [one for one in got["별칭"] if one["종류"] == "로직"]
|
|
assert 부름, got["별칭"]
|
|
assert all(one["원문"].startswith("로직(") for one in 부름)
|
|
요소 = [one.get("요소") for one in got["줄"] if one["갈래"] == "호표"]
|
|
assert any(str(one) in {x["이름"] for x in 부름} for one in 요소) # 호표 요소도 별칭으로
|
|
|
|
|
|
def test_로직_전부가_펴고_되돌리면_원래_식으로_돌아온다() -> None:
|
|
files = store.cm.load(folder=REAL)
|
|
master = store.cm.mf.Master(files)
|
|
bad, rows = [], 0
|
|
for data in files.values():
|
|
if data.get("그룹") != "로직":
|
|
continue
|
|
for row in data.get("줄") or []:
|
|
rows += 1
|
|
bad += mcp.check(master, row)
|
|
assert rows > 1000, rows
|
|
assert not bad, bad[:5]
|
|
|
|
|
|
# ── 되돌려 저장 ────────────────────────────────────────────────────────
|
|
def test_안_고치고_되돌려_저장하면_원본과_계산이_같다(client: TestClient) -> None:
|
|
inputs = _inputs(STACK)
|
|
was = _sums(client, STACK, inputs)
|
|
|
|
view = _get(client, "/api/m01/logic/formula", key=STACK)
|
|
made = _post(
|
|
client,
|
|
"/api/m01/logic/formula/save",
|
|
{"로직": view["로직"], "별칭": view["별칭"], "본뜬키": STACK, "이름": "메쌓기 복사본"},
|
|
)
|
|
key = made["key"]
|
|
assert key.startswith("GX"), made
|
|
assert made["logic"]["소유"] == "현장" and made["logic"]["구분"] == "자체"
|
|
assert f"{STACK} 를 본뜸" in made["logic"]["비고"]
|
|
|
|
# 별칭은 저장되지 않음 — 식은 원문 그대로
|
|
식 = made["logic"]["호표"][0]["수량"]
|
|
assert 식 == "찾기(QF000421, 뒷길이=뒷길이, 돌=돌, 쌓기=쌓기).석공 * (1 + 증가율 / 100)"
|
|
assert Decimal(str(_sums(client, key, inputs)["계"])) == Decimal(str(was["계"]))
|
|
assert Decimal(str(was["계"])) > 0
|
|
|
|
# 원본은 그대로
|
|
assert _get(client, "/api/m01/logic", key=STACK)["logic"]["호표"][0]["수량"] == 식
|
|
|
|
|
|
def test_식을_고쳐_저장하면_그_값으로_계산된다(client: TestClient) -> None:
|
|
inputs = _inputs(FORM)
|
|
was = _sums(client, FORM, inputs)
|
|
|
|
view = _get(client, "/api/m01/logic/formula", key=FORM)
|
|
row = view["로직"]
|
|
row["호표"][6]["수량"] = "합판거푸집6.기준수량_1회사용 * 인력율 * 2" # 형틀목공 품 두 배
|
|
made = _post(
|
|
client,
|
|
"/api/m01/logic/formula/save",
|
|
{"로직": row, "별칭": view["별칭"], "본뜬키": FORM},
|
|
)
|
|
saved = made["logic"]["호표"][6]["수량"]
|
|
assert saved == "찾기(QF000357, 구분='형틀목공').기준수량_1회사용 * 인력율 * 2"
|
|
|
|
now = _sums(client, made["key"], inputs)
|
|
assert Decimal(str(now["노무비"])) > Decimal(str(was["노무비"]))
|
|
assert Decimal(str(now["재료비"])) == Decimal(str(was["재료비"]))
|
|
|
|
|
|
def test_별칭_원문이_부르기가_아니면_막는다(client: TestClient) -> None:
|
|
view = _get(client, "/api/m01/logic/formula", key=STACK)
|
|
marks = [{**one} for one in view["별칭"]]
|
|
marks[0]["원문"] = "1 + 1"
|
|
got = _post(
|
|
client,
|
|
"/api/m01/logic/formula/save",
|
|
{"로직": view["로직"], "별칭": marks},
|
|
want=400,
|
|
)
|
|
assert "찾기" in str(got["detail"])
|
|
|
|
|
|
def test_미리보기는_저장_없이_원문으로_되돌린_로직만_준다(client: TestClient) -> None:
|
|
"""3-4 화면의 [시험 계산] 이 쓰는 길 — `/calc` 의 `row`+`file` 로 그대로 넣어 씀."""
|
|
inputs = _inputs(STACK)
|
|
was = _sums(client, STACK, inputs)
|
|
|
|
view = _get(client, "/api/m01/logic/formula", key=STACK)
|
|
before_keys = _get(client, "/api/m01/logics")["logics"]
|
|
preview = _post(
|
|
client, "/api/m01/logic/formula/preview", {"로직": view["로직"], "별칭": view["별칭"]}
|
|
)
|
|
resolved = preview["logic"]["호표"][0]["수량"]
|
|
assert resolved == "찾기(QF000421, 뒷길이=뒷길이, 돌=돌, 쌓기=쌓기).석공 * (1 + 증가율 / 100)"
|
|
after_keys = _get(client, "/api/m01/logics")["logics"]
|
|
assert after_keys == before_keys # 아무것도 저장 안 함
|
|
|
|
calc = _post(
|
|
client,
|
|
"/api/m01/calc",
|
|
{"key": STACK, "inputs": inputs, "row": preview["logic"], "file": view["file"]},
|
|
)
|
|
assert calc["ok"], calc
|
|
assert Decimal(str(calc["sums"]["계"])) == Decimal(str(was["계"]))
|
|
|
|
|
|
def test_미리보기도_별칭_원문이_부르기가_아니면_막는다(client: TestClient) -> None:
|
|
view = _get(client, "/api/m01/logic/formula", key=STACK)
|
|
marks = [{**one} for one in view["별칭"]]
|
|
marks[0]["원문"] = "아무거나"
|
|
got = _post(
|
|
client,
|
|
"/api/m01/logic/formula/preview",
|
|
{"로직": view["로직"], "별칭": marks},
|
|
want=400,
|
|
)
|
|
assert "찾기" in str(got["detail"])
|
|
|
|
|
|
def test_정본_로직은_이_길로도_안_바뀐다(client: TestClient) -> None:
|
|
"""저장은 늘 새 자체 로직(`GX`) — 본뜬 원본 파일은 판본까지 그대로."""
|
|
before = _get(client, "/api/m01/logic", key=STACK)
|
|
view = _get(client, "/api/m01/logic/formula", key=STACK)
|
|
_post(
|
|
client,
|
|
"/api/m01/logic/formula/save",
|
|
{"로직": view["로직"], "별칭": view["별칭"], "본뜬키": STACK},
|
|
)
|
|
after = _get(client, "/api/m01/logic", key=STACK)
|
|
assert after["version"] == before["version"]
|
|
assert after["logic"] == before["logic"]
|