- 표 값(요구 678-1): 「표」 꼬리표가 글뿐이라 눌러도 안 열림 → [표 · 고른 줄 · 칸 ▾] 단추 = 품셈 표 창(값 칸 누름 = 그 줄 · 칸) 옵션에 매인 조건(폼타이규격 등) = 그 줄만 고름 · 다른 줄 막힘 + 「위 옵션에서 고름」 안내 · M02 길 표 값 글 수도 고름 - 산출근거 들여쓰기(678-2): 머리 0 · 항목 1 · 가나다 2 · 줄 = 항목 + 1 · 펼친 블록 = 그 줄 + 1(수준 = stackLevels) - 찾아 넣기(678-3): [+ 줄] 인력 · 재료 · 기계 · 다른 단가산출 = M02 [항목 추가] 창 그대로(그 종류만) · 고르면 줄 생김(닫으면 없음) 있는 줄 = [찾아 바꾸기](같은 로직 기계 = 지역 · 보정작업 넘김 그대로) · 옛 찾기 칸 + 고름 목록 · 기종 코드 고르개 걷음 빈 틀 = [찾아 넣기](돈 아닌 단가산출 결과만 — Find_Pick results) - 짝 강조(679-1): 오른쪽 줄 누름 = 왼쪽 카드 강조 + 보이는 자리로 · 왼쪽 누름 = 오른쪽 같은 줄 강조 - 기계 이름(679-2): 기계 줄 = 기종 이름 · 규격 · machine-hourly 답에 로직 키 · 원문번호 더함(기존 칸 그대로) - 모델 common_util_line_stack_pick.ts(boundConds · currentRow · pickCell · addPicked · repick · stackLevels) - 시험 test_804_2 넓힘 3(표 고름 = 값 + 출처 · 서버 셈 같음 · 찾아 넣기 넷 = 요소 키 · 서버 금액 · 다시 고름 · 수준) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
175 lines
7.4 KiB
JavaScript
175 lines
7.4 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;
|
|
}
|
|
|
|
const input = JSON.parse(fs.readFileSync(0, "utf8"));
|
|
const job = {
|
|
왕복: roundTrip,
|
|
셈: money_,
|
|
펼침: unfold,
|
|
표고름: tablePick,
|
|
넣기: putPicked,
|
|
바꿈: repickOne,
|
|
}[input.할일];
|
|
process.stdout.write(JSON.stringify(input.문서들.map(job)));
|