From fb86195576e3c348a191eea52a75efc6d8c055c4 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 08:29:15 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat(B09):=20=E3=80=8C=EC=97=AC=EA=B8=B0?= =?UTF-8?q?=EC=84=9C=20=EC=84=B8=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=A4=84?= =?UTF-8?q?=E3=80=8D=EC=9D=84=20=ED=95=A0=20=EC=9D=BC=EA=B3=BC=20=EA=B0=88?= =?UTF-8?q?=EB=9D=BC=20=EB=83=84=20=E2=80=94=20=EC=9D=B4=EC=A4=91=EA=B3=84?= =?UTF-8?q?=EC=83=81=20=EC=9E=90=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B08 이 준비공 여섯 줄을 인계에 실으면서(`93546b85`) 세 갈래가 섞여 옴 - ⚠ `blocked_kind` 가 **없는데 `in_bill=false`** 면 **막힌 줄이 아님** — 「다른 표에서 이미 섬」(벌목·지장목제거)·「이 노선엔 없음」(사방 시설). 그것을 「우리가 만들어야 하는 것」에 얹으면 사용자가 세우려 들고 **그것이 곧 이중계상**(㉠~㉦ 규칙에 걸림) - 화면 목록을 **셋으로** 가름 — 입력하면 풀리는 것(2) · 우리가 만들 것(10) · **여기서 세지 않는 줄(2)** - 검산 줄 비고에 B08 사유 문구를 그대로 실음 - 짝 시험 — 같은 `in_bill=false` 라도 사유가 다르면 갈래가 달라야 함 실측(5174, 2f940d8a): 세 무리가 각각 뜨고 검산줄 5개가 수량만 보임 검증: pytest 212 통과(신규 1), tsc 0건 Co-Authored-By: Claude Opus 5 (1M context) --- .../B09_Estimation_BillOfQuantities.py | 21 ++++++++++++++++++- .../B09_Estimation_BillOfQuantities_Rows.py | 13 ++++++++++-- B09_Estimation/B09_Estimation_UI_Page.ts | 8 ++++++- ui_template/ui_template_locale_b2.ts | 4 ++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities.py b/B09_Estimation/B09_Estimation_BillOfQuantities.py index b75c5b13..96a30ce6 100644 --- a/B09_Estimation/B09_Estimation_BillOfQuantities.py +++ b/B09_Estimation/B09_Estimation_BillOfQuantities.py @@ -49,7 +49,13 @@ SUPPLY_UNKNOWN = "unknown" SUPPLY_OWNER = "owner_supplied" #: 막힘 갈래를 사람 말로. **할 일이 다르므로 화면에서 갈라 보인다.** +#: ⚠ `blocked_kind` 가 **`None` 인데 `in_bill=False`** 면 **막힌 줄이 아니다** — +#: 「다른 표에서 이미 섬」(벌목·지장목제거)이거나 「이 노선엔 없음」(사방 시설)이다. +#: 그것을 「우리가 만들어야 하는 것」에 얹으면 **결국 이중계상으로 간다**(㉠~㉦ 규칙). +_NOT_OUR_ROW = "not_our_row" + _BLOCKED_LABELS = { + _NOT_OUR_ROW: "여기서 세지 않는 줄", "input_missing": "입력이 필요합니다", "unit_data_missing": "원단위가 없습니다(우리가 만들 것)", "formula_missing": "전개식이 없습니다(우리가 만들 것)", @@ -343,7 +349,20 @@ def build_bill( # ⚠ 코드 유무보다 **먼저** 가른다. 보정량계는 공종코드가 없어서가 아니라 # **검산용 줄이라서** 금액이 없는 것이다 — `missing` 으로 새면 「단가를 구해야 할 # 줄」로 잘못 읽힌다. - result.excluded.append(_excluded_row(item)) + row = _excluded_row(item) + result.excluded.append(row) + if item.blocked_reason: + # ⚠ 「다른 표에서 이미 섬」과 「입력이 필요함」을 **갈라** 싣는다. + kind = item.blocked_kind or _NOT_OUR_ROW + result.missing.append( + { + "name": item.display_name, + "unit": item.unit, + "quantity": str(item.quantity), + "reason": f"{_BLOCKED_LABELS.get(kind, '막힘')} — {item.blocked_reason}", + "blocked_kind": kind, + } + ) continue if (item.composite_parts or item.composite_not_ready) and not item.work_item_code: # 묶음 줄 — 품셈에 그 공종이 없어 **조각을 합쳐** 한 줄로 세운다 diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py index 787dd508..d01c0d65 100644 --- a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py +++ b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py @@ -108,7 +108,14 @@ def _composite_row( def _excluded_row(item: HandoffWorkItem) -> BillRow: - """검산용 줄(`in_bill=false`). **수량만 보이고 단가·금액을 안 붙인다.**""" + """`in_bill=false` 줄. **수량만 보이고 단가·금액을 안 붙인다.** + + 세 갈래가 섞여 온다 — 갈라 적지 않으면 사용자가 할 일을 못 읽는다. + · **검산용**(보정량계·무대) — 합계 검산에만 쓰는 줄 + · **막힌 줄**(`blocked_kind` 있음) — 입력이나 원단위를 기다림 + · ⚠ **여기서 세지 않는 줄**(`blocked_kind` 없음) — 「다른 표에서 이미 섬」· + 「이 노선엔 없음」. **이것을 할 일 목록에 얹으면 결국 이중계상이 된다.** + """ return BillRow( item_no="", level=1, @@ -118,7 +125,9 @@ def _excluded_row(item: HandoffWorkItem) -> BillRow: unit=item.unit, quantity=item.quantity, in_bill=False, - note=item.in_bill_reason or "합계 검산용 줄 — 금액을 매기지 않습니다.", + note=item.blocked_reason + or item.in_bill_reason + or "합계 검산용 줄 — 금액을 매기지 않습니다.", ) diff --git a/B09_Estimation/B09_Estimation_UI_Page.ts b/B09_Estimation/B09_Estimation_UI_Page.ts index df04452b..abf5fa18 100644 --- a/B09_Estimation/B09_Estimation_UI_Page.ts +++ b/B09_Estimation/B09_Estimation_UI_Page.ts @@ -913,13 +913,19 @@ export async function renderB09Estimation(root: HTMLElement): Promise { // (2026-09-08 메인 창 지적), 한 화면에 뜨는 줄 수를 늘리지 않는다. // ⚠ **할 일이 다르므로 갈라 보인다** — 「사용자가 입력하면 풀리는 것」과 // 「우리가 만들어야 하는 것」. 한 목록에 섞으면 사용자가 무엇을 해야 할지 못 읽는다. + // ⚠ **세 갈래로 가른다.** 「여기서 세지 않는 줄」을 할 일 목록에 얹으면 + // 사용자가 세우려 들고, 그것이 곧 이중계상이다(㉠~㉦ 규칙). + const notOurs = bill.summary.missing.filter((item) => item.blocked_kind === "not_our_row"); const needsInput = bill.summary.missing.filter( (item) => item.blocked_kind === "input_missing", ); - const rest = bill.summary.missing.filter((item) => item.blocked_kind !== "input_missing"); + const rest = bill.summary.missing.filter( + (item) => item.blocked_kind !== "input_missing" && item.blocked_kind !== "not_our_row", + ); for (const [labelKey, group] of [ ["B09_Estimation_Boq_NeedsInput", needsInput], ["B09_Estimation_Boq_NeedsWork", rest], + ["B09_Estimation_Boq_NotOurs", notOurs], ] as Array<[keyof typeof ui_locales, typeof bill.summary.missing]>) { if (group.length === 0) continue; const head = document.createElement("div"); diff --git a/ui_template/ui_template_locale_b2.ts b/ui_template/ui_template_locale_b2.ts index 4b4b3d55..aac5b82c 100644 --- a/ui_template/ui_template_locale_b2.ts +++ b/ui_template/ui_template_locale_b2.ts @@ -725,6 +725,10 @@ export const ui_locales_b2 = { "검산용 줄 — 수량만 보이고 금액을 매기지 않습니다", "Check rows — quantity only, never priced", ], + B09_Estimation_Boq_NotOurs: [ + "여기서 세지 않는 줄 (다른 표에 있거나 이 노선에 없음)", + "Counted elsewhere or not present on this route", + ], B09_Estimation_Boq_NeedsInput: [ "입력하면 풀리는 것 (설계 화면에서 값을 고르면 금액이 섭니다)", "Waiting on input — pick the value on the design screen and the amount appears", From 2f19ba315222a25c9b9100c8c1c57d6c95e353c2 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 08:38:34 +0900 Subject: [PATCH 2/3] =?UTF-8?q?chore(B09):=20=EB=A7=88=EC=8A=A4=ED=84=B0?= =?UTF-8?q?=20=EA=B0=B1=EC=8B=A0=20=EB=B0=98=EC=98=81=20=E2=80=94=20?= =?UTF-8?q?=EA=B7=9C=EC=A4=80=ED=8B=80=20=EB=91=90=20=EC=A4=84=EC=97=90=20?= =?UTF-8?q?=EA=B8=88=EC=95=A1=EC=9D=B4=20=EB=B6=99=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 메인이 원문 **표 아래 [주]③**(「목재의 손율은 1개소 사용당 50 %」)에서 밑수를 찾아 `FP-11-02`·`FP-11-03` 을 「1 개소」로 채움(`fc867d32`) - 자원 축 다시 냄. 지문이 `3752af1e…` 로 따라 바뀜(낡음 감지 장치 동작) - 기준 미상 68 → 66 - **비탈 규준틀 68,512.2원/개소 · 수평 규준틀 90,997.7원/개소** 로 섬 - 내역서(2f940d8a) 합계 **11,494 → 946,067원** — 규준틀 7개소·5개소가 실제로 실림 · 3-1 비탈 규준틀 7 × 68,512.2 = 479,585 · 3-2 수평 규준틀 5 × 90,997.7 = 454,988 ⚠ 원문이 **표 위 본문이 아니라 표 아래 [주]** 에 있던 첫 사례 — 앞서 여덟 번은 다 표 위였음. 다음에 밑수를 찾을 땐 양쪽을 볼 것 검증: pytest 212 통과. 스냅숏 전후 대조로 준비공 6줄 신규 확인 Co-Authored-By: Claude Opus 5 (1M context) --- .../resource_axis_2026-01-01.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/resources/data_cost_resource_axis/resource_axis_2026-01-01.json b/resources/data_cost_resource_axis/resource_axis_2026-01-01.json index 7fe43416..fd37f80c 100644 --- a/resources/data_cost_resource_axis/resource_axis_2026-01-01.json +++ b/resources/data_cost_resource_axis/resource_axis_2026-01-01.json @@ -2620,7 +2620,7 @@ { "alternative_amount": null, "amount": "0.16", - "amount_unit": "", + "amount_unit": "개소", "group_ratio_pct": null, "pum_form": "requirement", "pum_table_id": "F0326", @@ -2635,7 +2635,7 @@ { "alternative_amount": null, "amount": "0.14", - "amount_unit": "", + "amount_unit": "개소", "group_ratio_pct": null, "pum_form": "requirement", "pum_table_id": "F0326", @@ -2650,7 +2650,7 @@ { "alternative_amount": null, "amount": "0.21", - "amount_unit": "", + "amount_unit": "개소", "group_ratio_pct": null, "pum_form": "requirement", "pum_table_id": "F0327", @@ -2665,7 +2665,7 @@ { "alternative_amount": null, "amount": "0.19", - "amount_unit": "", + "amount_unit": "개소", "group_ratio_pct": null, "pum_form": "requirement", "pum_table_id": "F0327", @@ -6287,14 +6287,13 @@ }, "source_master_file": { "file": "work_item_master_2026-01-01.json", - "sha256": "08b0c7c26c4aa569ace3b13490b8cbd00606254abb1439894eac750d387bfb03" + "sha256": "3752af1e0328a2faf4947268e449e0bc602a0738cc6a29a78ed71d343cf2e583" }, "stats": { "rows": 418, "skipped_forms": { "coefficient": 19, - "example_form": 8, - "reference": 56, + "reference": 64, "undetermined": 75 }, "unmatched": 497 From a6b5cb95489def38d7cdff8622b2c3b787d22204 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 09:26:36 +0900 Subject: [PATCH 3/3] =?UTF-8?q?chore(B09):=20=ED=83=80=EC=84=A4=20?= =?UTF-8?q?=EA=B2=B9=EC=B9=A8=20=EC=A0=9C=EA=B1=B0=20=EB=B0=98=EC=98=81=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20+=20=EC=B2=A0=EA=B7=BC=20=E3=80=8C?= =?UTF-8?q?=EB=91=90=20=EB=B2=88=E3=80=8D=20=EA=B2=80=EC=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 메인이 옹벽 묶음에 타설 조각이 이미 있는데 `_placing_rows` 가 그 콘크리트를 또 세던 것을 고침(`19d4009e`). 받아서 실물로 확인 - 인계에 **타설 줄이 없어짐** — 그 콘크리트는 묶음 조각이 셈 - 내역서 합계 3,136,861 → **2,149,464원** (메인 예고 약 2,149,464 와 일치). **결함이 아니라 겹친 것을 뺀 것** - ⚠ 묶음이 미확보라 값이 안 걸렸을 뿐, **묶음이 서는 날 두 번이 됐을 자리**였음 철근 「철근공·보통인부가 두 번 들어 있다」는 메인 관찰 — **정상임을 검산** - 품셈 12-3 표가 `구조별 | 가공 | 조립 | 계` 이고 진짜 자원 머리는 둘째 줄. 가공·조립 두 몫을 각각 세는 것이 맞음 - 검산: 철근공 1.24 + 1.84 = **3.08** = 품셈 「계」 열 · 보통인부 0.45 + 0.75 = **1.20** = 「계」 열. **「계」 열은 안 더함**(더하면 두 배) 검증: pytest 212 통과. 스냅숏 전후 대조 Co-Authored-By: Claude Opus 5 (1M context)