Merge remote-tracking branch 'origin/dev' into sub_laptop_2
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
* M02 사본(`copy`) — 저장 몸 {문서, 끊음(받은 그대로), 판} · 판 바뀜(409)은 문서가 그대로면 새 판으로 한 번 더
|
||||
* 기계 줄 · 옵션 가로 기계 = 기계 창(827-1 · `line_stack_machine`) — 초안은 문서 칸(캐시) · [저장] 때 서버가 내 기계로
|
||||
* [단가 갱신] · [재생성] = 미리 셈 → 알림 → 저장(`formula_doc_copy.ts` · 고친 것 먼저 저장)
|
||||
* 저장 전 문서(`memory` · 836-5 · 불러오기 미리 보기) = 빈 키 + 서버에 문서째(`본`) 읽음 · 왼쪽 줄 쌓기는 보기만(inert) · 디스크 쓰기 0
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
@@ -74,6 +75,8 @@ export interface StackScreenOptions {
|
||||
page?: () => void;
|
||||
/** 페이지 머리 칸(808-1 · 요구 701-3) — 있으면 [단가 갱신] · [재생성] 등 화면 단추를 여기에(화면 위 단추 줄 대신) */
|
||||
tools?: HTMLElement;
|
||||
/** 저장 전 SK 문서(836-5 · 불러오기 미리 보기) — 빈 키일 때 이 문서를 서버가 읽어 줌(파일 안 읽음 · 안 씀) */
|
||||
memory?: () => Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
type Docs = Record<string, FormulaDoc>;
|
||||
@@ -126,14 +129,21 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
let paired = ""; // 짝 강조 자리(679 — 항목 `k` · 줄 `k.j`)
|
||||
const blocks = new Set<string>(); // 풀린 공식 묶음
|
||||
|
||||
const mem = (): Record<string, unknown> | null => (cp && !key ? (opt.memory?.() ?? null) : null);
|
||||
const call = async <T>(path: string, body?: unknown, signal?: AbortSignal): Promise<T> => {
|
||||
const r = await fetch(`${API_BASE_URL}${cp ? cp.route(key, path) : opt.base + path}`, {
|
||||
method: body === undefined ? "GET" : "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
||||
signal,
|
||||
});
|
||||
const saved = mem(); // 저장 전 문서 = 길 키 「-」 · 몸 `본`
|
||||
const r = await fetch(
|
||||
`${API_BASE_URL}${cp ? cp.route(saved ? "-" : key, path) : opt.base + path}`,
|
||||
{
|
||||
method: body === undefined ? "GET" : "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
...(body === undefined
|
||||
? {}
|
||||
: { body: JSON.stringify(saved ? { ...(body as object), 본: saved } : body) }),
|
||||
signal,
|
||||
},
|
||||
);
|
||||
const data = (await r.json().catch(() => ({}))) as T & { detail?: unknown };
|
||||
if (!r.ok) {
|
||||
const why = data.detail;
|
||||
@@ -346,9 +356,9 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
);
|
||||
opt.tools?.replaceChildren(...tools);
|
||||
const items = stackOf(doc);
|
||||
root.dataset.edit = edit() ? "1" : "";
|
||||
root.dataset.edit = edit() || mem() ? "1" : "";
|
||||
left.replaceChildren(
|
||||
...(edit()
|
||||
...(edit() || mem()
|
||||
? [
|
||||
buildEdit({
|
||||
doc,
|
||||
@@ -368,6 +378,7 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
]
|
||||
: []),
|
||||
);
|
||||
left.inert = !!mem(); // 저장 전 문서 = 보기만(고침은 저장 뒤)
|
||||
right.replaceChildren(
|
||||
items.length || doc.결과
|
||||
? buildOut(doc, answer, {
|
||||
@@ -494,6 +505,26 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
[cut, mark, version] = [{}, undefined, ""];
|
||||
draw();
|
||||
if (!next) {
|
||||
if (mem()) {
|
||||
// 저장 전 문서(836-5) — 서버가 이 문서째 읽어 줌
|
||||
const mine = ++seq;
|
||||
try {
|
||||
const got = await call<Loaded>("/read", {});
|
||||
if (mine !== seq || key !== next) return;
|
||||
take(got);
|
||||
recalc();
|
||||
return draw();
|
||||
} catch (e) {
|
||||
if (mine === seq)
|
||||
head.replaceChildren(
|
||||
el("p", {
|
||||
className: "ui-text ui-text--muted",
|
||||
text: `${ft("Load_Failed")} — ${e instanceof Error ? e.message : ""}`,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (cp)
|
||||
return void right.replaceChildren(
|
||||
el("p", { className: "ui-text ui-text--muted", text: lt("Unsaved_New") }),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
POST /api/m02/layers/{층}/sangeun/{키}/doc/preview 미리 셈(층 쓰기) {문서?, 끊음?, 할일?}
|
||||
→ {…, 전, 후, 바뀐자원?, 버림?}
|
||||
POST /api/m02/layers/{층}/sangeun/{키}/doc/save 저장(층 쓰기) {문서?, 끊음?, 할일?, 판} → 읽기 꼴
|
||||
POST /api/m02/layers/{층}/sangeun/-/doc/read 저장 전 문서 읽기(층 읽기 · 836-5) {본} → 읽기 꼴 · 미리 셈 몸에도 `본` 가능
|
||||
GET /api/m02/layers/{층}/sangeun/{키}/doc/machine?no= 기계 창(827-1 · 층 읽기) → {사본, 지금, 새값}
|
||||
옛 별칭 장부 쓰기(812-9) · 연결 찾기 길(810-4)은 걷음(815-2 · 요구 762-1 · 765).
|
||||
⚠ 등록은 `main.py`(로그인만 · 층 권한은 길 안에서).
|
||||
@@ -29,6 +30,7 @@ class DocBody(BaseModel):
|
||||
할일: str | None = None
|
||||
판: str | None = None
|
||||
단가기준: str | None = None # 가져온 식만(807-1 · 엑셀 | 우리)
|
||||
본: dict[str, Any] | None = None # 저장 전 SK 문서째(836-5 · 파일 안 읽음 · 안 씀)
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/sangeun/{key}/doc")
|
||||
@@ -49,6 +51,16 @@ def machine_doc(
|
||||
return _call(_root(session, layer, owner), path.machine, key, no)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/sangeun/-/doc/read")
|
||||
def read_memory_doc(
|
||||
layer: Layer,
|
||||
body: DocBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
return _call(_root(session, layer, owner), path.read_memory, body.본)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/sangeun/{key}/doc/preview")
|
||||
def preview_doc(
|
||||
layer: Layer,
|
||||
@@ -57,7 +69,7 @@ def preview_doc(
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
folder = _root(session, layer, owner, write=body.본 is None) # 본 = 안 씀 → 읽기 권한이면 됨
|
||||
return _call(folder, path.preview, key, body.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ M01 수식 문서 길과 같은 꼴: 읽기 {문서, 셈} · 미리 셈 {문서}
|
||||
단가갱신 = 값 줄만 지금 M01(문서 그대로).
|
||||
가져온 식 SK(807-1) = 가져온 줄 문서 · 셈 = 단가기준(엑셀 값 · 연결 값) · 부름 · 끊음 없음
|
||||
(형제 SK 는 셈 때 읽음) · 단가갱신 = 문서 그대로 · 바탕 참조 SK = 값 사본 없음(400).
|
||||
저장 전 문서(836-5 · 불러오기 미리 보기 835-1) = 몸 `본`(그 SK 문서째) — 파일 안 읽음 · 안 씀 · 읽기 · 미리 셈만(`read_memory`).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -105,6 +106,20 @@ def read(key: str) -> dict[str, Any]:
|
||||
return out
|
||||
|
||||
|
||||
def read_memory(sk: Any) -> dict[str, Any]:
|
||||
"""저장 전 문서 읽기(836-5) — 몸 `본` 그대로 셈 · {read 꼴 · 판 = 빈 글} · 디스크 쓰기 0."""
|
||||
doc = _mem(sk)
|
||||
return {**answer(current(doc), doc), "판": ""}
|
||||
|
||||
|
||||
def _mem(raw: Any) -> dict:
|
||||
if not isinstance(raw, dict):
|
||||
raise _bad("본 · 묶음 아님")
|
||||
if len(json.dumps(raw, ensure_ascii=False, default=str).encode("utf-8")) > MAX:
|
||||
raise _bad("본 · 너무 큼")
|
||||
return raw
|
||||
|
||||
|
||||
# ── 몸 검사 ───────────────────────────────────────────────────────────
|
||||
def _doc(raw: Any, key: str, where: str) -> dict:
|
||||
if not isinstance(raw, dict):
|
||||
@@ -166,7 +181,11 @@ def preview(key: str, body: dict) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _preview(key: str, body: dict) -> dict[str, Any]:
|
||||
doc = _file(key)["문서"]
|
||||
if body.get("본") is not None: # 저장 전 문서(836-5) — 파일 아님
|
||||
doc = _mem(body["본"])
|
||||
key = str(doc.get("키") or "")
|
||||
else:
|
||||
doc = _file(key)["문서"]
|
||||
mine = _basis(doc, body)
|
||||
before = current(doc)
|
||||
job = body.get("할일") or None
|
||||
|
||||
@@ -116,6 +116,11 @@ export function mountSangeunDetail(
|
||||
scope,
|
||||
onSaved: (v) => void (refreshing = formulaSaved(v)),
|
||||
page: opt.source ? () => report() : undefined, // 804-8 페이지 [저장] · [되돌림]
|
||||
// 836-5 저장 전 가져온 식(불러오기 미리 보기) = 그 문서째 수식 보기
|
||||
memory: () =>
|
||||
doc.가져온 && !/^SK-(?!0{6})\d{6}$/.test(doc.키 ?? "")
|
||||
? (doc as Record<string, unknown>)
|
||||
: null,
|
||||
});
|
||||
/** 수식 탭 저장 = 파일 판 · `수식` 칸이 바뀜 — 페이지 판 · 이 화면 문서 `수식` 을 새로(상세 탭 셈이 씀) */
|
||||
async function formulaSaved(version: string): Promise<void> {
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface SkEquationOptions {
|
||||
onSaved: (version: string) => void;
|
||||
/** 페이지가 저장 · 되돌림을 맡음(804-8) — 수식 고침 있음이 바뀔 때 부름 */
|
||||
page?: () => void;
|
||||
/** 저장 전 불러온 단가산출 문서(836-5 · 미리 보기 835-1) — 있으면 그 문서째 수식 보기 · 없으면 null */
|
||||
memory?: () => Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
/** SK 수식 길 — 꼬리 물음(`/table?key=…`)에 주인 물음을 이어 붙임 */
|
||||
@@ -56,6 +58,7 @@ export function createSangeunTabs(
|
||||
scope: opt.scope, // 804-4 다른 단가산출 · 일위대가 복사
|
||||
page: opt.page,
|
||||
tools,
|
||||
memory: opt.memory,
|
||||
});
|
||||
const show = (doc: boolean): void => {
|
||||
stack.root.hidden = tools.hidden = !doc;
|
||||
@@ -68,7 +71,7 @@ export function createSangeunTabs(
|
||||
host: el("div", { className: "m02-sk__views", children: [stack.root] }),
|
||||
draw: (k: string) => {
|
||||
// 문서 없는 줄(일위대가 로직 줄 모달 등 · 페이지 아님) = 단가산출 상세 몸만(808-1 · 수식은 문서가 있어야)
|
||||
const doc = !!k || !!opt.page;
|
||||
const doc = !!k || !!opt.page || !!opt.memory?.();
|
||||
show(doc);
|
||||
if (doc) stack.draw(k);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
# 조사 860-2 — 건설기계 조종원 노임 시간 환산(1/8 · 16/12 · 25/20)
|
||||
|
||||
읽기 전용 조사. 코드 · 자료 안 고침. 【사실】 = 파일에서 직접 확인 · 【짐작】 = 추론.
|
||||
경로 줄임 — `원문/` = `resources/knowledge/original/` · `MD/` = `resources/master_data/`.
|
||||
|
||||
## 0. 결론 여섯 줄
|
||||
|
||||
1. 품셈 본문 어디에도 1/8 · 16/12 · 25/20 수치 없음 — 8-1-3 6 은 「월정액을 지급함을 원칙」 · 「예정가격 작성기준에 의거 계상」 만 적고 시간 환산식은 없음 【사실】.
|
||||
2. 세 계수의 유일한 원문 근거 = 대한건설협회 질의회신 건협기술-2230(2001-05-29). 1/8 = 1일 8시간 · 25/20 = 휴지계수(월 25일 가능 · 실가동 20일) · 16/12 = 상여계수(상여금 300 % + 퇴직급여충당금 100 %) 【사실】. 회신 스스로 「일률적으로 정하고 있지는 않으나」 라고 밝힘 — 품셈 규정 아닌 업계 관행 풀이.
|
||||
3. 우리 M01 기계경비 식 11 개는 1/8 만 씀(713-4 · 내역 15 · 11-01 사용자 확정으로 16/12 · 25/20 뺌). 실무 엑셀은 읽은 11 건 전부 ×0.2083(= 5/24). 1/8 만 쓰는 실무 자료 0 건 【사실】.
|
||||
4. 같은 프로그램 안에서도 갈림 — B09 원가 단가(환율및기초자료 운전사 시간 노임)는 지금도 1/8 × 16/12 × 25/20 을 곱함 【사실】.
|
||||
5. 금액 영향 — 조종원 줄 −40 %(0.125 ÷ 0.2083 = 0.6) · 굴착기 1.0㎥ 시간당 합계 −17.8 %(우리 108,958 ↔ 0.2083 적용 132,566) · 경유 쓰는 조종원 기계 228 대 중앙값 −17 % 안팎. 「기계 노임이 낮게 나옴」 은 이 차이로 설명됨 【사실】.
|
||||
6. 판정은 사용자 몫 — 아래 5절에 후보와 닿는 범위만 적음.
|
||||
|
||||
## 1. 품셈 원문 근거
|
||||
|
||||
### 1-1 찾은 곳
|
||||
|
||||
| 계수 | 원문 위치 | 원문 글 | 구분 |
|
||||
|---|---|---|---|
|
||||
| 1/8 | `원문/원가계산/건설공사_표준품셈/본문/01_공통부문/제01장_적용기준/1-01_일반사항.md` 1-1-3 2 | 「본 표준품셈에서 제시된 품은 일일 작업시간 8시간을 기준한 것이다.」 | 인력 「품」 의 하루 기준 — 기계 조종원 환산식은 아님 【사실】 |
|
||||
| 1/8 | `원문/원가계산/산림_표준품셈/본문/제01장_적용기준/1-01_일반사항.md` 2 | 「본 품셈에서 제시된 품은 일일 작업시간 8시간을 기준으로 한 것이다.」 | 같음 |
|
||||
| 1/8 | `원문/원가계산/노임단가_건설업_대한건설협회/2026-09-01/2. 임금적용요령.md` 4 가(재경원 회계 45101-45, 1995-01-13) | 「공표된 시중노임단가는 1일 8시간을 기준으로 한 것이며…」 | 노임 1 일 = 8 시간 【사실】 |
|
||||
| 월정액 | `…/제08장_건설기계/8-01_적용기준.md` 8-1-3 6 | 「운전사(건설기계운전사, 화물차운전사, 일반기계운전사)의 노임은 상시 고용일 경우에 월정액을 지급함을 원칙으로 하며 예정가격 작성기준(기획재정부 회계예규)에 의거 계상한다.」 | 환산식 없음 — 우리 식 출처 칸이 이 조(8-1-3 6)를 가리킴 |
|
||||
| 1/8 · 25/20 · 16/12 | `원문/원가계산/질의회신/대한건설협회/2001-05-29_노무비산출_관련_회신.md` 회신문 | 「"1/8"은 시간당 노무비를 산출키 위해 1일을 8시간으로 기준한 것이며, 휴지계수인 "25/20"는 휴지일(일요일, 법정공휴일 등)을 감안한 건설기계 작업가능일수를 월 25일로 보고 작업가능일수중 기상조건·공정상 대기·정비 등을 감안한 실가동일수를 20일 정도로 간주하여 구한 계수로서 현장여건에 맞게 조정·적용할 수도 있을 것이며, 상여계수인 "16/12"은 상여금(300%) 및 퇴직급여충당금(100%) 등을 감안한 개략적인 계수이며, "12/10"는 그래브준설선의 취업시간과 운전시간(표준품셈 제14장 항만 참조)의 비율인 것입니다.」 | 세 계수 정의의 유일 원문 |
|
||||
| 같은 회신 앞 문장 | 같은 파일 | 「표준품셈 등에서는 상시고용일 경우의 건설기계 운전사의 노무비를 산정하는 방식을 일률적으로 정하고 있지는 않으나 다만, 표준품셈(10-1 기계화시공 적용기준)에서는 상시고용일 경우의 운전사 노무비는 월정액을 지급함을 원칙으로 한다고 규정하고 있어 이를 토대로 상기와 같은 산정식이 나오게 된 것입니다.」 | 2001 년 당시 절 번호 10-1 → 지금 8-1-3 6 |
|
||||
| 상여 400 % | `원문/행정규칙/(계약예규) 예정가격작성기준/현행_20260825.md` 제10조 ① (제18조 · 제38조 ② 2 가 준용) | 「다만, 상여금은 기본급의 년 400%, 제수당, 퇴직급여충당금은 「근로기준법」상 인정되는 범위를 초과하여 계상할 수 없다.」 | 상한 400 % 규정 — 16/12(= 상여금 + 퇴직급여 400 %)와 액수 같음. 16/12 를 직접 정한 조문은 아님 【사실】 |
|
||||
| 임금 구성 | 같은 조 ① 1~4호 · 임금적용요령 4 나 | 기본급 · 제수당 · 상여금 · 퇴직급여충당금 · 「공표된 시중노임단가는 … 기본급여액」 | 시중노임 = 기본급 — 상여 · 퇴직급여는 따로 더하라는 취지 【사실】 |
|
||||
| 기계별 취업/운전시간 | `원문/원가계산/건설공사_표준품셈/본문/02_토목부문/제07장_항만공사/7-01_설계기준.md` 7-1-4 | 「펌프준설선 24hr 15hr · 그래브준설선 12hr 10hr · 양묘선 모선과 동일 · 예선 모선과 동일 · 실운전시간」 | 회신의 12/10 = 그래브 준설선. EST 의 24/15 = 펌프 준설선과 맞음 【사실 · 맞춤은 짐작】 |
|
||||
| 연간표준가동시간 | `MD/기계/EQ000022.json`(굴착기 1.0㎥) 연간표준가동시간 1250 · 출처 8-3-1 | — | 손료용 값 — 인건비 환산과 직접 연결한 원문 없음 |
|
||||
| 인건비 별도 | 8-1-7 2 | 「건설기계의 운전시간이 현장조건 및 공정계획상 연간 표준 가동시간보다 현저하게 저하될 경우에는 기계손료 중 관리비와 운전경비 중 인건비를 별도 산정한다.」 | 월정액 전제 뒷받침 · 산정식 없음 |
|
||||
|
||||
### 1-2 적용 직종 구분
|
||||
|
||||
| 직종 | 원문(8-1-3 5) 해당 기계 | 계수 적용 |
|
||||
|---|---|---|
|
||||
| 건설기계운전사 | 불도저 · 굴착기 · 로더 · 지게차 · 스크레이퍼 · 덤프트럭(12ton 이상) · 기중기 · 모터그레이더 · 롤러 · 노상안정기 등 | 품셈 본문은 직종별 계수 구분 없음 |
|
||||
| 화물차운전사 | 12ton 미만 덤프 · 화물트럭 · 살수차 · 트랙터 · 트럭탑재형크레인 등 | 같음 |
|
||||
| 일반기계운전사 | 소형 공기압축기 · 양수기 · 발전기 · 래머 · 콤팩터 등 | 같음 |
|
||||
| 조수 | 8-4 표에 「조종원(인/일)」 열만 있고 조수 열 없음 · 실무 엑셀 6 직종 줄에도 조수 줄 없음 | 해당 없음 |
|
||||
|
||||
- 실무 엑셀은 세 직종(건설기계 · 화물차 · 일반기계)에 같은 0.2083 을 곱함 — 거창 xlsx · 봉화 · 영월 · 기번3 · 계류 · 소광 `환율및기초자료` 6~8 행 【사실】.
|
||||
- 준설선 선장 · 기관장 · 운전사 등은 8-4-9 에 인원수 표로 있음 — 우리 기계표에서 조종원직종 1054(준설선운전사) 30 대 【사실】.
|
||||
|
||||
### 1-3 판(연도)에 따른 변화
|
||||
|
||||
- 지식DB 에는 2026 판 한 벌뿐(`2026년_건설공사_표준품셈.pdf` · `…개정사항.pdf` 는 안 열어봄). 판별 비교 불가 【사실】.
|
||||
- 간접 흔적 — 8-1-3 `('10, '17년 보완)` · 8-1-6 `('06년 보완)` · 8-4 `('08~'17년 보완)` 표기 · 2001 회신의 절 번호(10-1) ↔ 지금(8-1-3) 로 절 이동만 확인 【사실】.
|
||||
- 실무 자료의 계수는 2016(사방 표준품셈 xls · 노임 135,644) ~ 2026(노임 283,297)까지 같음 — 계수가 판에 따라 바뀐 흔적 없음 【사실】.
|
||||
|
||||
### 1-4 못 찾음 · 찾아본 범위
|
||||
|
||||
| 항목 | 결과 | 찾아본 곳 · 검색어 |
|
||||
|---|---|---|
|
||||
| 품셈 본문의 1/8 · 16/12 · 25/20 시간 환산식 | 못 찾음 | `원문/원가계산/` 전체 `16/12`·`25/20`(질의회신 · 직종대응근거 · 경쟁사 분석만 걸림) · 8 장 전 절(8-1 · 8-4 · 8-5 포함) · 산림 표준품셈 전체(`조종원|운전사|운전원|운전경비|기계경비|노임.{0,8}환산` — 운전 관련 7 건 모두 무관) · `월정액|취업시간|휴지계수|일일 작업시간 8시간` |
|
||||
| 24/15 · 14/12 · 24/5 근거(EST 기계별 계수) | 24/15 만 7-1-4 로 짐작 · 14/12 · 24/5 못 찾음 | 위와 같음 + `technical_info` 전체 `16/12|25/20|1/8` (기계경비_산정 · 원가_기초데이터_원천이 「근거 미확인」 이라 적음) |
|
||||
| 조수 · 직종별 다른 계수 | 못 찾음 | 8-1-3 5 · 8-4 표 머리 |
|
||||
| `technical_info` 기존 정리 | `원문/원가계산/노임단가_직종대응_근거.md` 「조종원 노임 시간 환산 계수」 절이 같은 결론: 「표준품셈 8-1 본문에는 조종원 시간환산 계수 산식 자체가 나오지 않음 … 계수의 근거는 품셈 본문이 아니라 위 질의회신·예정가격작성기준」 | — |
|
||||
|
||||
## 2. 우리 쪽 사실
|
||||
|
||||
### 2-1 식 · 계수표
|
||||
|
||||
| 자리 | 사실 |
|
||||
|---|---|
|
||||
| 계수표 `CC000336`(`MD/계수_건설품셈_08장_건설기계.json` 16794 행 · 출처 「질의회신 건협기술-2230」) | 세 줄 보관 — 시간환산 1/8(뜻 「1일 8시간」) · 상여계수 16/12(「상여금 300% + 퇴직급여충당금 100%」) · 휴지계수 25/20(「작업가능 월 25일 · 실가동 20일」). 주에 「기계별 취업시간·운전시간 비율(보기: 그래브준설선 12/10)은 따로 봄 — 이 표에 넣지 않음」 |
|
||||
| 기계경비 식(`MD/수식문서/…`) | 11 개 — 건설 08장 9(GC000267 · 268 · 269 · 270 · 271 · 275 · 276 · GC001197 · GC001201) + 산림 GF000337(체인톱) · GF000145(4륜구동 1톤 덤프). 전부 변수 `Q_분자^표` · `Q_분모^표` 가 CC000336 의 `구분='시간환산'` 줄만 읽음 → `Q_시간환산 = 분자 ÷ 분모` → 조종원 금액 = `Q_기계조종원 × Q_시간환산 × 노임` |
|
||||
| 상여계수 · 휴지계수 쓰는 식 | 0 개 — `test_713_4_source_match.py` 의 `test_조종원_시간환산_본문대로` 가 「상여계수 · 휴지계수 글이 식에 없음」 을 지킴 |
|
||||
| 내 기계 · 기계 양식 | `M01_MasterData_Machine.py` 74~79 행이 같은 `시간환산` 줄을 읽어 양식 `hour` 로 줌 · 못 찾으면 1/8 · `master_machine_coef.py:60` · `common_util_machine_coef.ts:72` 의 `hour` 기본값도 1/8 (짝 · 부르는 쪽이 기본값 씀) |
|
||||
| B09 원가 단가 | `B09_Estimation_Prices.py` 28~30 행 `DRIVER_PARTS = ("시간환산","상여계수","휴지계수")` · `driver_factor()` 가 세 줄 분자 · 분모를 모두 곱해 `num/den = 400/1920 (= 5/24)` → `B09_Estimation_Calc_Base.py/.ts` `driver_hour = ROUNDDOWN(일당 × 분자 ÷ 분모, 0)` · 시험 `test_144_b09_base_calc.py` 가 「1/8 × 16/12 × 25/20」 을 지킴 — M01 식과 갈림 【사실】 |
|
||||
| 경쟁사 환산 안 가져오기 | `M02_San_MyMachine.py` 머리 · `test_821_4_import_my_machine.py:127` · 지식 `기계경비_산정.md` 102 행: 불러온 기계 구성에서 「경쟁사 조종원 노임 환산 ×16/12 ×25/20 은 안 가져옴」 |
|
||||
|
||||
### 2-2 걸리는 수
|
||||
|
||||
| 항목 | 수 | 비고 |
|
||||
|---|---|---|
|
||||
| 기계표(`MD/기계/EQ*.json`) | 655 대 | |
|
||||
| 조종원 값 있는 기계 | 301 대 | 직종 1048(건설기계운전사) 184 · 1049(화물차운전사) 24 · 1050(일반기계운전사) 21 · 1054(준설선운전사) 30 · 직종 빈칸 42 · 인원 1 인 282 · 2 인 18 · 7 인 1(현장가열 표층재생기) |
|
||||
| 위 식 목록에 든 조종원 기계 | 297 대 | 안 든 4 대 = 아스팔트 믹싱플랜트(2 인) · 아스팔트 스프레어 2 · 현장가열 표층재생기(7 인) — 「손료만」 식(GC000273 · 274)이 받음 → 조종원 노무비 자체가 시간당에 안 들어감(`_방향_불러오기.md` 252 행 같은 기록) |
|
||||
| 식별 | 식 11 개 · 식마다 목록 수: GC000267 9 · 268 215 · 269 129 · 270 6 · 271 9 · 275 39 · 276 19 · GC001197 2 | (GC001201 · GF000145 · GF000337 은 목록이 다른 꼴 변수) |
|
||||
| 이 11 식을 부르는 정본 로직 | 472 개(총 로직 1,520 개 중 · 건설 03장 토공사 48 · 02장 토목 64 · 01장 도로포장 47 · 05장 기초공사 31 · 02장 가설 24 …) | 내역 14-5 때는 471 |
|
||||
| 사본 · 문서 | 내역 14-5(11-01) 수치: M02 층 시스템 10 · 회사 16 · 개인 89 파일 · 프로젝트 사본(templates 있는 프로젝트) 13 · 로직 직접 담은 곳 49 | 이번엔 storage 전체 재집계 못 함(검색이 시간 초과) — 옛 수치로만 적음 |
|
||||
|
||||
### 2-3 이력(713-4)
|
||||
|
||||
| 시점 | 내용 | 근거 |
|
||||
|---|---|---|
|
||||
| 09-20 | 조종원 환산 1/8 · 16/12 · 25/20 도입 — 「다산소프트 대조 −40 % → 0 %」 | `docs/raw/요구_내역서/_분석_내역14-5_로직_원문대조.md` 67 행(`eb7cc600`) |
|
||||
| 11-01 | 내역 15 ① 사용자 「기계경비 로직 8 의 조종원 시간 환산 … → 빼고 본문대로」 | `docs/raw/요구_내역서/사용자요구_내역서.md` 112 행 |
|
||||
| 11-01 | 713-4 로 반영 — 판정 근거 「본문 밖 근거 · 품셈 8장 본문에 16/12 · 25/20 없음」 · 분석표 「되돌리면 조종원 값이 크게 바뀜」 | `docs/raw/plans/2026-11-01_plan_709-719.md` 51 행 · `_분석_내역14-5…` 131 · 144 행 |
|
||||
|
||||
## 3. 경쟁사 · 실무 자료 사실
|
||||
|
||||
### 3-1 읽은 범위
|
||||
|
||||
`원문/실무문서/` 의 xlsx · xls 45 개 중 기계경비 · 중기사용료 시트 있는 파일 11 개(openpyxl · xlrd 읽기 전용).
|
||||
|
||||
| 꼴 | 건수 | 1/8 × 16/12 × 25/20 | 1/8 만 |
|
||||
|---|---|---|---|
|
||||
| EST 꼴 「기계경비」 시트(산출근거 줄에 글로 적음) | 5 | 5 | 0 |
|
||||
| STmate 꼴 「중기사용료」 시트(시간 노임은 `환율및기초자료` 에 ROUNDDOWN(일당 × 0.20833333334)) | 6 | 6 | 0 |
|
||||
| 못 읽음 | 3 | — | — |
|
||||
|
||||
못 읽음 = 장수 · 진안 `…내역+수량.xlsx` 2 개(파일 머리 `DRMONE` — DRM 걸림) · 봉화 `오솔길…v.13.xlsx`(zip 아님) · 계류 `원가계산…xls` 등(OLE 깨짐). `.estx` · `.ebkx` 원본은 안 열음 — 지식 `ESTX_값사전`/`ESTPlus2_기계경비_정적분석` 에 「운전사 환산계수(NCGasu) 1 = 1/8·16/12·25/20 = 0.208333」 로 적힌 것만 확인.
|
||||
|
||||
### 3-2 견본 다섯 + 하나
|
||||
|
||||
| # | 꼴 | 파일 | 시트 · 행 | 적힌 글 | 노임 | 시간당 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | EST | 거창 `00.25년_산불_거창…내역+수량.xlsx` | 기계경비 8 행(굴삭기 0.7㎥) | 「건설기계운전사 1 인 × 283297 × 1/8*16/12*25/20」 | 283,297 | 59,020.2 |
|
||||
| 2 | EST | `2026년_북부지방산림청_임도사업_설계내역서(공통단가).xls` | 기계경비 8 행 | 「화물차운전사 1 인 × 238936 × 1/8*16/12*25/20」 | 238,936 | 49,778.3 |
|
||||
| 3 | EST | 같은 파일 | 기계경비 20 행 | 「건설기계운전사 1 인 × 283297 × 1/8*16/12*25/20」 | 283,297 | 59,020.2 |
|
||||
| 4 | EST | `2016_상반_산림청_사방사업표준품셈(안).xls` | 기계경비 19 행 | 「건설기계운전사 1 인 × 135644 × 1/8*16/12*25/20」 | 135,644 | 28,259.1 |
|
||||
| 5 | STmate | 봉화 `2024년 산불진화임도 실시설계(기번41_현동)_ L=3.46km.xlsx` | 중기사용료 8 행 `건설기계운전사 1 인 = 환율및기초자료!G6` | `환율및기초자료` D6 267,360 · E6 「* 1/8*16/12*25/20 =」 · G6 `=ROUNDDOWN(D6*0.20833333334,0)` | 267,360 | **55,700** |
|
||||
| 6 | EST 참고 | 거창 `기계경비적용기준` 21~25 행 「노임계수」 표 | — | 1/8*16/12*25/20 · ×24/15 · ×12/10 · ×14/12 · ×24/5 다섯 | — | 0.2083 · 0.3333 · 0.25 · 0.2431 · 1.0 |
|
||||
|
||||
- 거창 `기계경비` 시트 환산 글 19 줄 모두 기본 계수(0.2083) · 북부 사방 21 · 북부 임도 28 · 수목진료 9 · 2016 사방 6 줄(전부 같은 글) 【사실】.
|
||||
- STmate 꼴 6 파일 모두 `환율및기초자료` 6~8 행이 세 직종에 같은 0.20833333334 곱함(봉화 · 영월 · 기번3 두 파일 노임 267,360 → 55,700 · 계류 · 소광 노임 273,971 → 57,077) 【사실】.
|
||||
|
||||
### 3-4 55,700 역산
|
||||
|
||||
- 55,700 ÷ 267,360 = 0.20833 = 5/24 = 1/8 × 16/12 × 25/20. 일 노임의 약 0.208 배 — 1/8(0.125)의 1.667 배.
|
||||
- 같은 꼴 6 파일의 비율: 영월 · 기번3 · 봉화 0.20833 · 계류 · 소광 0.20833 【사실】.
|
||||
- 같은 노임에 1/8 만 쓰면 267,360 ÷ 8 = 33,420 — 55,700 의 60 %.
|
||||
|
||||
## 4. 금액 영향
|
||||
|
||||
굴착기(무한궤도) 1.0㎥(`EQ000022` · 가격 142,069 천원 · 손료계수 상각 900 + 정비 700 + 관리 485 = 2,085 · 경유 19.5 ℓ/hr · 잡재료 22 % · 조종원 1 인 · 노임 283,297 원/일 · 경유 1,846.39 원/ℓ — `MD/재료_유가전력.json` MO000002) 계산 — 손료 보정 없음 · 비목 버림.
|
||||
|
||||
| 항목 | 우리(1/8) | 0.2083 적용 | 차 |
|
||||
|---|---|---|---|
|
||||
| 조종원(노무비) | 35,412 (283,297 ÷ 8) | 59,020 (283,297 × 5/24 · 경쟁사 거창 59,020.2 와 같음) | +23,608 (노무비 +66.7 %) |
|
||||
| 주연료 + 잡재료(재료비) | 43,925 | 43,925 | 0 |
|
||||
| 기계손료(경비) | 29,621 | 29,621 | 0 |
|
||||
| 시간당 합계 | 108,958 | 132,566 | +23,608 = 우리가 17.8 % 낮음(0.2083 쪽 기준) · 반대로 보면 +21.7 % |
|
||||
|
||||
- 노임이 일 노임에 선형이라 차는 항상 노임 ÷ 12 = 일 노임 × (5/24 − 1/8)(= 2/24) 원 · 조종원 줄은 어떤 노임에서도 정확히 −40 %.
|
||||
- 전수 환산(경유 쓰는 조종원 기계 228 대 · 직종 노임 1048 283,297 · 1049 238,936 · 1050 173,995 · 1054 209,441 · 손료 보정 · 끝수 단순화한 근사):
|
||||
|
||||
| 지표 | 최소 | 중앙값 | 최대 |
|
||||
|---|---|---|---|
|
||||
| 1/8 기준 조종원 몫(시간당 합계 중) | 0.5 % | 30.5 % | 84.4 % |
|
||||
| 0.2083 으로 바꾸면 합계 증가 | +0.3 % | +20.4 % | +56.3 % |
|
||||
|
||||
대표: 불도저(무한궤도) 19ton 124,841 → 148,450(+18.9 %) · 모터그레이더 3.6m 123,840 → 147,448(+19.1 %) · 덤프트럭 15ton 96,585 → 120,193(+24.4 %) — 조종원 몫이 클수록 차가 큼.
|
||||
- 「기계 노무비가 낮게 나옴」 설명 — 불러오기 대조에서 비교하는 경쟁사 값은 EST · STmate 모두 ×0.2083 · 우리 값 ×0.125 이므로 조종원 줄이 −40 % 로 나오는 것이 이 계수 차이로 그대로 설명됨 【사실 · 계산】. 이것이 대조 어긋남의 유일한 원인인지는 이 조사에서 개별 파일 단위로 재지 않음.
|
||||
- 기록 확인(Grep):
|
||||
- `docs/raw/요구_1104/_보고_812-12_불러오기정합성.md` — 조종원 환산 · 16/12 언급 없음.
|
||||
- `_방향_불러오기.md` 224 행 「참고: 현행 품셈 8 장에 조종원 노임 ×16/12 ×25/20 없음(우리 = ÷8) · EST 구성은 옛 환산 포함 → 자동 생성은 우리 틀에 값만 담는 쪽 권장.」 — 값 차이 영향은 안 적음.
|
||||
- `_조사_858_기호표기.md` 227 행 「경쟁사 `1/8*16/12*25/20`(= 0.2083) ↔ 우리 `1/8` — 값 차이는 이 조사 범위 밖 · 사실만 적음.」
|
||||
- `_분석_813_BDQTY복호_엔진개선.md` 125 행 「7 건 1 원 … 제품은 『×16/12 ×25/20 안 가져옴』 처리 — 대조 근거」 — 환산 제외한 채로 비교해 맞췄다는 취지(어느 쪽 값끼리 맞췄는지는 안 읽음).
|
||||
- `_분석_내역14-6_EST_장비줄.md` 39 · 174 행 — EST 장비줄 1/8*16/12*25/20 = 49,778.3 · 59,020.2 기록.
|
||||
|
||||
## 5. 고를 거리(판정은 사용자 몫 · 사실만)
|
||||
|
||||
### 5-1 품셈대로면 어느 계수
|
||||
|
||||
| 보기 | 계수 | 근거 | 한계 |
|
||||
|---|---|---|---|
|
||||
| A. 품셈 본문만 | 환산식 없음 — 「월정액 · 예정가격작성기준 계상」 | 8-1-3 6 | 시간당 숫자를 줄 수 없음 → 1/8(= 8 시간 하루 노임)은 본문 밖 풀이. 1-1-3 2 의 「8시간」 은 인력 품 기준 |
|
||||
| B. 질의회신 | 1/8 × 16/12 × 25/20 = 5/24 = 0.20833 | 건협기술-2230 + 예정가격작성기준 제10조 ① 상여 400 % | 회신이 「현장여건에 맞게 조정·적용」 허용 · 「개략적인 계수」 라 함 · 업계(EST · STmate · 실무 엑셀 11/11)가 관행으로 씀 |
|
||||
| C. 준설선 · 기타 | 그래브 12/10 · 펌프 24/15 별도 | 회신 12/10 · 7-1-4 | 우리 식엔 없음 — 조종원직종 1054 30 대가 시간환산 한 줄로 셈 · EST 14/12 · 24/5 근거 못 찾음 |
|
||||
|
||||
### 5-2 넣는다면 자리 후보와 바뀌는 범위
|
||||
|
||||
| 후보 | 방법 | 바뀌는 식 · 문서 | 따라붙는 일 · 주의 |
|
||||
|---|---|---|---|
|
||||
| ㉠ 계수표 값만 | `CC000336` `시간환산` 줄 분자 · 분모를 5/24 로 | 식 11 · 내 기계 양식(`M01_MasterData_Machine.py` 74~79 가 이 줄을 읽음) · 부르는 로직 472 · 옛 수치 M02 문서 약 115 파일 + 프로젝트 사본 13 | 뜻 칸 「1일 8시간」 과 어긋남 · **B09 `driver_factor` 는 세 줄을 곱하므로 시간환산을 5/24 로 바꾸면 B09 가 5/24 × 16/12 × 25/20 = 0.347 로 이중 적용** — B09 도 같이 고쳐야 함 |
|
||||
| ㉡ 식에 세 줄 곱함 | 식의 `Q_시간환산 = 분자 ÷ 분모` 를 시간환산 × 상여 × 휴지로 | 식 11 · 부르는 로직 472 · 같은 문서 수 · B09 와 값 같아짐 | 713-4 이전 꼴로 되돌림 · `test_713_4_source_match.py` 의 반대 지킴 고쳐야 · 변수 4 칸(분자 · 분모 두 줄씩)씩 늘어남 |
|
||||
| ㉢ 옵션화 | 사용자 · 마스터가 계수 세 줄 켜고 끄기(조종원 환산 방식 고르기) | 식 11 + 화면 | 기본값 정해야 · 사본 박힌 옛 문서와 갈림(M02 문서는 사본 기준 셈) |
|
||||
| ㉣ 현행 유지 | 1/8 | 0 | 경쟁사 불러오기 대조에서 조종원 줄 −40 % 계속 |
|
||||
|
||||
- 사본 · 갱신: M02 단가산출 · 프로젝트는 만들 때 로직 사본을 박아 둠(`_방향_불러오기.md` 241 행) · 836-2 의 「M01 이 바뀌어 다시 복사함」 알림 · [단가 갱신] · [재생성] 으로 따라오는지 옛 사본은 사용자 눌러야 반영 【사실 · 코드는 안 읽음】.
|
||||
- 바뀌는 숫자 크기: 위 4절 — 조종원 줄 +66.7 %(원 기준 −40 % 의 반대 방향) · 시간당 합계 +20 % 안팎 · 조종원 비중 큰 기계(조종원 몫 50 % 이상)는 +30 % 이상.
|
||||
- 안 바뀌는 것: 조종원 없는 기계 354 대 · 손료 · 연료 · 잡재료.
|
||||
@@ -1384,3 +1384,7 @@ GC000089의 단가품셈 기초의 항목을 내가 하나 삭제했음. 되돌
|
||||
1. 안2로 진행하고 보고 판단할께.
|
||||
2. 우리기계의 1/8이 결국은 8시간 기준으로 했다는건데 품셈에서 해당내용을 좀 찾아봐. 대체로 기계부분의 노임 계산이 좀 낮게 나오고 있음. 조사
|
||||
3. 일위대가 불러오기에서 경쟁사 단가와 우리단가화를 비교해보면 재료의 구매 단위 차이로 인해 값이 완전히 달라지는 경우가 있음. 조사
|
||||
|
||||
## 요구 861 (11-15 · 사용자 · 조사 결과는 코드 작업과 분석 작업이 끝나면 한 번에 출력)
|
||||
|
||||
조사는 코드작업과 분석작업이 끝나면 한번에 출력
|
||||
|
||||
@@ -163,7 +163,7 @@ def test_상세_부품_길_부품상자():
|
||||
view = _text(M02 / "M02_MasterTemplete_Sangeun_View.ts")
|
||||
detail = _text(M02 / "M02_MasterTemplete_Sangeun_Detail.ts")
|
||||
cover = _text(M02 / "M02_MasterTemplete_Sangeun_Cover.ts")
|
||||
assert "mountSangeunDetail(host, d," in view and "createEmpty" not in view
|
||||
assert "mountSangeunDetail(host, doc," in view and "createEmpty" not in view
|
||||
assert 'read: "/m02/logic-lib"' in detail and "setLogicApiPaths(LIB)" in detail
|
||||
assert "buildLabDetail(editor" in detail and "cover: { ho, open: lineOpener(hoCtx) }" in detail
|
||||
assert "buildCalc" not in detail # 799-2 시험 계산 보기 걷음
|
||||
@@ -242,6 +242,6 @@ def test_725_바탕_없으면_개정_안_뜸_새_SK_나무_다시_읽음_더한
|
||||
assert "!!(doc.바탕 && doc.바탕판 && ans?.바탕판 && doc.바탕판 !== ans.바탕판)" in detail
|
||||
assert detail.count("const revised = revisedBase();") == 2
|
||||
modal = _text(M02 / "M02_MasterTemplete_Labor_RowModal.ts")
|
||||
assert modal.index("saveAsSk(row") < modal.index("dispatchEvent(new Event(HOPYO_BUILT))")
|
||||
assert "saveAsSk" not in modal # 808-1 옛 [단가산출로도 저장] 걷음 — 일위대가 줄은 저장 때 펼침
|
||||
lab = _text(ROOT / "M01_MasterData" / "M01_MasterData_UI_LogicLab_Modal.ts")
|
||||
assert "lookup(element, element)" in lab and "Pill_NoValue" in lab
|
||||
|
||||
@@ -41,7 +41,7 @@ def test_752_1_source_panel_only_on_sangeun_screen():
|
||||
assert "source: true" in _ts("M02_MasterTemplete/M02_MasterTemplete_Sangeun_View.ts")
|
||||
assert "source" not in _ts("M02_MasterTemplete/M02_MasterTemplete_Labor_RowSk.ts")
|
||||
detail = _ts("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts")
|
||||
assert "opt.source ? createSangeunSide(opt.match)" in detail and "side?.set(" in detail
|
||||
assert "opt.source ? createSangeunSide()" in detail and "side?.set(" in detail
|
||||
|
||||
|
||||
def test_753_2_option_off_flag_reaches_sheet_and_views():
|
||||
|
||||
@@ -52,7 +52,7 @@ def test_길_앞머리_M02_공용화면():
|
||||
assert "/m02/layers/${scope.layer}/sangeun/${encodeURIComponent(key)}/doc${tail}" in tabs
|
||||
assert "admin: opt.edit" in tabs # 층 쓰기 권한 = 고침
|
||||
scr = _src("A00_Common/line_stack_screen.ts") # 804-6 수식 탭 = 줄 쌓기
|
||||
assert "cp ? cp.route(key, path) : opt.base + path" in scr
|
||||
assert 'cp ? cp.route(saved ? "-" : key, path) : opt.base + path' in scr # 836-5 저장 전 문서 = 길 키 「-」
|
||||
assert "{ 문서: doc, 끊음: cut, 판: version }" in scr # 저장 몸
|
||||
detail = _src("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts")
|
||||
assert (
|
||||
|
||||
@@ -209,7 +209,7 @@ def test_시험_페이지_걷음_화면_차림() -> None:
|
||||
def test_M02_수식탭_M01과_같은_단추_저장_한_번() -> None:
|
||||
"""804-8 — M02 단가산출 수식 탭 = M01 과 같은 공용 화면 · 페이지 [되돌림] · [저장] 한 번 · 머리 줄 한 줄."""
|
||||
screen = _src("A00_Common/line_stack_screen.ts")
|
||||
assert "...(cp && edit() && key ? copyTools(true, (job) => void runJob(job)) : [])," in screen
|
||||
assert "...(cp && edit() && key && !mark?.자동 ? copyTools(true, (job) => void runJob(job)) : [])," in screen # 836-2 자동 층 = 단추 없음 · 프로젝트 층 = 있음
|
||||
assert "notesOf" not in screen and "lstk__notes" not in screen # 못 고치는 칸 안내 줄 걷음
|
||||
assert "Notes:" not in _src("A00_Common/line_stack_text.ts")
|
||||
tabs = _src("M02_MasterTemplete/M02_MasterTemplete_Sangeun_Tabs.ts")
|
||||
|
||||
@@ -137,4 +137,4 @@ def test_808_1_모달_머리_일원화():
|
||||
assert "children: [tabs.tools]" in detail and "tabs.picker" not in detail
|
||||
assert r"/^SK-(?!0{6})\d{6}$/" in detail
|
||||
tabs = _src("M02_MasterTemplete_Sangeun_Tabs.ts")
|
||||
assert "const doc = !!k || !!opt.page;" in tabs # 836-3 문서 없는 줄 = 상세 몸만
|
||||
assert "const doc = !!k || !!opt.page || !!opt.memory?.();" in tabs # 836-3 · 836-5 문서 없는 줄 = 상세 몸만
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
||||
"""836-5 저장 전 단가산출(불러오기 미리 보기)도 수식 보기 — 임시 storage · M01 그대로.
|
||||
|
||||
① 미리 보기 문서째 수식 길 읽기(`Sk_Path.read_memory`) = 디스크 쓰기 0 · 금액 = 저장한 SK 를 읽은 값 · 미리 셈(`본`)도 쓰기 0
|
||||
② 화면 — 줄 쌓기가 빈 키 + 저장 전 문서를 서버에 문서째 읽힘 · 왼쪽은 보기만 · 탭은 그 문서가 있을 때만 수식
|
||||
실무 엑셀 없으면 ① skip.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import helper_m02_hopyo_golden as golden
|
||||
import pytest
|
||||
|
||||
from M02_MasterTemplete import M02_Doc_Sk_Path as path
|
||||
from M02_MasterTemplete import M02_Hopyo_Excel as hx
|
||||
from M02_MasterTemplete import M02_Hopyo_Store as hs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Sangeun_FromHopyo as fh
|
||||
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def _files(folder: Path) -> dict[str, int]:
|
||||
return {str(p.relative_to(folder)): p.stat().st_mtime_ns for p in folder.rglob("*") if p.is_file()} # fmt: skip
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("tag", "no"), [("봉화", 3), ("북부청임도", 2)]
|
||||
) # STmate 원파일 · 엑셀(EST)
|
||||
def test_memory_doc_read_writes_nothing_same_as_saved(world, tag, no) -> None:
|
||||
src = golden.FILES.get(tag)
|
||||
if src is None or not src.exists():
|
||||
pytest.skip(f"실무 엑셀 없음 {tag}")
|
||||
folder, data = world["personal"], src.read_bytes()
|
||||
with store.at(folder):
|
||||
parsed = hx.read_book(src.name, data)
|
||||
row = hs.save(folder, src.name, parsed, {"이름": "sub1임시"}, None, hs.fingerprint(data))
|
||||
fh.keep(folder, row["키"], src.name, data)
|
||||
with store.at(folder):
|
||||
seen = fh.draft(folder, row["키"], no)
|
||||
before = _files(folder)
|
||||
got = path.read_memory(seen["문서"]) # 저장 전 문서째
|
||||
asked = path.preview("-", {"본": seen["문서"], "문서": got["문서"]}) # 미리 셈
|
||||
assert _files(folder) == before # 읽기 · 미리 셈 = 디스크 쓰기 0
|
||||
assert got["판"] == "" and got["셈"] and asked["셈"] == got["셈"]
|
||||
made = fh.make(folder, row["키"], no, doc=seen["문서"]) # [저장]
|
||||
saved = path.read(made["이름"])
|
||||
assert saved["셈"] == got["셈"] # 금액 = 저장한 SK 와 같음
|
||||
with pytest.raises(store.StoreError):
|
||||
path.read_memory("문서 아님")
|
||||
|
||||
|
||||
def test_screen_memory_doc_stack_view() -> None:
|
||||
stack = (ROOT / "A00_Common" / "line_stack_screen.ts").read_text(encoding="utf-8")
|
||||
assert "memory?: () => Record<string, unknown> | null;" in stack
|
||||
assert 'cp.route(saved ? "-" : key, path)' in stack and "본: saved" in stack
|
||||
assert 'call<Loaded>("/read", {})' in stack and "left.inert = !!mem();" in stack
|
||||
tabs = (ROOT / "M02_MasterTemplete" / "M02_MasterTemplete_Sangeun_Tabs.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert (
|
||||
"const doc = !!k || !!opt.page || !!opt.memory?.();" in tabs
|
||||
and "memory: opt.memory," in tabs
|
||||
)
|
||||
detail = (ROOT / "M02_MasterTemplete" / "M02_MasterTemplete_Sangeun_Detail.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert "doc.가져온 &&" in detail and "memory: () =>" in detail
|
||||
router = (ROOT / "M02_MasterTemplete" / "M02_Doc_Router.py").read_text(encoding="utf-8")
|
||||
assert '"/layers/{layer}/sangeun/-/doc/read"' in router and "write=body.본 is None" in router
|
||||
Reference in New Issue
Block a user