- 3 연결 찾기 = 엑셀 이름 규격 · 단위 꼬리 뗀 이름으로 맞댐(`M02_Doc_Match_Note.bare` · 보이는 이름 그대로) · STmate 블록 규격도 가져온 칸에
- 4 기계 세 줄 = 기계 한 줄 — STmate X 코드 노 · 재 · 경 세 참조 줄(같은 수량 · 번지) 묶음(묶어서 셈 못 하는 세 줄만 그대로) ·
문서 단계 묶기 넓힘(차례 아무렇게 · 시간당 경비 이음 없으면 엑셀 값 묶음 · 번지 같을 때) · 조합 다음 줄 = 세 줄 뒤 · 같은 기계 한 항목
- 6 공구손료 율 비목 차이(재료비 ↔ 경비) = 참고(`M02_Doc_Match_Value.tool_cost`) — 확정 안 막음
- 7 못 이은 줄 까닭(`M02_San_Link.why` — 자재 없음 · 운임표 없음 · 기계 표에 없음 · 별칭 대기 · 후보 여럿 · 시간당 경비 없음 · 조합) =
문서 설명 · 이음 없는 자원 띠 · 연결 「없음」 띠 = 「우리 기초 없음 — 우리 정보로 셈 n/m 줄」 · 형제만 = 「조합」
- 8 유가 = 전국평균 줄 + 지역 옵션(`MO:경유:{S_지역}`) · 전력 = 일반용(갑)Ⅰ 저압 + 계약종별 옵션 · 엑셀 값 = 검산 칸
- 9 재료 = 지역 꼬리 뗀 이름 · 규격 정확 일치면 이음(지역 여럿 = 줄 지역 › 서울)
- 10 조합 첫 줄 규격 = 그 기계 · 얼린 줄도 소계 식에(북부청임도 33) · 기계 줄 찾기 판정 늘 붙음 · 올린 직후 409(HP 보이기 전 산근 보관) ·
엑셀 블록 「엑셀 읽기 확정」 글 · 전수 도우미 str() 뗌 · 줄 쌓기 이름에 규격이 들면 한 번만
- M02_San_Link 새 파일(San_Emit 700 줄 막음 — 요소 잇기 · 시간당 · 조합 · 까닭)
- 시험: 새 test_814_1 · 812-2 · 810-4 거푸집 41 = 근접 → 확정(공구손료 비목 = 값 · 요구 744 C1) · 바꾸기 거절 견본 = 19 ·
812-9 조합 첫 줄 규격 「0.7+브레이카」 → 「0.7」(칩 글 · 요구 738 흠 12)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
121 lines
4.8 KiB
TypeScript
121 lines
4.8 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;
|
|
/** 확정 칸 이름 — 원파일 = 「원파일 확정」 · 엑셀 = 「엑셀 읽기 확정」(814-1) */
|
|
확정이름?: 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)} %`,
|
|
확정이름: src.원파일 ? "원파일 확정" : "엑셀 읽기 확정",
|
|
}
|
|
: {}),
|
|
};
|
|
}
|