Files
Aislo/M02_MasterTemplete/logic/M02_Hopyo_Imported_Model.ts
T
eomsangdonandClaude Opus 5.5 5a6cccd997 fix(793-3b): 우리 기초로 바꾸기 — 옵션 = 블록 풀이 후보 바인딩 · 단위 성격 다르면 환산 계수 입력 필요 · 가져온 식 시트 M01 바뀜 표지 걷음
- to-base 몸 환산? · 환산 없이 단위 성격 다름 = 후 None · 필요 ["환산"](막지 않음 · 화면 붉은 칸)
- 화면 bind = 블록 풀이 그 기초 후보 바인딩(엑셀 값 · 기본) + 블록 검산 입력 필요 · 줄 검산(빈 입력값)은 옵션을 못 읽었음
- 가져온 식 SK = M01 짝 없음 — 시트 M01바뀜 · 짝잃음 안 띄움(저장 M01판이 바탕 줄과 달라 늘 떴음)
- 시험 test_793_3b 8

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:26:07 +09:00

143 lines
5.1 KiB
TypeScript

/* =============================================================================
* 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;
옵션?: Record<string, unknown>;
품검산?: unknown;
단: Tier;
까닭?: string;
차이?: string | number;
}
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<string, unknown>;
/** 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<Tier, BasisCand[]> {
const out: Record<Tier, BasisCand[]> = { 확정: [], 근접: [], 사람: [] };
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<string, unknown> => ({
...(d.입력값 ?? {}),
환산: d.환산?.계수,
});
/** 입력 필요 중 아직 빈 칸 */
export const stillNeed = (need: string[], values: Record<string, unknown> | undefined): string[] =>
need.filter((k) => blank(values?.[k]));
/** 단산 줄 고른 것 — 그 SK(필요 = 빈 입력 필요 칸 · `missing` 이 막음) */
export function skPick(
sk: string,
doc: ImportedDoc,
scope: { layer: string; owner: string | null },
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,
층: scope.layer,
주인: scope.owner,
필요: 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")}`;
}