채권 계산 엔진 — 법정변제충당
기반 ADR: 0003 Pack 1 채권 계산 (Amended) 실전 사용법: 회수 탭 (대여금) 사용 가이드 — 변호사·사무장 대상 5분 튜토리얼
개요
반복 대여·변제가 섞인 사건의 원금·이자·지연손해금을 민법 제477·479조 법정변제충당 규칙으로 자동 계산하는 순수 TypeScript 결정론 엔진. AI 호출 없음.
왜 필요한가
- 대여금·공사대금 사건의 70%+ 가 중간 변제 있는 케이스
- 엑셀 수기 계산: 개인 사무소 1건당 반나절
하루, 대형 사건 23일 - 계산 오류 → 일부 기각 → 변호사 과실 배상 (연 1~2건 발생)
- 국내 리걸테크에 이 계산을 깔끔히 제공하는 제품 없음 (moat)
핵심 원칙
- AI 금지: 숫자는 결정론 함수로만 (할루시네이션 차단)
- 정수 원 단위:
bigint또는 정수로 저장, 원 미만 절사 (재판부 관행) - KST 일할: 초일불산입·말일산입.
dayjs없이 순수 함수 - 법정이율 이력: 코드 상수 테이블 (민사 5%·소촉법 12% (2019.6.1~)·상사 6%·이자제한법 20%)
- 스냅샷 불변:
engineVersion태그로 과거 재현 보장
사용자 스토리
변호사
As a 10년차 민사 변호사 I want 대여·변제 내역을 입력하면 법정충당 원금·이자가 자동 계산되기 So that 엑셀 수기 오류를 피하고 법원이 요구하는 별지 계산서를 30분 안에 생성한다.
사무장
As a 법률사무소 사무장 I want 은행 거래내역 CSV 를 붙여넣으면 변제 내역이 자동 채워지기 So that 의뢰인이 가져온 자료를 타이핑하지 않아도 된다.
기능 상세
1. 계산 방식
A방식 (기본) — 개별 채권 법정충당
- 각 대여 건별로 독립 이자 계산
- 변제금을 충당 순서 (비용 → 이자 → 원본) 로 매핑
- 재판부 표준 포맷 (소장 청구취지·판결 주문과 동일 구조)
B방식 (Phase 1 도입) — 잔액 정산
- 계속적 거래·당좌대월 계약 명시 시
- 매 거래일마다 누적 잔액에 이자
- UI: "계속적 거래 약정 증빙 업로드 유도" + 증빙 없을 시 A 폴백 권고 경고
2. 입력
대여 (Loan):
- 원금 (정수 원)
- 대여일 (Timestamp KST)
- 이행기 (선택)
- 이자율 (약정 or 법정)
- 계약 유형 (A/B 방식)
- 증거 링크 (차용증·계약서)
변제 (Repayment):
- 금액
- 변제일
- 대상 채권 (선택, 없으면 법정충당 자동)
- 지정충당 오버라이드 (당사자 지정: 원본 먼저·이자 먼저)
- 출처 (계좌·현금·상계·공탁·대물변제)
- 증거 링크
이자율 이력 (InterestRate):
- 이자율 (basis points 정수, 예: 1200 = 12%)
- 기간 (시작·종료)
- 근거 (
"agreed"|"statutory"|"delay") - 복리 방식 (
"simple"|"monthly"|"annual")
3. 계산 흐름
입력: { loans, repayments, rates, asOf, method }
↓
1. 법정이율 구간 분할 (기간 경계일 자동 분기)
↓
2. 각 대여 건별 이자 계산 (정수 원, Math.floor 절사)
↓
3. 변제금 충당 순서 적용:
- 지정충당 (제476조) 있으면 그것 우선
- 없으면 법정충당 (제477·479조)
① 비용 → 이자 → 원본 (479조)
② 여러 채무 간: 이행기 도래 → 담보 없는 것 → 변제이익 큰 것 → 이행기 빠른 것 → 안분
↓
4. 기준일 (asOf) 까지 지연손해금 누적
↓
출력: SettlementResult {
totalPrincipal, totalInterest, totalDelay,
totalRepaid, outstanding,
breakdown: StepRow[] // 각 변제 → 원금/이자/지연 분해
}
4. 결과 표시 (3뷰)
요약 카드:
- 큰 숫자 (원금잔액·이자합계·지연손해금·합계)
- 기준일 표시
- 시나리오 B 대비 diff 뱃지 (Phase 1)
충당표:
- 각 변제 행 → 펼쳐서 "원금 X / 이자 Y / 지연 Z" 내역
- 엑셀 계산표 그대로 재현 (준비서면 첨부용)
- 자연어 수식 병기: "원금 50,000,000 × 연 5% × 92일/365일 = 629,452원"
타임라인:
- 세로 타임라인 + 연결선
- 호버 툴팁
- 인쇄·PDF 친화 (산키 다이어그램 거부 — 법원 제출 부적합)
5. 청구취지 템플릿 (결정론, AI weight 0)
3종 프리셋:
- 청구취지형: "피고는 원고에게 금 49,520,000원 및 이에 대한 2026.2.15.부터 다 갚는 날까지 연 12% 의 비율로 계산한 돈을 지급하라"
- 준비서면 계산표형: 마크다운 표 (날짜·원금·이자·변제·잔액 7열)
- 엑셀 CSV 다운로드
6. 별지 계산서 PDF
- A4 세로 · 법원 제출 형식
- 7열: 거래일·적요·차변·대변·잔액·이자 기산일·적용 이율 근거
- 수기 메모 여백 + 검토 도장란 (P9 대형 로펌 요구 수용)
- 재판부별 관행 차이 대응 (서울중앙 표준 · 부산·광주 서명란)
7. 스냅샷 시스템
구조:
- 이름 ("소장 첨부본 2026-02-10")
- 기산일 (asOf)
- 법정/지정 충당 플래그
engineVersion태그- 내부:
breakdown: StepRow[]불변 배열 - 부모 스냅샷 참조 (Phase 2 판결 후 재계산 이어받기)
frozen: true동결 플래그
동작:
- 사용자가 "스냅샷 저장" 클릭
- 현재 입력 상태로 계산 실행
- 결과 불변 저장
- 이후 원장 수정해도 기존 스냅샷 변함 없음
- 새 스냅샷 생성 시 diff 요약 ("변제 2건 추가, 원금 330만원 감액")
다이얼로그:
settlementSnapshot.frozen === true대상 변경 시도 →SETTLEMENT_SNAPSHOT_STALE에러- 무제한 저장 (ADR 0002 전 기능 무료)
엣지 케이스
| 케이스 | 처리 |
|---|---|
| 윤년 2/29 대여 | 365/366 일할 분기 |
| 법정이율 변경일(2019.6.1) 걸친 기간 | segment 분할 |
| 변제금 > 총 채무 | excess 필드에 초과액 |
| 이자율 0% | 지연손해금만 계산 |
asOf 가 마지막 변제 이후 | 지연이자 계속 발생 |
| 복리 vs 단리 | interestRate.compounding 필드 분기 |
| 이자 0, 지연손해금만 (상인 간) | 상사 6% 또는 소촉법 12% 자동 |
지정충당 appropriationOverride = "principal" | 법정충당 배제, 원본 먼저 |
| 다수 채권 이행기 동일 | 안분 비례 |
| 공탁 이벤트 (Phase 1) | 공탁일 = 변제일 간주 (민법 487조) |
데이터 모델
상세는 data-model.md 참조.
cases/{caseId}/
loans/{loanId} loan 필드
repayments/{repaymentId} repayment 필드
interestRates/{rateId} interestRate 필드
settlements/{settlementId} settlementSnapshot 필드
API (Server Action)
위치: apps/web/app/(workspace)/cases/[caseId]/_actions/settlement-actions.ts
// 계산만 (스냅샷 저장 안 함)
export async function calculateSettlement(
caseId: string,
asOf?: Timestamp,
method?: "A" | "B"
): Promise<ActionResult<SettlementResult>>;
// 스냅샷 생성 (불변 저장)
export async function createSettlementSnapshot(
caseId: string,
params: { asOf, method, name, scenarioId? }
): Promise<ActionResult<{ snapshotId: string }>>;
// 스냅샷 조회
export async function getSettlementSnapshot(
caseId: string,
snapshotId: string
): Promise<ActionResult<SettlementSnapshot>>;
// 대여·변제·이자율 CRUD
export async function upsertLoan(...): Promise<ActionResult>;
export async function upsertRepayment(...): Promise<ActionResult>;
export async function upsertInterestRate(...): Promise<ActionResult>;
// 청구취지 생성 (결정론 템플릿, AI weight 0)
export async function generateClaimClause(
snapshotId: string,
preset: "claim" | "brief" | "csv"
): Promise<ActionResult<{ text: string }>>;
모든 mutating Server Action 은 requireCaseOwnership({ mutating: true }) 사용 (CLAUDE.md 원칙).
에러 코드
SETTLEMENT_INPUT_INVALID— Zod 검증 실패 (음수 원금·역순 날짜·중복 이자구간)SETTLEMENT_RATE_MISSING— 계산 구간에 적용 이자율 없음 (기본값 자동 주입 금지, 명시 실패)SETTLEMENT_SNAPSHOT_STALE— 동결 스냅샷 변경 시도
순수 엔진 위치
apps/web/app/(workspace)/cases/[caseId]/_lib/debt-settlement.ts
특징:
- Firebase 의존 0
AiProvider의존 0 (AI 호출 없음)- 단위 테스트
_lib/debt-settlement.test.ts10+ 케이스 fast-checkproperty test (불변식 검증: "총 변제액 ≤ 총 채권액 + 지연이자")
테스트 케이스 (Phase 0 필수 10건)
- 단순 1건 대여 + 변제 1건, 변제금이 이자만 넘고 원본 못 닿음 (479조 순서)
- 윤년 2/29 대여·이듬해 3/1 변제 (일수 366 vs 365)
- 법정이율 변경일 경계 (2019.5.31 ↔ 6.1) 걸친 기간
- 연복리 vs 단리 교차검증
- 방식 A (채무 N개, 이행기 동일 → 안분)
- 방식 B (음수 잔액 방지)
- 변제금이 총 채무 초과 (과납
excess) - 충당 지정
appropriationOverride: "principal"(법정 배제) asOf가 마지막 변제 이후 (지연이자 계속)- 이자율 0% + 지연손해금만
추가 fast-check property test: "breakdown 합 = outstanding" 불변식.
Go/No-Go 관문 (Phase 0 종료)
- 대여금 계산 정확도 99.5%
- 내부 변호사 3명 dogfood 주 3회+
- 단위 테스트 10/10 통과 + property test 불변식 위반 0건
- 별지 계산서 PDF 법원 제출 샘플 변호사 승인