diff --git a/Z01_MasterData/Z01_MasterData_Tables.py b/Z01_MasterData/Z01_MasterData_Tables.py index 852cbb95..55b94e2e 100644 --- a/Z01_MasterData/Z01_MasterData_Tables.py +++ b/Z01_MasterData/Z01_MasterData_Tables.py @@ -26,14 +26,13 @@ ROW_KEY = "@key" VALUES_TABLE = "@values" _CODE_HIDDEN = {"@path"} # 낱값 표의 영문 자리 — 이름표에 없을 때만 -# ⚠ 잠정 — 이름표가 값 묶음마다 「값이냐 설명이냐」 표시를 주면 **이 자리만** 갈아끼움(브레인 ④) -_DESCRIPTION_KEYS = {"note", "notes", "policy", "source", "sources", "quote", "why"} - - -def is_description(group_key: str) -> bool: - """값 묶음이 설명·방침인가(값이 아님) — 끝 조각으로 가름.""" - last = group_key.rsplit("/", 1)[-1] - return last in _DESCRIPTION_KEYS or last.endswith("_note") +# 값 묶음 갈래 — 이름표 `value_groups[].role`(데스크탑 서브) · 낱말이 바뀌면 이 자리만 갈아끼움 +ROLE_FIELD = "role" +SHOWN_ROLE = "value" # 원가·수량에 드는 값 — 낱값 표에 보임 +HIDDEN_ROLE = "note" # 읽으라고 적은 글 — 안 보임 +# 이름표가 안 가른(또는 모르는 낱말) 마디 — 조용히 빼지도 섞지도 않고 값으로 보이되 드러냄(브레인) +UNDECIDED_COLUMN = "@undecided" +UNDECIDED = "미판정" # 파일 머리 — 표도 값 묶음도 아님(이름표 생성기와 같은 목록) @@ -106,18 +105,24 @@ def values_of(path: Path) -> dict[str, Any]: return _scan(str(path), path.stat().st_mtime_ns)[1] -def _value_rows(fid: str, path: Path, labels: dict[str, Any]) -> list[dict[str, Any]]: - """낱값 표 줄 — 찾는 차례: 「파일id::key」 → 「끝 조각」 → 영문 key.""" +def _value_rows(entry: dict[str, Any], path: Path, labels: dict[str, Any]) -> list[dict[str, Any]]: + """낱값 표 줄 — 이름 찾는 차례: 「파일id::key」 → 「끝 조각」 → 영문 key.""" + fid = entry["file_id"] + roles = {g.get("key"): g.get(ROLE_FIELD) for g in entry.get("value_groups") or []} rows = [] for key, value in values_of(path).items(): - if is_description(key): + role = roles.get(key) + if role == HIDDEN_ROLE: continue named = ( (labels.get("value_overrides") or {}).get(f"{fid}::{key}") or (labels.get("value_keys") or {}).get(key.rsplit("/", 1)[-1]) or {} ) - rows.append({"@name": named.get("name_ko") or key, "@value": value, "@path": key}) + row = {"@name": named.get("name_ko") or key, "@value": value, "@path": key} + if role != SHOWN_ROLE: + row[UNDECIDED_COLUMN] = UNDECIDED + rows.append(row) return rows @@ -153,7 +158,7 @@ def tree() -> dict[str, Any]: kind = entry.get("kind") or "" group = groups.setdefault(kind, {"key": kind, "label": kind, "files": []}) counts = {tid: len(rows) for tid, rows in tables_of(path).items()} - value_count = len(_value_rows(entry["file_id"], path, labels)) + value_count = len(_value_rows(entry, path, labels)) if value_count: counts[VALUES_TABLE] = value_count group["files"].append( @@ -178,11 +183,13 @@ def rows( entry = next((f for f in labels["files"] if f["file_id"] == fid), None) path = _file_path(entry) if entry else None if path and tid == VALUES_TABLE: - table = _value_rows(fid, path, labels) or None + table = _value_rows(entry, path, labels) or None else: table = tables_of(path).get(tid) if path else None if table is None: return None + own = next((t for t in entry.get("tables") or [] if t.get("key") == tid), {}) + own_columns = {c.get("key"): c for c in own.get("columns") or []} keys: dict[str, None] = {} for r in table: keys.update(dict.fromkeys(r)) @@ -195,8 +202,13 @@ def rows( size = max(1, min(size, MAX_PAGE_SIZE)) start = (max(page, 1) - 1) * size columns = [] - for key in keys: # 찾는 차례: 「파일id/열key」 → 「열key」 → 영문 key - named = labels["column_overrides"].get(f"{fid}/{key}") or labels["columns"].get(key) or {} + for key in keys: # 찾는 차례: 그 표 열 목록(줄 이름 열 `@key` 는 표마다 제 이름) → 「파일id/열key」 → 「열key」 → 영문 key + named = ( + own_columns.get(key) + or labels["column_overrides"].get(f"{fid}/{key}") + or labels["columns"].get(key) + or {} + ) columns.append( { "key": key, diff --git a/resources/data_master_labels/labels_2026-01-01.json b/resources/data_master_labels/labels_2026-01-01.json index ae9fbe4b..e7a836e7 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:16:20+09:00", + "generated_at": "2026-09-15T19:23:25+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_role_rule": "값 묶음은 갈래가 둘이다. **값** = 원가·수량에 그대로 드는 것(한 값짜리 요율·시세·계수·원단위표·잇는 값) — 낱값 마디(`@values`)에 **보인다**. **설명** = 읽으라고 적어 둔 글(방침·출처·비고·읽기 기록·표 머리의 키·단위·기준일) — 낱값 마디에 **안 보인다**. 가름은 「고치면 금액이 움직이나」로 한다. 움직이면 값, 안 움직이면 설명(2026-09-15 브레인 ②)." + "value_kind_rule": "값 묶음마다 `kind` 를 낱말로 적는다. `value`(값) = 원가·수량에 그대로 드는 것(한 값짜리 요율·시세·계수·원단위표·잇는 값) — 낱값 마디(`@values`)에 **보인다**. `doc`(설명) = 읽으라고 적어 둔 글(방침·출처·비고·읽기 기록·표 머리의 키·단위·기준일) — 낱값 마디에 **안 보인다**. 참·거짓이 아니라 낱말이라 수식 같은 갈래가 늘면 낱말만 더하면 된다. 가름은 「고치면 금액이 움직이나」로 한다. 움직이면 값, 안 움직이면 설명(2026-09-15 브레인 ②)." }, "lookup_order": { "column": [ @@ -49,24 +49,51 @@ "summary": "앞으로 늘려 갈 첫 벌. 모양을 잡아 두려고 먼저 놓은 것이다." } }, - "value_roles": { + "value_kinds": { "value": { "name_ko": "값", "summary": "원가·수량에 그대로 드는 값. 낱값 마디에 **보인다** — 여기서 사라지면 트리에서 값이 사라진다." }, - "note": { + "doc": { "name_ko": "설명", "summary": "읽으라고 적어 둔 글(방침·출처·비고·기록). 낱값 마디에 **안 보인다** — 보이면 화면이 흐려진다." } }, + "synthetic_tables": { + "@values": { + "name_ko": "낱값", + "summary": "표가 아닌 한 값들", + "columns": [ + { + "key": "@name", + "name_ko": "항목", + "unit": "", + "visible": true + }, + { + "key": "@value", + "name_ko": "값", + "unit": "", + "visible": true + }, + { + "key": "@path", + "name_ko": "원문 자리", + "unit": "", + "visible": false + } + ], + "note": "`kind: \"value\"` 인 값 묶음만 여기 선다. `kind: \"doc\"` 은 안 보인다." + } + }, "counts": { "files": 34, "tables": 91, "columns": 455, "value_groups": 172, "unknown": 0, - "value_groups_by_role": { - "note": 123, + "value_groups_by_kind": { + "doc": 123, "value": 49 } }, @@ -150,13 +177,13 @@ "key": "variables/pum/normalization_status", "name_ko": "정돈 상태", "summary": "자원 코드 정돈이 끝났는지.", - "role": "note" + "kind": "doc" }, { "key": "variables/pum/representation", "name_ko": "담은 꼴", "summary": "원문 표를 어떤 꼴로 담았는지.", - "role": "note" + "kind": "doc" } ] }, @@ -245,13 +272,13 @@ "key": "variables/pum/normalization_status", "name_ko": "정돈 상태", "summary": "자원 코드 정돈이 끝났는지.", - "role": "note" + "kind": "doc" }, { "key": "variables/pum/representation", "name_ko": "담은 꼴", "summary": "원문 표를 어떤 꼴로 담았는지.", - "role": "note" + "kind": "doc" } ] }, @@ -364,13 +391,13 @@ "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" }, { "key": "stats", "name_ko": "집계", "summary": "몇 줄이 섰고 몇 줄이 빠졌는지 센 것.", - "role": "note" + "kind": "doc" } ] }, @@ -576,67 +603,67 @@ "key": "variables/coef_soil_C/duplicate_note", "name_ko": "원문 중복 비고", "summary": "원문이 같은 줄을 두 번 실어 어느 줄을 쓸지 남은 자리.", - "role": "note" + "kind": "doc" }, { "key": "variables/coef_soil_C/key", "name_ko": "줄을 가르는 칸", "summary": "이 표에서 줄 하나를 집는 칸 이름.", - "role": "note" + "kind": "doc" }, { "key": "variables/coef_soil_L/duplicate_note", "name_ko": "원문 중복 비고", "summary": "원문이 같은 줄을 두 번 실어 어느 줄을 쓸지 남은 자리.", - "role": "note" + "kind": "doc" }, { "key": "variables/coef_soil_L/key", "name_ko": "줄을 가르는 칸", "summary": "이 표에서 줄 하나를 집는 칸 이름.", - "role": "note" + "kind": "doc" }, { "key": "variables/rate_tool", "name_ko": "공구손료율", "summary": "주요 재료비에 곱하는 공구손료 율(하한·상한). 어느 값을 쓸지는 고름이 필요하다.", - "role": "value" + "kind": "value" }, { "key": "variables/surcharge_labor/application_status", "name_ko": "적용 상태", "summary": "쓰려면 무엇이 더 정해져야 하는지.", - "role": "note" + "kind": "doc" }, { "key": "variables/surcharge_labor/representation", "name_ko": "담은 꼴", "summary": "원문 표를 어떤 꼴로 담았는지.", - "role": "note" + "kind": "doc" }, { "key": "variables/surcharge_labor/unit", "name_ko": "값 단위", "summary": "할증은 %다.", - "role": "note" + "kind": "doc" }, { "key": "variables/surcharge_mat/duplicate_application_prohibited", "name_ko": "중복 적용 금지", "summary": "두 자리에 걸면 두 번 세게 된다는 표시.", - "role": "note" + "kind": "doc" }, { "key": "variables/surcharge_mat/representation", "name_ko": "담은 꼴", "summary": "원문 표를 어떤 꼴로 담았는지.", - "role": "note" + "kind": "doc" }, { "key": "variables/surcharge_mat/unit", "name_ko": "값 단위", "summary": "할증은 %다.", - "role": "note" + "kind": "doc" } ] }, @@ -783,25 +810,25 @@ "key": "candidates_pending_user/note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "not_found", "name_ko": "못 찾은 것", "summary": "두 품셈을 다 뒤졌으나 이름이 없는 것.", - "role": "note" + "kind": "doc" }, { "key": "observed_practice", "name_ko": "실무 관측", "summary": "실무 집계에서 본 할증률. 참고이지 기본값이 아니다.", - "role": "value" + "kind": "value" }, { "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" } ] }, @@ -944,55 +971,55 @@ "key": "euroform_type/material_unit_note", "name_ko": "자재 밑수 비고", "summary": "자재 환산을 어느 창이 하는지.", - "role": "note" + "kind": "doc" }, { "key": "euroform_type/note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "euroform_type/reuse_note", "name_ko": "사용횟수 비고", "summary": "닮은 두 사용횟수를 하나로 잇지 않는 까닭.", - "role": "note" + "kind": "doc" }, { "key": "euroform_type/source", "name_ko": "출처", "summary": "어느 원문에서 왔는지.", - "role": "note" + "kind": "doc" }, { "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" }, { "key": "reuse_ratio_pct", "name_ko": "사용횟수별 기준수량 비율", "summary": "품셈 12-4 — 일위대가 재료비에 걸린다. B08 이 곱하면 이중계상이다.", - "role": "value" + "kind": "value" }, { "key": "shoring", "name_ko": "동바리", "summary": "강관동바리가 어느 구조물에 서는지.", - "role": "value" + "kind": "value" }, { "key": "source", "name_ko": "출처", "summary": "어느 원문에서 왔는지.", - "role": "note" + "kind": "doc" } ] }, @@ -1081,25 +1108,25 @@ "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" }, { "key": "price_hint_krw_per_ton", "name_ko": "단가 참고값", "summary": "표시 전용 — 갈래 차이를 사람이 보라고 둔 값. 계산에 안 들어간다.", - "role": "value" + "kind": "value" }, { "key": "source", "name_ko": "출처", "summary": "어느 원문에서 왔는지.", - "role": "note" + "kind": "doc" } ] }, @@ -1194,25 +1221,25 @@ "key": "basis_unit", "name_ko": "밑수", "summary": "값이 무엇 하나당인지.", - "role": "note" + "kind": "doc" }, { "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "pending_user", "name_ko": "사용자 확정 대기", "summary": "아직 사람이 답해야 닫히는 물음.", - "role": "note" + "kind": "doc" }, { "key": "source", "name_ko": "출처", "summary": "어느 원문에서 왔는지.", - "role": "note" + "kind": "doc" } ] }, @@ -1250,43 +1277,43 @@ "key": "back_length/note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "back_length/option_key", "name_ko": "고르개 키", "summary": "화면 고르개가 쓰는 저장 칸 이름.", - "role": "value" + "kind": "value" }, { "key": "back_length/source", "name_ko": "출처", "summary": "어느 원문에서 왔는지.", - "role": "note" + "kind": "doc" }, { "key": "bond", "name_ko": "쌓기 방식", "summary": "메쌓기·찰쌓기로 공종코드가 갈린다.", - "role": "value" + "kind": "value" }, { "key": "boulder_diameter", "name_ko": "돌 직경 갈래", "summary": "큰돌쌓기 — 저장 제원 stone_cm 으로 갈린다.", - "role": "value" + "kind": "value" }, { "key": "face_slope", "name_ko": "전면 기울기", "summary": "형식마다 정해진 1:n. 코드 기본 0.3 이 여기서 왔다.", - "role": "value" + "kind": "value" }, { "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" } ] }, @@ -1331,31 +1358,31 @@ "key": "not_here", "name_ko": "이 표가 안 다루는 것", "summary": "헷갈리기 쉬운 이웃 값을 여기 안 둔다고 적어 둔 자리.", - "role": "note" + "kind": "doc" }, { "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" }, { "key": "source", "name_ko": "출처", "summary": "어느 원문에서 왔는지.", - "role": "note" + "kind": "doc" }, { "key": "steps_m", "name_ko": "직고 칸", "summary": "표가 갈리는 높이 경계(m).", - "role": "value" + "kind": "value" } ] }, @@ -1371,37 +1398,37 @@ "key": "not_here", "name_ko": "이 표가 안 다루는 것", "summary": "헷갈리기 쉬운 이웃 값을 여기 안 둔다고 적어 둔 자리.", - "role": "note" + "kind": "doc" }, { "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" }, { "key": "source", "name_ko": "출처", "summary": "어느 원문에서 왔는지.", - "role": "note" + "kind": "doc" }, { "key": "steps_m", "name_ko": "직고 칸", "summary": "표가 갈리는 높이 경계(m).", - "role": "value" + "kind": "value" }, { "key": "table_cm", "name_ko": "뒷길이 범위표", "summary": "메·찰마다 직고 칸별 뒷길이 하한·상한(㎝).", - "role": "value" + "kind": "value" } ] }, @@ -1452,79 +1479,79 @@ "key": "back_lengths_cm", "name_ko": "뒷길이 규격", "summary": "표가 값을 주는 뒷길이 일곱 칸.", - "role": "value" + "kind": "value" }, { "key": "backfill_ratio_of_back_length", "name_ko": "뒤채움 몫", "summary": "뒷길이 가운데 뒤채움이 차지하는 비. 나머지가 잡석이다.", - "role": "value" + "kind": "value" }, { "key": "fallback", "name_ko": "미지정일 때", "summary": "사용자가 안 고른 경우 쓰는 한 벌.", - "role": "value" + "kind": "value" }, { "key": "fill_concrete_m3_per_m2", "name_ko": "채움 콘크리트 원단위", "summary": "돌 종류 × 뒷길이마다 ㎥/㎡. 원문이 두 줄뿐이다.", - "role": "value" + "kind": "value" }, { "key": "kinds", "name_ko": "돌 종류", "summary": "야면석·호박돌 / 깬잡석 / 깬돌 / 견치돌 네 줄.", - "role": "value" + "kind": "value" }, { "key": "no_folding", "name_ko": "접지 않음 규칙", "summary": "표에 없는 뒷길이를 가까운 칸으로 접지 않는다 — 접으면 값이 조용히 틀린다.", - "role": "note" + "kind": "doc" }, { "key": "not_here", "name_ko": "이 표가 안 다루는 것", "summary": "헷갈리기 쉬운 이웃 값을 여기 안 둔다고 적어 둔 자리.", - "role": "note" + "kind": "doc" }, { "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "option_key", "name_ko": "고르개 키", "summary": "화면 고르개가 쓰는 저장 칸 이름.", - "role": "value" + "kind": "value" }, { "key": "option_note", "name_ko": "고르개 비고", "summary": "고르개를 만들 때 챙길 것.", - "role": "note" + "kind": "doc" }, { "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" }, { "key": "wedge_stone_m3_per_m2", "name_ko": "고임돌 원단위", "summary": "돌 종류 × 뒷길이마다 ㎥/㎡. null 은 원문 「-」다.", - "role": "value" + "kind": "value" }, { "key": "why", "name_ko": "까닭", "summary": "이 벌을 왜 따로 두는지.", - "role": "note" + "kind": "doc" } ] }, @@ -1540,25 +1567,25 @@ "key": "items", "name_ko": "항목", "summary": "이 벌이 담는 항목 묶음.", - "role": "value" + "kind": "value" }, { "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "rank", "name_ko": "우선순위", "summary": "원문끼리 어긋날 때 어느 쪽을 먼저 보는지.", - "role": "note" + "kind": "doc" }, { "key": "scope", "name_ko": "걸치는 범위", "summary": "어디까지 쓰는 값인지.", - "role": "note" + "kind": "doc" } ] }, @@ -1834,37 +1861,37 @@ "key": "double_count_rules/note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "not_found/note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "pending_choices/note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" }, { "key": "原文_뒷받침/note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" } ] }, @@ -2307,67 +2334,67 @@ "key": "composite/note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "composite/unit_conversion", "name_ko": "단위 환산", "summary": "단가 단위와 원단위 단위가 달라 맞추는 값.", - "role": "value" + "kind": "value" }, { "key": "concrete_placing", "name_ko": "콘크리트 타설 잇기", "summary": "타설 방식 × 구조물 종류로 공종이 갈리는 자리.", - "role": "value" + "kind": "value" }, { "key": "ground_aliases_moved_to", "name_ko": "갈래 별칭 옮긴 자리", "summary": "갈래 이름 별칭은 별칭표로 옮겼다. 여기 다시 두지 않는다.", - "role": "note" + "kind": "doc" }, { "key": "masonry_class_reference", "name_ko": "돌쌓기 갈래 참고 자리", "summary": "돌쌓기 갈래는 이제 참고용이고 어긋나면 그것이 신호다.", - "role": "note" + "kind": "doc" }, { "key": "master", "name_ko": "마스터 자리", "summary": "공종 코드를 읽어 오는 마스터 파일.", - "role": "note" + "kind": "doc" }, { "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "pending_user/note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "pipe", "name_ko": "배수관 잇기", "summary": "관종으로 공종이 갈린다. 관 정본은 pipe_points 다.", - "role": "value" + "kind": "value" }, { "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" }, { "key": "variant_contract", "name_ko": "갈래 키 계약", "summary": "갈래 키 문자열을 두 창이 각자 조립하지 않기로 한 약속.", - "role": "note" + "kind": "doc" } ] }, @@ -2429,7 +2456,7 @@ "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" } ] }, @@ -2491,13 +2518,13 @@ "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" } ] }, @@ -2592,19 +2619,19 @@ "key": "variables/aliases", "name_ko": "노임 별칭", "summary": "코드 대신 쓰는 별칭 이름표(labor_1002 = 보통인부).", - "role": "value" + "kind": "value" }, { "key": "variables/labor_rate/key", "name_ko": "줄을 가르는 칸", "summary": "이 표에서 줄 하나를 집는 칸 이름.", - "role": "note" + "kind": "doc" }, { "key": "variables/labor_rate/reliability_note", "name_ko": "신뢰도 기호 뜻풀이", "summary": "노임 표의 * · ** 가 무엇을 뜻하는지.", - "role": "note" + "kind": "doc" } ] }, @@ -2705,7 +2732,7 @@ "key": "variables/labor_mfg/key", "name_ko": "줄을 가르는 칸", "summary": "이 표에서 줄 하나를 집는 칸 이름.", - "role": "note" + "kind": "doc" } ] }, @@ -3052,61 +3079,61 @@ "key": "parse_audit", "name_ko": "읽기 점검", "summary": "취득가 표에서 못 읽은 줄이 있는지 적어 둔 자리.", - "role": "note" + "kind": "doc" }, { "key": "variables/mach_fuel_rate/key", "name_ko": "줄을 가르는 칸", "summary": "이 표에서 줄 하나를 집는 칸 이름.", - "role": "note" + "kind": "doc" }, { "key": "variables/mach_fuel_rate/note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "variables/mach_fuel_rate/unit", "name_ko": "값 단위", "summary": "연료량은 L/시간이다.", - "role": "note" + "kind": "doc" }, { "key": "variables/mach_loss_coef/key", "name_ko": "줄을 가르는 칸", "summary": "이 표에서 줄 하나를 집는 칸 이름.", - "role": "note" + "kind": "doc" }, { "key": "variables/mach_loss_coef/unit", "name_ko": "값 단위", "summary": "손료계수는 1/시간이다.", - "role": "note" + "kind": "doc" }, { "key": "variables/mach_operator_map/note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "variables/mach_price/key", "name_ko": "줄을 가르는 칸", "summary": "이 표에서 줄 하나를 집는 칸 이름.", - "role": "note" + "kind": "doc" }, { "key": "variables/mach_price/unit", "name_ko": "값 단위", "summary": "취득가는 천원 단위다.", - "role": "note" + "kind": "doc" }, { "key": "variables/mach_rock_adj/unit", "name_ko": "값 단위", "summary": "할증은 %다.", - "role": "note" + "kind": "doc" } ] }, @@ -3186,31 +3213,31 @@ "key": "derived_from", "name_ko": "무엇에서 나왔나", "summary": "어느 벌을 풀어 만든 파생본인지.", - "role": "note" + "kind": "doc" }, { "key": "dropped_tables", "name_ko": "못 읽어 버린 표", "summary": "칸 수가 안 맞아 못 읽고 버린 원문 표.", - "role": "note" + "kind": "doc" }, { "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" }, { "key": "stats", "name_ko": "집계", "summary": "몇 줄이 섰고 몇 줄이 빠졌는지 센 것.", - "role": "note" + "kind": "doc" } ] }, @@ -3368,19 +3395,19 @@ "key": "selection_policy", "name_ko": "고르는 방침", "summary": "같은 물품이 여러 번 공고되면 어느 것을 쓰는지.", - "role": "note" + "kind": "doc" }, { "key": "source_row_count", "name_ko": "원천 줄 수", "summary": "걸러 내기 전 원본 줄 수.", - "role": "note" + "kind": "doc" }, { "key": "variables/mat_price/key", "name_ko": "줄을 가르는 칸", "summary": "이 표에서 줄 하나를 집는 칸 이름.", - "role": "note" + "kind": "doc" } ] }, @@ -3567,67 +3594,67 @@ "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "variables/oil_diesel/date", "name_ko": "기준일", "summary": "값이 선 날.", - "role": "note" + "kind": "doc" }, { "key": "variables/oil_diesel/scope", "name_ko": "걸치는 범위", "summary": "어디까지 쓰는 값인지.", - "role": "note" + "kind": "doc" }, { "key": "variables/oil_diesel/source_product_code", "name_ko": "원천 제품코드", "summary": "원천이 쓰는 제품 코드.", - "role": "note" + "kind": "doc" }, { "key": "variables/oil_diesel/source_product_name", "name_ko": "원천 제품명", "summary": "원천이 쓰는 제품 이름.", - "role": "note" + "kind": "doc" }, { "key": "variables/oil_diesel/unit", "name_ko": "값 단위", "summary": "단가는 원/L 이다.", - "role": "note" + "kind": "doc" }, { "key": "variables/oil_gasoline/date", "name_ko": "기준일", "summary": "값이 선 날.", - "role": "note" + "kind": "doc" }, { "key": "variables/oil_gasoline/scope", "name_ko": "걸치는 범위", "summary": "어디까지 쓰는 값인지.", - "role": "note" + "kind": "doc" }, { "key": "variables/oil_gasoline/source_product_code", "name_ko": "원천 제품코드", "summary": "원천이 쓰는 제품 코드.", - "role": "note" + "kind": "doc" }, { "key": "variables/oil_gasoline/source_product_name", "name_ko": "원천 제품명", "summary": "원천이 쓰는 제품 이름.", - "role": "note" + "kind": "doc" }, { "key": "variables/oil_gasoline/unit", "name_ko": "값 단위", "summary": "단가는 원/L 이다.", - "role": "note" + "kind": "doc" } ] }, @@ -4084,175 +4111,175 @@ "key": "processing_rules", "name_ko": "셈 규칙", "summary": "요율만으로는 금액이 안 나온다 — 밑수를 어떻게 만들고 어디서 자르는지 적은 자리. 코드와 어긋나면 거울 시험이 잡는다.", - "role": "note" + "kind": "doc" }, { "key": "variables/rate_asbestos_contribution", "name_ko": "석면피해구제 분담금", "summary": "노무비에 곱하는 율.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_care", "name_ko": "노인장기요양보험료", "summary": "건강보험료 금액에 곱하는 율.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_environment/base", "name_ko": "밑수", "summary": "이 요율을 곱하는 대상.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_environment/forest_road_selection_status", "name_ko": "임도 줄 고름 상태", "summary": "임도에 어느 줄을 쓸지 고름이 끝났는지.", - "role": "note" + "kind": "doc" }, { "key": "variables/rate_environment/minimum_estimated_amount_krw", "name_ko": "적용 하한 — 추정금액", "summary": "이 금액에 못 미치면 줄이 안 선다(원).", - "role": "value" + "kind": "value" }, { "key": "variables/rate_equipment_payment_guarantee/base", "name_ko": "밑수", "summary": "이 요율을 곱하는 대상.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_equipment_payment_guarantee/base_note", "name_ko": "밑수 비고", "summary": "밑수를 그렇게 적은 까닭.", - "role": "note" + "kind": "doc" }, { "key": "variables/rate_goyong/base", "name_ko": "밑수", "summary": "이 요율을 곱하는 대상.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_health", "name_ko": "건강보험료", "summary": "직접노무비에 곱하는 율.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_indirect_labor/base", "name_ko": "밑수", "summary": "이 요율을 곱하는 대상.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_other_expense/base", "name_ko": "밑수", "summary": "이 요율을 곱하는 대상.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_overhead/base", "name_ko": "밑수", "summary": "이 요율을 곱하는 대상.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_pension/base", "name_ko": "밑수", "summary": "이 요율을 곱하는 대상.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_pension/rate_from_2033_percent", "name_ko": "2033년 이후 요율", "summary": "표 밖으로 나가는 해부터 쓰는 율(%).", - "role": "value" + "kind": "value" }, { "key": "variables/rate_performance_guarantee_fee/base", "name_ko": "밑수", "summary": "이 요율을 곱하는 대상.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_performance_guarantee_fee/typical_forest_road_applicability", "name_ko": "임도에 걸리는지", "summary": "보통 임도 규모에서 이 항목이 서는지.", - "role": "note" + "kind": "doc" }, { "key": "variables/rate_profit/base", "name_ko": "밑수", "summary": "이 요율을 곱하는 대상.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_retirement_mutual_aid", "name_ko": "퇴직공제부금비", "summary": "직접노무비에 곱하는 율. 추정금액 하한이 있다.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_safety_base", "name_ko": "산업안전보건관리비 기초액", "summary": "요율표 안에 함께 든 기초액이 걸리는 구간.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_safety_pct/base_rule", "name_ko": "밑수 규칙", "summary": "밑수를 고르는 방식.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_safety_pct/base_with_owner_supplied_material", "name_ko": "대상액 — 관급자재 있을 때", "summary": "고시가 준 두 식 가운데 작은 쪽.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_safety_pct/base_without_owner_supplied_material", "name_ko": "대상액 — 관급자재 없을 때", "summary": "재료비 + 직접노무비.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_safety_pct/manager_thresholds", "name_ko": "안전관리자 선임 기준액", "summary": "안전관리자를 둬야 하는 금액 문턱.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_safety_pct/minimum_total_construction_amount_krw", "name_ko": "적용 하한 — 총공사금액", "summary": "이 금액에 못 미치면 줄이 안 선다(원).", - "role": "value" + "kind": "value" }, { "key": "variables/rate_sanjae", "name_ko": "산재보험료", "summary": "노무비에 곱하는 율.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_subcontract_payment_guarantee/base", "name_ko": "밑수", "summary": "이 요율을 곱하는 대상.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_vat", "name_ko": "부가가치세", "summary": "공급가액에 곱하는 율.", - "role": "value" + "kind": "value" }, { "key": "variables/rate_wage_claim_contribution", "name_ko": "임금채권보장 부담금", "summary": "노무비에 곱하는 율.", - "role": "value" + "kind": "value" } ] }, @@ -4302,7 +4329,7 @@ "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" } ] }, @@ -4358,7 +4385,7 @@ "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" } ] }, @@ -4462,13 +4489,13 @@ "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" }, { "key": "stats", "name_ko": "집계", "summary": "몇 줄이 섰고 몇 줄이 빠졌는지 센 것.", - "role": "note" + "kind": "doc" } ] }, @@ -4518,7 +4545,7 @@ "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" } ] }, @@ -4747,49 +4774,49 @@ "key": "code", "name_ko": "항목 코드", "summary": "라이브러리 항목 코드.", - "role": "note" + "kind": "doc" }, { "key": "differs_from_engine", "name_ko": "전개와 다른 자리", "summary": "지금 돌아가는 전개와 이 양식이 갈리는 곳을 적어 둔 자리.", - "role": "note" + "kind": "doc" }, { "key": "item_kind", "name_ko": "항목 갈래", "summary": "양식형(form)인지 고정형인지.", - "role": "note" + "kind": "doc" }, { "key": "library_tier", "name_ko": "라이브러리 단", "summary": "프로그램·회사·개인 가운데 어느 단의 항목인지.", - "role": "note" + "kind": "doc" }, { "key": "name", "name_ko": "항목 이름", "summary": "화면에 보이는 이름.", - "role": "note" + "kind": "doc" }, { "key": "note", "name_ko": "설명", "summary": "이 벌이 무엇이고 어떻게 쓰는지 적은 글.", - "role": "note" + "kind": "doc" }, { "key": "type_id", "name_ko": "구조물 종류 키", "summary": "어느 구조물 종류에 붙는 양식인지.", - "role": "note" + "kind": "doc" }, { "key": "unit_price/note", "name_ko": "일위대가 틀 설명", "summary": "일위대가 줄을 어떻게 세우는지 적은 글.", - "role": "note" + "kind": "doc" } ] }, @@ -4932,7 +4959,7 @@ "key": "policy", "name_ko": "방침", "summary": "이 벌을 다룰 때 지킬 것 — 지어내지 않기·빈칸 안 두기 같은 것.", - "role": "note" + "kind": "doc" } ] }, @@ -4976,7 +5003,7 @@ "key": "built_by", "name_ko": "만든 자리", "summary": "이 벌을 지은 코드나 사람.", - "role": "note" + "kind": "doc" } ] }, @@ -5020,13 +5047,13 @@ "key": "built_by", "name_ko": "만든 자리", "summary": "이 벌을 지은 코드나 사람.", - "role": "note" + "kind": "doc" }, { "key": "source", "name_ko": "출처", "summary": "어느 원문에서 왔는지.", - "role": "note" + "kind": "doc" } ] } diff --git a/resources/tester/test_master_labels_cover.py b/resources/tester/test_master_labels_cover.py index 250a6672..d13ed374 100644 --- a/resources/tester/test_master_labels_cover.py +++ b/resources/tester/test_master_labels_cover.py @@ -330,26 +330,40 @@ def test_줄_이름_열의_이름이_표마다_제_것이다(labels): assert duplicated <= {"출처 키"}, f"줄 이름이 겹친다: {sorted(duplicated)}" -def test_값_묶음마다_값이냐_설명이냐가_적혀_있다(labels): - known = set(labels["value_roles"]) +def test_값_묶음마다_kind_가_낱말로_적혀_있다(labels): + known = set(labels["value_kinds"]) bad = [] for entry in labels["files"]: for group in entry["value_groups"]: - if group.get("role") not in known: + if group.get("kind") not in known: bad.append(f"{entry['file_id']}::{group['key']} — {group.get('role')!r}") assert not bad, f"값·설명이 안 갈린 값 묶음: {bad}" -def test_값_갈래_잣대가_이름표에_적혀_있다(labels): - rule = labels["policy"]["value_role_rule"] +def test_값_갈래_잣대와_낱값_마디가_적혀_있다(labels): + rule = labels["policy"]["value_kind_rule"] assert "금액이 움직이나" in rule assert labels["policy"]["row_key_column"] + 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 node["columns"][2]["visible"] is False + + +def test_값_갈래는_참거짓이_아니라_낱말이다(labels): + """수식 같은 갈래가 늘면 낱말만 더하면 된다 — 참·거짓이면 그때 뜻이 뒤집힌다.""" + for entry in labels["files"]: + for group in entry["value_groups"]: + assert isinstance(group["kind"], str), f"{entry['file_id']}::{group['key']}" + assert set(labels["value_kinds"]) == {"value", "doc"} def test_한_값짜리_요율은_값으로_선다(labels): """산재 3.56 % 처럼 원가에 그대로 드는 요율이 설명으로 숨으면 트리에서 사라진다.""" rates = next(f for f in labels["files"] if f["file_id"] == "rates") - by_key = {g["key"]: g["role"] for g in rates["value_groups"]} + by_key = {g["key"]: g["kind"] for g in rates["value_groups"]} for key in ( "variables/rate_sanjae", "variables/rate_health", @@ -360,12 +374,12 @@ def test_한_값짜리_요율은_값으로_선다(labels): "variables/rate_asbestos_contribution", ): assert by_key.get(key) == "value", f"{key} 가 값으로 안 섰다" - assert by_key.get("processing_rules") == "note" + assert by_key.get("processing_rules") == "doc" -def test_세어_둔_갈래_수가_실제와_같다(labels): +def test_세어_둔_값_갈래_수가_실제와_같다(labels): counted: dict[str, int] = {} for entry in labels["files"]: for group in entry["value_groups"]: - counted[group["role"]] = counted.get(group["role"], 0) + 1 - assert counted == labels["counts"]["value_groups_by_role"] + counted[group["kind"]] = counted.get(group["kind"], 0) + 1 + assert counted == labels["counts"]["value_groups_by_kind"] diff --git a/resources/tester/test_z01_master_data.py b/resources/tester/test_z01_master_data.py index 7b5d77ef..73f28243 100644 --- a/resources/tester/test_z01_master_data.py +++ b/resources/tester/test_z01_master_data.py @@ -107,8 +107,10 @@ def test_묶음_표는_줄마다_이름_열(client: TestClient) -> None: assert slope["rows"][0]["성토"] == [0.3, 0.35, 0.4, 0.45, 0.5] cols = {c["key"]: c for c in slope["columns"]} assert cols["성토"]["label"] == "성토부 경사" and cols["성토"]["unit"] == "1:n" + assert cols["@key"]["label"] == "쌓기 방식" # 줄 이름 열은 표마다 제 이름(이름표 표 열 목록) fx = {c["key"]: c for c in _rows(client, file="fx", table="variables")["columns"]} assert fx["value"]["label"] == "환율" and fx["value"]["unit"] == "원" # 「fx/value」 덮어쓰기 + assert fx["@key"]["label"] == "통화" def test_숨김은_이름표의_보임에서(client: TestClient) -> None: @@ -167,8 +169,8 @@ def test_찾는_차례와_없는_이름은_영문_key( ) -def test_낱값은_파일마다_표_하나_설명류는_뺌(client: TestClient) -> None: - """브레인 ④ — 표 아닌 마디(값 묶음)도 트리에 `@values` 한 마디로 · 설명·방침은 값이 아님.""" +def test_낱값은_파일마다_표_하나_설명은_뺌(client: TestClient) -> None: + """브레인 ④ — 표 아닌 마디(값 묶음)도 트리에 `@values` 한 마디로 · 값/설명 가름은 이름표 `role`.""" files = _files(client) rates = files["rates"]["tables"] assert rates[-1]["id"] == "@values" @@ -183,20 +185,57 @@ def test_낱값은_파일마다_표_하나_설명류는_뺌(client: TestClient) "@value": False, "@path": True, } - master = {r["@path"] for r in _rows(client, file="work_item_master", table="@values")["rows"]} - assert master == {"stats"} # policy 는 방침 - assert "@values" not in {t["id"] for t in files["aliases"]["tables"]} # note 뿐 - assert "@values" not in {t["id"] for t in files["fx"]["tables"]} # 값 묶음 없음 - for f in files.values(): - if "@values" in {t["id"] for t in f["tables"]}: - paths = [ - r["@path"] for r in _rows(client, file=f["id"], table="@values", size=500)["rows"] - ] - assert not [p for p in paths if tables.is_description(p)], f["id"] + assert "@values" not in { + t["id"] for t in files["work_item_master"]["tables"] + } # 방침·집계 = 설명 + assert "@values" not in {t["id"] for t in files["fx"]["tables"]} # 값 묶음 없음 — 줄이 곧 값 + for entry in LABELS["files"]: # 보이는 것 = 이름표가 「값」 이라 한 것 그대로 + want = [g["key"] for g in entry["value_groups"] if g["role"] == "value"] + got = ( + _rows(client, file=entry["file_id"], table="@values", size=500)["rows"] + if "@values" in {t["id"] for t in files[entry["file_id"]]["tables"]} + else [] + ) + assert sorted(r["@path"] for r in got) == sorted(want), entry["file_id"] hit = _rows(client, file="rates", table="@values", q="산재") assert [r["@path"] for r in hit["rows"]] == ["variables/rate_sanjae"] +def test_이름표가_값_설명을_안_가른_마디는_미판정으로_보임( + client: TestClient, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """브레인 — 조용히 값으로 보이면 설명이 섞이고, 조용히 빼면 원가에 드는 값이 사라짐 → 값으로 보이되 드러나게.""" + entry = next(f for f in LABELS["files"] if f["file_id"] == "rates") + lagging = [dict(g) for g in entry["value_groups"]] + for g in lagging: + if g["key"] in ("variables/rate_vat", "variables/rate_goyong/base"): + del g["role"] # 이름표가 뒤처짐 + elif g["key"] == "variables/rate_care": + g["role"] = "formula" # 코드가 모르는 낱말도 미판정 + (tmp_path / "labels_2026-01-01.json").write_text( + json.dumps({**LABELS, "files": [{**entry, "value_groups": lagging}]}, ensure_ascii=False), + encoding="utf-8", + ) + monkeypatch.setattr(tables, "LABEL_DIR", tmp_path) + values = _rows(client, file="rates", table="@values", size=500) + marks = {r["@path"]: r.get(tables.UNDECIDED_COLUMN) for r in values["rows"]} + undecided = {"variables/rate_vat", "variables/rate_goyong/base", "variables/rate_care"} + assert {p for p, m in marks.items() if m} == undecided + assert {marks[p] for p in undecided} == {tables.UNDECIDED} + assert tables.UNDECIDED_COLUMN in {c["key"] for c in values["columns"]} + assert not next(c for c in values["columns"] if c["key"] == tables.UNDECIDED_COLUMN)["hidden"] + + +def test_진짜_이름표엔_미판정이_없음() -> None: + undecided = [ + f"{entry['file_id']}::{r['@path']}" + for entry in LABELS["files"] + for r in tables._value_rows(entry, tables.ROOT / entry["path"], LABELS) + if r.get(tables.UNDECIDED_COLUMN) + ] + assert undecided == [], undecided + + def test_값_묶음_자리는_이름표와_양쪽으로_같음() -> None: not_labelled, label_only = [], [] for entry in LABELS["files"]: @@ -207,7 +246,7 @@ def test_값_묶음_자리는_이름표와_양쪽으로_같음() -> None: assert not_labelled == [] and label_only == [], (not_labelled, label_only) for entry in LABELS["files"]: # 이름도 이름표가 푼 그대로(찾는 차례 「파일id::key」 → 끝 조각) named = {g["key"]: g["name_ko"] for g in entry["value_groups"]} - rows = tables._value_rows(entry["file_id"], tables.ROOT / entry["path"], LABELS) + rows = tables._value_rows(entry, tables.ROOT / entry["path"], LABELS) assert [r["@name"] for r in rows] == [named[r["@path"]] for r in rows], entry["file_id"]