From c7f34b8c4ba15e63463320d0f966418972b7f7a4 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 14 Sep 2026 20:12:09 +0900 Subject: [PATCH] =?UTF-8?q?feat(b09):=20=EC=82=AC=EB=9E=8C=EC=9D=B4=20?= =?UTF-8?q?=EA=B0=80=EB=A5=B8=20=ED=91=9C=20=ED=98=95=ED=83=9C=2066?= =?UTF-8?q?=ED=91=9C=20=EA=B9=8C=EB=8B=AD=EC=9D=84=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=20=E2=80=94=20=EC=9D=BC=EC=9C=84=EB=8C=80=EA=B0=80=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=ED=9A=8C=EC=83=89=20=ED=95=9C=20=EC=A4=84?= =?UTF-8?q?(form=5Fbasis=5Fnote)=20=C2=B7=20=EC=9D=BC=EC=9C=84=EB=8C=80?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EB=8A=94=20=EA=B3=B5=EC=A2=85=EC=9D=80=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20=EB=B9=84=EA=B3=A0=20=EA=B3=81=20=C2=B7=20?= =?UTF-8?q?=ED=8C=90=EC=A0=95=ED=91=9C=20=ED=95=9C=20=EB=B2=8C(=5FForms.FO?= =?UTF-8?q?RM=5FJUDGMENTS)=EC=9D=84=20=EC=9D=BD=EC=9D=8C(10-A=20=E2=91=AD?= =?UTF-8?q?=20=C2=B7=20=EC=9B=90=EB=AC=B8=20=EA=B7=BC=EA=B1=B0=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20SW=20=EA=B7=9C=EC=B9=99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn --- .../B09_Estimation_BillOfQuantities_Rows.py | 3 ++ B09_Estimation/B09_Estimation_KnownGaps.py | 35 +++++++++++++++++++ B09_Estimation/B09_Estimation_UI_Detail.ts | 1 + B09_Estimation/B09_Estimation_UI_Store.ts | 2 ++ .../B09_Estimation_UnitPrice_View.py | 4 ++- .../tester/test_b09_form_basis_visible.py | 33 +++++++++++++++++ 6 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 resources/tester/test_b09_form_basis_visible.py diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py index 6cf1df63..eacfa1f7 100644 --- a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py +++ b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py @@ -444,6 +444,8 @@ def _leaf_row( row.add_note( "unit_price_krw", "일위대가가 아직 없습니다 — 금액을 0 으로 때우지 않습니다." ) + if form_judgment_note(node.code): # 사람이 가른 표 형태 까닭(10-A ⑭) + row.add_note("unit_price_krw", form_judgment_note(node.code)) reason = "일위대가 없음" # 관경이 표 밖이면 **무엇을 정해야 하는지**까지 가리킨다. diameter_note = pipe_diameter_note(node.code, item.variant_value) if children else "" @@ -595,6 +597,7 @@ _PENDING_FORMULA: dict[str, str] = {} from B09_Estimation.B09_Estimation_KnownGaps import ( # noqa: E402 + form_judgment_note, known_gap_note, pipe_diameter_note, ) diff --git a/B09_Estimation/B09_Estimation_KnownGaps.py b/B09_Estimation/B09_Estimation_KnownGaps.py index c34d40f2..d94d6ca0 100644 --- a/B09_Estimation/B09_Estimation_KnownGaps.py +++ b/B09_Estimation/B09_Estimation_KnownGaps.py @@ -110,6 +110,41 @@ def known_gap_note(code: str | None) -> str: return " / ".join(parts) +#: 표 형태 이름 — 판정 까닭 한 줄에 붙임. +_FORM_LABELS = { + "reference": "참조표", + "coefficient": "계수표", + "productivity": "생산량형", + "requirement": "소요량형", +} + + +def form_judgment_note(code: str | None) -> str: + """사람이 가른 표 형태 까닭 — 원문 근거 없는 **SW 규칙**이라 화면에 드러냄(10-A ⑭ · 09-14). + + 정본은 B08 마스터 빌더의 판정표 한 벌(`_Forms.FORM_JUDGMENTS`) — 여기서 다시 적지 않고 읽음. + """ + plain = str(code or "").split("#")[0] + if not plain: + return "" + from B08_Quantity.B08_Quantity_Build_WorkItemMaster_Forms import FORM_JUDGMENTS + from B09_Estimation.B09_Estimation_ResourceAxis import load_work_item_master + + node = next( + (n for n in load_work_item_master()["work_items"] if n.get("work_item_code") == plain), + {}, + ) + parts = [] + for table in node.get("tables") or []: + found = FORM_JUDGMENTS.get(str(table.get("pum_table_id"))) + if found: + _section, form, why = found + parts.append(f"{table['pum_table_id']} {_FORM_LABELS.get(form, form)}: {why}") + if not parts: + return "" + return "ⓘ 표 형태는 사람이 가름(원문 근거 없음 · SW 규칙) — " + " / ".join(parts) + + #: 관부설 품셈 표가 다루는 관경 — 그 밖은 **표에 없는 것**이지 값이 틀린 것이 아니다. PIPE_TABLE_DIAMETERS_MM = (800, 1000, 1200) diff --git a/B09_Estimation/B09_Estimation_UI_Detail.ts b/B09_Estimation/B09_Estimation_UI_Detail.ts index e5bb3d05..913cb4ff 100644 --- a/B09_Estimation/B09_Estimation_UI_Detail.ts +++ b/B09_Estimation/B09_Estimation_UI_Detail.ts @@ -175,6 +175,7 @@ function drawDetail( } if (detail.unattached_note) box.append(hint(detail.unattached_note.replace(/\*\*/g, ""), true)); if (detail.known_gap_note) box.append(hint(detail.known_gap_note, true)); + if (detail.form_basis_note) box.append(hint(detail.form_basis_note, true)); } /** 본표 한 장을 `box` 에 — 자취를 쌓고 서버 본표를 받아 그림. */ diff --git a/B09_Estimation/B09_Estimation_UI_Store.ts b/B09_Estimation/B09_Estimation_UI_Store.ts index 1d415ee4..95a50c90 100644 --- a/B09_Estimation/B09_Estimation_UI_Store.ts +++ b/B09_Estimation/B09_Estimation_UI_Store.ts @@ -175,6 +175,8 @@ export interface DetailDto { rows: DetailRowDto[]; unattached_note: string; known_gap_note: string; + /** 사람이 가른 표 형태 까닭(원문 근거 없는 SW 규칙 · 10-A ⑭). 없으면 빈 문자열. */ + form_basis_note?: string; /** 구성행·Q 식을 고칠 수 있는 본표(B·D)인가 · 줄 더하기에 쓸 다음 칸 이름. */ editable?: boolean; next_add_key?: string; diff --git a/B09_Estimation/B09_Estimation_UnitPrice_View.py b/B09_Estimation/B09_Estimation_UnitPrice_View.py index c4d6c0be..c8a9363e 100644 --- a/B09_Estimation/B09_Estimation_UnitPrice_View.py +++ b/B09_Estimation/B09_Estimation_UnitPrice_View.py @@ -174,7 +174,7 @@ def detail_of(build: UnitPriceBuild, code: str) -> dict: money = build.book.resolve(code) # 코드에서 공종을 도로 뽑는다 — 「B-FP-09-11-01#갈래」의 갈래는 떼고 본다. work_item_code = code[2:].split("#")[0] if code.startswith("B-") else "" - from B09_Estimation.B09_Estimation_KnownGaps import known_gap_note + from B09_Estimation.B09_Estimation_KnownGaps import form_judgment_note, known_gap_note unattached = list(build.unattached.get(work_item_code, [])) rows: list[dict] = [] @@ -317,6 +317,8 @@ def detail_of(build: UnitPriceBuild, code: str) -> dict: "unattached": unattached, # ⚠ 원문에는 있는데 못 실린 몫 — 이름을 못 찾은 줄과 **다른 갈래**다. "known_gap_note": known_gap_note(work_item_code), + # 사람이 가른 표 형태 까닭(SW 규칙 · 10-A ⑭) — 칸 밑 회색 한 줄. + "form_basis_note": form_judgment_note(work_item_code), "unattached_note": ( f"⚠ 품셈 표에 있는 {len(unattached)}줄이 아직 안 붙었습니다 — " f"{', '.join(unattached[:4])}" diff --git a/resources/tester/test_b09_form_basis_visible.py b/resources/tester/test_b09_form_basis_visible.py new file mode 100644 index 00000000..e03e8887 --- /dev/null +++ b/resources/tester/test_b09_form_basis_visible.py @@ -0,0 +1,33 @@ +"""10-A ⑭ 표 형태 66표 까닭을 화면에 — 2026-09-14 사용자 확정 「SW 규칙 · 화면에 드러낼 것」. + +사람이 가른 표 형태(`_Forms.FORM_JUDGMENTS`)는 원문 근거가 없는 우리 규칙 — +마스터엔 있으나 화면에 한 번도 안 닿았음. 모양은 서브 10-A 채움과 같게 **칸 밑 회색 한 줄**: +일위대가 상세(`detail_of` → 화면 hint) · 일위대가가 없는 공종은 내역 「일위대가 없음」 비고. +""" + +from __future__ import annotations + +from B09_Estimation.B09_Estimation_KnownGaps import form_judgment_note +from B09_Estimation.B09_Estimation_UnitPrice import cached_build, detail_of + + +def test_판정표가_딸린_일위대가_상세에_까닭_한_줄() -> None: + build = cached_build() + code = next(c for c in build.book.titles if c.startswith("B-FP-13-04-04")) + note = detail_of(build, code)["form_basis_note"] + assert "F0412" in note and "참조표" in note and "뒷길이 표준" in note, note + assert "SW 규칙" in note + + +def test_판정표_없는_공종은_빈_칸() -> None: + assert form_judgment_note("FP-09-03-02") == "" + + +def test_일위대가_없는_공종은_내역_비고에_까닭() -> None: + from B09_Estimation.B09_Estimation_BillOfQuantities import build_bill + + item = {"work_item_code": "FP-12-38-01", "name": "사용횟수", "spec": "", "unit": "회", + "quantity": "1", "in_bill": True} # fmt: skip + rows = build_bill({"work_items": [item], "materials": []}).rows + line = next(r for r in rows if r.code == "FP-12-38-01") + assert "F0392" in line.note and "계수표" in line.note, line.note