본문으로 건너뛰기

채권 계산 엔진 — 법정변제충당

기반 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종 프리셋:

  1. 청구취지형: "피고는 원고에게 금 49,520,000원 및 이에 대한 2026.2.15.부터 다 갚는 날까지 연 12% 의 비율로 계산한 돈을 지급하라"
  2. 준비서면 계산표형: 마크다운 표 (날짜·원금·이자·변제·잔액 7열)
  3. 엑셀 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.ts 10+ 케이스
  • fast-check property test (불변식 검증: "총 변제액 ≤ 총 채권액 + 지연이자")

테스트 케이스 (Phase 0 필수 10건)

  1. 단순 1건 대여 + 변제 1건, 변제금이 이자만 넘고 원본 못 닿음 (479조 순서)
  2. 윤년 2/29 대여·이듬해 3/1 변제 (일수 366 vs 365)
  3. 법정이율 변경일 경계 (2019.5.31 ↔ 6.1) 걸친 기간
  4. 연복리 vs 단리 교차검증
  5. 방식 A (채무 N개, 이행기 동일 → 안분)
  6. 방식 B (음수 잔액 방지)
  7. 변제금이 총 채무 초과 (과납 excess)
  8. 충당 지정 appropriationOverride: "principal" (법정 배제)
  9. asOf 가 마지막 변제 이후 (지연이자 계속)
  10. 이자율 0% + 지연손해금만

추가 fast-check property test: "breakdown 합 = outstanding" 불변식.

Go/No-Go 관문 (Phase 0 종료)

  • 대여금 계산 정확도 99.5%
  • 내부 변호사 3명 dogfood 주 3회+
  • 단위 테스트 10/10 통과 + property test 불변식 위반 0건
  • 별지 계산서 PDF 법원 제출 샘플 변호사 승인

관련 기능

관련 ADR