- 셈 구동 떼기: 새 M01_MasterData_UI_Logic_Drive.ts(driveRun · previewText · autoFill · createDrive) — M01 상세는 숨은 시험 계산 칸 단추 대신 구동 함수 · 시험 계산 칸(Logic_Calc)은 로직 복사 화면이 쓰므로 남기고 같은 구동을 부름 · 재료 고름은 rerun 고리 - M02: 새 _Sangeun_Tabs.ts(수식 · 단가산출 상세 보기 고르기) · Detail 의 시험 계산 · 인력/재료/경비 걷음 - 걷은 파일(git rm): M01 LogicLab_Sheet · Logic_Sheet_Model · LogicLab_Formula(.ts .css) · FormulaTokens · LogicLab_Est / M02 Sangeun_Sheet(.ts .css) · SheetEdit · SheetFormula · logic Sheet_Model · Const_Model · Formula_Model · 부품 시트 줄(createCalcSheetRow) · Cover_Model.sheetGroups · Calc 의 M02 고리(answer · shown · held) · 죽은 CSS · 글 - 서버 길 · 문서 칸(변수줄 · 글줄 · 덮개)은 남김 — 금액 그대로 - 낡은 시험: test_790_1(시트 화면 전부) · test_714_3 · test_748_2(+ helper)는 화면이 없어져 지움 · test_794_1 · test_790_2 는 화면 · TS 거울만 덜고 서버 시험 남김 · test_714_2 · 717_2b · 721 · 722(원래 틀린 한 줄 고침) · 734 · 737 · 750_2 · 793_3b · 796_2 · 796_2b 는 보기 둘 · 새 파일에 맞춤 · 새 test_799_2_old_tabs_gone Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
235 lines
11 KiB
Python
235 lines
11 KiB
Python
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
|
"""790-2 단가산출(SK) 숫자 넓힘 — 상수 · 표값 덮개 · 글 줄 · 까닭 · M01 원래로 · 짝잃음(요구서_652).
|
|
|
|
- 금액 = 서버 셈 = _분석_650-H2 4 장 견본(지붕 감률 17 → 20 = 61,088 · 공구손료 1 → 2 % = 51,802 ·
|
|
인력운반 120 → 100 = 38,696 · 표값 운반량 30 = 35,764) · 옵션 바꿔도 상수 덮개 식 따라감
|
|
- 식 속 숫자 차례 = 엔진 파서(찾기 머리 · 키 · EQ: · 따옴표 글 안 셈)
|
|
- 옛 SK(새 칸 없음) = 꼴 · 금액 그대로 · 저장 = M01판 · 셈 답 `원래` · `시트`
|
|
- 799-2 — TS 거울(`Const_Model.ts`)은 시트 탭과 함께 걷음 · 서버 `M02_Sangeun_Const` 만 지킴
|
|
- 펼침 = 상수 덮개 SK 안 펼침(참조 그대로 · 값 그대로) · [초기화] = 새 덮개도 걷음
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
from M02_MasterTemplete import M02_Labor_Ref as refs
|
|
from M02_MasterTemplete import M02_Library_Flat as flat
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
|
from M02_MasterTemplete import M02_Sangeun_Const as const
|
|
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
|
from resources.tester.test_m02_labor_ref import MINE, world # noqa: F401 — 같은 임시 층
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
FORM = "GC000211" # 합판거푸집
|
|
CARRY = "GC001140" # 인력운반(지게)
|
|
ROOF = "지붕 슬래브(경사 20˚ 미만)"
|
|
TOOL = "pct:공구손료 및 경장비#0"
|
|
F211 = {
|
|
"자재지역": "포항",
|
|
"유형": "보통",
|
|
"사용횟수": "6회",
|
|
"높이감률": 0,
|
|
"지붕슬래브": "아님",
|
|
"폼타이": "아님",
|
|
"폼타이규격": "12.7㎜",
|
|
"측압": "3t/㎡",
|
|
"구멍땜": 0,
|
|
}
|
|
|
|
|
|
def _whole():
|
|
return m01.loaded()[1]
|
|
|
|
|
|
def _carry():
|
|
return {
|
|
**json.loads(json.dumps(_whole().logic(CARRY)["시험입력"], default=str)),
|
|
"재료종별": "암석",
|
|
"재료형상": "화강암",
|
|
"운반회수": 0,
|
|
}
|
|
|
|
|
|
def _doc(code, given, **more):
|
|
return {
|
|
"키": "SK-000001",
|
|
"이름": "견본",
|
|
"단위": "식",
|
|
"바탕": {"로직": code},
|
|
"입력값": given,
|
|
**more,
|
|
}
|
|
|
|
|
|
def _sum(doc):
|
|
whole = _whole()
|
|
return float(calc.run(sd.clean(doc, None, whole), whole)["기준"]["계"])
|
|
|
|
|
|
# ── 식 속 자리 ────────────────────────────────────────────────────────
|
|
def test_scan_엔진_차례():
|
|
t = "찾기(QC000431, 유형=유형).시공량 * (1 - (높이감률 + 만약(지붕슬래브 == '아님', 0, 17)) / 100)"
|
|
got = const.scan(t)
|
|
assert [t[a:b] for a, b in got["num"]] == ["1", "0", "17", "100"]
|
|
assert [t[a:b] for a, b in got["find"]] == ["찾기(QC000431, 유형=유형).시공량"]
|
|
tool = "(형틀목공 * LB000007 + 보통인부 * LB000002) * 1 / 100"
|
|
assert [tool[a:b] for a, b in const.scan(tool)["num"]] == ["1", "100"]
|
|
assert const.scan("EQ:0201-0012.잡재료율 * 2 + '3t/㎡'")["num"] == [(20, 21)]
|
|
assert const.scan("((((") == {"num": [], "find": []} # 못 읽는 식 = 빈
|
|
assert const.swap(t, {"2": 20}, {"0": -45}).startswith("(-45) * (1 - ")
|
|
assert ", 0, 20)) / 100)" in const.swap(t, {"2": 20}, {})
|
|
|
|
|
|
# ── 금액 = 견본(서버 셈) ──────────────────────────────────────────────
|
|
def test_상수_표값_덮개_견본_금액():
|
|
roof = {**F211, "지붕슬래브": ROOF}
|
|
assert _sum(_doc(FORM, F211)) == 51419
|
|
assert _sum(_doc(FORM, roof)) == 59341
|
|
assert _sum(_doc(FORM, roof, 상수덮개={"mid:시공량": {"2": 20}})) == 61088
|
|
assert _sum(_doc(FORM, F211, 상수덮개={TOOL: {"0": 2}})) == 51802
|
|
assert _sum(_doc(CARRY, _carry())) == 42917
|
|
assert _sum(_doc(CARRY, _carry(), 상수덮개={"mid:일운반회수": {"1": 100}})) == 38696
|
|
assert _sum(_doc(CARRY, _carry(), 표값덮개={"mid:운반량": {"0": 30}})) == 35764
|
|
|
|
|
|
def test_옵션_바꿔도_상수_덮개_따라감():
|
|
"""품 덮개(숫자로 굳힘)와 달리 식 구조 그대로 — 유형 복잡 = 손으로 식 고친 줄과 같은 값."""
|
|
from M02_MasterTemplete import M02_Labor_Doc as labor
|
|
|
|
whole = _whole()
|
|
given = {**F211, "지붕슬래브": ROOF, "유형": "복잡"}
|
|
got = _sum(_doc(FORM, given, 상수덮개={"mid:시공량": {"2": 20}}))
|
|
row = json.loads(json.dumps(whole.logic(FORM), ensure_ascii=False, default=str))
|
|
for s in row["중간"]:
|
|
if s["이름"] == "시공량":
|
|
s["식"] = s["식"].replace(", 0, 17))", ", 0, 20))")
|
|
hand = labor.run_line(calc._Over(whole, FORM, row), FORM, given)["기준"]["계"]
|
|
assert got == float(hand) and got not in (61088, _sum(_doc(FORM, given)))
|
|
|
|
|
|
# ── 문서 꼴 ──────────────────────────────────────────────────────────
|
|
def test_새_칸_꼴_검사():
|
|
whole = _whole()
|
|
doc = _doc(
|
|
FORM,
|
|
F211,
|
|
상수덮개={"mid:시공량": {"2": "20"}, TOOL: {}},
|
|
표값덮개={},
|
|
글줄=[{"자리": "", "글": " 건설표준품셈 6-3-1 "}, {"자리": "mid:시공량", "글": ""}],
|
|
까닭={"mid:시공량": "현장 경사 반영", "add:#0": " ", "add:#1/labor:LB000002#0": "x"},
|
|
)
|
|
got = sd.clean(doc, None, whole)
|
|
assert got["상수덮개"] == {"mid:시공량": {"2": 20}} and "표값덮개" not in got
|
|
assert got["글줄"] == [{"자리": "", "글": "건설표준품셈 6-3-1"}]
|
|
assert got["까닭"] == {"mid:시공량": "현장 경사 반영", "add:#1/labor:LB000002#0": "x"}
|
|
for bad in (
|
|
{"상수덮개": {"엉뚱": {"0": 1}}},
|
|
{"상수덮개": {"mid:시공량": {"a": 1}}},
|
|
{"표값덮개": {"mid:운반량": {"0": "글"}}},
|
|
{"까닭": {"x": "y"}},
|
|
{"글줄": [{"자리": "", "글": "가" * 501}]},
|
|
):
|
|
with pytest.raises(sd.DocError):
|
|
sd.clean(_doc(FORM, F211, **bad), None, whole)
|
|
|
|
|
|
def test_옛_SK_꼴_금액_그대로():
|
|
whole = _whole()
|
|
old = _doc(FORM, F211, 덮개={"labor:LB000007#0": {"qty": 0.2}}, 뺀줄=["labor:LB000002#0"])
|
|
got = sd.clean(old, None, whole)
|
|
assert not set(got) & {*const.NEW, "M01판"}
|
|
assert const.formulas(whole.logic(FORM), got) is whole.logic(
|
|
FORM
|
|
) # 새 덮개 없음 = 그 줄 그대로
|
|
assert sd.clean({**old, "M01판": {"mid:시공량": "x"}}, None, whole) == got # M01판 = 서버 몫
|
|
|
|
|
|
# ── 저장 · 셈 답(임시 storage) ─────────────────────────────────────────
|
|
def _sk(folder, **doc):
|
|
with store.at(folder):
|
|
return refs.create("sangeun", {"이름": "내 단가산출", "단위": "식", "더한줄": [], **doc})[
|
|
"이름"
|
|
]
|
|
|
|
|
|
def _read(folder, key):
|
|
with store.at(folder):
|
|
return store.read("sangeun", key)["문서"]
|
|
|
|
|
|
def _calc(folder, doc, given=None):
|
|
with store.at(folder):
|
|
return calc.calc_doc(doc, given)
|
|
|
|
|
|
def test_저장_M01판_셈답_원래_시트(world):
|
|
mine = world["personal"]
|
|
key = _sk(
|
|
mine,
|
|
바탕={"로직": FORM},
|
|
입력값={**F211, "지붕슬래브": ROOF},
|
|
상수덮개={"mid:시공량": {"2": 20}},
|
|
덮개={"labor:LB000002#0": {"qty": 0.05}},
|
|
글줄=[{"자리": "", "글": "※ 지붕 경사 반영"}],
|
|
까닭={"mid:시공량": "감률 20 % 적용"},
|
|
)
|
|
doc = _read(mine, key)
|
|
assert doc["상수덮개"] == {"mid:시공량": {"2": 20}} and len(doc["바탕판"]) == 16
|
|
assert set(doc["M01판"]) >= {"mid:시공량", TOOL, "labor:LB000007#0"}
|
|
got = _calc(mine, doc)
|
|
assert got["원래"]["계"] == 59341 # 같은 옵션 · 덮개 없음
|
|
assert got["기준"]["계"] != 59341
|
|
no_qty = _calc(mine, {**doc, "덮개": {}})
|
|
assert no_qty["기준"]["계"] == 61088 and no_qty["원래"]["계"] == 59341
|
|
spot = got["시트"]["자리"]["mid:시공량"]
|
|
assert spot["갈래"] == "중간" and spot["덮개"] == ["상수"] and spot["까닭"] == "감률 20 % 적용"
|
|
assert spot["상수"][2] == {"n": 2, "값": 20, "원값": 17}
|
|
assert spot["표값"][0]["글"].startswith("찾기(QC000431") and spot["표값"][0]["값"] == 45
|
|
assert spot["원래"] > spot["지금"] and spot["M01바뀜"] is False
|
|
line = got["시트"]["자리"]["labor:LB000002#0"]
|
|
assert line["덮개"] == ["품"] and line["원래"] != line["지금"]
|
|
assert got["시트"]["글줄"] == [{"자리": "", "글": "※ 지붕 경사 반영"}]
|
|
assert got["시트"]["짝잃음"] == []
|
|
# M01 바뀜 — 저장 지문과 지금 바탕이 다른 자리만 · 사라진 자리 = 짝잃음
|
|
stale = {**doc, "M01판": {**doc["M01판"], "mid:시공량": "000000000000", "mid:없는줄": "1"}}
|
|
got = _calc(mine, stale)
|
|
flags = {s for s, x in got["시트"]["자리"].items() if x.get("M01바뀜")}
|
|
assert flags == {"mid:시공량"}
|
|
assert {"cid": "mid:없는줄", "까닭": "M01 바뀜 — 바탕에 없는 줄"} in got["시트"]["짝잃음"]
|
|
|
|
|
|
def test_짝잃은_새_덮개_알림(world):
|
|
doc = _doc(FORM, F211, 상수덮개={"mid:없음": {"0": 1}, "mid:시공량": {"9": 1}})
|
|
got = _calc(world["personal"], doc)
|
|
assert got["기준"]["계"] == 51419
|
|
why = {(x["cid"], x["까닭"]) for x in got["시트"]["짝잃음"]}
|
|
assert why == {
|
|
("mid:없음", "상수덮개 — 바탕에 없는 줄"),
|
|
("mid:시공량", "상수덮개 — 10 번째 없음"),
|
|
}
|
|
|
|
|
|
def test_옛_저장본_셈답(world):
|
|
"""새 칸 없는 옛 저장본(M01판 없음) = 금액 그대로 · M01바뀜 없음."""
|
|
got = _calc(world["personal"], {**_doc(FORM, F211), "바탕판": "0" * 16})
|
|
assert got["기준"]["계"] == 51419 == got["원래"]["계"]
|
|
assert not any(x.get("M01바뀜") for x in got["시트"]["자리"].values())
|
|
|
|
|
|
def test_펼침_막음_초기화_걷음(world):
|
|
mine = world["personal"]
|
|
a = _sk(mine, 바탕={"로직": CARRY}, 입력값=_carry(), 상수덮개={"mid:일운반회수": {"1": 100}})
|
|
b = _sk(mine, 바탕=None, 더한줄=[{"참조": {**MINE, "키": a}, "수량": 1}])
|
|
assert _read(mine, b)["더한줄"][0]["참조"]["키"] == a # 안 펼침 = 참조 그대로
|
|
assert _calc(mine, _read(mine, b))["기준"]["계"] == 38696
|
|
with store.at(mine), pytest.raises(store.StoreError):
|
|
flat.flat_lines("sangeun", {**MINE, "키": a})
|
|
doc, n = flat.reset("sangeun", {**_read(mine, a), "까닭": {"mid:일운반회수": "가정"}})
|
|
assert n == 1 and not set(doc) & {"상수덮개", "까닭"}
|