From 6680cb0ff3c685d6813b15a756403f25fa6cb1c9 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 17 Sep 2026 16:58:05 +0900 Subject: [PATCH] =?UTF-8?q?wip(z01):=20=EC=B4=9D=EC=B9=99=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EA=B0=90=EC=B6=A4=20=EC=9E=A3=EB=8C=80=ED=99=94=20?= =?UTF-8?q?=E2=80=94=20=EB=B3=91=ED=95=A9=20=EC=A0=84=20=EC=9E=84=EC=8B=9C?= =?UTF-8?q?=20=EB=B3=B4=EA=B4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Z01_MasterData/Z01_MasterData_WorkItems.py | 43 ++++++++++++++----- .../data_master_labels/labels_2026-01-01.json | 24 +++++++++-- .../data_work_item_master/axis_policy.json | 21 +++++++-- resources/tester/test_z01_work_items.py | 27 ++++++++---- 4 files changed, 89 insertions(+), 26 deletions(-) diff --git a/Z01_MasterData/Z01_MasterData_WorkItems.py b/Z01_MasterData/Z01_MasterData_WorkItems.py index 0d955e3f..ac19aa2a 100644 --- a/Z01_MasterData/Z01_MasterData_WorkItems.py +++ b/Z01_MasterData/Z01_MasterData_WorkItems.py @@ -156,17 +156,33 @@ def _facets(kind: str) -> dict[str, dict[str, str]]: return {_key(i): {key: str(i.get(key) or "") for key in FILTER_KEYS} for i in items} -def _role_names() -> dict[str, str]: - """줄 구실 이름 — `axis_policy.json`(데스크탑 서브 잣대) 한 곳에서.""" +def _policy() -> dict[str, Any]: + """줄 구실 잣대 — `axis_policy.json`(데스크탑 서브) 한 곳에서.""" path = FOLDER / "axis_policy.json" - if not path.is_file(): - return {} - roles = _read(str(path), path.stat().st_mtime_ns).get("roles") or {} - return {key: str(role.get("name_ko") or key) for key, role in roles.items()} + return _read(str(path), path.stat().st_mtime_ns) if path.is_file() else {} + + +def _role_names() -> dict[str, str]: + """거르기 상자에 뜰 이름. 목록에서는 「총칙」이 아니라 「공종 아님 · 규정·기준」으로 적는다.""" + roles = _policy().get("roles") or {} + return { + key: str(role.get("list_label") or role.get("name_ko") or key) + for key, role in roles.items() + } + + +def _default_of(key: str) -> str: + """이 가름의 기본값 — **잣대가 정한다**(화면도 서버도 스스로 가르지 않음). + + 총칙 줄은 표가 붙어 목록에 섞여 뜨지만 공종이 아니라 규정·기준이다. 기본은 안 보이되 + 상자를 고르면 보인다 — ⚠ **지운 것이 아니라 감춘 것**이다(2026-09-17 브레인). + """ + wanted = _policy().get("list_default") or {} + return str(wanted.get("value") or ALL) if wanted.get("filter") == key else ALL def filters(kind: str, rows: list[dict[str, Any]]) -> list[dict[str, Any]]: - """거르기 상자 — 값이 둘 이상 갈리는 가름만 · 기본은 「전부」(안 거름).""" + """거르기 상자 — 값이 둘 이상 갈리는 가름만. 기본값은 **잣대**가 정한다(`list_default`).""" facets = _facets(kind) labels = {"division": ("부문", {}), "axis_role": ("줄 구실", _role_names())} out = [] @@ -179,13 +195,14 @@ def filters(kind: str, rows: list[dict[str, Any]]) -> list[dict[str, Any]]: if len(values) < 2: continue label, names = labels[key] + default = _default_of(key) out.append( { "key": key, "label": label, "options": [{"value": ALL, "label": "전부", "rows": len(rows)}] + [{"value": v, "label": names.get(v, v), "rows": counts[v]} for v in values], - "default": ALL, + "default": default if default in values else ALL, } ) return out @@ -194,9 +211,15 @@ def filters(kind: str, rows: list[dict[str, Any]]) -> list[dict[str, Any]]: def apply_filters( kind: str, rows: list[dict[str, Any]], picked: dict[str, str] ) -> list[dict[str, Any]]: + """고른 값으로 거른다. **안 고른 가름은 잣대의 기본값**을 쓴다. + + ⚠ 기본값을 서버가 매기는 까닭 — 화면만 기본값을 알면 「상자엔 공종이라 떠 있는데 목록엔 + 총칙이 섞여 있는」 어긋남이 난다. 「전부」는 빈 값을 **적어 보내면** 된다. + """ facets = _facets(kind) - for key, value in picked.items(): - if key in FILTER_KEYS and value != ALL: + for key in FILTER_KEYS: + value = picked[key] if key in picked else _default_of(key) + if value != ALL: rows = [r for r in rows if facets.get(r["@id"], {}).get(key) == value] return rows diff --git a/resources/data_master_labels/labels_2026-01-01.json b/resources/data_master_labels/labels_2026-01-01.json index 1a560ad0..6c92e86c 100644 --- a/resources/data_master_labels/labels_2026-01-01.json +++ b/resources/data_master_labels/labels_2026-01-01.json @@ -1933,11 +1933,11 @@ "merged_columns": 172, "files": 41, "tables": 100, - "columns": 532, - "value_groups": 202, + "columns": 534, + "value_groups": 203, "unknown": 0, "value_groups_by_kind": { - "doc": 136, + "doc": 137, "value": 66 } }, @@ -7533,6 +7533,18 @@ "name_ko": "까닭", "unit": "", "visible": true + }, + { + "key": "list_label", + "name_ko": "목록에 뜰 이름", + "unit": "", + "visible": true + }, + { + "key": "hidden_by_default", + "name_ko": "기본으로 감추나", + "unit": "", + "visible": true } ] }, @@ -7655,6 +7667,12 @@ "name_ko": "가르는 차례", "summary": "총칙 → 공종 → 묶는 마디 → 빈 잎 차례로 본다.", "kind": "doc" + }, + { + "key": "list_default", + "name_ko": "목록 기본 거르기", + "summary": "목록을 열면 무엇만 보일지 — 기본은 공종만. 총칙은 감춘 것이지 지운 것이 아니다.", + "kind": "doc" } ] }, diff --git a/resources/data_work_item_master/axis_policy.json b/resources/data_work_item_master/axis_policy.json index 96a9f4b0..efd19759 100644 --- a/resources/data_work_item_master/axis_policy.json +++ b/resources/data_work_item_master/axis_policy.json @@ -13,25 +13,33 @@ "work_item": { "name_ko": "공종", "rule": "품셈 표가 붙은 줄", - "keep": true + "keep": true, + "list_label": "공종", + "hidden_by_default": false }, "group": { "name_ko": "묶는 마디", "rule": "표는 없고 아래를 묶는 줄", "keep": true, - "why": "`parent_mode`(choose_one·sum_steps)가 여기 붙는다 — 계산 규칙을 지닌 줄이다." + "why": "`parent_mode`(choose_one·sum_steps)가 여기 붙는다 — 계산 규칙을 지닌 줄이다.", + "list_label": "묶는 마디", + "hidden_by_default": true }, "general_provision": { "name_ko": "총칙", "rule": "`general_provision` 뿌리 아래의 줄", "keep": true, - "why": "문서 구역이나 값표가 붙어 원가가 읽는다." + "why": "문서 구역이나 값표가 붙어 원가가 읽는다.", + "list_label": "공종 아님 · 규정·기준", + "hidden_by_default": true }, "empty": { "name_ko": "빈 잎", "rule": "표도 아래도 없는 잎", "keep": true, - "why": "원문에 표가 안 붙은 자리. 지우면 「있었는지 없었는지」가 안 남는다." + "why": "원문에 표가 안 붙은 자리. 지우면 「있었는지 없었는지」가 안 남는다.", + "list_label": "빈 잎", + "hidden_by_default": true } }, "decision_order": [ @@ -106,5 +114,10 @@ "note": "총칙 표시 전 셈(랩탑 메인 첫 산출). 뿌리를 붙이면 `general_provision` 으로 옮겨 간다 — 줄이 줄지는 않는다." } } + }, + "list_default": { + "filter": "axis_role", + "value": "work_item", + "why": "목록 기본은 **공종만**(산림 306 · 건설 983). 총칙 줄은 표가 붙어 있어 목록에 섞여 뜨는데, 공종이 아니라 규정·기준이다. 상자를 따로 만들지 않고 **기본 감춤 + 고르면 보임**으로 간다 (2026-09-17 브레인). ⚠ 지운 것이 아니라 감춘 것이다." } } diff --git a/resources/tester/test_z01_work_items.py b/resources/tester/test_z01_work_items.py index 9e8b30a8..1b470c7f 100644 --- a/resources/tester/test_z01_work_items.py +++ b/resources/tester/test_z01_work_items.py @@ -41,13 +41,14 @@ def test_산림_평평한_잎_목록과_계산_규칙(client: TestClient) -> Non assert forest["group"] == "work_item" and forest["rows"] == 365 table = _get(client, "work_item_forest", size=500) - assert table["total"] == 365 and table["stats"] == {"work_items": 365, "units": 627} + # 2026-09-17 목록 기본이 **공종만**(잣대 `list_default`) — 뜨는 줄 306, 총칙 59 는 감춰져 있다. + # `stats` 는 쪽 나누기·거르기와 무관한 **전체** 셈이라 365 그대로다. + assert table["total"] == 306 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}) == 365 - assert ( - len({r["name"] for r in rows}) == 365 - ) # 잎 이름 겹침 — 경로로 풀림 · 목차 오기 바로잡아 358 → 360(12-17-2 · 12-24-1) + assert len({r["@id"] for r in rows}) == 306 # 뜨는 줄 = 공종만 + # 잎 이름 겹침 — 경로로 풀림. 뜨는 줄만큼 이름도 다 달라야 한다(겹치면 사람이 못 고름). + assert len({r["name"] for r in rows}) == 306 by_code = {r["work_item_code"]: r for r in rows} assert by_code["FP-09-03-02"]["name"] == "토공 › 토사깍기 › 기계" @@ -68,7 +69,8 @@ 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"]["work_items"] > 900 + # 기본이 공종만이라 뜨는 줄 983 · 전체 997(총칙 14 감춤) + assert table["total"] == 983 and table["stats"]["work_items"] == 997 rows = table["rows"] assert all(r["@id"].startswith("CW-") for r in rows) assert all(r["name"].split(" › ")[0].endswith("부문") for r in rows) @@ -123,7 +125,12 @@ def test_거르기는_자료에_있는_가름만_서버가_판정(client: TestCl assert set(by_key) == {"division", "axis_role"} division = by_key["division"] assert division["default"] == "" - assert division["options"][0] == {"value": "", "label": "전부", "rows": const["total"]} + # 거르기 상자의 셈은 **전체** 기준이다 — 「전부 997」. 지금 뜨는 줄(`total`)과는 다르다. + assert division["options"][0] == { + "value": "", + "label": "전부", + "rows": const["stats"]["work_items"], + } assert [o["value"] for o in division["options"][1:]] == [ "공통부문", "토목부문", @@ -131,10 +138,12 @@ def test_거르기는_자료에_있는_가름만_서버가_판정(client: TestCl "기계설비부문", "유지관리부문", ] - assert sum(o["rows"] for o in division["options"][1:]) == const["total"] + assert sum(o["rows"] for o in division["options"][1:]) == const["stats"]["work_items"] + # 2026-09-17 목록에서는 「총칙」이 아니라 **공종이 아님**을 드러내는 이름으로 적는다(브레인). assert {o["value"]: o["label"] for o in by_key["axis_role"]["options"]}[ "general_provision" - ] == "총칙" + ] == "공종 아님 · 규정·기준" + assert by_key["axis_role"]["default"] == "work_item" # 기본 감춤 — 지운 것이 아니라 감춘 것 civil = _get(client, "work_item_const", size=500, division="토목부문") assert civil["total"] == next(