- 「줄 따름」 표 값 = 지금 조건으로 고른 표 줄의 「단위」 칸(rowUnit · 화면 캐시 표 읽기) — 오른쪽 · 왼쪽 값 칸 · 수량 꼬리표 - 식 줄 결과 단위 = TeX 글 안전(「%」 가 주석으로 먹혀 「가산 = 0 %」 가 안 보이던 것) - 단가 분모가 수량 단위와 같은 뜻이면 수량 글로(원/일 × 인 → 원/인 · ㎏ ↔ kg · ℓ ↔ L) · kWh = ㎾h · 기계 칸 전력 = ㎾h/hr(845-1 문서 단위와 같게) - 왼쪽 만약이 수 하나로 접힌 식 줄 = 단위 남김 - 전수(845-1 단위 채움 뒤) 표시 탓 0 · 시험 test_845_2_unit_show 줄 따름 · % · 원/인 더함 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
124 lines
5.9 KiB
TypeScript
124 lines
5.9 KiB
TypeScript
/* =============================================================================
|
||
* common_util_line_stack_unit.ts
|
||
* 단가산출 단위 그리는 규칙 한 벌(PLAN 845-2 · 요구 880) — 왼쪽 편집 · 오른쪽 산출근거 · 기계 제어 구역 · 기계 보기가 같이 씀
|
||
* ㉠ 값 줄 = 값 뒤 단위(`varUnit`) ㉡ 식 줄 = 결과 값 뒤만(식 가운데 숫자 뒤 없음)
|
||
* ㉢ 비목 · 금액 줄 = 「단가 원/단위 × (수량 식) 단위 = 금액 원」(`moneyUnits` — 수량이 식이면 괄호로 식 전체에 단위) ·
|
||
* 소계 · 합계 = 원(머리 [원/㎥] = 결과 하나당)
|
||
* ㉣ 무차원 = 빈 칸 ㉤ 율 = %(「%(인력품 대비)」 등 설명 괄호 뗌) ㉥ 왼쪽 값 칸 = 칸 바로 뒤 한 자리
|
||
* 메타 단위(「구분 따름」 · 「줄 따름」 · 「수(…)」 · 「원문 단위 없음」) = 단위 아님 → 안 그림 · 「10^-7」 = 10⁻⁷ · kWh = ㎾h
|
||
* 「줄 따름」 표 값 = 지금 조건으로 고른 표 줄의 「단위」 칸(`rowUnit`) · 단가 분모 = 같은 뜻이면 수량 단위 글(원/일 × 인 → 원/인)
|
||
* 기계 칸 연결(요소 `EQ:` 칸) 변수 = 변수 단위가 빈 칸이면 기계 표 칸 단위(`MACHINE_UNIT` — 기계 표 빌더 · 내 기계와 같은 글)
|
||
* 그리기만 — 자료 · 식 · 금액 불변 · 화면 없음(시험이 Node 로 돌림)
|
||
* ========================================================================== */
|
||
|
||
import type { DocVar, MoneyLine, Node } from "./common_util_formula_doc";
|
||
import { evalNode, tryParse, type Values } from "./common_util_formula_doc_view";
|
||
|
||
/** 기계 표 칸 단위(`build_기계_건설품셈.py` UNIT · `M02_MyMachine.py` UNITS · 845-1 문서 단위와 같은 글) */
|
||
export const MACHINE_UNIT: Record<string, string> = {
|
||
가격: "천원",
|
||
내용시간: "시간",
|
||
연간표준가동시간: "시간",
|
||
상각비계수: "10^-7/시간",
|
||
정비비계수: "10^-7/시간",
|
||
관리비계수: "10^-7/시간",
|
||
손료계수: "10^-7/시간",
|
||
주연료: "ℓ/hr",
|
||
전력: "㎾h/hr",
|
||
잡재료율: "%",
|
||
잡품: "%",
|
||
조종원: "인/일",
|
||
인력: "인/일",
|
||
노무비: "원/시간",
|
||
재료비: "원/시간",
|
||
경비: "원/시간",
|
||
};
|
||
|
||
/** 금액 · 소계 · 합계 단위 */
|
||
export const AMOUNT_UNIT = "원";
|
||
|
||
/** 자료 단위 글 → 화면 단위(메타 = 빈 글) */
|
||
export function unitText(u: unknown): string {
|
||
const s = String(u ?? "").trim();
|
||
if (!s || /따름|원문 단위 없음/.test(s) || /^수(\(| 또는|$)/.test(s)) return "";
|
||
return s
|
||
.replace(/^%\(.*\)$/, "%")
|
||
.replace(/10\^-7/g, "10⁻⁷")
|
||
.replace(/kWh/g, "㎾h");
|
||
}
|
||
|
||
/** 같은 뜻 다른 표기(단가 단위 분모 ↔ 수량 단위) — 노임 원/일 ↔ 인(인·일) · ㎏ ↔ kg · ℓ ↔ L */
|
||
const SAME: [string, string][] = [["일", "인"], ["㎏", "kg"], ["ℓ", "L"]]; // prettier-ignore
|
||
const same = (a: string, b: string): boolean =>
|
||
a === b || SAME.some(([x, y]) => (a === x && b === y) || (a === y && b === x));
|
||
|
||
/** 기계 칸 이름의 화면 단위 */
|
||
export const machineUnit = (col: string): string => unitText(MACHINE_UNIT[col]);
|
||
|
||
/** 변수 화면 단위 — 변수 단위 › 기계 칸 연결이면 기계 표 칸 단위 */
|
||
export function varUnit(v: DocVar | undefined): string {
|
||
const own = unitText(v?.단위);
|
||
if (own) return own;
|
||
const k = v?.연결;
|
||
return k?.갈래 === "요소" && /^EQ:/.test(String(k.키 ?? "")) && k.칸 ? machineUnit(k.칸) : "";
|
||
}
|
||
|
||
/** 표 한 칸 읽기(화면 캐시 `links.table`) */
|
||
export type TableLook = (p: Record<string, unknown>) => unknown;
|
||
|
||
/** 「줄 따름」(줄마다 다른 단위) 표 값 = 지금 조건으로 고른 표 줄의 「단위」 칸 · 못 읽으면 빈 글 */
|
||
export function rowUnit(v: DocVar | undefined, vals: Values, look?: TableLook): string {
|
||
const k = v?.연결;
|
||
if (!look || !k?.표 || !/따름/.test(String(v?.단위 ?? ""))) return "";
|
||
const 조건: Record<string, unknown> = {};
|
||
for (const [c, t] of Object.entries(k.조건 ?? {})) {
|
||
const n = tryParse(t);
|
||
if (n) 조건[c] = evalNode(n, vals);
|
||
}
|
||
try {
|
||
return unitText(look({ 표: k.표, 칸: "단위", 조건 }));
|
||
} catch {
|
||
return "";
|
||
}
|
||
}
|
||
|
||
/** 값 줄 단위 한 벌 — 변수 단위 › 기계 칸 › 고른 표 줄 단위 */
|
||
export const shownUnitOf = (v: DocVar | undefined, vals: Values, look?: TableLook): string =>
|
||
varUnit(v) || rowUnit(v, vals, look);
|
||
|
||
const HIDE = new Set(["반올림", "올림", "버림"]);
|
||
/** 한 덩이(기호 · 수 · 칸 — 감싸개 안 한 덩이 포함) */
|
||
export function isAtom(n: Node | null | undefined): boolean {
|
||
if (!n) return false;
|
||
if (n[0] === "call" && HIDE.has(n[1]) && n[2].length >= 1 && n[2].length <= 2)
|
||
return isAtom(n[2][0]);
|
||
return n[0] === "var" || n[0] === "num" || n[0] === "field" || n[0] === "str";
|
||
}
|
||
|
||
export interface MoneyUnits {
|
||
/** 단가 뒤(원/시간 · 원/인 …) — 단가가 한 덩이이고 수량이 있을 때만 */
|
||
price: string;
|
||
/** 수량 식 뒤 */
|
||
qty: string;
|
||
/** 수량 식을 괄호로 감쌈(식이고 단위가 있을 때) */
|
||
wrap: boolean;
|
||
}
|
||
|
||
/** 금액 줄 단위 자리(㉢) — `priceVar` = 단가 기호 변수 · `sub` = 묶음 · 단가산출 줄이 부르는 문서 */
|
||
export function moneyUnits(
|
||
line: MoneyLine,
|
||
price: Node | null,
|
||
qty: Node | null,
|
||
priceVar?: DocVar,
|
||
sub?: { 결과단위?: string } | null,
|
||
): MoneyUnits {
|
||
const has = line.수량 !== undefined && line.수량 !== null && line.수량 !== "";
|
||
const q = has ? unitText(line.단위) : "";
|
||
const wrap = !!q && !!qty && !isAtom(qty);
|
||
const own = [sub?.결과단위, priceVar?.단위].map(unitText).find((u) => u.startsWith("원"));
|
||
// 단가 분모가 수량 단위와 같은 뜻이면 수량 단위 글로(「원/일 × 인」 대신 「원/인 × 인」)
|
||
const mine = own && q && same(own.slice(2), q) ? `${AMOUNT_UNIT}/${q}` : own;
|
||
const p = has && isAtom(price) ? (mine ?? (q ? `${AMOUNT_UNIT}/${q}` : "")) : "";
|
||
return { price: p, qty: q, wrap };
|
||
}
|