feat(z01): 기초데이터 아홉 이름표 — 열 21칸 · 줄 열쇠 아홉 · 축 시험 셋

- kind 아홉을 이름표 `merged_tables` 에 더함(다섯 → **열넷**) — coef · material_surcharge ·
  formwork_reuse · rebar_complexity · timber_structure_class · masonry_slope ·
  masonry_back_length · stone_kind · masonry_class. 영문으로 뜨던 21칸 **0**
- ⚠ 구간 이름은 **원문 표기 그대로**(`~1.5`·`~3`·`~5`·`~7`·`7이상` · 돌 직경 `40~60`) —
  열쇠의 몸이라 예쁘게 고치면 덮개가 주인을 잃음. 시험이 그 표기를 박아 둠
- 축 칸(`table`·`bond`·`face`·`height_bracket`·`stone_kind`·`back_length_cm`·`class`·
  `soil_type`·`type_id`·`material`)은 이름에 「줄을 가리는 축 — 못 고침」을 적음
- `coef/source_rows`·`duplicate_note` 는 암괴 중복 두 줄을 한 줄로 합친 사유 칸으로 이름 붙임
- 줄 열쇠 아홉을 `row_keys` 에 더함 — 서버가 줄마다 내는 `@axis` 가 곧 열쇠 ·
  넷은 **id 칸이 없어 축으로만 가려짐**(masonry_slope·masonry_back_length·stone_kind·masonry_class)
- 시험 셋 더함(12건) — 이름표 축이 서버 `@axis` 를 다 덮는지 · 구간 표기가 원문 그대로인지 ·
  축 칸이 고칠 수 있는 칸으로 서 있지 않은지
- ⚠ 예외 하나 적어 둠 — `formwork_reuse.reuse_count` 는 갈래표에서는 **값**, 비율표에서는 **축**이라
  서버가 한 판정만 내는 지금은 고칠 수 있는 칸으로 섬(판정 대기)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FFCnEYNH4tsS2MbHvzBhZk
This commit is contained in:
2026-09-16 13:12:58 +09:00
co-authored by Claude Opus 5
parent 651efae409
commit ede97d30fb
3 changed files with 600 additions and 6 deletions
@@ -2,7 +2,7 @@
"schema_version": "1.0",
"dataset_id": "data_master_labels",
"effective_date": "2026-01-01",
"generated_at": "2026-09-16T13:00:09+09:00",
"generated_at": "2026-09-16T13:11:45+09:00",
"note": "마스터 자료의 **사람이 읽을 이름표**. 값이 아니라 이름만 담는다 — 여기를 고쳐도 계산은 안 바뀐다. Z01 마스터 관리 화면이 표·열 이름을 여기서 읽는다.",
"policy": {
"labels_only": "값·수식·단가를 담지 않는다. 마스터 파일은 손대지 않는다.",
@@ -770,6 +770,433 @@
"note": "못 채운 성분이 있으면 그 사실을 적는 자리 — 0 으로 안 때운다."
}
]
},
{
"key": "coef",
"name_ko": "토량환산계수",
"summary": "판 흙이 부풀고(L) 다진 흙이 줄어드는(C) 비. 토질마다 하한·상한.",
"sources": [
"coef::variables/coef_soil_L/records",
"coef::variables/coef_soil_C/records"
],
"rows_note": "L 16 + C 16 = 32 줄(암괴 중복 두 줄은 한 줄로 합침)",
"columns": [
{
"key": "table",
"name_ko": "어느 표",
"unit": "",
"visible": true,
"note": "`coef_soil_L`(부풂) · `coef_soil_C`(다짐). ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "soil_type",
"name_ko": "토질",
"unit": "",
"visible": true,
"note": "⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "min",
"name_ko": "하한",
"unit": "",
"visible": true
},
{
"key": "max",
"name_ko": "상한",
"unit": "",
"visible": true
},
{
"key": "rule",
"name_ko": "별도 규정",
"unit": "",
"visible": true,
"note": "원문이 값 대신 「별도 설계」라 적은 자리."
},
{
"key": "selection",
"name_ko": "채택 여부",
"unit": "",
"visible": true,
"note": "원문이 같은 토질을 두 줄로 실어 어느 줄을 쓸지 아직 안 고른 자리(`pending`)."
},
{
"key": "source_rows",
"name_ko": "원문 줄 수",
"unit": "줄",
"visible": true,
"note": "⭐ 한 줄로 보이지만 원문에는 몇 줄이었나 — 암괴 중복 쌍이 `2` 다."
},
{
"key": "duplicate_note",
"name_ko": "합친 사유",
"unit": "",
"visible": true,
"note": "⭐ 「원문 두 줄 · 값 같음 — 한 줄로 보임」. 원문은 그대로 두고 화면만 접었다는 뜻."
}
]
},
{
"key": "material_surcharge",
"name_ko": "재료 할증률",
"summary": "자재마다 몇 %를 더 사는지. 조건이 갈리면 다른 조건 칸에 함께 둔다.",
"sources": [
"material_surcharge::rates_pct"
],
"rows_note": "19 줄",
"columns": [
{
"key": "material",
"name_ko": "자재명",
"unit": "",
"visible": true,
"note": "⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "rate",
"name_ko": "할증률",
"unit": "%",
"visible": true
},
{
"key": "condition",
"name_ko": "조건",
"unit": "",
"visible": true
},
{
"key": "alt_rate",
"name_ko": "다른 조건 할증률",
"unit": "%",
"visible": true
},
{
"key": "alt_condition",
"name_ko": "다른 조건",
"unit": "",
"visible": true
},
{
"key": "pumsem",
"name_ko": "어느 품셈",
"unit": "",
"visible": true,
"note": "`forest`(산림 · 1차) · `construction`(건설 · 보완)."
}
]
},
{
"key": "formwork_reuse",
"name_ko": "거푸집 사용횟수",
"summary": "구조물 갈래마다 거푸집을 몇 번 쓰는지, 횟수마다 기준수량의 몇 %인지.",
"sources": [
"formwork_reuse::reuse_by_class",
"formwork_reuse::reuse_ratio_pct",
"formwork_reuse::euroform_type/classes"
],
"rows_note": "구조 갈래 4 + 사용횟수 비율 12(합판·각재 × 6) + 유로폼 갈래 3 = 19 줄",
"columns": [
{
"key": "table",
"name_ko": "어느 표",
"unit": "",
"visible": true,
"note": "`reuse_by_class`(갈래별 횟수) · `reuse_ratio_pct`(횟수별 비율) · `euroform_type/classes`(유로폼 갈래). ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "class",
"name_ko": "구조 갈래",
"unit": "",
"visible": true,
"note": "⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "reuse_count",
"name_ko": "사용횟수",
"unit": "회",
"visible": true,
"note": "비율표에서는 ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "examples",
"name_ko": "원문 예시",
"unit": "",
"visible": true
},
{
"key": "material",
"name_ko": "거푸집 재료",
"unit": "",
"visible": true,
"note": "`plywood`(합판) · `timber`(각재). ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "ratio_pct",
"name_ko": "기준수량 비율",
"unit": "%",
"visible": true,
"note": "⭐ 품셈 12-4 — 그 횟수로 쓸 때 기준수량의 몇 %인지. 일위대가 재료비에 걸린다."
},
{
"key": "daily_area_m2",
"name_ko": "1일 시공량",
"unit": "㎡",
"visible": true,
"note": "유로폼 설치·해체 품이 갈리는 값."
}
]
},
{
"key": "rebar_complexity",
"name_ko": "철근 가공·조립 갈래",
"summary": "간단·보통·복잡·매우복잡 갈래와 갈래별 단가 참고값.",
"sources": [
"rebar_complexity::classes",
"rebar_complexity::price_hint_krw_per_ton"
],
"rows_note": "갈래 4 + 단가 참고값 4 = 8 줄",
"columns": [
{
"key": "table",
"name_ko": "어느 표",
"unit": "",
"visible": true,
"note": "`classes`(갈래) · `price_hint_krw_per_ton`(단가 참고값). ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "class",
"name_ko": "갈래",
"unit": "",
"visible": true,
"note": "⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "examples",
"name_ko": "원문 예시",
"unit": "",
"visible": true,
"note": "품셈 12-3 [주]① 이 든 구조물 보기."
},
{
"key": "price_krw_per_ton",
"name_ko": "단가 참고값",
"unit": "원/ton",
"visible": true,
"note": "⚠ **표시 전용** — 갈래 차이를 사람이 보라고 둔 값이다. 계산에 안 들어간다."
}
]
},
{
"key": "timber_structure_class",
"name_ko": "목재공작물 구조 갈래",
"summary": "갈래마다 목재 채적 1㎥ 에 드는 건축목공·보통인부.",
"sources": [
"timber_structure_class::classes"
],
"rows_note": "6 줄",
"columns": [
{
"key": "class",
"name_ko": "갈래",
"unit": "",
"visible": true,
"note": "보통구조 하·중·상 · 상등구조 하·중·상. ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "carpenter",
"name_ko": "건축목공",
"unit": "인/㎥",
"visible": true
},
{
"key": "laborer",
"name_ko": "보통인부",
"unit": "인/㎥",
"visible": true
},
{
"key": "examples",
"name_ko": "원문 예시",
"unit": "",
"visible": true
}
]
},
{
"key": "masonry_slope",
"name_ko": "돌쌓기 표준경사",
"summary": "쌓기 방식 × 성절토 × 직고 구간마다 전면 기울기(1:n).",
"sources": [
"masonry_slope::table"
],
"rows_note": "메찰 2 × 성절토 2 × 직고 5 = 20 줄",
"columns": [
{
"key": "bond",
"name_ko": "쌓기 방식",
"unit": "",
"visible": true,
"note": "메쌓기 · 찰쌓기. ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "face",
"name_ko": "성토·절토",
"unit": "",
"visible": true,
"note": "⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "height_bracket",
"name_ko": "직고 구간",
"unit": "m",
"visible": true,
"note": "⚠ 원문 표기 그대로(`~1.5`·`~3`·`~5`·`~7`·`7이상`) — 예쁘게 고치면 열쇠가 바뀐다. ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "face_slope_ratio",
"name_ko": "전면 기울기",
"unit": "1:n",
"visible": true,
"note": "코드가 늘 쓰던 0.3 은 이 표의 「메쌓기·성토·~1.5」 한 칸일 뿐이었다."
}
]
},
{
"key": "masonry_back_length",
"name_ko": "돌쌓기 뒷길이 표준",
"summary": "쌓기 방식 × 직고 구간마다 뒷길이 하한·상한(㎝).",
"sources": [
"masonry_back_length::table_cm"
],
"rows_note": "메찰 2 × 직고 5 = 10 줄",
"columns": [
{
"key": "bond",
"name_ko": "쌓기 방식",
"unit": "",
"visible": true,
"note": "메쌓기 · 찰쌓기. ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "height_bracket",
"name_ko": "직고 구간",
"unit": "m",
"visible": true,
"note": "⚠ 원문 표기 그대로(`~1.5`·`~3`·`~5`·`~7`·`7이상`) — 예쁘게 고치면 열쇠가 바뀐다. ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "min_cm",
"name_ko": "뒷길이 하한",
"unit": "㎝",
"visible": true,
"note": "⭐ 안 고르면 이 값이 제안된다(범위 안에 드는 품셈 규격 중 가장 작은 것)."
},
{
"key": "max_cm",
"name_ko": "뒷길이 상한",
"unit": "㎝",
"visible": true,
"note": "원문이 상한을 안 준 칸은 빈다."
}
]
},
{
"key": "stone_kind",
"name_ko": "돌 종류별 소요량",
"summary": "돌 종류 × 뒷길이마다 고임돌·채움 콘크리트 원단위와 뒤채움 몫.",
"sources": [
"stone_kind::wedge_stone_m3_per_m2",
"stone_kind::fill_concrete_m3_per_m2",
"stone_kind::backfill_ratio_of_back_length",
"stone_kind::fallback"
],
"rows_note": "고임돌 28 + 채움 28 + 뒤채움 몫 4 + 미지정일 때 15 = 75 줄",
"columns": [
{
"key": "table",
"name_ko": "어느 표",
"unit": "",
"visible": true,
"note": "`wedge_stone_m3_per_m2`(고임돌) · `fill_concrete_m3_per_m2`(채움 콘크리트) · `backfill_ratio_of_back_length`(뒤채움 몫) · `fallback/…`(돌 종류 미지정일 때). ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "stone_kind",
"name_ko": "돌 종류",
"unit": "",
"visible": true,
"note": "야면석·호박돌 / 깬잡석 / 깬돌 / 견치돌. 미지정 벌에는 이 칸이 없다. ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "back_length_cm",
"name_ko": "뒷길이",
"unit": "㎝",
"visible": true,
"note": "25·30·35·45·55·60·75 일곱 규격. ⚠ 표에 없는 뒷길이를 가까운 칸으로 접지 않는다. ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "m3_per_m2",
"name_ko": "원단위",
"unit": "㎥/㎡",
"visible": true,
"note": "고임돌·채움 콘크리트 표의 값. 원문 「-」 칸은 빈다 — 0 이 아니다."
},
{
"key": "ratio",
"name_ko": "뒤채움 몫",
"unit": "",
"visible": true,
"note": "뒷길이 가운데 뒤채움이 차지하는 비. 나머지가 잡석이 된다."
}
]
},
{
"key": "masonry_class",
"name_ko": "돌쌓기 규격 갈래",
"summary": "저장 제원으로 자동 판정하는 축 — 뒷길이 갈래·돌 직경 갈래·형식별 전면 기울기.",
"sources": [
"masonry_class::back_length/classes",
"masonry_class::boulder_diameter",
"masonry_class::face_slope"
],
"rows_note": "뒷길이 갈래 3 + 돌 직경 갈래 3 + 형식별 기울기 3 = 9 줄",
"columns": [
{
"key": "table",
"name_ko": "어느 표",
"unit": "",
"visible": true,
"note": "`back_length/classes`(뒷길이 갈래) · `boulder_diameter`(돌 직경 갈래) · `face_slope`(형식별 기울기). ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "class",
"name_ko": "갈래",
"unit": "",
"visible": true,
"note": "뒷길이는 `35cm이하` 꼴, 돌 직경은 원문 표기 `40~60` 꼴. ⚠ 원문 표기 그대로(`~1.5`·`~3`·`~5`·`~7`·`7이상`) — 예쁘게 고치면 열쇠가 바뀐다. ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "max_cm",
"name_ko": "상한",
"unit": "㎝",
"visible": true,
"note": "뒷길이 갈래의 상한."
},
{
"key": "type_id",
"name_ko": "구조물 종류 키",
"unit": "",
"visible": true,
"note": "`masonry_wet`·`masonry_dry`·`boulder_masonry`. ⚠ 줄을 가리는 축 — 못 고침. 바꾸면 고친 값이 주인을 잃는다."
},
{
"key": "face_slope_ratio",
"name_ko": "전면 기울기",
"unit": "1:n",
"visible": true,
"note": "형식마다 정해진 값. 코드 기본 0.3 이 여기서 왔다."
}
]
}
],
"machine_cost_chain": {
@@ -997,6 +1424,99 @@
"machine_code"
],
"checked": "취득가 613 · 손료계수 387 · 연료 214 · 운전경비 92 **네 표 모두 기계코드 겹침 0**(전수). 취득가가 가장 넓어 뼈대가 되고 나머지가 붙는다."
},
{
"table": "coef",
"name_ko": "토량환산계수",
"verdict": "있음(묶어야 함)",
"key": [
"table",
"soil_type"
],
"checked": "L·C 두 표가 한 자리에 서므로 `table` 이 함께 있어야 한다. 토질 이름은 원문 그대로. 암괴 중복 두 줄은 값이 같아 한 줄로 합쳤고 `source_rows`·`duplicate_note` 가 그 사유를 든다."
},
{
"table": "material_surcharge",
"name_ko": "재료 할증률",
"verdict": "있음",
"key": [
"material"
],
"checked": "자재명 19 줄 겹침 0(전수). 그대로 열쇠로 쓴다."
},
{
"table": "formwork_reuse",
"name_ko": "거푸집 사용횟수",
"verdict": "있음(표마다 다름)",
"key": [
"table",
"class",
"material",
"reuse_count"
],
"checked": "갈래 표는 `table+class`, 비율표는 `table+material+reuse_count` 로 가려진다. 한 표 안에서 축이 갈리므로 열쇠도 줄마다 다르다."
},
{
"table": "rebar_complexity",
"name_ko": "철근 갈래",
"verdict": "있음(묶어야 함)",
"key": [
"table",
"class"
],
"checked": "갈래 표와 단가 참고값 표가 같은 갈래 이름을 쓰므로 `table` 이 함께 있어야 한다."
},
{
"table": "timber_structure_class",
"name_ko": "목재 구조 갈래",
"verdict": "있음",
"key": [
"class"
],
"checked": "갈래 6 줄 겹침 0(전수)."
},
{
"table": "masonry_slope",
"name_ko": "돌쌓기 표준경사",
"verdict": "없음 — 축으로만 가려짐",
"key": [
"bond",
"face",
"height_bracket"
],
"checked": "id 칸이 없다. 쌓기 방식 × 성절토 × 직고 구간 셋으로만 줄이 가려진다. ⚠ 직고 구간은 원문 표기 `~1.5`·`~3`·`~5`·`~7`·`7이상` 그대로 — 열쇠의 몸이라 못 바꾼다."
},
{
"table": "masonry_back_length",
"name_ko": "돌쌓기 뒷길이 표준",
"verdict": "없음 — 축으로만 가려짐",
"key": [
"bond",
"height_bracket"
],
"checked": "id 칸이 없다. 쌓기 방식 × 직고 구간 둘로만 가려진다. 구간 표기는 위와 같다."
},
{
"table": "stone_kind",
"name_ko": "돌 종류별 소요량",
"verdict": "없음 — 축으로만 가려짐",
"key": [
"table",
"stone_kind",
"back_length_cm"
],
"checked": "id 칸이 없다. 표 × 돌 종류 × 뒷길이로 가려지되 **표마다 축 수가 다르다** — 뒤채움 몫은 돌 종류까지, 미지정 벌은 뒷길이까지만 쓴다."
},
{
"table": "masonry_class",
"name_ko": "돌쌓기 규격 갈래",
"verdict": "없음 — 축으로만 가려짐",
"key": [
"table",
"class",
"type_id"
],
"checked": "id 칸이 없다. 뒷길이·돌 직경 갈래는 `table+class`, 형식별 기울기는 `table+type_id`. ⚠ 돌 직경 갈래 이름은 원문 표기 `40~60` 꼴 그대로다."
}
],
"null_is_real": {
@@ -1090,8 +1610,8 @@
}
},
"counts": {
"merged_tables": 5,
"merged_columns": 89,
"merged_tables": 14,
"merged_columns": 136,
"files": 34,
"tables": 91,
"columns": 462,
@@ -152,3 +152,60 @@ def test_단위가_붙어야_할_열에_단위가_있다(shown):
if units.get(key) != unit:
bad.append(f"{kind}/{key}: {units.get(key)!r}{unit!r}")
assert not bad, bad
def test_줄_열쇠가_서버_축과_같다(labels):
"""⭐ 덮개가 줄을 붙드는 끈 — 서버가 줄마다 `@axis` 로 내는 축이 곧 열쇠다.
이름표가 축을 빠뜨리면 고친 값이 갱신 뒤 주인을 잃는다.
"""
declared = {r["table"]: set(r["key"]) for r in labels["row_keys"]}
bad = []
for kind in base_prices.KINDS:
axes = set()
for row in base_prices.rows(kind):
axes |= set(row.get("@axis") or ())
if not axes: # 다섯 표는 `@id` 한 칸이라 축을 안 낸다
continue
missing = axes - declared.get(kind, set())
if missing:
bad.append(f"{kind}: 이름표에 없는 축 {sorted(missing)}")
assert not bad, bad
def test_구간_이름은_원문_표기_그대로다():
"""⚠ `~1.5`·`7이상` 을 예쁘게 고치면 줄 열쇠가 바뀌어 덮개가 주인을 잃는다."""
for kind in ("masonry_slope", "masonry_back_length"):
brackets = {str(r.get("height_bracket")) for r in base_prices.rows(kind)}
assert brackets == {"~1.5", "~3", "~5", "~7", "7이상"}, (kind, sorted(brackets))
classes = {
str(r.get("class"))
for r in base_prices.rows("masonry_class")
if r.get("table") == "boulder_diameter"
}
assert classes == {"40~60", "60~80", "80~100"}, sorted(classes)
#: ⚠ **줄마다 축이기도 값이기도 한 칸** — `formwork_reuse.reuse_count` 는
#: `reuse_by_class` 줄에서는 **고치는 값**(갈래별 사용횟수)이고
#: `reuse_ratio_pct` 줄에서는 **축**(몇 회째인가)이다. 서버는 열 하나에 한 판정만 내므로
#: 지금은 고칠 수 있는 칸으로 서 있다. 비율표 줄에서 이 칸을 고치면 **다른 줄로 옮겨 간다**.
#: 2026-09-16 크로스체크에서 드러났고 판정 대기 — 고치기 전까지 여기 예외로 적어 둔다.
AXIS_ALSO_VALUE = {"formwork_reuse/reuse_count"}
def test_축_칸은_못_고친다():
"""축을 고치면 줄이 다른 줄이 된다 — 서버가 잠가야 한다."""
bad = []
for kind in base_prices.KINDS:
rows = base_prices.rows(kind)
axes = set()
for row in rows:
axes |= set(row.get("@axis") or ())
if not axes:
continue
meta = base_prices.table(kind, page=1, size=1)
for axis in axes:
if axis in meta["editable"] and f"{kind}/{axis}" not in AXIS_ALSO_VALUE:
bad.append(f"{kind}/{axis} 가 고칠 수 있는 칸으로 섰다")
assert not bad, bad
+20 -3
View File
@@ -451,12 +451,29 @@ def test_미결은_미판정으로_표시된다(labels):
assert column["name_ko"] == "미판정"
MERGED_KEYS = ("labor", "material", "oil", "rate", "machine")
#: 2026-09-16 사용자 판정 — 기초데이터 아홉이 더해져 열넷.
#: 잣대 = 계산되는 값이 아니라 **그냥 값만으로 정의되는 데이터셋**.
MERGED_KEYS = (
"labor",
"material",
"oil",
"rate",
"machine",
"coef",
"material_surcharge",
"formwork_reuse",
"rebar_complexity",
"timber_structure_class",
"masonry_slope",
"masonry_back_length",
"stone_kind",
"masonry_class",
)
def test_기초단가_다섯이_다_있다(labels):
def test_기초데이터_열넷이_다_있다(labels):
merged = {m["key"]: m for m in labels["merged_tables"]}
assert tuple(merged) == MERGED_KEYS
assert set(merged) == set(MERGED_KEYS)
for entry in merged.values():
assert entry["name_ko"].strip()
assert entry["summary"].strip()