브레인 4바퀴. 원문을 열어 하나씩 확인한 뒤 고침. **값(숫자)은 원문 그대로, 자리·이름만 맞춤.**
**① 거푸집 「각재」 비율 여섯 — 지어낸 것이라 뺌**
원문 12-4(F0336)는 **「합 판」 한 줄에 두 열**(재료별% · 노무비%)을 적고 「각 재」 줄엔 비율이 없음.
각재 자리에 있던 값이 **합판 줄의 노무비 열과 한 자도 안 다름** — 다른 열을 갖다 쓴 것이었음.
⇒ `plywood` → `plywood_material_pct` · 노무비 열은 `plywood_labor_pct` 로 제자리 ·
각재는 `timber_absent` 로 **비워 두고 사유**(옛 값도 함께 남김)
**② 기계 손료 5202·5203 칸 밀림** — 규격 열이 둘·셋인 표라 한 칸(5202)·두 칸(5203) 밀려
상각비율 자리에 내용시간(800·4,000)이 들어가 있었음. 원문 건설 8장 L2617~2633 대로 여덟 줄 맞춤
**③ 전문공사 일반관리비 300억 이상** — 마지막 구간이 「100억-300억 미만」으로 닫혀 **300억 넘는
전문공사가 어느 구간에도 안 걸렸음.** 원본 XLSX 대로 300~1000억 · 1000억 이상(둘 다 4.5%)을 더함.
⚠ 대조 시험의 구간 지도(`SPECIAL_PRICE`)에도 그 둘이 없어 원문 쪽에서도 안 잡히고 있었음 — 함께 채움
**④ 제조 노임 115~123 상위업종** — 「품질 관리 / 하위업종」인데 하위만 담아 반쪽이었음(아홉 줄).
원문(중소기업중앙회 조사표)대로 채움. 노임 값은 안 건드림
**⑤ 덤프 토사 E0 는 0.60 이 맞음** — 원문 10-12-1 은 식에 `E0=0.75` 를 적지만 바로 아래 [주]⑤
「굴착기의 작업능력 산정 비교」 표가 `E0 | 토사 | 0.60(불량)` 비고에 **「임 도」** 라고 못박음. 근거를 자료에 적음
**⑥ 이형철근** — 원문이 범위 「6~7」로 적은 것을 `alt_rate_range` 로 함께 실음(쓰는 값은 7 그대로)
⚠ 기계 이름 뭉개짐·손료표 md 변환 오류는 **코덱스가 md 를 고치는 중**이라 손대지 않음
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsGw1Dz9HmhuAxGisxmDPF
136 lines
5.1 KiB
Python
136 lines
5.1 KiB
Python
"""거푸집 사용횟수 검사 — 품셈 1-7-1 · 2026-09-07 ⑩.
|
||
|
||
⚠ 사용횟수는 **관측값이 아니라 법**이다 — 품셈 1-7-1 이 구조물 종류별로 정해 둔다.
|
||
⚠⚠ **횟수별 재료 환산은 여기서 하지 않는다.** 품셈 12-4 의 비율(%)은 일위대가 재료비에
|
||
걸리는 값이라, B08 이 곱해 넘기면 B09 가 또 곱해 두 번 준다.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
sys.path.insert(0, str(ROOT))
|
||
|
||
from B08_Quantity.B08_Quantity_Engine_Formwork import ( # noqa: E402
|
||
FORMWORK_NAMES,
|
||
NOTE_REUSE_MISSING,
|
||
FormworkTable,
|
||
annotate,
|
||
load_formwork_table,
|
||
shoring_status,
|
||
)
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table # noqa: E402
|
||
|
||
|
||
def 옹벽() -> dict:
|
||
return {
|
||
"structure_id": "w1",
|
||
"type_id": "retaining_wall",
|
||
"start_m": 100.0,
|
||
"end_m": 110.0,
|
||
"options": {"form": "반중력식", "height_m": 2.0, "length_m": 10.0},
|
||
}
|
||
|
||
|
||
def test_원문에_이름이_있는_구조물은_횟수가_붙음() -> None:
|
||
"""품셈 1-7-1 의 3회 줄에 「옹벽」이 그대로 있다."""
|
||
table = build_table([옹벽()], {"retaining_wall": "옹벽"})
|
||
forms = [c for s in table["structures"] for c in s["components"] if c["name"] in FORMWORK_NAMES]
|
||
assert forms
|
||
assert all(c["reuse_count"] == 3 for c in forms)
|
||
assert all("1-7-1" in c["reuse_note"] for c in forms)
|
||
assert table["formwork_reuse_missing"] == []
|
||
|
||
|
||
def test_횟수별_비율을_여기서_곱하지_않을것() -> None:
|
||
"""⚠ 이 시험이 이 파일의 핵심 — 합판 3회 46.1 % 를 여기서 곱하면 B09 와 겹쳐 두 번 준다.
|
||
B08 이 내는 것은 **접촉 면적 그대로**이고 횟수는 옆에 적기만 한다."""
|
||
table = build_table([옹벽()], {"retaining_wall": "옹벽"})
|
||
euroform = next(
|
||
c for s in table["structures"] for c in s["components"] if c["name"] == "유로폼"
|
||
)
|
||
# 3.205 ㎡/m × 10m — 46.1 % 를 곱하지 않았다(2026-09-14 원문 값 3.205 · 전 라이브러리 3.20 → 32.0)
|
||
assert abs(euroform["amount"] - 32.05) < 1e-9
|
||
# 비율표는 데이터에 있되 값에 안 걸린다.
|
||
assert load_formwork_table().reuse_ratio_pct["plywood_material_pct"]["3"] == 46.1
|
||
|
||
|
||
def test_모르는_종류는_지어내지_않고_미확보() -> None:
|
||
structures = [
|
||
{
|
||
"type_id": "듣도보도못한구조물",
|
||
"name": "무엇",
|
||
"components": [{"name": "합판거푸집", "unit": "㎡", "amount": 5.0}],
|
||
}
|
||
]
|
||
notes, missing = annotate(structures)
|
||
assert missing == ["듣도보도못한구조물"]
|
||
assert structures[0]["components"][0]["reuse_count"] is None
|
||
assert structures[0]["components"][0]["reuse_note"] == NOTE_REUSE_MISSING
|
||
|
||
|
||
def test_거푸집이_없는_구조물은_건드리지_않음() -> None:
|
||
structures = [
|
||
{
|
||
"type_id": "retaining_wall",
|
||
"components": [{"name": "콘크리트", "unit": "㎥", "amount": 1.0}],
|
||
}
|
||
]
|
||
notes, missing = annotate(structures)
|
||
assert notes == [] and missing == []
|
||
assert "reuse_count" not in structures[0]["components"][0]
|
||
|
||
|
||
def test_이름은_정확히_일치로만_본다() -> None:
|
||
"""부분일치면 「거푸집씻기」(공사용수 항목)가 거푸집으로 잡힌다."""
|
||
structures = [
|
||
{
|
||
"type_id": "retaining_wall",
|
||
"components": [{"name": "거푸집씻기", "unit": "㎥", "amount": 1.0}],
|
||
}
|
||
]
|
||
annotate(structures)
|
||
assert "reuse_count" not in structures[0]["components"][0]
|
||
|
||
|
||
def test_파일이_없으면_전부_미확보() -> None:
|
||
structures = [
|
||
{
|
||
"type_id": "retaining_wall",
|
||
"components": [{"name": "유로폼", "unit": "㎡", "amount": 1.0}],
|
||
}
|
||
]
|
||
notes, missing = annotate(structures, FormworkTable())
|
||
assert missing == ["retaining_wall"]
|
||
|
||
|
||
# ── 동바리 ──────────────────────────────────────────────────────────
|
||
|
||
|
||
def test_동바리는_대상이_없으면_없다고_말할것() -> None:
|
||
"""0 으로 적으면 「대상이 없음」과 「값이 0」이 구별되지 않는다."""
|
||
status = shoring_status()
|
||
assert status["applicable"] is False
|
||
assert status["reason"]
|
||
# BOX암거는 2026-09-15 전개식이 서서 대상에서 빠짐 — 세월교만 남음(근거 없음 사유).
|
||
assert set(status["pending_types"]) == {"ford_bridge"}
|
||
|
||
|
||
def test_표에도_동바리_상태가_실림() -> None:
|
||
assert build_table([옹벽()])["shoring"]["applicable"] is False
|
||
box = {
|
||
"type_id": "box_culvert",
|
||
"options": {
|
||
"body_width_m": 2.0,
|
||
"body_height_m": 2.0,
|
||
"wall_thickness_m": 0.3,
|
||
"slab_thickness_m": 0.3,
|
||
"haunch_m": 0.2,
|
||
"blinding_thickness_m": 0.1,
|
||
"length_m": 5.0,
|
||
},
|
||
}
|
||
assert build_table([box])["shoring"]["applicable"] is True
|