From e71147c1b205a1c154ff10c04d05aa3bd5483ee2 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 9 Sep 2026 13:10:22 +0900 Subject: [PATCH] =?UTF-8?q?fix(B08):=20=EC=BD=94=EB=93=9C=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=A4=84=EC=97=90=20=EB=A7=89=ED=9E=98=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=EB=A5=BC=20=EB=B6=99=EC=9E=84=20=E2=80=94=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=EC=9D=B4=20=EC=A1=B0=EC=9A=A9=ED=9E=88=20=EB=B9=A0?= =?UTF-8?q?=EC=A7=80=EB=8D=98=20=EC=9E=90=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 랩탑 메인이 화면에서 잡음: 「측구터파기 · 굴삭기+브레카」가 work_item_code null 인데 blocked_kind 도 null 이라 아무 말 없이 금액만 빠졌음. 인계본의 bill_flag_warnings·unmatched_work_items 에는 이미 실려 있었음 — 목록은 있는데 줄에 표시가 없었던 것. 줄 단위로 보는 쪽(B09·화면)은 목록을 안 봄. 같은 모양이 여덟 줄이었음(흙깎기·측구터파기·도자운반 둘·덤프운반 둘·지장목제거·큰돌쌓기). - 토공 줄: 코드가 없으면 시공법 미지정이면 input_missing, 매핑을 못 이었으면 unit_data_missing 으로 갈라 답. 다음에 할 일이 다름(사용자 입력 vs 매핑 작업). - 운반 줄: 같은 규칙. 무대(free_haul)는 품에 포함이라 안 세우는 것이지 막힌 게 아니므로 표시를 안 답. - 불변식 시험을 세움 — 코드도 막힘 표시도 없이 내역에 서는 줄이 하나라도 있으면 깨짐. Co-Authored-By: Claude Opus 5 (1M context) --- .../B08_Quantity_Engine_Handoff_Rows.py | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py b/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py index a0ce263f..eaa403f0 100644 --- a/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py +++ b/B08_Quantity/B08_Quantity_Engine_Handoff_Rows.py @@ -131,9 +131,22 @@ def _earthwork_rows( elif mismatch is not None: spec_detail = f"집계 {amount:,.2f} {unit} (품셈 밑수 {mismatch[0]})" unit, amount = mismatch[0], 0.0 + # ⚠⚠ **코드가 없으면 반드시 막힘 표시를 단다**(2026-09-09 랩탑 메인 제보). + # 종전에는 `unmatched_work_items` 목록과 `bill_flag_warnings` 에만 실려, + # **줄 단위로 보는 쪽**(B09·화면)이 「코드도 없고 막힘 표시도 없는 멀쩡한 줄」로 + # 읽었다 — 금액이 조용히 빠졌다(측구터파기·흙깎기 「굴삭기+브레카」). + # ⇒ 오늘 세운 규칙 「막혔다고 말하기 전에 `blocked_kind` 를 볼 것」의 **뒤집힌 얼굴**: + # 보는 쪽을 고쳤으면 **다는 쪽도** 빠짐없이 달아야 한다. + blocked_kind = mismatch[1] if mismatch else None + blocked_reason = mismatch[2] if mismatch else "" if code is None and not is_subtotal: label = f"{group}({ground})" if ground else group unmatched.append(f"{label} — {method_note}" if method_note else label) + if blocked_kind is None: + # 시공법을 고르면 풀리는 자리와, 품셈을 아직 못 이은 자리를 **갈라 적는다** — + # 다음에 할 일이 다르다(하나는 사용자 입력, 하나는 매핑 작업). + blocked_kind = BLOCKED_INPUT_MISSING if method_note else BLOCKED_UNIT_DATA_MISSING + blocked_reason = method_note or f"{label} 의 품셈 공종을 아직 못 이었습니다" rows.append( { "work_item_code": code, @@ -162,9 +175,9 @@ def _earthwork_rows( "secondary_axes": None, "spec_class": None, "spec_class_basis": "", - # 토공 줄도 막힐 수 있다 — 품셈 밑수와 단위가 다르면 그 사유가 실린다. - "blocked_kind": mismatch[1] if mismatch else None, - "blocked_reason": mismatch[2] if mismatch else "", + # 토공 줄도 막힐 수 있다 — 밑수 어긋남과 **코드 없음** 둘 다 여기 실린다. + "blocked_kind": blocked_kind, + "blocked_reason": blocked_reason, "composite_not_ready": None, # 합계 줄과 무대 줄은 값은 내되 내역에 안 선다. "in_bill": bool(row.get("in_bill", True)) and not is_subtotal and mismatch is None, @@ -191,6 +204,13 @@ def _haul_rows( in_bill = bool(row.get("in_bill", True)) and entry.get("in_bill", True) if code is None and in_bill: unmatched.append(f"운반({equipment})") + # ⚠ 코드가 없으면 **줄에 막힘 표시를 단다**(2026-09-09) — 목록에만 실으면 줄 단위로 + # 보는 쪽이 「멀쩡한 줄」로 읽어 금액이 조용히 빠진다(도자운반·덤프운반이 그랬다). + # ⚠ `in_bill` 이 False 인 무대 줄은 **막힌 것이 아니다** — 품에 포함이라 안 세우는 것. + haul_blocked = BLOCKED_UNIT_DATA_MISSING if (code is None and in_bill) else None + haul_blocked_reason = ( + f"운반({equipment})의 품셈 공종을 아직 못 이었습니다" if haul_blocked else "" + ) # ⚠⚠ **내역서 수량은 자연상태다** — 유토곡선은 다짐상태로 쌓고(운반거리를 그 기준으로 # 재야 맞는다) 내역에 오르는 수량은 되돌린 값이다(`config_system_design` 5-4-3 # 「운반거리 산정 시 모든 수량은 다짐상태로 환산해 계산하고, **내역서에 적용하는 @@ -242,8 +262,8 @@ def _haul_rows( "secondary_axes": None, "spec_class": None, "spec_class_basis": "", - "blocked_kind": None, - "blocked_reason": "", + "blocked_kind": haul_blocked, + "blocked_reason": haul_blocked_reason, "composite_not_ready": None, "in_bill": in_bill, "in_bill_reason": str(entry.get("reason") or ""),