/* ============================================================================= * logic/M02_Hopyo_Imported_Model.ts * 불러오기 단산 줄 = 가져온 식 단가산출(PLAN 793-3b · 요구 646 · 647) — 꼴 · 읽기 · 바꾸기만. * · SK `가져온` 덧칸 꼴(793-2 `M02_Sangeun_Imported.py` 머리) · 근거 제안 세 단(확정 · 근접 · 사람 고름 · 793-3a 가 채움) * · 단산 줄 고른 것 = {갈래 단가산출, 키 SK-…, 필요(입력 필요), 표지(결과 칩)} — 층 = 옮길 층 — 필요가 비어야 확인 · 옮김 * · 줄 결과 칩 = 가져온 식 있음 · 근거 확정 · 근접 · 사람 고름 · 입력 필요 n(「2차 필요」 대신) * 화면 · fetch · sessionStorage 없음(`CLAUDE.md` 4장). * ========================================================================== */ import type { DraftPick } from "./M02_MasterTemplete_Hopyo_Picks"; export type Money4 = Record<"노무비" | "재료비" | "경비" | "계", number>; export type Tier = "확정" | "근접" | "사람"; export const TIERS: Tier[] = ["확정", "근접", "사람"]; /** 근거 후보 하나(793-3a `제안.근거후보`) — 까닭 = [[표지(판 차이 · 현장 값 · 감률 · 할증 배수 · 단위 기준 차이 · 그 밖), 글]] */ export interface BasisCand { 기초: string; 이름?: string; 원문번호?: string; 옵션?: Record; /** 글에 없는 칸(감률 · 할증 포함) — 사용자 입력 */ 입력필요?: string[]; 품검산?: "같음" | "다름" | "짝 없음" | null; 단: Tier; 까닭?: [string, string][]; } /** 근거 후보 까닭 글(「표지 글」 · 줄마다) */ export const reasonText = (c: BasisCand): string => (c.까닭 ?? []).map(([tag, text]) => `${tag} ${text}`.trim()).join(" · "); export interface CheckLine { 자리: string; 행?: number | null; 셈?: number | null; 엑셀?: number | null; 같음: boolean; 까닭?: string; } /** SK 문서 덧칸 `가져온` */ export interface ImportedMeta { 로직?: { 이름?: string; 호표?: { 이름?: string; 이음?: string | null }[] }; 단가기준?: "엑셀" | "우리"; 원본?: { 파일?: string; 시트?: string; 행?: number[]; 갈래?: string; 엑셀금액?: Money4; 글줄?: { 행?: number | null; 글?: string; 자리?: string; 값?: number | null }[]; }; 검산?: { 같음: boolean; 까닭?: string | null; 셈?: Money4 | null; 엑셀?: Money4; 줄: CheckLine[]; }; 근거후보?: BasisCand[]; 바꿈?: { 전?: Money4 | null; 후?: Money4 | null; 차이?: Money4 | null }; } export interface ImportedDoc { 이름?: string; 단위?: string; 바탕?: { 로직?: string } | null; 입력값?: Record; /** SK 단위 환산(기초 결과단위 성격이 다를 때 · 728-2) */ 환산?: { 계수?: number | string }; 가져온?: ImportedMeta; [k: string]: unknown; } /** 가져온 식(아직 우리 기초로 안 바꿈) */ export const isImported = (d: ImportedDoc | null | undefined): boolean => !!d?.가져온?.로직; /** 근거 후보 세 단 */ export function tiers(list: BasisCand[] | undefined): Record { const out: Record = { 확정: [], 근접: [], 사람: [] }; for (const c of list ?? []) if (TIERS.includes(c.단)) out[c.단].push(c); return out; } /** 검산 다른 줄 */ export const wrongLines = (d: ImportedDoc): CheckLine[] => (d.가져온?.검산?.줄 ?? []).filter((x) => !x.같음); const blank = (v: unknown): boolean => v === undefined || v === null || String(v).trim() === ""; /** 입력 필요 칸 값 — 입력값 + 「환산」 = 환산 계수 */ export const needValues = (d: ImportedDoc): Record => ({ ...(d.입력값 ?? {}), 환산: d.환산?.계수, }); /** 입력 필요 중 아직 빈 칸 */ export const stillNeed = (need: string[], values: Record | undefined): string[] => need.filter((k) => blank(values?.[k])); /** 단산 줄 고른 것 — 그 SK(필요 = 빈 입력 필요 칸 · `missing` 이 막음) · 층 · 주인 안 둠 = 옮길 층(SK 를 만든 층)의 SK */ export function skPick( sk: string, doc: ImportedDoc, need: string[], marks: string[], ): DraftPick & { 표지: string[] } { const values = needValues(doc); return { 갈래: "단가산출", 키: sk, 이름: String(doc.이름 ?? sk), 단위: String(doc.단위 ?? ""), 입력값: Object.fromEntries(need.map((k) => [k, values[k] ?? ""])), 출처: {}, 몫: null, 필요: need, 표지: marks, }; } /** 결과 표지 — 가져온 식 있음 | 우리 기초(+ 근거 단) */ export function marksOf(doc: ImportedDoc, tier: Tier | null): string[] { if (isImported(doc)) return ["가져온 식 있음"]; return tier ? [`근거 ${tier}`] : ["사람 고름"]; } /** 단산 줄 결과 칩 글 — SK 고른 것이 있으면 그 표지 + 입력 필요 n · 없으면 null(옛 판정 칩) */ export function rowChips(picks: (DraftPick & { 표지?: string[] })[]): string[] | null { const sk = picks.find((p) => p.갈래 === "단가산출" && /^SK-/.test(String(p.키 ?? ""))); if (!sk) return null; const need = stillNeed(sk.필요 ?? [], sk.입력값); return [...(sk.표지 ?? ["가져온 식 있음"]), ...(need.length ? [`입력 필요 ${need.length}`] : [])]; } /** 금액 차이 글(후 − 전) */ export function gapText( before: Money4 | null | undefined, after: Money4 | null | undefined, ): string { if (!before || !after) return "—"; const d = after.계 - before.계; return `${d > 0 ? "+" : ""}${d.toLocaleString("ko-KR")}`; }