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/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 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",