From 88007b1ad90d842f1e905cc5937dc447112f30ed Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 15 Sep 2026 19:34:15 +0900 Subject: [PATCH] =?UTF-8?q?feat(z01):=20=EA=B3=81=EA=B0=92=2017=20?= =?UTF-8?q?=EC=9D=84=20=EA=B0=92=20=EC=AA=BD=EC=9C=BC=EB=A1=9C=20=EC=98=AC?= =?UTF-8?q?=EB=A6=BC=20=C2=B7=20`@undecided`=20=E3=80=8C=EB=AF=B8=ED=8C=90?= =?UTF-8?q?=EC=A0=95=E3=80=8D=20=EB=91=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 곁값 = **없으면 값의 뜻이 안 서는 칸**(브레인 ②) — 값 단위 8(할증 둘·기계 넷·유가 둘) · 기준일 2(시세는 기준일이 값의 반) · 걸치는 범위 3 · 목재 밑수(채적 ㎥) · 기계 지급보증 밑수 비고 · 요율 적용 여부 2(임도 줄 고름·임도에 걸리는지) ⇒ 값 49 → **66** · 설명 123 → 106 - `@undecided` 「미판정」 열을 낱값 마디에 둠 · 값도 설명도 아닌 미결 셋에 `undecided: true` (목재 확정 대기 · 재료 할증 못 찾은 것 · 할증 확정 대기 후보 표) — **갈래를 임의로 안 못박음** - 낱말은 `kind: "value" | "doc"` 그대로(브레인 ③ 되물림) · `value_kinds`·`synthetic_tables` 도 그대로 - 시험 28건(2건 늘) — 곁값이 설명으로 숨거나 미결이 갈래에 박히면 빨강 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FFCnEYNH4tsS2MbHvzBhZk --- .../data_master_labels/labels_2026-01-01.json | 57 +++++++++------- resources/tester/test_master_labels_cover.py | 68 ++++++++++++++++++- 2 files changed, 99 insertions(+), 26 deletions(-) diff --git a/resources/data_master_labels/labels_2026-01-01.json b/resources/data_master_labels/labels_2026-01-01.json index e7a836e7..e59ae89f 100644 --- a/resources/data_master_labels/labels_2026-01-01.json +++ b/resources/data_master_labels/labels_2026-01-01.json @@ -2,7 +2,7 @@ "schema_version": "1.0", "dataset_id": "data_master_labels", "effective_date": "2026-01-01", - "generated_at": "2026-09-15T19:23:25+09:00", + "generated_at": "2026-09-15T19:33:14+09:00", "note": "마스터 자료의 **사람이 읽을 이름표**. 값이 아니라 이름만 담는다 — 여기를 고쳐도 계산은 안 바뀐다. Z01 마스터 관리 화면이 표·열 이름을 여기서 읽는다.", "policy": { "labels_only": "값·수식·단가를 담지 않는다. 마스터 파일은 손대지 않는다.", @@ -13,7 +13,7 @@ "out_of_31": "폴더 지문(_manifest.json) 셋은 브레인 31 밖이다. 부산물이 아니라 **지문** 갈래로 둔다 — 계산이 남긴 기록이 아니라 어느 판으로 셈했는지 못박는 표라 「정본 아님」 딱지가 안 맞는다(2026-09-15 브레인 ②).", "excluded": "강우 IDF 캐시(resources/data_rainfall_idf_cache, 96 파일)는 배수용이라 뺀다.", "row_key_column": "사전 모양(shape=map) 표는 **줄 이름(키) 자체가 한 열**이다. API 가 `@key` 로 내보내므로 열 목록 맨 앞에 `@key`(`is_row_key: true`)를 두고 표마다 제 이름을 붙인다 — 뜻이 표마다 달라 한 이름으로 뭉치지 않는다(2026-09-15 브레인 ①).", - "value_kind_rule": "값 묶음마다 `kind` 를 낱말로 적는다. `value`(값) = 원가·수량에 그대로 드는 것(한 값짜리 요율·시세·계수·원단위표·잇는 값) — 낱값 마디(`@values`)에 **보인다**. `doc`(설명) = 읽으라고 적어 둔 글(방침·출처·비고·읽기 기록·표 머리의 키·단위·기준일) — 낱값 마디에 **안 보인다**. 참·거짓이 아니라 낱말이라 수식 같은 갈래가 늘면 낱말만 더하면 된다. 가름은 「고치면 금액이 움직이나」로 한다. 움직이면 값, 안 움직이면 설명(2026-09-15 브레인 ②)." + "value_kind_rule": "값 묶음마다 `kind` 를 낱말로 적는다. `value`(값) = 원가·수량에 그대로 드는 것(한 값짜리 요율·시세·계수·원단위표·잇는 값) — 낱값 마디(`@values`)에 **보인다**. `doc`(설명) = 읽으라고 적어 둔 글(방침·출처·비고·읽기 기록·집계·표 머리의 줄 키·담은 꼴) — 낱값 마디에 **안 보인다**. 참·거짓이 아니라 낱말이라 수식 같은 갈래가 늘면 낱말만 더하면 된다. 가름은 「고치면 금액이 움직이나」로 한다. 움직이면 값, 안 움직이면 설명. 곁값(값 단위·기준일·걸치는 범위)은 **값 쪽**이다 — 없으면 값의 뜻이 안 선다. `undecided: true` 는 값도 설명도 아닌 미결이라 판정을 기다린다(2026-09-15 브레인 ②)." }, "lookup_order": { "column": [ @@ -81,6 +81,12 @@ "name_ko": "원문 자리", "unit": "", "visible": false + }, + { + "key": "@undecided", + "name_ko": "미판정", + "unit": "", + "visible": true } ], "note": "`kind: \"value\"` 인 값 묶음만 여기 선다. `kind: \"doc\"` 은 안 보인다." @@ -93,8 +99,8 @@ "value_groups": 172, "unknown": 0, "value_groups_by_kind": { - "doc": 123, - "value": 49 + "doc": 106, + "value": 66 } }, "files": [ @@ -645,7 +651,7 @@ "key": "variables/surcharge_labor/unit", "name_ko": "값 단위", "summary": "할증은 %다.", - "kind": "doc" + "kind": "value" }, { "key": "variables/surcharge_mat/duplicate_application_prohibited", @@ -663,7 +669,7 @@ "key": "variables/surcharge_mat/unit", "name_ko": "값 단위", "summary": "할증은 %다.", - "kind": "doc" + "kind": "value" } ] }, @@ -723,7 +729,8 @@ "unit": "", "visible": true } - ] + ], + "undecided": true }, { "key": "rates_pct", @@ -816,7 +823,8 @@ "key": "not_found", "name_ko": "못 찾은 것", "summary": "두 품셈을 다 뒤졌으나 이름이 없는 것.", - "kind": "doc" + "kind": "doc", + "undecided": true }, { "key": "observed_practice", @@ -1221,7 +1229,7 @@ "key": "basis_unit", "name_ko": "밑수", "summary": "값이 무엇 하나당인지.", - "kind": "doc" + "kind": "value" }, { "key": "note", @@ -1233,7 +1241,8 @@ "key": "pending_user", "name_ko": "사용자 확정 대기", "summary": "아직 사람이 답해야 닫히는 물음.", - "kind": "doc" + "kind": "doc", + "undecided": true }, { "key": "source", @@ -1585,7 +1594,7 @@ "key": "scope", "name_ko": "걸치는 범위", "summary": "어디까지 쓰는 값인지.", - "kind": "doc" + "kind": "value" } ] }, @@ -3097,7 +3106,7 @@ "key": "variables/mach_fuel_rate/unit", "name_ko": "값 단위", "summary": "연료량은 L/시간이다.", - "kind": "doc" + "kind": "value" }, { "key": "variables/mach_loss_coef/key", @@ -3109,7 +3118,7 @@ "key": "variables/mach_loss_coef/unit", "name_ko": "값 단위", "summary": "손료계수는 1/시간이다.", - "kind": "doc" + "kind": "value" }, { "key": "variables/mach_operator_map/note", @@ -3127,13 +3136,13 @@ "key": "variables/mach_price/unit", "name_ko": "값 단위", "summary": "취득가는 천원 단위다.", - "kind": "doc" + "kind": "value" }, { "key": "variables/mach_rock_adj/unit", "name_ko": "값 단위", "summary": "할증은 %다.", - "kind": "doc" + "kind": "value" } ] }, @@ -3600,13 +3609,13 @@ "key": "variables/oil_diesel/date", "name_ko": "기준일", "summary": "값이 선 날.", - "kind": "doc" + "kind": "value" }, { "key": "variables/oil_diesel/scope", "name_ko": "걸치는 범위", "summary": "어디까지 쓰는 값인지.", - "kind": "doc" + "kind": "value" }, { "key": "variables/oil_diesel/source_product_code", @@ -3624,19 +3633,19 @@ "key": "variables/oil_diesel/unit", "name_ko": "값 단위", "summary": "단가는 원/L 이다.", - "kind": "doc" + "kind": "value" }, { "key": "variables/oil_gasoline/date", "name_ko": "기준일", "summary": "값이 선 날.", - "kind": "doc" + "kind": "value" }, { "key": "variables/oil_gasoline/scope", "name_ko": "걸치는 범위", "summary": "어디까지 쓰는 값인지.", - "kind": "doc" + "kind": "value" }, { "key": "variables/oil_gasoline/source_product_code", @@ -3654,7 +3663,7 @@ "key": "variables/oil_gasoline/unit", "name_ko": "값 단위", "summary": "단가는 원/L 이다.", - "kind": "doc" + "kind": "value" } ] }, @@ -4135,7 +4144,7 @@ "key": "variables/rate_environment/forest_road_selection_status", "name_ko": "임도 줄 고름 상태", "summary": "임도에 어느 줄을 쓸지 고름이 끝났는지.", - "kind": "doc" + "kind": "value" }, { "key": "variables/rate_environment/minimum_estimated_amount_krw", @@ -4153,7 +4162,7 @@ "key": "variables/rate_equipment_payment_guarantee/base_note", "name_ko": "밑수 비고", "summary": "밑수를 그렇게 적은 까닭.", - "kind": "doc" + "kind": "value" }, { "key": "variables/rate_goyong/base", @@ -4207,7 +4216,7 @@ "key": "variables/rate_performance_guarantee_fee/typical_forest_road_applicability", "name_ko": "임도에 걸리는지", "summary": "보통 임도 규모에서 이 항목이 서는지.", - "kind": "doc" + "kind": "value" }, { "key": "variables/rate_profit/base", diff --git a/resources/tester/test_master_labels_cover.py b/resources/tester/test_master_labels_cover.py index d13ed374..26ed63b0 100644 --- a/resources/tester/test_master_labels_cover.py +++ b/resources/tester/test_master_labels_cover.py @@ -347,8 +347,8 @@ def test_값_갈래_잣대와_낱값_마디가_적혀_있다(labels): node = labels["synthetic_tables"]["@values"] assert node["name_ko"] == "낱값" assert node["summary"] == "표가 아닌 한 값들" - assert [c["key"] for c in node["columns"]] == ["@name", "@value", "@path"] - assert [c["name_ko"] for c in node["columns"]] == ["항목", "값", "원문 자리"] + assert [c["key"] for c in node["columns"]] == ["@name", "@value", "@path", "@undecided"] + assert [c["name_ko"] for c in node["columns"]] == ["항목", "값", "원문 자리", "미판정"] assert node["columns"][2]["visible"] is False @@ -383,3 +383,67 @@ def test_세어_둔_값_갈래_수가_실제와_같다(labels): for group in entry["value_groups"]: counted[group["kind"]] = counted.get(group["kind"], 0) + 1 assert counted == labels["counts"]["value_groups_by_kind"] + + +def test_곁값은_값_쪽에_선다(labels): + """값 단위·기준일·걸치는 범위는 **없으면 값의 뜻이 안 선다** — 설명으로 숨기면 + 「유가 1,858」만 떠서 리터인지 드럼인지, 언제 값인지 모른다(2026-09-15 브레인 ②).""" + want = { + "coef": ("variables/surcharge_labor/unit", "variables/surcharge_mat/unit"), + "mach_base": ( + "variables/mach_fuel_rate/unit", + "variables/mach_loss_coef/unit", + "variables/mach_price/unit", + "variables/mach_rock_adj/unit", + ), + "oil_regional": ( + "variables/oil_diesel/unit", + "variables/oil_gasoline/unit", + "variables/oil_diesel/date", + "variables/oil_gasoline/date", + "variables/oil_diesel/scope", + "variables/oil_gasoline/scope", + ), + "revetment_sabang": ("scope",), + "timber_structure_class": ("basis_unit",), + "rates": ( + "variables/rate_equipment_payment_guarantee/base_note", + "variables/rate_environment/forest_road_selection_status", + "variables/rate_performance_guarantee_fee/typical_forest_road_applicability", + ), + } + by_file = { + f["file_id"]: {g["key"]: g["kind"] for g in f["value_groups"]} for f in labels["files"] + } + bad = [] + for file_id, keys in want.items(): + for key in keys: + if by_file.get(file_id, {}).get(key) != "value": + bad.append(f"{file_id}::{key}") + assert not bad, f"곁값이 설명으로 숨었다: {bad}" + # 브레인 쪽지는 「16 쯤」이라 적었으나 짚어 준 자리를 세면 17 이다 + # (unit 8 · date 2 · scope 3 · basis_unit · 밑수 비고 · 요율 적용여부 2). + assert sum(len(v) for v in want.values()) == 17 + + +def test_미결은_미판정으로_표시된다(labels): + """값도 설명도 아닌 「미결」류 — 사용자가 트리를 본 뒤 정한다. 갈래를 임의로 못박지 않는다.""" + marked = { + f"{f['file_id']}::{g['key']}" + for f in labels["files"] + for g in f["value_groups"] + if g.get("undecided") + } | { + f"{f['file_id']}::{t['key']}" + for f in labels["files"] + for t in f["tables"] + if t.get("undecided") + } + assert marked == { + "timber_structure_class::pending_user", + "material_surcharge::not_found", + "material_surcharge::candidates_pending_user/items", + } + column = labels["synthetic_tables"]["@values"]["columns"][3] + assert column["key"] == "@undecided" + assert column["name_ko"] == "미판정"