- 표 값 기호 화면 이름 = 고른 줄 이름(Q_수량2^표 → Q_특별인부 · 겹치면 2 · 3) — 왼쪽 · 산출근거 같음 · 칸 글은 화면 이름 ↔ 기호로 오감(문서 기호 그대로) - 단위 「줄 따름」 안 보임 · 표 값만 든 묶음 머리 = 「표 값」 · [식으로 풀기] → 「식 고치기」 - 산출근거 = 넓을 때 화면 고정 + 자기 스크롤(높이 = 스크롤 영역) · 나란히 놓는 탭 폭 1240 → 1040 - 글 「항목」 → 「그룹」(그룹 이름 · + 그룹 · 그룹 지움) - 짝 강조 = 왼쪽 카드 · 그룹 머리 · 오른쪽 줄 모두 같은 바탕 채우기(테두리 걷음) - [틀로 저장] · 내 틀 목록(브라우저 저장) 걷음 · 공식 틀 고르기 = M01 단가산출 결과만 - 시험 test_804_4 표 값 화면 이름 1,518 전수(겹침 없음 · 식 글 왕복 같음) 더함 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
256 lines
10 KiB
JavaScript
256 lines
10 KiB
JavaScript
/* 804-2 줄 쌓기 헬퍼 — common_util_line_stack.ts · 계산기 · 캐시를 그 자리에서 트랜스파일해 Node 로 돌림.
|
|
* stdin = {할일, 문서들 [{문서, 연결?}]} · stdout = 할일마다 답 목록 */
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
|
|
|
const cache = new Map();
|
|
function load(at) {
|
|
if (cache.has(at)) return cache.get(at);
|
|
const js = ts.transpileModule(fs.readFileSync(at, "utf8"), {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
|
}).outputText;
|
|
const box = { exports: {} };
|
|
cache.set(at, box.exports);
|
|
const need = (p) =>
|
|
p.startsWith(".") ? load(path.resolve(path.dirname(at), `${p}.ts`)) : require(p);
|
|
new Function("exports", "module", "require", js)(box.exports, box, need);
|
|
return box.exports;
|
|
}
|
|
const util = (name) => load(path.join(__dirname, "..", "..", "common_util", `${name}.ts`));
|
|
const ls = util("common_util_line_stack");
|
|
const calc = util("common_util_formula_doc_calc");
|
|
const dc = util("common_util_formula_doc_cache");
|
|
|
|
const money = (a) => ({ 노무비: a.노무비, 재료비: a.재료비, 경비: a.경비, 계: a.계, 결과: a.결과 });
|
|
|
|
/** 왕복 — 문서 → 줄 → 문서 · 덮음(모든 금액 줄 한 번 · 차림 기호 한 번) · 차림 고침 뒤 다시 읽어 같음 */
|
|
function roundTrip({ 문서 }) {
|
|
const items = ls.stackOf(문서);
|
|
const back = ls.docOf(문서, items);
|
|
const ms = items.flatMap((it) => it.줄.filter((l) => l.꼴 === "금액").map((l) => l.i));
|
|
const syms = items.flatMap((it) => it.줄.filter((l) => l.꼴 === "공식").flatMap((l) => l.기호));
|
|
const hidden = ls.hiddenOf(문서);
|
|
const want = ls.symbols(문서).filter((x) => !hidden.has(x));
|
|
// 차림 고침 — 항목마다 글 줄 · 첫 항목 가나다 · 맨 앞 새 항목 제목
|
|
const edited = JSON.parse(JSON.stringify(items));
|
|
edited.forEach((it, k) => it.줄.push({ 꼴: "글", 글: `< 근거 ${k} >` }));
|
|
if (edited[0]) edited[0].가나다 = true;
|
|
edited.unshift({ 제목: "시험 머리", 줄: [{ 꼴: "글", 글: "※ 메모" }] });
|
|
const laid = ls.docOf(문서, edited);
|
|
const again = ls.stackOf(laid);
|
|
return {
|
|
같음: JSON.stringify(back) === JSON.stringify(문서),
|
|
쌓기: "쌓기" in back,
|
|
금액: ms.length === (문서.금액 || []).length && new Set(ms).size === ms.length,
|
|
기호:
|
|
syms.length === want.length &&
|
|
new Set(syms).size === syms.length &&
|
|
want.every((x) => syms.includes(x)),
|
|
차림: JSON.stringify(again) === JSON.stringify(edited),
|
|
차림문서: laid,
|
|
못: ls.notesOf(문서),
|
|
};
|
|
}
|
|
|
|
/** 셈 — 연결 기록만으로(화면과 같은 길) · 기계 · 단가산출 펼침 셈 */
|
|
function money_({ 문서, 연결 }) {
|
|
const c = dc.createDocCache();
|
|
c.merge(연결);
|
|
const a = calc.calcDoc(문서, c.links, {});
|
|
const subs = [];
|
|
(문서.금액 || []).forEach((l, i) => {
|
|
const kind = ls.kindOf(문서, i);
|
|
if (kind !== "기계" && kind !== "단가산출") return;
|
|
const s = ls.priceSym(l);
|
|
const link = (문서.변수 || []).find((v) => ls.symbols({ 변수: [v] })[0] === s)?.연결;
|
|
const sub = c.links.doc(link.키);
|
|
const ins = ls.subInputs(link, a.값 || {});
|
|
const got = calc.calcDoc(sub, c.links, ins);
|
|
subs.push({ i, 갈래: kind, 키: link.키, 입력: ins, ...money(got), 값: (a.값 || {})[s] });
|
|
});
|
|
return { ...money(a), 펼침: subs, 누락: c.takeMissing() };
|
|
}
|
|
|
|
/** 틀 펼침 — 단가산출 결과 연결 기호를 이 문서 식으로 옮김 → 셈 같음 · 기호 없으면 그런 기호 전부(하나씩)
|
|
* 고침 = 펼친 뒤 [{기호, 글}](setRow) · 지움 = 펼친 뒤 지울 기호(dropSyms) */
|
|
function unfold({ 문서, 연결, 기호, 고침, 지움 }) {
|
|
const c = dc.createDocCache();
|
|
c.merge(연결);
|
|
const before = calc.calcDoc(문서, c.links, {});
|
|
const idOf = (v) => ls.symbols({ 변수: [v] })[0];
|
|
const targets = 기호
|
|
? [기호]
|
|
: (문서.변수 || [])
|
|
.filter((v) => v.연결?.갈래 === "단가산출" && v.연결.키 && !v.연결.키.includes("{"))
|
|
.filter((v) => c.links.doc(v.연결.키)?.결과)
|
|
.map(idOf);
|
|
return targets.map((id) => {
|
|
const link = 문서.변수.find((v) => idOf(v) === id).연결;
|
|
const doc = JSON.parse(JSON.stringify(문서));
|
|
const moved = ls.unfold(doc, c.links.doc(link.키), id);
|
|
const after = calc.calcDoc(doc, c.links, {});
|
|
for (const g of 고침 || []) ls.setRow(doc, g.기호, g.글);
|
|
if (지움) ls.dropSyms(doc, 지움);
|
|
const edited = calc.calcDoc(doc, c.links, {});
|
|
const items = ls.stackOf(doc);
|
|
return {
|
|
기호: id,
|
|
전: money(before),
|
|
후: money(after),
|
|
고친: money(edited),
|
|
표지: (edited.표지 || []).map((m) => m.갈래),
|
|
옮김: moved,
|
|
틀꼴: ls.shapeOf(doc, moved),
|
|
왕복: JSON.stringify(ls.docOf(doc, items)) === JSON.stringify(doc),
|
|
문서: doc,
|
|
};
|
|
});
|
|
}
|
|
|
|
/* 804-3 고르기 — common_util_line_stack_pick.ts */
|
|
const pk = util("common_util_line_stack_pick");
|
|
const idOf = (v) => ls.symbols({ 변수: [v] })[0];
|
|
const varOf = (doc, id) => (doc.변수 || []).find((v) => idOf(v) === id);
|
|
|
|
/** 표 고름 — 앞 = [{기호, 조건}](먼저 조건을 바꿔 둠) · 기호의 표에서 줄 · 칸 고름 → 연결 · 값 · 금액 */
|
|
function tablePick({ 문서, 연결, 기호, 줄, 칸, 앞 }) {
|
|
const doc = JSON.parse(JSON.stringify(문서));
|
|
for (const f of 앞 || []) varOf(doc, f.기호).연결.조건 = f.조건;
|
|
const c = dc.createDocCache();
|
|
c.merge(연결);
|
|
const link = varOf(doc, 기호).연결;
|
|
const t = 연결.표[link.표];
|
|
const before = calc.calcDoc(doc, c.links, {});
|
|
const now = pk.currentRow(t, link, before.값 || {});
|
|
const bound = pk.boundConds(doc, link);
|
|
pk.pickCell(doc, 기호, t, t.줄[줄], 칸);
|
|
const after = calc.calcDoc(doc, c.links, {});
|
|
return {
|
|
전줄: now,
|
|
묶임: bound,
|
|
연결: varOf(doc, 기호).연결,
|
|
값: (after.값 || {})[기호],
|
|
후줄: pk.currentRow(t, varOf(doc, 기호).연결, after.값 || {}),
|
|
...money(after),
|
|
문서: doc,
|
|
};
|
|
}
|
|
|
|
/** 찾아 넣기 — 새 항목에 고른 것 차례로 · 바꿈 = [{차례, 고른}](넣은 줄 다시 고름) */
|
|
function putPicked({ 문서, 넣을, 바꿈 }) {
|
|
const doc = JSON.parse(JSON.stringify(문서));
|
|
const items = ls.stackOf(doc);
|
|
items.push({ 제목: "시험 넣기", 가나다: true, 줄: [] });
|
|
const base = (doc.금액 || []).length;
|
|
const ids = 넣을.map((x) => pk.addPicked(doc, items[items.length - 1], x.갈래, x.고른));
|
|
for (const r of 바꿈 || []) pk.repick(doc, base + r.차례, ids[r.차례], 넣을[r.차례].갈래, r.고른);
|
|
const laid = ls.docOf(doc, items);
|
|
return {
|
|
문서: laid,
|
|
기호: ids,
|
|
처음: base,
|
|
갈래: ids.map((_, k) => ls.kindOf(laid, base + k)),
|
|
수준: pk.stackLevels(ls.stackOf(laid)),
|
|
};
|
|
}
|
|
|
|
/** 있는 줄 다시 고름 — {문서, 줄, 기호, 갈래, 고른} → 문서 */
|
|
function repickOne({ 문서, 줄, 기호, 갈래, 고른 }) {
|
|
const doc = JSON.parse(JSON.stringify(문서));
|
|
pk.repick(doc, 줄, 기호, 갈래, 고른);
|
|
return doc;
|
|
}
|
|
|
|
/* 804-4 복사해 펼침 — common_util_line_stack_copy.ts · 받음 = 서버 조각(차례로 넣음) */
|
|
const cp = util("common_util_line_stack_copy");
|
|
function takeIn({ 문서, 받음 }) {
|
|
const doc = JSON.parse(JSON.stringify(문서));
|
|
const items = ls.stackOf(doc);
|
|
const was = items.length;
|
|
for (const t of 받음) cp.addTaken(doc, items, t);
|
|
const laid = ls.docOf(doc, items);
|
|
const again = ls.stackOf(laid);
|
|
return {
|
|
문서: laid,
|
|
항목: again.slice(was).map((it) => ({
|
|
제목: it.제목,
|
|
가나다: !!it.가나다,
|
|
줄: it.줄.map((l) =>
|
|
l.꼴 === "금액"
|
|
? `금액:${laid.금액[l.i].이름}`
|
|
: l.꼴 === "글"
|
|
? `글:${l.글}`
|
|
: `공식:${l.기호.length}`,
|
|
),
|
|
})),
|
|
옵션: (laid.변수 || []).filter((v) => ls.isOption(v)).map(idOf),
|
|
못: ls.notesOf(laid),
|
|
왕복: JSON.stringify(ls.docOf(laid, again)) === JSON.stringify(laid),
|
|
};
|
|
}
|
|
|
|
/* 804-5 끌어 옮김 · 번지 읽는 이름 — 옮김 = [[from, to] …](줄 "i.j" → "k.p" · 항목 "i" → "p") */
|
|
function moveAll({ 문서, 옮김 }) {
|
|
let doc = JSON.parse(JSON.stringify(문서));
|
|
for (const [a, b] of 옮김) {
|
|
const items = ls.stackOf(doc);
|
|
if (a.includes("."))
|
|
ls.moveLine(items, ...a.split(".").map(Number), ...b.split(".").map(Number));
|
|
else ls.moveItem(items, Number(a), Number(b));
|
|
doc = ls.docOf(doc, items);
|
|
}
|
|
const items = ls.stackOf(doc);
|
|
return {
|
|
문서: doc,
|
|
항목: items.map((it) => ({
|
|
제목: it.제목,
|
|
줄: it.줄.map((l) =>
|
|
l.꼴 === "금액" ? doc.금액[l.i].이름 : l.꼴 === "글" ? l.글 : l.기호.join(","),
|
|
),
|
|
})),
|
|
};
|
|
}
|
|
const placeOf = ({ 문서 }) => cp.placeNames(문서, ls.stackOf(문서));
|
|
|
|
/* 683 표 값 기호 화면 이름 — 별칭 · 글 왕복(화면 이름 → 기호 = 처음 식과 같은 나무) */
|
|
const pk2 = util("common_util_line_stack_pick");
|
|
const fd = util("common_util_formula_doc");
|
|
function aliasOf({ 문서 }) {
|
|
const alias = pk2.tableAlias(문서);
|
|
const texts = [
|
|
...(문서.식 || []).map((l) => l.식),
|
|
...(문서.금액 || []).flatMap((l) => [l.수량, l.단가]),
|
|
].filter((t) => typeof t === "string" && t);
|
|
const bad = [];
|
|
for (const t of texts) {
|
|
let want;
|
|
try {
|
|
want = fd.textOf(fd.parse(t));
|
|
} catch {
|
|
continue;
|
|
}
|
|
const shown = pk2.aliasText(t, alias);
|
|
const back = pk2.aliasText(shown, alias, true);
|
|
if (fd.textOf(fd.parse(back)) !== want) bad.push(t);
|
|
if (Object.keys(alias).some((id) => fd.names(fd.parse(shown)).includes(id)))
|
|
bad.push(`남음:${t}`);
|
|
}
|
|
return { 별칭: alias, 틀림: bad };
|
|
}
|
|
|
|
const input = JSON.parse(fs.readFileSync(0, "utf8"));
|
|
const job = {
|
|
왕복: roundTrip,
|
|
셈: money_,
|
|
펼침: unfold,
|
|
표고름: tablePick,
|
|
넣기: putPicked,
|
|
바꿈: repickOne,
|
|
복사: takeIn,
|
|
옮김: moveAll,
|
|
번지: placeOf,
|
|
별칭: aliasOf,
|
|
}[input.할일];
|
|
process.stdout.write(JSON.stringify(input.문서들.map(job)));
|