Files
Aislo/resources/tester/test_m01_copy.py
T
eomsangdon c57f0ea06d feat(M01): 복사해서 만들기 엔진·서버 — 정본 로직을 별칭 꼴 식으로 펴고 되돌려 자체 로직으로 저장
- `scripts/master_copy.py` — `찾기()`·`로직()` 한 덩이를 별칭 이름으로 · 원문은 별칭표에 그대로
  (자르기는 `master_formula` 파서 그대로 · 완전 같은 부르기는 합침 · 인자가 갈리면 등장 차례로 번호)
- `GET /logic/formula` · `POST /logic/formula/save` — 저장은 별칭을 원문으로 되돌린 뒤 `logic/new` 재사용
- 계약 `_화면_계약.md` 7장 · 시험 `test_m01_copy.py` 8 통과(로직 1,354줄 되돌리기 전수 포함)
2026-09-21 20:02:12 +09:00

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"]