feat(m01-sl): 표 상자 열 차례를 서버 표 블록에 맞춤 — 값 칸 범위도 아래 · 위 두 칸 · 숫자 하나는 같은 수

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:28:59 +09:00
co-authored by Claude Sonnet 5.5
parent 184c66e14e
commit 679a759e84
3 changed files with 57 additions and 19 deletions
+19 -18
View File
@@ -77,17 +77,20 @@ const at = (r: number, c: number): string => `${colName(c)}${r}`;
const MARGIN = 20;
const quote = (s: string): string => `"${s.replace(/"/g, '""')}"`;
/** 블록 열 차례 — 조건 칸 · 값 칸 차례 그대로 · 범위 칸은 둘로 */
/** 범위 칸인가 — 조건 칸이 「범위」 이거나 값 칸에 범위(배열) 줄이 하나라도 있으면(서버 표 블록과 같은 규칙) */
export const isSplit = (info: TblInfo, c: string): boolean =>
info.conds[c] === "범위" || (c in info.vals && info.rows.some((r) => Array.isArray(r[c])));
/** 블록 열 차례 — 조건 칸 → 값 칸 · 범위 칸은 「이름·아래」 「이름·위」 둘로(서버 `_table` 과 같은 차례) */
export function layoutOf(info: TblInfo): TblCol[] {
const out: TblCol[] = [];
for (const c of Object.keys(info.conds))
if (info.conds[c] === "범위")
for (const c of [...Object.keys(info.conds), ...Object.keys(info.vals)])
if (isSplit(info, c))
out.push(
{ header: `${c}·아래`, of: c, part: "아래" },
{ header: `${c}·위`, of: c, part: "위" },
);
else out.push({ header: c, of: c, part: "값" });
for (const c of Object.keys(info.vals)) out.push({ header: c, of: c, part: "값" });
return out;
}
@@ -106,13 +109,8 @@ export function blockCells(info: TblInfo): Record<string, Cell> {
info.rows.forEach((row, i) =>
cols.forEach((col, c) => {
const v = row[col.of];
const x = Array.isArray(v)
? col.part === "아래"
? v[0]
: v[1]
: col.part === "값"
? v
: null;
// 범위 칸에 숫자 하나면 아래 · 위에 같은 수
const x = Array.isArray(v) ? (col.part === "아래" ? v[0] : v[1]) : v;
const cell = cellOf(x);
if (cell) cells[at(i + 2, c)] = cell;
}),
@@ -258,10 +256,11 @@ export function planLookup(info: TblInfo, roles: TblRoles): TblPlan {
);
const outCells: Record<string, string> = {};
for (const c of roles.out.filter((o) => o in info.vals)) {
outCells[c] = idxRow(
c,
`IF(${statusCell}=${quote("찾음")},INDEX(${rng(letter(c))},${idx}),NA())`,
);
for (const h of isSplit(info, c) ? [`${c}·아래`, `${c}·위`] : [c])
outCells[h] = idxRow(
h,
`IF(${statusCell}=${quote("찾음")},INDEX(${rng(letter(h))},${idx}),NA())`,
);
}
return {
cols,
@@ -286,7 +285,7 @@ export function pillsOf(
const pillsOut = Object.entries(plan.outCells).map(([name, cell]): SlPill => ({
name,
cell,
unit: info.vals[name] || undefined,
unit: info.vals[name.replace(/·(아래|위)$/, "")] || undefined,
}));
if (outside) pillsOut.push({ name: "표", cell: "A1", outside: true });
return { pillsIn, pillsOut };
@@ -416,8 +415,10 @@ export function infoFromSheet(sheet: Sheet, prev: Pick<TblInfo, "conds" | "name"
names.push(m ? { of: m[1], part: m[2] as "아래" | "위", c } : { of: h, part: "값", c });
});
for (const nm of names) {
if (nm.part !== "값") conds[nm.of] = "범위";
else if (prev.conds[nm.of]) conds[nm.of] = prev.conds[nm.of];
if (nm.part !== "값") {
if (prev.conds[nm.of]) conds[nm.of] = "범위";
else vals[nm.of] = "";
} else if (prev.conds[nm.of]) conds[nm.of] = prev.conds[nm.of];
else vals[nm.of] = "";
}
const rows: Rec[] = [];