Files
Aislo/resources/tester/test_740_1_library_flat.py
T
eomsangdonandClaude Opus 5.5 4c8a06559a feat(740-1): 단가산출 · 일위대가 서로 독립 — 넣기 펼침(M01 키 줄 + 덮개 사본 · 본뜬) · 초기화 길 · 옮기기 함수 · B09 덮개 줄 굳힘
- 새 M02_Library_Flat: SK 에 SK = 더한줄 로직 한 줄에 원본 몸 통째(덮개 · 뺀줄 · 고름 · 더한줄 · % 덧줄까지 같은 금액) · SC 에 SK = 로직 줄 + 패치 · SK 에 SC = 줄마다 더한줄(수량 = 저장 수량.손 › 실무수량 › 1) · 바탕 SK · 패치 SK 참조 = 덮개 겹침 · 줄 본뜬 {층, 주인, 키, 이름, 때}
- 저장 · 읽기 · 층 복사 때 서버가 펼침(못 펼친 줄 = 옛 참조 그대로) · SC 안 SC(하위 호표)는 저장 · 읽기에서 참조 그대로(엑셀 하위 호표 끝수 · B09 하위 대가) · 찾아 넣기 길만 펴 넣음
- 길: POST {kind}-flat(넣을 줄 · 못 펼침 400) · POST {kind}-reset(미리 보기 걷힐 수 · 씀)
- 초기화 = 덮개 · 뺀줄 · 더한줄 단가 걷음(안쪽 넣은 몸까지) · 항목 · 수량 · 입력값 · 고름 남김
- 옮기기 = migrate(apply) · 전후 1 단위 금액 같을 때만 씀 · 공유 storage 는 브레인
- 둘째 기초 세부 셈 = 넣은 SK 몸이면 이름 없는 SK 로(Sangeun_Calc.sub_run)
- B09: SC 사본 덮개 줄 = 굳힌 줄(굳힌 {키, 줄 …}) → 사본 셈 · 대가 블록이 덮개를 읽음(옛 산근 길은 덮개 무시 · 실측 6,451 대 M02 210,866)
- 733 지문 = 옛 참조 줄 펼친 꼴로 대조
- 낡은 시험 고침(까닭 = 단가산출이 낀 참조가 이제 펼쳐 담김): 715 store(바탕 · 사전조합 SK · 층 복사) · 717-2a 목록 바탕 · 717-4 SC 안 SK · 719-1 원본판(원본 고쳐도 그대로) · 719-2 속 일위대가(빠진 줄 없음 · 쓰지 않는 _calc_given 지움) · 725-1 SK 안 SC · 729 SK 줄 본뜬 · 733-3 쓰는 곳 = 구조물 조합(SK 안 SK 참조 없음) · 572 지문 대조 대상

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 19:47:33 +09:00

367 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
"""740-1 단가산출(SK) · 일위대가(SC) 서로 독립 · 연결 = M01 키 · 초기화(임시 storage · M01 읽기만).
- 넣기 = 펼침(SK 에 SK = 몸 통째 한 줄 · SC 에 SK = 로직 줄 + 패치 · SK 에 SC = 줄마다
더한줄 · SC 에 SC = 넣기 길만 안 줄 펴 넣음 · 저장 · 읽기는 하위 호표 참조 그대로) · 줄 `본뜬` ·
펼치기 전후 금액 같음 · 원본 고쳐도 · 지워도 그대로
- 분석 579-2 실측 E1(129,073 → 481,406) = 이제 안 일어남
- 바탕 SK 참조 · 패치 SK 참조 = 덮개 겹침 · 못 펼침(겹침 · 바탕 없음 · 환산) = 넣기 400 ·
저장은 옛 참조
- 초기화 = 덮개 · 뺀줄 · 더한줄 단가 걷음 → M01 값 · 옵션 · 항목 · 수량 남음 · 길(미리 보기 · 씀)
- 옮기기 함수 = 옛 참조 문서 펼쳐 씀(전후 같음 · 겹침 남김 · 두 번째 = 할 것 없음) · 733 지문 같음
- B09 = SC 덮개 줄 굳힘 → 대가 블록 금액 = M02(옛 산근 길은 덮개를 안 읽었음)
"""
import json
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from common_util.common_util_auth import verify_session
from M01_MasterData import M01_MasterData_Store as m01
from M02_MasterTemplete import M02_Labor_Doc as labor
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_Library_Flat as flat
from M02_MasterTemplete import M02_Library_Same as same
from M02_MasterTemplete import M02_Library_Same_Router as same_router
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_Sangeun_Calc as calc
from M02_MasterTemplete import M02_Sangeun_Doc as sd
from resources.tester.test_m02_labor_ref import MINE, world # noqa: F401
GF = "GF000099" # 측구터파기 토사 · 원/㎥
PCT = "GC001109" # 원/㎡ · 지붕잇기공 · 보통인부 · 「노무비 * 3 / 100」
ADD = {
"종류": "인력",
"요소": "LB000002",
"비목": "노무비",
"이름": "보통인부",
"단위": "인",
"수량": 0.5,
}
USER = {"user_id": 42, "company_id": 7, "role": "USER", "is_master": False, "name": "갑"}
def _whole():
return m01.loaded()[1]
def _raw(folder, kind, **doc):
"""옛 꼴 문서(참조 줄 그대로) — 저장 길을 거치지 않고 파일로."""
(folder / kind).mkdir(parents=True, exist_ok=True)
key = f"{refs.PREFIX[kind]}-{len(list((folder / kind).glob('*.json'))) + 1:06d}"
head = {"종류": refs.TITLE[kind], "키": key, "단위": "㎡"}
body = {"입력값": {}, "덮개": {}, "뺀줄": [], "고름": {}, "자재표시": {}, "더한줄": []}
full = {**head, **(body if kind == "sangeun" else {"자재": []}), **doc}
(folder / kind / f"{key}.json").write_text(json.dumps(full, ensure_ascii=False), "utf-8")
return key
def _make(folder, kind, **doc):
with store.at(folder):
extra = {"더한줄": []} if kind == "sangeun" else {}
return refs.create(kind, {"단위": "㎡", **extra, **doc})["이름"]
def _read(folder, kind, key):
with store.at(folder):
return store.read(kind, key)["문서"]
def _sk_total(folder, doc):
with store.at(folder):
return float(calc.run(sd.clean(doc, None, _whole()), _whole())["1단위"]["계"])
def _sc_value(folder, key, read=None):
with store.at(folder):
got = flat._value({**MINE, "키": key}, read or refs.read_ref, _whole())
assert got is not None
return got["계"]
def _y(folder):
"""원본 SK — 바탕 PCT(% 덧줄) · 품 덮개 · 인력 더한줄(덧줄 밑에 듦)."""
return _make(
folder,
"sangeun",
이름="원본",
바탕={"로직": PCT},
덮개={"labor:LB000036#0": {"qty": 0.036}},
더한줄=[ADD],
)
def test_SK_에_SK_몸_통째_금액_같음_원본_고쳐도_지워도_그대로(world):
me = world["personal"]
y = _y(me)
legacy = {
"이름": "밖",
"단위": "㎥",
"바탕": {"로직": GF},
"더한줄": [{"참조": {**MINE, "키": y}, "수량": 2}],
}
before = _sk_total(me, {"키": "SK-000099", **legacy})
x = _make(me, "sangeun", **legacy)
doc = _read(me, "sangeun", x)
entry = doc["더한줄"][0]
assert not refs.refs_in(doc)
assert entry["로직"] == PCT and entry["덮개"] and entry["더한줄"][0]["요소"] == "LB000002"
assert entry["본뜬"]["키"] == y and entry["본뜬"]["이름"] == "원본" and entry["본뜬"]["때"]
assert _sk_total(me, doc) == pytest.approx(before, rel=1e-9)
with store.at(me): # 원본 고침 · 지움 → 넣은 쪽 그대로
now = store.read("sangeun", y)
store.write(
"sangeun", y, now["판"], {**now["문서"], "덮개": {"labor:LB000002#0": {"qty": 2}}}
)
assert refs.users({**MINE, "키": y}) == []
store.delete("sangeun", y)
assert _sk_total(me, _read(me, "sangeun", x)) == pytest.approx(before, rel=1e-9)
with store.at(me): # 화면 셈 길 = 넣은 줄 세부 펼침(안 더한줄 = add:#0/add:#0)
got = calc.calc_doc(_read(me, "sangeun", x))
sub = next(r for r in got["줄"] if r["cid"] == "add:#0")["펼침"]
assert "add:#0/add:#0" in [r["cid"] for r in sub]
def test_SC_에_SK_패치_겹침_E1_원본_고쳐도_그대로(world):
"""분석 579-2 E1 — 밖 SC(SK 참조 + 줄 패치 · 안 SC) 의 원본 SK 다른 줄을 고쳐도 밖 금액
그대로."""
me = world["personal"]
sk = _make(me, "sangeun", 이름="밑", 바탕={"로직": PCT}, 더한줄=[])
inner = _make(me, "precombo", 이름="안", 담은로직=[{"로직": "GC001111"}])
lines = [
{"참조": {**MINE, "키": sk}, "패치": {"덮개": {"labor:LB000036#0": {"qty": 0.5}}}},
{"참조": {**MINE, "키": inner}},
]
old = _raw(me, "precombo", 이름="옛 밖", 담은로직=lines)
before = _sc_value(me, old)
host = _make(me, "precombo", 이름="밖", 담은로직=lines)
doc = _read(me, "precombo", host)
assert doc["담은로직"][0]["로직"] == PCT and doc["담은로직"][0]["본뜬"]["키"] == sk
assert doc["담은로직"][0]["패치"]["덮개"] == {"labor:LB000036#0": {"qty": 0.5}}
assert doc["담은로직"][1]["참조"]["키"] == inner # 하위 호표(SC 안 SC) = 참조 그대로
assert _sc_value(me, host) == pytest.approx(before, rel=1e-9)
with store.at(me): # E1 — 원본 SK 의 덮지 않은 줄 품 2
now = store.read("sangeun", sk)
store.write(
"sangeun", sk, now["판"], {**now["문서"], "덮개": {"labor:LB000002#0": {"qty": 2}}}
)
assert _sc_value(me, old) > before * 3 # 옛 참조 = 따라감(129,073 → 481,406 꼴)
assert _sc_value(me, host) == pytest.approx(before, rel=1e-9) # 펼친 것 = 그대로
def test_SC_에_SC_펴넣기_실무수량_겹침_넣기_400(world):
me = world["personal"]
hands = {"층": "실무자료", "실무수량": {"줄": {"0": 3}, "자재": {}}}
inner = _make(me, "precombo", 이름="안", 담은로직=[{"로직": PCT}], 본뜬=hands)
outer_hands = {"층": "실무자료", "실무수량": {"줄": {"0": 2, "1": 5}, "자재": {}}}
lines = [{"로직": GF}, {"참조": {**MINE, "키": inner}, "별칭": "안쪽"}]
old = _raw(me, "precombo", 이름="옛", 담은로직=lines, 본뜬=outer_hands)
assert flat.flat_doc("precombo", _read(me, "precombo", old))["담은로직"][1][
"참조"
] # 저장 · 읽기
got = flat.flat_sc(_read(me, "precombo", old), refs.read_ref, _whole()) # 넣기 길 = 펴 넣음
assert [x["로직"] for x in got["담은로직"]] == [GF, PCT] and got["담은로직"][1][
"별칭"
] == "안쪽"
assert got["본뜬"]["실무수량"]["줄"] == {"0": 2, "1": 15} # 바깥 5 × 안 3
def mine(r):
return got if r["키"] == old else refs.read_ref(r)
assert _sc_value(me, old, mine) == pytest.approx(_sc_value(me, old), rel=1e-9)
# 같은 기초가 겹침 — 넣기 길 = 400
dup = [{"로직": PCT}, {"참조": {**MINE, "키": inner}}]
kept = _make(me, "precombo", 이름="겹침", 담은로직=dup)
assert _read(me, "precombo", kept)["담은로직"][1]["참조"]["키"] == inner
with store.at(me), pytest.raises(store.StoreError) as e:
flat.flat_lines("precombo", {**MINE, "키": inner}, None, {"담은로직": [{"로직": PCT}]})
assert e.value.status == 400 and "겹침" in str(e.value.detail)
def test_SK_에_SC_줄마다_더한줄(world):
me = world["personal"]
sc = _make(me, "precombo", 이름="조합", 담은로직=[{"로직": GF}])
with store.at(me):
got = flat.flat_lines("sangeun", {**MINE, "키": sc}, 2)["더한줄"]
assert len(got) == 1 and got[0]["로직"] == GF and got[0]["수량"] == 2 # 실무수량 없음 = 1 × 2
assert got[0]["본뜬"]["키"] == sc
# 742-1 저장 수량(수량.손)이 있으면 그 수 · 안 SC 펴기 = 바깥 × 안
from decimal import Decimal
hand = flat.sc_adds({"담은로직": [{"로직": GF, "수량": {"손": 3}}]}, Decimal(2), _whole(), {})
assert hand[0]["수량"] == 6
lines, _, _ = flat._spread(
{"담은로직": [{"로직": GF, "수량": {"손": 3}}]}, {"수량": {"손": 2}}, None, {}
)
assert lines[0]["수량"] == {"손": 6}
doc = {"키": "SK-000099", "이름": "x", "단위": "㎥", "바탕": None, "더한줄": got}
one = float(labor.run_line(_whole(), GF, {})["기준"]["계"])
assert _sk_total(me, doc) == pytest.approx(2 * one, rel=1e-9)
def test_바탕_SK_겹침_못_펼침_까닭(world):
me = world["personal"]
under = _make(
me, "sangeun", 이름="밑", 바탕={"로직": PCT}, 덮개={"labor:LB000036#0": {"qty": 0.1}}
)
over = {"이름": "위", "단위": "㎡", "바탕": {"참조": {**MINE, "키": under}}, "더한줄": [ADD]}
over |= {"덮개": {"labor:LB000036#0": {"배율": 2}}, "뺀줄": ["labor:LB000002#0"]}
before = _sk_total(me, {"키": "SK-000099", **over})
got = flat.flat_doc("sangeun", {"키": "SK-000099", **over})
assert got["바탕"] == {"로직": PCT} and got["덮개"]["labor:LB000036#0"]["qty"] == 0.2
assert got["뺀줄"] == ["labor:LB000002#0"]
assert _sk_total(me, got) == pytest.approx(before, rel=1e-9)
# SC 에 바탕 없는 SK · 단위 환산 SK = 못 넣음
bare = _make(me, "sangeun", 이름="바탕 없음", 바탕=None, 더한줄=[ADD])
with store.at(me):
for key, why in ((bare, "바탕 기초 없는"),):
with pytest.raises(store.StoreError) as e:
flat.flat_lines("precombo", {**MINE, "키": key})
assert e.value.status == 400 and why in str(e.value.detail)
def test_초기화_SK_SC_M01_값_옵션_남음(world):
me = world["personal"]
rock = {"보정작업": "암석작업(연암・보통암・경암)"}
entry = {
"종류": "로직",
"로직": PCT,
"입력값": {},
"수량": 1,
"덮개": {"labor:LB000036#0": {"qty": 1}},
}
priced = {**ADD, "단가": 1}
x = _make(
me,
"sangeun",
이름="고친 것",
단위="㎥",
바탕={"로직": GF},
입력값=rock,
덮개={"labor:LB000002#0": {"qty": 1}},
뺀줄=["logic:GC000267#0"],
더한줄=[entry, priced],
)
with store.at(me):
look = flat.reset_doc("sangeun", x, None, True, None)
assert look["걷힘"] == 4 and _read(me, "sangeun", x)["덮개"] # 미리 보기 = 안 씀
done = flat.reset_doc("sangeun", x, None, False, None)
doc = _read(me, "sangeun", x)
assert doc["덮개"] == {} and doc["뺀줄"] == [] and doc["입력값"] == rock
assert [x.get("요소") or x.get("로직") for x in doc["더한줄"]] == [PCT, "LB000002"]
assert "덮개" not in doc["더한줄"][0] and "단가" not in doc["더한줄"][1]
assert done["걷힘"] == 4
plain = {"키": "SK-000099", "이름": "x", "단위": "㎥", "바탕": {"로직": GF}, "입력값": rock}
plain["더한줄"] = [{**entry, "덮개": {}}, ADD]
assert _sk_total(me, doc) == pytest.approx(_sk_total(me, plain), rel=1e-9)
sc = _make(
me,
"precombo",
이름="줄 덮개",
담은로직=[
{"로직": PCT, "패치": {"덮개": {"labor:LB000036#0": {"qty": 1}}, "더한줄": [priced]}}
],
)
with store.at(me):
got = flat.reset_doc("precombo", sc, None, False, None)
line = _read(me, "precombo", sc)["담은로직"][0]
assert got["걷힘"] == 2 and line["패치"] == {"더한줄": [{**ADD, "수량": 0.5}]}
def test_길_넣기_초기화_권한(world):
app = FastAPI()
app.include_router(same_router.router)
app.dependency_overrides[verify_session] = lambda: USER
client = TestClient(app)
me = world["personal"]
y = _y(me)
url = "/api/m02/layers/personal/{}-{}"
res = client.post(url.format("sangeun", "flat"), json={"참조": {**MINE, "키": y}, "수량": 1})
assert res.status_code == 200, res.text
assert res.json()["더한줄"][0]["본뜬"]["키"] == y
bad = client.post(url.format("precombo", "flat"), json={"참조": {**MINE, "키": "SK-000777"}})
assert bad.status_code == 400
look = client.post(url.format("sangeun", "reset"), json={"키": y})
assert look.status_code == 200 and look.json()["걷힘"] == 1
firm = client.post("/api/m02/layers/company/sangeun-reset", json={"키": y, "미리보기": False})
assert firm.status_code == 403 # 일반 사용자 회사 층 쓰기 막힘
def test_옮기기_함수_전후_같음_겹침_남김(world):
me = world["personal"]
y = _y(me)
inner = _make(me, "precombo", 이름="안", 담은로직=[{"로직": GF}])
sk = _raw(
me,
"sangeun",
이름="옛 SK",
단위="㎥",
바탕={"로직": GF},
더한줄=[{"참조": {**MINE, "키": y}, "수량": 1}],
)
sc = _raw(
me,
"precombo",
이름="옛 SC",
담은로직=[{"참조": {**MINE, "키": y}}, {"참조": {**MINE, "키": inner}}],
)
dup = _raw(
me, "precombo", 이름="겹침", 담은로직=[{"로직": GF}, {"참조": {**MINE, "키": inner}}]
)
before = {k: _sc_value(me, k) for k in (sc, dup)} | {
sk: _sk_total(me, _read(me, "sangeun", sk))
}
look = {r["키"]: r for r in flat.migrate(False)}
assert look[sk]["같음"] and look[sc]["같음"] and not look[sc]["씀"]
assert look[sc]["남은참조"] == look[dup]["남은참조"] == [inner] # 하위 호표 = 그대로
rows = {r["키"]: r for r in flat.migrate(True)}
assert rows[sk]["씀"] and rows[sc]["씀"] and not rows[dup]["씀"]
assert not refs.refs_in(json.loads((me / "sangeun" / f"{sk}.json").read_bytes()))
left = refs.refs_in(json.loads((me / "precombo" / f"{sc}.json").read_bytes()))
assert [r["키"] for r in left] == [inner]
assert _sk_total(me, _read(me, "sangeun", sk)) == pytest.approx(before[sk], rel=1e-9)
assert _sc_value(me, sc) == pytest.approx(before[sc], rel=1e-9)
assert _sc_value(me, dup) == pytest.approx(before[dup], rel=1e-9)
again = flat.migrate(False) # 두 번째 = 하위 호표 참조만 남음(바꿀 것 없음)
assert [r["키"] for r in again] == [sc, dup] and all(r["남은참조"] == [inner] for r in again)
def test_733_지문_옛_참조_펼친_것_같음(world):
me = world["personal"]
y = _y(me)
old = _raw(me, "precombo", 이름="옛", 담은로직=[{"참조": {**MINE, "키": y}}, {"로직": GF}])
new = _make(me, "precombo", 이름="새", 담은로직=[{"참조": {**MINE, "키": y}}, {"로직": GF}])
assert not refs.refs_in(_read(me, "precombo", new))
fingers = {k: same.finger("precombo", _read(me, "precombo", k)) for k in (old, new)}
assert fingers[old] == fingers[new]
def test_B09_SC_덮개_줄_대가_금액_M02_같음(world, tmp_path):
"""SC 덮개 줄(SK 넣기 꼴) — 옛 B09 = 산근(덮개 안 읽음) · 이제 굳힌 줄 대가 = M02 값."""
from B09_Estimation import B09_Estimation_Bill_Library as library
from resources.tester.test_719_2_sk_project import _b09, _project
me = world["personal"]
cover = {"labor:LB000036#0": {"qty": 0.5}}
sc = _make(
me,
"precombo",
이름="덮은 줄",
담은로직=[{"로직": PCT, "패치": {"덮개": cover, "더한줄": [ADD]}}],
)
m02 = _sc_value(me, sc)
bare = float(labor.run_line(_whole(), PCT, {})["기준"]["계"])
assert m02 > bare * 2 # 덮개 · 더한줄이 값에 듦(옛 B09 산근 = bare)
root = _project(tmp_path / "proj", [])
made = library.import_copy(root, "precombo", "personal", "7/42", sc)
daega, done, _ = _b09(root)
block = done[daega["libs"][made["폴더"]]]
line = block["lines"][0]
assert line["kind"] == "daega" and block["blank"] == 1 # 안 줄 수량 = 이 공사에서 입력(723-4)
cover_qty = {block["cover"]: {line["cid"]: {"qty": 1}}}
block = _b09(root, cover_qty)[1][daega["libs"][made["폴더"]]]
assert block["blank"] == 0
assert block["sum"] == pytest.approx(m02, abs=5) # 끝수(내림)만 다름