@lexq/cli 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -3
- package/CONTEXT.md +2 -2
- package/README.md +12 -6
- package/dist/index.js +10 -8
- package/dist/mcp/register.d.ts +1 -1
- package/dist/mcp/register.js +6 -6
- package/package.json +1 -1
- package/skills/lexq-execution/SKILL.md +26 -24
- package/skills/lexq-recipes/SKILL.md +119 -21
- package/skills/lexq-rules/SKILL.md +90 -46
- package/skills/lexq-simulation/SKILL.md +116 -68
package/AGENTS.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
LexQ CLI (`@lexq/cli`, binary: `lexq`) manages a policy execution engine. Policies are business rules (if-then) that
|
|
8
8
|
evaluate input facts and produce actions (discounts, blocks, notifications, etc.).
|
|
9
9
|
|
|
10
|
-
The CLI also doubles as an **MCP server** — run `lexq serve --mcp` to expose
|
|
10
|
+
The CLI also doubles as an **MCP server** — run `lexq serve --mcp` to expose 63 tools to any MCP-compatible AI client.
|
|
11
11
|
|
|
12
12
|
This file tells you how to use the CLI as an AI agent.
|
|
13
13
|
|
|
@@ -35,7 +35,7 @@ Detailed documentation lives in the `skills/` directory. **Read the relevant ski
|
|
|
35
35
|
| **Shared** | `skills/lexq-shared/SKILL.md` | Core concepts, auth, workflow, error codes. **Read first.** |
|
|
36
36
|
| **Groups** | `skills/lexq-groups/SKILL.md` | Policy groups, conflict resolution, A/B testing |
|
|
37
37
|
| **Rules** | `skills/lexq-rules/SKILL.md` | Condition syntax, action types, mutex, examples |
|
|
38
|
-
| **Simulation** | `skills/lexq-simulation/SKILL.md` | Dry run,
|
|
38
|
+
| **Simulation** | `skills/lexq-simulation/SKILL.md` | Dry run, Impact Simulation, compare |
|
|
39
39
|
| **Execution** | `skills/lexq-execution/SKILL.md` | History, stats, failure logs, integrations, webhook subscriptions |
|
|
40
40
|
| **Recipes** | `skills/lexq-recipes/SKILL.md` | End-to-end workflows (copy-paste ready) |
|
|
41
41
|
|
|
@@ -142,7 +142,7 @@ Connect via:
|
|
|
142
142
|
- **Cloud:** `https://mcp.lexq.io` (OAuth 2.1)
|
|
143
143
|
- **Local stdio:** `npx @lexq/cli serve --mcp`
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
63 tools mirror the CLI command inventory.
|
|
146
146
|
|
|
147
147
|
## Troubleshooting
|
|
148
148
|
|
package/CONTEXT.md
CHANGED
|
@@ -7,7 +7,7 @@ simulation, deploy to production, and execute via REST API — all without modif
|
|
|
7
7
|
|
|
8
8
|
**Core differentiators:**
|
|
9
9
|
|
|
10
|
-
-
|
|
10
|
+
- Impact Simulation against real data
|
|
11
11
|
- A/B testing for rule versions
|
|
12
12
|
- Git-style versioning with full audit trail
|
|
13
13
|
- Platform event webhooks for deployment lifecycle notifications
|
|
@@ -91,7 +91,7 @@ Tenant
|
|
|
91
91
|
|
|
92
92
|
### Action Types
|
|
93
93
|
|
|
94
|
-
`
|
|
94
|
+
`MUTATE_FACT`, `INCREMENT_FACT`, `EMIT_EVENT`, `BLOCK`, `EMIT_NOTIFICATION`, `EMIT_WEBHOOK`, `SET_FACT`, `ADD_TAG`
|
|
95
95
|
|
|
96
96
|
### Conflict Resolution Modes
|
|
97
97
|
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# LexQ CLI
|
|
2
2
|
|
|
3
|
-
> **The
|
|
3
|
+
> **The Decision Operations Platform for engineering teams.**
|
|
4
4
|
> CLI and MCP server for [LexQ](https://lexq.io) — move business rules out of
|
|
5
|
-
> your deploy pipeline, prove every change with
|
|
5
|
+
> your deploy pipeline, prove every change with Impact Simulation, and ship without
|
|
6
6
|
> a git push.
|
|
7
7
|
|
|
8
8
|
**[Website](https://lexq.io)** · **[Docs](https://docs.lexq.io)** · **[Console](https://console.lexq.io)**
|
|
@@ -24,7 +24,7 @@ that changes weekly shouldn't live in code that ships quarterly.
|
|
|
24
24
|
|
|
25
25
|
**LexQ separates the two.** Rules live outside your application. You change
|
|
26
26
|
them in a visual console or through this CLI. You prove every change with
|
|
27
|
-
|
|
27
|
+
Impact Simulation against real execution data. You deploy without touching
|
|
28
28
|
your codebase.
|
|
29
29
|
|
|
30
30
|
Every decision leaves a full audit trace.
|
|
@@ -71,8 +71,14 @@ lexq rules create --group-id <GROUP_ID> --version-id <VERSION_ID> --json '{
|
|
|
71
71
|
"valueType": "STRING"
|
|
72
72
|
},
|
|
73
73
|
"actions": [{
|
|
74
|
-
"type": "
|
|
75
|
-
"parameters": {
|
|
74
|
+
"type": "MUTATE_FACT",
|
|
75
|
+
"parameters": {
|
|
76
|
+
"method": "PERCENTAGE",
|
|
77
|
+
"rate": 10,
|
|
78
|
+
"refVar": "payment_amount",
|
|
79
|
+
"operator": "SUB",
|
|
80
|
+
"rounding": { "mode": "HALF_UP", "scale": 0 }
|
|
81
|
+
}
|
|
76
82
|
}]
|
|
77
83
|
}'
|
|
78
84
|
|
|
@@ -176,7 +182,7 @@ skills/
|
|
|
176
182
|
├── lexq-shared/SKILL.md Core concepts, auth, workflow
|
|
177
183
|
├── lexq-groups/SKILL.md Policy groups, conflict resolution, A/B testing
|
|
178
184
|
├── lexq-rules/SKILL.md Condition syntax, action types, mutex
|
|
179
|
-
├── lexq-simulation/SKILL.md Dry run,
|
|
185
|
+
├── lexq-simulation/SKILL.md Dry run, Impact Simulation, compare
|
|
180
186
|
├── lexq-execution/SKILL.md Execution history, stats, failure logs
|
|
181
187
|
└── lexq-recipes/SKILL.md End-to-end recipes
|
|
182
188
|
|
package/dist/index.js
CHANGED
|
@@ -2139,7 +2139,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2139
2139
|
dedent9`
|
|
2140
2140
|
|
|
2141
2141
|
Returns the full execution detail including request facts, result traces,
|
|
2142
|
-
and decision traces (SELECTED,
|
|
2142
|
+
and decision traces (SELECTED, NO_MATCH, BLOCKED, etc.).
|
|
2143
2143
|
`
|
|
2144
2144
|
).action(async (opts) => {
|
|
2145
2145
|
try {
|
|
@@ -3296,7 +3296,7 @@ function registerFactTools(server, callApi) {
|
|
|
3296
3296
|
"lexq_facts_action_metadata",
|
|
3297
3297
|
{
|
|
3298
3298
|
title: "Get Action Runtime Fact Metadata",
|
|
3299
|
-
description: "Retrieve runtime Facts metadata for each Action type. Shows which Facts are required as input, produced as output, or consumed at runtime by each Action (
|
|
3299
|
+
description: "Retrieve runtime Facts metadata for each Action type. Shows which Facts are required as input, produced as output, or consumed at runtime by each Action (MUTATE_FACT, INCREMENT_FACT, EMIT_EVENT, BLOCK, EMIT_NOTIFICATION, EMIT_WEBHOOK, SET_FACT, ADD_TAG). Use this BEFORE designing rules to understand which Action mutates which fact (e.g., MUTATE_FACT mutates the refVar fact and auto-generates {fact_name}__delta in generatedVariables). Data is static and changes only on engine deployment \u2014 safe to cache in-session.",
|
|
3300
3300
|
inputSchema: {}
|
|
3301
3301
|
},
|
|
3302
3302
|
async () => callApi("GET", "schema/action-metadata")
|
|
@@ -3447,10 +3447,10 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
3447
3447
|
|
|
3448
3448
|
Returns:
|
|
3449
3449
|
inputFacts — normalized input facts
|
|
3450
|
-
mutatedFacts — input facts changed by rule actions (e.g.
|
|
3451
|
-
generatedVariables —
|
|
3450
|
+
mutatedFacts — input facts changed by rule actions (e.g. MUTATE_FACT mutates payment_amount)
|
|
3451
|
+
generatedVariables — system-generated values; every fact in mutatedFacts gets a paired {fact_name}__delta key (signed difference)
|
|
3452
3452
|
executionTraces — per-rule match status
|
|
3453
|
-
decisionTraces — per-rule decision (SELECTED /
|
|
3453
|
+
decisionTraces — per-rule decision (SELECTED / NO_MATCH / BLOCKED / etc.)
|
|
3454
3454
|
|
|
3455
3455
|
Example input: { "facts": { "payment_amount": 100000, "customer_tier": "VIP" } }
|
|
3456
3456
|
Always dry-run before publishing to validate rule behavior.`,
|
|
@@ -3509,7 +3509,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
3509
3509
|
{
|
|
3510
3510
|
title: "Start Simulation",
|
|
3511
3511
|
description: dedent14`
|
|
3512
|
-
Start
|
|
3512
|
+
Start an Impact Simulation against historical or uploaded data.
|
|
3513
3513
|
|
|
3514
3514
|
dataset.type: "HISTORICAL" or "UPLOADED"
|
|
3515
3515
|
dataset.source (when HISTORICAL): "EXECUTION_LOGS"
|
|
@@ -3667,7 +3667,7 @@ function registerHistoryTools(server, callApi) {
|
|
|
3667
3667
|
"lexq_history_get",
|
|
3668
3668
|
{
|
|
3669
3669
|
title: "Get Execution Detail",
|
|
3670
|
-
description: "Get full execution detail including
|
|
3670
|
+
description: "Get full execution detail including inputFacts, mutatedFacts, generatedVariables, executionTraces, and decisionTraces.",
|
|
3671
3671
|
inputSchema: {
|
|
3672
3672
|
traceId: z7.string().describe("Trace ID from execution history")
|
|
3673
3673
|
}
|
|
@@ -3991,7 +3991,9 @@ function getVersion2() {
|
|
|
3991
3991
|
}
|
|
3992
3992
|
function createCli() {
|
|
3993
3993
|
const program = new Command();
|
|
3994
|
-
program.name("lexq").description(
|
|
3994
|
+
program.name("lexq").description(
|
|
3995
|
+
"Command-line interface for the LexQ Decision Operations Platform. Manage policies, run Impact Simulation, and deploy from your terminal."
|
|
3996
|
+
).version(getVersion2(), "-V, --version").option("--format <format>", "Output format: json or table", "json").option("--api-key <key>", "Override stored API key").option("--base-url <url>", "Override API base URL").option("--dry-run", "Preview the HTTP request without executing").option("--verbose", "Show request/response details").option("--no-color", "Disable colored output");
|
|
3995
3997
|
registerAuthCommands(program);
|
|
3996
3998
|
registerStatusCommand(program);
|
|
3997
3999
|
registerGroupCommands(program);
|
package/dist/mcp/register.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ type CallApi = (method: string, path: string, opts?: {
|
|
|
27
27
|
declare function paginationParams(page?: number, size?: number): Record<string, string>;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Registers all
|
|
30
|
+
* Registers all 63 MCP tools on the given server.
|
|
31
31
|
*
|
|
32
32
|
* @param server - McpServer instance
|
|
33
33
|
* @param callApi - API caller function (config-based for CLI, Bearer-based for HTTP)
|
package/dist/mcp/register.js
CHANGED
|
@@ -465,7 +465,7 @@ function registerFactTools(server, callApi) {
|
|
|
465
465
|
"lexq_facts_action_metadata",
|
|
466
466
|
{
|
|
467
467
|
title: "Get Action Runtime Fact Metadata",
|
|
468
|
-
description: "Retrieve runtime Facts metadata for each Action type. Shows which Facts are required as input, produced as output, or consumed at runtime by each Action (
|
|
468
|
+
description: "Retrieve runtime Facts metadata for each Action type. Shows which Facts are required as input, produced as output, or consumed at runtime by each Action (MUTATE_FACT, INCREMENT_FACT, EMIT_EVENT, BLOCK, EMIT_NOTIFICATION, EMIT_WEBHOOK, SET_FACT, ADD_TAG). Use this BEFORE designing rules to understand which Action mutates which fact (e.g., MUTATE_FACT mutates the refVar fact and auto-generates {fact_name}__delta in generatedVariables). Data is static and changes only on engine deployment \u2014 safe to cache in-session.",
|
|
469
469
|
inputSchema: {}
|
|
470
470
|
},
|
|
471
471
|
async () => callApi("GET", "schema/action-metadata")
|
|
@@ -616,10 +616,10 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
616
616
|
|
|
617
617
|
Returns:
|
|
618
618
|
inputFacts — normalized input facts
|
|
619
|
-
mutatedFacts — input facts changed by rule actions (e.g.
|
|
620
|
-
generatedVariables —
|
|
619
|
+
mutatedFacts — input facts changed by rule actions (e.g. MUTATE_FACT mutates payment_amount)
|
|
620
|
+
generatedVariables — system-generated values; every fact in mutatedFacts gets a paired {fact_name}__delta key (signed difference)
|
|
621
621
|
executionTraces — per-rule match status
|
|
622
|
-
decisionTraces — per-rule decision (SELECTED /
|
|
622
|
+
decisionTraces — per-rule decision (SELECTED / NO_MATCH / BLOCKED / etc.)
|
|
623
623
|
|
|
624
624
|
Example input: { "facts": { "payment_amount": 100000, "customer_tier": "VIP" } }
|
|
625
625
|
Always dry-run before publishing to validate rule behavior.`,
|
|
@@ -678,7 +678,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
678
678
|
{
|
|
679
679
|
title: "Start Simulation",
|
|
680
680
|
description: dedent2`
|
|
681
|
-
Start
|
|
681
|
+
Start an Impact Simulation against historical or uploaded data.
|
|
682
682
|
|
|
683
683
|
dataset.type: "HISTORICAL" or "UPLOADED"
|
|
684
684
|
dataset.source (when HISTORICAL): "EXECUTION_LOGS"
|
|
@@ -836,7 +836,7 @@ function registerHistoryTools(server, callApi) {
|
|
|
836
836
|
"lexq_history_get",
|
|
837
837
|
{
|
|
838
838
|
title: "Get Execution Detail",
|
|
839
|
-
description: "Get full execution detail including
|
|
839
|
+
description: "Get full execution detail including inputFacts, mutatedFacts, generatedVariables, executionTraces, and decisionTraces.",
|
|
840
840
|
inputSchema: {
|
|
841
841
|
traceId: z7.string().describe("Trace ID from execution history")
|
|
842
842
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
Once policies are deployed, the engine evaluates incoming facts against live rules. This skill covers **observing**
|
|
7
|
+
Once policies are deployed, the engine evaluates incoming facts against live rules. This skill covers **observing**
|
|
8
|
+
production behavior: execution history, statistics, failure logs, and integrations.
|
|
8
9
|
|
|
9
10
|
## 1. Execution History
|
|
10
11
|
|
|
@@ -16,23 +17,23 @@ lexq history list --page 0 --size 20
|
|
|
16
17
|
|
|
17
18
|
Filter options:
|
|
18
19
|
|
|
19
|
-
| Flag
|
|
20
|
-
|
|
21
|
-
| `--trace-id <id>`
|
|
22
|
-
| `--group-id <id>`
|
|
23
|
-
| `--version-id <id>`
|
|
24
|
-
| `--status <status>`
|
|
25
|
-
| `--start-date <date>` | Start date (ISO)
|
|
26
|
-
| `--end-date <date>`
|
|
20
|
+
| Flag | Description | Example |
|
|
21
|
+
|-----------------------|------------------------|---------------------------|
|
|
22
|
+
| `--trace-id <id>` | Filter by trace ID | `--trace-id abc123` |
|
|
23
|
+
| `--group-id <id>` | Filter by policy group | `--group-id <gid>` |
|
|
24
|
+
| `--version-id <id>` | Filter by version | `--version-id <vid>` |
|
|
25
|
+
| `--status <status>` | Filter by status | `--status SUCCESS` |
|
|
26
|
+
| `--start-date <date>` | Start date (ISO) | `--start-date 2025-01-01` |
|
|
27
|
+
| `--end-date <date>` | End date (ISO) | `--end-date 2025-01-31` |
|
|
27
28
|
|
|
28
29
|
### Execution Statuses
|
|
29
30
|
|
|
30
|
-
| Status
|
|
31
|
-
|
|
32
|
-
| `SUCCESS`
|
|
33
|
-
| `NO_MATCH` | No rules matched the input facts
|
|
34
|
-
| `ERROR`
|
|
35
|
-
| `TIMEOUT`
|
|
31
|
+
| Status | Meaning |
|
|
32
|
+
|------------|------------------------------------------------|
|
|
33
|
+
| `SUCCESS` | At least one rule matched and actions executed |
|
|
34
|
+
| `NO_MATCH` | No rules matched the input facts |
|
|
35
|
+
| `ERROR` | Engine error during evaluation |
|
|
36
|
+
| `TIMEOUT` | Execution exceeded time limit |
|
|
36
37
|
|
|
37
38
|
### Get Execution Detail
|
|
38
39
|
|
|
@@ -40,7 +41,7 @@ Filter options:
|
|
|
40
41
|
lexq history get --id <executionId>
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
Returns full detail including `
|
|
44
|
+
Returns full detail including `inputFacts`, `executionTraces`, and `decisionTraces`.
|
|
44
45
|
|
|
45
46
|
### Execution Statistics
|
|
46
47
|
|
|
@@ -62,6 +63,7 @@ Returns KPI summary:
|
|
|
62
63
|
```
|
|
63
64
|
|
|
64
65
|
Use stats to monitor:
|
|
66
|
+
|
|
65
67
|
- **Success rate** — should be close to 1.0 for well-configured policies
|
|
66
68
|
- **Average latency** — should be single-digit ms under normal load
|
|
67
69
|
- **No-match count** — high values may indicate missing rules or incomplete fact definitions
|
|
@@ -78,14 +80,14 @@ lexq logs list --page 0 --size 20
|
|
|
78
80
|
|
|
79
81
|
Filter options:
|
|
80
82
|
|
|
81
|
-
| Flag
|
|
82
|
-
|
|
83
|
-
| `--category <cat>`
|
|
84
|
-
| `--task-type <type>`
|
|
85
|
-
| `--status <status>`
|
|
86
|
-
| `--keyword <kw>`
|
|
87
|
-
| `--start-date <date>` | Start date (ISO)
|
|
88
|
-
| `--end-date <date>`
|
|
83
|
+
| Flag | Description | Values |
|
|
84
|
+
|-----------------------|-----------------------------------------|----------------------------------------------------------------------|
|
|
85
|
+
| `--category <cat>` | Task category | `INTEGRATION`, `INTERNAL` |
|
|
86
|
+
| `--task-type <type>` | Task type | `COUPON_ISSUE`, `POINT_EARN`, `NOTIFICATION_SEND`, `WEBHOOK_EXECUTE` |
|
|
87
|
+
| `--status <status>` | Log status | `PENDING`, `RESOLVED`, `IGNORED` |
|
|
88
|
+
| `--keyword <kw>` | Search in refId, refSubId, errorMessage | any string |
|
|
89
|
+
| `--start-date <date>` | Start date (ISO) | `2025-01-01` |
|
|
90
|
+
| `--end-date <date>` | End date (ISO) | `2025-01-31` |
|
|
89
91
|
|
|
90
92
|
### Get Failure Log Detail
|
|
91
93
|
|
|
@@ -35,8 +35,14 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
35
35
|
"valueType": "NUMBER"
|
|
36
36
|
},
|
|
37
37
|
"actions": [{
|
|
38
|
-
"type": "
|
|
39
|
-
"parameters": {
|
|
38
|
+
"type": "MUTATE_FACT",
|
|
39
|
+
"parameters": {
|
|
40
|
+
"refVar": "payment_amount",
|
|
41
|
+
"method": "PERCENTAGE",
|
|
42
|
+
"operator": "SUB",
|
|
43
|
+
"rate": 20,
|
|
44
|
+
"rounding": { "mode": "HALF_UP", "scale": 0 }
|
|
45
|
+
}
|
|
40
46
|
}]
|
|
41
47
|
}'
|
|
42
48
|
|
|
@@ -52,8 +58,14 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
52
58
|
]
|
|
53
59
|
},
|
|
54
60
|
"actions": [{
|
|
55
|
-
"type": "
|
|
56
|
-
"parameters": {
|
|
61
|
+
"type": "MUTATE_FACT",
|
|
62
|
+
"parameters": {
|
|
63
|
+
"refVar": "payment_amount",
|
|
64
|
+
"method": "PERCENTAGE",
|
|
65
|
+
"operator": "SUB",
|
|
66
|
+
"rate": 10,
|
|
67
|
+
"rounding": { "mode": "HALF_UP", "scale": 0 }
|
|
68
|
+
}
|
|
57
69
|
}]
|
|
58
70
|
}'
|
|
59
71
|
|
|
@@ -68,17 +80,23 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
68
80
|
"valueType": "NUMBER"
|
|
69
81
|
},
|
|
70
82
|
"actions": [{
|
|
71
|
-
"type": "
|
|
72
|
-
"parameters": {
|
|
83
|
+
"type": "MUTATE_FACT",
|
|
84
|
+
"parameters": {
|
|
85
|
+
"refVar": "payment_amount",
|
|
86
|
+
"method": "PERCENTAGE",
|
|
87
|
+
"operator": "SUB",
|
|
88
|
+
"rate": 5,
|
|
89
|
+
"rounding": { "mode": "HALF_UP", "scale": 0 }
|
|
90
|
+
}
|
|
73
91
|
}]
|
|
74
92
|
}'
|
|
75
93
|
|
|
76
94
|
# 5. Validate
|
|
77
95
|
lexq analytics dry-run --version-id <vid> --debug --mock --json '{"facts":{"payment_amount":600000}}'
|
|
78
|
-
# Expected:
|
|
96
|
+
# Expected: mutatedFacts.payment_amount = 480000, generatedVariables.payment_amount__delta = -120000
|
|
79
97
|
|
|
80
98
|
lexq analytics dry-run --version-id <vid> --debug --mock --json '{"facts":{"payment_amount":200000}}'
|
|
81
|
-
# Expected:
|
|
99
|
+
# Expected: mutatedFacts.payment_amount = 180000, generatedVariables.payment_amount__delta = -20000
|
|
82
100
|
|
|
83
101
|
# 6. Deploy
|
|
84
102
|
lexq deploy publish --group-id <gid> --version-id <vid> --memo "Tiered discount v1"
|
|
@@ -160,8 +178,14 @@ lexq versions clone --group-id <gid> --version-id <v1id>
|
|
|
160
178
|
# 2. Update the discount rule in v2
|
|
161
179
|
lexq rules update --group-id <gid> --version-id <v2id> --id <ruleId> --json '{
|
|
162
180
|
"actions": [{
|
|
163
|
-
"type": "
|
|
164
|
-
"parameters": {
|
|
181
|
+
"type": "MUTATE_FACT",
|
|
182
|
+
"parameters": {
|
|
183
|
+
"refVar": "payment_amount",
|
|
184
|
+
"method": "PERCENTAGE",
|
|
185
|
+
"operator": "SUB",
|
|
186
|
+
"rate": 15,
|
|
187
|
+
"rounding": { "mode": "HALF_UP", "scale": 0 }
|
|
188
|
+
}
|
|
165
189
|
}]
|
|
166
190
|
}'
|
|
167
191
|
|
|
@@ -205,10 +229,11 @@ lexq versions create --group-id <gid> --json '{"commitMessage": "Points program
|
|
|
205
229
|
|
|
206
230
|
lexq facts create --key purchase_amount --name "Purchase Amount" --type NUMBER --required
|
|
207
231
|
lexq facts create --key is_first_purchase --name "First Purchase" --type BOOLEAN
|
|
232
|
+
lexq facts create --key total_points --name "Total Points" --type NUMBER
|
|
208
233
|
|
|
209
|
-
#
|
|
234
|
+
# Bonus points for first purchase (fixed 200)
|
|
210
235
|
lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
211
|
-
"name": "First Purchase
|
|
236
|
+
"name": "First Purchase Bonus Points",
|
|
212
237
|
"priority": 0,
|
|
213
238
|
"condition": {
|
|
214
239
|
"type": "SINGLE",
|
|
@@ -218,12 +243,30 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
218
243
|
"valueType": "BOOLEAN"
|
|
219
244
|
},
|
|
220
245
|
"actions": [
|
|
221
|
-
{
|
|
222
|
-
|
|
246
|
+
{
|
|
247
|
+
"type": "INCREMENT_FACT",
|
|
248
|
+
"parameters": {
|
|
249
|
+
"targetVar": "total_points",
|
|
250
|
+
"method": "AMOUNT",
|
|
251
|
+
"value": 200,
|
|
252
|
+
"rounding": { "mode": "HALF_UP", "scale": 0 }
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"type": "EMIT_NOTIFICATION",
|
|
257
|
+
"parameters": {
|
|
258
|
+
"integrationId": "<notification-integration-uuid>",
|
|
259
|
+
"target": "user_id",
|
|
260
|
+
"notificationPayload": {
|
|
261
|
+
"channel": "PUSH",
|
|
262
|
+
"templateId": "welcome_points"
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
223
266
|
]
|
|
224
267
|
}'
|
|
225
268
|
|
|
226
|
-
# Standard points
|
|
269
|
+
# Standard points: 0.1% of purchase_amount = 1 point per 1000 KRW
|
|
227
270
|
lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
228
271
|
"name": "Standard Purchase Points",
|
|
229
272
|
"priority": 1,
|
|
@@ -235,7 +278,16 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
235
278
|
"valueType": "NUMBER"
|
|
236
279
|
},
|
|
237
280
|
"actions": [
|
|
238
|
-
{
|
|
281
|
+
{
|
|
282
|
+
"type": "INCREMENT_FACT",
|
|
283
|
+
"parameters": {
|
|
284
|
+
"targetVar": "total_points",
|
|
285
|
+
"refVar": "purchase_amount",
|
|
286
|
+
"method": "PERCENTAGE",
|
|
287
|
+
"rate": 0.1,
|
|
288
|
+
"rounding": { "mode": "FLOOR", "scale": 0 }
|
|
289
|
+
}
|
|
290
|
+
}
|
|
239
291
|
]
|
|
240
292
|
}'
|
|
241
293
|
```
|
|
@@ -267,7 +319,17 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
267
319
|
"valueType": "NUMBER"
|
|
268
320
|
},
|
|
269
321
|
"actions": [
|
|
270
|
-
{
|
|
322
|
+
{
|
|
323
|
+
"type": "EMIT_WEBHOOK",
|
|
324
|
+
"parameters": {
|
|
325
|
+
"url": "https://api.example.com/webhooks/orders",
|
|
326
|
+
"payloadTemplate": {
|
|
327
|
+
"event": "rule_matched",
|
|
328
|
+
"rule": "{{ruleName}}",
|
|
329
|
+
"amount": "{{output.order_total}}"
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
},
|
|
271
333
|
{ "type": "ADD_TAG", "parameters": { "tag": "large_order" } }
|
|
272
334
|
]
|
|
273
335
|
}'
|
|
@@ -289,7 +351,16 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
289
351
|
"condition": {
|
|
290
352
|
"type": "SINGLE", "field": "customer_tier", "operator": "EQUALS", "value": "VIP", "valueType": "STRING"
|
|
291
353
|
},
|
|
292
|
-
"actions": [{
|
|
354
|
+
"actions": [{
|
|
355
|
+
"type": "MUTATE_FACT",
|
|
356
|
+
"parameters": {
|
|
357
|
+
"refVar": "payment_amount",
|
|
358
|
+
"method": "PERCENTAGE",
|
|
359
|
+
"operator": "SUB",
|
|
360
|
+
"rate": 20,
|
|
361
|
+
"rounding": { "mode": "HALF_UP", "scale": 0 }
|
|
362
|
+
}
|
|
363
|
+
}]
|
|
293
364
|
}'
|
|
294
365
|
|
|
295
366
|
lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
@@ -301,7 +372,16 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
301
372
|
"condition": {
|
|
302
373
|
"type": "SINGLE", "field": "payment_amount", "operator": "GREATER_THAN_OR_EQUAL", "value": 50000, "valueType": "NUMBER"
|
|
303
374
|
},
|
|
304
|
-
"actions": [{
|
|
375
|
+
"actions": [{
|
|
376
|
+
"type": "MUTATE_FACT",
|
|
377
|
+
"parameters": {
|
|
378
|
+
"refVar": "payment_amount",
|
|
379
|
+
"method": "PERCENTAGE",
|
|
380
|
+
"operator": "SUB",
|
|
381
|
+
"rate": 15,
|
|
382
|
+
"rounding": { "mode": "HALF_UP", "scale": 0 }
|
|
383
|
+
}
|
|
384
|
+
}]
|
|
305
385
|
}'
|
|
306
386
|
|
|
307
387
|
# If a VIP customer pays 50000+, only the 20% VIP discount fires (priority 0 wins).
|
|
@@ -322,7 +402,16 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
322
402
|
"condition": {
|
|
323
403
|
"type": "SINGLE", "field": "user_region", "operator": "IN", "value": ["KR"], "valueType": "LIST_STRING"
|
|
324
404
|
},
|
|
325
|
-
"actions": [{
|
|
405
|
+
"actions": [{
|
|
406
|
+
"type": "EMIT_EVENT",
|
|
407
|
+
"parameters": {
|
|
408
|
+
"integrationId": "<coupon-integration-uuid>",
|
|
409
|
+
"eventPayload": {
|
|
410
|
+
"couponId": "KR_WELCOME_2025",
|
|
411
|
+
"expiryDays": 30
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}]
|
|
326
415
|
}'
|
|
327
416
|
|
|
328
417
|
lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
@@ -331,7 +420,16 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
331
420
|
"condition": {
|
|
332
421
|
"type": "SINGLE", "field": "user_region", "operator": "IN", "value": ["US"], "valueType": "LIST_STRING"
|
|
333
422
|
},
|
|
334
|
-
"actions": [{
|
|
423
|
+
"actions": [{
|
|
424
|
+
"type": "EMIT_EVENT",
|
|
425
|
+
"parameters": {
|
|
426
|
+
"integrationId": "<coupon-integration-uuid>",
|
|
427
|
+
"eventPayload": {
|
|
428
|
+
"couponId": "US_WELCOME_2025",
|
|
429
|
+
"expiryDays": 14
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}]
|
|
335
433
|
}'
|
|
336
434
|
```
|
|
337
435
|
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
A **Policy Rule** is a condition → actions pair within a version. Rules are evaluated in priority order (0 = highest).
|
|
7
|
+
A **Policy Rule** is a condition → actions pair within a version. Rules are evaluated in priority order (0 = highest).
|
|
8
|
+
When a rule's condition matches the input facts, its actions fire.
|
|
8
9
|
|
|
9
10
|
## Rule Structure
|
|
10
11
|
|
|
@@ -12,8 +13,12 @@ A **Policy Rule** is a condition → actions pair within a version. Rules are ev
|
|
|
12
13
|
{
|
|
13
14
|
"name": "VIP 10% Discount",
|
|
14
15
|
"priority": 0,
|
|
15
|
-
"condition": {
|
|
16
|
-
|
|
16
|
+
"condition": {
|
|
17
|
+
...
|
|
18
|
+
},
|
|
19
|
+
"actions": [
|
|
20
|
+
...
|
|
21
|
+
],
|
|
17
22
|
"mutexGroup": null,
|
|
18
23
|
"mutexMode": "NONE",
|
|
19
24
|
"mutexStrategy": "FIRST_MATCH",
|
|
@@ -65,27 +70,27 @@ Conditions use a tree structure with two node types: `SINGLE` and `GROUP`.
|
|
|
65
70
|
|
|
66
71
|
### Operators
|
|
67
72
|
|
|
68
|
-
| Operator
|
|
69
|
-
|
|
70
|
-
| `EQUALS`
|
|
71
|
-
| `NOT_EQUALS`
|
|
72
|
-
| `GREATER_THAN`
|
|
73
|
-
| `GREATER_THAN_OR_EQUAL` | NUMBER
|
|
74
|
-
| `LESS_THAN`
|
|
75
|
-
| `LESS_THAN_OR_EQUAL`
|
|
76
|
-
| `CONTAINS`
|
|
77
|
-
| `IN`
|
|
78
|
-
| `NOT_IN`
|
|
73
|
+
| Operator | Types | Description |
|
|
74
|
+
|-------------------------|----------------|-----------------------------------|
|
|
75
|
+
| `EQUALS` | all | Exact match |
|
|
76
|
+
| `NOT_EQUALS` | all | Negation |
|
|
77
|
+
| `GREATER_THAN` | NUMBER | `>` |
|
|
78
|
+
| `GREATER_THAN_OR_EQUAL` | NUMBER | `>=` |
|
|
79
|
+
| `LESS_THAN` | NUMBER | `<` |
|
|
80
|
+
| `LESS_THAN_OR_EQUAL` | NUMBER | `<=` |
|
|
81
|
+
| `CONTAINS` | STRING | Substring match |
|
|
82
|
+
| `IN` | STRING, NUMBER | Value is in the provided list |
|
|
83
|
+
| `NOT_IN` | STRING, NUMBER | Value is not in the provided list |
|
|
79
84
|
|
|
80
85
|
### Value Types
|
|
81
86
|
|
|
82
|
-
| Type
|
|
83
|
-
|
|
84
|
-
| `STRING`
|
|
85
|
-
| `NUMBER`
|
|
86
|
-
| `BOOLEAN`
|
|
87
|
-
| `LIST_STRING` | `["a","b"]`
|
|
88
|
-
| `LIST_NUMBER` | `[1,2]`
|
|
87
|
+
| Type | JSON Value | Example |
|
|
88
|
+
|---------------|--------------|------------------|
|
|
89
|
+
| `STRING` | `"string"` | `"VIP"` |
|
|
90
|
+
| `NUMBER` | `number` | `100000` |
|
|
91
|
+
| `BOOLEAN` | `true/false` | `true` |
|
|
92
|
+
| `LIST_STRING` | `["a","b"]` | `["KR","US"]` |
|
|
93
|
+
| `LIST_NUMBER` | `[1,2]` | `[10000, 20000]` |
|
|
89
94
|
|
|
90
95
|
### Nested Conditions Example
|
|
91
96
|
|
|
@@ -100,12 +105,31 @@ Conditions use a tree structure with two node types: `SINGLE` and `GROUP`.
|
|
|
100
105
|
"type": "GROUP",
|
|
101
106
|
"operator": "AND",
|
|
102
107
|
"children": [
|
|
103
|
-
{
|
|
104
|
-
|
|
108
|
+
{
|
|
109
|
+
"type": "SINGLE",
|
|
110
|
+
"field": "customer_tier",
|
|
111
|
+
"operator": "EQUALS",
|
|
112
|
+
"value": "VIP",
|
|
113
|
+
"valueType": "STRING"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"type": "SINGLE",
|
|
117
|
+
"field": "payment_amount",
|
|
118
|
+
"operator": "GREATER_THAN_OR_EQUAL",
|
|
119
|
+
"value": 100000,
|
|
120
|
+
"valueType": "NUMBER"
|
|
121
|
+
}
|
|
105
122
|
]
|
|
106
123
|
},
|
|
107
124
|
{
|
|
108
|
-
"type": "SINGLE",
|
|
125
|
+
"type": "SINGLE",
|
|
126
|
+
"field": "region",
|
|
127
|
+
"operator": "IN",
|
|
128
|
+
"value": [
|
|
129
|
+
"KR",
|
|
130
|
+
"JP"
|
|
131
|
+
],
|
|
132
|
+
"valueType": "LIST_STRING"
|
|
109
133
|
}
|
|
110
134
|
]
|
|
111
135
|
}
|
|
@@ -115,26 +139,34 @@ Conditions use a tree structure with two node types: `SINGLE` and `GROUP`.
|
|
|
115
139
|
|
|
116
140
|
Each rule can have multiple actions. Actions fire sequentially.
|
|
117
141
|
|
|
118
|
-
| Type
|
|
119
|
-
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
| `BLOCK`
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `SET_FACT`
|
|
127
|
-
| `ADD_TAG`
|
|
142
|
+
| Type | Description | Key Parameters |
|
|
143
|
+
|---------------------|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
|
|
144
|
+
| `MUTATE_FACT` | Mutate a fact value (arithmetic) | `refVar`, `method` (PERCENTAGE/AMOUNT), `operator` (ADD/SUB/MUL/DIV), `rate` or `value`, `rounding` |
|
|
145
|
+
| `INCREMENT_FACT` | Increment a fact (cumulative add) | `targetVar`, `refVar`, `method`, `value` or `rate`, `rounding` |
|
|
146
|
+
| `EMIT_EVENT` | Emit an event to an external integration (coupons, etc.) | `integrationId`, `eventPayload` (Map) |
|
|
147
|
+
| `BLOCK` | Block the transaction | `reason`, `code` |
|
|
148
|
+
| `EMIT_NOTIFICATION` | Send a notification | `integrationId`, `target`, `notificationPayload` (Map) |
|
|
149
|
+
| `EMIT_WEBHOOK` | Call an external URL | `url`, `payloadTemplate` |
|
|
150
|
+
| `SET_FACT` | Set a fact value (literal assignment) | `key`, `value` |
|
|
151
|
+
| `ADD_TAG` | Add a tag to the result | `tag` |
|
|
128
152
|
|
|
129
|
-
### Action Example: 10%
|
|
153
|
+
### Action Example: 10% Discount via MUTATE_FACT
|
|
154
|
+
|
|
155
|
+
Reduces `payment_amount` by 10%. `__delta` is auto-generated in `generatedVariables` (e.g.,
|
|
156
|
+
`payment_amount__delta: -10000` for a 100,000 input).
|
|
130
157
|
|
|
131
158
|
```json
|
|
132
159
|
{
|
|
133
|
-
"type": "
|
|
160
|
+
"type": "MUTATE_FACT",
|
|
134
161
|
"parameters": {
|
|
162
|
+
"refVar": "payment_amount",
|
|
135
163
|
"method": "PERCENTAGE",
|
|
164
|
+
"operator": "SUB",
|
|
136
165
|
"rate": 10,
|
|
137
|
-
"
|
|
166
|
+
"rounding": {
|
|
167
|
+
"mode": "HALF_UP",
|
|
168
|
+
"scale": 0
|
|
169
|
+
}
|
|
138
170
|
}
|
|
139
171
|
}
|
|
140
172
|
```
|
|
@@ -183,8 +215,14 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
183
215
|
},
|
|
184
216
|
"actions": [
|
|
185
217
|
{
|
|
186
|
-
"type": "
|
|
187
|
-
"parameters": {
|
|
218
|
+
"type": "MUTATE_FACT",
|
|
219
|
+
"parameters": {
|
|
220
|
+
"refVar": "payment_amount",
|
|
221
|
+
"method": "PERCENTAGE",
|
|
222
|
+
"operator": "SUB",
|
|
223
|
+
"rate": 10,
|
|
224
|
+
"rounding": { "mode": "HALF_UP", "scale": 0 }
|
|
225
|
+
}
|
|
188
226
|
}
|
|
189
227
|
],
|
|
190
228
|
"isEnabled": true
|
|
@@ -198,8 +236,14 @@ lexq rules update --group-id <gid> --version-id <vid> --id <ruleId> --json '{
|
|
|
198
236
|
"name": "VIP 15% Discount",
|
|
199
237
|
"actions": [
|
|
200
238
|
{
|
|
201
|
-
"type": "
|
|
202
|
-
"parameters": {
|
|
239
|
+
"type": "MUTATE_FACT",
|
|
240
|
+
"parameters": {
|
|
241
|
+
"refVar": "payment_amount",
|
|
242
|
+
"method": "PERCENTAGE",
|
|
243
|
+
"operator": "SUB",
|
|
244
|
+
"rate": 15,
|
|
245
|
+
"rounding": { "mode": "HALF_UP", "scale": 0 }
|
|
246
|
+
}
|
|
203
247
|
}
|
|
204
248
|
]
|
|
205
249
|
}'
|
|
@@ -234,11 +278,11 @@ lexq rules toggle --group-id <gid> --version-id <vid> --id <ruleId> --enabled fa
|
|
|
234
278
|
|
|
235
279
|
Within a single version, rules can belong to a `mutexGroup` to limit how many fire.
|
|
236
280
|
|
|
237
|
-
| mutexMode
|
|
238
|
-
|
|
239
|
-
| `NONE`
|
|
240
|
-
| `EXCLUSIVE` | Only one rule per mutex group fires
|
|
241
|
-
| `MAX_N`
|
|
281
|
+
| mutexMode | Behavior |
|
|
282
|
+
|-------------|-----------------------------------------------|
|
|
283
|
+
| `NONE` | All matching rules fire (default) |
|
|
284
|
+
| `EXCLUSIVE` | Only one rule per mutex group fires |
|
|
285
|
+
| `MAX_N` | Up to `mutexLimit` rules per mutex group fire |
|
|
242
286
|
|
|
243
287
|
```bash
|
|
244
288
|
lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
LexQ provides three levels of pre-deploy validation:
|
|
8
8
|
|
|
9
|
-
| Tool
|
|
10
|
-
|
|
11
|
-
| **Dry Run**
|
|
12
|
-
| **Dry Run Compare** | Single input, two versions | Side-by-side version comparison
|
|
13
|
-
| **Simulation**
|
|
9
|
+
| Tool | Scope | When to Use |
|
|
10
|
+
|---------------------|----------------------------|------------------------------------|
|
|
11
|
+
| **Dry Run** | Single input | Quick validation of one scenario |
|
|
12
|
+
| **Dry Run Compare** | Single input, two versions | Side-by-side version comparison |
|
|
13
|
+
| **Simulation** | Batch (historical data) | Full regression test before deploy |
|
|
14
14
|
|
|
15
15
|
**Golden rule:** Always dry-run before publishing, always simulate before deploying to production.
|
|
16
16
|
|
|
@@ -30,11 +30,29 @@ Response includes:
|
|
|
30
30
|
"versionId": "...",
|
|
31
31
|
"versionNo": 3,
|
|
32
32
|
"requiredFacts": [
|
|
33
|
-
{
|
|
34
|
-
|
|
33
|
+
{
|
|
34
|
+
"key": "payment_amount",
|
|
35
|
+
"type": "NUMBER",
|
|
36
|
+
"required": true,
|
|
37
|
+
"usedBy": [
|
|
38
|
+
"VIP Discount",
|
|
39
|
+
"Premium Block"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"key": "customer_tier",
|
|
44
|
+
"type": "STRING",
|
|
45
|
+
"required": true,
|
|
46
|
+
"usedBy": [
|
|
47
|
+
"VIP Discount"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
35
50
|
],
|
|
36
51
|
"exampleRequest": {
|
|
37
|
-
"facts": {
|
|
52
|
+
"facts": {
|
|
53
|
+
"payment_amount": 0,
|
|
54
|
+
"customer_tier": ""
|
|
55
|
+
},
|
|
38
56
|
"context": {}
|
|
39
57
|
}
|
|
40
58
|
}
|
|
@@ -57,11 +75,11 @@ lexq analytics dry-run --version-id <vid> --json '{
|
|
|
57
75
|
|
|
58
76
|
### Options
|
|
59
77
|
|
|
60
|
-
| Flag
|
|
61
|
-
|
|
62
|
-
| `--debug`
|
|
63
|
-
| `--mock`
|
|
64
|
-
| `--file <path>` | Read request body from file instead of `--json`
|
|
78
|
+
| Flag | Description | Default |
|
|
79
|
+
|-----------------|-----------------------------------------------------|---------|
|
|
80
|
+
| `--debug` | Include execution traces (which rules matched, why) | `false` |
|
|
81
|
+
| `--mock` | Mock external calls (webhooks, integrations) | `false` |
|
|
82
|
+
| `--file <path>` | Read request body from file instead of `--json` | — |
|
|
65
83
|
|
|
66
84
|
### Recommended: Always Use `--debug --mock`
|
|
67
85
|
|
|
@@ -75,53 +93,77 @@ lexq analytics dry-run --version-id <vid> --debug --mock --json '{
|
|
|
75
93
|
|
|
76
94
|
```json
|
|
77
95
|
{
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
{
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
"result": "SUCCESS",
|
|
97
|
+
"data": {
|
|
98
|
+
"inputFacts": {
|
|
99
|
+
"payment_amount": 150000,
|
|
100
|
+
"customer_tier": "VIP"
|
|
101
|
+
},
|
|
102
|
+
"mutatedFacts": {
|
|
103
|
+
"payment_amount": 135000,
|
|
104
|
+
"customer_tier": "VIP"
|
|
105
|
+
},
|
|
106
|
+
"generatedVariables": {
|
|
107
|
+
"payment_amount__delta": -15000
|
|
108
|
+
},
|
|
109
|
+
"executionTraces": [
|
|
110
|
+
{
|
|
111
|
+
"traceId": "...",
|
|
112
|
+
"ruleId": "...",
|
|
113
|
+
"ruleName": "VIP 10% Discount",
|
|
114
|
+
"matched": true,
|
|
115
|
+
"matchExpression": "(customer_tier == VIP AND payment_amount >= 100000)",
|
|
116
|
+
"generatedActions": [
|
|
117
|
+
{
|
|
118
|
+
"type": "MUTATE_FACT",
|
|
119
|
+
"parameters": {
|
|
120
|
+
...
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"decisionTraces": [
|
|
127
|
+
{
|
|
128
|
+
"ruleId": "...",
|
|
129
|
+
"ruleName": "VIP 10% Discount",
|
|
130
|
+
"status": "SELECTED",
|
|
131
|
+
"reasonCode": "FINAL_WINNER",
|
|
132
|
+
"reasonDetail": "..."
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"latencyMs": 12,
|
|
136
|
+
"versionNo": 3
|
|
137
|
+
}
|
|
100
138
|
}
|
|
101
139
|
```
|
|
102
140
|
|
|
141
|
+
- `mutatedFacts` — input facts changed by rule actions (e.g., `MUTATE_FACT` reduces `payment_amount`)
|
|
142
|
+
- `generatedVariables` — for every fact in `mutatedFacts`, a paired `{fact_name}__delta` key is auto-generated with the
|
|
143
|
+
signed difference (negative = decrease, positive = increase)
|
|
144
|
+
|
|
103
145
|
### Reading Decision Traces
|
|
104
146
|
|
|
105
|
-
| Status
|
|
106
|
-
|
|
107
|
-
| `SELECTED`
|
|
108
|
-
| `NO_MATCH`
|
|
109
|
-
| `NOT_SELECTED`
|
|
110
|
-
| `BLOCKED_MUTEX` | Blocked by mutex group constraint
|
|
111
|
-
| `LOST_PRIORITY` | Lost to a higher-priority rule
|
|
112
|
-
| `DROPPED_LIMIT` | Execution limit reached
|
|
113
|
-
| `ERROR`
|
|
147
|
+
| Status | Meaning |
|
|
148
|
+
|-----------------|---------------------------------------------|
|
|
149
|
+
| `SELECTED` | Rule matched and its actions fired |
|
|
150
|
+
| `NO_MATCH` | Condition did not match the input |
|
|
151
|
+
| `NOT_SELECTED` | Matched but excluded by conflict resolution |
|
|
152
|
+
| `BLOCKED_MUTEX` | Blocked by mutex group constraint |
|
|
153
|
+
| `LOST_PRIORITY` | Lost to a higher-priority rule |
|
|
154
|
+
| `DROPPED_LIMIT` | Execution limit reached |
|
|
155
|
+
| `ERROR` | Rule evaluation failed |
|
|
114
156
|
|
|
115
157
|
### Reading Reason Codes
|
|
116
158
|
|
|
117
|
-
| Code
|
|
118
|
-
|
|
119
|
-
| `FINAL_WINNER`
|
|
120
|
-
| `CONDITION_MISMATCH`
|
|
159
|
+
| Code | Meaning |
|
|
160
|
+
|-----------------------|----------------------------------------------------------|
|
|
161
|
+
| `FINAL_WINNER` | Successfully executed |
|
|
162
|
+
| `CONDITION_MISMATCH` | Input facts didn't satisfy the condition |
|
|
121
163
|
| `MUTEX_PRIORITY_LOST` | Another rule in the same mutex group had higher priority |
|
|
122
|
-
| `MUTEX_LIMIT_REACHED` | Mutex group's max rules already fired
|
|
123
|
-
| `GROUP_LIMIT_REACHED` | Group's `executionLimit` reached
|
|
124
|
-
| `ACTION_ERROR`
|
|
164
|
+
| `MUTEX_LIMIT_REACHED` | Mutex group's max rules already fired |
|
|
165
|
+
| `GROUP_LIMIT_REACHED` | Group's `executionLimit` reached |
|
|
166
|
+
| `ACTION_ERROR` | Action execution failed (e.g., webhook timeout) |
|
|
125
167
|
|
|
126
168
|
## 3. Dry Run Compare
|
|
127
169
|
|
|
@@ -137,7 +179,7 @@ lexq analytics dry-run-compare --json '{
|
|
|
137
179
|
|
|
138
180
|
Useful for validating that changes in a new version produce expected differences.
|
|
139
181
|
|
|
140
|
-
## 4.
|
|
182
|
+
## 4. Impact Simulation
|
|
141
183
|
|
|
142
184
|
Run a full regression test against historical execution data:
|
|
143
185
|
|
|
@@ -157,7 +199,7 @@ lexq analytics simulation start --json '{
|
|
|
157
199
|
"maxRecords": 10000,
|
|
158
200
|
"baselinePolicyVersionId": "<currentLiveVersionId>",
|
|
159
201
|
"metricConfig": {
|
|
160
|
-
"targetVariable": "
|
|
202
|
+
"targetVariable": "payment_amount__delta",
|
|
161
203
|
"aggregationType": "SUM"
|
|
162
204
|
}
|
|
163
205
|
}
|
|
@@ -166,13 +208,14 @@ lexq analytics simulation start --json '{
|
|
|
166
208
|
|
|
167
209
|
### Dataset Types
|
|
168
210
|
|
|
169
|
-
| Type
|
|
170
|
-
|
|
171
|
-
| `HISTORICAL` | `EXECUTION_LOGS` | Replay past executions from a date range
|
|
172
|
-
| `MANUAL`
|
|
173
|
-
| `UPLOADED`
|
|
211
|
+
| Type | Source | Description |
|
|
212
|
+
|--------------|------------------|-------------------------------------------|
|
|
213
|
+
| `HISTORICAL` | `EXECUTION_LOGS` | Replay past executions from a date range |
|
|
214
|
+
| `MANUAL` | `REQUEST_BODY` | Provide `manualData` array in the request |
|
|
215
|
+
| `UPLOADED` | `S3_BUCKET` | Reference an uploaded dataset by `path` |
|
|
174
216
|
|
|
175
217
|
### File Upload Dataset
|
|
218
|
+
|
|
176
219
|
```bash
|
|
177
220
|
# 1. Download template (optional)
|
|
178
221
|
lexq analytics dataset template \
|
|
@@ -212,13 +255,13 @@ lexq analytics simulation status --id <simulationId>
|
|
|
212
255
|
|
|
213
256
|
Simulation is async. Poll until `status` is `COMPLETED` or `FAILED`.
|
|
214
257
|
|
|
215
|
-
| Status
|
|
216
|
-
|
|
217
|
-
| `PENDING`
|
|
218
|
-
| `RUNNING`
|
|
219
|
-
| `COMPLETED` | Done — results available
|
|
220
|
-
| `FAILED`
|
|
221
|
-
| `CANCELLED` | Manually cancelled
|
|
258
|
+
| Status | Meaning |
|
|
259
|
+
|-------------|--------------------------------------------|
|
|
260
|
+
| `PENDING` | Queued |
|
|
261
|
+
| `RUNNING` | In progress (`progress` field shows 0–100) |
|
|
262
|
+
| `COMPLETED` | Done — results available |
|
|
263
|
+
| `FAILED` | Error occurred |
|
|
264
|
+
| `CANCELLED` | Manually cancelled |
|
|
222
265
|
|
|
223
266
|
### List Simulations
|
|
224
267
|
|
|
@@ -254,7 +297,7 @@ lexq analytics simulation export --id <simulationId> --format csv --output resul
|
|
|
254
297
|
"matchRate": 0.85
|
|
255
298
|
},
|
|
256
299
|
"metricSummary": {
|
|
257
|
-
"targetVariable": "
|
|
300
|
+
"targetVariable": "payment_amount__delta",
|
|
258
301
|
"aggregationType": "SUM",
|
|
259
302
|
"baselineValue": 5000000,
|
|
260
303
|
"simulatedValue": 4500000,
|
|
@@ -273,7 +316,12 @@ lexq analytics simulation export --id <simulationId> --format csv --output resul
|
|
|
273
316
|
}
|
|
274
317
|
},
|
|
275
318
|
"ruleStats": [
|
|
276
|
-
{
|
|
319
|
+
{
|
|
320
|
+
"ruleId": "...",
|
|
321
|
+
"ruleName": "VIP Discount",
|
|
322
|
+
"matchedCount": 5000,
|
|
323
|
+
"metricValue": 3000000
|
|
324
|
+
}
|
|
277
325
|
]
|
|
278
326
|
}
|
|
279
327
|
```
|
|
@@ -299,7 +347,7 @@ lexq analytics simulation start --json '{
|
|
|
299
347
|
"options": {
|
|
300
348
|
"baselinePolicyVersionId": "<currentLiveVersionId>",
|
|
301
349
|
"includeRuleStats": true,
|
|
302
|
-
"metricConfig": { "targetVariable": "
|
|
350
|
+
"metricConfig": { "targetVariable": "payment_amount__delta", "aggregationType": "SUM" }
|
|
303
351
|
}
|
|
304
352
|
}'
|
|
305
353
|
|