RAG 연습 (9) — 키워드 매칭을 버리고 multi-tier (계정코드 + 거래처 마스터 +a) 를 채택하였음
RAG 연습 (8) 끝에서 “세무 검토자 노드가 키워드 매칭 수준이라 더 나가야 한다”고 적었습니다. 이번에 그걸 손봤습니다. 결론부터 말하면 거래 설명 텍스트를 보지 않고, 계정코드·금액·날짜·거래처 마스터를 같이 봅니다.
무엇이 문제였나
이전 세무 노드는 거래 설명(description)에 들어 있는 키워드로만 룰을 발화시켰습니다.
1
2
3
4
5
_TAX_RULES = [
("WH-001", ["컨설팅", "서비스", "라이선스"], "원천징수 검토"),
("VAT-001", ["광고", "마케팅"], "매입세액공제 확인"),
("CUT-001", ["말", "분기말", "12월"], "컷오프 리스크"),
]
이전 방식의 문제는 두 가지였습니다.
자유 텍스트에 의존했습니다. “IT 컨설팅”이라고 쓰면 WH-001이 잡히지만, 같은 거래를 누가 “외부 인력 투입”·”프로젝트 지원”이라고 쓰면 안 잡혔습니다. ERP 거래 설명은 담당자마다 입력 방식이 달라서 동일한 거래가 다른 키워드로 들어옵니다. 동일한 거래가 경우에 따라 다르게 검토된다는 문제입니다.
거래당 룰 하나만 발화했습니다(break). “해외 클라우드 비용”은 부가세 역발행 대상(WH-002)이면서 동시에 비거주자 원천징수 대상(VEN-001)입니다. 두 리스크는 성격이 달라 둘 다 검토해야 하는데 한쪽만 나오고 끝났습니다. 이 경우 코드를 수정해야 하는데 다음에서 소개하는 tier별 플래그 룰로 바꾸면 함께 해결됩니다.
3-tier 플래그 룰로 바꿨습니다
세 가지 기준을 순서대로 붙였습니다.
1
2
3
Tier 1 — 계정코드 + 금액
Tier 2 — 거래 날짜
Tier 3 — 거래처 마스터
각 tier는 독립적으로 발화합니다. 한 거래가 세 tier 모두에 걸리면 플래그도 셋이 붙습니다.
Tier 1: 계정코드 prefix + 금액 임계값
1
2
3
4
5
6
7
8
_ACCOUNT_RULES = [
("WH-001", {"521","522","523"}, Decimal("330000"),
"외부 용역비 52x 계정 33만 원 이상 — 원천징수 대상 확인"),
("VAT-001", {"531","532"}, Decimal("0"),
"광고·마케팅비 53x — 매입세액공제 요건 확인"),
("WH-002", {"561","562"}, Decimal("0"),
"클라우드·SaaS 56x — 해외 공급자 부가세 역발행 여부 확인"),
]
account_code.startswith(prefix)로 매칭합니다. 계정코드 5210이면 description에 “컨설팅”이 한 글자도 없어도 521 접두로 WH-001이 발화합니다.
원천징수에는 금액 조건이 같이 붙습니다. 소득세법 기준 33만 원이 3.3% 적용 최저선이라 그걸 임계값으로 잡았습니다.
Tier 2: 날짜 기반 컷오프
1
2
3
if day >= 25 and any(acct.startswith(p) for p in _CUTOFF_ACCOUNTS):
quarter_end = month in (3, 6, 9, 12)
detail = f"월말{'(분기말)' if quarter_end else ''} 거래 — 기간귀속 컷오프 검토"
description에 “분기말”이라는 단어가 없어도 거래 날짜가 25일 이후면 컷오프 리스크를 붙입니다. 분기말(3·6·9·12월)이면 별도 표시까지.
Tier 3: 거래처 마스터
1
2
3
4
5
6
7
8
9
VENDOR_MASTER = {
"V-AmazonKR": {"type": "overseas", "country": "US"},
"V-Unknown": {"type": "unregistered"},
}
_VENDOR_RULES = [
("VEN-001", "overseas", "비거주자 원천징수 22% + 조세조약 확인"),
("VEN-002", "unregistered", "적격증빙 미수취 — 매입세액공제 불인정 + 가산세"),
]
거래처 코드를 마스터 테이블에 조회해 속성(해외·미등록 등)을 가져옵니다. 지금은 하드코딩된 dict인데, 실제 ERP에선 거래처 테이블 조회로 바꾸면 됩니다.
처음 보는 용어 짧게 짚기
- 계정코드: 회계에서 거래를 분류하는 번호 체계. 외부 용역비는 521·광고비는 531·클라우드는 561 같은 식으로 계정 그룹별로 prefix가 정해져 있습니다.
- 역발행 부가세: 해외 공급자(예: AWS)에서 서비스를 받을 때 국내 매입자가 본인 명의로 세금계산서를 만들어 부가세를 내는 제도. 빠뜨리면 가산세.
- 매입세액공제: 매입할 때 낸 부가세를 환급·공제받는 절차. 적격증빙(세금계산서·신용카드매출전표 등)이 있어야 가능.
- 비거주자 원천징수: 해외 법인에 돈을 보낼 때 국내에서 세금을 떼고 송금. 조세조약이 적용되면 세율이 낮아집니다.
- 컷오프: 거래를 어느 회계 기간에 잡을지. 분기말 직전 거래가 다음 분기로 밀리면 분식의 신호라 따로 봅니다.
break를 없앤 이유
이전 코드는 한 거래가 첫 룰에 걸리면 break로 끝냈습니다. 이번에 제거했습니다.
T009 거래(거래처 V-AmazonKR, 계정 5610, 클라우드 사용료)를 예로 들면:
- Tier 1에서 WH-002 (56x 계정) 발화
- Tier 3에서 VEN-001 (해외 법인) 발화
WH-002는 부가세 역발행 여부, VEN-001은 원천징수 세율 조약 적용 여부라 검토 포인트가 다릅니다. 하나만 보고 나머지를 놓치면 실무에서 놓칩니다.
결과 비교
| 구분 | 이전 | 이번 |
|---|---|---|
| 트리거 기준 | 거래 설명 키워드 | 계정코드 + 금액 + 날짜 + 거래처 |
| 플래그 종류 | WH-001, VAT-001, CUT-001 | + WH-002, VEN-001, VEN-002 |
| 거래당 플래그 수 | 1개 (break) | 복수 가능 |
| 컷오프 발화 조건 | “말”·”분기말” 키워드 | day ≥ 25 |
| 해외 법인 탐지 | 없음 | VEN-001 |
| 미등록 거래처 탐지 | 없음 | VEN-002 |
| 18건 픽스처 기준 세무 플래그 | 8건 | 10건 |
플래그 분포는 이렇게 잡혔습니다.
1
2
3
4
5
6
WH-001 : T003, T004 — 외부용역비 52x 계정
VAT-001 : T005, T016 — 광고비 53x 계정
WH-002 : T009 — 클라우드 56x 계정
CUT-001 : T016, T017, T018 — 4월 25일 이후 거래
VEN-001 : T009 — V-AmazonKR (해외)
VEN-002 : T010 — V-Unknown (미등록)
T009와 T016이 각각 두 플래그를 받는 게 보입니다. break를 없애지 않았으면 T009의 VEN-001은 영영 안 나왔습니다.
새 플래그도 RAG 쿼리로 자동 변환
(8)편에서 만든 “플래그 → 검색 쿼리” 매핑에 새 항목을 추가했습니다.
1
2
3
"WH-002": "클라우드 SaaS 해외 공급자 부가세 역발행",
"VEN-001": "해외 법인 비거주자 원천징수 세율 기준",
"VEN-002": "적격증빙 미수취 가산세 법인세법",
VEN-001이 잡힌 거래에는 “해외 법인 비거주자 원천징수” 쿼리로 검색된 법령 청크가 자동으로 붙습니다.
다음 단계
거래처 마스터는 아직 하드코딩된 dict입니다. 실제 환경으로 옮기려면 세 가지가 필요합니다.
- 거래처 테이블 조인 (asyncpg 쿼리로 교체)
- 계정코드 계층을 설정 파라미터로 분리 (ERP마다 코드 체계가 다름)
- 금액 임계값을 법인 규모별로 분리 (중소·대기업 따라 원천징수 기준이 다름)
DB 연동과 함께 다음 글에서 다루겠습니다.
(클로드 코드의 도움을 받았습니다.)