브레인 일감(마지막 병목). 코덱스가 8장 md 를 손질(뭉친 손료표 되살리기 · 8-4 운전경비 줄마다 풀기)했는데
자료가 앞 판 그대로였음. 품셈 벌(`_build_pum.py`)과 같은 병 — 뽑는 길이 저장소에 없었음.
- `resources/data_cost_input_value/_build_mach.py` — 절은 글(`8-3 기계손료`…)로, 자리는 **기계 코드**로 잡음
· 한 칸에 코드가 여럿인 줄은 토막 수가 맞을 때만 읽고, 안 맞으면 지어내지 않고 알림·`parse_audit` 셈으로
· 2단 머리(「시 간 당(10-7)」 + 상각비·정비비·관리비 계수) 그대로 읽음 · 규격 열이 둘·셋인 표(5202·5203)도 제자리
· 값 안에 공백이 있는 규격 칸은 닫는 괄호로 가름(진동기 「전기식 …(0.75㎾) 엔진식 …(2.6㎾)」)
· 사람 판단은 옛 벌에서 이어받음 — 조종원 배정 121 · 암석 보정 · 손으로 맞춘 연료 13 · 손질 사유 8 ·
머리글 없는 기종 이름 7
- 옛 md(177d8844)로 돌리면 **자료의 숫자·규격과 어긋나는 자리 0** · 운전경비 원문 표 21개의 절까지 그대로
- 다시 뽑음 — 가격 613(값 그대로 · 뭉개진 이름 30 정리) · 손료 387 → 611(되살아난 표에서 221 코드) ·
연료 214(코드 그대로)
- ⭐ 금액 불변 — 직접공사비 5,983,724 · 합계 10,124,000 그대로(원가계산서 열세 칸 전부)
- ⬜ **연료 197 ↔ 214 밝힘 — 갈린 것이 정상임**. 원문 8-4 에 적힌 코드 303 =
값이 ℓ/hr 인 201 + 사람이 맞춘 13(`parse_method`) + **주연료가 수가 아닌 21**(전기 `㎾`·`-` — 연료 벌에 안 실음) +
한 칸에 뭉쳐 토막이 안 맞는 3줄 81. 곧 214 는 맞는 수이고, 197 은 대조 시험이 더 좁게 읽던 수였음
- `test_build_mach.py` — 자료가 md 와 같음 · 줄이 밀려도 값 그대로 · 뭉친 줄과 풀린 줄이 같은 값 ·
2단 머리 읽기 · 토막 안 맞으면 안 지어냄 · 사람 손질 이어받음 · 사라진 코드 알림
- 곁 — 기초단가 대조 목록 기계 어긋남 127 → 3 · 이름표 손료 줄 수 387 → 611 · 코덱스 8-4 손질로 건설 품셈 벌
4표가 바뀌어 `_build_pum.py` 로 다시 뽑음(⬜ 건설 공종 마스터 다시 짓기는 데스크탑 서브 몫)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
145 lines
6.5 KiB
Python
145 lines
6.5 KiB
Python
"""기계 원천 벌 뽑기(`_build_mach.py`) 시험 — 2026-09-18 브레인 일감(랩탑 메인).
|
|
|
|
못박는 것
|
|
- 기계 자료가 **지금 8장 md 와 같음** — md 가 고쳐졌는데 안 뽑았으면 빨강(뽑기를 돌리라는 뜻).
|
|
- ⚠ **줄이 밀려도 값이 그대로** — 자리는 줄 번호가 아니라 **기계 코드**로 잡음.
|
|
- 한 칸에 뭉친 줄(`0101-0007 0010 …`)과 줄마다 푼 판이 **같은 값**을 냄(코덱스 8-4 손질이 그 꼴).
|
|
- 토막 수가 안 맞으면 **지어내지 않고** `parse_audit` 에 적음.
|
|
- 사람이 맞춘 줄(`parse_method`)·손질 사유(`note`)는 **옛 벌에서 이어받음**.
|
|
금액 불변 — 뽑은 뒤 `test_work_item_key_gate.py` 원가계산서 시험(직접공사비 5,983,724)이 잼.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT / "resources" / "data_cost_input_value"))
|
|
|
|
import _build_mach as build # noqa: E402
|
|
|
|
GLUED = """
|
|
8-3 기계손료(0101) 불도저(무한궤도)
|
|
|
|
| 분류 번호 | 규격 (ton) | 내용 시간 | 연간표준 가동시간 | 상각 비율 | 정비 비율 | 연간 관리 비율 | 시 간 당(10-7) | | | |
|
|
|---|---|---|---|---|---|---|---|---|---|---|
|
|
| | | | | | | | 상각비 계수 | 정비비 계수 | 관리비 계수 | 계 |
|
|
| 0101-0007 | 7 | 12,000 | 1,250 | 0.9 | 0.7 | 0.1 | 750 | 583 | 478 | 1,811 |
|
|
| 0010 | 10 | 12,000 | 1,250 | 0.9 | 0.7 | 0.1 | 750 | 583 | 478 | 1,811 |
|
|
|
|
8-4 운전경비 산정8-4-1 [00]토공기계
|
|
|
|
| 분류번호 | 기계명 | 규격 | 주연료 (ℓ/hr) | 잡재료 (주연료의%) | 조종원 (인/일) |
|
|
|---|---|---|---|---|---|
|
|
| 0101-0007 0010 | 불 도 저 | 7ton 10 | 9.0 12.5 | 16% 16 | 1 1 |
|
|
|
|
8-5 기계가격8-5-1 [00]토공기계
|
|
|
|
| 기 종 | 분류번호 | 가격(₩) |
|
|
|---|---|---|
|
|
| 불 도 저 ( 무 한 궤 도 ) | 0101-0007 0010 | 76,547 168,668 |
|
|
"""
|
|
UNGLUED = GLUED.replace(
|
|
"| 0101-0007 0010 | 불 도 저 | 7ton 10 | 9.0 12.5 | 16% 16 | 1 1 |",
|
|
"| 0101-0007 | 불도저(무한궤도) | 7ton | 9.0 | 16% | 1 |\n| 0010 | | 10 | 12.5 | 16 | 1 |",
|
|
)
|
|
BARE = {
|
|
"sources": [{"path": "x.md", "sha256": "", "role": "primary"}],
|
|
"variables": {
|
|
"mach_price": {"unit": "thousand_KRW", "key": "machine_code", "records": []},
|
|
"mach_loss_coef": {"unit": "1/hour", "key": "machine_code", "records": []},
|
|
"mach_fuel_rate": {"unit": "L/hour", "key": "machine_code", "parsed_records": [], "source_tables": []},
|
|
},
|
|
} # fmt: skip
|
|
|
|
|
|
def _of(doc: dict, key: str, field: str = "records") -> dict:
|
|
return {r["machine_code"]: r for r in doc["variables"][key][field]}
|
|
|
|
|
|
def _extract(text: str, old: dict | None = None) -> dict:
|
|
return build.extract(json.loads(json.dumps(old or BARE)), text.encode("utf-8"))[0]
|
|
|
|
|
|
def test_기계_자료가_지금_md_와_같음() -> None:
|
|
"""md 가 고쳐졌으면 `_build_mach.py` 를 돌릴 것."""
|
|
old = json.loads(build.TARGET.read_text(encoding="utf-8"))
|
|
doc, report = build.extract(old, (ROOT / old["sources"][0]["path"]).read_bytes())
|
|
diff = build.diff_report(old, doc)
|
|
stale = {k: {n: len(v) for n, v in item.items() if v} for k, item in diff.items() if any(item.values())} # fmt: skip
|
|
assert not stale, f"기계 자료가 md 와 다름 — _build_mach.py 돌릴 것: {stale}"
|
|
|
|
|
|
def test_줄이_밀려도_값이_그대로() -> None:
|
|
doc = _extract(GLUED)
|
|
shifted = _extract(GLUED.replace("8-4 운전경비", "보탠 줄\n\n보탠 줄\n\n8-4 운전경비"))
|
|
for key, field in (("mach_price", "records"), ("mach_loss_coef", "records"), ("mach_fuel_rate", "parsed_records")): # fmt: skip
|
|
assert _of(doc, key, field) == _of(shifted, key, field)
|
|
|
|
|
|
def test_뭉친_줄과_풀린_줄이_같은_값() -> None:
|
|
"""코덱스가 8-4 를 줄마다 풀어 놓아도 값이 같아야 함 — 이름만 원문 표기를 따름."""
|
|
glued, unglued = _of(_extract(GLUED), "mach_fuel_rate", "parsed_records"), _of(_extract(UNGLUED), "mach_fuel_rate", "parsed_records") # fmt: skip
|
|
assert set(glued) == set(unglued) == {"0101-0007", "0101-0010"}
|
|
for code in glued:
|
|
assert {k: v for k, v in glued[code].items() if k != "machine_name"} == {
|
|
k: v for k, v in unglued[code].items() if k != "machine_name"
|
|
}
|
|
assert glued["0101-0010"]["fuel_rate_l_per_hour"] == 12.5
|
|
|
|
|
|
def test_손료_2단_머리를_읽고_계수를_시간당으로() -> None:
|
|
loss = _of(_extract(GLUED), "mach_loss_coef")["0101-0007"]
|
|
assert loss["source_coefficient_1e_minus_7"] == 1811
|
|
assert loss["loss_coefficient_per_hour"] == 1811 * 1e-7
|
|
assert (loss["economic_life_hours"], loss["depreciation_ratio"], loss["specification"]) == (
|
|
12000,
|
|
0.9,
|
|
"7",
|
|
)
|
|
assert loss["machine_name"] == "불도저(무한궤도)"
|
|
|
|
|
|
def test_토막_수가_안_맞으면_지어내지_않고_적어_둠() -> None:
|
|
broken = GLUED.replace("| 9.0 12.5 |", "| 9.0 |")
|
|
doc, report = build.extract(json.loads(json.dumps(BARE)), broken.encode("utf-8"))
|
|
assert "0101-0007" not in _of(doc, "mach_fuel_rate", "parsed_records")
|
|
assert doc["parse_audit"]["unparsed_fuel_rows"] == 1 # 자료엔 셈만
|
|
assert report["unparsed_rows"]["fuel"] == [
|
|
{"what": "0101-0007 0010", "codes": 2}
|
|
] # 자세한 것은 알림에
|
|
|
|
|
|
def test_사람이_맞춘_줄과_사유는_이어받음() -> None:
|
|
old = json.loads(json.dumps(BARE))
|
|
old["variables"]["mach_fuel_rate"]["parsed_records"] = [
|
|
{
|
|
"machine_code": "7505-0025",
|
|
"machine_name": "발전기",
|
|
"fuel_type": "diesel",
|
|
"fuel_rate_l_per_hour": 4.3,
|
|
"parse_method": "verified_source_row_alignment",
|
|
} # fmt: skip
|
|
]
|
|
old["variables"]["mach_loss_coef"]["records"] = [
|
|
{
|
|
"machine_code": "0101-0007",
|
|
"machine_name": "불도저(무한궤도)",
|
|
"note": "칸 밀림 바로잡음",
|
|
}
|
|
]
|
|
doc = _extract(GLUED, old)
|
|
assert _of(doc, "mach_fuel_rate", "parsed_records")["7505-0025"]["parse_method"] == "verified_source_row_alignment" # fmt: skip
|
|
assert _of(doc, "mach_loss_coef")["0101-0007"]["note"] == "칸 밀림 바로잡음"
|
|
|
|
|
|
def test_사라진_코드는_알림에() -> None:
|
|
old = json.loads(json.dumps(BARE))
|
|
old["variables"]["mach_price"]["records"] = [
|
|
{"machine_code": "9999-0001", "machine_name": "옛 기계", "price_thousand_krw": 1}
|
|
]
|
|
doc = _extract(GLUED, old)
|
|
assert build.diff_report(old, doc)["mach_price"]["사라진 코드"] == ["9999-0001"]
|