Files
Aislo/resources/tester/test_144_b09_skeleton.py
T
eomsangdonandClaude Opus 5.5 5d8d9a4fd3 test(841-1): B09 = M02 값 — 산근 · 대가 · 고정 줄 셈 다듬기 · 낡은 시험 정리 — C 걸음
코드
- 고정 줄 단가 = M02_Labor_Fixed.line(사본 묶음 · 이은 노임 · 자재 = 묶음 값) — B09 단가 책으로 다시 안 덮음
- 대가 · 산근 고친 칸 = 품만 실제로(단가 칸 덮기 줄 남던 흠 · 품 고친 뒤 줄 금액 다시 셈 흠 py · ts 짝)
- 산근 글 = 엔진 단가 셈 그대로 · 별도계상 재료 줄 끝 「· 별도계상 = 0.0」 · 계 줄 = 블록 합
- 노무비 · 중기목록 이름에서 규격 겹침 뗌 · TS 바탕 셈 이름 PARTS → MACHINE_PARTS(짝 시험이 TS 를 이어 붙일 때 겹침)

시험(고친 까닭 = 단가 원천이 B09 책 → M02 엔진 · 노임 · 기계 덮개 · 단가 고친 칸 · 중기사용료 걷음)
- 144 바탕 · 뼈대: 운전사 · 중기사용료 · 경비 · 덮개 시험 걷음 → 노무 · 중기 나열 · 사급 표시 · 자재만 책
- 145-1 · 145-2 · 155 · 157-1 · 166-2 · 166-6 · 719-3 · 719-4 · 723-4 · 778-4 · 792-3: 줄 단가 = own(엔진) · 산근 = Solver() · collect 새 부름 꼴
- 702 · 705-1 · 719-2: 단가 칸 덮개 = 안 읽음 · 고정 줄 = M02 셈(노임 새 판 따라가기 시험 걷음)
- 203-1 · 296 · 703-3 · 795-4: 단가 책 = 자재 줄로
- 707 · 707-6 · 707-7: 단가표 불러오기 = 자재만 · 거창 이은 안내판 줄 = M02 사본 셈(엑셀 −50,000)
- 201-1: 환율 · 중기사용료 표 걷음 · b09 저장 속도: 산근 앞 풀이 그대로(단가 표 같음 견줌 걷음)
- 대상 113 파일 764 통과 · 25 실패 = 원래 실패(formatMoney · formatPrice 없음 · 단위 글 ㎜ · sec · 719-1 · 796-2e · 717-4 · 721 · 311 · 243 · 246)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-15 16:57:01 +09:00

134 lines
6.9 KiB
Python

"""144-1 B09 뼈대 — 탭 차례 · 저장 자리 · 길 · 빈칸 규칙 · 「연결 없음」 목록 · 단가 굳히기.
- 탭 = 기번3 공통 엑셀 보이는 탭 차례(목차 · 안내 = 좌측 「데이터 점검」 묶음 203-1 · INITIAL 안 옮김)
- 저장 = 프로젝트 안 `B09_Estimation/v1/`(사용자 값 · 굳힌 단가) · 결과는 저장 안 함
- 줄 = 수요 따라(사용자 승인 ①) — 연결된 사본 없으면 노무 · 경비 · 중기 빈 표
· B08 자재만 줄 · 출처 빈칸
"""
import asyncio
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B09_Estimation import B09_Estimation_Prices as prices # noqa: E402
from B09_Estimation import B09_Estimation_Router as router_mod # noqa: E402
from B09_Estimation import B09_Estimation_Store as store # noqa: E402
#: 엑셀 보이는 탭(목차 · 안내 빼고) — 조사 F 1절 차례
EXCEL_TABS = [
"공사원가계산서", "총괄설계내역서", "설계내역서", "일위대가목록표", "일위대가표",
"단가산출근거목록표", "단가산출근거", "환율및기초자료", "중기목록표", "중기사용료",
"재료비목록표", "노무비목록표", "경비목록표", "자재단가대비표", "재료비수량금액집계표",
"노무비수량금액집계표", "경비수량금액집계표", "중기시간금액집계표",
] # fmt: skip
B08_SHEETS = {
"material": {
"rows": [
{"key": "sand", "group": "골재", "spec": "모래", "unit": "㎥", "amount": 16},
{"key": "rubble", "group": "골재", "spec": "막자갈", "unit": "㎥", "amount": None},
{"key": "cement", "group": "시멘트", "spec": "40kg/대", "unit": "대", "amount": 244},
]
}
}
def test_탭_차례가_엑셀과_같다():
source = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Tabs.ts").read_text(encoding="utf-8")
block = source[
source.index("export const TABS") : source.index("];", source.index("export const TABS"))
]
names = re.findall(r'\["\w+", "([^"]+)"\]', block)
assert names == EXCEL_TABS # 203-1 「연결 점검」 탭 = 좌측 묶음으로 옮김
def test_길_셋():
routes = {(r.path, tuple(sorted(r.methods))) for r in router_mod.router.routes}
assert ("/api/projects/{project_id}/estimation", ("GET",)) in routes
assert ("/api/projects/{project_id}/estimation", ("PUT",)) in routes
assert ("/api/projects/{project_id}/estimation/prices", ("POST",)) in routes
main = (ROOT / "main.py").read_text(encoding="utf-8")
assert "app.include_router(b09_estimation_router" in main
def test_사용자_값_다듬기_저장_자리(tmp_path):
raw = {
"materials": {"MT1": {"applied": 1200, "supply": "관급", "x": 1}, "MT2": {"applied": None},
"MT3": {"supply": "엉뚱"}},
"estimate": {"estimated_price": "abc", "profit_adjust": 905980, "work_type": "토목",
"rates": {"RT000001": 3.56, "RT000002": None}, "zzz": 1},
"other": 1,
} # fmt: skip
clean = store.write_user(tmp_path, raw)
assert clean == {
"materials": {"MT1": {"applied": 1200.0, "supply": "관급"}, "MT2": {"applied": None}},
"estimate": {"estimated_price": None, "profit_adjust": 905980.0, "work_type": "토목",
"rates": {"RT000001": 3.56, "RT000002": None}},
} # fmt: skip
path = tmp_path / "B09_Estimation" / "v1" / "user_values.json"
assert json.loads(path.read_text(encoding="utf-8")) == clean
assert store.read_user(tmp_path) == clean
assert store.read_book(tmp_path) is None
def test_841_1_단가_책_자재만():
"""841-1 — 노무 · 기계 · 운전사 · 연료 · 환율은 책 밖(B09 금액 = 사본 M02 엔진 값) · 자재만 굳힘."""
book = prices.refresh_book(["EQ000004", "LB000048", "MT000401", "MO000002"])
rows = book["rows"]
assert set(rows) == {"MT000401", "MO000002"} and "driver_factor" not in book
assert rows["MO000002"]["prices"]["유가"] > 0
# [저장] = 책에 없는 새 줄만 더함 — 있는 줄 값은 안 바꿈
stale = {**book, "rows": {**rows, "MT000401": {**rows["MT000401"], "prices": {}}}}
grown = prices.extend_book(stale, ["MT000401", "MT000338"])
assert grown["rows"]["MT000401"]["prices"] == {} and "MT000338" in grown["rows"]
# 책 밖 줄 = 지금 M01 값 + fresh 표시
inputs = prices.build_inputs(stale, ["MT000401", "MT000338", "LB000002"])
fresh = {row["key"]: row.get("fresh", False) for row in inputs["materials"]}
assert fresh == {"MT000401": False, "MT000338": True}
assert set(inputs) == {"materials", "missing", "now"}
def test_연결_없으면_빈_표와_연결_없음(tmp_path, monkeypatch):
async def fake_materials(_project_id):
return {"source": None, "sheets": B08_SHEETS} # B08 길 답 꼴(167-2)
monkeypatch.setattr(router_mod, "material_sheets", fake_materials)
body = asyncio.run(router_mod.payload("p", tmp_path))
sheets = body["sheets"]
assert sheets["labor"] == [] and sheets["expense"] == [] and sheets["machine"] == []
assert [row["key"] for row in sheets["compare"]] == ["b08:sand", "b08:cement"] # 수량 있는 줄만
assert all(row["applied"] is None for row in sheets["compare"]) # 출처 없음 = 빈칸(0 아님)
assert [(g["tab"], g["key"]) for g in sheets["gaps"] if g["tab"] == "compare"] == [
("compare", "b08:sand"),
("compare", "b08:cement"),
]
assert any("연결된 구조물 조합 사본 없음" in n for n in body["notes"])
assert body["book"] is None # GET 은 아무것도 쓰지 않음
assert not (tmp_path / "B09_Estimation").exists()
def test_연결된_사본이_부른_자원만(tmp_path):
"""사본 단가 묶음 통째(고르기 후보 기계 100여)가 아니라 로직이 실제로 부른 줄만."""
from B09_Estimation.B09_Estimation_Linked import used_keys
from M02_MasterTemplete import M02_Item_Prices as item_prices
bundle = item_prices.to_json(item_prices.collect(["GC000268"])) # 기계 시간당 기계경비
files = {
"단가.json": bundle,
"일위대가.json": {"줄": {"종류": "일위대가", "담은로직": [{"로직": "GC000268"}]}},
"입력값.json": {"GC000268": {"기계": {"값": "0101-0019"}, "지역": {"값": "전국평균"}}},
}
for name, data in files.items():
(tmp_path / name).write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
assert used_keys(tmp_path) == ["EQ000004", "MO000002", "LB000048"]
(tmp_path / "입력값.json").write_text("{}", encoding="utf-8")
# 빈 입력 = 자동값(142-1 사본 셈 한 벌 · 기계 고르기 첫 값 1730-0015) — 그 로직이 부른 줄
assert used_keys(tmp_path) == ["EQ000112", "MO000001", "LB000050"]
assert used_keys(tmp_path / "없음") == []