@lexq/cli 0.1.22 → 0.1.25
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 +186 -6
- package/dist/mcp/register.d.ts +1 -1
- package/dist/mcp/register.js +51 -5
- package/package.json +11 -9
- 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
|
@@ -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,8 +3447,8 @@ 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
3453
|
decisionTraces — per-rule decision (SELECTED / NO_MATCH / BLOCKED / etc.)
|
|
3454
3454
|
|
|
@@ -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
|
}
|
|
@@ -3910,6 +3910,51 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
3910
3910
|
);
|
|
3911
3911
|
}
|
|
3912
3912
|
|
|
3913
|
+
// src/mcp/tools/domain-templates.ts
|
|
3914
|
+
import { z as z11 } from "zod";
|
|
3915
|
+
function registerDomainTemplateTools(server, callApi) {
|
|
3916
|
+
server.registerTool(
|
|
3917
|
+
"lexq_domain_templates_list",
|
|
3918
|
+
{
|
|
3919
|
+
title: "List Domain Templates",
|
|
3920
|
+
description: "List all domain templates. A domain template is a curated, industry-specific starter pack of fact definitions and sample rules (e.g. ECOMMERCE). Each entry reports its key, status (ACTIVE or COMING_SOON), and a summary of what it provisions. Call this before preview or apply to discover which templates can currently be applied.",
|
|
3921
|
+
inputSchema: {}
|
|
3922
|
+
},
|
|
3923
|
+
async () => callApi("GET", "domain-templates")
|
|
3924
|
+
);
|
|
3925
|
+
server.registerTool(
|
|
3926
|
+
"lexq_domain_templates_preview",
|
|
3927
|
+
{
|
|
3928
|
+
title: "Preview Domain Template",
|
|
3929
|
+
description: "Preview exactly what a domain template will provision before applying it: the fact definitions it registers, the sample rules it creates, and an apply plan. This is a read-only dry run \u2014 nothing is created. Only ACTIVE templates can be previewed.",
|
|
3930
|
+
inputSchema: {
|
|
3931
|
+
template: z11.string().describe(
|
|
3932
|
+
"Domain template key (e.g. ECOMMERCE). Use lexq_domain_templates_list to see available keys \u2014 currently only ECOMMERCE is ACTIVE."
|
|
3933
|
+
)
|
|
3934
|
+
}
|
|
3935
|
+
},
|
|
3936
|
+
async ({ template }) => callApi("GET", `domain-templates/${template}/preview`)
|
|
3937
|
+
);
|
|
3938
|
+
server.registerTool(
|
|
3939
|
+
"lexq_domain_templates_apply",
|
|
3940
|
+
{
|
|
3941
|
+
title: "Apply Domain Template",
|
|
3942
|
+
description: "Apply a domain template to the current tenant. Creates the template's fact definitions and a new policy group pre-populated with its sample rules as a DRAFT version. Existing facts are skipped \u2014 apply is additive and never overwrites existing schema. Run lexq_domain_templates_preview first to review what will be created. Only ACTIVE templates can be applied.",
|
|
3943
|
+
inputSchema: {
|
|
3944
|
+
template: z11.string().describe("Domain template key to apply (e.g. ECOMMERCE)."),
|
|
3945
|
+
customName: z11.string().optional().describe(
|
|
3946
|
+
"Optional custom name for the policy group that gets created. If omitted, the template's default name is used."
|
|
3947
|
+
)
|
|
3948
|
+
}
|
|
3949
|
+
},
|
|
3950
|
+
async ({ template, customName }) => {
|
|
3951
|
+
const body = {};
|
|
3952
|
+
if (customName !== void 0) body.customName = customName;
|
|
3953
|
+
return callApi("POST", `domain-templates/${template}/apply`, { body });
|
|
3954
|
+
}
|
|
3955
|
+
);
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3913
3958
|
// src/mcp/register.ts
|
|
3914
3959
|
function registerAllTools(server, callApi) {
|
|
3915
3960
|
registerStatusTools(server, callApi);
|
|
@@ -3922,6 +3967,7 @@ function registerAllTools(server, callApi) {
|
|
|
3922
3967
|
registerHistoryTools(server, callApi);
|
|
3923
3968
|
registerIntegrationTools(server, callApi);
|
|
3924
3969
|
registerLogTools(server, callApi);
|
|
3970
|
+
registerDomainTemplateTools(server, callApi);
|
|
3925
3971
|
registerWebhookSubscriptionTools(server, callApi);
|
|
3926
3972
|
}
|
|
3927
3973
|
|
|
@@ -3979,6 +4025,137 @@ function registerServeCommand(program) {
|
|
|
3979
4025
|
});
|
|
3980
4026
|
}
|
|
3981
4027
|
|
|
4028
|
+
// src/commands/domain-templates.ts
|
|
4029
|
+
import "commander";
|
|
4030
|
+
import dedent16 from "dedent";
|
|
4031
|
+
function registerDomainTemplateCommands(program) {
|
|
4032
|
+
const templates = program.command("domain-templates").description("Browse and apply domain templates").addHelpText(
|
|
4033
|
+
"after",
|
|
4034
|
+
dedent16`
|
|
4035
|
+
|
|
4036
|
+
A domain template is an industry-specific starter pack of fact
|
|
4037
|
+
definitions and sample rules. Applying one provisions a ready-to-use
|
|
4038
|
+
policy group so you start from a working baseline instead of an
|
|
4039
|
+
empty schema.
|
|
4040
|
+
|
|
4041
|
+
Commands:
|
|
4042
|
+
list List available domain templates
|
|
4043
|
+
preview Preview the facts and rules a template provisions
|
|
4044
|
+
apply Apply a template to the current tenant
|
|
4045
|
+
|
|
4046
|
+
Workflow: list → preview → apply
|
|
4047
|
+
Currently available: ECOMMERCE (FINTECH, SAAS — coming soon)
|
|
4048
|
+
`
|
|
4049
|
+
);
|
|
4050
|
+
templates.command("list").description("List available domain templates").action(async () => {
|
|
4051
|
+
try {
|
|
4052
|
+
const globalOpts = program.opts();
|
|
4053
|
+
const format = globalOpts.format ?? "json";
|
|
4054
|
+
const data = await apiRequest("GET", "domain-templates", {
|
|
4055
|
+
apiKey: globalOpts.apiKey,
|
|
4056
|
+
baseUrl: globalOpts.baseUrl,
|
|
4057
|
+
dryRun: globalOpts.dryRun,
|
|
4058
|
+
verbose: globalOpts.verbose
|
|
4059
|
+
});
|
|
4060
|
+
if (format === "table") {
|
|
4061
|
+
printTable(
|
|
4062
|
+
["Template", "Name", "Facts", "Rules", "Available"],
|
|
4063
|
+
data.map((t) => [
|
|
4064
|
+
t.template,
|
|
4065
|
+
t.displayName,
|
|
4066
|
+
String(t.factCount),
|
|
4067
|
+
String(t.ruleCount),
|
|
4068
|
+
t.isAvailable ? "\u2713" : "\u2013"
|
|
4069
|
+
]),
|
|
4070
|
+
{ truncate: 32 }
|
|
4071
|
+
);
|
|
4072
|
+
} else {
|
|
4073
|
+
printJson(data);
|
|
4074
|
+
}
|
|
4075
|
+
} catch (error) {
|
|
4076
|
+
printError(error);
|
|
4077
|
+
process.exit(1);
|
|
4078
|
+
}
|
|
4079
|
+
});
|
|
4080
|
+
templates.command("preview").description("Preview what a domain template provisions").requiredOption("--template <key>", "Domain template key (e.g. ECOMMERCE)").addHelpText(
|
|
4081
|
+
"after",
|
|
4082
|
+
dedent16`
|
|
4083
|
+
|
|
4084
|
+
Read-only dry run — shows the fact definitions and sample rules the
|
|
4085
|
+
template will create. Nothing is provisioned.
|
|
4086
|
+
|
|
4087
|
+
Example:
|
|
4088
|
+
$ lexq domain-templates preview --template ECOMMERCE
|
|
4089
|
+
`
|
|
4090
|
+
).action(async (opts) => {
|
|
4091
|
+
try {
|
|
4092
|
+
const globalOpts = program.opts();
|
|
4093
|
+
const data = await apiRequest(
|
|
4094
|
+
"GET",
|
|
4095
|
+
`domain-templates/${opts.template}/preview`,
|
|
4096
|
+
{
|
|
4097
|
+
apiKey: globalOpts.apiKey,
|
|
4098
|
+
baseUrl: globalOpts.baseUrl,
|
|
4099
|
+
dryRun: globalOpts.dryRun,
|
|
4100
|
+
verbose: globalOpts.verbose
|
|
4101
|
+
}
|
|
4102
|
+
);
|
|
4103
|
+
printJson(data);
|
|
4104
|
+
} catch (error) {
|
|
4105
|
+
printError(error);
|
|
4106
|
+
process.exit(1);
|
|
4107
|
+
}
|
|
4108
|
+
});
|
|
4109
|
+
templates.command("apply").description("Apply a domain template to the current tenant").requiredOption("--template <key>", "Domain template key (e.g. ECOMMERCE)").option("--name <n>", "Custom name for the policy group that gets created").option("--force", "Skip confirmation prompt").addHelpText(
|
|
4110
|
+
"after",
|
|
4111
|
+
dedent16`
|
|
4112
|
+
|
|
4113
|
+
Creates the template's fact definitions and a new DRAFT policy group
|
|
4114
|
+
populated with its sample rules. Existing facts are skipped — apply is
|
|
4115
|
+
additive and never overwrites your schema.
|
|
4116
|
+
|
|
4117
|
+
Run "preview" first to review what will be created.
|
|
4118
|
+
|
|
4119
|
+
Example:
|
|
4120
|
+
$ lexq domain-templates apply --template ECOMMERCE
|
|
4121
|
+
$ lexq domain-templates apply --template ECOMMERCE --name "My Store Policy"
|
|
4122
|
+
`
|
|
4123
|
+
).action(async (opts) => {
|
|
4124
|
+
try {
|
|
4125
|
+
const globalOpts = program.opts();
|
|
4126
|
+
if (!opts.force) {
|
|
4127
|
+
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
4128
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
4129
|
+
const answer = await rl.question(
|
|
4130
|
+
`Apply domain template ${opts.template} to the current tenant? [y/N] `
|
|
4131
|
+
);
|
|
4132
|
+
rl.close();
|
|
4133
|
+
if (answer.toLowerCase() !== "y") {
|
|
4134
|
+
console.log("Cancelled.");
|
|
4135
|
+
return;
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
const body = {};
|
|
4139
|
+
if (opts.name) body.customName = opts.name;
|
|
4140
|
+
const data = await apiRequest(
|
|
4141
|
+
"POST",
|
|
4142
|
+
`domain-templates/${opts.template}/apply`,
|
|
4143
|
+
{
|
|
4144
|
+
apiKey: globalOpts.apiKey,
|
|
4145
|
+
baseUrl: globalOpts.baseUrl,
|
|
4146
|
+
dryRun: globalOpts.dryRun,
|
|
4147
|
+
verbose: globalOpts.verbose,
|
|
4148
|
+
body
|
|
4149
|
+
}
|
|
4150
|
+
);
|
|
4151
|
+
printJson(data);
|
|
4152
|
+
} catch (error) {
|
|
4153
|
+
printError(error);
|
|
4154
|
+
process.exit(1);
|
|
4155
|
+
}
|
|
4156
|
+
});
|
|
4157
|
+
}
|
|
4158
|
+
|
|
3982
4159
|
// src/cli.ts
|
|
3983
4160
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
3984
4161
|
function getVersion2() {
|
|
@@ -3991,13 +4168,16 @@ function getVersion2() {
|
|
|
3991
4168
|
}
|
|
3992
4169
|
function createCli() {
|
|
3993
4170
|
const program = new Command();
|
|
3994
|
-
program.name("lexq").description(
|
|
4171
|
+
program.name("lexq").description(
|
|
4172
|
+
"Command-line interface for the LexQ Decision Operations Platform. Manage policies, run Impact Simulation, and deploy from your terminal."
|
|
4173
|
+
).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
4174
|
registerAuthCommands(program);
|
|
3996
4175
|
registerStatusCommand(program);
|
|
3997
4176
|
registerGroupCommands(program);
|
|
3998
4177
|
registerVersionCommands(program);
|
|
3999
4178
|
registerRuleCommands(program);
|
|
4000
4179
|
registerFactCommands(program);
|
|
4180
|
+
registerDomainTemplateCommands(program);
|
|
4001
4181
|
registerDeployCommands(program);
|
|
4002
4182
|
registerAnalyticsCommands(program);
|
|
4003
4183
|
registerHistoryCommands(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 66 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,8 +616,8 @@ 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
622
|
decisionTraces — per-rule decision (SELECTED / NO_MATCH / BLOCKED / etc.)
|
|
623
623
|
|
|
@@ -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
|
}
|
|
@@ -1109,6 +1109,51 @@ function registerWebhookSubscriptionTools(server, callApi) {
|
|
|
1109
1109
|
);
|
|
1110
1110
|
}
|
|
1111
1111
|
|
|
1112
|
+
// src/mcp/tools/domain-templates.ts
|
|
1113
|
+
import { z as z11 } from "zod";
|
|
1114
|
+
function registerDomainTemplateTools(server, callApi) {
|
|
1115
|
+
server.registerTool(
|
|
1116
|
+
"lexq_domain_templates_list",
|
|
1117
|
+
{
|
|
1118
|
+
title: "List Domain Templates",
|
|
1119
|
+
description: "List all domain templates. A domain template is a curated, industry-specific starter pack of fact definitions and sample rules (e.g. ECOMMERCE). Each entry reports its key, status (ACTIVE or COMING_SOON), and a summary of what it provisions. Call this before preview or apply to discover which templates can currently be applied.",
|
|
1120
|
+
inputSchema: {}
|
|
1121
|
+
},
|
|
1122
|
+
async () => callApi("GET", "domain-templates")
|
|
1123
|
+
);
|
|
1124
|
+
server.registerTool(
|
|
1125
|
+
"lexq_domain_templates_preview",
|
|
1126
|
+
{
|
|
1127
|
+
title: "Preview Domain Template",
|
|
1128
|
+
description: "Preview exactly what a domain template will provision before applying it: the fact definitions it registers, the sample rules it creates, and an apply plan. This is a read-only dry run \u2014 nothing is created. Only ACTIVE templates can be previewed.",
|
|
1129
|
+
inputSchema: {
|
|
1130
|
+
template: z11.string().describe(
|
|
1131
|
+
"Domain template key (e.g. ECOMMERCE). Use lexq_domain_templates_list to see available keys \u2014 currently only ECOMMERCE is ACTIVE."
|
|
1132
|
+
)
|
|
1133
|
+
}
|
|
1134
|
+
},
|
|
1135
|
+
async ({ template }) => callApi("GET", `domain-templates/${template}/preview`)
|
|
1136
|
+
);
|
|
1137
|
+
server.registerTool(
|
|
1138
|
+
"lexq_domain_templates_apply",
|
|
1139
|
+
{
|
|
1140
|
+
title: "Apply Domain Template",
|
|
1141
|
+
description: "Apply a domain template to the current tenant. Creates the template's fact definitions and a new policy group pre-populated with its sample rules as a DRAFT version. Existing facts are skipped \u2014 apply is additive and never overwrites existing schema. Run lexq_domain_templates_preview first to review what will be created. Only ACTIVE templates can be applied.",
|
|
1142
|
+
inputSchema: {
|
|
1143
|
+
template: z11.string().describe("Domain template key to apply (e.g. ECOMMERCE)."),
|
|
1144
|
+
customName: z11.string().optional().describe(
|
|
1145
|
+
"Optional custom name for the policy group that gets created. If omitted, the template's default name is used."
|
|
1146
|
+
)
|
|
1147
|
+
}
|
|
1148
|
+
},
|
|
1149
|
+
async ({ template, customName }) => {
|
|
1150
|
+
const body = {};
|
|
1151
|
+
if (customName !== void 0) body.customName = customName;
|
|
1152
|
+
return callApi("POST", `domain-templates/${template}/apply`, { body });
|
|
1153
|
+
}
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1112
1157
|
// src/mcp/register.ts
|
|
1113
1158
|
function registerAllTools(server, callApi) {
|
|
1114
1159
|
registerStatusTools(server, callApi);
|
|
@@ -1121,6 +1166,7 @@ function registerAllTools(server, callApi) {
|
|
|
1121
1166
|
registerHistoryTools(server, callApi);
|
|
1122
1167
|
registerIntegrationTools(server, callApi);
|
|
1123
1168
|
registerLogTools(server, callApi);
|
|
1169
|
+
registerDomainTemplateTools(server, callApi);
|
|
1124
1170
|
registerWebhookSubscriptionTools(server, callApi);
|
|
1125
1171
|
}
|
|
1126
1172
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexq/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"description": "LexQ CLI — manage policies, simulate rules, and deploy from the terminal. Built for humans and AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"dev": "tsup --watch",
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"lint": "eslint src/",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"start": "node dist/index.js",
|
|
32
|
+
"prepublishOnly": "pnpm build"
|
|
33
|
+
},
|
|
26
34
|
"keywords": [
|
|
27
35
|
"lexq",
|
|
28
36
|
"rule-engine",
|
|
@@ -43,6 +51,7 @@
|
|
|
43
51
|
"engines": {
|
|
44
52
|
"node": ">=18.0.0"
|
|
45
53
|
},
|
|
54
|
+
"packageManager": "pnpm@10.28.0",
|
|
46
55
|
"dependencies": {
|
|
47
56
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
48
57
|
"cli-table3": "^0.6.5",
|
|
@@ -60,12 +69,5 @@
|
|
|
60
69
|
"tsup": "^8.5.1",
|
|
61
70
|
"typescript": "^5.7.0",
|
|
62
71
|
"typescript-eslint": "^8.24.0"
|
|
63
|
-
},
|
|
64
|
-
"scripts": {
|
|
65
|
-
"dev": "tsup --watch",
|
|
66
|
-
"build": "tsup",
|
|
67
|
-
"lint": "eslint src/",
|
|
68
|
-
"typecheck": "tsc --noEmit",
|
|
69
|
-
"start": "node dist/index.js"
|
|
70
72
|
}
|
|
71
|
-
}
|
|
73
|
+
}
|
|
@@ -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
|
|