feat(B09): 기계만 쓰는 공종이 공식만으로 서게 함 — 층따기 살아남

「미판정 77건」을 훑다 나온 것 — 그중 일부는 **미판정이 아니라 시공능력 공식표**였음
```
F0287 9-18 층따기        굴착기 0.7㎥ · K 0.9 · f 1/1.3 · E 0.7 · Cm 22
F0328 11-4 쇄석·혼합석 부설  같은 모양
```
- ⚠ **자원 줄이 하나도 없으면 조립 반복문이 그 공종을 아예 안 보고 있었음** —
  표에 인력 줄이 없는 기계 전용 공종이 그 자리. 공식이 온전하면 세우게 고침
- 층따기 **1,550.9원/㎡** · 쇄석 부설 **3,549.5원/㎡** 로 섬 (일위대가 203 → 206)
- 내역서(2f940d8a) 합계 **2,149,464 → 6,252,172원** —
  층따기 2,645.21㎡ × 1,550.9 = 4,102,708 이 실제로 붙음
- ⚠ 짝 시험 — 자원도 공식도 없으면 **여전히 안 섬**(0 원 일위대가 금지).
  그 문이 넓으면 빈 제목이 무더기로 서고 화면이 「단가가 있다」고 거짓말함

검증: pytest 215 통과(신규 2)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-08 10:37:50 +09:00
co-authored by Claude Opus 5
parent 6aa8001780
commit dd23c9d04f
+29 -1
View File
@@ -333,6 +333,19 @@ def build_unit_prices(axis: AxisResult | None = None) -> UnitPriceBuild:
for row in axis.rows:
by_item.setdefault((row.work_item_code, getattr(row, "variant", "")), []).append(row)
# ⚠ **자원 줄이 하나도 없어도 공식이 온전하면 세운다.** 기계만 쓰는 공종(층따기 9-18 ·
# 쇄석 부설 11-4)은 표에 인력 줄이 없어 자원 축이 비고, 그러면 아래 반복문이 그 공종을
# 아예 안 본다 — 「미판정」으로 남아 있던 것이 실은 **시공능력 공식표**였다
# (2026-09-08 미판정 77건을 훑다 발견).
for node in master.get("work_items", []):
code = node.get("work_item_code")
if not code or (code, "") in by_item:
continue
for table in node.get("tables", []):
if isinstance(extract_cycle_factors(code, table), CycleFactors):
by_item[(code, "")] = []
break
for (work_item_code, variant), rows in sorted(by_item.items()):
# 갈래 키는 **내부 공백을 지운 것**, 화면 문구는 **원문 그대로**
# (2026-09-08 두 창 합의). 원문이 「보 통」·「보 통」으로 들쭉날쭉해
@@ -349,7 +362,8 @@ def build_unit_prices(axis: AxisResult | None = None) -> UnitPriceBuild:
for row in rows
]
attachable = [(row, ref) for row, ref in attachable if ref in build.book.titles]
if not attachable:
if not attachable and not _has_full_formula(master, work_item_code):
# 붙을 상세도 없고 공식도 없으면 **제목도 안 세운다**(0 원 일위대가 금지).
build.skipped.append(work_item_code)
continue
@@ -416,6 +430,20 @@ def build_unit_prices(axis: AxisResult | None = None) -> UnitPriceBuild:
return build
def _has_full_formula(master: dict, work_item_code: str) -> bool:
"""그 공종에 **온전한 시공능력 공식**이 있는가 (기계만 쓰는 공종용)."""
node = next(
(w for w in master.get("work_items", []) if w.get("work_item_code") == work_item_code),
None,
)
if node is None:
return False
return any(
isinstance(extract_cycle_factors(work_item_code, table), CycleFactors)
for table in node.get("tables", [])
)
def _share_of(row) -> Decimal:
"""그 줄이 차지하는 몫(0~1). 배분율이 없으면 1 — 종전과 같다."""
ratio = getattr(row, "group_ratio_pct", None)