feat(z01): 낱값 표 @values — 표 아닌 마디(산재 3.56% 같은 값 묶음)를 파일마다 표 하나(이름 · 값 · 영문 자리 숨김)로 같은 rows 길에 태움 · 설명·방침(note·policy·source …)은 뺌(가름은 한 자리 · 이름표 표시 오면 갈아끼움) · 값 묶음 자리·이름 이름표와 양쪽 셈 0(브레인 ④)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
2026-09-15 19:20:30 +09:00
co-authored by Claude Opus 5
parent e130a7f7ec
commit f2bfaf888d
2 changed files with 106 additions and 14 deletions
+51 -4
View File
@@ -63,19 +63,22 @@ def test_갈래는_이름표_차례_파일_34_강우_캐시는_없음(client: Te
def test_트리_표_id_와_이름표_표_id_는_양쪽으로_같음(client: TestClient) -> None:
files = _files(client)
files = { # 낱값 표 `@values` 는 예약어 — 이름표 표 목록 밖(아래 시험이 따로 셈)
fid: [t for t in f["tables"] if t["id"] != tables.VALUES_TABLE]
for fid, f in _files(client).items()
}
not_labelled, label_only = [], []
for entry in LABELS["files"]:
tree_ids = {t["id"] for t in files[entry["file_id"]]["tables"]}
tree_ids = {t["id"] for t in files[entry["file_id"]]}
label_ids = {t["key"] for t in entry["tables"]}
not_labelled += [f"{entry['file_id']}::{i}" for i in sorted(tree_ids - label_ids)]
label_only += [f"{entry['file_id']}::{i}" for i in sorted(label_ids - tree_ids)]
assert not_labelled == [], not_labelled
assert label_only == [], label_only
assert sum(len(f["tables"]) for f in files.values()) == LABELS["counts"]["tables"] == 91
assert sum(len(t) for t in files.values()) == LABELS["counts"]["tables"] == 91
for entry in LABELS["files"]: # 이름이 실제로 붙음 · 줄 수도 이름표 셈과 같음
named = {t["key"]: t for t in entry["tables"]}
for t in files[entry["file_id"]]["tables"]:
for t in files[entry["file_id"]]:
assert t["label"] == named[t["id"]]["name_ko"], (entry["file_id"], t["id"])
assert t["row_count"] == named[t["id"]]["rows"], (entry["file_id"], t["id"])
@@ -164,6 +167,50 @@ def test_찾는_차례와_없는_이름은_영문_key(
)
def test_낱값은_파일마다_표_하나_설명류는_뺌(client: TestClient) -> None:
"""브레인 ④ — 표 아닌 마디(값 묶음)도 트리에 `@values` 한 마디로 · 설명·방침은 값이 아님."""
files = _files(client)
rates = files["rates"]["tables"]
assert rates[-1]["id"] == "@values"
values = _rows(client, file="rates", table="@values", size=500)
assert values["total"] == rates[-1]["row_count"]
by_path = {r["@path"]: r for r in values["rows"]}
sanjae = by_path["variables/rate_sanjae"]
assert sanjae["@value"] == {"rate_percent": 3.56, "base": "total_labor_cost"}
assert sanjae["@name"] == "산재보험료"
assert {c["key"]: c["hidden"] for c in values["columns"]} == {
"@name": False,
"@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"]
hit = _rows(client, file="rates", table="@values", q="산재")
assert [r["@path"] for r in hit["rows"]] == ["variables/rate_sanjae"]
def test_값_묶음_자리는_이름표와_양쪽으로_같음() -> None:
not_labelled, label_only = [], []
for entry in LABELS["files"]:
ours = set(tables.values_of(tables.ROOT / entry["path"]))
theirs = {g["key"] for g in entry["value_groups"]}
not_labelled += [f"{entry['file_id']}::{k}" for k in sorted(ours - theirs)]
label_only += [f"{entry['file_id']}::{k}" for k in sorted(theirs - ours)]
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)
assert [r["@name"] for r in rows] == [named[r["@path"]] for r in rows], entry["file_id"]
def test_없는_파일_표는_404(client: TestClient) -> None:
for params in (
{"file": "nope", "table": "rows"},