Files
Aislo/M02_MasterTemplete/logic/M02_Sangeun_Origin.ts
T
eomsangdonandClaude Opus 5.5 d11075c7c2 feat(810-3): 원파일 가져온 식 화면 — 옵션 칸 · 추정 칸 · 소계 빈칸 · 카드 확정률 · 화면 셈 거울
- 올림 칸 accept .estx · .stc · 올림 안내 「원파일 · EST/STmate — 호표 N · 같은 공사 엑셀 HP 에 붙음」
- 줄 쌓기 위 옵션 가로 — 가져온 식 값으로 드러난 수 하나 변수(q · K · E · Cm · Q …) = 값 칸 + 꼬리표 「원파일」 · 「엑셀」(`sourceOf` · 공식 묶음 밖) · 규격 칸(원파일) · 품셈 조건 글 = 「추정」 칸(읽기만 · 기초에 안 묶음)
- 합 밖 · 비목 없는 소계 줄 = 종류 「소계」 · 비목 칸 빈칸(편집 · 산출근거 표 칸 모두)
- 원문 카드 — 「원파일 · EST 산근 D… · 행 …」 한 줄 + 원파일 확정(확정 · 표지 · 추정 · 확정률)
- 서버 — 원파일 원본에 원코드 · 규격(EST) · 문서 머리 가져온에 원코드 · 규격 · 확정 · 추정 글줄(금액 · 길 그대로)
- 화면 계산기 큰나머지 끝전 — 나머지 비교 소수 흔들림 반올림(파이썬 Decimal 과 같은 갈래 · 동부청영월 블록 1810 재료비 1 원 어긋남 고침)
- 시험 test_810_3_native_screen(EST 1,849 · STC 371 블록 화면 셈 = 서버 셈 · 옵션 칸 · 추정 칸 수 = 확정 추정 · 소계 · 카드) · 헬퍼 helper_810_3_screen.cjs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 20:55:48 +09:00

118 lines
4.6 KiB
TypeScript

/* =============================================================================
* M02_Sangeun_Origin.ts
* 가져온 식 단가산출 「어디서 왔나」 카드 자료(PLAN 807-1 · 요구 699) — 원문 패널 첫 화면.
* SK `가져온.원본`(파일 · 시트 · 행 · 갈래 EST|STmate · 코드 · 표지 · 가져온때 · 원파일 · 글줄)에서 보이기 자료만 뽑음 ·
* 품셈 절 글 = 엑셀 글줄에 적힌 「…품셈 N-N-N」 글(책 · 절 번호 · 그 줄 글) — 찾기 · 잇기 없음(보이기만)
* 화면 · DOM · fetch 없음
* ========================================================================== */
export interface OriginMeta {
로직?: unknown;
원본?: {
파일?: string;
시트?: string;
행?: number[];
갈래?: string;
코드?: string | number;
표지?: string[];
가져온때?: string;
원파일?: boolean; // 경쟁사 원파일(.estx · .stc)에서 옮김(810-2) — 엑셀 아님
원코드?: string; // 원파일 블록 코드(EST `D…` · 810-3) — STmate 는 `코드` 가 그것
확정?: { 확정?: number; 표지?: number; 추정?: number }; // 원파일 줄 확정 수(810-2)
글줄?: { 행?: number; 글?: string }[];
};
}
export interface Clause {
책: string;
절: string;
글: string;
행?: number;
}
export interface Origin {
프로그램: string;
파일: string;
블록: string;
행: string;
때: string;
표지: string[];
품셈: Clause[];
/** 원파일 한 줄 「원파일 · EST 산근 D… · 행 …」(810-3) · 엑셀 = 없음 */
요약?: string;
/** 원파일 확정 「확정 N · 표지 N · 추정 N · 확정률 N %」 */
확정?: string;
}
const BOOKS: [RegExp, string][] = [
[/건설/, "건설표준품셈"],
[/산림/, "산림표준품셈"],
[/임도/, "임도품셈"],
];
/** 「건설공사표준품셈8-1-3」 · 「산림사업표준품셈 10-4」 · 「[2025년 표준품셈3-4-8]」 · 「임도품셈 5-1-2」 */
const CLAUSE =
/((?:건설(?:공사)?|산림(?:사업)?|임도)?\s*(?:표준\s*)?품셈)\s*[[(]?\s*(?:제\s*)?(\d{1,2}(?:-\d{1,3}){1,3})/g;
/** 글줄 → 품셈 절(책 · 절 같은 것은 처음 하나) */
export function clausesOf(lines: { 행?: number; 글?: string }[] = []): Clause[] {
const out: Clause[] = [];
const seen = new Set<string>();
for (const line of lines) {
const text = String(line.글 ?? "")
.replace(/\s+/g, " ")
.trim();
for (const m of text.matchAll(CLAUSE)) {
const book = BOOKS.find(([re]) => re.test(m[1]))?.[1] ?? "표준품셈";
const key = `${book} ${m[2]}`;
if (seen.has(key)) continue;
seen.add(key);
out.push({ 책: book, 절: m[2], 글: text, ...(line.행 !== undefined ? { 행: line.행 } : {}) });
}
}
return out;
}
const pad = (n: number): string => String(n).padStart(2, "0");
/** 서버 때(UTC 글) → 이 컴퓨터 시각 「YYYY-MM-DD HH:mm」 · 못 읽음 = 빈 글 */
const localTime = (s?: string): string => {
const at = new Date(s ?? "");
if (!s || Number.isNaN(at.getTime())) return "";
return `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())} ${pad(at.getHours())}:${pad(at.getMinutes())}`;
};
/** 가져온 식이 아니면 null */
export function originOf(meta: OriginMeta | null | undefined): Origin | null {
if (!meta?.로직 || !meta.원본) return null;
const src = meta.원본;
const code = src.코드 ?? "";
const block = src.갈래 === "EST" && code !== "" ? `단산${code}` : String(code);
const rows = (src.행 ?? []).filter((x) => Number.isFinite(x));
const native = src.원파일 ? String(src.원코드 ?? (src.갈래 === "EST" ? "" : code)) : "";
const sure = src.확정;
const rate = sure ? (sure.확정 ?? 0) / Math.max(1, (sure.확정 ?? 0) + (sure.표지 ?? 0)) : 0;
return {
프로그램: `${src.갈래 ?? ""}${src.원파일 ? " · 원파일" : ""}`,
파일: src.파일 ?? "",
블록: block,
행: rows.length ? `${src.시트 ? `${src.시트} ` : ""}${rows.join("~")}행` : (src.시트 ?? ""),
때: localTime(src.가져온때),
표지: [...(src.표지 ?? [])],
품셈: clausesOf(src.글줄),
...(src.원파일
? {
요약: [
`원파일 · ${src.갈래 ?? ""} ${src.시트 ?? ""} ${native}`.trim(),
rows.length ? `행 ${rows.join("~")}` : "",
]
.filter(Boolean)
.join(" · "),
}
: {}),
...(sure
? {
확정: `확정 ${sure.확정 ?? 0} · 표지 ${sure.표지 ?? 0} · 추정 ${sure.추정 ?? 0} · 확정률 ${(Math.floor(rate * 1000) / 10).toFixed(1)} %`,
}
: {}),
};
}