Merge remote-tracking branch 'origin/dev' into sub_laptop_1
This commit is contained in:
@@ -264,7 +264,6 @@ def finish(
|
||||
# 불변 열쇠(`FW-00001`) — 목차 번호를 열쇠에서 칸으로 내린다(2026-09-17 브레인 ①).
|
||||
# ⚠ 장부에 없는 목차 코드는 **새 열쇠**를 받는다. 짐작으로 옛 열쇠를 물려주지 않는다.
|
||||
from B08_Quantity.B08_Quantity_Build_WorkItemMaster_Keys import (
|
||||
GENERAL_PROVISION_ROOTS,
|
||||
assign_keys,
|
||||
decorate,
|
||||
load_registry,
|
||||
@@ -274,8 +273,7 @@ def finish(
|
||||
edition = toc_edition(data.get("sources"), data["effective_date"])
|
||||
registry = load_registry(registry_path, key_prefix)
|
||||
newly_issued, toc_duplicates = assign_keys(nodes, registry, edition=edition)
|
||||
roots = GENERAL_PROVISION_ROOTS if general_roots is None else general_roots
|
||||
decorate(nodes, edition=edition, general_roots=roots)
|
||||
decorate(nodes, edition=edition, roots=general_roots) # None = 산림 잣대(`axis_policy.json`)
|
||||
role_counts: dict[str, int] = {}
|
||||
for node in nodes:
|
||||
role_counts[node["axis_role"]] = role_counts.get(node["axis_role"], 0) + 1
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
못 씀 → 표가 놓인 원문 줄에서 위로 거슬러 **번호 뒤 글이 목차 이름과 같은** 제목을 찾음.
|
||||
장 파일 경계가 쪽 기준이라(원문 머리 경고) 앞뒤 장 제목도 받고, 파일 첫머리 표는 앞 장 파일 꼬리까지 봄.
|
||||
⚠ 못 붙인 표는 조용히 버리지 않고 `orphan_tables` 목록.
|
||||
⚠ 총칙·묶는 마디 덜어내기 안 함 — 잣대는 데스크탑 서브. 총칙 표시도 비움(`axis_role` 은 공종·묶음·빈 잎).
|
||||
⚠ 총칙·묶는 마디 덜어내기 안 함(가름만) — 총칙 뿌리는 `axis_policy.json` 건설 축(데스크탑 서브 잣대) · 미확정 둘은 안 넣음.
|
||||
⚠ 갈래 키 안 붙임 — B09 표 읽기가 산림 표로만 검증됨(빈칸 = 못 가름).
|
||||
"""
|
||||
|
||||
@@ -218,6 +218,20 @@ class SectionFinder:
|
||||
return None
|
||||
|
||||
|
||||
def general_roots() -> tuple[str, ...]:
|
||||
"""총칙 뿌리 — `axis_policy.json` 건설 축의 장 파일 이름을 목차 코드로(「01_공통부문/제1장_적용기준.md」 → `CP-01-01`).
|
||||
|
||||
⚠ 미확정(`general_provision_pending` · 제8장 건설기계 · 유지관리 제1장)은 안 넣음 — 판정이 오면 자료만 고치면 됨.
|
||||
"""
|
||||
from B08_Quantity.B08_Quantity_Build_WorkItemMaster_Keys import general_provision_roots
|
||||
|
||||
roots = []
|
||||
for chapter_file in general_provision_roots("const"):
|
||||
division, chapter = file_place(chapter_file)
|
||||
roots.append(_code(f"{CODE_PREFIX}-{DIVISIONS.index(division) + 1:02d}", str(chapter)))
|
||||
return tuple(roots)
|
||||
|
||||
|
||||
def book_lines(data: dict[str, Any]) -> list[str]:
|
||||
folder = (ROOT / str(data["sources"][0]["path"])).parents[1]
|
||||
return (folder / BOOK_NAME).read_text(encoding="utf-8").splitlines()
|
||||
@@ -284,12 +298,12 @@ def build_const() -> tuple:
|
||||
registry_path=CONST_KEY_REGISTRY,
|
||||
toc_corrections=[],
|
||||
key_prefix=KEY_PREFIX,
|
||||
general_roots=(), # 총칙 잣대는 데스크탑 서브 몫 — 오기 전까지 표시 안 함
|
||||
general_roots=general_roots(),
|
||||
policy={
|
||||
"divisions": list(DIVISIONS),
|
||||
"toc_source": BOOK_NAME + " 머리 「목 차」",
|
||||
"section_from_body": True,
|
||||
"variant_keys_attached": False,
|
||||
"general_provision_marked": False,
|
||||
"general_provision_roots": list(general_roots()),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -29,8 +29,27 @@ KEY_PREFIX = (
|
||||
KEY_DIGITS = 5
|
||||
PATH_SEP = " › "
|
||||
|
||||
#: 총칙 — 값표는 붙어 있으나 공종이 아닌 장. 뿌리 코드로 가른다.
|
||||
GENERAL_PROVISION_ROOTS = ("FP-01", "FP-02")
|
||||
#: 줄 구실 잣대는 **자료에 둔다** — 총칙 범위는 기관이 정하는 것이라 코드에 박으면 관리자가 못 고친다.
|
||||
AXIS_POLICY = (
|
||||
Path(__file__).resolve().parent.parent
|
||||
/ "resources"
|
||||
/ "data_work_item_master"
|
||||
/ "axis_policy.json"
|
||||
)
|
||||
|
||||
|
||||
def load_policy(axis: str, path: Path = AXIS_POLICY) -> dict[str, Any]:
|
||||
"""`axis_policy.json` 의 한 축(`forest`·`const`). 없는 축을 부르면 멈춘다."""
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
try:
|
||||
return data["axes"][axis]
|
||||
except KeyError:
|
||||
raise ValueError(f"공종 축 잣대에 `{axis}` 가 없다 — {path}") from None
|
||||
|
||||
|
||||
def general_provision_roots(axis: str = "forest", path: Path = AXIS_POLICY) -> tuple[str, ...]:
|
||||
"""총칙 뿌리 — 값표는 붙어 있으나 공종이 아닌 장. ⚠ 지우지 않고 구실로만 가른다."""
|
||||
return tuple(load_policy(axis, path).get("general_provision_roots") or ())
|
||||
|
||||
|
||||
#: 원문 경로에 박힌 고시 번호 — 「(산림청고시 제2025-82호)」.
|
||||
@@ -176,20 +195,21 @@ def path_names(nodes: list[dict[str, Any]]) -> list[str]:
|
||||
|
||||
|
||||
def axis_role(
|
||||
node: dict[str, Any],
|
||||
*,
|
||||
has_children: bool,
|
||||
general_roots: tuple[str, ...] = GENERAL_PROVISION_ROOTS,
|
||||
node: dict[str, Any], *, has_children: bool, roots: tuple[str, ...] | None = None
|
||||
) -> str:
|
||||
"""이 줄이 공종인가 문서 구역인가.
|
||||
"""이 줄이 공종인가 문서 구역인가. 차례가 뜻을 가진다(총칙 먼저).
|
||||
|
||||
`work_item` 품셈 표가 붙은 줄 — 값이 있는 자리
|
||||
`group` 표는 없고 아래를 묶는 마디 — `parent_mode` 가 계산 규칙을 지닌다(지우면 안 된다)
|
||||
`general_provision` 총칙(1·2장) — 문서 구역이나 **값표가 붙어 있어 B09 가 읽는다**
|
||||
`general_provision` 총칙 — 문서 구역이나 **값표가 붙어 있어 B09 가 읽는다**
|
||||
`empty` 표도 아래도 없는 잎 — 원문에 표가 안 붙은 자리
|
||||
|
||||
⚠ **어느 구실이든 지우지 않는다.** 가름은 화면·조합이 무엇을 볼지 고르는 것뿐이다.
|
||||
"""
|
||||
if roots is None:
|
||||
roots = general_provision_roots()
|
||||
code = node["work_item_code"]
|
||||
if any(code == root or code.startswith(root + "-") for root in general_roots):
|
||||
if any(code == root or code.startswith(root + "-") for root in roots):
|
||||
return "general_provision"
|
||||
if node.get("tables"):
|
||||
return "work_item"
|
||||
@@ -197,15 +217,14 @@ def axis_role(
|
||||
|
||||
|
||||
def decorate(
|
||||
nodes: list[dict[str, Any]],
|
||||
*,
|
||||
edition: str,
|
||||
general_roots: tuple[str, ...] = GENERAL_PROVISION_ROOTS,
|
||||
nodes: list[dict[str, Any]], *, edition: str, roots: tuple[str, ...] | None = None
|
||||
) -> None:
|
||||
"""각 줄에 목차 칸·경로 이름·구실을 얹는다. 열쇠는 `assign_keys` 가 이미 박았다.
|
||||
|
||||
기존 칸은 손대지 않는다 — 금액을 낳는 값은 그대로다.
|
||||
"""
|
||||
if roots is None:
|
||||
roots = general_provision_roots()
|
||||
paths = path_names(nodes)
|
||||
has_kid = {n.get("parent_code") for n in nodes}
|
||||
for node, path in zip(nodes, paths):
|
||||
@@ -214,6 +233,4 @@ def decorate(
|
||||
node["toc_number"] = node["number"]
|
||||
node["toc_edition"] = edition
|
||||
node["path_name"] = path
|
||||
node["axis_role"] = axis_role(
|
||||
node, has_children=code in has_kid, general_roots=general_roots
|
||||
)
|
||||
node["axis_role"] = axis_role(node, has_children=code in has_kid, roots=roots)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Z01 공종 축 — 산림·건설 두 kind 를 기초단가와 **같은 계약**으로(2026-09-17 브레인 · PLAN_공종축 8-4).
|
||||
|
||||
줄 = **잎**(아래 마디 없고 품셈 표가 붙은 마디) — 일위대가가 붙는 단위. 갈래(`variant_keys`)는 칸으로(편 수 = `stats.units`).
|
||||
줄 = **품셈 표가 붙은 마디**(`axis_policy.json` `flat_list_filter: pum_tables > 0` — 잎으로 거르면 부모에 붙은 표가 안 뜸) ·
|
||||
갈래(`variant_keys`)는 칸으로(편 수 = `stats.units`).
|
||||
이름 = 뿌리부터 전체 경로(「토공 › 토사깍기 › 기계」 · 건설은 부문부터) — 잎 이름만으론 겹침.
|
||||
계산 규칙 = 표 수준 `rules`(부모 마디 choose_one · sum_steps) + 줄 칸 `rule`·`step_weight`
|
||||
— 빠지면 발파암(절취 0.1 + 깍기 0.9 + 집토 1) 금액이 틀어짐.
|
||||
@@ -78,7 +79,7 @@ def base_rows(kind: str) -> list[dict[str, Any]]:
|
||||
items, by, children = _tree(d)
|
||||
rows = []
|
||||
for item in items:
|
||||
if item["work_item_code"] in children or not item.get("tables"):
|
||||
if not item.get("tables"):
|
||||
continue
|
||||
parent = by.get(item.get("parent_code")) or {}
|
||||
weight = next(
|
||||
@@ -144,12 +145,12 @@ def notice(kind: str) -> list[str]:
|
||||
|
||||
|
||||
def extra(kind: str) -> dict[str, Any]:
|
||||
"""표 수준 덧붙임 — 계산 규칙 · 잎 수 · 갈래 편 수(쪽 나누기와 무관하게 전체)."""
|
||||
leaves = base_rows(kind)
|
||||
"""표 수준 덧붙임 — 계산 규칙 · 줄 수 · 갈래 편 수(쪽 나누기와 무관하게 전체)."""
|
||||
rows = base_rows(kind)
|
||||
return {
|
||||
"rules": rules(kind),
|
||||
"stats": {
|
||||
"leaves": len(leaves),
|
||||
"units": sum(max(1, len(r["variant_keys"])) for r in leaves),
|
||||
"work_items": len(rows),
|
||||
"units": sum(max(1, len(r["variant_keys"])) for r in rows),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1366,7 +1366,7 @@
|
||||
"sources": [
|
||||
"work_item_master::work_items"
|
||||
],
|
||||
"rows_note": "잎 360 줄 · 갈래 펴면 620",
|
||||
"rows_note": "표가 붙은 줄 365(잎 360 + 표 붙은 부모 5) · 갈래 펴면 627",
|
||||
"columns": [
|
||||
{
|
||||
"key": "work_item_code",
|
||||
@@ -1429,7 +1429,7 @@
|
||||
"sources": [
|
||||
"work_item_master_const::work_items"
|
||||
],
|
||||
"rows_note": "잎 995 줄 · 갈래 키 아직 없음(편 수 = 잎 수)",
|
||||
"rows_note": "표가 붙은 줄 997(잎 995 + 표 붙은 부모 2) · 갈래 키 아직 없음(편 수 = 줄 수)",
|
||||
"columns": [
|
||||
{
|
||||
"key": "work_item_code",
|
||||
@@ -1826,7 +1826,7 @@
|
||||
"key": [
|
||||
"work_item_key"
|
||||
],
|
||||
"checked": "잎 360 줄 불변 열쇠(FW-) 겹침 0(전수 · `test_z01_work_items`). 목차 코드(FP-)는 칸으로만 — 개정 때 밀림."
|
||||
"checked": "표가 붙은 줄 365 불변 열쇠(FW-) 겹침 0(전수 · `test_z01_work_items`). 목차 코드(FP-)는 칸으로만 — 개정 때 밀림."
|
||||
},
|
||||
{
|
||||
"table": "work_item_const",
|
||||
@@ -1835,7 +1835,7 @@
|
||||
"key": [
|
||||
"work_item_key"
|
||||
],
|
||||
"checked": "잎 995 줄 불변 열쇠(CW-) 겹침 0(전수 · `test_work_item_master_const`). 목차 코드(CP-)는 칸으로만 — 개정 때 밀림."
|
||||
"checked": "표가 붙은 줄 997 불변 열쇠(CW-) 겹침 0(전수 · `test_work_item_master_const`). 목차 코드(CP-)는 칸으로만 — 개정 때 밀림."
|
||||
}
|
||||
],
|
||||
"null_is_real": {
|
||||
@@ -8733,124 +8733,28 @@
|
||||
"name_ko": "갈래 키 계약",
|
||||
"summary": "갈래 키 문자열을 두 창이 각자 조립하지 않기로 한 약속."
|
||||
},
|
||||
"work_item_master::work_item": {
|
||||
"name_ko": "공종 — 품셈 표가 붙은 줄"
|
||||
},
|
||||
"work_item_master::group": {
|
||||
"name_ko": "묶는 마디 — 표는 없고 아래를 고르는 자리(계산 규칙을 지닌다)"
|
||||
},
|
||||
"work_item_master::general_provision": {
|
||||
"name_ko": "총칙 — 문서 구역이나 값표가 붙어 B09 가 읽는다"
|
||||
"work_item_master::choose_one": {
|
||||
"name_ko": "아래 하나를 고름"
|
||||
},
|
||||
"work_item_master::empty": {
|
||||
"name_ko": "빈 잎 — 표도 아래도 없는 자리"
|
||||
},
|
||||
"work_item_master::choose_one": {
|
||||
"name_ko": "아래 하나를 고름"
|
||||
"work_item_master::general_provision": {
|
||||
"name_ko": "총칙 — 문서 구역이나 값표가 붙어 B09 가 읽는다"
|
||||
},
|
||||
"work_item_master::group": {
|
||||
"name_ko": "묶는 마디 — 표는 없고 아래를 고르는 자리(계산 규칙을 지닌다)"
|
||||
},
|
||||
"work_item_master::sum_steps": {
|
||||
"name_ko": "아래를 단계로 다 더함"
|
||||
},
|
||||
"work_item_master::work_item": {
|
||||
"name_ko": "공종 — 품셈 표가 붙은 줄"
|
||||
}
|
||||
},
|
||||
"unknown": [],
|
||||
"work_item_axis": {
|
||||
"note": "공종 축(불변 열쇠 FW·CW) 화면 이름표. 기초데이터 열넷 셈(`merged_tables`)에 섞이지 않게 여기 따로 둔다. Z01 이 이 구획을 읽어 상자 이름·열 이름을 뜨면 된다.",
|
||||
"group": "work_item",
|
||||
"kinds": [
|
||||
{
|
||||
"key": "work_item_forest",
|
||||
"name_ko": "산림 공종",
|
||||
"summary": "산림사업 표준품셈 목차가 세운 공종 나무.",
|
||||
"source": "resources/data_work_item_master/work_item_master_2026-01-01.json",
|
||||
"key_prefix": "FW"
|
||||
},
|
||||
{
|
||||
"key": "work_item_const",
|
||||
"name_ko": "건설 공종",
|
||||
"summary": "건설공사 표준품셈 목차가 세울 공종 나무(아직 안 뽑음).",
|
||||
"source": "resources/data_cost_input_value/pum_const_2026.json",
|
||||
"key_prefix": "CW"
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
{
|
||||
"key": "work_item_key",
|
||||
"name_ko": "공종 열쇠",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "path_name",
|
||||
"name_ko": "전체 경로 이름",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "name",
|
||||
"name_ko": "이름",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "toc_code",
|
||||
"name_ko": "그 판 목차 코드",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "toc_number",
|
||||
"name_ko": "그 판 목차 번호",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "toc_edition",
|
||||
"name_ko": "목차 판(고시)",
|
||||
"unit": "",
|
||||
"visible": false
|
||||
},
|
||||
{
|
||||
"key": "level",
|
||||
"name_ko": "단계",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "axis_role",
|
||||
"name_ko": "줄 구실",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "pum_tables",
|
||||
"name_ko": "딸린 품셈 표",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "rule",
|
||||
"name_ko": "계산 규칙",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "step_weight",
|
||||
"name_ko": "단계 비중",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
}
|
||||
],
|
||||
"values": {
|
||||
"axis_role": {
|
||||
"work_item": "공종 — 품셈 표가 붙은 줄",
|
||||
"group": "묶는 마디 — 표는 없고 아래를 고르는 자리(계산 규칙을 지닌다)",
|
||||
"general_provision": "총칙 — 문서 구역이나 값표가 붙어 B09 가 읽는다",
|
||||
"empty": "빈 잎 — 표도 아래도 없는 자리"
|
||||
},
|
||||
"rule": {
|
||||
"choose_one": "아래 하나를 고름",
|
||||
"sum_steps": "아래를 단계로 다 더함"
|
||||
}
|
||||
}
|
||||
"pending_merged_tables": {
|
||||
"note": "**서버가 아직 안 내는 kind** 의 이름표를 미리 지어 둔 자리. `merged_tables` 는 서버 kind 와 한 짝이어야 해서(묵은 열 시험) 여기 둔다. 서버가 kind 를 내는 날 **그대로 `merged_tables` 로 옮기고** `test_master_labels_cover.py` 의 `MERGED_KEYS` 에 한 줄 더할 것.",
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"dataset_id": "data_work_item_master_manifest",
|
||||
"generated_at": "2026-09-17T12:57:59+09:00",
|
||||
"generated_at": "2026-09-17T13:25:57+09:00",
|
||||
"built_by": "B08_Quantity/B08_Quantity_Build_WorkItemMaster.py",
|
||||
"source": {
|
||||
"dataset_id": "pum_forest",
|
||||
@@ -12,7 +12,7 @@
|
||||
"files": [
|
||||
{
|
||||
"file": "work_item_master_2026-01-01.json",
|
||||
"sha256": "af00d1436125a45b2f9ad72d5d3937d47f1698ce1262fea2b4bdd957977ec1c7",
|
||||
"sha256": "7ea89212f1d9180d66114731b8a19c9240139bb643a006c7ebebaf31a6fb4b71",
|
||||
"size_bytes": 956887
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"dataset_id": "data_work_item_master_manifest",
|
||||
"generated_at": "2026-09-17T12:57:59+09:00",
|
||||
"generated_at": "2026-09-17T13:25:57+09:00",
|
||||
"built_by": "B08_Quantity/B08_Quantity_Build_WorkItemMaster.py",
|
||||
"source": {
|
||||
"dataset_id": "pum_const",
|
||||
@@ -12,8 +12,8 @@
|
||||
"files": [
|
||||
{
|
||||
"file": "const_work_item_master_2026-01-01.json",
|
||||
"sha256": "b1574590d7d6206f48e1bcca5fcc4b5bdebbb07ecaaf376c700a75e7794c1faa",
|
||||
"size_bytes": 3752144
|
||||
"sha256": "7ceb89a603c90d27e3354f34c592e4ae5ee3550ea460c58ab39bea0bfefc3df3",
|
||||
"size_bytes": 3752575
|
||||
},
|
||||
{
|
||||
"file": "const_form_undetermined_2026-01-01.json",
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"dataset_id": "work_item_axis_policy",
|
||||
"effective_date": "2026-01-01",
|
||||
"note": "공종 축의 줄 구실(axis_role) 잣대. 코드에 박지 않고 여기 둔다 — 기관이 정하는 범위라 시스템관리자가 고칠 값이다.",
|
||||
"policy": {
|
||||
"never_delete": true,
|
||||
"why": "표 없는 마디 가운데 상당수가 `parent_mode`(계산 규칙)를 지니고, 총칙 장에는 원가가 실제로 읽는 값표가 붙어 있다. 지우면 계산 규칙과 값이 함께 사라진다(2026-09-17 산림에서 확인 · 브레인 승인).",
|
||||
"flat_list_filter": "pum_tables > 0",
|
||||
"flat_list_why": "「잎」으로 거르면 부모 마디에 붙은 표가 통째로 안 뜬다(산림 5건)."
|
||||
},
|
||||
"roles": {
|
||||
"work_item": {
|
||||
"name_ko": "공종",
|
||||
"rule": "품셈 표가 붙은 줄",
|
||||
"keep": true
|
||||
},
|
||||
"group": {
|
||||
"name_ko": "묶는 마디",
|
||||
"rule": "표는 없고 아래를 묶는 줄",
|
||||
"keep": true,
|
||||
"why": "`parent_mode`(choose_one·sum_steps)가 여기 붙는다 — 계산 규칙을 지닌 줄이다."
|
||||
},
|
||||
"general_provision": {
|
||||
"name_ko": "총칙",
|
||||
"rule": "`general_provision` 뿌리 아래의 줄",
|
||||
"keep": true,
|
||||
"why": "문서 구역이나 값표가 붙어 원가가 읽는다."
|
||||
},
|
||||
"empty": {
|
||||
"name_ko": "빈 잎",
|
||||
"rule": "표도 아래도 없는 잎",
|
||||
"keep": true,
|
||||
"why": "원문에 표가 안 붙은 자리. 지우면 「있었는지 없었는지」가 안 남는다."
|
||||
}
|
||||
},
|
||||
"decision_order": [
|
||||
"general_provision",
|
||||
"work_item",
|
||||
"group",
|
||||
"empty"
|
||||
],
|
||||
"axes": {
|
||||
"forest": {
|
||||
"name_ko": "산림 공종",
|
||||
"settled": true,
|
||||
"source": "resources/data_cost_input_value/pum_forest_2026.json",
|
||||
"toc_source": "목차표 F0001 — 원문이 목차를 표로 싣는다",
|
||||
"code_prefix": "FP",
|
||||
"key_prefix": "FW",
|
||||
"general_provision_roots": [
|
||||
"FP-01",
|
||||
"FP-02"
|
||||
],
|
||||
"general_provision_basis": "FP-01 적용기준 51줄 · FP-02 소요재료 및 기계손료 27줄. ⚠ 지우면 안 됨 — `B09_Estimation_Consumables.py` 가 FP-02-01-01·FP-02-01-08(연료 F0042·F0043·F0058)과 FP-02-02-01/02/04/05/06(손료계수 F0064~F0069)을 읽는다.",
|
||||
"counts_2025_82": {
|
||||
"rows": 477,
|
||||
"work_item": 304,
|
||||
"group": 82,
|
||||
"general_provision": 78,
|
||||
"empty": 13
|
||||
}
|
||||
},
|
||||
"const": {
|
||||
"name_ko": "건설 공종",
|
||||
"settled": false,
|
||||
"source": "resources/data_cost_input_value/pum_const_2026.json",
|
||||
"toc_source": "⚠ 목차표가 없다 — 장 파일 46개(`source_file` 칸)가 유일한 계층 근거다",
|
||||
"code_prefix": "CP",
|
||||
"key_prefix": "CW",
|
||||
"code_shape": "CP-<부문2>-<장2>-<절…>",
|
||||
"code_shape_why": "⚠ **부문마다 장 번호가 1부터 다시 시작한다** — 01_공통부문 제1장은 적용기준, 02_토목부문 제1장은 도로포장공사다. 장 번호만으로 코드를 지으면 부문이 다른 공종끼리 덮는다. 산림에서 목차 번호 겹침 둘(12-17-2·12-24-1)이 실제로 서로 덮고 있었다.",
|
||||
"general_provision_roots": [
|
||||
"01_공통부문/제1장_적용기준.md"
|
||||
],
|
||||
"general_provision_basis": "산림 FP-01 적용기준과 짝. 단위표준·토질·할증 같은 기준표 46개.",
|
||||
"general_provision_pending": [
|
||||
{
|
||||
"chapter": "01_공통부문/제8장_건설기계.md",
|
||||
"tables": 351,
|
||||
"why": "산림 FP-02 「소요재료 및 기계손료」와 짝으로 보이나, 기계 시공능력·손료 파라미터라 공종으로 볼 여지도 있음. ⬜ 안티그래비티 조사 대기.",
|
||||
"already_extracted": "C0426·C0427 은 `resources/data_machine_productivity/` 로 이미 꺼냈음"
|
||||
},
|
||||
{
|
||||
"chapter": "05_유지관리부문/제1장_공통.md",
|
||||
"tables": 40,
|
||||
"why": "이름이 「공통」 — 기준표 장인지 공종 장인지 원문 확인 필요. ⬜ 안티그래비티 조사 대기."
|
||||
}
|
||||
],
|
||||
"known_defects": [
|
||||
"표 2,192 중 883(40 %)이 절 이름이 어긋남 — 파서가 글 속 제목을 못 잡음(PLAN 데스크탑서브 「빠진 표 일곱」 절)",
|
||||
"`section` 이 절 번호가 아닌 표 289 — 그 가운데 11 은 값이 박힘"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"effective_date": "2026-01-01",
|
||||
"pum_edition": "2026-01-01",
|
||||
"toc_edition": "2026-01-01",
|
||||
"generated_at": "2026-09-17T12:57:59+09:00",
|
||||
"generated_at": "2026-09-17T13:25:57+09:00",
|
||||
"dataset_version": {
|
||||
"dataset_id": "pum_const",
|
||||
"effective_date": "2026-01-01",
|
||||
@@ -28,7 +28,9 @@
|
||||
"toc_source": "2026년_건설공사_표준품셈.md 머리 「목 차」",
|
||||
"section_from_body": true,
|
||||
"variant_keys_attached": false,
|
||||
"general_provision_marked": false
|
||||
"general_provision_roots": [
|
||||
"CP-01-01"
|
||||
]
|
||||
},
|
||||
"stats": {
|
||||
"toc_nodes": 1367,
|
||||
@@ -40,9 +42,10 @@
|
||||
"basis_missing": 447,
|
||||
"basis_grouped": 54,
|
||||
"axis_roles": {
|
||||
"group": 270,
|
||||
"empty": 100,
|
||||
"work_item": 997
|
||||
"group": 264,
|
||||
"general_provision": 37,
|
||||
"work_item": 983,
|
||||
"empty": 83
|
||||
},
|
||||
"keys_newly_issued": 0,
|
||||
"toc_duplicate_rows": 0,
|
||||
@@ -86,7 +89,7 @@
|
||||
"toc_number": "1",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준",
|
||||
"axis_role": "group"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-01",
|
||||
@@ -104,7 +107,7 @@
|
||||
"toc_number": "1-1",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 일반사항",
|
||||
"axis_role": "group"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-01-01",
|
||||
@@ -121,7 +124,7 @@
|
||||
"toc_number": "1-1-1",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 일반사항 › 목적",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-01-02",
|
||||
@@ -138,7 +141,7 @@
|
||||
"toc_number": "1-1-2",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 일반사항 › 적용범위",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-01-03",
|
||||
@@ -155,7 +158,7 @@
|
||||
"toc_number": "1-1-3",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 일반사항 › 적용방법",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-02",
|
||||
@@ -173,7 +176,7 @@
|
||||
"toc_number": "1-2",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 설계 및 수량",
|
||||
"axis_role": "group"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-02-01",
|
||||
@@ -190,7 +193,7 @@
|
||||
"toc_number": "1-2-1",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 설계 및 수량 › 수량의 계산",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-02-02",
|
||||
@@ -544,7 +547,7 @@
|
||||
"toc_number": "1-2-2",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 설계 및 수량 › 단위표준",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-02-03",
|
||||
@@ -758,7 +761,7 @@
|
||||
"toc_number": "1-2-3",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 설계 및 수량 › 토질",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-02-04",
|
||||
@@ -1030,7 +1033,7 @@
|
||||
"toc_number": "1-2-4",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 설계 및 수량 › 재료 및 자재의 단가",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-02-05",
|
||||
@@ -1047,7 +1050,7 @@
|
||||
"toc_number": "1-2-5",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 설계 및 수량 › 인력",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-02-06",
|
||||
@@ -1064,7 +1067,7 @@
|
||||
"toc_number": "1-2-6",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 설계 및 수량 › 공구 및 경장비",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-02-07",
|
||||
@@ -1719,7 +1722,7 @@
|
||||
"toc_number": "1-2-7",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 설계 및 수량 › 운반",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-02-08",
|
||||
@@ -1861,7 +1864,7 @@
|
||||
"toc_number": "1-2-8",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 설계 및 수량 › 작업조 구성 및 적용",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-02-09",
|
||||
@@ -2010,7 +2013,7 @@
|
||||
"toc_number": "1-2-9",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 설계 및 수량 › 소규모(작업물량 제한)",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-03",
|
||||
@@ -2028,7 +2031,7 @@
|
||||
"toc_number": "1-3",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 재료 및 노임의 할증",
|
||||
"axis_role": "group"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-03-01",
|
||||
@@ -2385,7 +2388,7 @@
|
||||
"toc_number": "1-3-1",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 재료 및 노임의 할증 › 재료의 할증",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-03-02",
|
||||
@@ -2402,7 +2405,7 @@
|
||||
"toc_number": "1-3-2",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 재료 및 노임의 할증 › 노임의 할증",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-04",
|
||||
@@ -2420,7 +2423,7 @@
|
||||
"toc_number": "1-4",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 품의 할증",
|
||||
"axis_role": "group"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-04-01",
|
||||
@@ -2437,7 +2440,7 @@
|
||||
"toc_number": "1-4-1",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 품의 할증 › 적용기준",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-04-02",
|
||||
@@ -2454,7 +2457,7 @@
|
||||
"toc_number": "1-4-2",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 품의 할증 › 할증의 중복가산요령",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-04-03",
|
||||
@@ -2594,7 +2597,7 @@
|
||||
"toc_number": "1-4-3",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 품의 할증 › 작업지연",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-04-04",
|
||||
@@ -2912,7 +2915,7 @@
|
||||
"toc_number": "1-4-4",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 품의 할증 › 지세/지형",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-04-05",
|
||||
@@ -3086,7 +3089,7 @@
|
||||
"toc_number": "1-4-5",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 품의 할증 › 위험",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-04-06",
|
||||
@@ -3166,7 +3169,7 @@
|
||||
"toc_number": "1-4-6",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 품의 할증 › 작업제한",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-04-07",
|
||||
@@ -3215,7 +3218,7 @@
|
||||
"toc_number": "1-4-7",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 품의 할증 › 작업환경",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-05",
|
||||
@@ -3233,7 +3236,7 @@
|
||||
"toc_number": "1-5",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 기타",
|
||||
"axis_role": "group"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-05-01",
|
||||
@@ -3250,7 +3253,7 @@
|
||||
"toc_number": "1-5-1",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 기타 › 품질관리비",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-05-02",
|
||||
@@ -3267,7 +3270,7 @@
|
||||
"toc_number": "1-5-2",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 기타 › 산업안전보건관리비",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-05-03",
|
||||
@@ -3284,7 +3287,7 @@
|
||||
"toc_number": "1-5-3",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 기타 › 산업재해보상 보험료 및 기타",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-05-04",
|
||||
@@ -3376,7 +3379,7 @@
|
||||
"toc_number": "1-5-4",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 기타 › 환경관리비",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-05-05",
|
||||
@@ -3393,7 +3396,7 @@
|
||||
"toc_number": "1-5-5",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 기타 › 안전관리비",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-05-06",
|
||||
@@ -3489,7 +3492,7 @@
|
||||
"toc_number": "1-5-6",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 기타 › 사용료",
|
||||
"axis_role": "work_item"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-05-07",
|
||||
@@ -3506,7 +3509,7 @@
|
||||
"toc_number": "1-5-7",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 기타 › 현장시공상세도면의 작성",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-05-08",
|
||||
@@ -3523,7 +3526,7 @@
|
||||
"toc_number": "1-5-8",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 기타 › 종합시운전 및 조정비",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-05-09",
|
||||
@@ -3540,7 +3543,7 @@
|
||||
"toc_number": "1-5-9",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 기타 › 시공측량비",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-01-05-10",
|
||||
@@ -3557,7 +3560,7 @@
|
||||
"toc_number": "1-5-10",
|
||||
"toc_edition": "2026-01-01",
|
||||
"path_name": "공통부문 › 적용기준 › 기타 › 표준품셈 보완실사",
|
||||
"axis_role": "empty"
|
||||
"axis_role": "general_provision"
|
||||
},
|
||||
{
|
||||
"work_item_code": "CP-01-02",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"effective_date": "2026-01-01",
|
||||
"pum_edition": "2026-01-01",
|
||||
"toc_edition": "산림청고시제2025-82호",
|
||||
"generated_at": "2026-09-17T12:57:59+09:00",
|
||||
"generated_at": "2026-09-17T13:25:57+09:00",
|
||||
"dataset_version": {
|
||||
"dataset_id": "pum_forest",
|
||||
"effective_date": "2026-01-01",
|
||||
|
||||
@@ -28,6 +28,8 @@ from B08_Quantity.B08_Quantity_Build_WorkItemMaster_Keys import ( # noqa: E402
|
||||
axis_role,
|
||||
empty_registry,
|
||||
format_key,
|
||||
general_provision_roots,
|
||||
load_policy,
|
||||
path_names,
|
||||
toc_edition,
|
||||
toc_slot,
|
||||
@@ -213,6 +215,38 @@ def test_구실_가름_규칙() -> None:
|
||||
assert axis_role({"work_item_code": "FP-09-01", "tables": []}, has_children=False) == "empty"
|
||||
|
||||
|
||||
def test_총칙_범위는_코드가_아니라_자료가_정함() -> None:
|
||||
"""⚠ 뿌리를 코드에 박으면 관리자가 못 고친다 — `axis_policy.json` 이 정본."""
|
||||
assert general_provision_roots("forest") == ("FP-01", "FP-02")
|
||||
const = load_policy("const")
|
||||
assert const["key_prefix"] == "CW"
|
||||
assert const["settled"] is False
|
||||
assert const["general_provision_roots"] == ["01_공통부문/제1장_적용기준.md"]
|
||||
assert [p["chapter"] for p in const["general_provision_pending"]] == [
|
||||
"01_공통부문/제8장_건설기계.md",
|
||||
"05_유지관리부문/제1장_공통.md",
|
||||
]
|
||||
# 뿌리를 바꿔 주면 구실도 따라 바뀐다 — 잣대가 자료에 있다는 증거
|
||||
assert (
|
||||
axis_role({"work_item_code": "FP-09-03", "tables": []}, has_children=True, roots=("FP-09",))
|
||||
== "general_provision"
|
||||
)
|
||||
|
||||
|
||||
def test_어느_구실도_지우지_않음() -> None:
|
||||
"""브레인 승인(2026-09-17) — 「덜어내기」가 아니라 「가름만」."""
|
||||
import json as _json
|
||||
from pathlib import Path as _Path
|
||||
|
||||
policy = _json.loads(
|
||||
_Path("resources/data_work_item_master/axis_policy.json").read_text(encoding="utf-8")
|
||||
)
|
||||
assert policy["policy"]["never_delete"] is True
|
||||
assert policy["policy"]["flat_list_filter"] == "pum_tables > 0"
|
||||
assert all(role["keep"] for role in policy["roles"].values())
|
||||
assert list(policy["roles"]) == ["work_item", "group", "general_provision", "empty"]
|
||||
|
||||
|
||||
def test_경로_이름_잇기() -> None:
|
||||
nodes = [
|
||||
{"work_item_code": "A", "parent_code": None, "name": "토공"},
|
||||
|
||||
@@ -38,15 +38,15 @@ def _get(client: TestClient, kind: str, **params) -> dict:
|
||||
def test_산림_평평한_잎_목록과_계산_규칙(client: TestClient) -> None:
|
||||
listed = client.get("/api/master-data/base-prices").json()["kinds"]
|
||||
forest = next(i for i in listed if i["kind"] == "work_item_forest")
|
||||
assert forest["group"] == "work_item" and forest["rows"] == 360
|
||||
assert forest["group"] == "work_item" and forest["rows"] == 365
|
||||
|
||||
table = _get(client, "work_item_forest", size=500)
|
||||
assert table["total"] == 360 and table["stats"] == {"leaves": 360, "units": 620}
|
||||
assert table["total"] == 365 and table["stats"] == {"work_items": 365, "units": 627}
|
||||
assert table["editable"] == [] and set(table["locked"]) == set(table["sortable"])
|
||||
rows = table["rows"]
|
||||
assert len({r["@id"] for r in rows}) == 360
|
||||
assert len({r["@id"] for r in rows}) == 365
|
||||
assert (
|
||||
len({r["name"] for r in rows}) == 360
|
||||
len({r["name"] for r in rows}) == 365
|
||||
) # 잎 이름 겹침 — 경로로 풀림 · 목차 오기 바로잡아 358 → 360(12-17-2 · 12-24-1)
|
||||
by_code = {r["work_item_code"]: r for r in rows}
|
||||
assert by_code["FP-09-03-02"]["name"] == "토공 › 토사깍기 › 기계"
|
||||
@@ -57,6 +57,7 @@ def test_산림_평평한_잎_목록과_계산_규칙(client: TestClient) -> Non
|
||||
assert [s["weight"] for s in blast["steps"]] == ["0.1", "0.9", "1"]
|
||||
assert by_code["FP-09-05-02"]["rule"] == "sum_steps"
|
||||
assert by_code["FP-09-05-02"]["step_weight"] == "0.9"
|
||||
# 2026-09-17 FW- 가 옴(데스크탑 서브) — 알림이 사라지는 것이 곧 열쇠가 섰다는 증거.
|
||||
assert all(r["@id"].startswith("FW-") for r in rows) # 불변 열쇠(데스크탑 서브 7dd2c515)
|
||||
assert not any("불변 열쇠" in line for line in table["notice"])
|
||||
|
||||
@@ -67,7 +68,7 @@ def test_산림_평평한_잎_목록과_계산_규칙(client: TestClient) -> Non
|
||||
def test_건설_공종_축도_같은_길(client: TestClient) -> None:
|
||||
"""건설(2026-09-17 뽑음) — 부문이 뿌리라 경로가 부문부터 · 열쇠 CW- · 계산 규칙은 전부 choose_one."""
|
||||
table = _get(client, "work_item_const", size=500)
|
||||
assert table["total"] == table["stats"]["leaves"] > 900
|
||||
assert table["total"] == table["stats"]["work_items"] > 900
|
||||
rows = table["rows"]
|
||||
assert all(r["@id"].startswith("CW-") for r in rows)
|
||||
assert all(r["name"].split(" › ")[0].endswith("부문") for r in rows)
|
||||
@@ -103,7 +104,7 @@ def test_불변_열쇠가_오면_id_로_씀_자료_파일은_판_id_로_가림(
|
||||
assert [r["@id"] for r in table["rows"]] == ["CW-00002"]
|
||||
assert table["rows"][0]["work_item_code"] == "CP-01-01" # 목차 코드는 칸으로 남음
|
||||
assert table["rows"][0]["name"] == "토공 › 인력"
|
||||
assert table["stats"] == {"leaves": 1, "units": 2}
|
||||
assert table["stats"] == {"work_items": 1, "units": 2}
|
||||
assert table["rules"][0]["steps"] == [{"@id": "CW-00002", "weight": "0.5"}]
|
||||
assert _get(client, "work_item_forest")["total"] == 0 # 산림 파일은 이 폴더에 없음
|
||||
|
||||
|
||||
Reference in New Issue
Block a user