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,