feat(m02-df): 단가산출 흐름도 0걸음 — CostFlow 를 DataFlow 로 옮겨 이름 바꿈

- 파일 M02_CF_* → M02_DF_* · css m02-cf/m01-cf → m02-df · 서버 길 /api/m02/cf → /api/m02/df · 키 CF- → DF- · 사용자 요소 UE- 그대로
- _sample · .gitignore(_data git 밖) 옮김 · DataFlow 는 CostFlow 를 import 안 함
- 시험 test_m02_df_model · router · sample · inputpanel_build(옛 cf 시험은 그대로)
- 서버 등록은 sub1 몫 — M02_MasterTemplete.DataFlow.M02_DF_Router 의 router

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBToTCGk5dqDKeXCEP3vhk
This commit is contained in:
2026-10-01 19:37:51 +09:00
co-authored by Claude Opus 5.5
parent 12a60baeeb
commit fc7affb019
28 changed files with 8538 additions and 0 deletions
@@ -0,0 +1,65 @@
/* =============================================================================
* M02_DF_Sample.ts
* 단가산출 다이어그램 견본 — 처음 열었을 때 바로 보고 따라 만들 견본 셋(`_sample/*.json` · 손으로 짬)
* ① 돌쌓기 메쌓기(인력) — 변수 · 품표 · 증가율표 · 인력 → 함수(표 찾기 · 높이 할증 · 직렬 · 병렬) → 출력 노무비
* ② 공구손료(비율) — 노무비 × 율 → 출력 재료비
* ③ 덤프트럭 시간당 작업량(값) — 변수 넷 → 함수 하나 → 출력 값
* 견본은 읽기만 — `sampleLoad` 는 「복사해서 열기」 사본(key 없음 · 저장하면 `_data` 에 새 번호).
* 입력 값(노임 · 표)은 견본에 안 들고 열 때 · 저장 때 마스터에서 받음(정함 「계산」).
* 모양 = `M02_DF_Model` CfLogic · 알약 이름 규칙(입력 = 요소 이름 · 「표」 · 변수 이름 / 함수 = inputs[].port ·
* output.name / 출력 = 「값」) — 정함 `docs/raw/M01_쉬운단가/원가다이어그램_정함.md` 11. 이 폴더째 지워도 됨.
* ========================================================================== */
import { currentLanguageIndex } from "@ui/ui_template_locale";
import type { CfLogic } from "./M02_DF_Model";
const FILES = import.meta.glob<CfLogic>("./_sample/*.json", { eager: true, import: "default" });
/** 견본마다 한 줄 설명 — [한, 영] */
const NOTE: Record<string, readonly [string, string]> = {
masonry: [
"변수 · 품표 · 증가율표 · 인력 → 표 찾기 · 높이 할증 → 품 × 노임 → 노무비",
"Variables, tables and labor → lookup, height surcharge → crew × wage → labor cost",
],
rate: ["노무비 × 공구손료율 → 재료비", "Labor cost × small-tools rate → material cost"],
value: [
"q · f · E · Cm → 60 × q × f × E ÷ Cm → 값(시간당 작업량)",
"q, f, E, Cm → 60 × q × f × E ÷ Cm → value (output per hour)",
],
};
/** 보여 줄 차례 */
const ORDER = ["masonry", "rate", "value"];
export interface CfSampleItem {
/** 파일 이름(확장자 뺌) */
id: string;
name: string;
resultUnit: string;
/** 쉬운 말 한 줄 */
note: string;
}
const idOf = (path: string): string => path.replace(/^.*\//, "").replace(/\.json$/, "");
const byId = (): Map<string, CfLogic> =>
new Map(Object.entries(FILES).map(([path, lg]) => [idOf(path), lg]));
/** 견본 목록 — 정한 차례 먼저 · 나머지는 이름 차례 */
export function sampleList(): CfSampleItem[] {
const all = byId();
const rank = (id: string): number => (ORDER.includes(id) ? ORDER.indexOf(id) : ORDER.length);
return [...all.entries()]
.sort(([a, x], [b, y]) => rank(a) - rank(b) || x.name.localeCompare(y.name))
.map(([id, lg]) => ({
id,
name: lg.name,
resultUnit: lg.resultUnit,
note: NOTE[id]?.[currentLanguageIndex === 1 ? 1 : 0] ?? "",
}));
}
/** 견본 하나 「복사해서 열기」 — 깊은 사본 · key 없음(저장하면 서버가 새 번호) · 없으면 null */
export function sampleLoad(id: string): CfLogic | null {
const lg = byId().get(id);
return lg ? { ...(JSON.parse(JSON.stringify(lg)) as CfLogic), key: null } : null;
}