fix(b08): 공종 축 목차 오기 둘을 본문 절 번호로 — 12-17-2 무근진동기 제외 → 12-17-3 · 12-24-1 지수판 설치 → 12-27-1(코덱스 원문 대조) · 혼성 줄 풀어 네 줄 제자리(F0363·F0364·F0371·F0375) · orphan 17 → 15 · 열쇠는 그대로(장부에 손으로 이음) · 바로잡은 기록 toc_corrections · 「표 절 제목이 다른 줄을 가리키면 빨강」 시험(옛 자료에서 빨강 확인) · 잎 358 → 360(브레인)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
@@ -161,25 +161,66 @@ def parse_toc(rows: list[list[str]]) -> list[dict[str, Any]]:
|
||||
else:
|
||||
continue # 부록 등 — 공종 계층이 아니다.
|
||||
order += 256 # STmate 의 SORTCODE 관례(256 간격) — 중간 삽입 여유.
|
||||
parts = number.split("-")
|
||||
nodes.append(
|
||||
{
|
||||
"work_item_code": f"{CODE_PREFIX}-" + "-".join(p.zfill(2) for p in parts),
|
||||
"number": number,
|
||||
"name": name,
|
||||
"level": len(parts),
|
||||
"parent_code": (
|
||||
f"{CODE_PREFIX}-" + "-".join(p.zfill(2) for p in parts[:-1])
|
||||
if len(parts) > 1
|
||||
else None
|
||||
),
|
||||
"sort_order": order,
|
||||
"tables": [],
|
||||
}
|
||||
)
|
||||
nodes.append({"number": number, "name": name, "sort_order": order, "tables": []})
|
||||
_set_number(nodes[-1], number)
|
||||
return nodes
|
||||
|
||||
|
||||
def _set_number(node: dict[str, Any], number: str) -> None:
|
||||
"""번호에서 코드·단계·윗줄을 셈 — 목차를 읽을 때와 본문 번호로 바로잡을 때 한 벌."""
|
||||
parts = number.split("-")
|
||||
node.update(
|
||||
{
|
||||
"work_item_code": f"{CODE_PREFIX}-" + "-".join(p.zfill(2) for p in parts),
|
||||
"number": number,
|
||||
"level": len(parts),
|
||||
"parent_code": (
|
||||
f"{CODE_PREFIX}-" + "-".join(p.zfill(2) for p in parts[:-1])
|
||||
if len(parts) > 1
|
||||
else None
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
#: 본문 절 제목 — 「12-17-3. 무근진동기 제외」 꼴(번호 + 마침표 + 이름)만. 문장 조각은 안 봄.
|
||||
_BODY_TITLE_RE = re.compile(r"^\s*(\d+(?:-\d+){0,3})\.\s+(.+)$")
|
||||
|
||||
|
||||
def renumber_from_body(
|
||||
nodes: list[dict[str, Any]], tables: list[dict[str, Any]]
|
||||
) -> list[dict[str, str]]:
|
||||
"""⚠ **목차 오기**를 본문 절 번호로 바로잡음 — 원문이 또렷하면 원문(2026-09-17 코덱스 원문 대조 · 브레인).
|
||||
|
||||
품셈 목차가 「12-17-2 무근진동기 제외」(본문 12-17-3) · 「12-24-1 지수판 설치」(본문 12-27-1) 로
|
||||
**같은 번호를 두 번** 적었음. 목차만 믿으면 뒤 줄이 앞 표를 덮어써 이름과 표가 어긋난 줄이 서고
|
||||
진짜 표(F0364·F0375)가 orphan 으로 사라짐 — 재료·노무·장비가 있는 원가 공종이라 금액이 빠짐.
|
||||
⚠ 겹친 번호의 줄이고 · 본문에 같은 이름 제목이 있고 · 그 본문 번호가 목차에 없을 때만 고침.
|
||||
셋 중 하나라도 아니면 그대로 둠(겹친 채 남아 `toc_duplicate_rows` 와 시험이 드러냄).
|
||||
"""
|
||||
body: dict[str, set[str]] = {}
|
||||
for table in tables:
|
||||
if found := _BODY_TITLE_RE.match(norm(table.get("section"))):
|
||||
body.setdefault("".join(found.group(2).split()), set()).add(found.group(1))
|
||||
count: dict[str, int] = {}
|
||||
for node in nodes:
|
||||
count[node["number"]] = count.get(node["number"], 0) + 1
|
||||
fixed = []
|
||||
for node in nodes:
|
||||
numbers = body.get("".join(node["name"].split()), set())
|
||||
if count[node["number"]] < 2 or node["number"] in numbers:
|
||||
continue
|
||||
candidates = sorted(numbers - set(count))
|
||||
if len(candidates) != 1:
|
||||
continue
|
||||
printed = node["number"]
|
||||
_set_number(node, candidates[0])
|
||||
count[printed] -= 1
|
||||
count[node["number"]] = 1
|
||||
fixed.append({"name": node["name"], "toc_number": printed, "body_number": node["number"]})
|
||||
return fixed
|
||||
|
||||
|
||||
def _is_section_title(section: str, by_number: dict[str, dict[str, Any]]) -> bool:
|
||||
"""진짜 절 제목인가 — 「번호. 이름」, 또는 마침표 없이 번호 뒤 글이 목차 절 이름과 같음."""
|
||||
if re.match(r"^\s*\d+(?:-\d+){0,3}\.\s", section):
|
||||
@@ -606,6 +647,7 @@ def build() -> tuple[
|
||||
break
|
||||
toc_table = next(t for t in tables if t["table_id"] == "F0001")
|
||||
nodes = parse_toc(toc_table["rows"])
|
||||
toc_corrections = renumber_from_body(nodes, tables)
|
||||
by_number = {n["number"]: n for n in nodes}
|
||||
|
||||
attached = 0
|
||||
@@ -786,7 +828,10 @@ def build() -> tuple[
|
||||
"axis_roles": role_counts,
|
||||
"keys_newly_issued": len(newly_issued),
|
||||
"toc_duplicate_rows": len(toc_duplicates),
|
||||
"toc_corrections": len(toc_corrections),
|
||||
},
|
||||
# 목차 오기를 본문 절 번호로 바로잡은 줄 — 목차에 적힌 번호(`toc_number`)도 남김.
|
||||
"toc_corrections": toc_corrections,
|
||||
# ⚠ 품셈 목차가 **같은 번호를 두 번 적은 자리**. 목차 번호를 열쇠로 쓰면 이 줄들이
|
||||
# 서로 덮는다 — 열쇠를 따로 두는 까닭의 실물이다.
|
||||
"toc_duplicate_rows": toc_duplicates,
|
||||
|
||||
@@ -1366,7 +1366,7 @@
|
||||
"sources": [
|
||||
"work_item_master::work_items"
|
||||
],
|
||||
"rows_note": "잎 358 줄 · 갈래 펴면 618",
|
||||
"rows_note": "잎 360 줄 · 갈래 펴면 620",
|
||||
"columns": [
|
||||
{
|
||||
"key": "work_item_code",
|
||||
@@ -1763,7 +1763,7 @@
|
||||
"key": [
|
||||
"work_item_key"
|
||||
],
|
||||
"checked": "잎 358 줄 불변 열쇠(FW-) 겹침 0(전수 · `test_z01_work_items`). 목차 코드(FP-)는 칸으로만 — 개정 때 밀림."
|
||||
"checked": "잎 360 줄 불변 열쇠(FW-) 겹침 0(전수 · `test_z01_work_items`). 목차 코드(FP-)는 칸으로만 — 개정 때 밀림."
|
||||
}
|
||||
],
|
||||
"null_is_real": {
|
||||
@@ -1861,11 +1861,11 @@
|
||||
"merged_columns": 164,
|
||||
"files": 34,
|
||||
"tables": 92,
|
||||
"columns": 472,
|
||||
"value_groups": 176,
|
||||
"columns": 471,
|
||||
"value_groups": 177,
|
||||
"unknown": 0,
|
||||
"value_groups_by_kind": {
|
||||
"doc": 110,
|
||||
"doc": 111,
|
||||
"value": 66
|
||||
}
|
||||
},
|
||||
@@ -2066,7 +2066,7 @@
|
||||
"name_ko": "공종에 못 붙인 표",
|
||||
"summary": "목차 어디에도 못 붙인 품셈 표.",
|
||||
"shape": "list",
|
||||
"rows": 17,
|
||||
"rows": 15,
|
||||
"columns": [
|
||||
{
|
||||
"key": "pum_table_id",
|
||||
@@ -2194,24 +2194,12 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "toc_duplicate_rows",
|
||||
"name_ko": "목차 번호가 겹친 줄",
|
||||
"summary": "품셈 목차가 같은 번호를 두 번 적은 자리. 번호를 열쇠로 못 쓰는 실물이다.",
|
||||
"key": "toc_corrections",
|
||||
"name_ko": "목차 오기를 본문 번호로 바로잡은 줄",
|
||||
"summary": "품셈 목차가 같은 번호를 두 번 적은 자리를 본문 절 제목 번호로 고친 기록. 목차만 믿으면 표가 딴 줄에 붙는다.",
|
||||
"shape": "list",
|
||||
"rows": 2,
|
||||
"columns": [
|
||||
{
|
||||
"key": "toc_code",
|
||||
"name_ko": "목차 코드",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "toc_number",
|
||||
"name_ko": "목차 번호",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "name",
|
||||
"name_ko": "이름",
|
||||
@@ -2219,8 +2207,14 @@
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "slot",
|
||||
"name_ko": "장부 자리",
|
||||
"key": "toc_number",
|
||||
"name_ko": "목차에 적힌 번호",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "body_number",
|
||||
"name_ko": "본문 절 번호",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
}
|
||||
@@ -2245,6 +2239,12 @@
|
||||
"name_ko": "목차 판(고시)",
|
||||
"summary": "목차 번호가 묶인 고시 판. 값이 아니라 **판을 가리키는 표**다.",
|
||||
"kind": "doc"
|
||||
},
|
||||
{
|
||||
"key": "toc_duplicate_rows",
|
||||
"name_ko": "목차 번호가 겹친 줄",
|
||||
"summary": "목차가 같은 번호를 두 번 적었는데 본문 번호로도 못 가른 자리. 비어 있어야 정상 — 줄이 생기면 표가 딴 줄에 붙는다.",
|
||||
"kind": "doc"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -7059,6 +7059,11 @@
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
"body_number": {
|
||||
"name_ko": "본문 절 번호",
|
||||
"unit": "",
|
||||
"visible": true
|
||||
},
|
||||
"bond_codes": {
|
||||
"name_ko": "쌓기 방식별 공종코드",
|
||||
"unit": "",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"dataset_id": "data_work_item_master_manifest",
|
||||
"generated_at": "2026-09-17T10:24:52+09:00",
|
||||
"generated_at": "2026-09-17T12:17:18+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": "6aad7aa1e526d306e7b7ab70229e364d24b0d236671e4e7b3ad1dac5a57e4b3b",
|
||||
"size_bytes": 954644
|
||||
"sha256": "e58445ea630862cc5391cd6c5d320c76065cdce92abd7e9f3c5dc55b71369273",
|
||||
"size_bytes": 956887
|
||||
},
|
||||
{
|
||||
"file": "form_undetermined_2026-01-01.json",
|
||||
|
||||
@@ -399,7 +399,6 @@
|
||||
"FP-12-17": "FW-00385",
|
||||
"FP-12-17-01": "FW-00386",
|
||||
"FP-12-17-02": "FW-00387",
|
||||
"FP-12-17-02#2": "FW-00388",
|
||||
"FP-12-18": "FW-00389",
|
||||
"FP-12-19": "FW-00390",
|
||||
"FP-12-20": "FW-00391",
|
||||
@@ -412,7 +411,6 @@
|
||||
"FP-12-25": "FW-00398",
|
||||
"FP-12-26": "FW-00399",
|
||||
"FP-12-27": "FW-00400",
|
||||
"FP-12-24-01#2": "FW-00401",
|
||||
"FP-12-27-02": "FW-00402",
|
||||
"FP-12-27-03": "FW-00403",
|
||||
"FP-12-28": "FW-00404",
|
||||
@@ -488,7 +486,9 @@
|
||||
"FP-13-16-02": "FW-00474",
|
||||
"FP-14": "FW-00475",
|
||||
"FP-14-01": "FW-00476",
|
||||
"FP-14-02": "FW-00477"
|
||||
"FP-14-02": "FW-00477",
|
||||
"FP-12-17-03": "FW-00388",
|
||||
"FP-12-27-01": "FW-00401"
|
||||
}
|
||||
},
|
||||
"keys": {
|
||||
@@ -3206,7 +3206,8 @@
|
||||
"issued_on": "2026-09-17",
|
||||
"first_toc_code": "FP-12-17-02",
|
||||
"first_toc_number": "12-17-2",
|
||||
"first_name": "무근진동기 제외"
|
||||
"first_name": "무근진동기 제외",
|
||||
"relinked": "목차 오기 바로잡음 — 목차 12-17-2(겹친 번호 · 자리 FP-12-17-02#2) → 본문 12-17-3 · 같은 공종이라 열쇠 그대로 (2026-09-17 코덱스 원문 대조 · 브레인 지시 · 손으로 이음)"
|
||||
},
|
||||
"FW-00389": {
|
||||
"issued_edition": "산림청고시제2025-82호",
|
||||
@@ -3297,7 +3298,8 @@
|
||||
"issued_on": "2026-09-17",
|
||||
"first_toc_code": "FP-12-24-01",
|
||||
"first_toc_number": "12-24-1",
|
||||
"first_name": "지수판 설치"
|
||||
"first_name": "지수판 설치",
|
||||
"relinked": "목차 오기 바로잡음 — 목차 12-24-1(겹친 번호 · 자리 FP-12-24-01#2) → 본문 12-27-1 · 같은 공종이라 열쇠 그대로 (2026-09-17 코덱스 원문 대조 · 브레인 지시 · 손으로 이음)"
|
||||
},
|
||||
"FW-00402": {
|
||||
"issued_edition": "산림청고시제2025-82호",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,10 @@ MASTER = OUT_DIR / "work_item_master_2026-01-01.json"
|
||||
|
||||
#: 열쇠를 얹기 **전** 벌(`work_items`)의 지문. 2026-09-17 데스크탑_서브가 뜬 값.
|
||||
#: ⚠ 이 값이 흔들리면 열쇠 작업이 값을 건드렸다는 뜻이다 — 지문을 고치지 말고 원인을 찾을 것.
|
||||
BASELINE_WORK_ITEMS_SHA = "f8b8fba1ab5e6d1d5611b99148eecbed1eaf049c42d7154820f0d9a45e20ffea"
|
||||
#: 2026-09-17 랩탑 메인 — 목차 오기 둘을 본문 번호로 바로잡아(브레인 지시) **네 줄만** 바뀌어 새로 뜸
|
||||
#: (옛 f8b8fba1…). 네 줄 모습은 `test_목차_오기는_본문_번호로_바로잡고_열쇠는_그대로` 가 잼 ·
|
||||
#: 미판정·밑수 목록 파일은 한 글자도 안 바뀜.
|
||||
BASELINE_WORK_ITEMS_SHA = "be88f730b325c290012e5a2a79ad593ce5a74ffd82464226857191fefe6647e3"
|
||||
|
||||
#: 열쇠 작업이 새로 얹은 칸. 지문을 잴 때만 떼어낸다.
|
||||
ADDED_FIELDS = (
|
||||
@@ -128,16 +131,29 @@ def test_열쇠_차례는_목차_차례() -> None:
|
||||
assert nodes[0]["work_item_key"] == format_key(2)
|
||||
|
||||
|
||||
def test_목차_번호가_겹친_줄도_열쇠는_따로(master: dict) -> None:
|
||||
"""⚠ 품셈 목차가 12-17-2·12-24-1 을 두 번 적었음 — 번호를 열쇠로 못 쓰는 실물."""
|
||||
dups = master["toc_duplicate_rows"]
|
||||
assert [d["toc_number"] for d in dups] == ["12-17-2", "12-24-1"]
|
||||
assert [d["slot"] for d in dups] == ["FP-12-17-02#2", "FP-12-24-01#2"]
|
||||
for number in ("12-17-2", "12-24-1"):
|
||||
rows = [it for it in master["work_items"] if it["number"] == number]
|
||||
assert len(rows) == 2
|
||||
assert len({r["work_item_key"] for r in rows}) == 2
|
||||
assert len({r["path_name"] for r in rows}) == 2
|
||||
def test_목차_오기는_본문_번호로_바로잡고_열쇠는_그대로(master: dict) -> None:
|
||||
"""⚠ 품셈 목차가 12-17-2·12-24-1 을 두 번 적었음 — 본문은 12-17-3 무근진동기 제외 · 12-27-1 지수판 설치
|
||||
(2026-09-17 코덱스 원문 대조). 목차를 믿으면 뒤 줄이 앞 표를 덮어써 혼성 줄이 서고 F0364·F0375 가 사라짐.
|
||||
열쇠는 같은 공종이라 그대로(장부에 손으로 이음 · `relinked`)."""
|
||||
assert master["toc_duplicate_rows"] == []
|
||||
assert [(c["toc_number"], c["body_number"]) for c in master["toc_corrections"]] == [
|
||||
("12-17-2", "12-17-3"),
|
||||
("12-24-1", "12-27-1"),
|
||||
]
|
||||
by_key = {it["work_item_key"]: it for it in master["work_items"]}
|
||||
for key, number, name, tables in (
|
||||
("FW-00387", "12-17-2", "철근, 펌프카 0-15m", ["F0363"]),
|
||||
("FW-00388", "12-17-3", "무근진동기 제외", ["F0364"]),
|
||||
("FW-00396", "12-24-1", "뒷채움", ["F0371"]),
|
||||
("FW-00401", "12-27-1", "지수판 설치", ["F0375"]),
|
||||
):
|
||||
it = by_key[key]
|
||||
assert (it["number"], it["name"], [t["pum_table_id"] for t in it["tables"]]) == (
|
||||
number,
|
||||
name,
|
||||
tables,
|
||||
)
|
||||
assert not {o["pum_table_id"] for o in master["orphan_tables"]} & {"F0364", "F0375"}
|
||||
|
||||
|
||||
def test_슬롯_이름() -> None:
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""공종 축 — **이름과 붙은 표의 절이 어긋나면 빨강** (2026-09-17 브레인 · 코덱스 원문 대조).
|
||||
|
||||
이 병은 한 번 나면 아무도 못 알아챔: 품셈 목차가 같은 번호를 두 번 적자(12-17-2 · 12-24-1)
|
||||
뒤 줄(「무근진동기 제외」)이 앞 표(F0363 「철근, 펌프카 0-15m」)를 물고, 진짜 표(F0364)는 orphan 으로 사라졌음.
|
||||
표 번호로는 안 잡힘(번호는 같았음) — **표 절 제목의 이름이 다른 줄 이름과 똑같은데 그 줄이 아닌 데 붙음**이 그 꼴.
|
||||
⚠ 이름 글자만 조금 다른 자리(목차 「깍기」 · 본문 「깎기」)는 판정하지 않음 — 번호·이름이 딴 줄을 가리킬 때만.
|
||||
⚠ 부록 사례 표 7(F0455·F0456·F0472~F0476)은 번호가 본문 절과 겹쳐 그 절에 붙어 있으나 모두 참조형 —
|
||||
일위대가로 안 읽혀 금액 무관(브레인 전수 판정). 자리 판정은 데스크탑 서브 몫.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
MASTER = ROOT / "resources" / "data_work_item_master" / "work_item_master_2026-01-01.json"
|
||||
_TITLE = re.compile(r"^\s*(\d+(?:-\d+){0,3})\.\s+(.+)$")
|
||||
|
||||
|
||||
def _squeeze(text: str) -> str:
|
||||
return "".join(str(text).split())
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def master() -> dict:
|
||||
return json.loads(MASTER.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def test_목차_번호가_안_겹침(master: dict) -> None:
|
||||
numbers = [it["number"] for it in master["work_items"]]
|
||||
assert len(set(numbers)) == len(numbers)
|
||||
|
||||
|
||||
def test_표_절_제목이_다른_줄을_가리키지_않음(master: dict) -> None:
|
||||
items = master["work_items"]
|
||||
by_name: dict[str, set[str]] = {} # 이름 → 그 이름 줄의 열쇠(번호가 겹친 줄도 갈림)
|
||||
for it in items:
|
||||
by_name.setdefault(_squeeze(it["name"]), set()).add(it["work_item_key"])
|
||||
bad = []
|
||||
for it in items:
|
||||
for table in it["tables"]:
|
||||
title = _TITLE.match(table["section"])
|
||||
if title is None:
|
||||
continue
|
||||
number, name = title.group(1), _squeeze(title.group(2))
|
||||
others = by_name.get(name, set()) - {it["work_item_key"]}
|
||||
if number != it["number"] or (others and name != _squeeze(it["name"])):
|
||||
bad.append(
|
||||
f"{table['pum_table_id']} 「{table['section']}」 → {it['number']} {it['name']}"
|
||||
)
|
||||
assert not bad, bad
|
||||
|
||||
|
||||
def test_사라진_표는_목록으로_남음(master: dict) -> None:
|
||||
"""orphan 을 조용히 버리지 않음 — 목록 수와 집계가 같아야 함."""
|
||||
assert master["stats"]["tables_orphan"] == len(master["orphan_tables"])
|
||||
assert (
|
||||
master["stats"]["tables_attached"] + len(master["orphan_tables"])
|
||||
== master["stats"]["tables_total"]
|
||||
)
|
||||
@@ -39,14 +39,14 @@ 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"] == 358
|
||||
assert forest["group"] == "work_item" and forest["rows"] == 360
|
||||
|
||||
table = _get(client, "work_item_forest", size=500)
|
||||
assert table["total"] == 358 and table["stats"] == {"leaves": 358, "units": 618}
|
||||
assert table["total"] == 360 and table["stats"] == {"leaves": 360, "units": 620}
|
||||
assert table["editable"] == [] and set(table["locked"]) == set(table["sortable"])
|
||||
rows = table["rows"]
|
||||
assert len({r["@id"] for r in rows}) == 358
|
||||
assert len({r["name"] for r in rows}) == 358 # 잎 이름 35줄 겹침 — 경로로 풀림
|
||||
assert len({r["@id"] for r in rows}) == 360
|
||||
assert len({r["name"] for r in rows}) == 360 # 잎 이름 겹침 — 경로로 풀림 · 목차 오기 바로잡아 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"] == "토공 › 토사깍기 › 기계"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user