diff --git a/docs/raw/요구_1104/_목록_802-2_변환대조.xlsx b/docs/raw/요구_1104/_목록_802-2_변환대조.xlsx index 1aef7d64c..ab3b94924 100644 Binary files a/docs/raw/요구_1104/_목록_802-2_변환대조.xlsx and b/docs/raw/요구_1104/_목록_802-2_변환대조.xlsx differ diff --git a/resources/master_data/scripts/build_수식문서_802_2.py b/resources/master_data/scripts/build_수식문서_802_2.py index 4356a8ed0..5d32c7cf5 100644 --- a/resources/master_data/scripts/build_수식문서_802_2.py +++ b/resources/master_data/scripts/build_수식문서_802_2.py @@ -12,7 +12,9 @@ · 계(돈 아닌 로직 = 결과) 원 단위 — 기본 옵션 전부 + 옵션 표본(로직마다 고르기 입력의 다른 갈래 하나씩 · 나머지 기본). 둘 다 못 셈 = 같음(따로 셈). 보정 없음 — 다르면 목록에 까닭. -- 저장 = 같은 것만(요구서_664 1 장 3) — 모든 대조가 같고 부르는 단가산출도 저장되는 문서 · +- 채택된 차이(`ADOPTED`) = 사용자 요구 714-3 「1원 차이는 로직만 맞다면 문제 없음」 — 식 속 단가산출 + 부름 값 = 끝수 뒤(새 계산기 그대로) · 목록에 남기고 저장은 막지 않음. +- 저장 = 같은 것만(요구서_664 1 장 3) — 모든 대조가 같고(채택된 차이 빼고) 부르는 단가산출도 저장되는 문서 · `resources/master_data/수식문서/수식_<옛 로직 파일 꼬리>.json`(로직 파일마다 · 옛 로직 JSON 은 안 씀). - 목록 = `docs/raw/요구_1104/_목록_802-2_변환대조.xlsx`(로직 · 옵션 · 옛 · 새 · 차이 · 까닭 + 요약). @@ -44,6 +46,8 @@ RAW_SUB = ( "식 속 단가산출 부름 = 끝수 뒤 값" "(옛 엔진 = 끝수 전 날 값 · `master_formula.run` 결과를 식에 씀)" ) +# 채택된 차이 — (로직, 옵션) · 요구 714-3 +ADOPTED = {("GC000100", "지역=경북")} def rows_of(files: dict) -> list[tuple[str, dict]]: @@ -108,7 +112,7 @@ def why(doc: dict, old, new) -> str: if isinstance(new, str) and not isinstance(old, str): return f"새 계산기 못 셈 — {new}" if _uses_raw_sub(doc): - return RAW_SUB + return RAW_SUB + " · 채택(요구 714-3)" return "까닭 모름" @@ -151,6 +155,7 @@ def build(log=print) -> dict: ) for one in runs: if not one["같음"]: + one["채택"] = (one["로직"], one["옵션"]) in ADOPTED one["까닭"] = why(docs[one["로직"]], one["옛"], one["새"]) keep = _keep(docs, runs) for one in runs: # 같아도 못 넣는 것 — 부르는 단가산출이 빠짐 @@ -165,6 +170,7 @@ def build(log=print) -> dict: "옵션 표본": sum(1 for x in runs if x["옵션"]), "옵션 같음": sum(1 for x in runs if x["옵션"] and x["같음"]), "옵션 다름": sum(1 for x in runs if x["옵션"] and not x["같음"]), + "채택된 차이": sum(1 for x in runs if x.get("채택")), "둘 다 못 셈": both_out, "저장": len(keep), "변환 알림": sum(1 for v in notes.values() if v), @@ -192,7 +198,7 @@ def _calls(doc: dict) -> set[str]: def _keep(docs: dict, runs: list[dict]) -> set[str]: """저장할 문서 — 모든 대조 같음 + 부르는 단가산출도 저장(닫힘). 키 구멍 부름은 그 로직 대조로 판정.""" - keep = set(docs) - {x["로직"] for x in runs if not x["같음"]} + keep = set(docs) - {x["로직"] for x in runs if not x["같음"] and not x.get("채택")} while True: drop = {k for k in keep if not _calls(docs[k]) <= keep} if not drop: diff --git a/resources/master_data/수식문서/수식_건설품셈_03장_토공사.json b/resources/master_data/수식문서/수식_건설품셈_03장_토공사.json index 1e2796a26..fb90d4e9a 100644 --- a/resources/master_data/수식문서/수식_건설품셈_03장_토공사.json +++ b/resources/master_data/수식문서/수식_건설품셈_03장_토공사.json @@ -641,6 +641,55 @@ "끝전": "비목버림" } }, + { + "꼴": "수식문서", + "판": 1, + "키": "GC000100", + "이름": "암파쇄(유압식 할암공법)", + "결과단위": "원/㎥", + "출처": "건설품셈 공통 3-3-8", + "변수": [ + {"열쇠": "지역", "기호": "S", "아래": "지역", "위": "", "이름": "지역", "단위": "", "출처": "건설품셈 공통 8-1-7 5", "연결": {"갈래": "선택", "목록": ["전국평균", "전국", "서울", "경기", "강원", "충북", "충남", "전북", "경북", "경남", "부산", "제주", "대구", "인천", "대전", "울산", "세종", "전남광주"], "값": "전국평균"}}, + {"열쇠": "보정작업", "기호": "S", "아래": "보정작업", "위": "", "이름": "보정작업", "단위": "", "출처": "건설품셈 공통 8-1-7 1", "연결": {"갈래": "선택", "목록": ["해당 없음", "암석작업(연암・보통암・경암)", "전석섞인토사"], "값": "해당 없음"}}, + {"열쇠": "드릴경비", "기호": "C", "아래": "드릴", "위": "", "이름": "드릴경비", "단위": "", "연결": {"갈래": "식"}}, + {"열쇠": "브레이커경비", "기호": "C", "아래": "브레이커", "위": "", "이름": "브레이커경비", "단위": "", "연결": {"갈래": "식"}}, + {"열쇠": "P_기계시간당", "기호": "P", "아래": "기계시간당", "위": "", "이름": "기계 시간당 기계경비(손료 보정 없는 기종)", "단위": "원/시간", "연결": {"갈래": "단가산출", "키": "GC000268", "입력": {"기계": "'5405-0110'", "지역": "S_지역"}, "칸": "계"}}, + {"열쇠": "Q_유압식크롤드^표", "기호": "Q", "아래": "유압식크롤드", "위": "표", "이름": "암파쇄(유압식 할암공법).유압식크롤러드릴", "단위": "hr", "연결": {"갈래": "표", "표": "QC000201", "조건": {}, "칸": "유압식크롤러드릴"}}, + {"열쇠": "P_굴착기무한궤", "기호": "P", "아래": "굴착기무한궤", "위": "", "이름": "굴착기(무한궤도)+대형브레이커 시간당 기계경비", "단위": "원/시간", "연결": {"갈래": "단가산출", "키": "GC000271", "입력": {"기계": "'0201-0100'", "브레이커": "'0230-0010'", "지역": "S_지역", "보정작업": "S_보정작업"}, "칸": "계"}}, + {"열쇠": "Q_굴착기대형브^표", "기호": "Q", "아래": "굴착기대형브", "위": "표", "이름": "암파쇄(유압식 할암공법).굴착기_대형브레이커", "단위": "hr", "연결": {"갈래": "표", "표": "QC000201", "조건": {}, "칸": "굴착기_대형브레이커"}}, + {"열쇠": "Q_기계설비공^표", "기호": "Q", "아래": "기계설비공", "위": "표", "이름": "암파쇄(유압식 할암공법).기계설비공", "단위": "인", "연결": {"갈래": "표", "표": "QC000201", "조건": {}, "칸": "기계설비공"}}, + {"열쇠": "w_기계설비공", "기호": "w", "아래": "기계설비공", "위": "", "이름": "기계설비공노임", "단위": "", "연결": {"갈래": "요소", "키": "LB000051"}}, + {"열쇠": "Q_특별인부^표", "기호": "Q", "아래": "특별인부", "위": "표", "이름": "암파쇄(유압식 할암공법).특별인부", "단위": "인", "연결": {"갈래": "표", "표": "QC000201", "조건": {}, "칸": "특별인부"}}, + {"열쇠": "w_특별인부", "기호": "w", "아래": "특별인부", "위": "", "이름": "특별인부노임", "단위": "", "연결": {"갈래": "요소", "키": "LB000003"}}, + {"열쇠": "P_기계시간당2", "기호": "P", "아래": "기계시간당2", "위": "", "이름": "기계 시간당 기계경비(손료 보정 없는 기종)", "단위": "원/시간", "연결": {"갈래": "단가산출", "키": "GC000268", "입력": {"기계": "'5405-0110'", "지역": "S_지역"}}}, + {"열쇠": "Q_발전기^표", "기호": "Q", "아래": "발전기", "위": "표", "이름": "암파쇄(유압식 할암공법).발전기", "단위": "hr", "연결": {"갈래": "표", "표": "QC000201", "조건": {}, "칸": "발전기"}}, + {"열쇠": "P_기계시간당3", "기호": "P", "아래": "기계시간당3", "위": "", "이름": "기계 시간당 기계경비(손료 보정 없는 기종)", "단위": "원/시간", "연결": {"갈래": "단가산출", "키": "GC000268", "입력": {"기계": "'7505-0025'", "지역": "S_지역"}}}, + {"열쇠": "Q_유압식할암기^표", "기호": "Q", "아래": "유압식할암기", "위": "표", "이름": "암파쇄(유압식 할암공법).유압식할암기", "단위": "hr", "연결": {"갈래": "표", "표": "QC000201", "조건": {}, "칸": "유압식할암기"}}, + {"열쇠": "P_기계시간당4", "기호": "P", "아래": "기계시간당4", "위": "", "이름": "기계 시간당 손료(연료·조종원 없는 기종)", "단위": "원/시간", "연결": {"갈래": "단가산출", "키": "GC000273", "입력": {"기계": "'5501-0080'"}}}, + {"열쇠": "P_굴착기무한2", "기호": "P", "아래": "굴착기무한2", "위": "", "이름": "굴착기(무한궤도)+대형브레이커 시간당 기계경비", "단위": "원/시간", "연결": {"갈래": "단가산출", "키": "GC000271", "입력": {"기계": "'0201-0100'", "브레이커": "'0230-0010'", "지역": "S_지역", "보정작업": "S_보정작업"}}}, + {"열쇠": "Q_기계경비의^표", "기호": "Q", "아래": "기계경비의", "위": "표", "이름": "소모자재 요율.기계경비의", "단위": "%", "연결": {"갈래": "표", "표": "QC000202", "조건": {"장비": "'유압식 크롤러드릴'"}, "칸": "기계경비의"}}, + {"열쇠": "Q_기계경비의2^표", "기호": "Q", "아래": "기계경비의2", "위": "표", "이름": "소모자재 요율.기계경비의", "단위": "%", "연결": {"갈래": "표", "표": "QC000202", "조건": {"장비": "'굴착기+대형브레이커'"}, "칸": "기계경비의"}} + ], + "식": [ + {"기호": "C_드릴", "식": "P_기계시간당 × Q_유압식크롤드^표"}, + {"기호": "C_브레이커", "식": "P_굴착기무한궤 × Q_굴착기대형브^표"} + ], + "금액": [ + {"이름": "기계설비공", "단위": "인", "비목": "노무비", "수량": "Q_기계설비공^표", "단가": "w_기계설비공", "묶음": "인력"}, + {"이름": "특별인부", "단위": "인", "비목": "노무비", "수량": "Q_특별인부^표", "단가": "w_특별인부", "묶음": "인력"}, + {"이름": "유압식 크롤러드릴 110㎾", "단위": "시간", "비목": "묶음", "수량": "Q_유압식크롤드^표", "단가": "P_기계시간당2", "묶음": "유압식 크롤러드릴 110㎾"}, + {"이름": "발전기 25㎾", "단위": "시간", "비목": "묶음", "수량": "Q_발전기^표", "단가": "P_기계시간당3", "묶음": "발전기 25㎾"}, + {"이름": "유압식할암기 ∅80", "단위": "시간", "비목": "묶음", "수량": "Q_유압식할암기^표", "단가": "P_기계시간당4", "묶음": "유압식할암기 ∅80"}, + {"이름": "굴착기+대형브레이커 1.0㎥", "단위": "시간", "비목": "묶음", "수량": "Q_굴착기대형브^표", "단가": "P_굴착기무한2", "묶음": "굴착기+대형브레이커 1.0㎥"}, + {"이름": "소모자재(크롤러드릴)", "단위": "식", "비목": "재료비", "단가": "C_드릴 × Q_기계경비의^표 ÷ 100", "출처": "건설품셈 공통 3-3-8 주④", "묶음": "소모자재(크롤러드릴) · 소모자재(대형브레이커)"}, + {"이름": "소모자재(대형브레이커)", "단위": "식", "비목": "재료비", "단가": "C_브레이커 × Q_기계경비의2^표 ÷ 100", "출처": "건설품셈 공통 3-3-8 주④", "묶음": "소모자재(크롤러드릴) · 소모자재(대형브레이커)"} + ], + "끝수": { + "줄": {"방법": "버림", "자리": 1}, + "비목": {"방법": "버림", "자리": 0}, + "끝전": "비목버림" + } + }, { "꼴": "수식문서", "판": 1, diff --git a/resources/tester/test_802_2_convert.py b/resources/tester/test_802_2_convert.py index 90cb4af63..3b999d9d7 100644 --- a/resources/tester/test_802_2_convert.py +++ b/resources/tester/test_802_2_convert.py @@ -4,7 +4,7 @@ ① --check — 다시 돌린 결과 = 저장본(`resources/master_data/수식문서/`) · 목록 xlsx. ② 로직 1,519 전수 기본 옵션 = 옛 엔진 노 · 재 · 경 · 계(결과) 원 단위 같음. ③ 옵션 표본 — 로직마다 고르기 입력의 모든 갈래 한 번 이상 · 같음 수 · 다름 = 목록 · 까닭 모름 0. -④ 저장 = 같은 것만(부르는 단가산출까지) · 저장본을 읽어 다시 셈해도 같음. +④ 저장 = 같은 것만(부르는 단가산출까지 · 채택된 차이 GC000100 지역=경북 재료비 1 원 빼고) · 저장본을 읽어 다시 셈해도 같음. ⑤ 791 실무 손 옮김 견본 넷(번지 · 단계 · 합밖 · 줄 자리 · 끝전 · 조정 · 값묶음)도 변환 셈 = 엑셀 금액. """ @@ -56,7 +56,7 @@ def test_옵션_표본_모든_갈래_한_번_이상(result): assert (row["키"], f"{spec['이름']}={opt}") in seen info = result["요약"] assert info["옵션 표본"] == want == info["옵션 같음"] + info["옵션 다름"] - assert info["옵션 같음"] >= 18700 and info["옵션 다름"] <= 1 + assert info["옵션 같음"] >= 18700 and info["옵션 다름"] == info["채택된 차이"] == 1 def _whole(): @@ -69,15 +69,25 @@ def test_다름_목록_까닭(result): assert sheet[0] == list(build.HEAD) and sheet[1:] == rows diff = [x for x in result["대조"] if not x["같음"]] assert all(x["까닭"] != "까닭 모름" for x in diff) - assert [(x["로직"], x["옵션"], x["까닭"]) for x in diff] == [ - ("GC000100", "지역=경북", build.RAW_SUB) - ] + # 채택된 차이(요구 714-3) — 식 속 단가산출 부름 값 = 끝수 뒤 · 재료비 1 원 · 저장은 함 + assert [(x["로직"], x["옵션"], x["채택"]) for x in diff] == [("GC000100", "지역=경북", True)] + assert diff[0]["까닭"].startswith(build.RAW_SUB) + assert diff[0]["옛"]["재료비"] != diff[0]["새"]["재료비"] + assert abs(diff[0]["새"]["재료비"] - diff[0]["옛"]["재료비"]) == 1 + assert {k: diff[0]["옛"][k] for k in ("노무비", "경비")} == { + k: diff[0]["새"][k] for k in ("노무비", "경비") + } def test_저장_같은_것만_다시_셈(result): saved = ml.load_all() assert set(saved) == result["저장"] - assert "GC000100" not in saved and len(saved) == result["요약"]["저장"] >= 1518 + assert "GC000100" in saved and len(saved) == result["요약"]["저장"] >= 1519 + base = next(x for x in result["대조"] if x["로직"] == "GC000100" and not x["옵션"]) + assert ( + base["같음"] + and build.new_answer(ml.Links(_whole(), saved), saved["GC000100"]) == base["옛"] + ) for key in ("GC000211", "GC001147", "GF000294", "GC000087"): doc = saved[key] got = build.new_answer(ml.Links(_whole(), saved), doc) diff --git a/resources/tester/test_802_3_doc_screen.py b/resources/tester/test_802_3_doc_screen.py index 3f9018658..6675897f1 100644 --- a/resources/tester/test_802_3_doc_screen.py +++ b/resources/tester/test_802_3_doc_screen.py @@ -67,7 +67,11 @@ def test_읽기_미리셈_연결기록(client): def test_없는문서_빈문서_없는키_404(client): - fresh = _read(client, "GC000100") # 802-2a 로 문서 안 넣음 + assert ( + _read(client, "GC000100")["새"] is False + ) # 802-2a 로 문서 넣음 — 임시 사본에서 지워 빈 문서 길을 봄 + doc_screen.drop("GC000100") + fresh = _read(client, "GC000100") assert fresh["새"] is True and fresh["문서"]["식"] == [] and fresh["문서"]["금액"] == [] assert fresh["셈"]["셈됨"] is False and fresh["셈"]["계"] is None assert client.get("/api/m01/logic/doc", params={"key": "GX999999"}).status_code == 404