메인 콘텐츠로 건너뛰기

정책 룰이란?

정책 룰(Policy Rule) 은 조건 → 액션의 쌍입니다. 입력 facts가 조건을 만족하면 룰의 액션이 실행됩니다. 룰은 우선순위 순서 (0이 가장 높은 우선순위)로 평가됩니다.
IF 조건이 매칭되면 → THEN 액션을 실행
각 룰은 특정 정책 버전에 속하며, 이름 / 우선순위 / 조건(트리 구조 로직) / 하나 이상의 액션을 가집니다.

조건 문법

조건은 SINGLEGROUP 두 노드 타입의 트리 구조입니다.
LexQ는 type: "SINGLE" / type: "GROUP", field, operator, value, valueType 필드를 사용하는 자체 condition DTO 포맷을 사용합니다. Console 내부 폼 표현(다른 필드명 사용 가능)과 혼동하지 마세요. API 또는 CLI 호출 시에는 항상 엔진 포맷을 사용해야 합니다.

SINGLE 조건

단일 fact를 값과 비교하는 leaf 노드:
{
  "type": "SINGLE",
  "field": "payment_amount",
  "operator": "GREATER_THAN_OR_EQUAL",
  "value": 100000,
  "valueType": "NUMBER"
}

GROUP 조건

자식 조건을 AND 또는 OR로 결합하는 branch 노드:
{
  "type": "GROUP",
  "operator": "AND",
  "children": [
    { "type": "SINGLE", "field": "customer_tier", "operator": "EQUALS", "value": "VIP", "valueType": "STRING" },
    { "type": "SINGLE", "field": "payment_amount", "operator": "GREATER_THAN", "value": 50000, "valueType": "NUMBER" }
  ]
}

연산자

연산자호환 타입설명
EQUALSAll정확히 일치
NOT_EQUALSAll부정
GREATER_THANNUMBER>
GREATER_THAN_OR_EQUALNUMBER>=
LESS_THANNUMBER<
LESS_THAN_OR_EQUALNUMBER<=
CONTAINSSTRING부분 문자열 일치
INSTRING, NUMBER값이 제공된 리스트에 포함
NOT_INSTRING, NUMBER값이 제공된 리스트에 미포함
IN / NOT_IN의 경우 호환 타입 열은 검사 대상 fact의 타입(STRING 또는 NUMBER)을 가리킵니다. 제공하는 value는 리스트 자체이며, 그 valueTypeLIST_STRING 또는 LIST_NUMBER입니다.

값 타입

타입JSON 값예시
STRING"string""VIP"
NUMBERnumber100000
BOOLEANtrue / falsetrue
LIST_STRING["a", "b"]["KR", "US"]
LIST_NUMBER[1, 2][10000, 20000]

중첩 조건 예시

(customer_tier = "VIP" AND payment_amount >= 100000) OR region IN ["KR", "JP"]
{
  "type": "GROUP",
  "operator": "OR",
  "children": [
    {
      "type": "GROUP",
      "operator": "AND",
      "children": [
        { "type": "SINGLE", "field": "customer_tier", "operator": "EQUALS", "value": "VIP", "valueType": "STRING" },
        { "type": "SINGLE", "field": "payment_amount", "operator": "GREATER_THAN_OR_EQUAL", "value": 100000, "valueType": "NUMBER" }
      ]
    },
    { "type": "SINGLE", "field": "region", "operator": "IN", "value": ["KR", "JP"], "valueType": "LIST_STRING" }
  ]
}
위 예시에서 customer_tier, region, payment_amount는 사용자 정의 fact로, 사용 전에 Fact Definitions에 등록되어야 합니다. 시스템 fact는 user_id, user_tags 두 개뿐입니다.

액션 타입

LexQ 엔진의 액션은 도메인 중립 원시(primitive) 입니다. 엔진은 숫자와 구조만 봅니다 — 커머스 / 핀테크 / 보험 / 특정 비즈니스 모델을 가정하지 않습니다. 도메인 의미는 fact 이름과 integration 페이로드에 담깁니다.
타입설명주요 파라미터
MUTATE_FACT산술 연산자로 숫자 fact를 in-place 변경refVar, operator, method, rate (PERCENTAGE 일 때) 또는 value, rounding (선택)
INCREMENT_FACT계산된 양만큼 숫자 fact를 누적 증가targetVar, method, refVar, rate (PERCENTAGE 일 때) 또는 value, rounding (선택)
EMIT_EVENT외부 integration에 이벤트 발행integrationId, eventPayload
EMIT_NOTIFICATIONintegration을 통해 알림 발송integrationId, targetVar, notificationPayload
EMIT_WEBHOOK외부 URL 또는 webhook integration 호출url, method, payloadTemplate (선택)
BLOCK요청 차단reason
SET_FACTfact를 고정 값으로 설정key, value
ADD_TAG리스트 fact에 태그 추가tag, targetVar

MUTATE_FACT — Percentage

payment_amount를 10% 감소:
{
  "type": "MUTATE_FACT",
  "parameters": {
    "refVar": "payment_amount",
    "operator": "SUB",
    "method": "PERCENTAGE",
    "rate": 10
  }
}
payment_amount가 200,000이면 → 180,000으로 감소. 변화량(-20,000)은 generatedVariablespayment_amount__delta로 노출됩니다.

연산자(Operator)

연산자AMOUNTPERCENTAGE
ASSIGNrefVar = valuerefVar = refVar × rate / 100
ADDrefVar += valuerefVar += refVar × rate / 100
SUBrefVar -= valuerefVar -= refVar × rate / 100
MULrefVar *= valuerefVar *= (rate / 100 + 1)
DIVrefVar /= valueinvalid — 역수 rate를 사용한 MUL로 대체
DIV + PERCENTAGE 조합은 DRAFT 저장 시점에 거부됩니다. DIV + value=0도 마찬가지입니다.

MUTATE_FACT — Fixed Amount

{
  "type": "MUTATE_FACT",
  "parameters": {
    "refVar": "payment_amount",
    "operator": "SUB",
    "method": "AMOUNT",
    "value": 5000
  }
}

MUTATE_FACT — 반올림(Rounding)

기본적으로 calculator 출력은 무손실(lossless) 정밀도로 보존됩니다. 고정 자릿수가 필요할 때만 선택적 rounding 필드를 사용하세요.
{
  "type": "MUTATE_FACT",
  "parameters": {
    "refVar": "payment_amount",
    "operator": "SUB",
    "method": "PERCENTAGE",
    "rate": 13.7,
    "rounding": { "scale": 0, "mode": "FLOOR" }
  }
}
rounding.scale[0, 16] 범위의 정수. rounding.modeHALF_UP(default), HALF_DOWN, HALF_EVEN, FLOOR, CEILING, DOWN, UP 중 하나. rounding을 생략하면 무손실 정밀도가 유지됩니다 — 가능하면 엔진이 아니라 다운스트림(예: 통화 표시 시점)에서 반올림하세요.

INCREMENT_FACT — Percentage

payment_amount의 1%를 total_point에 적립:
{
  "type": "INCREMENT_FACT",
  "parameters": {
    "targetVar": "total_point",
    "refVar": "payment_amount",
    "method": "PERCENTAGE",
    "rate": 1
  }
}
payment_amount가 100,000이면 → total_point가 1,000 증가. 증분량은 generatedVariablestotal_point__delta로 노출됩니다.
INCREMENT_FACT는 엔진 내부 fact 변경만 수행합니다. 외부 시스템(예: 포인트 서비스) 동기화가 필요하면 동일 룰 안에서 [INCREMENT_FACT, EMIT_EVENT] 체인으로 구성하세요. 엔진은 INCREMENT_FACT 대신 외부 시스템을 호출하지 않습니다 — 이는 엔진 상태와 외부 상태 사이의 audit-grade 분리를 보존하기 위함입니다.

INCREMENT_FACT — Fixed Amount

{
  "type": "INCREMENT_FACT",
  "parameters": {
    "targetVar": "total_point",
    "method": "AMOUNT",
    "value": 500
  }
}

EMIT_EVENT

외부 integration에 generic 이벤트 페이로드를 발행합니다. 엔진은 integrationId와 비어있지 않은 eventPayload 맵의 존재만 검증합니다 — 페이로드 구조는 integration provider의 책임입니다.
{
  "type": "EMIT_EVENT",
  "parameters": {
    "integrationId": "<your-integration-id>",
    "eventPayload": {
      "couponId": "WELCOME_VIP_2026"
    }
  }
}
도메인 전용 키(couponId, ticketId, claimId 등)는 integration provider가 라우팅합니다. 이로써 EMIT_EVENT는 진정한 generic primitive가 됩니다 — 쿠폰 발행, 티켓 생성, 보험 청구 제출 등 어떤 이벤트성 외부 호출에도 사용할 수 있습니다.

EMIT_NOTIFICATION

외부 integration을 통해 알림을 발송합니다.
{
  "type": "EMIT_NOTIFICATION",
  "parameters": {
    "integrationId": "<your-notification-integration-id>",
    "targetVar": "phone_number",
    "notificationPayload": {
      "channel": "SMS",
      "templateId": "ORDER_CONFIRM_001",
      "variables": { "order_id": "order_id" }
    }
  }
}
targetVar는 수신자 fact를 지정합니다 (예: phone_number, email, device_token). notificationPayload는 채널, 템플릿, 변수 매핑을 integration provider에 전달 — 엔진은 이 키들을 해석하지 않습니다.

BLOCK — 요청 차단

{
  "type": "BLOCK",
  "parameters": { "reason": "Suspected fraud" }
}

SET_FACT — Output Variable

SET_FACT리터럴 값 setter이지 expression evaluator가 아닙니다. 출력의 키에 고정 값을 할당합니다. 산술 표현식이나 다른 fact 참조는 지원하지 않습니다.
{
  "type": "SET_FACT",
  "parameters": { "key": "risk_level", "value": "HIGH" }
}

ADD_TAG

{
  "type": "ADD_TAG",
  "parameters": {
    "tag": "VIP_VERIFIED",
    "targetVar": "user_tags"
  }
}
리스트 fact에 태그를 추가합니다. 중복은 자동으로 방지됩니다. targetVar를 생략하면 user_tags가 기본값입니다.

EMIT_WEBHOOK — Basic

{
  "type": "EMIT_WEBHOOK",
  "parameters": {
    "url": "https://api.example.com/webhooks/orders",
    "method": "POST"
  }
}
payloadTemplate 없이 사용하면 엔진은 모든 input/output facts를 요청 본문으로 전송합니다 (시스템 변수 제외).

EMIT_WEBHOOK — With Payload Template

payloadTemplate로 요청 본문을 커스터마이징하세요. 템플릿 변수는 실행 시점에 치환됩니다.
{
  "type": "EMIT_WEBHOOK",
  "parameters": {
    "url": "<integration-id-or-direct-url>",
    "method": "POST",
    "payloadTemplate": {
      "text": "🔔 Rule {{ruleName}} fired\nCustomer: {{fact.customer_tier}}\nAmount: {{output.payment_amount}}"
    }
  }
}

사용 가능한 템플릿 변수

변수설명예시 값
{{fact.xxx}}입력 fact 값{{fact.payment_amount}}150000
{{output.xxx}}출력 변수 (액션 후){{output.payment_amount__delta}}-20000
{{timestamp}}실행 시각 (ISO-8601)2026-04-14T05:30:00Z
{{ruleName}}매칭된 룰 이름VIP 20% Discount
{{groupName}}정책 그룹 이름Payment Policy
{{versionNo}}실행된 버전 번호5
{{xxx}}직접 fact 조회 (단축형){{customer_tier}}VIP
Slack{"text": "..."}, Discord{"content": "..."} 형식이 필요합니다. 각 플랫폼이 기대하는 형식에 맞춰 payloadTemplate을 사용하세요.
{{fact.xxx}}{{output.xxx}}는 동일한 데이터 맵을 참조합니다. 이전 액션(예: MUTATE_FACT)이 payment_amount를 변경하면, {{fact.payment_amount}}{{output.payment_amount}} 모두 변경된 값을 반영합니다.

생성 변수 (Generated Variables)

변경된 facts와 별도로, 엔진은 액션별 변화 정보를 generatedVariables로 노출합니다:
패턴의미생성 액션
{refVar}__deltarefVar의 순변화량 (mutated - original, 부호 포함)MUTATE_FACT
{targetVar}__deltatargetVar의 순증분량 (항상 0 이상)INCREMENT_FACT
한 룰에서 여러 액션이 같은 fact를 변경하면 __delta는 누적 변화량을 보고합니다. 액션별 스냅샷은 audit drill-down용으로 executionTraces에 보관됩니다.

Mutex — 룰 단위 충돌 해소

한 버전 내에서 룰들은 mutex 그룹에 속할 수 있으며, 이를 통해 매칭되는 룰 중 몇 개를 발화시킬지 제어합니다.
필드설명
mutexGroup관련 룰을 그룹핑하는 문자열 키 (예: "best-discount")
mutexModeNONE(default), EXCLUSIVE, 또는 MAX_N
mutexStrategyFIRST_MATCH, HIGHEST_PRIORITY, MAX_BENEFIT
mutexLimit이 그룹에서 발화 가능한 최대 룰 수 (MAX_N 모드용)
mutexModeEXCLUSIVE이면 winning 룰 하나만 발화합니다. MAX_N이면 우선순위 순서로 최대 mutexLimit개의 룰이 발화합니다. 발화하지 못한 다른 매칭 룰은 Decision Trace에 status BLOCKED + reason MUTEX_PRIORITY_LOST 또는 MUTEX_LIMIT_REACHED로 기록됩니다 (Decision Trace 참조).

Decision Trace

룰 평가 결과는 모두 decision trace 엔트리로 기록되어 해당 룰이 발화했는지 / 왜 그렇게 결정됐는지를 설명합니다. 이는 LexQ의 audit-grade 추론 영역의 핵심입니다 — 모든 결과는 특정 status와 reason code로 추적할 수 있습니다. decision trace 엔트리는 두 분류 필드를 가집니다:
  • status — 결과의 상위 분류
  • reasonCode — 그 분류 안에서의 구체적 사유

DecisionStatus

Status의미
SELECTED룰이 발화하고 액션이 실행됨
NO_MATCH룰의 condition 이 false 로 평가됨
NOT_SELECTED경쟁 이전 사전 필터링 (예: effective date — 향후 지원 예정)
BLOCKEDmutex 또는 activation 그룹 경쟁에서 탈락
ERROR평가 도중 액션 또는 엔진 오류 발생

DecisionReasonCode

ReasonCode의미
FINAL_WINNER이 평가에서 선택된 룰
EFFECTIVE_DATE_INVALID룰의 유효 기간 [from, to] 범위 밖 (향후 지원 예정)
CONDITION_MISMATCH룰의 condition 트리가 입력 facts와 불일치
MUTEX_PRIORITY_LOSTEXCLUSIVE mutex (limit=1) — 같은 그룹의 다른 룰이 더 높은 우선순위 또는 점수
MUTEX_LIMIT_REACHEDMAX_N mutex (limit>1) — mutexLimit 이 더 높은 우선순위 룰들로 이미 채워짐
GROUP_PRIORITY_LOSTEXCLUSIVE activation 그룹 (limit=1) — 다른 룰이 priority 로 승리
GROUP_LIMIT_REACHEDMAX_N activation 그룹 (limit>1) — executionLimit 이 이미 채워짐
ACTION_ERROR이 룰의 액션 executor 가 실행 중 throw
ENGINE_ERROR엔진 자체가 룰 처리 전/도중에 실패

Status × ReasonCode 매핑

Status가능한 ReasonCode
SELECTEDFINAL_WINNER
NO_MATCHCONDITION_MISMATCH
NOT_SELECTEDEFFECTIVE_DATE_INVALID (향후 지원 예정)
BLOCKEDMUTEX_PRIORITY_LOST, MUTEX_LIMIT_REACHED, GROUP_PRIORITY_LOST, GROUP_LIMIT_REACHED
ERRORACTION_ERROR, ENGINE_ERROR
“왜 내 룰이 발화하지 않았지?”를 디버깅할 때 decision trace는 두 단계로 답을 줍니다 — status는 어떤 분류의 필터링이 룰을 제거했는지 알려주고, reasonCode는 정확히 어떤 검증이 룰을 거부했는지 알려줍니다.

완전한 룰 예시

{
  "name": "VIP 20% Discount",
  "priority": 0,
  "condition": {
    "type": "GROUP",
    "operator": "AND",
    "children": [
      { "type": "SINGLE", "field": "customer_tier", "operator": "EQUALS", "value": "VIP", "valueType": "STRING" },
      { "type": "SINGLE", "field": "payment_amount", "operator": "GREATER_THAN_OR_EQUAL", "value": 100000, "valueType": "NUMBER" }
    ]
  },
  "actions": [
    { "type": "MUTATE_FACT", "parameters": { "refVar": "payment_amount", "operator": "SUB", "method": "PERCENTAGE", "rate": 20 } },
    { "type": "SET_FACT", "parameters": { "key": "discount_applied", "value": true } }
  ],
  "mutexGroup": "best-discount",
  "mutexMode": "EXCLUSIVE",
  "mutexStrategy": "HIGHEST_PRIORITY",
  "isEnabled": true
}

다음 단계

Fact Definitions

룰이 기대하는 입력 변수를 정의하세요.

Dry Run

배포 전에 룰을 테스트하세요.