From 15d91be7b0c78ae687d83ccff1288d6fac93604d Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 14 Sep 2026 23:33:47 +0900 Subject: [PATCH] =?UTF-8?q?feat(b09):=20=EB=B4=89=EC=83=81=ED=9B=84?= =?UTF-8?q?=EB=A0=89=EC=8B=9C=EB=B8=94=20=EC=85=8B(12-12=20=EB=82=A0?= =?UTF-8?q?=EA=B0=9C=EB=B2=BD=20=C2=B7=2012-13=20=EB=A9=B4=EB=B2=BD=20?= =?UTF-8?q?=C2=B7=2012-16=20=EB=A7=A8=ED=99=80)=20+=2012-34-1=20=E2=80=94?= =?UTF-8?q?=20=EA=B0=99=EC=9D=80=20=EC=A7=84=EB=8F=99=EA=B8=B0=20=EC=A4=84?= =?UTF-8?q?=20=ED=95=98=EB=82=98=EA=B0=80=20=EB=A7=89=EB=8D=98=20=EB=84=A4?= =?UTF-8?q?=20=EA=B3=B5=EC=A2=85=EC=9D=84=20=ED=8C=90=EC=A0=95=ED=91=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=84=B8=EC=9B=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit · 12-12·12-13·12-16 — 12-15 집수정과 같은 모양(㎥ 줄 비고 인력 + 다짐기 줄) · 판정표 한 벌에 더함 · 맨홀은 비고가 끝 칸이 아니라 단위 뒤 칸 전체에서 찾음 · 비고가 인/㎥ 인 판정표는 밑수 없음 목록을 안 봄(12-12 는 「개소당」 머리조차 없음) · 12-34-1 — 형태 판정 참조→소요량(「별도계상」 은 레미콘 줄 비고일 뿐) · 새 모양 per_m3_rows · 진동기(3.5HP) 대 2 → 4611-0350 × 2/5.4 · 봉상후렉시블 대 2 는 엔진식 진동기의 봉이라 안 셈 · 머리 개소당 ↔ 인력 0.17·0.29(12-16 ㎥당과 같음)·Q ㎥/hr ⇒ ㎥당으로 읽고 고른 쪽·버린 쪽을 사유에 나란히(브레인 판정) · 범위 별칭 넷 → 엔진식 4611-0350 · 표의 나머지 줄(거푸집·철근·기초잡석·원형거푸집·설치비 등)은 원문 그대로 사유 한 줄(조용히 안 사라지게) · 실무 영월 「콘크리트타설(임도품셈 5-5)」 호표가 같은 모양 — 인력 인/㎥ + 진동기 엔진식 1/Q · 손료 252천원×0.5101 · 노무 0 · 마스터 재생성 — 네 공종 갈래 키 · F0385 형태만 새 제목 다섯 · 바뀐 제목 0 · 프로젝트 내역 그대로 · 잴 시험 넷 빨강→초록 · 끄기 다섯 빨강 · 전체 시험 1699 + 337 통과 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn --- ...B08_Quantity_Build_WorkItemMaster_Forms.py | 5 ++ B09_Estimation/B09_Estimation_KnownGaps.py | 27 ++++++ ...B09_Estimation_ResourceAxis_JudgedTable.py | 64 +++++++++++++- B09_Estimation/B09_Estimation_UnitPrice.py | 6 +- .../data_aliases/aliases_2026-01-01.json | 32 +++++++ .../data_work_item_master/_manifest.json | 6 +- .../work_item_master_2026-01-01.json | 32 +++++-- .../test_b09_flexible_vibrator_tables.py | 85 +++++++++++++++++++ 8 files changed, 242 insertions(+), 15 deletions(-) create mode 100644 resources/tester/test_b09_flexible_vibrator_tables.py diff --git a/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Forms.py b/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Forms.py index dbac4226..51053dc7 100644 --- a/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Forms.py +++ b/B08_Quantity/B08_Quantity_Build_WorkItemMaster_Forms.py @@ -110,6 +110,11 @@ FORM_JUDGMENTS: dict[str, tuple[str, str, str]] = { # 절 제목으로 읽어 12-2 에 붙어 있던 표(빌더가 앞 절 이어받기로 고침). 형태 판정은 그대로. "F0358": ("12-17-1", "reference", "시설유형 Type-Ⅰ~Ⅳ 적용 기준 설명"), "F0360": ("12-17-1", "reference", "현장조건 Type-Ⅰ~Ⅲ 적용 기준 설명"), + "F0385": ( + "12-34-1", + "requirement", + "인력(인)·기계(대, Q=5.4㎥/hr) 소요량 표 — 「별도계상」 은 레미콘 자재 줄 비고일 뿐", + ), "F0388": ("12-34-4", "reference", "「재료비 JOINT FILLER」 항목만 — 값이 없는 구성 안내"), "F0390": ("12-36", "reference", "제작비·운송비·설치비 「견적처리」"), "F0392": ("12-38-1", "coefficient", "사용횟수별 잔존율(12회 25%·25회 10%)"), diff --git a/B09_Estimation/B09_Estimation_KnownGaps.py b/B09_Estimation/B09_Estimation_KnownGaps.py index d94d6ca0..3dad3531 100644 --- a/B09_Estimation/B09_Estimation_KnownGaps.py +++ b/B09_Estimation/B09_Estimation_KnownGaps.py @@ -55,6 +55,33 @@ KNOWN_GAPS: dict[str, tuple[str, str]] = { "(제안 무한궤도 — 영월 실무 「06M3 B/H」 · 2026-09-14 브레인 ②).", ), # 2026-09-14 ㉮ — 사용횟수 갈래로 푼 뒤 남는 원문 몫. 값을 짓지 않고 말만. + # 봉상후렉시블 셋(2026-09-14) — 판정표가 콘크리트 ㎥ 줄만 읽음 · 표의 나머지 줄이 조용히 안 사라지게 원문 그대로. + "FP-12-12": ( + "표의 다른 줄", + "ⓘ 이 일위대가는 콘크리트 ㎥(콘크리트공·보통인부 + 진동기) 몫만 — 표의 거푸집(합판4회) · 철근(SD300 D13" + " · 철근가공조립 간단) · 기초잡석 운반·부설·다짐(소할 30% 브레이커 · 적사 굴착기 0.7㎥ · 운반 덤프트럭" + " 15톤 · 부설다짐 보통인부 0.6인/㎥) · 바닥정리는 안 넣음(따로 공종) · [주] 성토부 날개벽 인건비 30%" + " 할증은 안 걺.", + ), + "FP-12-13": ( + "표의 다른 줄", + "ⓘ 이 일위대가는 콘크리트 ㎥(콘크리트공·보통인부 + 진동기) 몫만 — 표의 거푸집(합판4회) · 기초잡석" + " 운반·부설·다짐(소할 30% 브레이커 · 적사 굴착기 0.7㎥ · 운반 덤프트럭 15톤 · 부설다짐 보통인부" + " 0.6인/㎥)은 안 넣음(따로 공종).", + ), + "FP-12-16": ( + "표의 다른 줄", + "ⓘ 이 일위대가는 구체·버림콘크리트 ㎥ 몫만 — 표의 원형거푸집(PE10회 · 각 1조/10) · 설치비(기초 및" + " 슬라브 특별인부 0.16·보통인부 0.45인/㎡ · 벽체 0.20·0.60인/㎡) · 거푸집(합판4회) · 철근(가공조립" + " 보통) · 설치비 재료비의 5% · 사다리설치(철근가공조립 간단 준용) · 인력 보통인부 0.004 는 안 넣음" + "(따로 공종·조립 몫).", + ), + "FP-12-34-01": ( + "원문 머리", + "ⓘ 원문 머리 「(단위: 개소당)」 ↔ 인력 콘크리트공 0.17·보통인부 0.29 가 12-16 맨홀 ㎥당(0.17인/㎥" + " · 0.29인/㎥)과 같고 기계도 Q=5.4㎥/hr ⇒ ㎥당으로 읽음(고른 쪽 ㎥당 · 버린 쪽 개소당) ·" + " 봉상후렉시블(45mm) 대 2 는 엔진식 진동기의 봉이라 두 번 안 셈(2026-09-14 브레인).", + ), "FP-12-04": ( "원문 [주]", "ⓘ 「사용고재 평가기준 23%(합판과 각재의 설계단가 기준)」 은 원문이 셈을 안 줘 값으로 안 씀" diff --git a/B09_Estimation/B09_Estimation_ResourceAxis_JudgedTable.py b/B09_Estimation/B09_Estimation_ResourceAxis_JudgedTable.py index f5ccccdf..954b7373 100644 --- a/B09_Estimation/B09_Estimation_ResourceAxis_JudgedTable.py +++ b/B09_Estimation/B09_Estimation_ResourceAxis_JudgedTable.py @@ -63,6 +63,39 @@ JUDGED_TABLES: dict[str, dict[str, Any]] = { "needs_machine": {"구체콘크리트": "다짐:봉상후렉시블(45mm)"}, "why": "원문 L6460 「구체·버림콘크리트 ㎥ — 비고 칸 콘크리트공·보통인부 인/㎥」", }, + # 12-15 와 같은 모양 셋 — 같은 봉상후렉시블 줄 하나가 셋을 막고 있었음(2026-09-14 브레인 · 672 다음). + "F0350": { + "code": "FP-12-12", + "shape": "remark_labor", + "prefix": "날개벽", + "needs_machine": {"콘크리트": "다짐:봉상후렉시블(45mm)"}, + "why": "원문 L6419 12-12 「콘크리트(레미콘) ㎥ — 비고 칸 콘크리트공·보통인부 인/㎥」", + }, + "F0351": { + "code": "FP-12-13", + "shape": "remark_labor", + "prefix": "면벽", + "needs_machine": {"콘크리트": "다짐:봉상후렉시블(45mm)"}, + "why": "원문 L6436 12-13 「콘크리트(레미콘) ㎥ — 비고 칸 콘크리트공·보통인부 인/㎥」", + }, + "F0354": { + "code": "FP-12-16", + "shape": "remark_labor", + "prefix": "맨홀", + # 칸이 하나 밀려 비고가 끝 칸이 아님(「구체콘크리트 | 철근 | ㎥ | | 비고 | 」). + "needs_machine": {"구체콘크리트": "봉상후렉시블(45mm)"}, + "why": "원문 L6474 12-16 「구체·버림콘크리트 ㎥ — 비고 칸 콘크리트공·보통인부 인/㎥」", + }, + # 12-34-1 — 머리 「(단위: 개소당)」 이나 인력 0.17·0.29 가 12-16 맨홀 ㎥당과 같고 기계가 Q ㎥/hr + # ⇒ ㎥당으로 읽음 · 「봉상후렉시블 대 2」 는 엔진식 진동기(엔진+플렉시블 한 대)의 봉(2026-09-14 브레인). + "F0385": { + "code": "FP-12-34-01", + "shape": "per_m3_rows", + "prefix": "콘크리트 타설", + # 엔진식 진동기(건설품셈 8-3 (4611) 엔진+플렉시블 한 대)의 봉 — 「진동기(3.5HP) 대 2」 로 셈. + "same_machine": ("봉상후렉시블(45mm)",), + "why": "원문 L6839 12-34-1 「인력 콘크리트공·보통인부 인 · 기계 대 (Q=5.4㎥/hr)」", + }, } #: 비고 칸 인력 — 「콘크리트공0.24인/㎥, 보통인부 0.42인/㎥」. _RE_REMARK_LABOR = re.compile(r"([가-힣]+)\s*(\d+(?:\.\d+)?)\s*인/㎥") @@ -122,7 +155,8 @@ def match_judged_table( result.partial_items[code] = why return True - if basis_quantity in (None, 0): + # 비고·Q 가 ㎥당을 적는 모양은 표 머리 밑수를 안 씀(12-12 날개벽은 「개소당」 머리조차 없음). + if basis_quantity in (None, 0) and judged["shape"] not in ("remark_labor", "per_m3_rows"): return block("판정 표에 밑수가 없습니다") if judged["shape"] == "header_row": staged = _header_row(code, table, judged, rows, catalog, basis_quantity, unit) @@ -130,6 +164,8 @@ def match_judged_table( staged = _use_count(code, table, rows, catalog, basis_quantity, unit) elif judged["shape"] == "remark_labor": staged = _remark_labor(code, table, judged, rows, catalog) + elif judged["shape"] == "per_m3_rows": + staged = _per_m3_rows(code, table, judged, rows, catalog) else: staged = _merged_first(code, table, judged, rows, catalog, basis_quantity, unit) if isinstance(staged, str): @@ -240,7 +276,7 @@ def _remark_labor(code, table, judged, rows, catalog) -> list | str: by_name = {"".join(cells[0].split()): cells for cells in rows if cells} staged: list = [] for index, cells in enumerate(rows): - labors = _RE_REMARK_LABOR.findall(cells[-1] if cells else "") + labors = _RE_REMARK_LABOR.findall(" ".join(cells[3:])) # 비고가 끝 칸이 아닌 표(12-16) if len(cells) < 3 or cells[2] != "㎥" or not labors: continue variant = cells[0].split("(")[0].strip() @@ -268,3 +304,27 @@ def _remark_labor(code, table, judged, rows, catalog) -> list | str: for entry, amount in pieces: staged.append(_row(code, table, entry, amount, "㎥", index, variant)) return staged + + +def _per_m3_rows(code, table, judged, rows, catalog) -> list | str: + """「인」 칸 앞 이름 · 뒤 수(인/㎥) · 「대」 칸 앞 이름 · 뒤 대수 ÷ Q — 칸이 밀린 줄도 단위 칸으로 찾음.""" + table_id = str(table.get("pum_table_id", "")) + staged: list = [] + for index, cells in enumerate(rows): + unit = next((i for i, c in enumerate(cells) if c in ("인", "대") and i > 0), None) + if unit is None: + continue + name = cells[unit - 1] + if "".join(name.split()) in judged.get("same_machine", {}): + continue # 같은 기계 두 번 안 셈 — 까닭은 공종 사유 한 줄(`KnownGaps`)이 화면에 보임 + amount = next((parse_amount(c) for c in cells[unit + 1 :] if parse_amount(c)), None) + entry = _entry(catalog, name, code) + if amount is None or entry is None: + return f"{name} 줄" + if cells[unit] == "대": + found_q = _RE_Q.search(" ".join(cells)) + if found_q is None: + return f"{name} Q" + amount = amount / Decimal(found_q.group(1)) + staged.append(_row(code, table, entry, amount, "㎥", index, "")) + return staged diff --git a/B09_Estimation/B09_Estimation_UnitPrice.py b/B09_Estimation/B09_Estimation_UnitPrice.py index 100028f0..2c824a43 100644 --- a/B09_Estimation/B09_Estimation_UnitPrice.py +++ b/B09_Estimation/B09_Estimation_UnitPrice.py @@ -899,9 +899,13 @@ def build_unit_prices( # 예외 = 사용자가 다른 품셈 밑수를 확정한 자리(9-21 「1,000㎡당」 · ÷ 는 ChooseOne 이 검) — # 마스터 목록은 원문 사실이라 그대로 두고 여기서만 걷음(2026-09-14 브레인 · 사유는 비고). borrowed = work_item_code.startswith(tuple(BORROWED_BASIS_PER)) + from B09_Estimation.B09_Estimation_ResourceAxis_JudgedTable import JUDGED_TABLES + for row in rows: section = missing_basis.get(str(row.pum_table_id)) - if section and not borrowed: + # 비고가 「인/㎥」 로 밑수를 적은 판정표(12-12 날개벽 「개소당」 머리 없음)는 밑수가 ㎥ 로 섬. + per_remark = JUDGED_TABLES.get(str(row.pum_table_id), {}).get("shape") == "remark_labor" + if section and not borrowed and not per_remark: build.basis_missing[work_item_code] = section break diff --git a/resources/data_aliases/aliases_2026-01-01.json b/resources/data_aliases/aliases_2026-01-01.json index b41c5f8a..90611b55 100644 --- a/resources/data_aliases/aliases_2026-01-01.json +++ b/resources/data_aliases/aliases_2026-01-01.json @@ -51,6 +51,38 @@ "pum_edition": "2026-01-01", "basis": "12-15 집수정 표(원문 L6460)는 「봉상후렉시블(45mm) Q=5.4㎥/hr」 만 적고 전기·엔진을 안 적음 — 건설품셈 8-3 (4611) 은 전기식 플렉시블형 ø45(0.75㎾)·엔진식 플렉시블형 ø45(2.6㎾) 둘 · 12-34-1 「콘크리트 진동기(3.5HP)」 = 2.6㎾ 엔진식 · 운전경비표(8-4)에 엔진식 4611-0350(휘발유 1.0L)만 있음 → 엔진식. 전기식이 필요하면 그때 엶. 2026-09-14 브레인 승인(661 뒤 ①)" }, + { + "axis": "resource", + "from": "다짐:봉상후렉시블(45mm)", + "to": "4611-0350", + "scope": "FP-12-12", + "pum_edition": "2026-01-01", + "basis": "12-12 날개벽(원문 L6419) 표는 「봉상후렉시블(45mm) Q=5.4㎥/hr」 만 적고 전기·엔진을 안 적음 — 12-15 집수정과 같은 줄 · 같은 근거(건설품셈 8-3 (4611) 전기식·엔진식 둘 · 12-34-1 「콘크리트 진동기(3.5HP)」 = 2.6㎾ 엔진식 · 운전경비표에 엔진식 4611-0350 만) → 엔진식. 2026-09-14 브레인(672 다음 · 봉상후렉시블 셋)" + }, + { + "axis": "resource", + "from": "다짐:봉상후렉시블(45mm)", + "to": "4611-0350", + "scope": "FP-12-13", + "pum_edition": "2026-01-01", + "basis": "12-13 면벽(원문 L6436) 표는 「봉상후렉시블(45mm) Q=5.4㎥/hr」 만 적고 전기·엔진을 안 적음 — 12-15 집수정과 같은 줄 · 같은 근거(건설품셈 8-3 (4611) 전기식·엔진식 둘 · 12-34-1 「콘크리트 진동기(3.5HP)」 = 2.6㎾ 엔진식 · 운전경비표에 엔진식 4611-0350 만) → 엔진식. 2026-09-14 브레인(672 다음 · 봉상후렉시블 셋)" + }, + { + "axis": "resource", + "from": "봉상후렉시블(45mm)", + "to": "4611-0350", + "scope": "FP-12-16", + "pum_edition": "2026-01-01", + "basis": "12-16 맨홀(원문 L6474) 표는 「봉상후렉시블(45mm) Q=5.4㎥/hr」 만 적고 전기·엔진을 안 적음 — 12-15 집수정과 같은 줄 · 같은 근거(건설품셈 8-3 (4611) 전기식·엔진식 둘 · 12-34-1 「콘크리트 진동기(3.5HP)」 = 2.6㎾ 엔진식 · 운전경비표에 엔진식 4611-0350 만) → 엔진식. 2026-09-14 브레인(672 다음 · 봉상후렉시블 셋)" + }, + { + "axis": "resource", + "from": "콘크리트 진동기(3.5HP)", + "to": "4611-0350", + "scope": "FP-12-34-01", + "pum_edition": "2026-01-01", + "basis": "12-34-1 「콘크리트 진동기(3.5HP)」 — 3.5HP = 2.6㎾ · 건설품셈 8-3 (4611) 엔진식 플렉시블형 ø45(2.6㎾) · 운전경비표에 엔진식만 · 실무 영월 중기목록 「콘크리트 진동기 45φ(2.6㎾)엔진식플렉시블형」. 2026-09-14 브레인" + }, { "axis": "variant", "from": "0.2·소림", diff --git a/resources/data_work_item_master/_manifest.json b/resources/data_work_item_master/_manifest.json index 07545afd..faaf4482 100644 --- a/resources/data_work_item_master/_manifest.json +++ b/resources/data_work_item_master/_manifest.json @@ -1,7 +1,7 @@ { "schema_version": "1.0", "dataset_id": "data_work_item_master_manifest", - "generated_at": "2026-09-14T22:25:14+09:00", + "generated_at": "2026-09-14T23:14:39+09:00", "built_by": "B08_Quantity/B08_Quantity_Build_WorkItemMaster.py", "source": { "dataset_id": "pum_forest", @@ -12,8 +12,8 @@ "files": [ { "file": "work_item_master_2026-01-01.json", - "sha256": "2f0d8bfb3836597ee6f2fd755e2f2e172cb33df1e1e9061af81982a43497f9b2", - "size_bytes": 838655 + "sha256": "708a6a80d016c42d3402bd2a6cb5266fa18e1e2b5301bac823e6ff79d17fb2b6", + "size_bytes": 838969 }, { "file": "form_undetermined_2026-01-01.json", diff --git a/resources/data_work_item_master/work_item_master_2026-01-01.json b/resources/data_work_item_master/work_item_master_2026-01-01.json index fef4885e..08b0f120 100644 --- a/resources/data_work_item_master/work_item_master_2026-01-01.json +++ b/resources/data_work_item_master/work_item_master_2026-01-01.json @@ -3,7 +3,7 @@ "dataset_id": "work_item_master_forest", "effective_date": "2026-01-01", "pum_edition": "2026-01-01", - "generated_at": "2026-09-14T22:25:14+09:00", + "generated_at": "2026-09-14T23:14:39+09:00", "dataset_version": { "dataset_id": "pum_forest", "effective_date": "2026-01-01", @@ -32888,7 +32888,9 @@ "formula_rows": [], "special_glyphs": [], "capacity_formula_here": false, - "variant_key": [], + "variant_key": [ + "콘크리트" + ], "condition_note": [ "구 분", "규 격", @@ -32970,7 +32972,9 @@ ] } ], - "variant_keys": [] + "variant_keys": [ + "콘크리트" + ] }, { "work_item_code": "FP-12-13", @@ -32997,7 +33001,9 @@ "formula_rows": [], "special_glyphs": [], "capacity_formula_here": false, - "variant_key": [], + "variant_key": [ + "콘크리트" + ], "condition_note": [ "구 분", "규 격", @@ -33065,7 +33071,9 @@ ] } ], - "variant_keys": [] + "variant_keys": [ + "콘크리트" + ] }, { "work_item_code": "FP-12-14", @@ -33242,7 +33250,10 @@ "formula_rows": [], "special_glyphs": [], "capacity_formula_here": false, - "variant_key": [], + "variant_key": [ + "구체콘크리트", + "버림콘크리트" + ], "condition_note": [ "구 분", "단위", @@ -33349,7 +33360,10 @@ ] } ], - "variant_keys": [] + "variant_keys": [ + "구체콘크리트", + "버림콘크리트" + ] }, { "work_item_code": "FP-12-17", @@ -35258,8 +35272,8 @@ "pum_table_id": "F0385", "section": "12-34-1. 콘크리트 타설(철근 진동기포함)", "source_line": 6843, - "pum_form": "reference", - "form_basis": "'별도계상' — 값이 아니라 참조 지시", + "pum_form": "requirement", + "form_basis": "사람 판정 — 인력(인)·기계(대, Q=5.4㎥/hr) 소요량 표 — 「별도계상」 은 레미콘 자재 줄 비고일 뿐", "basis_quantity": 1.0, "basis_unit": "개소", "basis_source": "본문", diff --git a/resources/tester/test_b09_flexible_vibrator_tables.py b/resources/tester/test_b09_flexible_vibrator_tables.py new file mode 100644 index 00000000..6e895029 --- /dev/null +++ b/resources/tester/test_b09_flexible_vibrator_tables.py @@ -0,0 +1,85 @@ +"""봉상후렉시블 셋 + 12-34-1 — 2026-09-14 브레인(672 다음). + +같은 「봉상후렉시블(45mm) Q=5.4㎥/hr」 줄 하나가 12-12 날개벽 · 12-13 면벽 · 12-16 맨홀을 막고 있었음. +세 표는 12-15 집수정과 같은 모양(㎥ 줄 비고 인력 + 다짐기 줄) — 판정표 한 벌에 더하고 엔진식 4611-0350 로 이음. +실무 영월 「콘크리트타설(임도품셈 5-5)」 호표가 같은 모양: 콘크리트공 0.15·보통인부 0.27 인/㎥ + 진동기 1/Q(5.4) +· 진동기 45φ(2.6㎾)엔진식플렉시블형 손료 252천원 × 0.5101 · 노무 0 — 엔진식·계수·조종원 없음이 실무 실증. + +12-34-1 콘크리트 타설(철근 진동기 포함) — 실무 호표 없음 → 브레인 판정 + ① 「콘크리트 진동기(3.5HP) 대 2」 만 4611-0350 × 2대 · 「봉상후렉시블(45mm) 대 2」 는 같은 기계의 봉이라 안 셈 + ② 머리 「(단위: 개소당)」 ↔ 인력 0.17·0.29 가 12-16 맨홀 ㎥당과 같고 기계도 Q ㎥/hr ⇒ ㎥당으로 읽고 사유에 나란히 +""" + +from __future__ import annotations + +from decimal import Decimal + +from B09_Estimation.B09_Estimation_UnitPrice import cached_build, detail_of + +VIBRATOR = "X-4611-0350" +PER_Q = Decimal(1) / Decimal("5.4") + + +def _rows(build, code: str) -> dict[str, Decimal]: + return { + r["ref_code"]: Decimal(r["quantity"]) + for r in detail_of(build, code)["rows"] + if r.get("ref_code") + } + + +def test_날개벽_면벽은_콘크리트_갈래가_진동기와_함께_섬() -> None: + build = cached_build() + for code in ("B-FP-12-12#콘크리트", "B-FP-12-13#콘크리트"): + rows = _rows(build, code) + assert rows[VIBRATOR] == PER_Q and rows["1013"] == Decimal("0.15"), (code, rows) + assert rows["1002"] == Decimal("0.27"), (code, rows) + assert build.book.titles[code].unit == "㎥" + assert ( + "FP-12-12" not in build.basis_missing + ) # 비고가 인/㎥ — 「개소당」 머리가 없어도 밑수는 ㎥ + + +def test_맨홀은_비고가_끝_칸이_아니어도_구체_버림_갈래() -> None: + build = cached_build() + body = _rows(build, "B-FP-12-16#구체콘크리트") + assert ( + body[VIBRATOR] == PER_Q + and body["1013"] == Decimal("0.17") + and body["1002"] == Decimal("0.29") + ) + lean = _rows(build, "B-FP-12-16#버림콘크리트") + assert lean == {"1013": Decimal("0.15"), "1002": Decimal("0.27")}, lean + for code in ("FP-12-12", "FP-12-13", "FP-12-16"): + assert "봉상후렉시블" not in " ".join(build.unattached.get(code, [])), code + + +def test_12_34_1_진동기_두_대만_세고_봉은_두_번_안_셈() -> None: + build = cached_build() + rows = _rows(build, "B-FP-12-34-01") + assert rows[VIBRATOR] == 2 * PER_Q, rows + assert rows["1013"] == Decimal("0.17") and rows["1002"] == Decimal("0.29"), rows + from B09_Estimation.B09_Estimation_KnownGaps import known_gap_note + + assert "봉상후렉시블" in known_gap_note("FP-12-34-01") and "두 번 안 셈" in known_gap_note( + "FP-12-34-01" + ) + refs = [r.get("ref_code") for r in detail_of(build, "B-FP-12-34-01")["rows"]] + assert refs.count(VIBRATOR) == 1, refs # 봉 줄이 기계로 또 안 섬(한 줄 · 2대 몫) + + +def test_12_34_1_은_개소당_머리를_세제곱미터당으로_읽은_까닭을_보임() -> None: + from B09_Estimation.B09_Estimation_KnownGaps import known_gap_note + + build = cached_build() + assert build.book.titles["B-FP-12-34-01"].unit == "㎥" + note = known_gap_note("FP-12-34-01") + assert "개소당" in note and "㎥당" in note and "12-16" in note, note + + +def test_표의_나머지_줄은_조용히_안_사라지고_사유에_원문_그대로() -> None: + from B09_Estimation.B09_Estimation_KnownGaps import known_gap_note + + assert "원형거푸집" in known_gap_note("FP-12-16") and "0.16" in known_gap_note("FP-12-16") + assert "기초잡석" in known_gap_note("FP-12-12") and "30%" in known_gap_note("FP-12-12") + assert "기초잡석" in known_gap_note("FP-12-13")