설계: 시트 참조·수식 아키텍처 통합 (F-1 + C-1 + C-2 + H-2)
상태: 초안 v1 (2026-07-06) · 근거 피드백: 시트 UX 피드백 2026-07 v2 (외부 문서로 이관됨 — 항목 번호 F-1/C-1/C-2/H-2는 그 문서 기준) 결정: 아키텍처급 항목부터 착수 (2026-07-06, 사용자 결정) 관련 구조: 시트 아키텍처
0. 왜 묶어서 설계하나
F-1(시트 간 참조), C-1(= 인라인 수식), C-2(참조 가독성), H-2(상수 참조)는 하나의 문법을 공유한다. 따로 만들면 "시트 참조 문법 ≠ 셀 수식 문법 ≠ 상수 문법"으로 갈라져 사용자·구현 모두 3배 비용. 이 문서는 저장 문법 하나 + 표시 문법 하나로 네 요구를 관통하는 설계를 정의한다.
1. 핵심 발견 — 백엔드는 이미 절반 이상 있다
조사 결과(2026-07-06), "사실상 연동이 거의 불가능"의 원인은 엔진 부재가 아니라 authoring UX 부재다.
| 이미 있는 것 | 위치 | 비고 |
|---|---|---|
| 수식 평가기 (shunting-yard) | resolver.ts:1305 evaluateFormula | [colKey] 참조, Excel 함수(TODAY/IF/ROUND/ABS…), 비교연산, 날짜 산술, 콤마 정규화 |
| 시트 간 참조 | resolver.ts:2106~ (Phase 5+) | [slug:colKey] · [uuid:colKey] 문법. 같은 (org, 기간)에서 slug 해석 → 대상 시트 재귀 resolve → employeeId 매칭 O(1) 조회. 미해석 참조는 0 |
| 의존성 정렬 + 순환 감지 | resolver (Kahn) | 순환 수식은 N/A |
| 조직 파라미터 저장소 | OrganizationParameter(+Version) 엔티티, compute.ts $:namespace.key | 버전(effectiveFrom) 개념 보유. computeConfig에서만 소비, 사용자 UI 없음 |
| 셀 수식 스키마 | SheetCell.kind=formula + formulaExpression | 스키마만 존재 — 평가 경로 없음 (평가는 컬럼 수식 col.formula만) |
격차 (실제로 만들어야 하는 것)
- 참조를 이름으로 쓰고 읽는 레이어 (지금은 slug/uuid를 알아야 함 — 시드 템플릿 작성자만 가능)
- 시트·컬럼 픽커/자동완성 (F-1)
- 셀 단위 수식 평가 경로 +
=진입 UX (C-1) - 수식 내 상수 참조 문법 + 파라미터 저장소 사용자 UI (H-2)
- 위치 주소(A1)·행 이름 표시 (C-2)
2. 설계 원칙
저장은 안정 앵커(stable anchor), 표시는 사람 언어. 둘 사이 치환은 편집기 레이어의 책임.
- 저장 문법은 기존
[slug:colKey]를 그대로 유지 (하위호환 — 기존 시드 수식 전부 무수정). - 시트 이름·행 이름·A1 주소는 표시 전용 — 이름이 바뀌어도, 행이 이동해도 저장된 수식은 불변.
- 새 문법 추가는 상수 참조
[$namespace.key]하나뿐. 나머지는 전부 표시/입력 레이어.
3. 문법 정의
3.1 저장 문법 (canonical — DB에 기록되는 형태)
| 참조 대상 | 문법 | 상태 |
|---|---|---|
| 같은 시트, 같은 행의 컬럼 | [colKey] | 기존 |
| 다른 시트(slug 보유) 컬럼 | [slug:colKey] | 기존 |
| 다른 시트(slug 없음) 컬럼 | [uuid:colKey] | 기존 (픽커가 생성, 사용자 노출 최소화) |
| 조직 상수 | [$namespace.key] | 신규 — 평가 시점에 파라미터 저장소에서 해석, 커밋 시 appliedParameters 스냅샷에 포함 |
| 특정 행 셀 (셀 수식용) | [colKey@rowId] | 신규(Phase R3) — 행 고정 참조. rowId가 안정 앵커 |
3.2 표시 문법 (편집기·렌더에서 보이는 형태)
| 저장 | 표시 (편집기) | 비고 |
|---|---|---|
[colKey] | [기본급] (컬럼 라벨) | 라벨 중복 시 [기본급(2)] 비활성 — colKey 노출 폴백 |
[slug:colKey] | [퇴직 정산!일할환수액] (시트 이름!컬럼 라벨) | 이름은 현재 기간의 해당 slug 시트에서 조회 |
[$단가.야간수당] | [$단가.야간수당] | namespace.key 자체를 한글로 지을 수 있게 (파라미터 UI에서) |
[colKey@rowId] | B12 + 호버 시 행 이름 툴팁 | A1 표시: 열=layout 순서(A,B,…), 행=현재 표시 순서(1,2,…) |
- 편집기는 입력 시 이름→canonical, 표시 시 canonical→이름 양방향 치환. 치환 실패(삭제된 시트 등)는 원문 그대로 + 경고 배지.
- C-2의 "ID는 있되 보조": 참조 칩 호버 시
slug · uuid툴팁.
4. 컴포넌트 설계
4.1 참조 픽커 (F-1)
- 수식 편집 중
!없는 상태에서[또는 자동완성 트리거 → 1단계 시트 선택(현재 기간 시트 목록 + 검색), 2단계 컬럼 선택(대상 시트 columnLayout). - 삽입 시: 대상에 slug 있으면
[slug:colKey], 없으면 → slug 자동 부여 제안 ("연동하려면 이 시트에 고유 키가 필요합니다 — 자동 생성:custom_xxxx") 후[slug:colKey], 거부 시[uuid:colKey].- uuid 참조는 월 이월 시 끊기므로 픽커가 slug 경로로 유도하는 것이 핵심.
- 이름 중복 표기(열린 질문 → 제안): 같은 이름 시트가 여럿이면 픽커 항목에
폴더 · 'YY.M배지. 수식 표시에는 첫 항목만 이름, 중복이면이름(폴더). - 긴 이름: 표시 칩 12자 말줄임 + 전체는 툴팁.
4.2 = 인라인 셀 수식 (C-1)
- 셀 에디터에서 첫 글자
=→ 수식 모드 (구문 하이라이트 + 픽커 자동완성 + 참조 셀 하이라이트). - 저장:
SheetCell.kind=formula,formulaExpression. - 평가 경로 신설: resolver의 컬럼 수식 pass 뒤에 셀 수식 pass 추가 —
- 같은 행
[colKey], 행 고정[colKey@rowId], 시트 간[slug:colKey](employeeId 매칭), 상수[$…]지원. - 의존성: 셀 수식은 컬럼 수식 결과를 읽을 수 있음(단방향) — 역방향(컬럼 수식이 특정 셀 수식 참조)은 금지로 시작해 순환 문제를 차단.
- 결과는
computedValue캐시 +computedAt(기존 스키마 그대로).
- 같은 행
- 성능 가드: 셀 수식 수가 큰 시트(>1천)에서 평가 시간 로깅 → 필요 시 dirty-cell 부분 재계산.
4.3 상수 (H-2)
- 파라미터 저장소 사용자 UI: 설정 → "단가/상수 관리" — namespace 그룹핑, 값 + 적용 시작월(effectiveFrom 버전), 변경 이력.
- 수식에서
[$입력 → 상수 자동완성. - 평가: resolver가 시트 기간 기준 유효 버전을 해석. 커밋 시
appliedParameters에 스냅샷 — "그때 단가"가 레코드에 박제되는 기존 감사 모델과 일치. - 이행 경로: 엑셀 "구석 단가표" 관행은 ① 상수로 등록하거나 ② 별도 시트에 두고 F-1 참조 — 픽커가 두 경로 모두 제공.
4.4 위치 주소 표시 (C-2)
- 그리드에 열 헤더 A,B,C…, 행 번호 1,2,3… 상시 표시 (표시 순서 기준).
- 셀 수식 입력 중 셀 클릭 →
B12형태로 삽입되되 저장은[colKey@rowId]— 정렬/이동 후에도 참조 대상 불변, 표시 주소만 재계산. - 이 선택의 의미: 엑셀과 달리 "주소가 아니라 대상"을 참조 — 행 이동 시 수식이 따라간다. 문서·온보딩에 명시 필요.
5. 단계별 로드맵
| Phase | 내용 | 푸는 피드백 | 크기 |
|---|---|---|---|
| R1 | 참조 픽커 + 이름 표시 치환 (컬럼 수식 편집기부터) + slug 자동 부여 — 구현됨 (2026-07-06): apps/hr-system/src/sheets/formula-refs.ts (치환 모듈, 21 unit tests) + header-formula/columns 슬라이스 배선 + sheetView.ensureSlug API. 기존 픽커(트리·xref·헤더클릭)가 표시 문법을 삽입하고 커밋 시 canonical 로 변환, uuid→slug 업그레이드 포함 | F-1, D-1, C-2(이름) | 중 |
| R2 | 상수: 파라미터 저장소 UI + [$…] 문법 + 자동완성 — 구현됨 (2026-07-06): resolver 가 수식의 [$namespace.key] 를 기간 유효 버전(월말 기준 effectiveFrom)으로 해석해 행 values 에 주입 (resolver.ts 상수 pre-scan). 팝오버 더보기에 💲 상수 픽커(파라미터 카탈로그 lazy-load) + 참조 확인 패널 상수 검증. 관리 UI 는 기존 admin 패널 → 파라미터 탭 재사용 (신규 UI 불필요했음) | H-2 | 중 |
| R3 | = 셀 수식: 평가 pass + 에디터 + [colKey@rowId] — MVP 구현됨 (2026-07-06): resolver 에 셀 수식 pass 신설(컬럼 수식 결과·크로스시트·상수 참조 가능, 셀↔셀 참조는 미지원 문서화), pre-scan 이 셀 표현식도 포함(DISTINCT 쿼리 1회). 셀 에디터 = 감지 → kind=formula 저장(표시형 참조 canonicalize + ensureSlug), 재편집 시 표시형 수식 시딩(cellMeta). amount 에디터 type=number → text+inputmode=decimal 전환(수식 입력 차단 버그 해결). [colKey@rowId] 행 고정 참조는 R4(A1 클릭 삽입)와 함께 | C-1 | 상 |
| R4 | A1 위치 표시 (헤더/행번호 + 클릭 삽입) — 구현됨 (2026-07-06): 행 고정 참조 [colKey@rowId] (resolver 후처리 pass — 전 행 resolve 후 @참조 셀 수식 평가, 대상 행 값 정규화 주입). 표시 문법은 [라벨@행번호] (행번호 = # 컬럼의 현재 표시 순서; 저장은 rowId 앵커라 행 이동에도 참조 유지). 셀 수식 편집 중(=) 다른 셀 클릭 → 참조 삽입 (같은 행 [라벨] / 다른 행 [라벨@N], capture-phase mousedown 으로 blur-커밋 차단). 열 문자(A,B,C) 헤더 표시는 [라벨@N] 문법이 더 읽기 좋아 보류 | C-2(위치) | 중 |
| — | (보류) 행 기준 수식·합계 행 | C-3 | 추후 |
R1이 최소·최대가치: 엔진이 이미 있으므로 픽커+치환만으로 "시트 연동이 된다"가 성립한다.
6. 열린 질문 (결정 필요)
- 비-직원 시트 매칭: 시트 간 참조의 행 매칭이 employeeId 고정인데,
rowBasis=free시트끼리는? → R1은 employee 시트로 한정, free 시트는 R3의@rowId고정 참조로 커버하는 안. - slug 자동 부여 시 네이밍/충돌 규칙 (
custom_prefix + 난수? 이름 로마자화?). - 상수 namespace의 권한 — 편집을 PAYROLL_ADMIN으로 제한할지.
- 셀 수식과 커밋: 셀 수식 결과도 커밋 대상 컬럼이면 레코드로 나가는지 (제안: 예 — 값 출처 무관하게 컬럼의 commit 태그를 따름).
- A1 표시에서 고정(frozen) 식별 컬럼도 A,B를 소모하는지 (제안: 식별 제외, 데이터 컬럼부터 A).