Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_2

This commit is contained in:
2026-10-01 13:00:01 +09:00
6 changed files with 336 additions and 1 deletions
+1
View File
@@ -0,0 +1 @@
_data/
+108
View File
@@ -0,0 +1,108 @@
/* =============================================================================
* M01_SL_Api.ts
* 시트 로직 Api — 목록 · 열기 · 저장(서버가 바깥 값을 다시 넣고 같은 엔진으로 다시 셈) · 지우기 ·
* 바깥 값 한 번에 받기(품셈 표 · 노임 · 자재 · 장비 · 기존 로직 결과)
* 서버 = `M01_SL_Router.py`(`/api/m01/sl/*`) · 정함 `docs/raw/M01_쉬운단가/시트로직_정함.md` 4 · 5 · 6
* ========================================================================== */
import type { Workbook } from "../../A00_Common/spreadsheet/spreadsheet_types";
/** 바깥 값 하나 — 값은 열 때 · 저장 때 마스터에서 새로(표는 사본으로 들고 있지 않음)
* · 표 = 품셈 표 통째(머리 줄 + 줄 · 범위 조건 · 값은 「이름·아래」 「이름·위」 두 칸)
* · 노임 · 자재 = 그 줄의 단가(`/calc` 와 같은 셈 · 준용 · 낮은 값)
* · 장비 = 기계경비 로직 부름 글(EasyCalc `machineElement` 의 element) + 넘길 입력값 → 한 시간 값
* · 로직 = 기존 로직 결과(`/calc` · 돈 로직은 `field` 비목 · 기본 계) */
export type SlOutside =
| { id: string; kind: "표"; table: string }
| { id: string; kind: "노임"; key: string }
| { id: string; kind: "자재"; key: string }
| {
id: string;
kind: "장비";
element: string;
inputs: Record<string, unknown>;
field?: SlField;
}
| {
id: string;
kind: "로직";
key: string;
inputs: Record<string, unknown>;
field?: SlField;
};
export type SlField = "노무비" | "재료비" | "경비" | "계";
/** 바깥 값이 들어갈 자리 — 시트 id · 칸(표는 이 칸을 왼쪽 위로 펼침) */
export interface SlOutsideAt {
item: SlOutside;
sheet: string;
cell: string;
}
/** 바깥 값 답 — 표는 `rows`(머리 줄 포함) · 그 밖은 `value` · 못 구하면 ok false + 쉬운 말 */
export interface SlOutsideValue {
id: string;
ok: boolean;
value?: number | null;
rows?: (string | number | null)[][];
name?: string;
unit?: string;
reason?: string;
}
/** 시트 로직 한 벌 = 통합문서 하나(상자 = 시트) + 바깥 값 자리 */
export interface SlLogic {
/** 새 로직 null · 저장하면 서버가 `SL-0001` 꼴로 줌 */
key: string | null;
이름: string;
결과단위: string;
book: Workbook;
outside: SlOutsideAt[];
/** 다른 창이 더하는 칸(상자 · 알약 · 선 …)은 그대로 저장 · 돌려줌 */
[extra: string]: unknown;
}
/** 목록 한 줄 */
export interface SlListItem {
key: string;
이름: string;
결과단위: string;
/** 마지막 저장 때(ISO) */
saved: string;
}
/** 저장 답 — `logic.book.계산값` 은 서버가 다시 셈한 값 · `values` 는 넣은 바깥 값 */
export interface SlSaved {
key: string;
logic: SlLogic;
values: SlOutsideValue[];
}
const notReady = (): never => {
throw new Error("준비 중");
};
export async function slList(): Promise<SlListItem[]> {
return notReady();
}
export async function slGet(_key: string): Promise<SlLogic> {
return notReady();
}
export async function slSave(_logic: SlLogic): Promise<SlSaved> {
return notReady();
}
export async function slDelete(_key: string): Promise<void> {
return notReady();
}
export async function slOutside(_items: SlOutside[]): Promise<SlOutsideValue[]> {
return notReady();
}
/** 실패 → 쉬운 말(EasyCalc `plainError` 그대로) */
export function slPlain(_e: unknown): string {
return notReady();
}
+49
View File
@@ -0,0 +1,49 @@
/* =============================================================================
* M01_SL_Eval.ts
* 시트 로직 화면 계산 — 바깥 값 넣기 → 우리 스프레드시트 엔진(브라우저) → 출력 알약 · 결과 읽기 · 검사
* 저장 때는 서버가 같은 엔진을 Node 로(정함 5) · DOM 없음
* ========================================================================== */
import type { SheetLogic, SlOutside } from "./M01_SL_Model";
/** 알약 값 — 수 · 글 · 참거짓 · 빈 칸 */
export type SlValue = number | string | boolean | null;
/** 바깥 값 하나 — 표는 칸 덩어리(cell 이 왼위) */
export interface SlOutsideValue {
node: string;
cell: string;
value: SlValue | SlValue[][];
}
export interface SlEvalResult {
/** 상자 id → 출력 알약 이름 → 값 */
pills: Record<string, Record<string, SlValue>>;
result: { 노무비: number | null; 재료비: number | null; 경비: number | null; 계: number | null };
/** 쉬운 말 */
errors: string[];
}
const notReady = (): never => {
throw new Error("준비 중");
};
/** 바깥 값 칸 목록 — 화면 · 서버가 이 칸에 값을 넣음 */
export function outsideCells(_sl: SheetLogic): SlOutside[] {
return notReady();
}
/** 바깥 값 넣기 — 새 시트 로직(받은 것은 안 바꿈) */
export function applyOutside(_sl: SheetLogic, _values: SlOutsideValue[]): SheetLogic {
return notReady();
}
/** 화면 계산 */
export function evalLogic(_sl: SheetLogic): SlEvalResult {
return notReady();
}
/** 검사 — 빈 입력 알약 · 순환 · 오류 칸(쉬운 말 목록) */
export function checkLogic(_sl: SheetLogic): string[] {
return notReady();
}
+133
View File
@@ -0,0 +1,133 @@
/* =============================================================================
* M01_SL_Model.ts
* 시트 로직 Model — 로직 한 벌 = 통합문서 하나 · 상자 하나 = 시트 하나 · 알약 = 이름 붙은 칸
* 선 = 받는 입력 알약 칸에 「'시트 이름'!칸」 참조 수식 · DOM 없음
* 모양은 `docs/raw/M01_쉬운단가/시트로직_정함.md` 정함 1~5 그대로
* ========================================================================== */
import type { Workbook } from "../../A00_Common/spreadsheet/spreadsheet_types";
/** 상자 종류 — 입력값 · 고정값 · 표 · 계산 · 항목 · 다른 로직 · 결과 */
export type SlKind = "input" | "const" | "table" | "calc" | "item" | "logic" | "result";
/** 알약 = 이름 붙은 칸 */
export interface SlPill {
name: string;
/** 「B3」 */
cell: string;
unit?: string;
/** 바깥 값 칸 — 열 때 · 저장 때 마스터에서 넣음 */
outside?: boolean;
}
/** 상자 = 시트 하나 */
export interface SlNode {
id: string;
kind: SlKind;
name: string;
sheetId: string;
pillsIn: SlPill[];
pillsOut: SlPill[];
/** 표 키 · 요소 · 기존 로직 키 · 내 표 id */
ref?: string;
/** 띠 단계 — 0 입력값 · 1 표 · 2 계산 · 3 항목 · 4 결과 */
stage: number;
}
/** 선 — 출력 알약 → 입력 알약 */
export interface SlEdge {
id: string;
from: { node: string; pill: string };
to: { node: string; pill: string };
}
/** 바깥 값 칸 */
export interface SlOutside {
node: string;
cell: string;
kind: "table" | "price" | "logic";
ref: string;
}
/** 내 표 — 품셈에 없는 표(사용자가 만듦) */
export interface SlUserTable {
id: string;
name: string;
cols: string[];
rows: (number | string | null)[][];
}
export interface SheetLogic {
key: string | null;
name: string;
resultUnit: string;
workbook: Workbook;
nodes: SlNode[];
edges: SlEdge[];
userTables?: SlUserTable[];
}
/** 알약 한쪽 끝 */
export interface SlEnd {
node: string;
pill: string;
}
const notReady = (): never => {
throw new Error("준비 중");
};
/** 빈 시트 로직 */
export function newLogic(_name?: string, _resultUnit?: string): SheetLogic {
return notReady();
}
/** 상자 더하기 — 그 종류 틀(칸 A 이름 · B 값 · C 설명)로 시트를 만듦 */
export function addNode(
_sl: SheetLogic,
_kind: SlKind,
_name: string,
): { sl: SheetLogic; node: SlNode } {
return notReady();
}
/** 상자 지우기 — 시트 · 이어진 선 · 받던 참조 수식을 함께 지움 */
export function removeNode(_sl: SheetLogic, _node: string): SheetLogic {
return notReady();
}
/** 선 잇기 — 받는 알약 칸에 참조 수식 · 순환 · 없는 알약이면 막고 까닭(쉬운 말) */
export function connect(
_sl: SheetLogic,
_from: SlEnd,
_to: SlEnd,
): { sl: SheetLogic; why: string | null } {
return notReady();
}
/** 선 끊기 — 그 참조를 받는 칸 수식에서 뺌 */
export function disconnect(_sl: SheetLogic, _edge: string): SheetLogic {
return notReady();
}
/** 알약 정하기 — 같은 칸이나 같은 이름이 있으면 바꿈 */
export function setPill(
_sl: SheetLogic,
_node: string,
_cell: string,
_dir: "in" | "out",
_name: string,
_extra?: Pick<SlPill, "unit" | "outside">,
): SheetLogic {
return notReady();
}
/** 알약 빼기 — 이어진 선도 끊음 */
export function removePill(
_sl: SheetLogic,
_node: string,
_dir: "in" | "out",
_name: string,
): SheetLogic {
return notReady();
}
+3 -1
View File
@@ -1,5 +1,5 @@
{
"다음": {"LB": 409, "EQ": 655, "RT": 20, "MN": 7000, "MT": 30719, "MO": 74, "MP": 364, "FX": 6, "CF": 159, "QF": 477, "GF": 331, "CC": 341, "GC": 1140, "QC": 2060, "GX": 2, "UA": 38},
"다음": {"LB": 409, "EQ": 655, "RT": 20, "MN": 7000, "MT": 30719, "MO": 74, "MP": 364, "FX": 6, "CF": 159, "QF": 477, "GF": 331, "CC": 341, "GC": 1140, "QC": 2060, "GX": 4, "UA": 38},
"폐기": {"MT000001": "자재품목 휘발유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000002": "자재품목 경유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000003": "자재품목 선박용경유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000004": "자재품목 중유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000005": "자재품목 등유 — 값이 비고 글뿐 · 유가 테이블로 감", "LB000399": "이름 「〃」 — 직종이 아니라 되풀이 기호 · 소요량 13-6-9 에서 윗줄 직종으로 바로잡음", "MN": "재료_나라장터자재 — old 로 옮김 · 로직은 자재품목·품셈재료로 갈아 끼움", "LB000372": "미확보 고급기능사 — 측량노임 항공사진고급기능사 옛이름으로 옮김", "LB000393": "미확보 리베팅공 — 건설노임 철골공 옛이름으로 옮김(2010 통합표 연번10)", "LB000405": "미확보 Belt Conveyor 설치공 — 품셈 13-10-1 주② 배분으로 다섯 직종에 나눔", "MT000006": "자재품목 전력 — 재료_유가전력 전력 줄로 감", "MT000007": "자재품목 전력 — 재료_유가전력 전력 줄로 감", "LB000368": "미확보 기술사 — 기술사(건설) 옛이름으로 옮김", "LB000369": "미확보 특급기술자 — 특급기술자(건설) 옛이름으로 옮김", "LB000370": "미확보 고급기술자 — 고급기술자(건설) 옛이름으로 옮김", "LB000361": "미확보 중급기술자 — 중급기술자(건설) 옛이름으로 옮김", "LB000364": "미확보 초급기술자 — 초급기술자(건설) 옛이름으로 옮김", "LB000365": "미확보 고급숙련기술자 — 고급숙련기술자(건설) 옛이름으로 옮김", "LB000366": "미확보 중급숙련기술자 — 중급숙련기술자(건설) 옛이름으로 옮김", "LB000367": "미확보 초급숙련기술자 — 초급숙련기술자(건설) 옛이름으로 옮김", "LB000389": "미확보 기계기사 — 초급기술자(기계·설비) 옛이름으로 옮김", "LB000388": "미확보 기계산업기사 — 초급숙련기술자(기계·설비) 옛이름으로 옮김", "MO000055": {"원문번호": "주택용저압:기본_200이하", "파일": "재료_유가전력.json"}, "MO000056": {"원문번호": "주택용저압:기본_201~400", "파일": "재료_유가전력.json"}, "MO000057": {"원문번호": "주택용저압:기본_400초과", "파일": "재료_유가전력.json"}, "MO000059": {"원문번호": "주택용저압:전력량_다음200", "파일": "재료_유가전력.json"}, "MO000060": {"원문번호": "주택용저압:전력량_400초과", "파일": "재료_유가전력.json"}, "MO000061": {"원문번호": "주택용저압:월간최저요금", "파일": "재료_유가전력.json"}, "MO000062": {"원문번호": "일반용갑I저압:기본", "파일": "재료_유가전력.json"}, "MO000063": {"원문번호": "일반용갑I저압:전력량_여름철", "파일": "재료_유가전력.json"}, "MO000064": {"원문번호": "일반용갑I저압:전력량_봄가을철", "파일": "재료_유가전력.json"}, "MO000065": {"원문번호": "일반용갑I저압:전력량_겨울철", "파일": "재료_유가전력.json"}, "MO000066": {"원문번호": "산업용갑I저압:기본", "파일": "재료_유가전력.json"}, "MO000067": {"원문번호": "산업용갑I저압:전력량_여름철", "파일": "재료_유가전력.json"}, "MO000068": {"원문번호": "산업용갑I저압:전력량_봄가을철", "파일": "재료_유가전력.json"}, "MO000069": {"원문번호": "산업용갑I저압:전력량_겨울철", "파일": "재료_유가전력.json"}, "MO000070": {"원문번호": "임시전력갑:기본요금적용", "파일": "재료_유가전력.json"}, "MO000071": {"원문번호": "임시전력갑:전력량요금적용", "파일": "재료_유가전력.json"}, "MO000072": {"원문번호": "임시전력갑:월간최저요금", "파일": "재료_유가전력.json"}},
"키": {
"CC000001": {"원문번호": "공통 1-3-1 콘크리트 및 포장용 재료", "파일": "계수_건설품셈_01장_적용기준.json"},
@@ -2629,6 +2629,8 @@
"GF000329": {"원문번호": "13-11-2 타원형 돌망태", "파일": "로직_산림품셈_13장_구조물.json"},
"GF000330": {"원문번호": "13-15-1 목재 껍질벗기기", "파일": "로직_산림품셈_13장_구조물.json"},
"GX000001": {"원문번호": "13-4-1 돌쌓기 메쌓기(인력)", "파일": "로직_자체.json"},
"GX000002": {"원문번호": "12-34-4 암거이음받침 채움재(T=20㎜)", "파일": "로직_자체.json"},
"GX000003": {"원문번호": "13-4-1 돌쌓기 메쌓기(인력) (2)", "파일": "로직_자체.json"},
"LB000001": {"원문번호": "1001", "파일": "인력.json"},
"LB000002": {"원문번호": "1002", "파일": "인력.json"},
"LB000003": {"원문번호": "1003", "파일": "인력.json"},
+42
View File
@@ -28,6 +28,48 @@
"덧줄": [],
"끝수": null,
"시험입력": { "높이": 4.0, "돌": "깬잡석" }
},
{
"키": "GX000002",
"원문번호": "12-34-4",
"구분": "자체",
"상세구분": "12장 철근콘크리트",
"이름": "암거이음받침 채움재(T=20㎜)",
"결과단위": "원/㎡",
"출처": "산림품셈 12-34-4",
"소유": "현장",
"비고": "해석: 원문 재료비 「JOINT FILLER」 뿐 — ㎡당 1㎡ · 설치 품 원문 없음 · GF000208 를 본뜸",
"입력": [],
"중간": [],
"호표": [{ "종류": "재료", "요소": { "구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "JOINT FILLER" }, "이름": "JOINT FILLER(T=20㎜)", "단위": "㎡", "수량": "1", "비목": "재료비" }],
"덧줄": [],
"끝수": null
},
{
"키": "GX000003",
"원문번호": "13-4-1",
"구분": "자체",
"상세구분": "13장 구조물",
"이름": "돌쌓기 메쌓기(인력)",
"결과단위": "원/㎡",
"출처": "산림품셈 13-4-1",
"소유": "현장",
"비고": "고르기 첫 값 = 원문에 있는 조합(뒷길이 25 · 깬잡석 · 골쌓기) · 「원문에 없는 조합: 뒷길이=25 · 돌=견치돌 · 쌓기=골쌓기 / 뒷길이=25 · 돌=견치돌 · 쌓기=켜쌓기 / 뒷길이=25 · 돌=깬돌 · 쌓기=골쌓기 / 뒷길이=25 · 돌=깬돌 · 쌓기=켜쌓기 / 뒷길이=30 · 돌=견치돌 · 쌓기=골쌓기 외 5」 · GF000219 를 본뜸",
"입력": [
{ "이름": "뒷길이", "설명": "쌓을 돌의 뒤쪽 길이", "단위": "㎝", "고르기": [25, 30, 35, 45, 55, 60, 75] },
{ "이름": "돌", "설명": "쌓을 돌 종류", "고르기": ["깬잡석", "견치돌", "깬돌", "호박돌 및 야면석"] },
{ "이름": "쌓기", "설명": "돌을 어긋나게 쌓는지 줄 맞춰 쌓는지", "고르기": ["골쌓기", "켜쌓기"] },
{ "이름": "높이", "설명": "쌓는 벽 높이", "단위": "m" },
{ "이름": "초과증가율", "설명": "기준 높이를 넘어 더 드는 품 비율", "단위": "%", "범위": [80, 100] }
],
"중간": [{ "이름": "증가율", "식": "만약(높이 <= 3, 0, 만약(높이 <= 7.5, 찾기(QF000422, 높이=높이).증가율, 초과증가율))", "출처": "산림품셈 13-4-1 주③" }],
"호표": [
{ "종류": "인력", "요소": "LB000033", "이름": "석공", "단위": "인", "수량": "찾기(QF000421, 뒷길이=뒷길이, 돌=돌, 쌓기=쌓기).석공 * (1 + 증가율 / 100)", "비목": "노무비" },
{ "종류": "인력", "요소": "LB000002", "이름": "보통인부", "단위": "인", "수량": "찾기(QF000421, 뒷길이=뒷길이, 돌=돌, 쌓기=쌓기).보통인부 * (1 + 증가율 / 100)", "비목": "노무비" }
],
"덧줄": [],
"끝수": null,
"시험입력": { "높이": 4.0, "돌": "깬잡석" }
}
]
}