feat(m01-sl): 표 상자 Table 완성 — 내 표 붙여넣기 · 범위 조건 · 빈칸 무관 · 선 수식 보존 · 시험

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
This commit is contained in:
2026-10-01 13:13:03 +09:00
co-authored by Claude Sonnet 5.5
parent 7bd6e8d7d1
commit eb9082dc30
2 changed files with 29 additions and 1 deletions
@@ -159,7 +159,7 @@ export function planLookup(info: TblInfo, roles: TblRoles): TblPlan {
const bl = blankable.slice(0, 2);
const subsets =
bl.length === 2 ? [[bl[0]], [bl[1]], [bl[0], bl[1]]] : bl.length === 1 ? [[bl[0]]] : [];
variants.push(...subsets);
variants.push(...subsets.filter((sub) => sub.length < exact.length)); // 모든 조건 칸이 빈칸인 변형은 열쇠가 없음
}
const keyCols = exact.length ? variants : [];
const kStart = W + 1;
@@ -310,6 +310,8 @@ export function applyTable(
if (si < 0) throw new Error("상자의 시트가 없음");
const plan = planLookup(info, roles);
const sheet: Sheet = next.workbook.시트[si];
// 이미 있던 입력 칸 내용(예시값 · 선을 그은 참조 수식)은 같은 이름이면 그대로 가져감
const oldIn = Object.fromEntries(node.pillsIn.map((p) => [p.name, sheet.칸[p.cell]]));
const keep = Object.entries(sheet.칸).filter(
([a]) => colIndex(a.replace(/\d+/g, "")) < plan.helperFrom,
);
@@ -319,6 +321,8 @@ export function applyTable(
Object.assign(sheet.칸, blockCells(info));
}
Object.assign(sheet.칸, plan.cells);
for (const [name, cell] of Object.entries(plan.inCells))
if (oldIn[name]) sheet.칸[cell] = oldIn[name];
const pills = pillsOf(info, plan, opts.outside);
const gone = (dir: "in" | "out", name: string): boolean =>
!(dir === "in" ? pills.pillsIn : pills.pillsOut).some((p) => p.name === name);
@@ -36,6 +36,17 @@ const out = jobs.map((job) => {
return { cells, plan };
}
if (job.op === "paste") return m.parsePaste(job.text);
if (job.op === "reapply") {
const base = { key: null, name: "t", resultUnit: "원/㎡", edges: [],
workbook: { 종류: "통합문서", 판: 1, 열: "t", 서식: [{}], 시트: [{ id: "s1", 이름: "표", 칸: {} }] },
nodes: [{ id: "n1", kind: "table", name: "표", sheetId: "s1", pillsIn: [], pillsOut: [], stage: 1 }] };
const opts = { ref: "T1", fillBlock: true, outside: false };
const first = m.applyTable(base, "n1", job.info, job.roles, opts).sl;
const cell = first.nodes[0].pillsIn[0].cell;
first.workbook.시트[0].칸[cell] = { 식: "'다른 시트'!B2" };
const second = m.applyTable(first, "n1", job.info, job.roles, { ...opts, fillBlock: false }).sl;
return { cell, kept: second.workbook.시트[0].칸[cell], pills: second.nodes[0].pillsIn.map((p) => p.name), name: second.nodes[0].name };
}
return null;
});
process.stdout.write(JSON.stringify(out));
@@ -173,3 +184,16 @@ def test_붙여넣기_읽기():
assert info["vals"] == {"할증": ""}
assert info["rows"][1] == {"경사": "급", "범위": [10, None], "할증": 10}
assert run([{"op": "paste", "text": "한 줄뿐"}])[0]["error"]
def test_다시_짜도_선_수식은_그대로():
out = run([{"op": "reapply", "info": MINE, "roles": {"cond": ["경사"], "out": ["할증"]}}])[0]
assert out["kept"] == {"식": "'다른 시트'!B2"}
assert out["pills"] == ["경사"] and out["name"] == "표 · 견본"
def test_표가_비었어도_수식이_깨지지_않음():
"""내 표를 막 만든 때(줄이 다 빈칸) — 빈칸 변형 열쇠가 빈 수식이 되면 안 됨."""
empty = {**MINE, "rows": [{}, {}]}
r = solve(empty, {"경사": "급"}, ["할증"])
assert r["상태"] == "맞는 줄 없음" and r["할증"] == "#N/A"