Merge remote-tracking branch 'origin/dev' into sub_laptop_1

This commit is contained in:
2026-09-15 00:16:45 +09:00
9 changed files with 313 additions and 15 deletions
@@ -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·소림",
@@ -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",
@@ -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": "본문",
@@ -0,0 +1,83 @@
"""봉상후렉시블 셋 + 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:
"""손 사유로 달던 줄 목록은 판정표 자동 목록(`_unread_rows`)으로 옮김 — `test_b09_judged_unread_rows`."""
left = " ".join(cached_build().unattached.get("FP-12-16", []))
assert "원형거푸집" in left and "설치비" in left, left
@@ -0,0 +1,50 @@
"""판정표가 안 읽은 줄 — 자동으로 「못 붙은 줄」 목록에 (2026-09-14 브레인 · ㉰ 앞 뿌리 고침).
판정표는 적어 둔 줄만 읽음 → 나머지 줄(거푸집·철근·뚜껑·설치비·비고 …)이 사유 없이 사라지고 있었음
(12-15 · 12-04 비고 · 12-34-1 레미콘). 표를 넣을 때마다 손으로 사유를 달면 다음 표가 또 샘.
⇒ 판정표 공통 한 곳에서 「읽힌 줄 밖의 줄」 을 올림.
㉠ 넘치지 않게 — 자원 머리(첫 줄 머리 모양) · 빈 줄 · 다짐기처럼 다른 줄이 쓴 줄은 뺌
㉡ 손 사유와 안 겹치게 — 공종 사유 한 줄(`known_gap_note`)이 이미 적은 이름은 안 올림
"""
from __future__ import annotations
from B09_Estimation.B09_Estimation_KnownGaps import known_gap_note
from B09_Estimation.B09_Estimation_UnitPrice import cached_build
def _left(build, code: str) -> str:
return " / ".join(build.unattached.get(code, []))
def test_집수정_표의_거푸집_철근_뚜껑_설치비가_목록에_뜸() -> None:
left = _left(cached_build(), "FP-12-15")
for name in ("거 푸 집", "철근", "집수정 뚜껑", "설치비"):
assert name in left, (name, left)
assert "봉상후렉시블" not in left, left # 구체 갈래가 쓴 다짐기 줄은 안 올림(㉠)
def test_합판거푸집_비고는_글과_함께_뜨고_손_사유에_있는_사용고재는_안_겹침() -> None:
left = _left(cached_build(), "FP-12-04")
assert "비고" in left and "7m" in left, left
assert "사용고재" in known_gap_note("FP-12-04") and "사용고재" not in left, left # ㉡
assert "횟수별" not in left, left # 머리 줄(㉠)
def test_토사면_고르기_자원_머리_줄은_안_뜸() -> None:
left = _left(cached_build(), "FP-09-19-01")
assert "보통인부 (인)" not in left and "공기압축기 (시간)" not in left, left
def test_12_34_1_레미콘_별도계상_줄은_이름으로_뜸() -> None:
left = _left(cached_build(), "FP-12-34-01")
assert "콘크리트(레미콘)" in left and "자재" not in left.split(" / "), left
def test_날개벽_면벽_맨홀은_자동_목록이_맡고_손_사유는_줄_목록을_안_되풀이() -> None:
build = cached_build()
assert "원형거푸집" in _left(build, "FP-12-16") and "기초잡석" in _left(build, "FP-12-12")
assert "기초잡석" in _left(build, "FP-12-13")
for code in ("FP-12-12", "FP-12-13", "FP-12-16"):
assert "거푸집" not in known_gap_note(code), code
assert "30%" in known_gap_note("FP-12-12") # 줄이 아닌 [주] 는 손 사유로 남음