diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities.py b/B09_Estimation/B09_Estimation_BillOfQuantities.py index 4a5f7329..b75c5b13 100644 --- a/B09_Estimation/B09_Estimation_BillOfQuantities.py +++ b/B09_Estimation/B09_Estimation_BillOfQuantities.py @@ -96,6 +96,10 @@ class HandoffWorkItem: #: 갈래 축·원본값 — 「stone_cm」·「60~80」. **키 문자열은 우리가 만든다**(두 창 합의). variant_axis: str = "" variant_value: str = "" + #: 규격 갈래를 B08 이 판정해 보낸 것(「철근구조물」)과 그 근거 문구. + #: **근거는 산출근거 칸에 그대로 적는다** — 우리가 다시 지어내지 않는다. + spec_class: str = "" + spec_class_basis: str = "" @property def display_name(self) -> str: @@ -243,6 +247,8 @@ def parse_handoff(payload: dict[str, Any]) -> tuple[list[HandoffWorkItem], list[ blocked_kind=row.get("blocked_kind") or "", variant_axis=row.get("variant_axis") or "", variant_value=str(row.get("variant_value") or ""), + spec_class=row.get("spec_class") or "", + spec_class_basis=row.get("spec_class_basis") or "", ) for row in payload["work_items"] ] diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py index 46a19f4c..787dd508 100644 --- a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py +++ b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py @@ -140,6 +140,9 @@ def _leaf_row( quantity=item.quantity, in_bill=item.in_bill, ) + if item.spec_class_basis: + # 갈래 판정 근거는 **B08 문구를 그대로** 쓴다(두 벌로 짜지 않는다). + row.note = " / ".join(part for part in (row.note, item.spec_class_basis) if part) if item.application_ratio_pct is not None: # ⚠ 곱하지 않는다 — B08 이 이미 곱한 값이다. 산출근거로만 적는다. row.note = f"반영률 {item.application_ratio_pct}% 적용 후 수량" diff --git a/B09_Estimation/B09_Estimation_ResourceAxis.py b/B09_Estimation/B09_Estimation_ResourceAxis.py index 958ce3c2..bc790a7f 100644 --- a/B09_Estimation/B09_Estimation_ResourceAxis.py +++ b/B09_Estimation/B09_Estimation_ResourceAxis.py @@ -357,14 +357,9 @@ def match_table( result: AxisResult, ) -> None: """표 하나에 자원 축을 붙인다. 값이 안 서면 `unmatched` 로 보낸다.""" - # ⚠ **예시 서식 표는 자원 표가 아니다.** 품셈이 「ha당 …단가산출서(예시)」처럼 - # **빈 서식**을 실어 두는데(4-2 · 3-2 등), 그것을 읽으려 들면 구간 라벨·항목명이 - # 자원 이름으로 오해돼 못 맞춘 목록이 못 쓰게 된다(2026-09-08 실측 39줄). - # 값이 있는데 안 서는 자리가 아니라 **애초에 값이 없는 표**다. - headers = " ".join(str(c) for c in (table.get("condition_note") or [])) - if "단가산출서" in headers or "예시" in headers: - result.skipped_forms["example_form"] = result.skipped_forms.get("example_form", 0) + 1 - return + # 예시 서식(「ha당 …단가산출서(예시)」)은 **B08 이 마스터 원천에서 거른다**(8건). + # 두 곳에서 같은 것을 거르면 **나중에 한쪽만 고쳐진다** — 원천이 이긴다. + # ⚠ 짝 시험은 남겨 둔다(예시 서식이 자원으로 안 서는지) — 원천이 바뀌면 그것이 알려 준다. from B09_Estimation.B09_Estimation_CrewOutput import match_crew_table diff --git a/B09_Estimation/B09_Estimation_UI_Page.ts b/B09_Estimation/B09_Estimation_UI_Page.ts index 7afc0166..df04452b 100644 --- a/B09_Estimation/B09_Estimation_UI_Page.ts +++ b/B09_Estimation/B09_Estimation_UI_Page.ts @@ -908,11 +908,9 @@ export async function renderB09Estimation(root: HTMLElement): Promise { } if (bill.summary.missing.length > 0) { - const note = document.createElement("div"); - note.className = "b09-hint"; - note.textContent = `${L("B09_Estimation_Boq_Missing")} (${bill.summary.missing.length})`; - body.append(note); - + // ⚠ **머리글을 한 번만 단다.** 종전엔 「못 세운 줄 (16)」 뒤에 갈래별 머리글이 + // 또 붙어 **같은 수가 두 번** 떴다. 안내를 더하는 것이 곧 다른 안내를 묻는 것이라 + // (2026-09-08 메인 창 지적), 한 화면에 뜨는 줄 수를 늘리지 않는다. // ⚠ **할 일이 다르므로 갈라 보인다** — 「사용자가 입력하면 풀리는 것」과 // 「우리가 만들어야 하는 것」. 한 목록에 섞으면 사용자가 무엇을 해야 할지 못 읽는다. const needsInput = bill.summary.missing.filter( @@ -926,7 +924,10 @@ export async function renderB09Estimation(root: HTMLElement): Promise { if (group.length === 0) continue; const head = document.createElement("div"); head.className = "b09-hint"; - head.textContent = `${L(labelKey)} (${group.length})`; + // 갈래가 하나뿐이면 「금액을 못 세운 줄」이라는 말을 앞에 붙여 뜻이 온전하게 한다. + const prefix = + needsInput.length > 0 && rest.length > 0 ? "" : `${L("B09_Estimation_Boq_Missing")} — `; + head.textContent = `${prefix}${L(labelKey)} (${group.length})`; body.append(head); const list = document.createElement("ul"); for (const item of group) { @@ -1019,6 +1020,16 @@ export async function renderB09Estimation(root: HTMLElement): Promise { return; } + // 자재가 아예 없으면 **빈 표 셋을 늘어놓지 않는다** — 「없다」 한 줄이면 된다 + // (2026-09-08 화면 전수에서 세 무리가 모두 「(0) — 0」 으로 뜨고 있었다). + if (sheet.contractor.length === 0 && sheet.owner.length === 0 && sheet.unknown.length === 0) { + const none = document.createElement("div"); + none.className = "b09-empty"; + none.textContent = L("B09_Estimation_Mat_None"); + body.append(none); + return; + } + for (const [labelKey, rows, total] of [ ["B09_Estimation_Mat_Contractor", sheet.contractor, sheet.contractor_total_krw], ["B09_Estimation_Mat_Owner", sheet.owner, sheet.owner_total_krw], diff --git a/ui_template/ui_template_locale_b2.ts b/ui_template/ui_template_locale_b2.ts index 85712f86..4b4b3d55 100644 --- a/ui_template/ui_template_locale_b2.ts +++ b/ui_template/ui_template_locale_b2.ts @@ -709,6 +709,10 @@ export const ui_locales_b2 = { "관급·사급이 안 갈린 것 — 어느 합계에도 안 넣습니다", "Supply type undecided — excluded from both totals", ], + B09_Estimation_Mat_None: [ + "이 프로젝트에는 자재 줄이 없습니다 — 구조물이 없거나 자재가 안 나온 상태입니다.", + "This project has no material rows yet.", + ], B09_Estimation_Mat_Empty: [ "설계내역서를 먼저 불러오면 자재대가 섭니다.", "Load the bill first and the material sheet appears.", @@ -722,15 +726,15 @@ export const ui_locales_b2 = { "Check rows — quantity only, never priced", ], B09_Estimation_Boq_NeedsInput: [ - "입력하면 풀리는 것 — 설계 화면에서 값을 고르면 금액이 섭니다", + "입력하면 풀리는 것 (설계 화면에서 값을 고르면 금액이 섭니다)", "Waiting on input — pick the value on the design screen and the amount appears", ], B09_Estimation_Boq_NeedsWork: [ - "우리가 만들어야 하는 것 — 원단위·전개식이 아직 없습니다", + "우리가 만들어야 하는 것 (원단위·전개식 없음)", "Needs build — unit data or formula is missing", ], B09_Estimation_Boq_Missing: [ - "금액을 못 세운 줄 — 0 으로 채우지 않고 그대로 보입니다", + "금액을 못 세운 줄 — 0 으로 안 채웁니다", "Rows without an amount — shown as-is, not zero-filled", ], B09_Estimation_Boq_Materials: ["자재 (별도 벌)", "Materials (separate set)"],