feat(axis): 마스터 갈래 키를 표 모양대로 — 단가표 갈래 제목 294 중 294 가 키에 있음(종전 86)
종전 variant_key 는 표 첫 열 값 24개라 머리행 갈래·두 단 머리를 놓치고 첫 열이 자원 이름인 표를 갈래로 적었음. 표 모양 가르기는 B09 표 읽기에 이미 있어 두 벌을 안 짜고, 그 읽기를 메모리의 마스터에 돌려 표마다 가른 갈래를 원문 표기로 실음. 못 가른 표는 빈칸. 공종마다 variant_keys(불도저 공식 갈래·합산형 단계 암질·[주]① 평균 · 부모는 단계 갈래). 짝 시험 — 단가표 #갈래 제목이 전부 마스터 키에 있어야 함. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
@@ -566,16 +566,6 @@ def capacity_formula_pending(table: dict[str, Any]) -> bool:
|
||||
return bool((keys | cells) & CAPACITY_SYMBOLS)
|
||||
|
||||
|
||||
def variant_axis(table: dict[str, Any]) -> list[str]:
|
||||
"""행이 갈리는 축 — 표의 첫 열 값들(토질·암종·규격). 값 열은 뺀다."""
|
||||
seen: list[str] = []
|
||||
for row in table.get("rows", []):
|
||||
key = norm(row[0]) if row else ""
|
||||
if key and key not in seen:
|
||||
seen.append(key)
|
||||
return seen[:24]
|
||||
|
||||
|
||||
def basis_quantity_is_grouped(quantity: float | None) -> bool:
|
||||
"""「10㎡당」처럼 **묶음 기준**인가. 1 이 아니면 곱셈이 그만큼 갈린다."""
|
||||
return quantity is not None and abs(quantity - 1.0) > 1e-9
|
||||
@@ -678,7 +668,8 @@ def build() -> dict[str, Any]:
|
||||
"special_glyphs": special_glyphs(table),
|
||||
# 공식 기호가 이 표에 직접 있는가 — 없으면 앞 표에서 물려받는 모양이다.
|
||||
"capacity_formula_here": capacity_formula_pending(table),
|
||||
"variant_key": variant_axis(table),
|
||||
# 갈래 키 — 부모 모양을 단 뒤 표 읽기가 가른 대로 채움(`_Variants`). 못 가르면 빈칸.
|
||||
"variant_key": [],
|
||||
"condition_note": [norm(h) for h in table.get("headers", []) if norm(h)],
|
||||
"raw_row": table.get("rows", []), # 원문 셀 — B09 자원 축이 읽는다.
|
||||
}
|
||||
@@ -703,6 +694,10 @@ def build() -> dict[str, Any]:
|
||||
from B08_Quantity.B08_Quantity_Build_WorkItemMaster_Parents import apply_parent_modes
|
||||
|
||||
apply_parent_modes(nodes)
|
||||
# 갈래 키(명세 14장 정정) — 표 모양 가르기는 B09 표 읽기 한 벌을 그대로 돌려 씀.
|
||||
from B08_Quantity.B08_Quantity_Build_WorkItemMaster_Variants import attach_variant_keys
|
||||
|
||||
attach_variant_keys(nodes, data["effective_date"])
|
||||
|
||||
src_meta = {
|
||||
"dataset_id": data["dataset_id"],
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""공종 마스터 — **갈래 키(`variant_key`)를 표 읽기가 실제로 가른 대로** 싣는다 (명세 14장 정정).
|
||||
|
||||
종전 `variant_axis` 는 표 첫 열 값 24개만 담아, 단가표 갈래 제목 294 중 86 만 맞았다
|
||||
(2026-09-13 브레인 실측). 머리행 갈래(벌목 「5m이상~8m미만」) · 두 단 머리 · 첫 열이 자원
|
||||
이름인 표를 못 가렸다.
|
||||
|
||||
⚠ 표 모양을 여기서 **다시 판정하지 않는다** — 첫 열 · 열이 자원 · 뭉친 이름+갈래 열 · 축 셋 ·
|
||||
두 단 머리 · 작업조 · 공정 합산 · 고르기형 등 모양 가르기는 B09 표 읽기에 이미 있다.
|
||||
두 벌로 짜면 한쪽만 고쳐진다. 그 읽기를 **메모리의 이 마스터**에 돌려 표마다 가른 갈래
|
||||
이름을 원문 표기 그대로 싣는다(키 비교는 받는 쪽이 `normalize_variant_key` 로).
|
||||
⚠ 못 가른 표는 **빈칸** — 자원을 못 맞춘 표 · 계수 · 참조 · 미판정 표에 갈래를 지어내지 않는다.
|
||||
|
||||
공종마다 `variant_keys` — 표들의 갈래 + 원문이 정했으나 자원 줄로 안 서는 갈래
|
||||
(불도저 운반 공식 갈래 · 합산형 단계 잎의 암질 행 · 9-4-1 [주]① 평균) · 합산형 부모는 단계 갈래.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _add(labels: list[str], seen: set[str], label: str, normalize: Any) -> None:
|
||||
key = normalize(label)
|
||||
if key and key not in seen:
|
||||
seen.add(key)
|
||||
labels.append(label)
|
||||
|
||||
|
||||
def attach_variant_keys(nodes: list[dict[str, Any]], edition: str) -> None:
|
||||
"""표마다 `variant_key`, 공종마다 `variant_keys` 를 채운다(자리에서)."""
|
||||
from B09_Estimation.B09_Estimation_MachineProductivity_Dozer import extract_dozer_factors
|
||||
from B09_Estimation.B09_Estimation_ParentSteps import AVERAGE_BASIS, AVERAGE_VARIANT, rock_rows
|
||||
from B09_Estimation.B09_Estimation_ResourceAxis import build_resource_axis
|
||||
from B09_Estimation.B09_Estimation_ResourceAxis_Sources import load_combined_catalog
|
||||
from B09_Estimation.B09_Estimation_UnitPrice import normalize_variant_key as normalize
|
||||
|
||||
axis = build_resource_axis(
|
||||
{"effective_date": edition, "work_items": nodes}, load_combined_catalog()
|
||||
)
|
||||
by_table: dict[tuple[str, str], tuple[list[str], set[str]]] = {}
|
||||
for row in axis.rows:
|
||||
if row.variant:
|
||||
labels, seen = by_table.setdefault((row.work_item_code, row.pum_table_id), ([], set()))
|
||||
_add(labels, seen, row.variant, normalize)
|
||||
|
||||
steps = {
|
||||
str(step["code"])
|
||||
for node in nodes
|
||||
if node.get("parent_mode") == "sum_steps"
|
||||
for step in node.get("steps") or []
|
||||
}
|
||||
by_code: dict[str, list[str]] = {}
|
||||
for node in nodes:
|
||||
code = str(node.get("work_item_code") or "")
|
||||
keys: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for table in node.get("tables") or []:
|
||||
labels, table_seen = by_table.get((code, str(table.get("pum_table_id"))), ([], set()))
|
||||
found = extract_dozer_factors(code, table)
|
||||
for label in found if isinstance(found, dict) else {}:
|
||||
_add(labels, table_seen, label, normalize)
|
||||
if code in steps:
|
||||
for rock, _, _ in rock_rows({"tables": [table]}):
|
||||
_add(labels, table_seen, rock, normalize)
|
||||
table["variant_key"] = labels
|
||||
for label in labels:
|
||||
_add(keys, seen, label, normalize)
|
||||
if code in AVERAGE_BASIS and keys:
|
||||
_add(keys, seen, AVERAGE_VARIANT, normalize)
|
||||
by_code[code] = keys
|
||||
for node in nodes:
|
||||
code = str(node.get("work_item_code") or "")
|
||||
keys = list(by_code.get(code) or [])
|
||||
if node.get("parent_mode") == "sum_steps":
|
||||
seen = {normalize(k) for k in keys}
|
||||
for step in node.get("steps") or []:
|
||||
for label in by_code.get(str(step["code"])) or []:
|
||||
_add(keys, seen, label, normalize)
|
||||
node["variant_keys"] = keys
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"dataset_id": "data_work_item_master_manifest",
|
||||
"generated_at": "2026-09-13T22:33:42+09:00",
|
||||
"generated_at": "2026-09-13T23:03:27+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": "4e7790cd9d288e6026e7ccd81ce0c81320b395c84575655ff2dce443f44d65a4",
|
||||
"size_bytes": 862887
|
||||
"sha256": "1463ea14ec2d3d45ec18b3a7e4810333de8e5fc40aaeb0dd329c68f85c863e33",
|
||||
"size_bytes": 835659
|
||||
},
|
||||
{
|
||||
"file": "form_undetermined_2026-01-01.json",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
"""공종 마스터 갈래 키 `variant_key` — 표 읽기가 가른 대로 (명세 14장 정정 · 2026-09-13).
|
||||
|
||||
지키는 것
|
||||
① 짝 시험 — 단가표의 `B-<코드>#<갈래>` 제목은 **전부** 그 공종의 마스터 갈래 키에 있다
|
||||
(종전 첫 열 24개 방식은 294 중 86). 표 읽기를 고치고 마스터를 안 다시 뽑으면 여기서 걸림
|
||||
② 머리행 갈래(벌목)·뭉친 이름 표(찰쌓기 장비)는 머리행에서 · 첫 열이 자원 이름인 표는 빈칸
|
||||
③ 합산형 부모는 단계 갈래를 물려받음(흙깎기 리핑암 9-4 = 암질 + [주]① 평균)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from B09_Estimation.B09_Estimation_ResourceAxis import load_work_item_master
|
||||
from B09_Estimation.B09_Estimation_UnitPrice import cached_build, normalize_variant_key
|
||||
|
||||
|
||||
def _nodes() -> dict[str, dict]:
|
||||
return {node["work_item_code"]: node for node in load_work_item_master()["work_items"]}
|
||||
|
||||
|
||||
def test_단가표_갈래_제목은_전부_마스터_갈래_키에_있다() -> None:
|
||||
nodes = _nodes()
|
||||
missing = []
|
||||
for code in cached_build().book.titles:
|
||||
if not code.startswith("B-") or "#" not in code:
|
||||
continue
|
||||
work_item, variant = code[2:].split("#", 1)
|
||||
keys = {
|
||||
normalize_variant_key(k) for k in nodes.get(work_item, {}).get("variant_keys") or []
|
||||
}
|
||||
if variant not in keys:
|
||||
missing.append(code)
|
||||
assert not missing, f"마스터를 다시 뽑을 것(B08_Quantity_Build_WorkItemMaster): {missing[:5]}"
|
||||
|
||||
|
||||
def test_표_모양대로_갈래를_가르고_못_가르면_빈칸() -> None:
|
||||
nodes = _nodes()
|
||||
felling = nodes["FP-04-02-02"]["tables"][0]["variant_key"]
|
||||
assert "5m이상~8m미만" in felling # 머리행 갈래 — 첫 열(자원 이름)이 아님
|
||||
assert nodes["FP-13-04-05"]["tables"][0]["variant_key"] == [
|
||||
"35cm 이하",
|
||||
"55cm 이하",
|
||||
"75cm 이하",
|
||||
]
|
||||
finishing = next(t for t in nodes["FP-12-02"]["tables"] if t["pum_table_id"] == "F0334")
|
||||
assert finishing["variant_key"] == [] # 첫 열이 자원 이름(미장공)인 표
|
||||
|
||||
|
||||
def test_합산형_부모는_단계_갈래를_물려받는다() -> None:
|
||||
assert _nodes()["FP-09-04"]["variant_keys"] == ["연암", "보통암", "경암", "평균"]
|
||||
Reference in New Issue
Block a user