- `scripts/master_copy.py` — `찾기()`·`로직()` 한 덩이를 별칭 이름으로 · 원문은 별칭표에 그대로 (자르기는 `master_formula` 파서 그대로 · 완전 같은 부르기는 합침 · 인자가 갈리면 등장 차례로 번호) - `GET /logic/formula` · `POST /logic/formula/save` — 저장은 별칭을 원문으로 되돌린 뒤 `logic/new` 재사용 - 계약 `_화면_계약.md` 7장 · 시험 `test_m01_copy.py` 8 통과(로직 1,354줄 되돌리기 전수 포함)
197 lines
8.5 KiB
Python
197 lines
8.5 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:
|
|
"""저장은 늘 새 자체 로직(`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"]
|