LexQLexQ
패턴 목록으로
핀테크승인 워크플로우운영신뢰중급

우선순위 상호 배타 그룹으로 신용 신청 판정하기

신용 신청 하나를 승인·거부·수동 심사 중 하나로 가르고, 어느 규칙이 그 결정을 내렸는지까지 남기는 패턴.

Sanghyun Park·2026년 6월 24일16분 읽기15 min

문제

대출 기관은 신청 한 건마다 승인할지, 거부할지, 아니면 사람이 한 번 더 들여다볼지를 정합니다. 신용점수, 연 소득, DTI(부채 대비 소득 비율) 같은 신호 몇 개를 보고 정하는데, 이 신호를 어떤 결과로 연결할지 가르는 기준값이 곧 정책입니다. 기준값은 엔지니어링이 정하는 게 아닙니다. 리스크 부서가 정하고, 주기적으로 들여다보고, 자기 일정에 맞춰 바꿉니다.

이 판단은 차단 다음 단계입니다. KYC 등급별 이체 한도 집행은 "이 이체를 진행해도 되는가"만 가리면 됐습니다. 승인 심사는 다릅니다. 결과가 여러 개라, 그중 어디로 보낼지와 그렇게 정한 근거를 함께 답해야 합니다.

코드로 옮기면 기준값은 상수가 되고, 결과는 if 분기로 갈립니다. 문제는 신호끼리 겹친다는 점입니다. 점수는 높은데 부채 비율도 높은 신청자는 얼마든지 있고, 이 둘은 서로 반대쪽 결과를 가리킵니다. if-else에서는 맨 먼저 조건이 맞는 분기에서 결과가 정해지고 끝납니다. 그러니 어떤 검사를 위에 뒀느냐가 곧 판정을 가릅니다. 승인 검사를 부채 비율 검사보다 위에 두면, 점수 760에 부채 비율 55%인 신청자는 부채 비율 상한이 실행되기도 전에 승인되어 버립니다. 원래대로라면 이 상한에 걸러졌어야 할 신청인데도요. 검사 순서가 어느새 정책 노릇을 하고 있을 뿐, 그렇게 정하기로 하거나 적어 둔 사람은 아무도 없습니다.

대가는 하나 더 있는데, 이쪽은 한참 뒤에야 드러납니다. 신청이 거부되면 신청자는 그 사유를 들을 권리가 있고, 대출 기관은 몇 달이 지난 뒤에도 어느 규칙이 그 신청을 그렇게 판정했는지, 다른 규칙은 왜 밀렸는지 내놓을 수 있어야 합니다. 로그 한 줄을 뒤져 손으로 짜 맞춘 거부 사유로는, 기관이 그 결정을 책임지기 어렵습니다.

이 패턴이 풀려는 문제가 바로 이것입니다. 신청 하나하나를 승인·거부·수동 심사 중 정확히 한 곳으로 보내고, 몇 달이 지난 뒤에도 그 신청을 어떤 규칙이 어떤 이유로 그렇게 판정했고 다른 규칙은 왜 밀렸는지까지 답할 수 있게 만드는 것.

단순한 접근

처음에는 기준값을 검사 코드 바로 옆, 결정 서비스의 상수로 둡니다.

public class CreditDecisionService {

    // 기준값. 리스크 부서가 관리합니다.
    private static final int        SCORE_FLOOR = 580;
    private static final int        SCORE_PRIME = 720;
    private static final BigDecimal DTI_CEILING = new BigDecimal("0.50");
    private static final BigDecimal MIN_INCOME  = new BigDecimal("40000");

    public Decision adjudicate(Application app) {
        int        score  = app.getCreditScore();
        BigDecimal dti    = app.getDtiRatio();
        BigDecimal income = app.getAnnualIncomeUsd();

        // 이 검사들의 순서가 곧 정책입니다.
        // 하지만 그 순서를 정한 사람은 없습니다. 메서드가 그렇게 작성됐을 뿐입니다.
        if (score >= SCORE_PRIME && income.compareTo(MIN_INCOME) >= 0) {
            return Decision.APPROVE;
        }
        if (dti.compareTo(DTI_CEILING) > 0) {
            return Decision.DECLINE;
        }
        if (score < SCORE_FLOOR) {
            return Decision.DECLINE;
        }
        return Decision.REFER;
    }
}

개발자가 머릿속에 그린 신청에는 이대로도 잘 돌아갑니다. 이 코드의 결함은 조건식이 아니라 구조에 있습니다. 판정 순서와 기준값과 판정의 기록을 전부 서비스 코드가 떠안았고, 그 셋이 차례로 문제가 됩니다.

  • 검사 순서가 곧 정책인데, 그 정책이 눈에 보이지 않습니다. 승인 분기가 맨 위에 있으면 점수 760에 부채 비율 55%인 신청자는 APPROVE로 반환되고, 부채 비율 상한은 거치지도 않습니다. 부채 비율 검사를 위로 올리면 같은 신청자가 거부됩니다. 결과가 뒤집혔는데 빨갛게 변하는 테스트는 하나도 없습니다. 그 결과를 뒤집은 건, 아무도 "결정"이라고 부르지 않은 순서 바꾸기 한 번이었습니다.
  • 기준값을 바꾸려면 배포를 해야 합니다. 리스크는 적용 시점을 정하지만, 상수는 배포 일정에 실려 나갑니다. 신청자가 실제로 적용받는 기준값은 어느 배포가 먼저 나갔는지에 따라 갈리고, 기준값이 어떻게 바뀌어 왔는지는 서비스의 git 로그에 남습니다.
  • 거부가 나중에 찾아볼 기록을 남기지 않습니다. "이 신청은 왜 거부됐는가"는 기록이 됐다 해도 로그 한 줄입니다. "5월에 부채 비율 상한에 막힌 신청을 전부"는 로그를 파헤치는 작업이 되고, 손으로 짜 맞춰야 하는 거부 사유는 대출 기관이 믿고 쓸 수 없는 사유입니다.

패턴 정의

해결의 핵심은 구조에 있습니다. 결과마다 규칙 하나로 만들고, 결정을 내리는 규칙을 모두 하나의 EXCLUSIVE 상호 배타 그룹에 넣은 뒤, 둘 이상이 걸리면 규칙 우선순위가 그중 하나만 선택하게 합니다.

이 구조를 LexQ 어휘로 옮기면 fact, 규칙, 상호 배타 그룹으로 나뉩니다.

  • fact: 엔진이 읽는 입력값입니다. creditScore, annualIncomeUsd, dtiRatio.
  • 규칙: 결과마다 규칙 하나입니다. 조건과, 결정을 기록하는 SET_FACT 액션으로 이루어집니다. 결정은 범주값이라, 액션은 숫자를 바꾸는 대신 문자열을 적습니다.
  • 상호 배타 그룹(Mutex Group): 여러 결과 가운데 정확히 하나만 선택되도록 경쟁시키는 필드입니다.

KYC 차단과 갈리는 지점이 여기입니다. KYC에서는 계좌마다 인증 등급이 하나뿐이라, 이체 한 건에 걸리는 규칙은 많아야 하나입니다. 상호 배타 그룹이 필요 없습니다. 여기서는 신호가 겹칩니다. 부채 비율로 거부하는 규칙과 점수로 승인하는 규칙이 같은 신청자에게 동시에 걸릴 수 있습니다. 두 규칙이 함께 걸려 정반대 결정을 내리니, 무언가가 하나를 고르고 나머지에 무슨 일이 있었는지 남겨야 합니다. 그 무언가가 상호 배타 그룹입니다. 상호 배타 그룹 자체가 어떻게 동작하는지는 VIP 등급 할인 중첩 해소에서 다루고, 이 패턴은 그것을 세 갈래 결정에 가져다 씁니다.

결정 규칙은 모두 같은 mutexGroupcredit-decision을 답니다. mutexModeEXCLUSIVE라, 선택된 규칙의 액션만 실행됩니다. mutexStrategyHIGHEST_PRIORITY라서, 걸린 규칙 중 priority 숫자가 가장 작은 규칙이 선택됩니다.

priority는 규칙을 만들 때 정하는 값이 아닙니다. 버전 안에서 1..N으로 자동으로 매겨지는 순서이고, 순서 바꾸기(콘솔에서 드래그)로만 바뀝니다. 이 순번은 그룹마다 따로 매겨지지 않고 버전 전체에 하나로 이어집니다. 거부 규칙을 먼저 만들어 위에 올려 두면 가장 작은 priority를 받습니다. 거부와 승인이 함께 걸리면 거부가 선택되는데, 여신 심사에서는 이게 맞는 판단입니다. 상한을 넘는 부채 비율은 점수가 아무리 높아도 결격이니까요.

우선순위가 가장 높은 규칙은 부채 비율 거부입니다.

{
  "name": "Decline — DTI above 0.50",
  "condition": {
    "type": "GROUP",
    "operator": "AND",
    "children": [
      {
        "type": "SINGLE",
        "field": "dtiRatio",
        "operator": "GREATER_THAN",
        "value": 0.50,
        "valueType": "NUMBER"
      }
    ]
  },
  "actions": [
    {
      "type": "SET_FACT",
      "parameters": { "targetVar": "creditDecision", "value": "DECLINED" }
    },
    {
      "type": "SET_FACT",
      "parameters": { "targetVar": "decisionReason", "value": "Debt-to-income ratio above 0.50" }
    }
  ],
  "mutexGroup": "credit-decision",
  "mutexMode": "EXCLUSIVE",
  "mutexStrategy": "HIGHEST_PRIORITY",
  "mutexLimit": 1,
  "isEnabled": true
}

점수 하한 거부도 모양은 같습니다. creditScore580 미만이라는 조건 하나로, DECLINED와 사유 Score below 580을 적습니다. 두 번째에 둡니다.

승인 규칙은 신호 두 개를 함께 읽습니다.

{
  "name": "Approve — prime",
  "condition": {
    "type": "GROUP",
    "operator": "AND",
    "children": [
      {
        "type": "SINGLE",
        "field": "creditScore",
        "operator": "GREATER_THAN_OR_EQUAL",
        "value": 720,
        "valueType": "NUMBER"
      },
      {
        "type": "SINGLE",
        "field": "annualIncomeUsd",
        "operator": "GREATER_THAN_OR_EQUAL",
        "value": 40000,
        "valueType": "NUMBER"
      }
    ]
  },
  "actions": [
    {
      "type": "SET_FACT",
      "parameters": { "targetVar": "creditDecision", "value": "APPROVED" }
    },
    {
      "type": "SET_FACT",
      "parameters": { "targetVar": "decisionReason", "value": "Score 720+ with income 40000+" }
    }
  ],
  "mutexGroup": "credit-decision",
  "mutexMode": "EXCLUSIVE",
  "mutexStrategy": "HIGHEST_PRIORITY",
  "mutexLimit": 1,
  "isEnabled": true
}

수동 심사 규칙은 맨 뒤에 오고, 점수 구간을 빈틈없이 메웁니다. 준우량 구간과, 승인 규칙이 비워 두는 한 가지 경우(점수는 우량인데 소득이 하한에 못 미치는 신청)를 같이 잡습니다. 조건은 AND 그룹 두 개를 OR로 묶은 모양입니다.

{
  "type": "GROUP",
  "operator": "OR",
  "children": [
    {
      "type": "GROUP",
      "operator": "AND",
      "children": [
        {
          "type": "SINGLE",
          "field": "creditScore",
          "operator": "GREATER_THAN_OR_EQUAL",
          "value": 580,
          "valueType": "NUMBER"
        },
        {
          "type": "SINGLE",
          "field": "creditScore",
          "operator": "LESS_THAN",
          "value": 720,
          "valueType": "NUMBER"
        }
      ]
    },
    {
      "type": "GROUP",
      "operator": "AND",
      "children": [
        {
          "type": "SINGLE",
          "field": "creditScore",
          "operator": "GREATER_THAN_OR_EQUAL",
          "value": 720,
          "valueType": "NUMBER"
        },
        {
          "type": "SINGLE",
          "field": "annualIncomeUsd",
          "operator": "LESS_THAN",
          "value": 40000,
          "valueType": "NUMBER"
        }
      ]
    }
  ]
}

이 네 규칙이면 fact가 갖춰진 신청자는 누구든 규칙 하나 이상에 걸립니다. 상한을 넘는 부채 비율은 거부에 걸리고, 그 아래에서는 하한 미만 점수가 점수 거부에, 준우량 구간과 소득이 하한에 못 미치는 우량 신청이 수동 심사에, 소득이 충분한 우량 점수가 승인에 걸립니다. "신청 한 건에 결정 하나"라는 제약은 이제 if 블록 네 개가 우연히 놓인 순서가 아니라 mutexMode라는 필드에 적혀 있습니다.

EXCLUSIVE 상호 배타 그룹의 규칙 네 개. 거부 규칙 두 개가 승인과 수동 심사 위에 정렬됨

이제 기준값은 데이터입니다. 소득 하한을 바꾸는 일은 초안 버전에서 값 하나를 고치는 일이고, "5월 14일에 우량 점수 기준이 얼마였는가"에는 버전 이력이 답합니다.

변경 영향 시뮬레이션 전략

리스크가 부채 비율 상한을 0.50에서 0.45로 내린다고 합시다. 이 값이 실제 신청에 닿기 전에, 변경이 미칠 파장을 미리 잴 수 있습니다. 결정이 바뀌는 신청이 얼마나 되고, 각 신청이 어느 쪽으로 바뀌는지입니다. 운영 중인 버전을 복제해 기준값 하나만 고치면, 아직 트래픽을 받지 않는 대상 버전이 됩니다.

묻는 것은 승인과 거부와 수동 심사의 건수 배분이지 어떤 금액의 합이 아니라서, 분석 대상 fact 항목은 비워 둡니다. 규칙별 통계 포함(includeRuleStats)을 켜면 규칙마다 매칭률이 나오고, 대상 버전 매칭률에서 비교 기준 버전 매칭률을 뺀 차이가 곧 결정 구성이 옮겨 간 양입니다. 상한을 더 조인 거부가 신청을 얼마나 더 거부하는지, 그 신청들이 어느 규칙에서 빠져나왔는지를 보여 줍니다.

lexq analytics simulation start --json '{
  "policyVersionId": "<candidate-version-id>",
  "dataset": {
    "type": "HISTORICAL",
    "source": "EXECUTION_LOGS",
    "from": "2026-05-01",
    "to": "2026-05-31"
  },
  "options": {
    "baselinePolicyVersionId": "<baseline-version-id>",
    "includeRuleStats": true,
    "maxRecords": 50000
  }
}'

거부 규칙의 매칭률 변화(대상 버전에서 비교 기준 버전을 뺀 값)가 리스크가 감당하기로 한 선 안에 들어오면 배포합니다. 그 숫자가 늘어나는 거부 건수이자, 거기서 나가는 거부 통지의 양입니다. 그 선을 넘으면 기준값을 다시 잡습니다. 손대지 않은 규칙의 매칭률까지 움직였다면 이번 수정이 기준값 하나만 옮긴 게 아니라는 뜻이니, 배포 전에 대상 버전을 다시 들여다봅니다. 시뮬레이션은 지난달 심사 이력을 읽어 다시 판정해 볼 뿐, 그때 신청자에게 나간 승인과 거부에는 손대지 않습니다.

대상 버전의 결정 구성을 비교 기준 버전과 대조하는 변경 영향 시뮬레이션

심사 이력이 아직 쌓이지 않았다면 신청 데이터셋을 올려 같은 대조를 돌립니다. 모든 구간이 들어가고 점수와 비율이 각 기준값 언저리에 몰린 신청으로 구성하면, 결정이 바뀌는 경우가 빠짐없이 들어갑니다.

의사결정 트레이스 출력

신호가 부딪치는 경우를 드라이런으로 돌려 봅니다. 점수 760, 소득 120,000, 부채 비율 0.55입니다.

{
  "result": "SUCCESS",
  "data": {
    "inputFacts": {
      "creditScore": 760,
      "annualIncomeUsd": 120000,
      "dtiRatio": 0.55
    },
    "mutatedFacts": {},
    "generatedVariables": {
      "creditDecision": "DECLINED",
      "decisionReason": "Debt-to-income ratio above 0.50"
    },
    "executionTraces": [ ... ],
    "decisionTraces": [
      {
        "ruleName": "Decline — DTI above 0.50",
        "status": "SELECTED",
        "reasonCode": "FINAL_WINNER",
        "reasonDetail": null
      },
      {
        "ruleName": "Decline — score below 580",
        "status": "NO_MATCH",
        "reasonCode": "CONDITION_MISMATCH",
        "reasonDetail": null
      },
      {
        "ruleName": "Approve — prime",
        "status": "BLOCKED",
        "reasonCode": "MUTEX_PRIORITY_LOST",
        "reasonDetail": "Winner=[Decline — DTI above 0.50], Strategy=HIGHEST_PRIORITY"
      },
      {
        "ruleName": "Refer — manual review",
        "status": "NO_MATCH",
        "reasonCode": "CONDITION_MISMATCH",
        "reasonDetail": null
      }
    ]
  }
}
거부된 신청의 드라이런. 승인 규칙은 걸렸지만 우선순위에서 상호 배타에 밀림

애플리케이션이 읽어 가는 결정은 generatedVariables에 담깁니다. creditDecisionDECLINED이고, decisionReason에는 규칙이 적은 문자열이 들어 있습니다. mutatedFacts는 비어 있습니다. 이 맵에는 입력으로 들어온 fact 중 값이 바뀐 것만 담기는데, SET_FACT는 기존 fact를 바꾸는 게 아니라 새 fact를 만드는 액션이라 결정이 generatedVariables 쪽으로 갑니다. KYC 차단의 isBlocked가 담기던 곳과 같고, 이유도 같습니다. __delta도 없습니다. 그 키는 규칙이 값을 바꾼 숫자 fact에만 붙는데, 범주형 결정은 숫자를 건드리지 않으니까요.

결정이 설명되는 자리는 트레이스입니다. 승인 규칙은 분명히 걸렸습니다. 점수 760과 소득 120,000이 조건을 채웁니다. 다만 우선순위가 더 높은 거부에 상호 배타 경쟁에서 밀렸고, 이 사실이 BLOCKED / MUTEX_PRIORITY_LOST로 남으며 어느 규칙이 선택됐는지가 reasonDetail에 적힙니다. 그렇게 남은 탈락이 곧 거부 사유입니다. "점수를 무시했다"가 아니라 "점수는 자격을 채웠지만, 상한을 넘는 부채 비율이 그보다 앞섰다"입니다. SELECTED 규칙의 이름은 대출 기관이 신청자에게 건네는 사유이고, 무엇이 선택됐고 무엇이 어떤 이유로 탈락했는지 그 전체 그림은 기관이 감사용으로 보관하는 답입니다. 각 규칙이 어떤 조건으로 평가됐는지는 위 드라이런 화면의 실행 트레이스 표에서 볼 수 있습니다.

같은 신청자를 부채 비율 0.30으로 다시 돌리면 충돌이 사라집니다.

{
  "result": "SUCCESS",
  "data": {
    "inputFacts": {
      "creditScore": 760,
      "annualIncomeUsd": 120000,
      "dtiRatio": 0.30
    },
    "mutatedFacts": {},
    "generatedVariables": {
      "creditDecision": "APPROVED",
      "decisionReason": "Score 720+ with income 40000+"
    },
    "executionTraces": [ ... ],
    "decisionTraces": [
      {
        "ruleName": "Decline — DTI above 0.50",
        "status": "NO_MATCH",
        "reasonCode": "CONDITION_MISMATCH",
        "reasonDetail": null
      },
      {
        "ruleName": "Decline — score below 580",
        "status": "NO_MATCH",
        "reasonCode": "CONDITION_MISMATCH",
        "reasonDetail": null
      },
      {
        "ruleName": "Approve — prime",
        "status": "SELECTED",
        "reasonCode": "FINAL_WINNER",
        "reasonDetail": null
      },
      {
        "ruleName": "Refer — manual review",
        "status": "NO_MATCH",
        "reasonCode": "CONDITION_MISMATCH",
        "reasonDetail": null
      }
    ]
  }
}
승인된 신청의 드라이런. 승인 규칙이 선택되고 경쟁 규칙은 걸리지 않음

거부가 더는 걸리지 않으니 승인 규칙만 실행됩니다. SELECTED 하나에 NO_MATCH 셋, creditDecisionAPPROVED가 됩니다. 깔끔한 승인이든 다툼이 있는 거부든 트레이스 구조는 똑같고, 달라지는 건 어느 규칙이 SELECTED를 차지하느냐뿐입니다.

엣지 케이스

기준값은 리스크가 언제든 바꿉니다. 바뀌지 않는 것은 신청 한 건의 결정이 한 곳에서만 난다는 점이고, 아래 경우들은 그 한 곳을 만들 때 미리 정해 둬야 하는 선택입니다.

  • 기준값과 딱 걸치는 신청자. GREATER_THAN은 부채 비율이 정확히 0.50인 신청을 거부에서 통과시킵니다. "상한"을 허용되는 최대 비율로 본 것입니다. 정책이 "그 값부터 거부"이라면 연산자는 GREATER_THAN_OR_EQUAL입니다. 같은 선택이 720 점수 경계에도 똑같이 걸립니다. 한 번 정해 연산자에 적어 두면, 기록된 조건식이 지금 어느 해석으로 도는지 보여 줍니다.
  • 빠진 fact. dtiRatio가 없는 페이로드는 조용히 지나가지 않습니다. 엔진은 임의의 기본값으로 메우는 대신 빠진 fact 이름을 담아 에러를 던집니다. 호출하는 쪽은 에러로 끝난 실행을 미심사로 처리해야 하고, 조용한 거부나 조용한 승인으로 넘겨선 안 됩니다. 엔진이 dtiRatio를 임의로 채우기를 거부한 자리를 호출하는 쪽이 대신 채우면, 근거 없는 값으로 내린 결정이 그대로 신청자에게 나갑니다.
  • 신청자에게 줘야 할 사유. 결정이 내려질 때마다 decisionReason이 남고, SELECTED 규칙 이름은 어느 규칙이 그 결과를 냈는지 가리킵니다. 둘은 일부러 갈라 둡니다. 규칙 이름은 내부용 기록이고, decisionReason은 신청자에게 보일 수 있게 다듬은 문장입니다. 둘 다 결정마다 남으니, 거부 통지는 짜 맞추는 일이 아니라 필드 하나 읽는 일입니다.
  • 과거 이력에 기대는 결정. 이 패턴은 한 시점의 fact만 보고 신청 한 건을 결정합니다. 지난 행동에 기대는 규칙(조회를 거듭하며 떨어지는 점수, 이번 달 세 번째 신청)은 다른 문제입니다. 엔진은 실행과 실행 사이에 아무 상태도 들고 있지 않습니다. 누적 횟수나 추세는 집계 구간까지 호출하는 쪽에서 관리해 입력 fact로 넘겨야 합니다. 실패하는 방식이 다른 별도 패턴이라 여기서는 다루지 않습니다.
  • 어느 규칙에도 걸리지 않는 신청. 수동 심사는 준우량 구간과, 소득이 하한에 못 미치는 우량 신청을 사람에게 넘기는 분명한 결정입니다. 설계가 일부러 만들어 둔 세 번째 결과입니다. 누가 구간을 잘못 손봐 빈틈이 생기면, 그 틈에 떨어진 신청은 어디에도 걸리지 않습니다. 이때 엔진은 에러를 내지 않습니다. 실행은 성공으로 끝나고, 모든 규칙이 NO_MATCH 의사결정 트레이스를 남기며, generatedVariablescreditDecision이 없습니다. 기록은 무슨 일이 있었는지 그대로 남기지만 승인도 거부도 수동 심사도 아닙니다. 호출하는 쪽은 creditDecision 부재를 미심사로 처리해야 합니다. 빈틈이 스스로 드러나게 하려면 수동 심사 결정을 내리는 catch-all 규칙을 맨 마지막에 만들어 가장 큰 priority 숫자를 받게 하세요. 이때 같은 credit-decision 그룹에 같은 mutexMode·mutexStrategy로 넣어야 합니다. 그룹 밖에 둔 catch-all은 경쟁 자체를 하지 않아 모든 신청에 걸리고, 그룹이 내린 결정을 덮어씁니다. 부채 비율 상한에 걸린 신청이 수동 심사로 되돌아옵니다.

운영 배포

시뮬레이션이 그려 낸 결정 구성 이동을 리스크가 받아들이면, 그 대상 버전을 운영 정책 그룹에 올립니다. 배포되는 순간 결정 규칙 네 개의 스냅샷에 해시가 찍혀 이후의 변조가 드러나고, 어느 사용자가 언제 어떤 버전을 신청 트래픽에 붙였는지가 배포 기록에 남습니다.

기준값 변경은 트래픽을 쪼개 조금씩 적용하지 않고, 모든 신청에 한 번에 적용합니다. A/B 테스트로 트래픽을 나누면 일부 신청은 새 기준값으로, 나머지는 이전 기준값으로 판정됩니다. 점수와 소득과 부채 비율이 같은 두 신청자가 어느 쪽에 떨어졌는지만으로 다른 결정을 받는다는 뜻입니다. 부분 적용을 견디는 할인이라면 그 정도는 괜찮지만, 여신 결정에서는 같은 사실에 들쭉날쭉한 판정 자체가 결함이지 안전장치가 아닙니다. 카나리로 조금씩 흘려보내도 사정은 같습니다. 배포 여부는 변경 영향 시뮬레이션을 보고 판단합니다. 배포한 뒤에 하는 일은 운영 데이터로 그 판단을 확인하는 것뿐입니다. 규칙별 운영 매칭률을 시뮬레이션 매칭률과 맞대어 봅니다.

배포 뒤 들어오는 심사 물량에서 아래 둘 중 하나가 보이면 곧바로 이전 버전으로 되돌립니다.

  • 바꾼 규칙의 운영 매칭률이 시뮬레이션 매칭률에서 벗어날 때. 운영 신청자 분포가 시뮬레이션에 쓴 과거 구간과 다르다는 뜻이고, 승인했던 파장 범위는 더는 유효하지 않습니다.
  • 시뮬레이션이 변동 없다고 본 구간에서 결정이 나타날 때. 신청 페이로드가 싣고 오는 점수와 소득과 부채 비율의 분포가 대상 버전을 재던 데이터셋과 어긋난다는 신호입니다.
승인 심사 버전의 배포 상세

두 버전이 같은 신청을 어떻게 다르게 판정하는지 봐야 한다면, A/B 테스트가 아니라 변경 영향 시뮬레이션에 비교 기준 버전을 지정해 돌립니다. 같은 기간의 신청을 두 버전으로 재서 차이만 읽으니, 운영 결정은 한 건도 갈리지 않습니다. 롤백하면 심사 정책 그룹이 직전 기준값을 담은 버전으로 돌아가고, 되돌렸다는 사실까지 배포 이력에 남습니다. 버전이 운영에 올라가면, "5월에 부채 비율로 거부된 신청이 몇 건인가"에는 규칙별 통계가 상시 답이 됩니다. 규칙과 버전으로 범위를 좁혀 실행 이력을 조회하면 그만입니다. 그리고 "5월 14일에 운영 중이던 우량 점수 기준"은 버전 이력과 배포 기록에 그대로 있습니다. 심사 응답이 늦어지기 시작하면 네 규칙 가운데 어디서 시간이 새는지가 다음 질문입니다. 느려진 규칙 집어내기가 그 프로파일을 규칙 단위로 읽는 방법을 다룹니다.


LexQ가 어떻게 동작하는지 playground에서 직접 확인해보세요.

결정을 배포 파이프라인 밖으로 꺼낼 준비가 되셨나요?

LexQ를 무료로 체험하세요. 신용카드 불필요.

무료로 시작