Files
Aislo/resources/tester/test_form_const.py
T
eomsangdonandClaude Opus 5 83220dc231 feat(b08): 건설 표 모양 판정층 — 미판정 891 → 276 · 건설 밑수 미판독 184 → 90 · 산림 0 그대로
브레인 일감. 표 모양 판정이 산림 표에 맞춰져 있어 건설 표를 못 가렸음(건설 미판정 891 · 산림 0).

- `B08_Quantity_Build_WorkItemMaster_FormsConst.py` — **산림 잣대가 미판정으로 떨어질 때만** 도는 건설 층
  · 기계 손료표(머리 「분류번호」+「상각 비율」) 122 · 율·계수표(손율·요율·보정계수) · 값 없는 설명·서식표 ·
    건설 직종(특급·고급·중급·초급 기술자 · 지적기사 · 비계공 …) · 줄에 단위 칸+값 · 값 자리 계산식 ·
    줄 이름이 공식 기호뿐(β·E) · 값이 모두 백분율
  · ⚠ 비고 칸의 산출식은 값으로 안 봄 — 인력품 표가 계수로 뒤집히던 자리(9-5-4 수치지도)
  · ⚠ 꼬리 단위는 품·자재 단위만 값으로 셈 — `24hr`·`30톤`·`0.166일` 은 규격·기간 표기
  · ⚠ **못 가리면 미판정으로 남김**(276) — 지어내 채우지 않음
- 밑수 읽기 넓힘(`_Table.SOURCE_BASIS_RE`) — 건설은 절 제목 끝에 밑수를 적음
  · `(용접개소당)`·`(세면기 개당)`·`(절단길이 10m당)`·`(m당 : 관길이기준)`·`(일당)`·`(기당)` 을 읽음
  · 단위에 일·기·조·명·틀 더함 · ⚠ 「회」는 넣지 않음(2026-09-17 서브 판정)
  · ⚠ `(1ha당, 100본당)` 처럼 **밑수가 둘이면 안 고름** · `공㎥당` 이 `㎥` 로 깎이던 것 고침
  · ⚠ **작업조 표에는 「일」 밑수를 안 붙임**(c3da333e — 붙이면 유로폼 호표가 사라짐)
  · 건설 밑수 확보 537 → 639 · 미판독 184 → 90 · 산림 확보 251 → 253(원문에 있던 2-2-4 ha당 등)
- 브레인 ①[주] 17 중 3 — `md_notes` 이어짐에 「- 4. 고철공제 : A=…」 꼴(콜론·식) 과 중첩 [주] 를 더함
  · 남은 14 는 마스터가 뒤 표의 [주]까지 붙여 둔 자리 — 「애매하면 안 깎는 쪽」으로 까닭만 달아 둠
- 브레인 ②밑수 5 중 3 — `(단위 : ㎥)`(당 없음) · 줄 칸의 `(160ha당)` · 설명 글이 앞에 붙은 `(…, ha당)` 읽음
  · F0322·F0323 은 원문 밑수가 표 둘 아래 산출식에만 있어 사람 판정 유지
-  금액 불변 — 직접공사비 5,983,724 · 합계 10,124,000 그대로
- `test_form_const.py` 22 — 산림이 건설 층에 안 닿음 · 꼴 여섯 가름 · 못 가리면 미판정 · 밑수 아홉 꼴
- 곁가지(받은 md 손질이 부른 것)
  · 8-11 이동식 임목파쇄기 — 랩탑 서브가 8-4 를 줄마다 풀자 **연료 칸이 「-」 인 빈 레코드**가 생겨 산림
    8-11 [주]⑤(13.5ℓ·16%) 층이 통째로 빠졌음 → 「연료가 비면 산림 [주]가 정본」으로 두 자리 고침
  · 기계 벌 — 중유 기종(`중유487.2`) 을 경유로 적던 것 · 연료가 둘인 칸(`73.7+휘발유54.5`) 을 반쪽만 싣던 것 ·
    원문에 없는 기종을 옛 벌에서 이어받던 것 고침(연료 274)
  · ⚠ 이어받기는 **원문에 줄이 있는데 연료를 못 읽은 코드**까지 — 못 읽은 줄 + 주연료 칸이 수가 아닌 줄
    (양수기 7993-0020 은 원문이 규격을 연료 칸에 밀어 적음 `1.49㎾` · 사람이 맞춘 1.3ℓ 를 지킴)
  · 이름표 줄 수·건설 공종 축 셈·열쇠 지문 갱신(까닭을 파일에 적음)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
2026-09-18 09:07:49 +09:00

161 lines
7.3 KiB
Python

"""건설 표 모양 판정·밑수 읽기 — 2026-09-18 브레인 일감(랩탑 메인).
못박는 것
- ⚠ **산림은 건설 층에 닿지 않음** — 건설 규칙은 산림 잣대가 못 가른 표에서만 돌고, 산림 미판정은 0.
- 건설 꼴 여섯(기계 손료표 · 율·계수 · 값 없는 설명표 · 건설 직종 · 단위 칸 · 공식 기호)을 가름.
- **못 가리면 미판정** — 지어내지 않음.
- 밑수 읽기: 건설 꼴 `(용접개소당)`·`(세면기 개당)`·`(m당 : 관길이기준)`·`(일당)` 를 읽되
⚠ `(1ha당, 100본당)` 처럼 **둘이면 안 고름** · `공㎥당` 은 `㎥` 로 안 깎임 · 「회」는 밑수가 아님.
- ⚠ **작업조 표에는 「일」 밑수를 안 붙임**(c3da333e — 붙이면 유로폼 호표가 사라짐).
금액 불변은 `test_work_item_key_gate.py` 원가계산서 시험(직접공사비 5,983,724)이 잼.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from B08_Quantity.B08_Quantity_Build_WorkItemMaster_FormsConst import ( # noqa: E402
judged_const_form,
)
from B08_Quantity.B08_Quantity_Build_WorkItemMaster_Table import ( # noqa: E402
basis_from_source,
crew_table,
detect_form,
new_report,
table_entry,
)
FOREST = ROOT / "resources/data_cost_input_value/pum_forest_2026.json"
MASTER = ROOT / "resources/data_work_item_master"
def table(headers: list[str], rows: list[list[str]]) -> dict:
return {"table_id": "T0001", "section": "시험", "line": 3, "headers": headers, "rows": rows}
# ── 산림을 흔들지 않음 ────────────────────────────────────────────────────
def test_산림_표는_건설_층에_닿지_않음() -> None:
"""산림 표는 산림 잣대에서 이미 갈림 — 건설 규칙이 산림 값을 바꿀 수 없음(마스터 산출로 잼)."""
master = json.loads((MASTER / "work_item_master_2026-01-01.json").read_text(encoding="utf-8"))
reached = [
t["pum_table_id"]
for w in master["work_items"]
for t in w.get("tables", [])
if str(t.get("form_basis", "")).startswith("건설 꼴")
]
assert reached == [], f"산림 표가 건설 층까지 내려감: {reached[:5]}"
def test_산림_마스터는_형태_미판정이_없음() -> None:
master = json.loads((MASTER / "work_item_master_2026-01-01.json").read_text(encoding="utf-8"))
assert master["stats"]["form_undetermined"] == 0
# ── 건설 꼴 가름 ─────────────────────────────────────────────────────────
def test_기계_손료표는_계수() -> None:
got = judged_const_form(
table(
["분류 번호", "규격 (ton)", "내용 시간", "상각 비율", "시 간 당(10-7)"],
[["0101-0007", "7", "12,000", "0.9", "1,811"]],
)
)
assert got and got[0] == "coefficient"
@pytest.mark.parametrize(
"headers, rows",
[
(
["사용기간별 구 분", "3개월 (%)", "2개년초과 평균손율 (%)"],
[["철 물", "30", "85"]],
), # 손율
(["지형종류", "시가지"], [["계수", "0.58"]]), # 보정계수
(["설치간격", "0.6m이하"], [["요 율", "17%"]]), # 요율
],
)
def test_율_계수표는_계수(headers: list[str], rows: list[list[str]]) -> None:
got = judged_const_form(table(headers, rows))
assert got and got[0] == "coefficient"
def test_값이_없는_설명표는_참조() -> None:
got = judged_const_form(
table(["노 임 구 분", "산 정 식"], [["노임합계", "P+PO"], ["기본노임", "P"]])
)
assert got == ("reference", "값(숫자) 칸이 없음 — 설명·서식 표")
def test_건설_직종이_있으면_소요량() -> None:
got = judged_const_form(
table(["구 분", "특급 기술자", "고급 기술자"], [["작업계획", "0.5", "1.1"]])
)
assert got and got[0] == "requirement"
def test_비고의_산출식은_계수로_안_뒤집음() -> None:
"""인력품 표의 비고에 산출식이 있어도 **품 표**다(9-5-4 수치지도)."""
got = judged_const_form(
table(
["구 분", "고급 기술자", "비 고"],
[["자동독취", "0.016인", "4매×20분/60분/8시간=0.166일"]],
)
)
assert got and got[0] == "requirement"
def test_단위_칸과_값이_한_줄이면_소요량() -> None:
got = judged_const_form(table(["구 분", "단 위", "브라켓형"], [["비 계 공", "인", "1.40"]]))
assert got and got[0] == "requirement"
def test_줄_이름이_공식_기호뿐이면_계수() -> None:
got = judged_const_form(table(["구 분", "R.C.D", "올케이싱"], [["β", "1.14", "1.08"]]))
assert got and got[0] == "coefficient"
def test_못_가리면_미판정으로_남김() -> None:
"""지역·월별 숫자만 있는 표 — 무엇의 값인지 원문으로 못 가름."""
assert judged_const_form(table(["지역별", "1월", "2월"], [["춘천", "7", "5"]])) is None
assert detect_form(table(["지역별", "1월"], [["춘천", "7"]]), None)[0] == "undetermined"
# ── 밑수 읽기 ────────────────────────────────────────────────────────────
@pytest.mark.parametrize(
"line, want",
[
("1-2-1 용접접합('93년 보완)(용접개소당)", (1.0, "개소")),
("7-1-5 카운터형 세면기 설치(분리형)('26년 보완)(세면기 개당)", (1.0, "개")),
("- 1. 이중보온관 부설(m당 : 관길이기준)", (1.0, "m")),
("1-3-10 플륨관 해체('22년 보완)(일당)", (1.0, "일")),
("6-8-1 주철제 게이트 제수밸브 부설 및 접합('23년 보완)(기당)", (1.0, "기")),
("1-3-1 데크플레이트 가스절단('18년 보완)(절단길이 10m당)", (10.0, "m")),
("### 12-19. 강관비계 (단위: 공㎥당)", (1.0, "공㎥")),
("### 6-2-1. 둘레베기 (1ha당, 100본당)", (None, None)), # 밑수가 둘 — 안 고름
("### 10-4. 중기운반 (단위: 회당)", (None, None)), # 「회」는 밑수 단위가 아님
],
)
def test_건설_꼴_밑수_읽기(line: str, want: tuple) -> None:
assert basis_from_source([line, "", "| 머리 |"], 3) == want
def test_작업조_표에는_일_밑수를_안_붙임() -> None:
"""원문이 `(일당)` 이라도 작업조 표면 밑수를 비움 — 밑수는 시공량 열."""
crew = table(
["구 분", "단 위", "수 량", "시 공 량 (㎡)"],
[["형틀목공 보통인부", "인 인", "4 1", "25"]],
)
assert crew_table(crew)
lines = ["### 12-38-3 설치 및 해체 (일당)", "", "| 머리 |"]
entry = table_entry(crew, "12-38-3", "12-38-3", "12", lines, None, new_report())
assert (entry["basis_quantity"], entry["basis_unit"]) == (None, None)
plain = table(["구 분", "단 위", "수 량"], [["보통인부", "인", "1.4"]])
entry = table_entry(plain, "2-1", "2-1", "2", lines, None, new_report())
assert (entry["basis_quantity"], entry["basis_unit"]) == (1.0, "일")