Files
Aislo/resources/master_data/_화면_계약.md
T
eomsangdonandClaude Opus 5.5 54ce67ceb8 refactor(M02): 자재 조합(UM) 지움 — 좌측 컨테이너 · 재료산출 옆 판 · 길(material-combos · 갈라 내기 · 키 적기 자재조합) · 저장소 · 틀 검사 · 복사 길의 UM 줄 · 시스템 층 표 · fixture · 시험
- 자재 고르기 창은 M02_Material_Pick.ts 로 옮겨 남김
- storage 층(회사 · 개인 · 프로젝트) 자재조합.json 은 지우지 않음(읽기만 끊음)
- check_master 틀 검사 · _틀.md 11장 · _화면_계약.md 자재 조합 절 정리

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
2026-10-04 17:39:16 +09:00

213 lines
30 KiB
Markdown

# 관리자 화면 계약 (M01)
`resources/master_data/` 첫 층 JSON 을 보고 고치는 화면과 서버(`M01_MasterData/`) 사이의 약속. 파일 모양은 `_틀.md`.
## 1. 공통
- 길 머리 `/api/m01` · 시스템 관리자만(아니면 403).
- 봉투 칸 이름은 영문 · 줄·표·로직 알맹이는 파일 그대로(한글 칸).
- 수는 JSON 수로 옴.
- `version` = 파일 판본(내용 해시). 읽을 때 받아 두고 저장 때 그대로 돌려줌.
- `file` = 파일 이름(`소요량_건설품셈_07장_위생기구설비공사.json` · `인력.json`). 밑줄 파일·하위 폴더는 없음.
- `key` = 키(`LB000123`) — 전체에서 하나. 줄의 원문 번호는 `원문번호` 칸.
- 없는 파일·키 = 404 · 틀린 요청 = 400.
## 2. 읽기
| 길 | 받음 | 줌 |
| --------------------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /groups` | | `{groups: [{group, files, rows}]}` — 그룹 차례는 `_틀.md` 2장 |
| `GET /groups/{group}/files` | | `{files: [{file, book, chapter, edition, rows, version, order}]}` — 머리 `차례` 순 |
| `GET /subs` | `file` 또는 `group` | `{file, version, slot, subs: [{name, book, details}]}` — 인력 `구분` · 기계 `세부분류`(머리에 등록된 것) · `group`(소요량 · 계수 · 로직)은 장 파일에서 모음(구분 = 원문 + 부문 · 상세구분 = 장 · 차례대로) |
| `GET /rows` | `file` · `page`(1) · `size`(50) · `q` · `sub` · `detail` | `{file, version, total, page, size, rows: [줄…]}` |
| `GET /tables` | `file` 또는 `group` · `sub` · `detail` · `section` · `q` · `page`(1) · `size`(30) | `{file, version, total, page, size, tables: [{file, 키, 원문번호, 구분, 상세구분, 이름, 기준, 출처, 조건, 값칸, count}]}` — 파일을 가로지른 한 목록 |
| `GET /table` | `file` · `key` | `{file, version, table: {표 하나 통째}}` |
| `GET /table/options` | `file` · `key` | `{file, key, 이름, 조건, 범위규칙, 값칸, count, 값: {조건 이름: [값…]}}` — 표 줄을 통째로 안 받고 조건 칸의 값만 |
| `GET /sections` | `book`(필수) · `division` · `chapter` · `q` · `limit`(200 · 1000까지) | `{total, sections: [{book, division, chapter, chapterNo, section, title, tables, logics}]}` — 원문 절 목록 |
| `GET /logics` | `sub` · `detail` · `q` · `blocked`(0/1) · `owner` | `{logics: [{file, 키, 원문번호, 구분, 상세구분, 이름, 결과단위, 출처, 소유, blocked, reasons}]}` — 장 차례대로 |
| `GET /logic` | `key` | `{file, version, logic: {로직 한 줄 통째}, blocked, reasons, prices: {요소: 요약 \| null}}` |
| `GET /elements` | `group` · `q` · `limit`(50 · 200까지) | `{total, items: [{ref, file, 원문번호, 구분, 상세구분, 이름, 규격, 단위, 값, 값칸}]}` |
| `GET /materials` | `sub`(구분) · `detail` · `spec` · `region`(자재지역) · `limit`(50 · 200까지) | `{total, 기본, items: [{ref, 구분, 상세구분, 이름, 규격, 단위, 값, 관급}]}` — 재료 고르기 조건 안 후보 · `기본` = 시험 계산이 쓸 줄 |
- `rows` 는 요소·로직 파일은 `줄`, 표형 파일(소요량·계수)은 `표` 를 줄로 봄.
- `q` = 찾기 — `키`·`원문번호`·`이름`·`옛이름` 에 든 글(대소문자 무시).
- `sub` = 인력 `구분`·기계 `세부분류` 거름 · `detail` = 인력 `상세구분` 거름 · 빈 글 = 전체.
- `chapter` = 「부문 NN장 장 제목」(`공통 03장 토공사` · `13장 구조물`) · 장 파일 아니면 "".
- `blocked` = 로직 검사(`check_master.py 로직`)에 걸림 · `reasons` = 걸린 까닭 글.
- `prices` = 호표 `요소` → `{ref, 이름, 규격, 단위, 값}`(단가 자동 칸) · 없는 요소는 `null` · `{이름}` 이 낀 원문번호·하위 로직 줄은 빠짐(계산 때 정해짐). 품셈재료는 `값` = 연결을 따라간 낮은 값(시험 계산용) · `출처` = 그 값의 열(`<자재품목 키>.<열>`) · 자재지역 후보는 `값` null(계산 때 정해짐).
- `elements` = 요소 찾기 창 — 그룹 전체에서 `q` 찾기 · `ref` = 식에 넣는 키 · 표형 그룹은 `값칸` 이 옴.
- `materials` = 재료 고르기 단계 — 로직 재료 줄 `요소` 가 조건 `{구분, 상세구분, 규격}` 이면 이 API 로 후보(방식 A·B·C 가 부품 `M01_MasterData_UI_Test_Pick.ts` 하나를 같이 씀 · 구분 → 상세구분 → 후보 순으로 좁힘). `값` 이 비어 있어도 후보로 둠. 옛 재료 줄(품셈재료 키)은 품셈재료 이름(없으면 이름 첫 낱말)으로 `pick?kind=price` 후보. 후보 글 = 이름 규격 · 값 있는 열 전부(물가자료 · 유통물가 …). 멈춤 까닭 `입력 「X」 없음` · `맞는 줄 0개` 는 방식 A·B·C 가 쉬운 말로 바꿔 보임(`plainReason`) · 빈 수 칸은 「값을 넣어 주세요」 안내 글.
- 소요량·계수 표의 `용도`({공종, 대상, 로직키}) = 방식 B 원문 표 미리보기 머리에 안내 · 칸이 없으면 안 보임 · `/tables` 는 `usage` 로 `용도.대상` 거름(기계 `sub`·`detail` 은 인력과 같이 구분 → 상세구분).
- 「새로 만들기」 모달(`M01_MasterData_UI_Logic_Wizard*.ts`) — 원문 절 = `/sections` · 그 절의 표 = `/tables?section=`(소요량·계수) · 표 조건 값 = `/table/options` · 인력 = `/pick?kind=job` · 재료 = `/pick?kind=price`(구분·상세구분 조건 묶음으로 넣음) · 기계 = `/elements` · 다른 로직 = `/logics?q`. 시험 계산 = `/calc` 키 "" + `row`(파일 안 보냄) · 저장 = `/logic/new`(소유 `현장` · 키 GX 는 서버) · 본떠 만들기 = `/logic/copy`. 끝수는 묶음 `{대상: 계, 자리: 0, 방법}` 으로 보냄.
- `section` = 절 번호 거름 — 그 절과 그 아래 절만(`13-4` 는 `13-4` · `13-4-1` 만 · `13-40` 은 안 걸림). 「만들기」가 절을 고르면 그 절의 표만 보임.
- `/sections` = 만들기 첫 걸음 — 원문 본문의 절 제목 줄에서 모음(`book` = 산림품셈 · 건설품셈 · `division` = 건설품셈 부문(공통 · 토목 · 건축 · 기계설비 · 유지관리) · `chapter` = 장 번호 두 자리 「13」 · `q` = 절 번호·제목 찾기). `chapter` = 「부문 NN장 장 제목」 · `chapterNo` = 「NN」 · `tables` · `logics` = 그 절(아래 절 포함)의 마스터 표·로직 수 — 0 이면 아직 안 올린 절.
- `/table/options` = 조건 값 고르기 — `고르기` 조건은 줄에 나온 값 차례대로(중복 없음) · `범위` 조건은 `[[아래, 위]…]` · `count` = 줄 수. 표 몸을 통째로 받지 않으려는 자리에만 씀(통째는 `/table`).
- `owner` = 로직 `소유` 거름(`공용` 정본 · `현장` 자체) · 빈 글 = 전체.
- `pick` = 고르기 창 — `price` = `재료_자재품목.json`(값 = 값 열 다섯 가운데 낮은 값 · `관급` = 관급 값 있음) ref 키 · `job` = `인력.json` 값 있는 공표 직종(구분 「미확보」 빼고) ref 키.
## 3. 시험 계산
`POST /calc` 받음 `{key, inputs: {이름: 값}, row?, file?, 고름?}` — 새 로직은 `key` "" · `고름` 은 10장
- 됨 — `{ok: true, lines: [{이름, 단위, 수량, 단가, 금액, 비목: {비목: 금액}, 출처?, 단위경고?}], sums: {노무비, 재료비, 경비, 계}, middle: {이름: 값}}` · `출처` = 재료 줄의 단가 칸 · `단위경고` 는 10장
- 돈 아닌 로직 — `{ok: true, result, middle}`
- 멈춤 — `{ok: false, reason}` (200). 까닭 = 엔진 글 그대로(「입력 「돌」 없음」 · 「값 없음 — 관리자가 채울 값」 …).
- `row` 없음 = 저장된 파일로 계산 · `row` 있음 = 그 로직만 고친 줄로 바꿔(없던 로직이면 `file` 에 더해) 메모리에서 계산 — 파일에 안 씀. 요소 값은 늘 저장된 파일.
- 저장 안 한 새 초안 = `key` "" · `row` 에 로직 한 줄 통째 · `file` 은 안 보내도 됨(서버가 메모리에만 얹음). 「만들기」 모달의 [시험 계산] 이 이 길을 씀 — 키를 미리 받지 않음.
- `끝수` 가 묶음이면 계산 끝에 붙음(6장) · 글자면 설명일 뿐 계산에 안 붙음.
`POST /text` — 몸은 `/calc` 와 같고, 같은 계산을 **사람이 읽는 식 줄**로 돌려줌(화면은 받은 글을 그대로 찍음).
- 됨 — `{ok: true, groups: [{비목, 줄: [{이름, 글, 이름글, 짝, 금액, 까닭?}], 소계, 끝수}], 계, 별칭: […]}` · 멈춤은 `/calc` 와 같은 `{ok: false, reason}`.
- `글` = 호표 줄은 「단가 \* 수량식 = 금액」 · 덧줄은 「식 = 금액」. 중간 값 · 요소 · `찾기()` · `로직()` · `만약()` 은 글자로 안 나오고 **그 자리에서 나온 수**로 · 「… / 100」 은 「… %」 로 · 수는 화면과 같은 자릿수(소수 넷째 자리까지 · 천 단위 쉼표).
- `이름글` = 같은 줄을 **변수 이름으로** 적은 것(「석공노임 \* 메쌓기인력.석공 \* (1 + 증가율 %) = 석공비」) — 화면 왼쪽 칸 · `글` 은 오른쪽 칸.
- 단가 자리 = 「요소 이름 + 노임(인력) · 손료(기계) · 단가(그 밖)」 · 비목이 나뉜 줄은 「이름 + 비목 + 단가」.
- 금액 자리 = 「이름 + 비」 · 비목이 나뉜 줄은 「이름 + 비목」 · 덧줄·결과는 그 줄 이름.
- 표 찾기 · 로직 부르기 = **별칭 이름**(7장 규칙 그대로 · 값 칸은 `.석공` 처럼 뒤에 붙음) · 중간 값 · 입력 · 비목 이름은 그대로 · `만약()` 은 두 꼴 모두 **걸린 쪽만** 적어 줄이 어긋나지 않음.
- `짝` = 1 부터 매긴 줄 번호(답 전체에서 하나씩 · 묶음을 가로질러 차례대로) — 두 칸을 줄 맞춰 놓고 한쪽을 누르면 다른 쪽을 밝히는 데 씀. 두 꼴은 한 줄 안에 나란히 오므로 **줄 수와 차례가 늘 같음**.
- `별칭` = `이름글` 에 쓰인 표·로직 이름 목록 — 7장 `별칭` 과 같은 모양(`{이름, 종류, 참조, 원문, 밑이름, 값칸, 결과단위, 자리}`).
- `비목` = `노무비` · `재료비` · `경비`(돈 아닌 로직은 묶음 하나 `결과`) · 줄이 없는 비목은 빠짐 · `소계` 는 끝수까지 붙은 값이고 `끝수` 는 그 한 마디(「원 미만 버림」 · 없으면 "") — 끝수는 따로 줄로 안 보임.
- 하위 로직 줄처럼 한 줄이 여러 비목에 걸리면 비목마다 한 줄씩 · 그 비목 몫으로 단가를 되계산해 적음.
- `까닭` = 단가가 0 이거나 못 구한 줄의 한 마디(「수량 0 — 이 조건에서는 안 쓰는 줄」 · 「단가 0 — 「MP000082」 값이 아직 없음」).
## 4. 저장
`POST /save` 받음 `{files: [{file, version, changes: [{op, key, row}]}]}`
- `op` — `edit`(키 `key` 줄을 `row` 로 바꿈 · 키는 못 바꿈) · `add`(`row` 더함 · 키는 서버가 `_키대장.json` 의 다음 번호로 줌) · `delete`(`key` 줄 지움).
- 요소 파일 = `줄` 한 줄 · 표형 파일 = `표` 하나 · 로직 파일 = 로직 한 줄. `row` 는 통째(칸 일부만 보내지 않음).
- 화면은 조작을 캐시에 쌓고 [저장] 한 번에 보냄 — 서버는 이때만 파일에 씀.
- 순서: 모든 `version` 대조 → 모두 적용(메모리) → 검사 → 모두 씀. 하나라도 걸리면 아무것도 안 씀.
| 답 | 뜻 | 몸 |
| --- | ------------------------------- | -------------------------------------------------- |
| 200 | 씀 | `{files: [{file, version}]}` — 새 판본 |
| 409 | 낡은 화면 — 그 사이 파일이 바뀜 | `{detail: {stale: [file…]}}` → 다시 읽고 다시 고침 |
| 422 | 검사 걸림 | `{detail: {errors: [글…]}}` |
| 404 | 없는 파일·키 | `{detail}` |
| 400 | 틀린 `op` · 고친 줄의 키를 바꿈 | `{detail}` |
- 검사 = `check_master.py` 의 틀(고친 파일) + 로직 변수(모든 로직 파일, 고친 뒤 마스터 기준). **고친 뒤 새로 생긴 걸림**만 막음 — 원래 있던 걸림은 막지 않음. 인력·재료의 원문번호 겹침도 걸림(표·로직 원문번호는 절 번호라 겹쳐도 됨 · 자재품목은 줄에 원문번호가 없음).
- 요소를 지워 어떤 로직이 가리키던 것이 사라지면 로직 변수 검사에 걸림(422).
- 쓰기는 UTF-8 · LF · 들여쓰기 2칸.
## 5. 자체 로직 (만들기 · 본뜨기 · 고치기 · 지우기)
정본 로직(`소유` 「공용」 · `로직_산림품셈…` · `로직_건설품셈…`)은 이 길로 못 고침 — 403. 고치려면 먼저 복제.
- 자리 = `로직_자체.json` 한 파일(원문 장으로 나누지 않음 — 틀이 「자체」 파일에 장 이름을 두지 않음) · 없으면 서버가 머리(`{"그룹": "로직", "원문": "자체", "판": <올해>, "줄": []}`)와 함께 만듦.
- 키 = 서버가 `_키대장.json` 에서 `GX` 다음 번호로 줌 — 화면은 키를 만들지 않음. 원문 절이 같아도 줄마다 다른 키.
- `소유` = `현장`(기본) · `공용`. 줄의 `구분` = 「자체」 · `상세구분` = 「NN장 장 제목」(`출처`·`원문번호` 에서 서버가 찾음 · 원문 절이 없으면 빈 글)은 서버가 넣음.
| 길 | 받음 | 줌 |
| -------------------- | ----------------------- | ------------------------------------------------------------ |
| `POST /logic/new` | `{logic, owner?}` | `{file, version, key, logic}` |
| `POST /logic/copy` | `{key, 이름?, owner?}` | `{file, version, key, logic}` — 정본·자체 둘 다 본뜰 수 있음 |
| `POST /logic/edit` | `{key, version, logic}` | `{file, version, logic}` |
| `POST /logic/delete` | `{key, version}` | `{file, version}` |
- `logic` = 로직 한 줄 통째(`_틀.md` 7장) · `키`·`구분`·`상세구분` 은 보내도 서버 것이 이김.
- `version` = `GET /logic` 이 준 그 파일 판본 — 다르면 409 `{detail: {stale: [file]}}`.
- `copy` 는 비고 끝에 「`<본뜬 키>` 를 본뜸」 을 붙이고 `시험입력` 은 그대로 가져옴 · `이름` 을 주면 그 이름으로.
- 답 — 200 · 400(틀린 몸) · 403(정본 로직) · 404(없는 키·파일) · 409(낡은 판본) · 422 `{detail: {errors: […]}}`(검사 걸림 — `/save` 와 같은 검사).
- 지움은 그 로직을 부르는 다른 로직이 있으면 422.
## 6. 끝수
로직 줄의 `끝수` 는 묶음 `{대상, 자리, 방법, 출처?, 비고?}` — 계산 맨 끝에 붙음.
- `대상` — `계`(돈 로직 · 비목 합 셋을 각각 끊고 계 = 그 셋의 합) · `결과`(돈 아닌 로직의 결과 수).
- `자리` = 소수 자리(0 = 원 미만 · 3 = 소수 넷째 자리에서).
- `방법` — `버림` · `올림` · `반올림`.
- 옛 줄의 글자 `끝수`(「손료 원 미만 버림(…)」)는 원문 근거를 적어 둔 설명 — 계산에 안 붙음(그 끊기는 이미 식 안에 있음).
## 7. 복사해서 만들기 (별칭 꼴 식)
정본 로직을 식 줄로 펴 보이고, 고친 식을 자체 로직(`GX`)으로 저장하는 길. 규칙은 `ref/_설계_식_복사.md` · 엔진은 `scripts/master_copy.py`.
| 길 | 받음 | 줌 |
| ----------------------------- | -------------------------------------- | -------------------------------------------------------------- |
| `GET /logic/formula` | `key` | `{file, version, key, 소유, prices, 로직, 별칭: […], 줄: […]}` |
| `POST /logic/formula/save` | `{로직, 별칭, owner?, 이름?, 본뜬키?}` | `/logic/new` 와 같음 — `{file, version, key, logic}` |
| `POST /logic/formula/preview` | `{로직, 별칭}` | `{logic}` — 별칭을 원문으로 되돌린 로직 줄만(저장 안 함) |
- `로직` = 로직 한 줄 통째(`_틀.md` 7장) · **식 칸만** 별칭 꼴(`찾기(…)`·`로직(…)` 한 덩이가 이름 하나로). 식이 든 칸 = `호표[].수량` · `호표[].요소`(글자일 때) · `중간[].식` · `덧줄[].식` · `결과.식`.
- `별칭` = `{이름, 종류(찾기·로직), 참조, 원문, 밑이름, 값칸, 결과단위, 자리: […]}` — `원문` 이 되돌릴 부르기 글 그대로 · `자리` 는 그 별칭이 쓰인 자리표 목록.
- `줄` = 화면에 쌓을 평평한 목록(읽는 차례 중간 → 호표 → 덧줄 → 결과) · `{자리, 갈래, 차례, 이름, 단위, 비목, 식, 출처}` · 호표 줄은 `종류`·`요소` 가 붙음.
- 별칭 이름 — 표·로직의 `이름` 에서 낱말 글자만 남김 · 비거나 12자를 넘으면 키를 그대로 · 완전히 같은 부르기는 하나로 합침 · 같은 표를 다른 인자로 부르면 등장 차례로 번호(`합판거푸집1`…). 입력·중간·비목·함수·표 값 칸 이름과 겹치면 뒤에 번호를 더 붙임.
- 값 칸(`.석공`)은 별칭 뒤에 그대로 둠 — 별칭은 부르기 한 덩이까지만.
- **별칭은 저장하지 않음** — 화면에 보일 때만 씌우는 겉치레. 저장은 별칭을 `원문` 으로 되돌린 뒤 5장 `logic/new` 를 그대로 씀(정본은 손대지 않음 · 늘 새 `GX` 줄).
- 되돌릴 때 이미 원문으로 적힌 부르기 안 · 작은따옴표 안 · `.` 뒤 값 칸 이름은 건드리지 않음.
- `별칭[].원문` 은 부르기 하나여야 함 — 아니면 400. 표를 바꾸면 화면이 그 별칭의 `원문` 을 새 부르기로 바꿔 보냄.
- `본뜬키` 를 주면 비고 끝에 「`<키>` 를 본뜸」 을 붙임(5장 `logic/copy` 와 같은 말) · `이름` 을 주면 그 이름으로.
- 답 — 200 · 400(별칭·몸이 틀림) · 404(없는 키) · 422(검사 걸림 — `/save` 와 같은 검사).
- 복사본은 원본을 따라가지 않음(값을 그대로 박은 스냅숏) · 식 안 `로직(…)` 부르기는 참조 그대로라 그 하위 로직은 계속 정본을 봄.
- `formula/preview` 는 저장 전 시험 계산용 — 되돌린 로직 줄을 `POST /calc`·`/text` 의 `row`(`file` 은 `GET /logic/formula` 가 준 `file`)로 그대로 넣어 씀(3장과 같은 길).
## 8. 식으로 만들기
`POST /logic/draft` 받음 `{text, decided, head?, save?, owner?}` — 줄로 적은 식을 받아 변수를 뽑고 검사한 뒤 로직 한 줄로 옮김(해석기 `scripts/master_draft.py`).
- `text` = 줄마다 「이름 = 식」 **또는 「식 = 이름」**(`A * B = C`) · 한쪽이 값 이름 하나면 그쪽이 이름 · 양쪽이 다 식이면 그 줄의 등호 자리를 짚어 `못읽음`. 괄호 안 등호(`찾기(…, 뒷길이=높이)`)와 비교 기호(`==` `<=` `>=` `!=`)는 안 가름. 빈 줄과 `#` 는 건너뜀 · **마지막 줄의 값 이름이 결과 변수**. 식 글자는 `_틀.md` 8장 범위만.
- `decided` = 변수마다 정한 것 `{이름: {무엇, 단위, 비목, 요소 | 찾기 | 값, 설명 · 고르기 · 범위}}`.
- `무엇` — `마스터요소`(키 또는 재료 고르기 조건을 `요소` 에 · `비목` 도 정함) · `설계입력`(입력 칸이 됨) · `고정값`(`값` 을 중간으로) · `표찾기`(`찾기` = `{표, 값칸, 조건}` → `찾기(…).값칸` 을 중간으로).
- `단위` — 마스터요소는 그 줄의 단위(「인」 · 「포」)를 적음. 식에서는 단가라 `원/단위` 로 셈.
- **단위 없는 맨 수**(횟수 · 배율 · 비율)는 단위 칸에 `없음`(또는 `맨수` · `-` · `%` · 빈 글)을 골라 무차원으로 둠 — 「전용 4회」 를 「회」 로 두면 `원/회` 가 남아 덧셈 검사에 걸림. 칸 자체가 없으면 아직 안 정한 것(`정할것` 에 `단위`).
- 비목 합 이름(`노무비` · `재료비` · `경비` · `계`)은 정하지 않음 — 덧줄에서만 쓰는 이름.
- `head` = 로직 머리(`이름` · `결과단위` · `원문번호` · `출처` · `비고` · `끝수`). 없으면 검사만 함.
- 줌 `{줄, 변수, 결과, 문제, 다됨, logic}` — `logic` = 옮긴 로직 한 줄(`head` 가 있을 때만 · 저장 전).
- `변수` = `{이름, 처음, 갈래, 정의줄, 쓰임, 무엇, 단위, 비목, 정할것}` · `갈래` = `정의됨`(식이 있음) · `미정`(쓰기만 함) · `비목합` · `정할것` = 아직 안 정한 칸 이름들. `단위` 는 그 값이 **식에서** 가지는 단위(무차원은 빈 글).
- `문제` = `{줄, 자리, 갈래, 말}` · `자리` = 그 줄의 글자 자리(모르면 null) · `갈래` = `못읽음` · `이름` · `두번정의` · `돌고돎` · `안쓰임`(= 결과가 하나인지) · `단위` · `덜정함` · `항` · `비목` · `비목합`.
- 단위 검사 — 곱셈은 단위를 곱하고 나눗셈은 나눔(같은 단위끼리 나누면 맨 수) · 덧셈·뺄셈과 `만약`·`최소`·`최대`는 단위가 같아야 함 · 비교는 한쪽이 맨 수면 안 따짐(`높이 <= 3`). 「m \* m」 과 「㎡」 는 같게 봄. 모르는 단위는 안 따짐.
- **덧줄이 될 값**(결과 식의 항 가운데 마스터요소가 없는 것)은 막지 않고 그 값의 `정할것` 에 `비목` 을 얹음 — 화면은 「비목을 골라 주세요」 로 보임.
- `save` = true 면 `POST /logic/new` 와 같은 자리에 저장(키 `GX` 는 서버 · 소유 `owner`) → 답에 `{file, version, key, logic}` 이 더 옴. 문제가 하나라도 있으면 422 `{detail: {errors, 문제}}` 로 아무것도 안 씀.
- 옮기는 법 — 돈 로직(`결과단위` 가 「원」 으로 시작)은 결과 식을 `+` 로 갈라 항마다: 마스터요소 변수 하나면 **호표 줄**(나머지 곱이 `수량`) · 비목만 정한 변수면 **덧줄** · 그 밖은 문제 `항`. 나머지 정의 줄은 `중간`(쓰는 차례대로). 돈 아닌 로직은 호표 없이 `중간` + `결과` 식.
- 표 찾기는 글자로 치지 않음 — 변수 하나로 두고 `무엇` 을 `표찾기` 로 정할 때 모달이 `찾기` 묶음을 채움.
## 9. 일위대가 조합
관리자가 이름을 붙이고 **단가산출 로직을 골라 담은 묶음**. 파일 모양은 `_틀.md` 10장 · 엔진은 `scripts/master_combo.py`.
- 자리 = `일위대가조합.json` 한 파일 · 없으면 서버가 머리(`{"그룹": "일위대가조합", "판": <올해>, "줄": []}`)와 함께 만듦.
- 키 = 서버가 `_키대장.json` 에서 `UA` 다음 번호로 줌 — 화면은 키를 만들지 않음.
- 조합은 **담은 로직 목록만** 지님 — 수량 · 비율 · 계산은 두지 않음(프로젝트 몫 · B08 · B09).
- 조합은 **요소 그룹이 아님** — `GET /groups` 목록에 끼지 않고 `/rows`·`/groups/{group}/files` 로 다루지 않음(줄 모양이 요소와 아주 다름). 아래 제 길로만.
| 길 | 받음 | 줌 |
| --------------------- | -------------------------------- | -------------------------------------------------------- |
| `GET /combos` | `sub?` `detail?` `q?` `owner?` | `{combos: […]}` — 줄마다 `count` · `blocked` · `reasons` |
| `GET /combo` | `key` | `{file, version, combo, 줄: […], blocked, reasons}` |
| `GET /logic/combos` | `key` | `{key, combos: […]}` — 이 로직을 담은 조합 |
| `POST /combo/new` | `{combo, owner?}` | `{file, version, key, combo}` |
| `POST /combo/edit` | `{key, version, combo, owner?}` | `{file, version, combo}` |
| `POST /combo/delete` | `{key, version}` | `{file, version}` |
| `POST /combo/preview` | `{combo, inputs}` | `{키, 이름, 단위, 줄: […], 노무비, 재료비, 경비, 계, 멈춤}` |
- `본뜬키` = 본뜬 원본 조합 키(같은 파일의 다른 UA) 또는 `null`(원본) — 없는 키 · 자기 키는 400 · 목록 · 상세에 그대로 나옴.
- `POST /combo/delete` 는 구조물집계표 · `구조물집계표조합` 의 「일위대가」 칸으로 쓰는 열이 있으면 409 `{detail: {errors: […], users: [구조물 이름…]}}` — 지우지 않음.
- 조합 알약 `GET /combo/{키}/inputs` = `{키, 이름, 물량: [{키 = 「이름|단위」, 이름, 단위, 로직: […], 배?}], 조건: []}` — 같은 이름 · 단위 물량은 한 알약.
- `combo` = 조합 한 줄 통째(`_틀.md` 10장) · `키`·`소유` 는 보내도 서버 것이 이김 · 안 준 칸은 `null` 로 채워 열 한 벌을 맞춤.
- `version` = `GET /combo` 가 준 그 파일 판본 — 다르면 409 `{detail: {stale: ["일위대가조합.json"]}}`.
- `GET /combo` 의 `줄` = 담은 로직에 이름을 붙인 목록 `{차례, 로직, 메모, ok, 이름, 원문번호, 구분, 상세구분, 결과단위, 출처}` — 없는 키는 `ok` false 와 `까닭`.
- `GET /logic/combos` 의 `combos` 줄 = `{키, 이름, 구분, 상세구분, 단위, 소유, 차례}` — `차례` 는 그 조합 안 몇 번째인지(여러 번이면 여럿). 로직 줄에 칸으로 박지 않고 물을 때 훑음.
- `preview` 는 **보기만** — 마스터에 아무것도 쓰지 않음. `combo` 는 저장된 키(글자)여도 되고 저장 전 조합 줄(묶음)이어도 됨.
- `inputs` = `{로직 키: {입력 이름: 값}}` — 담은 로직마다 그 자리에서 따로 받음(로직마다 하는 일이 달라 수량이 따로 들어감).
- `줄` = `{차례, 로직, 메모, 이름, 원문번호, 결과단위, 구분, 상세구분, 출처, ok, 갈래, 노무비, 재료비, 경비, 계}` · 돈이 아닌 로직은 `갈래` 「결과」 와 `결과` 수 하나(비목이 없어 합계에 안 얹음).
- 못 도는 줄은 통째로 멈추지 않고 그 줄만 `ok` false · `까닭` · `멈춤` 에 한 줄 — 나머지 줄의 합계는 그대로 냄.
- 답 — 200 · 400(틀린 몸 · 이름 없음 · 모르는 소유) · 404(없는 키) · 409(낡은 판본) · 422 `{detail: {errors: […]}}`(검사 걸림 — 없는 로직 키 · 같은 로직 두 번 · 빈 조합 · 조합을 담음).
## 10. 재료 · 기계 고르기
재료 줄은 키 대신 기본 검색어로 품목을 찾음 — 파일 모양 · 찾기 규칙 · 자동값 차례 · 단위 환산은 `_틀.md` 7장.
| 무엇 | 모양 |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| 고른 품목 | `/calc` · `/text` 몸에 `"고름": {"<호표 줄 차례>": "MT000019"}` · 저장 안 함 · 기계는 그 입력값에 원문 분류번호(목록 밖 번호도 기계 마스터에 있으면 받음) |
| 단위 경고 | 성격이 다르면 계산은 하되 `/calc` 줄에 `"단위경고": "줄 m · 자재 Ton"` · 화면은 그 줄에 딱지 |
| 모달 검색 | 재료 = 자재품목 표 · 검색 칸 = 검색어 첫 낱말(예 「합판」) · 잡힌 품목 칠함 / 기계 = 기계 표 · 검색 칸 = 기계 이름(예 「크레인(타이어)」) |
- `고름` 이 없거나 그 줄에 없으면 자동값(`_틀.md` 7장 차례의 첫째).
- 고르면 다시 계산 · 고른 품목은 파일에 저장하지 않고 계산 요청에만 실음.
- 드롭다운 고르기는 걷음 — 재료 줄 · 기계 입력의 「품목 고르기」 가 모달을 검색 칸 채워 엶(재료 컨테이너 표 재사용).
- 인력은 고르지 않음(지정값).