- 끝수 한 곳 `master_cut.py`(B09 `rounddown` 재사용) — 엔진 답에 입힘: 줄 비목마다 0.1 원 버림 · 하위 로직 줄 = 하위 끝수 계 × 수량 · 덧줄 = 버린 줄 합으로 다시 셈 · 비목 합 1 원 버림 · 중간 칸도 버린 값 - 쓰는 곳: M01 시험 계산(`check_master.calc`) · 읽는 식(`master_text.lines`) · 옛 조합 미리보기(`master_combo`) · M02 `run_line`(단가산출 · 일위대가 · 펼침) · 별도계상 뺀 기준 · 켜쌓기율 곱 뒤 · 일위대가 반영 · 반영합 · 비율 줄 · 사전조합 안 줄 - 일위대가 반영 TS 짝(`Labor_Calc.ts`) 같은 끝수 · 거울 헬퍼가 상대 경로 .ts 가져옴 - B09 = `run_line(raw=True)` 엔진 날 값(제 단가로 다시 버림 · % 덧줄 율 비 그대로) — 검증_sub3 B09 247 칸 전후 같음 - `master_text` 꼴 셋 = `common_util_money` 로 갈음(줄 금액 `fmt_line_money` · 식 안 금액 = 버린 0.1 원) - 화면: 상세 줄 금액 · 시험 계산 줄 · 중간 돈 = `formatLineMoney` · 일위대가 반영 칸 0.1 원 · 상세 바닥 · 박스 · 인력재료경비 비목 소계 = 줄 합 1 원 버림 - 테스트1 네 보기 · 서고 목록 = 124,419(옛 124,420 / 124,419.85) · 재료 줄 11,694.6 + 23,425.6 + 1,404.8 = 소계 36,525 - 새 지킴 시험 test_751_3_money_cut - 옛 날 금액(끝수 없음)을 대조하던 시험 24 파일 고침 — 까닭 = 금액 규칙이 줄 0.1 원 · 소계 1 원 버림으로 바뀜(고정 값 → 새 값 · 줄 합 = 계 → 줄 합 아래 비목마다 1 원 안 · 곱 비 = 끝수 폭 안) · 실무 xlsx 영월 엑셀 같음 22 → 23 · 이어 붙이는 TS 시험(704_3 · 717_2b)에 `rounddown` 같은 식 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
135 lines
6.5 KiB
Python
135 lines
6.5 KiB
Python
"""M01 견본 입력(`GET /logic/auto`) · 값 빈 품목은 그 줄만 비움 — PLAN 3-2 · 계약 3-1 표.
|
||
|
||
자동값 = 고르기 목록 첫째 · 범위 최솟값 · 찾기 조건 값은 그 표 가장 낮은 줄 · 나누는 수 1 · 그 밖 0.
|
||
정본 폴더를 읽기만 함(쓰지 않음).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from decimal import Decimal
|
||
|
||
import pytest
|
||
from fastapi import FastAPI
|
||
from fastapi.testclient import TestClient
|
||
|
||
from M01_MasterData import M01_MasterData_Router as router_module
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def client() -> TestClient:
|
||
app = FastAPI()
|
||
app.include_router(router_module.router)
|
||
return TestClient(app)
|
||
|
||
|
||
def _auto(client: TestClient, key: str) -> dict:
|
||
got = client.get("/api/m01/logic/auto", params={"key": key})
|
||
assert got.status_code == 200, got.text
|
||
return got.json()["값"]
|
||
|
||
|
||
def _calc(client: TestClient, key: str) -> dict:
|
||
got = client.post("/api/m01/calc", json={"key": key, "inputs": _auto(client, key)}).json()
|
||
assert got["ok"] is True, got
|
||
return got
|
||
|
||
|
||
def test_고르기는_목록_첫째_범위는_최솟값(client) -> None:
|
||
got = _auto(client, "GF000160") # 12-4 합판거푸집
|
||
assert got["횟수"] == "1회사용시" and got["폼타이"] == "아님"
|
||
assert "합판" not in got # 재료 입력 키 목록은 걷음 — 품목은 기본 검색어(PLAN 8-3)
|
||
assert got["소형할증"] == 0 # 범위 [0, 30]
|
||
assert got["높이할증"] == 0 # 찾기·나누기에 안 쓰임
|
||
|
||
|
||
def test_찾기_조건_값은_표_가장_낮은_줄(client) -> None:
|
||
assert _auto(client, "GC000561")["층수층"] == 0 # 가장 낮은 줄 「6 미만」 — 아래 끝이 열림
|
||
assert _auto(client, "GC000559")["강재총사용량"] == 0 # 「60 미만」
|
||
# 두 표가 같은 입력을 씀 — 한 표는 6 부터 · 한 표는 15 부터 → 둘 다에 드는 가장 낮은 값
|
||
assert _auto(client, "GC000724")["규격"] == 15
|
||
|
||
|
||
def test_나누는_수는_1(client) -> None:
|
||
assert _auto(client, "GC000998")["수직고초과단계"] == 1
|
||
assert _auto(client, "GC000278")["두께"] == 1
|
||
|
||
|
||
def test_없는_로직은_404(client) -> None:
|
||
assert client.get("/api/m01/logic/auto", params={"key": "GF999999"}).status_code == 404
|
||
|
||
|
||
def test_견본_금액은_앞과_같음(client) -> None:
|
||
# 751-3 끝수(줄 0.1 원 · 비목 합 1 원 버림) — 옛 날 값 117,485.212 · 79,279.668
|
||
assert Decimal(str(_calc(client, "GF000160")["sums"]["계"])) == Decimal("117484")
|
||
assert Decimal(str(_calc(client, "GF000219")["sums"]["계"])) == Decimal("79279")
|
||
|
||
|
||
def test_빈_줄을_부르는_덧줄도_비움(client) -> None:
|
||
got = _calc(client, "GF000185") # 레미콘 후보 0 → 레미콘 금액을 부르는 덧줄
|
||
blank = [one for one in got["lines"] if one.get("까닭")]
|
||
assert [one["이름"] for one in blank] == ["구체콘크리트(철근) 레미콘", "콘크리트다짐"]
|
||
assert "구체콘크리트(철근) 레미콘" in blank[1]["까닭"] and blank[1]["금액"] is None
|
||
|
||
|
||
def test_원_단위_입력은_자동값_null(client) -> None:
|
||
got = _auto(client, "GF000051") # 예취기가격(원)
|
||
assert got["예취기가격"] is None
|
||
assert got["조림목본수"] == 0 # 원 아닌 수는 그대로 0
|
||
|
||
|
||
def test_빈_입력을_쓰는_줄만_비움(client) -> None:
|
||
got = _calc(client, "GF000051")
|
||
blank = {one["이름"]: one for one in got["lines"] if one.get("까닭")}
|
||
assert set(blank) == {"예취기 손료"}
|
||
for one in blank.values():
|
||
assert one["단가"] is None and one["금액"] is None and "비어 있음" in one["까닭"]
|
||
rest = sum(Decimal(str(one["금액"])) for one in got["lines"] if one["금액"] is not None)
|
||
gap = rest - Decimal(str(got["sums"]["계"])) # 빈 줄은 빼고 더함 · 비목마다 1 원 버림(751-3)
|
||
assert rest > 0 and 0 <= gap < 3
|
||
assert got["sums"]["경비"] == 0
|
||
|
||
|
||
def test_빠진_원_입력도_null_처럼_줄만_비움(client) -> None:
|
||
given = {k: v for k, v in _auto(client, "GF000051").items() if k != "예취기가격"}
|
||
for path in ("calc", "text"):
|
||
got = client.post(f"/api/m01/{path}", json={"key": "GF000051", "inputs": given}).json()
|
||
assert got["ok"] is True, (path, got)
|
||
lines = client.post("/api/m01/calc", json={"key": "GF000051", "inputs": given}).json()["lines"]
|
||
assert {o["이름"] for o in lines if o.get("까닭")} == {"예취기 손료"}
|
||
|
||
|
||
@pytest.mark.parametrize("key", ["GF000002", "GF000056", "GF000076"])
|
||
def test_체인톱_가격_오일은_자재품목(client, key) -> None:
|
||
"""44-5 — 체인톱 가격 = 「동력체인톱」(기본 첫 규격) · 체인오일 = 「작동유」 · 손 입력 없음."""
|
||
assert not {"체인톱가격", "체인오일단가"} & set(_auto(client, key))
|
||
lines = {o["이름"]: o for o in _calc(client, key)["lines"]}
|
||
assert lines["체인톱 손료"]["품목"]["키"] == "MT016587" # 400mm · 530,000원
|
||
assert lines["체인톱 손료"]["단가"] == 530000 and "경비" in lines["체인톱 손료"]["비목"]
|
||
assert lines["체인오일"]["품목"]["키"] == "MT000583" # 기어작동유 · 4,612원/ℓ
|
||
assert lines["체인오일"]["단가"] == 4612
|
||
gas = Decimal(str(lines["휘발유(체인톱)"]["금액"]))
|
||
assert abs(Decimal(str(lines["잡품"]["금액"])) - gas * Decimal("0.4")) < Decimal(
|
||
"0.1"
|
||
) # 잡품 = 휘발유값 40% 그대로(0.1 원 버림 · 751-3)
|
||
|
||
|
||
def test_체인톱_규격은_고를_수_있음(client) -> None:
|
||
given = _auto(client, "GF000056")
|
||
body = {"key": "GF000056", "inputs": given, "고름": {"4": "MT016588"}} # 4 = 체인톱 손료 줄
|
||
got = client.post("/api/m01/calc", json=body).json()
|
||
saw = next(o for o in got["lines"] if o["이름"] == "체인톱 손료")
|
||
assert saw["품목"]["키"] == "MT016588" and saw["단가"] == 900000
|
||
|
||
|
||
def test_표_찾기_실패는_그_줄만_비우고_까닭은_날글_없이(client) -> None:
|
||
given = {**_auto(client, "GC001000"), "시공구분": "기계시공"} # 표 1-2-3 에 그 조합 줄이 없음
|
||
got = client.post("/api/m01/calc", json={"key": "GC001000", "inputs": given}).json()
|
||
assert got["ok"] is True, got
|
||
blank = [one for one in got["lines"] if one.get("까닭")]
|
||
assert blank and all(one["금액"] is None for one in blank)
|
||
why = blank[0]["까닭"]
|
||
assert "표 QC001869(1-2-3)에 시공구분 기계시공" in why and "인 줄이 없음" in why
|
||
assert not any(ch in why for ch in "{}'") and "Decimal" not in why
|
||
rest = sum(Decimal(str(one["금액"])) for one in got["lines"] if one["금액"] is not None)
|
||
assert Decimal(str(got["sums"]["계"])) == rest
|