From de31cd9c42348ecc1006af47e022365c9e231ae7 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 06:23:47 +0900 Subject: [PATCH] =?UTF-8?q?fix(B08):=20=EA=B0=9C=EC=86=8C=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=AC=BC=EC=9D=B4=20=EC=9D=B8=EA=B3=84=EC=97=90=20?= =?UTF-8?q?=EC=97=B0=EC=9E=A5(m)=EC=9C=BC=EB=A1=9C=20=EB=82=98=EA=B0=80?= =?UTF-8?q?=EB=8D=98=20=EA=B2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ㉕ 타설 줄 실증 중에 드러남. 관측 원단위가 「개소당」인 종류(집수정 셋)를 인계 줄이 늘 「m · 연장」으로 내보내고 있었음. - 연장 2m 짜리 집수정이 「2.000 m」로 나가 값이 두 배로 실림 - 성분은 개소 기준으로 맞게 서는데 줄의 축만 어긋나 아무 시험도 안 잡았음 - billing_of() 로 관측표가 정한 단위·개수를 꺼내 전개 결과에 실음 - 인계 줄이 그 단위를 따름. 비어 있으면 종전대로 「m · 연장」 시험 셋 — 개소로 서는 것 · 연장이 길어도 한 개소 · ⚠ 정상 「m」 줄은 그대로일 것(좁게 고치는 짝 시험). 곁들여 타설 줄 실증: 옹벽 H=2.0(20m) + 집수정 Ø800 으로 돌리면 FP-12-01-01#철근구조물 32.840㎥ 가 인계에 섬(옹벽 27+3 · 집수정 2.84). 시험: 677 passed · 24 skipped (B05 코리도 1건 기존 깨짐, 무관). Co-Authored-By: Claude Opus 5 (1M context) --- B08_Quantity/B08_Quantity_Engine_Handoff.py | 13 +++++++-- .../B08_Quantity_Engine_ObservedUnit.py | 20 +++++++++++++ .../B08_Quantity_Engine_UnitQuantity.py | 28 +++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff.py b/B08_Quantity/B08_Quantity_Engine_Handoff.py index bb73d436..5079967d 100644 --- a/B08_Quantity/B08_Quantity_Engine_Handoff.py +++ b/B08_Quantity/B08_Quantity_Engine_Handoff.py @@ -643,13 +643,22 @@ def _structure_rows( elif entry.get("class_from") in ("back_length", "bond") and class_key is None: unmatched.append(f"{wording_type_label(type_id)} — {class_basis}") length = float(structure.get("length_m") or 0.0) + # ⚠ 관측 원단위가 「개소당」·「㎡당」인 종류는 **연장으로 세면 축이 어긋난다** — + # 집수정 한 개소가 연장 2m 면 값이 두 배로 실린다(2026-09-08 ㉕ 실증). + # 성분은 개소 기준으로 맞게 서는데 **줄의 축만** 틀렸던 자리다. + bill_unit = str(structure.get("billing_unit") or "") or "m" + bill_quantity = ( + float(structure.get("billing_quantity") or 0.0) + if structure.get("billing_unit") + else length + ) rows.append( { "work_item_code": code, "name": str(structure.get("name") or type_id), "spec": _spec_detail(structure), - "unit": "m", - "quantity": length, + "unit": bill_unit, + "quantity": bill_quantity, "quantity_gross": None, "application_ratio_pct": None, "application_ratio_breakdown": None, diff --git a/B08_Quantity/B08_Quantity_Engine_ObservedUnit.py b/B08_Quantity/B08_Quantity_Engine_ObservedUnit.py index b2caa79d..1069c131 100644 --- a/B08_Quantity/B08_Quantity_Engine_ObservedUnit.py +++ b/B08_Quantity/B08_Quantity_Engine_ObservedUnit.py @@ -131,6 +131,26 @@ def scale_for(entry: dict[str, Any], structure: dict[str, Any]) -> tuple[float, return 1.0, "1 개소" +def billing_of( + type_id: str, + spec: dict[str, Any], + structure: dict[str, Any], + table: ObservedUnitTable | None = None, +) -> tuple[str, float] | None: + """(내역 단위, 그 단위로 센 수량). 표에 없으면 `None`. + + ⚠ **왜 있나** — 관측 원단위는 「개소당」·「㎡당」으로도 온다. 그런데 인계 줄이 늘 + 「m · 연장」으로 나가고 있어, **집수정 한 개소가 「연장 2m」면 값이 두 배로 실렸다** + (2026-09-08 ㉕ 실증에서 드러남). 성분은 개소 기준으로 맞게 서는데 **줄의 축만 + 어긋나** 있어서 아무 시험도 안 잡았다. + """ + found = (table or load_observed_table()).find(type_id, spec) + if found is None: + return None + scale, _note = scale_for(found, structure) + return str(found.get("unit") or "개소"), scale + + def expand_observed( type_id: str, spec: dict[str, Any], diff --git a/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py b/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py index 0f656b00..ed3dcd3c 100644 --- a/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py +++ b/B08_Quantity/B08_Quantity_Engine_UnitQuantity.py @@ -38,6 +38,7 @@ from B08_Quantity.B08_Quantity_Engine_ObservedUnit import ( BASIS_DERIVED, BASIS_OBSERVED, ObservedUnitTable, + billing_of, expand_observed, load_observed_table, ) @@ -139,6 +140,11 @@ class StructureQuantity: options: dict[str, Any] = field(default_factory=dict) components: list[Component] = field(default_factory=list) notes: list[str] = field(default_factory=list) + #: 내역 줄이 설 **단위와 그 단위로 센 수량**. 관측 원단위가 「개소당」·「㎡당」인 + #: 종류는 연장(m)으로 세면 축이 어긋난다(2026-09-08 ㉕ 실증). + #: 비어 있으면 종전대로 「m · 연장」으로 선다. + billing_unit: str = "" + billing_quantity: float = 0.0 #: ⚠ **저장 제원의 실제 칸 이름**은 `back_len_cm` 이다(레지스트리 확인). @@ -499,6 +505,22 @@ def _observed_components( return expand_observed(type_id, spec, structure, observed) +def _observed_billing( + type_id: str, + structure: dict[str, Any], + observed: ObservedUnitTable | None, +) -> tuple[str, float] | None: + """관측표가 정한 **내역 단위와 개수**. 규격 키가 없는 종류는 건드리지 않는다.""" + keys = OBSERVED_SPEC_KEYS.get(type_id) + if keys is None: + return None + options = structure.get("options") or {} + spec = {key: options[key] for key in keys if options.get(key) is not None} + if not spec: + return None + return billing_of(type_id, spec, structure, observed) + + def expand( structure: dict[str, Any], names: dict[str, str] | None = None, @@ -538,6 +560,9 @@ def expand( if components or notes: result.components = [Component(**item) for item in components] result.notes.extend(notes) + billing = _observed_billing(type_id, structure, observed) + if billing is not None: + result.billing_unit, result.billing_quantity = billing return result from B08_Quantity.B08_Quantity_Wording import type_label @@ -601,6 +626,9 @@ def build_table( "height_m": item.height_m, "start_m": item.start_m, "end_m": item.end_m, + # 내역 줄이 설 단위·수량 — 관측 원단위가 「개소당」인 종류는 연장으로 못 센다. + "billing_unit": item.billing_unit, + "billing_quantity": item.billing_quantity, # 저장된 제원 — 형식(반중력식…)처럼 **뒤 단계가 읽어야 하는** 값이 여기 있다. "options": item.options, "notes": item.notes,