프로젝트
약 9분 분량 학습 프로젝트/결제 시스템

제재 명단에 한글이 한 건도 없어서 한국인을 한 명도 못 보고 있었습니다

PaymentAML제재 스크리닝이름 매칭결제 시스템
목차

개요

정산금을 판매자 계좌로 보내기 전에 그 판매자가 제재 대상인지 명단과 대조하는 장치를 만들어
실제 지급 경로에 이었습니다. 실제 UN 제재 명단을 받아 보니 제가 쓴 파서가 세 군데 틀려
있었고, 무엇보다 그 명단에 한글이 한 건도 없었습니다. 한국 관련 79건이 전부 로마자라,
한글로 들어오는 판매자 이름을 그대로 맞대면 한국인 제재 대상을 한 명도 못 봅니다.
통과율 100%가 안전해 보이는 상태였습니다.

제재 스크리닝은 은행과 결제사가 돈을 내보내기 전에 상대가 제재 명단에 올라 있는지 확인하는
절차입니다. UN과 미국 OFAC, EU가 각각 명단을 내고, 이름이 비슷하기만 해도 일단 사람에게
보내 확인합니다. 놓치면 제재 위반이고, 과하게 잡으면 멀쩡한 지급이 멈춥니다.

이 글에 적는 것은 넷입니다. 판정기를 만들어 놓고 아무 데서도 부르지 않고 있던 것,
손으로 만든 시험용 데이터가 제 가정대로 생겨서 파서의 버그 셋을 통째로 가리고 있던 것,
“문자열 거리로는 못 가른다”고 적어 둔 결론이 틀렸던 것, 그리고 한글 이름을 로마자 명단과
맞대는 방법입니다.

측정은 전부 실제 UN 통합 명단(2026년 9월 기준 1,011건)으로 했습니다.


1. 클래스는 있는데 아무 데서도 안 불리고 있었습니다

이름 매칭 점수를 내는 클래스와 판정 로직을 먼저 만들었습니다. 그리고 그것이 실제로는
어디에도 붙어 있지 않았습니다.

  • screen() 을 부르는 곳이 테스트뿐이었습니다.
  • 명단을 가져오는 구현체가 없어 조회 결과가 항상 비었고, 그래서 무엇을 넣어도 판정이
    “확실하지 않음”으로 나왔습니다.
  • 정산은 판매자의 심사 상태를 보지 않았습니다.
  • 판매자 테이블은 마이그레이션으로 만들어 뒀는데 그것을 읽는 코드가 없었습니다.

테스트는 초록불이었습니다. 판정 로직 자체는 맞게 돌고 있었으니까요. “만들었다”와 “돌고
있다”는 다른 말인데, 테스트가 앞의 것만 확인하고 있었습니다.

네 자리를 이었습니다.

명단을 하루 한 번 받아 옵니다. 못 받으면 직전에 받아 둔 판을 그대로 씁니다. 여기서
비우는 쪽을 고르면 모든 판매자가 “확실하지 않음”이 되어 지급이 통째로 멈춥니다. 명단을
못 받은 것과 명단에 있는 것은 전혀 다른 사건인데, 비워 두면 둘이 같아집니다.

판정마다 한 행을 남기고 대조한 명단의 판을 함께 적습니다. 나중에 문제가 되면 “그때는
통과였고, 대조한 명단은 이 판이었다”를 대야 합니다. 지금 명단으로 다시 돌려서는 그때를
재현할 수 없습니다.

정산은 판매자를 직접 읽지 않습니다. 지급 직전에 게이트에 물어 예/아니오와 이유만
받습니다. 정산이 판매자 테이블을 직접 읽으면 심사 규칙까지 정산 쪽으로 새어 나옵니다.

집계는 하되 돈만 막습니다. 보류 상태를 따로 뒀습니다. 집계까지 건너뛰면 나중에 심사가
풀렸을 때 그 날짜의 매출이 영영 안 잡힙니다. 집계 키는 이 프로젝트에서 이미 한 번
조용히 틀려 지급이 통째로 빠진 적이 있는 자리라 조심해서 갔습니다.


2. 제가 만든 시험 데이터가 제 가정대로 생겨 있었습니다

파서를 쓰고, 시험용 XML을 손으로 만들어 검사했습니다. 전부 통과했습니다.

실제 UN 명단을 받아 보니 세 군데가 틀려 있었습니다.

생년월일을 한 번도 비교한 적이 없었습니다. 명단이 날짜의 정확도를 표시해 주는데,
“정확함”으로 표시된 751건 중에서 연월일이 온전한 것이 0건이었습니다. 263건은 연도만
줍니다. 저는 연월일이 다 있어야 비교하도록 짰으니, 그 코드는 한 번도 실행되지
않았습니다.
겹치는 자리까지만 비교하도록 고쳐서 192건이 비교 대상으로 돌아왔습니다.

국적이 한 번도 맞은 적이 없었습니다. 저는 KP 같은 두 글자 코드를 들고 있는데 명단은
Democratic Republic of the Congo 처럼 이름 전체를 줍니다. 그대로 맞대면 항상 불일치가
나오고, 불일치는 점수를 깎으니 맞는 사람의 점수를 깎고 있었습니다. 이름을 코드로
옮기는 표를 만들어 634건 전부를 알아보게 했습니다.

값이 한 겹 더 싸여 있었습니다. 태그 안에 값이 바로 있는 줄 알았는데 <VALUE> 로 한 번
더 감싸여 오는 자리가 있었습니다. 나라 49건을 못 읽고 있었습니다.

셋 다 제가 만든 XML이 제 가정대로 생겨서 안 보였습니다. 시험 데이터를 스스로 만들면
가정이 틀렸을 때 그것을 검사할 방법이 없습니다. 그래서 실제 명단을 받아 도는 검사를 따로
두고, 파싱한 건수와 날짜·국적이 실제로 채워지는 비율을 확인하게 했습니다.

여기서 하나 더 걸렸습니다. 명단을 매일 받아 오는 스케줄러를 스위치 없이 붙였다가 이
저장소의 규칙 테스트에 걸렸습니다. “환경 스위치 없이 항상 도는 배치가 없어야 한다”는 규칙을
제가 예전에 적어 두고 똑같이 밟았습니다.


3. “문자열 거리의 한계”라고 적은 것이 틀렸습니다

몇 점부터 사람에게 보낼지 정하려고 표본을 만들었는데 두 사례가 같은 점수로 나왔습니다.

같은 사람 다른 로마자 Kim Cheol Su ↔ Kim Chul Soo 67점
다른 사람 비슷한 이름 김철수 ↔ 김철순 67점

동점입니다. 선을 어디에 그어도 둘이 같은 쪽에 남습니다. 그때 “이 점수는 자동 통과에 쓸 수
없다”고 적고 사람 검토로 보냈고, 결론을 “문자열 거리의 한계”라고 적었습니다.

실무 자료를 확인하니 제재 스크리닝은 세 층으로 합니다.

  1. 정확 일치: 이름과 별칭에 더해 생년월일, 국적, 여권·문서번호까지 맞댑니다.
  2. 퍼지 매칭: 편집거리, 음역, 발음으로 비슷한 것을 찾습니다.
  3. 맥락 판별: 2층이 올린 후보를 2차 식별자로 좁혀 사람이 볼 것을 줄입니다.

경보의 90% 이상이 오탐이라 3층이 실무의 핵심인데, 제가 만든 것은 2층까지였습니다.
명단 항목이 이름과 프로그램과 국가만 들고 있어서 좁힐 재료 자체가 없었습니다. 거리 계산은
그대로 두고 볼 것을 늘렸습니다.

사례이름만생년월일·국적을 더한 뒤
같은 사람, 다른 로마자67점82점
다른 사람, 비슷한 이름67점12점
같은 사람, 띄어쓰기만 다름83점98점
흔한 이름의 남남(Lee Min HoLee Min Ho)100점45점

임계 80 기준으로 오판이 2/4에서 0/4가 됐습니다. 마지막 줄이 이 작업의 값입니다.
철자가 완전히 같으니 이름만으로는 만점이고, 선을 어디에 그어도 걸립니다. 생년월일과
국적이 달라서 떨어집니다. 흔한 이름일수록 이름 매칭이 쓸모없어지는데, 정작 흔한 이름이
대부분입니다.

두 가지를 규칙으로 박았습니다.

모르는 것은 점수를 건드리지 않습니다. 생년월일을 우리가 모르거나 명단이 주지 않으면
이름 점수를 그대로 씁니다. 모르는 것을 “안 맞았다”로 읽으면 제재 대상이 조용히
통과합니다.

깎는 폭을 올리는 폭보다 크게 뒀습니다. 생년월일 불일치는 40점을 깎고 일치는 10점을
올립니다. 맞다는 증거보다 아니라는 증거가 셉니다. 생년월일이 다르면 그건 대체로 다른
사람이지만, 같다고 해서 같은 사람이라고 하기에는 같은 날 태어난 사람이 너무 많습니다.


4. 명단에 한글이 한 건도 없었습니다

여기까지 하고 실제 명단에서 한국 관련 항목을 세어 봤습니다.

명단 전체 1,011건
한국 관련 79건
한글로 적힌 것 0건

79건이 전부 RI JE-SON, PAEK CHANG-HO, CHOE CHUN-SIK 같은 로마자입니다. 그것도
북한식 표기라, 같은 성을 남한에서는 Lee, Park, Choi로 씁니다.

우리 판매자는 사업자등록 정보로 들어오니 대표자 이름이 한글입니다. 한글과 로마자를 그대로
맞대면 점수가 0에 가깝게 나오고, 전원이 통과합니다.
앞에서 임계를 못 골라 막혔던 것도
사실 이 자리였습니다. 재던 대상이 애초에 맞댈 수 없는 두 문자였습니다.

한글 이름을 쓸 법한 로마자 표기로 펼쳐서 대조하기로 했습니다. 김을 KIMGIM으로,
철을 CHUL·CHEOL·CHOL로 펼치는 식입니다.

여기서 한 가지를 정했습니다. 점수를 올리는 데 쓰지 않고 정확히 맞을 때만 봅니다.
점수로 재 봤더니 김철수가 67점에서 100점이 되는 동시에 김철순도 92점이 됐습니다.
후보를 늘린 만큼 남에게도 잘 걸립니다. 그래서 후보 중 하나가 명단의 표기와 완전히 같을 때만
100점을 주고, 아니면 원래 점수를 그대로 씁니다. 앞 절의 “모르는 것은 점수를 건드리지
않는다”와 같은 규칙입니다.

명단에 실제로 있는 이름으로 재서 8건 중 8건이 걸렸습니다.

윤호진 ↔ YUN HO-JIN 이제선 ↔ RI JE-SON
백창호 ↔ PAEK CHANG-HO 한유로 ↔ HAN YU-RO
김광일 ↔ KIM KWANG-IL 최준식 ↔ CHOE CHUN-SIK
장범수 ↔ JANG BOM SU 현광일 ↔ HYON KWANG IL

만들면서 세 번 틀렸습니다.

종성 표를 한 칸 밀려 적었습니다. 의 받침 ㅇ이 T로 나와서 PAK CHANG HO가 안
걸렸습니다. 자모 인덱스를 찍어 보고서야 찾았습니다.

후보 개수 상한을 48로 낮게 잡아 진짜 대상을 잘라내고 있었습니다. 후보를 많이 만들면
남에게 걸린다고 걱정했는데, 그건 점수로 재던 때의 걱정입니다. 정확 일치만 보기로 한 뒤에는
후보가 많을수록 좋습니다.

검사를 손으로 지은 이름으로 짜서 1/4이 나왔습니다. 매처를 의심했는데, 제가 지어낸
사람들이 명단에 없어서 나온 수였습니다. 없는 사람을 못 찾은 것은 실패가 아닙니다.
명단에 실제로 있는 이름으로 다시 짜서 8/8을 얻었습니다.


5. 같은 성을 여섯 가지로 씁니다

후보를 펼쳐도 사람이 실제로 쓰는 표기를 다 담을 수는 없습니다. 한국 이름 스크리닝을 다룬
실무 자료가 어려운 자리를 그대로 적어 두고 있었습니다.

  • 한국인 성씨의 74%가 네 글자 미만입니다. 퍼지 매칭이 보통 네 글자 이상에서 도는데,
    성씨가 그 밑입니다.
  • 한국인의 절반이 성씨 다섯 개를 공유합니다. 성씨만으로 좁히면 오탐이 폭발합니다.
  • Park, Bak, Pak, Bahk, Pahk, Back여섯 다 맞는 표기입니다. 잘못 쓴 것이 하나도 없습니다.
  • 명단마다 성이 앞에 오기도 하고 뒤에 오기도 합니다.

업계는 하나로 안 되니 정확 일치와 음성학과 편집거리를 병렬로 돌립니다. OFAC 자신의
검색 도구도 Jaro-Winkler와 Soundex를 같이 씁니다. 제가 만든 것은 정확 일치까지였습니다.

그래서 소리를 접는 키를 만들었습니다. 여섯 표기가 하나로 모입니다.

Park Bak Pak Bahk Pahk Back → PAK

Soundex를 그대로 쓰면 안 됐습니다. Soundex는 영어 이름용이라 첫 글자를 그대로
남기는데
, 한국 이름이 갈리는 자리가 정확히 첫 글자입니다. ParkBakP620
B200으로 아예 다른 키가 됩니다. 그래서 한국어 로마자에서 실제로 섞이는 짝(ㄱ↔ㅋ, ㅂ↔ㅍ,
ㅈ↔ㅊ, ㄹ↔ㄴ)만 접는 키를 따로 만들었습니다.

여기서도 두 번 틀렸습니다. ParkR을 ㄹ로 읽어 접었더니 PANK가 되어 Bak
갈렸습니다. 모음 뒤의 R은 영어식 표기라 소리로 세면 안 됐습니다. Yi의 첫머리
Y도 소리로 세는 바람에 Lee와 갈렸습니다.

두 글자 성과 이름 순서도 같이 다뤘습니다. 두 글자 성을 모르면 남궁철수를 “남”과 “궁철수”로
자릅니다.
성이 뒤에 오는 표기도 후보로 만듭니다.


6. 소리가 같다고 확정하지는 않습니다

소리 키가 맞으면 사람에게 보내는 임계까지만 올리고, 일치로 확정하지는 않습니다.

소리를 접을수록 남남도 같은 키를 받습니다. 한국인의 절반이 성씨 다섯 개를 공유하는데
그 다섯 개를 소리로 또 접으면, 같은 키를 받는 사람이 인구의 상당수가 됩니다.
이 키는 사람에게 보낼 이유로 쓰고, 일치의 근거로는 쓰지 않습니다.

정리하면 세 갈래입니다.

신호무엇을 하나
로마자 후보가 정확히 맞음100점. 우연으로 보기 어렵습니다
소리가 같음사람에게 보내는 임계까지만 올립니다
편집거리만 가까움원래 점수 그대로 둡니다

여기서 못 하는 것도 적어 둡니다. PEP(정치적 주요 인물) 명단은 넣지 않았습니다. 정부가
배포하는 공식 명단이 없고, 공개 데이터는 상업 이용에 별도 라이선스가 필요합니다. 붙일 자리만
열어 두고 비워 뒀습니다. 국내 금융위 고시도 기계가 읽을 수 있는 형태의 공개 배포를 찾지
못해 쓰지 않았습니다.

그리고 이 글의 수치는 UN 통합 명단 하나로 잰 값입니다. OFAC과 EU 명단은 항목의 모양이
달라서, 붙이면 2절의 파서 버그와 같은 종류가 또 나올 것으로 봅니다. 실제 데이터를 받아
보기 전에는 제 가정이 맞는지 알 수 없다
는 것이 이번에 가장 크게 배운 자리입니다.


참고

프로필 사진
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글

댓글 수정/삭제는 GitHub Discussions에서 가능합니다.