<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>ezrules blog</title>
  <subtitle>Decision infrastructure for teams that need control, speed, and proof.</subtitle>
  <link href="https://ezrules.io/feed.xml" rel="self"/>
  <link href="https://ezrules.io/"/>
  <updated>2026-07-02T00:00:00Z</updated>
  <id>https://ezrules.io/</id>
  <author>
    <name>ezrules</name>
    <uri>https://ezrules.io</uri>
  </author>
  <entry>
    <title>How fraudsters prepare their attack infrastructure</title>
    <link href="https://ezrules.io/blog/attack-infrastructure-connected-components/"/>
    <updated>2026-07-02T00:00:00Z</updated>
    <id>https://ezrules.io/blog/attack-infrastructure-connected-components/</id>
    <summary>Learn how fraudsters prepare for a fraud attack and what you can do to spot it earlier.</summary>
    <content type="html">&lt;p&gt;In this blog post we will look at how fraudsters prepare infrastructure for a fraud attack and what you can do today to be better prepared. For professional scammers, not opportunists, defrauding a financial institution is a highly sophisticated operation. As the defences of companies become more sophisticated, so do the fraudsters.&lt;/p&gt;
&lt;h2&gt;What fraudsters are trying to manufacture&lt;/h2&gt;
&lt;p&gt;The goal is not just to get one transaction through. The goal is to make an account, a payment method, or a set of identities look ordinary enough that the first serious fraud attempt is not treated as the first suspicious event. Fraudsters are trying to manufacture age, activity, verification, and distance from known bad behaviour.&lt;/p&gt;
&lt;p&gt;Normally, preparing attack infrastructure includes, but is not limited to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;buying stolen card details (aka fullz)&lt;/li&gt;
&lt;li&gt;buying or renting accounts with the targeted companies (e.g. TapTapSend, or WorldRemit)&lt;/li&gt;
&lt;li&gt;potentially buying stolen identities for account verification&lt;/li&gt;
&lt;li&gt;making several small transactions&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How trust is built before the attack&lt;/h2&gt;
&lt;p&gt;While buying stolen card details is done mostly for payment facilitation, the other steps are about building up trust with the companies. Any fraud detection system at any payment company looks at how long the user has been known to the company - one way or another. There are multiple angles this can be assessed from. The first and most obvious one is how long the user has been on the books.&lt;/p&gt;
&lt;p&gt;There is often increased attention and separate checks for new accounts or accounts making their first ever transaction. That means buying older accounts is preferable to opening new ones, which is why there is a large market for older accounts with payment companies. Not just that, but people often voluntarily advertise those accounts for sale/rent.&lt;/p&gt;
&lt;p&gt;Buying stolen identities can be an important step in preparing fraud infrastructure. This is mostly done to open new accounts and immediately verify them with a stolen ID. In many markets ID verification is not mandatory and is often considered a &amp;quot;safety&amp;quot; signal by the companies. However, since the competition between remittance companies is fierce, they tend to lower barriers to transactions and ID verification is introduced only in the markets with high fraud rates and/or strict regulations.&lt;/p&gt;
&lt;p&gt;With any sort of account, especially with new ones, fraudsters tend to make a few small transactions. This works because they know that payment companies are overly suspicious of dormant accounts that suddenly start transacting large amounts. Small transactions are cheap probes. They test whether the account, card, recipient, device, and behavioural pattern can pass through controls. They also create recent benign-looking history, which can weaken simple dormancy and velocity rules if those rules are not designed carefully.&lt;/p&gt;
&lt;p&gt;Small transactions like this reset companies&#39; internal aggregation features (amount sent last 3 days, number of transactions last X days, etc), thus lowering the probability of such accounts being flagged.&lt;/p&gt;
&lt;p&gt;So to sum it up, an ideal account for a fraud attack is an old account that was verified by the previous owner and either has recent transaction history, or a recent history created with small transactions performed with a stolen card.&lt;/p&gt;
&lt;h2&gt;What can you do to help yourself?&lt;/h2&gt;
&lt;p&gt;Firstly, treat new customers with a stricter set of rules and/or separate ML models. There are a lot of conversations about how the customer mass should be sliced for analysis and control. My experience is that separating first/non-first time transacting customers is often enough and a good first step.&lt;/p&gt;
&lt;p&gt;Secondly, make sure that among the features/fields you check on a transaction the dormancy-related features are present. Make sure you track the cadence and frequency of transactions, as well as how much time has passed since the last transaction. A dormant account that suddenly starts moving money is not the same as an account with a stable recent history, even if the last transaction amount is the same.&lt;/p&gt;
&lt;p&gt;Do not overweight the verification flag. It is not foolproof. When analysing your machine learning models, make sure to do counterfactual analysis to see how the prediction changes when the flag is flipped. If you see a dramatic score drop - this might be a bad signal and needs readjusting. Verification should help you understand the customer, not suppress every other signal.&lt;/p&gt;
&lt;h2&gt;Use the customer graph&lt;/h2&gt;
&lt;p&gt;There is also one specific method that I found extremely useful in the past: look at the evolution of the customer graph. Count connected components and track the size of the largest one. If it spikes - alert, pay attention to the market slice where it appears.&lt;/p&gt;
&lt;p&gt;Let&#39;s get into a bit more detail here. Imagine a graph where nodes are transactions and their details. For example, a transaction with &lt;code&gt;id1&lt;/code&gt; is related to this phone number and that card hash, as well as this name and that town. Now look at the last 90 days in a market slice, pull all the data available for that slice and build that graph.&lt;/p&gt;
&lt;p&gt;The next step is to calculate connected components - isolated segments of the graph that share some details in common. What you are looking for is connected components that are relatively large compared to the market size. Say, if the CC size you normally see in that market is 5 and you see 50 - that&#39;s your first clue to investigate it. It means that a lot of transactions share a lot of details in common: same card hashes, same phone numbers, same names, same devices, same recipients, or other repeated details.&lt;/p&gt;
&lt;p&gt;For example, imagine 40 accounts that look unrelated at the account level. When you build the graph, you discover that they share 3 card hashes, 6 phone numbers, 2 device fingerprints, and a repeating set of recipients. None of those links may be decisive alone. Together, they start to look like infrastructure.&lt;/p&gt;
&lt;p&gt;It might be a false positive, of course. Some large connected components are normal: families, shared devices, agents, payroll flows, or community patterns can all create legitimate connections. The useful signal is usually the combination of size, growth rate, newness, market context, and the type of shared identifiers.&lt;/p&gt;
&lt;p&gt;However, one large connected component means nothing on its own - you want its temporal dynamics. So what you do is rewind your data back in time and start building the same connected components as of one week ago, 6 days ago, 5 days ago, etc, until today. If a component is persistent and keeps growing in size - it has every chance of being a coordinated fraud attack. Dig into it. This is the infrastructure we talked about - bought card details are shared between accounts, same phone numbers can be used for verification, etc.&lt;/p&gt;
&lt;h2&gt;Spot the preparation, not just the spike&lt;/h2&gt;
&lt;p&gt;The main idea is simple: fraud infrastructure often appears before the fraud spike. If you only look at individual transactions, you may see normal-looking activity. If you look at how accounts, cards, phones, identities, recipients, and devices connect over time, the preparation phase becomes much easier to spot.&lt;/p&gt;
&lt;p&gt;This is also the kind of problem ezrules is built for: turning those observations into testable rules, graph-derived signals, and investigation workflows that fraud teams can adjust quickly when the pattern changes.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Graph-Based Event Investigation in ezrules</title>
    <link href="https://ezrules.io/blog/graph-event-investigation/"/>
    <updated>2026-06-14T00:00:00Z</updated>
    <id>https://ezrules.io/blog/graph-event-investigation/</id>
    <summary>ezrules adds graph-backed event investigation and graph-derived rule features, revealing shared cards, devices, and identities to catch fraud rings and clusters.</summary>
    <content type="html">&lt;p&gt;Fraud investigations rarely stop at one transaction.&lt;/p&gt;
&lt;p&gt;A single event can look suspicious because of its own fields: high amount, fresh device, risky merchant category, unusual geography, or a burst of recent declines. But many of the most useful signals are relational:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the same card appearing across several users&lt;/li&gt;
&lt;li&gt;a device reused by unrelated accounts&lt;/li&gt;
&lt;li&gt;a merchant cluster tied to repeated chargebacks&lt;/li&gt;
&lt;li&gt;an email domain connecting a burst of new accounts&lt;/li&gt;
&lt;li&gt;a customer identity touching many payment instruments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those relationships are hard to see when every event is inspected as an isolated JSON payload. ezrules now supports graph-backed investigation from Tested Events and graph-derived rule features for production evaluation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/tested-event-graph.png&quot; alt=&quot;Connected tested-event graph&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;What the graph represents&lt;/h2&gt;
&lt;p&gt;The graph is built from configured entity fields. Each stored event is linked to entity values extracted from its payload, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;customer_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer.id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sender.id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;merchant_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;email_domain&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;card_fingerprint&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;device_id&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When two events share the same configured entity value, they become connected through that entity. The graph view renders this as event nodes connected to entity nodes.&lt;/p&gt;
&lt;p&gt;This means an analyst can start from one served decision and immediately see nearby traffic connected through shared operational identities.&lt;/p&gt;
&lt;h2&gt;Why this is useful during review&lt;/h2&gt;
&lt;p&gt;Without a graph, an analyst reviewing one event has to manually search for related activity. They might copy a card fingerprint, filter historical events, inspect another transaction, copy a device id, and repeat the process.&lt;/p&gt;
&lt;p&gt;The event graph makes the first pass faster:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;open a Tested Events row&lt;/li&gt;
&lt;li&gt;click &lt;strong&gt;Show graph&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;inspect related events up to the configured event and hop limits&lt;/li&gt;
&lt;li&gt;click entity nodes to expand additional nearby relationships&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The graph is intentionally bounded. By default it opens with nearby relationships up to three event-to-event hops, and the max event limit prevents broad clusters from overwhelming the browser.&lt;/p&gt;
&lt;h2&gt;How rule execution uses graph data&lt;/h2&gt;
&lt;p&gt;The investigation graph is not only a visual aid. The same stored event-to-entity links support graph-derived computed stats in rule logic.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if stat[user.unique_cards_graph_90d] &amp;gt;= 2:
    return !HOLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At evaluation time, ezrules resolves &lt;code&gt;stat[...]&lt;/code&gt; before executing the rule. A graph feature starts from an entity in the current event, traverses historical event-to-entity links within the configured time window and depth, and returns a bounded aggregate such as a distinct count of target entities.&lt;/p&gt;
&lt;p&gt;That makes relationship-aware rules possible without embedding graph traversal logic in every rule.&lt;/p&gt;
&lt;h2&gt;Keeping graph features bounded&lt;/h2&gt;
&lt;p&gt;Graph traversal can grow quickly in production traffic, so graph features include guardrails:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;allowed time windows&lt;/li&gt;
&lt;li&gt;allowed entity types&lt;/li&gt;
&lt;li&gt;max traversal depth&lt;/li&gt;
&lt;li&gt;max expanded node count&lt;/li&gt;
&lt;li&gt;active-feature lifecycle controls&lt;/li&gt;
&lt;li&gt;statement timeout protection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal is to support useful connected-event signals while keeping rule execution predictable.&lt;/p&gt;
&lt;h2&gt;A practical fraud example&lt;/h2&gt;
&lt;p&gt;Consider an event where &lt;code&gt;customer_id = cust_00013&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In isolation, the payload might show:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a card-not-present checkout&lt;/li&gt;
&lt;li&gt;a risky merchant category&lt;/li&gt;
&lt;li&gt;a disposable email domain&lt;/li&gt;
&lt;li&gt;elevated velocity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The graph can show that the same customer, email domain, merchant category, or other configured entity also appears in nearby events. That gives the reviewer immediate context: this may be part of a broader cluster rather than a one-off transaction.&lt;/p&gt;
&lt;p&gt;For rules, a graph feature can turn that cluster context into a policy signal. Instead of only checking this event&#39;s fields, a rule can ask whether the current user has connected to too many cards, devices, or merchants in the configured window.&lt;/p&gt;
&lt;h2&gt;When to use graph investigation&lt;/h2&gt;
&lt;p&gt;Graph investigation is especially useful for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;card testing bursts&lt;/li&gt;
&lt;li&gt;synthetic identity clusters&lt;/li&gt;
&lt;li&gt;account takeover patterns&lt;/li&gt;
&lt;li&gt;mule-account networks&lt;/li&gt;
&lt;li&gt;merchant abuse&lt;/li&gt;
&lt;li&gt;repeated disposable-email activity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is less useful for events that are genuinely isolated or for rules where the decision depends only on event-local fields.&lt;/p&gt;
&lt;h2&gt;What to configure carefully&lt;/h2&gt;
&lt;p&gt;The quality of graph results depends on the configured entity fields.&lt;/p&gt;
&lt;p&gt;If an entity field is not configured, ezrules will not materialize links for it. If historical events were stored before a field was configured, those older events need link backfill before they appear in graph traversal.&lt;/p&gt;
&lt;p&gt;Useful graph fields are stable identifiers with operational meaning. Avoid low-cardinality fields that connect too much traffic, such as country or currency, unless the feature is deliberately designed around broad groups.&lt;/p&gt;
&lt;p&gt;Good candidates include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;customer ids&lt;/li&gt;
&lt;li&gt;account ids&lt;/li&gt;
&lt;li&gt;device ids&lt;/li&gt;
&lt;li&gt;card fingerprints&lt;/li&gt;
&lt;li&gt;merchant ids&lt;/li&gt;
&lt;li&gt;email domains&lt;/li&gt;
&lt;li&gt;beneficiary ids&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The graph should reveal meaningful relationships, not turn every event into the same giant component.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>From Zero to Live Rule Evaluation in 10 Minutes with ezrules</title>
    <link href="https://ezrules.io/blog/zero-to-live-rule-evaluation/"/>
    <updated>2026-05-05T00:00:00Z</updated>
    <id>https://ezrules.io/blog/zero-to-live-rule-evaluation/</id>
    <summary>Spin up ezrules and prove the full fraud-rule workflow in 10 minutes: author a rule in the UI, evaluate a live event via the API, and confirm the decision.</summary>
    <content type="html">&lt;p&gt;This is the fastest path to prove value with ezrules: create one rule, send one real event, and see a decision come back from the live evaluator.&lt;/p&gt;
&lt;p&gt;If you already finished &lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/getting-started/installation/&quot;&gt;Installation&lt;/a&gt;, this walkthrough is designed to take about 10 minutes.&lt;/p&gt;
&lt;p&gt;Teams often have a familiar problem: fraud and compliance logic takes too long to validate because rule authoring, API behavior, and analyst tooling are disconnected. You can spend days debating rule ideas before seeing a real decision in a live path.&lt;/p&gt;
&lt;p&gt;This guide focuses on a single outcome: prove the full workflow quickly. In one short run, you will create a rule in the UI, evaluate an event through &lt;code&gt;/api/v2/evaluate&lt;/code&gt;, and confirm the decision path is working end to end. That gives you a concrete artifact for demos, internal buy-in, and faster iteration.&lt;/p&gt;
&lt;h2&gt;Minute 0-2: Start the stack&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker compose up -d
uv run ezrules api --port 8888
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a second terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ezrules/frontend
npm install
npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Quick checks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API health: &lt;code&gt;http://localhost:8888/ping&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Frontend login page: &lt;code&gt;http://localhost:4200&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Minute 2-5: Create your first decision rule&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Log in at &lt;code&gt;http://localhost:4200&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;Rules&lt;/strong&gt; and create a new rule with this logic:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if $amount &amp;gt; 10000:
    return !HOLD
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Open &lt;strong&gt;Outcomes&lt;/strong&gt; and add &lt;code&gt;HOLD&lt;/code&gt; if it is not there yet.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Fresh installs start with an empty outcomes catalog, so creating the first outcome is part of the setup.&lt;/p&gt;
&lt;h2&gt;Minute 5-7: Send a live event to the evaluator&lt;/h2&gt;
&lt;p&gt;Authenticate first (for example via &lt;code&gt;POST /api/v2/auth/login&lt;/code&gt;) and then call the evaluator endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -X POST http://localhost:8888/api/v2/evaluate &#92;
  -H &amp;quot;Authorization: Bearer &amp;lt;access_token&amp;gt;&amp;quot; &#92;
  -H &amp;quot;Content-Type: application/json&amp;quot; &#92;
  -d &#39;{
    &amp;quot;transaction_id&amp;quot;: &amp;quot;txn_live_001&amp;quot;,
    &amp;quot;effective_at&amp;quot;: 1700000000,
    &amp;quot;event_data&amp;quot;: {
      &amp;quot;amount&amp;quot;: 15000,
      &amp;quot;user_id&amp;quot;: &amp;quot;user_42&amp;quot;
    }
  }&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should get JSON with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rule_results&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;outcome_counters&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;outcome_set&lt;/code&gt; (should include &lt;code&gt;HOLD&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Minute 7-9: Validate in the UI&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Dashboard&lt;/strong&gt; to confirm transaction activity.&lt;/li&gt;
&lt;li&gt;Open the rule detail page and use &lt;strong&gt;Test Rule&lt;/strong&gt; to try a low amount (for example, &lt;code&gt;500&lt;/code&gt;) and compare results.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Use the 30-day Dashboard window for demos so the chart shows the traffic shape clearly, then zoom into the rule&#39;s own test panel for the concrete decision result.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/dashboard-30d-volume.png&quot; alt=&quot;30-day transaction volume dashboard&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/rule-test-panel.png&quot; alt=&quot;Rule test panel with successful HOLD result&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After the live call, the Tested Events view gives analysts the concrete event record, outcome, and highlighted payload fields that explain why the rule fired.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/tested-event-inspector.png&quot; alt=&quot;Tested event payload with referenced fields highlighted&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Minute 9-10: Tune and rerun&lt;/h2&gt;
&lt;p&gt;Change the threshold, save, and call &lt;code&gt;/api/v2/evaluate&lt;/code&gt; again. This quick edit-test-evaluate loop is the core workflow teams use to iterate on fraud and compliance logic without redeploying application code.&lt;/p&gt;
&lt;h2&gt;Why this 10-minute flow matters&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;You validate the full path: UI authoring -&amp;gt; stored rule config -&amp;gt; live evaluator response.&lt;/li&gt;
&lt;li&gt;You show both analyst and engineering workflows in one short demo.&lt;/li&gt;
&lt;li&gt;You have a concrete artifact to share in release notes, demos, and onboarding.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/getting-started/quickstart/&quot;&gt;Quick Start (UI First)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/getting-started/integration-quickstart/&quot;&gt;Integration Quickstart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/user-guide/creating-rules/&quot;&gt;Creating Rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/user-guide/monitoring/&quot;&gt;Monitoring &amp;amp; Analytics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Rule Lifecycle Management in ezrules: Draft, Promote, Pause, Roll Back, Archive with Audit Trail</title>
    <link href="https://ezrules.io/blog/rule-lifecycle-management/"/>
    <updated>2026-04-30T00:00:00Z</updated>
    <id>https://ezrules.io/blog/rule-lifecycle-management/</id>
    <summary>ezrules adds an explicit rule lifecycle: draft, active, paused, archived, with auditable promotion, reversible pause, and rollback that restores known-good logic.</summary>
    <content type="html">&lt;p&gt;Rule engines usually fail at one of two extremes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every edit is live immediately, which is fast but risky.&lt;/li&gt;
&lt;li&gt;Every edit requires an external process, which is safe but slow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ezrules now supports an explicit lifecycle so teams can move fast without losing control: &lt;code&gt;draft&lt;/code&gt; -&amp;gt; &lt;code&gt;active&lt;/code&gt; -&amp;gt; &lt;code&gt;paused&lt;/code&gt; -&amp;gt; &lt;code&gt;active&lt;/code&gt; or &lt;code&gt;archived&lt;/code&gt;, with lifecycle events captured in the audit trail and rollback available when a historical revision needs to be restored as a new draft.&lt;/p&gt;
&lt;h2&gt;Why lifecycle controls matter&lt;/h2&gt;
&lt;p&gt;If a rule change can go live directly from an edit, then you can accidentally ship unreviewed logic. If a rule change requires manual coordination outside the system, then iteration slows down and people work around process.&lt;/p&gt;
&lt;p&gt;The useful middle ground is to separate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;rule authoring (&lt;code&gt;draft&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;rule deployment (&lt;code&gt;active&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;temporary operational disablement (&lt;code&gt;paused&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;retirement (&lt;code&gt;archived&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gives you cleaner change control and a clear operational state for every rule.&lt;/p&gt;
&lt;h2&gt;What is now stored per rule&lt;/h2&gt;
&lt;p&gt;Each rule now includes lifecycle metadata:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt;: &lt;code&gt;draft&lt;/code&gt;, &lt;code&gt;active&lt;/code&gt;, &lt;code&gt;paused&lt;/code&gt;, or &lt;code&gt;archived&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;effective_from&lt;/code&gt;: when the active version became effective&lt;/li&gt;
&lt;li&gt;&lt;code&gt;approved_by&lt;/code&gt;: user id recorded when the current rule version was activated or reactivated&lt;/li&gt;
&lt;li&gt;&lt;code&gt;approved_at&lt;/code&gt;: timestamp recorded when the current rule version was activated&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is no separate approval workflow. The &lt;code&gt;approved_*&lt;/code&gt; field names are kept for API compatibility and refer to promote, resume, or auto-promote actions. The audit trail exposes lifecycle transitions as event-log rows: action, actor, timestamp, and status transition.&lt;/p&gt;
&lt;h2&gt;API behavior by lifecycle&lt;/h2&gt;
&lt;p&gt;The lifecycle is enforced by API behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /api/v2/rules&lt;/code&gt; creates a &lt;code&gt;draft&lt;/code&gt; rule&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PUT /api/v2/rules/{id}&lt;/code&gt; saves edits as &lt;code&gt;draft&lt;/code&gt; and clears previous activation metadata by default&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/v2/rules/{id}/rollback&lt;/code&gt; restores a historical revision&#39;s logic and description into a brand new &lt;code&gt;draft&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/v2/rules/{id}/promote&lt;/code&gt; moves &lt;code&gt;draft&lt;/code&gt; to &lt;code&gt;active&lt;/code&gt; and records the activation actor + timestamp&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/v2/rules/{id}/pause&lt;/code&gt; moves &lt;code&gt;active&lt;/code&gt; to &lt;code&gt;paused&lt;/code&gt; without archiving the rule and requires a dedicated pause permission&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/v2/rules/{id}/resume&lt;/code&gt; moves &lt;code&gt;paused&lt;/code&gt; back to &lt;code&gt;active&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/v2/rules/{id}/archive&lt;/code&gt; moves a rule to &lt;code&gt;archived&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DELETE /api/v2/rules/{id}&lt;/code&gt; deletes the rule (requires &lt;code&gt;DELETE_RULE&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Production evaluation config now includes only &lt;code&gt;active&lt;/code&gt; rules.&lt;/p&gt;
&lt;p&gt;An org can opt into &lt;code&gt;auto_promote_active_rule_updates&lt;/code&gt; through the runtime settings API or &lt;strong&gt;Settings → General&lt;/strong&gt;. When that setting is enabled, editing an already active rule keeps it active and rewrites the production config immediately, but the caller still needs &lt;code&gt;PROMOTE_RULES&lt;/code&gt; in addition to &lt;code&gt;MODIFY_RULE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/rule-lifecycle-setting.png&quot; alt=&quot;Rule lifecycle setting for active-rule edits&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    A[Create rule] --&amp;gt; B[draft]
    B --&amp;gt; C[Edit rule]
    C --&amp;gt; B
    B --&amp;gt; D[Promote]
    D --&amp;gt; E[active]
    E --&amp;gt; F[Pause]
    F --&amp;gt; G[paused]
    G --&amp;gt; H[Resume]
    H --&amp;gt; E
    E --&amp;gt; I[Edit or detect issue]
    I --&amp;gt; J[Rollback to prior revision]
    J --&amp;gt; B
    E --&amp;gt; K[Archive]
    G --&amp;gt; K
    K --&amp;gt; L[archived]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the rule list, the lifecycle state stays visible next to the rule, alongside the actions that are available for that state.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/rule-lifecycle-statuses.png&quot; alt=&quot;Rule list with draft, active, and shadow lifecycle badges&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Promotion is a first-class activation step&lt;/h2&gt;
&lt;p&gt;Promotion is no longer an implicit side effect of editing. It is an explicit operation that records:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;who activated the rule&lt;/li&gt;
&lt;li&gt;when it was activated&lt;/li&gt;
&lt;li&gt;when it became effective&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That gives you a defensible trail for internal governance and external audits, while keeping authoring fast for rule editors.&lt;/p&gt;
&lt;h2&gt;Pause is not archive&lt;/h2&gt;
&lt;p&gt;Pausing is useful when a rule should stop affecting live outcomes right now, but you expect to reuse it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;paused&lt;/code&gt; rules are excluded from production config&lt;/li&gt;
&lt;li&gt;the rule can be resumed later without recreating it&lt;/li&gt;
&lt;li&gt;teams can distinguish &amp;quot;temporarily off&amp;quot; from &amp;quot;retired&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use pause when you want a reversible operational stop. Use archive when the rule is retired.&lt;/p&gt;
&lt;h2&gt;Archive is not delete&lt;/h2&gt;
&lt;p&gt;Archiving is useful when you need to retire a rule but keep full context.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;archived&lt;/code&gt; rules are no longer active in production config&lt;/li&gt;
&lt;li&gt;historical versions and metadata remain available&lt;/li&gt;
&lt;li&gt;teams can distinguish &amp;quot;no longer used&amp;quot; from &amp;quot;removed forever&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use delete when you intentionally want permanent removal. Use archive when you want operational retirement with history intact.&lt;/p&gt;
&lt;h2&gt;Rollback is not history deletion&lt;/h2&gt;
&lt;p&gt;Rollback does not rewind the database in place and it does not remove newer revisions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the selected historical revision remains in history&lt;/li&gt;
&lt;li&gt;the current revision remains in history&lt;/li&gt;
&lt;li&gt;rollback creates a new &lt;code&gt;draft&lt;/code&gt; version using the older logic and description&lt;/li&gt;
&lt;li&gt;the rollback action itself is recorded in audit history&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the safer operational model: you recover known-good logic quickly without destroying evidence of what changed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/rule-history-timeline.png&quot; alt=&quot;Rule history timeline with rollback action&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/rule-history-rollback-dialog.png&quot; alt=&quot;Rollback confirmation with current versus target diff&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Example: promote and archive&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Roll back rule 42 to revision 3 (creates a new draft)
curl -X POST http://localhost:8888/api/v2/rules/42/rollback &#92;
  -H &amp;quot;Authorization: Bearer &amp;lt;access_token&amp;gt;&amp;quot; &#92;
  -H &amp;quot;Content-Type: application/json&amp;quot; &#92;
  -d &#39;{&amp;quot;revision_number&amp;quot;: 3}&#39;

# Promote draft rule 42
curl -X POST http://localhost:8888/api/v2/rules/42/promote &#92;
  -H &amp;quot;Authorization: Bearer &amp;lt;access_token&amp;gt;&amp;quot;

# Pause rule 42
curl -X POST http://localhost:8888/api/v2/rules/42/pause &#92;
  -H &amp;quot;Authorization: Bearer &amp;lt;access_token&amp;gt;&amp;quot;

# Resume rule 42
curl -X POST http://localhost:8888/api/v2/rules/42/resume &#92;
  -H &amp;quot;Authorization: Bearer &amp;lt;access_token&amp;gt;&amp;quot;

# Archive rule 42
curl -X POST http://localhost:8888/api/v2/rules/42/archive &#92;
  -H &amp;quot;Authorization: Bearer &amp;lt;access_token&amp;gt;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How this fits with shadow deployment&lt;/h2&gt;
&lt;p&gt;Lifecycle and shadow solve different concerns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lifecycle controls whether a rule is draft, active, paused, or archived in production management flow.&lt;/li&gt;
&lt;li&gt;Shadow deployment validates candidate behavior on live traffic before promotion.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A practical sequence is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Edit rule in draft&lt;/li&gt;
&lt;li&gt;(Optional) deploy to shadow for live validation&lt;/li&gt;
&lt;li&gt;Roll back to a known-good revision if a newer draft or active version proves wrong&lt;/li&gt;
&lt;li&gt;Promote when ready to activate&lt;/li&gt;
&lt;li&gt;Archive when rule is retired&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Net effect&lt;/h2&gt;
&lt;p&gt;You get a cleaner rule lifecycle without adding operational friction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;safer releases through explicit promotion&lt;/li&gt;
&lt;li&gt;faster recovery through auditable rollback&lt;/li&gt;
&lt;li&gt;auditable activation trail&lt;/li&gt;
&lt;li&gt;clear operational state in UI and API&lt;/li&gt;
&lt;li&gt;predictable retirement path through archive&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Related docs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/api-reference/manager-api/&quot;&gt;Manager API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/user-guide/creating-rules/&quot;&gt;Creating Rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/user-guide/shadow-deployment/&quot;&gt;Shadow Deployment guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>AI-Assisted Rule Authoring in ezrules: Generate Transaction Monitoring Rules from Natural Language</title>
    <link href="https://ezrules.io/blog/ai-rule-authoring/"/>
    <updated>2026-04-30T00:00:00Z</updated>
    <id>https://ezrules.io/blog/ai-rule-authoring/</id>
    <summary>Turn plain-English intent into validated, reviewable transaction-monitoring rules with ezrules AI authoring, complete with diffs, explanations, and human-in-the-loop control.</summary>
    <content type="html">&lt;p&gt;Writing transaction-monitoring rules is usually a mix of policy thinking and syntax work.&lt;/p&gt;
&lt;p&gt;An analyst knows the behavior they want to detect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;large transfers from risky destinations&lt;/li&gt;
&lt;li&gt;activity from disposable email domains&lt;/li&gt;
&lt;li&gt;repeated proxy-heavy transactions from low-trust devices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But getting that intent into a valid rule still takes time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;finding the right fields&lt;/li&gt;
&lt;li&gt;remembering list names&lt;/li&gt;
&lt;li&gt;using the correct outcome syntax&lt;/li&gt;
&lt;li&gt;checking whether the draft logic actually compiles&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ezrules now adds AI-assisted rule authoring directly inside the existing rule editor so teams can move from natural-language intent to a reviewable draft faster.&lt;/p&gt;
&lt;h2&gt;What AI rule authoring does&lt;/h2&gt;
&lt;p&gt;The AI assistant is built into the normal rule create and rule edit workflows.&lt;/p&gt;
&lt;p&gt;A user can describe the rule they want in plain English, and ezrules will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;generate a draft in native ezrules rule syntax&lt;/li&gt;
&lt;li&gt;validate the generated logic&lt;/li&gt;
&lt;li&gt;attempt bounded repair for obvious issues&lt;/li&gt;
&lt;li&gt;explain the generated rule line by line&lt;/li&gt;
&lt;li&gt;show a diff against the current rule when editing&lt;/li&gt;
&lt;li&gt;offer a manual backtest action for valid edit-mode drafts&lt;/li&gt;
&lt;li&gt;require an explicit copy step into the real editor before save&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is important: the assistant does &lt;strong&gt;not&lt;/strong&gt; auto-save and does &lt;strong&gt;not&lt;/strong&gt; auto-activate anything.&lt;/p&gt;
&lt;p&gt;The generated output is a draft preview until the user explicitly copies it into the main rule editor.&lt;/p&gt;
&lt;h2&gt;Why this matters for fraud and compliance teams&lt;/h2&gt;
&lt;p&gt;Most rule authoring friction is not about business logic. It is about translation.&lt;/p&gt;
&lt;p&gt;A fraud analyst might want to say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hold high-value wire transfers from newer accounts when the beneficiary is in a sanctioned country.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The system already knows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which fields have been observed&lt;/li&gt;
&lt;li&gt;which user lists exist&lt;/li&gt;
&lt;li&gt;which outcomes are configured&lt;/li&gt;
&lt;li&gt;whether the rule is in the main lane or allowlist lane&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI-assisted authoring closes the gap between those two layers.&lt;/p&gt;
&lt;p&gt;Instead of starting from a blank editor, the analyst starts from a generated draft that already uses ezrules-native concepts such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$amount&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$customer.account.age_days&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@SanctionedCountries&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!HOLD&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That shortens time-to-first-draft without creating a second rule language that has to be translated back into the engine.&lt;/p&gt;
&lt;h2&gt;The review flow is intentionally explicit&lt;/h2&gt;
&lt;p&gt;The safest part of the feature is not the generation. It is the review flow around generation.&lt;/p&gt;
&lt;p&gt;The assistant now makes the generated draft easier to inspect before it affects the real rule body.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/ai-authoring-panel.png&quot; alt=&quot;AI rule authoring panel with prompt and context&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1. Validation and repair happen before the user copies anything&lt;/h3&gt;
&lt;p&gt;Generated rules are checked against the same validation path used by the rule API.&lt;/p&gt;
&lt;p&gt;That means the preview can surface:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;syntax errors&lt;/li&gt;
&lt;li&gt;missing or invalid outcomes&lt;/li&gt;
&lt;li&gt;warnings about referenced fields&lt;/li&gt;
&lt;li&gt;lane-specific constraints&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The assistant can also attempt bounded repair before handing the draft back to the user.&lt;/p&gt;
&lt;h3&gt;2. The draft is visually separated from the real editor&lt;/h3&gt;
&lt;p&gt;The generated rule appears in a dedicated preview block, not silently inside the actual editable rule body.&lt;/p&gt;
&lt;p&gt;This avoids the common problem where an AI suggestion looks authoritative even though it has not been reviewed yet.&lt;/p&gt;
&lt;h3&gt;3. A diff shows exactly what changed&lt;/h3&gt;
&lt;p&gt;When editing an existing rule, the assistant can show a char-level diff against the current editor content.&lt;/p&gt;
&lt;p&gt;That matters because many useful AI edits are small:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;adding one more condition&lt;/li&gt;
&lt;li&gt;changing one threshold&lt;/li&gt;
&lt;li&gt;swapping one outcome&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Char-level diff makes those edits easier to inspect than a full-line replacement view.&lt;/p&gt;
&lt;h3&gt;4. A line-by-line explainer is available when needed&lt;/h3&gt;
&lt;p&gt;The assistant also produces a line-by-line explanation of the generated draft.&lt;/p&gt;
&lt;p&gt;This is useful when a reviewer wants to understand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what each branch is doing&lt;/li&gt;
&lt;li&gt;which condition maps to which business idea&lt;/li&gt;
&lt;li&gt;whether the generated logic matches the original analyst request&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. Manual backtests show likely impact before save&lt;/h3&gt;
&lt;p&gt;When a valid draft is generated while editing an existing rule, ezrules shows a &lt;strong&gt;Run Backtest&lt;/strong&gt; action under the proposed logic change.&lt;/p&gt;
&lt;p&gt;The action uses the same Backtest Results section as the rest of the rule detail page. It compares the stored rule and proposed draft before the user copies or saves anything, and shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;outcome distribution deltas between the stored and proposed logic&lt;/li&gt;
&lt;li&gt;skipped-record counts and missing-field warnings&lt;/li&gt;
&lt;li&gt;label-aware precision, recall, and F1 when historical labels exist&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This keeps the expensive comparison user-triggered while preserving the normal save, promote, rollout, and shadow workflows.&lt;/p&gt;
&lt;h3&gt;6. Copy into the main editor is a deliberate action&lt;/h3&gt;
&lt;p&gt;The preview becomes real rule content only when the user chooses:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use Draft In Main Editor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Until then, the normal Save/Create actions are still tied to the main rule editor, not the AI preview.&lt;/p&gt;
&lt;p&gt;That keeps the human-in-the-loop and makes the authoring step auditable and reviewable.&lt;/p&gt;
&lt;h2&gt;What context the assistant uses&lt;/h2&gt;
&lt;p&gt;AI rule authoring is not a generic chatbot prompt box. It uses ezrules-specific context from the organisation.&lt;/p&gt;
&lt;p&gt;The current implementation can include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;observed fields&lt;/li&gt;
&lt;li&gt;configured field types&lt;/li&gt;
&lt;li&gt;user lists&lt;/li&gt;
&lt;li&gt;configured outcomes&lt;/li&gt;
&lt;li&gt;lane constraints&lt;/li&gt;
&lt;li&gt;neutral outcome rules for allowlist behavior&lt;/li&gt;
&lt;li&gt;the current rule body and description when editing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That makes the generated output much more likely to fit the actual rule engine than a generic model prompt would.&lt;/p&gt;
&lt;h2&gt;OpenAI-backed settings in the product&lt;/h2&gt;
&lt;p&gt;ezrules also adds AI configuration to the Settings page.&lt;/p&gt;
&lt;p&gt;An organisation can now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;enable or disable AI rule authoring&lt;/li&gt;
&lt;li&gt;select the OpenAI provider&lt;/li&gt;
&lt;li&gt;choose the model&lt;/li&gt;
&lt;li&gt;manage the provider API key&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/ai-authoring-settings.png&quot; alt=&quot;AI rule authoring settings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At the moment, the product UI intentionally supports &lt;strong&gt;OpenAI only&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That keeps the first release simple while preserving a backend shape that can support additional providers later.&lt;/p&gt;
&lt;h2&gt;A practical example&lt;/h2&gt;
&lt;p&gt;Imagine an existing rule:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if $amount &amp;gt; 500 and $email_domain in @DisposableEmailDomains:
    return !HOLD
else:
    return !RELEASE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An analyst may want to tighten it with one more requirement, such as email age.&lt;/p&gt;
&lt;p&gt;Instead of manually editing and rechecking syntax, they can ask the assistant for that change and review:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;whether the threshold logic is still correct&lt;/li&gt;
&lt;li&gt;whether only the intended condition changed&lt;/li&gt;
&lt;li&gt;whether the new draft preserved the right outcome behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is exactly the kind of small-but-risky authoring step where AI can help without replacing human judgment.&lt;/p&gt;
&lt;h2&gt;What this feature is not&lt;/h2&gt;
&lt;p&gt;AI rule authoring in ezrules is not:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;automatic promotion&lt;/li&gt;
&lt;li&gt;automatic activation&lt;/li&gt;
&lt;li&gt;a separate no-code rule engine&lt;/li&gt;
&lt;li&gt;a replacement for analyst review&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is a drafting accelerator on top of the existing expert editor.&lt;/p&gt;
&lt;p&gt;That distinction matters. In transaction monitoring, the problem is rarely “how do we let the model decide?” It is usually “how do we get from intent to a reviewable draft faster without weakening controls?”&lt;/p&gt;
&lt;h2&gt;Final thought&lt;/h2&gt;
&lt;p&gt;The most useful AI features in compliance and fraud tooling are usually the ones that remove mechanical work while keeping responsibility clear.&lt;/p&gt;
&lt;p&gt;AI-assisted rule authoring in ezrules does exactly that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;faster rule drafting&lt;/li&gt;
&lt;li&gt;native engine syntax&lt;/li&gt;
&lt;li&gt;explicit validation&lt;/li&gt;
&lt;li&gt;visual review aids&lt;/li&gt;
&lt;li&gt;human acceptance before save&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is a much better fit for production transaction monitoring than a fully automatic rule-writing workflow.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Related docs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/user-guide/creating-rules/&quot;&gt;Creating Rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/api-reference/manager-api/&quot;&gt;Manager API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/getting-started/configuration/&quot;&gt;Configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Shipping Rule Changes with Less Guesswork: Shadow Deployment in ezrules</title>
    <link href="https://ezrules.io/blog/shadow-deployment/"/>
    <updated>2026-04-24T00:00:00Z</updated>
    <id>https://ezrules.io/blog/shadow-deployment/</id>
    <summary>Shadow deployment in ezrules evaluates candidate rules against live traffic in parallel with production, so you validate real-world impact before promoting a change.</summary>
    <content type="html">&lt;p&gt;There&#39;s a gap between &amp;quot;this rule passes tests&amp;quot; and &amp;quot;this rule is safe to ship.&amp;quot; Closing that gap is what shadow deployment is for.&lt;/p&gt;
&lt;h2&gt;The problem with deploying rule changes&lt;/h2&gt;
&lt;p&gt;You write a new version of a rule. You test it with realistic payloads and the logic is correct. You backtest it against stored historical events and the numbers look acceptable. Then you ship it and find out fairly quickly that production traffic was different from what you expected — a field arriving as a string instead of an integer, a traffic spike at a time you didn&#39;t have good historical coverage for, a new upstream product generating a pattern your backtest window didn&#39;t include.&lt;/p&gt;
&lt;p&gt;This is not primarily a testing problem. Backtesting is genuinely useful: it tells you how a rule would have performed on a defined historical window, which helps you calibrate thresholds and catch logic errors before they touch anything real. But a historical window is fixed. Your traffic today might differ from three months ago in ways that matter — seasonal patterns, data quality shifts from upstream systems, new transaction types entering the mix. A backtest from Q3 is a reasonable but imperfect proxy for Q1.&lt;/p&gt;
&lt;p&gt;What you actually want to know is: how does this rule behave on current traffic, with current data, at current volumes?&lt;/p&gt;
&lt;h2&gt;What shadow mode does&lt;/h2&gt;
&lt;p&gt;When you deploy a rule to shadow, it evaluates every incoming event alongside the production rule set. The same payload goes through both. Production results are returned to the caller as normal. Shadow results are stored in a separate table and never returned.&lt;/p&gt;
&lt;p&gt;From the caller&#39;s perspective, nothing has changed. From your perspective, you&#39;re accumulating a real distribution of how the candidate rule would have answered every question the production system received, starting from the moment you deployed it.&lt;/p&gt;
&lt;p&gt;There is no traffic splitting, no separate service to deploy, no routing configuration. You hit the Deploy to Shadow button (or the API equivalent), and from that point forward every event is evaluated against the shadow config in parallel with production.&lt;/p&gt;
&lt;p&gt;The Shadow Rules page shows the candidate rule, its current shadow status, and the live comparison surface operators use before promotion.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/shadow-active-rule.png&quot; alt=&quot;Active shadow rule card with comparison controls&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    A[Incoming event] --&amp;gt; B[POST /api/v2/evaluate]
    B --&amp;gt; C[Production rules]
    B --&amp;gt; D[Shadow rules]
    C --&amp;gt; E[Response to caller]
    D --&amp;gt; F[(shadow_results_log)]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What the comparison tells you&lt;/h2&gt;
&lt;p&gt;The stats endpoint (&lt;code&gt;GET /api/v2/shadow/stats&lt;/code&gt;) shows outcome distributions for each shadow rule alongside the production outcomes for the same events. If you&#39;re tightening a threshold — a rule that should fire more often than the current version — you expect more &lt;code&gt;HOLD&lt;/code&gt; decisions in the shadow column than in the production column. If they&#39;re close, your change has less bite than you expected. If they diverge significantly, you know the magnitude before it matters.&lt;/p&gt;
&lt;p&gt;This doesn&#39;t tell you whether the decisions would have been &lt;em&gt;correct&lt;/em&gt;, only how different they are from current production behavior. Whether that difference is acceptable is a judgment call that depends on your situation. But you&#39;re making that call with data from today&#39;s traffic rather than from a historical window that may or may not be representative.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/shadow-comparison.png&quot; alt=&quot;Shadow comparison for production versus candidate outcomes&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The draft-logic path&lt;/h2&gt;
&lt;p&gt;One workflow that comes up often: you want to test a logic change in shadow without committing it to the rules table first. The deploy endpoint accepts optional logic and description fields. If you provide them, they&#39;re stored in the shadow config and the rules table stays unchanged.&lt;/p&gt;
&lt;p&gt;If you decide the change is wrong after observing the shadow results, you remove it. Nothing was written to production. If it looks right, you promote: the rules table updates to the shadow logic, the production config updates, and the shadow entry is cleared in one atomic operation.&lt;/p&gt;
&lt;p&gt;This matters for iteration speed. You can go through several candidate versions of a rule — deploy to shadow, watch the distribution, decide it&#39;s off, adjust, re-deploy — without any of those intermediate versions touching production. The only version that ever enters the production config is the one you decided to promote.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    A[Candidate logic] --&amp;gt;|deploy to shadow| B[Shadow config]
    B --&amp;gt;|traffic accumulates| C[shadow_results_log]
    C --&amp;gt; D{Stats look right?}
    D --&amp;gt;|yes| E[Promote → production]
    D --&amp;gt;|no| F[Remove → nothing written]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Backtesting and shadow are complementary&lt;/h2&gt;
&lt;p&gt;Backtesting and shadow deployment answer different questions and the useful pattern is to use both.&lt;/p&gt;
&lt;p&gt;Backtest before you have a candidate ready. It&#39;s good for initial calibration: does this threshold make sense on historical data, are there edge cases in the stored event set, is the logic handling the patterns it&#39;s supposed to handle? This is where you rule out obviously bad ideas.&lt;/p&gt;
&lt;p&gt;Shadow after you have a candidate ready. It&#39;s good for validation on current traffic: is the production impact what I expected, are there patterns in live data that weren&#39;t well represented in the historical window? This is where you build confidence before promoting.&lt;/p&gt;
&lt;p&gt;Neither tool tells you what the other does. Backtesting has no access to live traffic. Shadow has no access to historical events. A rule that passes both gives you substantially more confidence than one that only passed one.&lt;/p&gt;
&lt;h2&gt;A few things shadow doesn&#39;t do&lt;/h2&gt;
&lt;p&gt;Shadow results aren&#39;t labeled. You can see how often the shadow rule returns each outcome and compare that to production for the same events, but you don&#39;t automatically know whether those shadow decisions would have been correct against ground truth. If you have labeled data covering the shadow period, you can cross-reference manually, but that&#39;s not built in.&lt;/p&gt;
&lt;p&gt;Shadow evaluation is best-effort. If the shadow evaluation for an event fails — a missing field, a type error, anything that raises an exception — the error is silenced and that event isn&#39;t stored in the shadow results. The production evaluation is unaffected. This means the shadow results may have gaps, and if the rule has bugs that only surface on certain payload shapes, those events are undercounted rather than surfaced as errors. Worth checking result counts against total event volume if completeness matters to you.&lt;/p&gt;
&lt;p&gt;Stats reset when you re-deploy the same rule to shadow. If you update the draft logic and push a new version, the existing results are cleared. You start accumulating again from zero. This is intentional — stale results from a previous candidate version aren&#39;t meaningful for evaluating the current one — but it&#39;s worth knowing if you&#39;re expecting continuous accumulation across logic changes.&lt;/p&gt;
&lt;h2&gt;The net effect&lt;/h2&gt;
&lt;p&gt;Shadow deployment narrows the gap between &amp;quot;the backtest looked acceptable&amp;quot; and &amp;quot;I&#39;m confident this is right.&amp;quot; It doesn&#39;t eliminate judgment, but it moves the validation point from historical data to current traffic, which is where the rule will actually run.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Full docs: &lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/user-guide/shadow-deployment/&quot;&gt;Shadow Deployment guide&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Rule Quality in ezrules: Precision and Recall by Outcome-Label Pair</title>
    <link href="https://ezrules.io/blog/rule-quality-precision-recall/"/>
    <updated>2026-04-24T00:00:00Z</updated>
    <id>https://ezrules.io/blog/rule-quality-precision-recall/</id>
    <summary>The ezrules Rule Quality view scores every rule on precision, recall, and F1 by outcome-label pair, surfacing over-flagging and missed cases against ground truth.</summary>
    <content type="html">&lt;p&gt;Teams usually know which rules fire often. The harder part is knowing whether those rules are right.&lt;/p&gt;
&lt;p&gt;In ezrules v0.19, the new &lt;strong&gt;Rule Quality&lt;/strong&gt; view makes that explicit by comparing rule outcomes against ground-truth labels.&lt;/p&gt;
&lt;h2&gt;The practical problem&lt;/h2&gt;
&lt;p&gt;Rules return outcomes like &lt;code&gt;HOLD&lt;/code&gt;, &lt;code&gt;RELEASE&lt;/code&gt;, &lt;code&gt;CANCEL&lt;/code&gt;. Labels are business ground truth like &lt;code&gt;FRAUD&lt;/code&gt;, &lt;code&gt;NORMAL&lt;/code&gt;, &lt;code&gt;CHARGEBACK&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Those vocabularies are intentionally separate, so quality analysis must evaluate &lt;strong&gt;pairs&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HOLD -&amp;gt; FRAUD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RELEASE -&amp;gt; NORMAL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CANCEL -&amp;gt; CHARGEBACK&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each pair can have different precision/recall behavior.&lt;/p&gt;
&lt;p&gt;Before ranking rules, analysts can confirm that labels are flowing over a useful window in Analytics.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/label-analytics-30d.png&quot; alt=&quot;30-day label analytics charts&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;What the page shows&lt;/h2&gt;
&lt;p&gt;Open &lt;strong&gt;Rule Quality&lt;/strong&gt; from the sidebar.&lt;/p&gt;
&lt;p&gt;You get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Best Rules&lt;/strong&gt;: highest average F1 (across valid outcome→label pairs)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Needs Attention&lt;/strong&gt;: lowest average F1&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pair Metrics Table&lt;/strong&gt; for every rule, including:
&lt;ul&gt;
&lt;li&gt;precision&lt;/li&gt;
&lt;li&gt;recall&lt;/li&gt;
&lt;li&gt;F1&lt;/li&gt;
&lt;li&gt;TP / FP / FN&lt;/li&gt;
&lt;li&gt;predicted positives / actual positives&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This helps analysts answer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which rules are over-flagging (low precision)?&lt;/li&gt;
&lt;li&gt;Which rules are missing confirmed cases (low recall)?&lt;/li&gt;
&lt;li&gt;Which mapping actually represents this rule best?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/rule-quality-report.png&quot; alt=&quot;Rule quality report with ranked rules and pair metrics&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Support filtering matters&lt;/h2&gt;
&lt;p&gt;Low-volume pairs can create noisy rankings. The page includes a &lt;strong&gt;Min support&lt;/strong&gt; filter.&lt;/p&gt;
&lt;p&gt;Under the hood, this maps to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /api/v2/analytics/rule-quality?min_support=&amp;lt;n&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Raise the threshold when you want stable operational ranking; lower it for exploratory analysis.&lt;/p&gt;
&lt;p&gt;Configured outcome-label pairs keep the report focused on mappings the team actually reviews.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/rule-quality-pairs-settings.png&quot; alt=&quot;Curated rule quality pair settings&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Feeding data quickly: bombardment fraud labels&lt;/h2&gt;
&lt;p&gt;The bombardment script now supports in-line labeling of a small random percentage of evaluated events:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run python scripts/bombard_evaluator.py &#92;
  --api-key &amp;lt;api_key&amp;gt; &#92;
  --token &amp;lt;access_token&amp;gt; &#92;
  --fraud-rate 0.01
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That gives you continuous traffic plus a trickle of labeled events for quality monitoring, without waiting for manual CSV uploads.&lt;/p&gt;
&lt;h2&gt;Operational workflow&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Run traffic (live or bombardment).&lt;/li&gt;
&lt;li&gt;Label events (manual, CSV, or bombardment rate).&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;Rule Quality&lt;/strong&gt; and rank by F1.&lt;/li&gt;
&lt;li&gt;Inspect low-scoring pairs and decide whether to adjust rule logic or mapping assumptions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For API details, see &lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/api-reference/manager-api/&quot;&gt;Manager API reference&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Ordered Rule Execution in ezrules: When First Match Beats Conflict Resolution</title>
    <link href="https://ezrules.io/blog/rule-ordering-first-match/"/>
    <updated>2026-04-24T00:00:00Z</updated>
    <id>https://ezrules.io/blog/rule-ordering-first-match/</id>
    <summary>ezrules adds ordered execution and a first_match mode for the main rule lane, letting teams stop on the first matching rule with full audit trail and governance.</summary>
    <content type="html">&lt;p&gt;Most rule engines start with one simple idea: run every active rule, collect every outcome, and then resolve conflicts at the end.&lt;/p&gt;
&lt;p&gt;That works well when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you want broad signal collection&lt;/li&gt;
&lt;li&gt;multiple rules are allowed to fire on the same event&lt;/li&gt;
&lt;li&gt;the final decision should come from a severity hierarchy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It starts to break down when the business intent is really procedural:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;run the strongest allow or deny rule first&lt;/li&gt;
&lt;li&gt;stop once you already know the answer&lt;/li&gt;
&lt;li&gt;make the ordering itself part of the policy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ezrules now supports that second model for the main rule lane through ordered execution and a &lt;code&gt;first_match&lt;/code&gt; mode.&lt;/p&gt;
&lt;h2&gt;When ordered execution is useful&lt;/h2&gt;
&lt;p&gt;Ordered execution is useful when the rule set is really a sequence, not a bag of independent checks.&lt;/p&gt;
&lt;p&gt;Common cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Progressive screening&lt;/strong&gt;: early rules handle obvious or high-confidence cases, and later rules are fallback checks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Short-circuit decisions&lt;/strong&gt;: once one rule returns the intended outcome, running more rules only adds noise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Policy precedence&lt;/strong&gt;: some rules should win because they represent stricter or more authoritative business intent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operational clarity&lt;/strong&gt;: analysts need to know not just which rules exist, but which one gets first shot.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In those cases, &amp;quot;run everything and resolve later&amp;quot; can be misleading. The severity hierarchy still gives you a winner, but it does not reflect the procedural order people actually intended.&lt;/p&gt;
&lt;h2&gt;What ezrules now supports&lt;/h2&gt;
&lt;p&gt;For the &lt;strong&gt;main&lt;/strong&gt; rule lane, ezrules supports two execution modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;all_matches&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;first_match&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;all_matches&lt;/code&gt; is the legacy behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;every active main rule is evaluated&lt;/li&gt;
&lt;li&gt;all non-null outcomes are collected&lt;/li&gt;
&lt;li&gt;the platform resolves the stored &lt;code&gt;resolved_outcome&lt;/code&gt; from the configured outcome hierarchy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;first_match&lt;/code&gt; changes that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;active main rules are evaluated in explicit order&lt;/li&gt;
&lt;li&gt;evaluation stops at the first main rule that returns an outcome&lt;/li&gt;
&lt;li&gt;only that matching rule is persisted for the served result&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The allowlist lane still keeps its own behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;allowlist runs before the main lane&lt;/li&gt;
&lt;li&gt;if an allowlist rule matches, the main lane is skipped&lt;/li&gt;
&lt;li&gt;allowlist is still governed by the configured neutral outcome&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the feature is specifically about how the &lt;strong&gt;main&lt;/strong&gt; rule lane behaves once allowlist did not short-circuit the request.&lt;/p&gt;
&lt;h2&gt;How operators use it&lt;/h2&gt;
&lt;p&gt;The operator flow is now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings -&amp;gt; General&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Main rule execution mode&lt;/strong&gt; to &lt;strong&gt;Stop on first match&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Rules&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Reorder Rules&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Use:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;↑&lt;/code&gt; to move a rule earlier&lt;/li&gt;
&lt;li&gt;&lt;code&gt;↓&lt;/code&gt; to move a rule later&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#&lt;/code&gt; to jump a rule to an exact position&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save Order&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/settings-execution-mode.png&quot; alt=&quot;Main rule execution mode setting&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/rule-order-controls.png&quot; alt=&quot;Rule ordering controls in first-match mode&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When first-match mode is not enabled, the ordering UI is intentionally hidden:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;no &lt;code&gt;Order&lt;/code&gt; column&lt;/li&gt;
&lt;li&gt;no &lt;code&gt;Reorder Rules&lt;/code&gt; button&lt;/li&gt;
&lt;li&gt;no inline position controls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is deliberate. The product only shows ordering controls when ordering actually changes live execution semantics.&lt;/p&gt;
&lt;h2&gt;Why the controls live on the Rules page&lt;/h2&gt;
&lt;p&gt;The first implementation exposed an exact numeric order on individual rule edit screens. That turned out to be the wrong mental model.&lt;/p&gt;
&lt;p&gt;Ordering is not really a property of one rule in isolation. It is a property of the whole main rule set.&lt;/p&gt;
&lt;p&gt;To make that clearer, ezrules now groups rule-order controls in one place:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;Rules&lt;/strong&gt; list page shows the visible sequence&lt;/li&gt;
&lt;li&gt;button-based reordering and exact-position entry both live there&lt;/li&gt;
&lt;li&gt;create and edit screens no longer ask authors to manage absolute position numbers in isolation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That keeps the UI closer to the actual question operators are answering:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;Where should this rule sit relative to the other main rules?&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;How ezrules implements it&lt;/h2&gt;
&lt;p&gt;At the data model level, each rule stores:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;evaluation_lane&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;execution_order&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only non-archived main rules participate in the reorder workflow.&lt;/p&gt;
&lt;p&gt;When the platform rebuilds the production rule config for the main lane, it sorts by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;execution_order&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r_id&lt;/code&gt; as a deterministic tie-breaker&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The runtime setting &lt;code&gt;main_rule_execution_mode&lt;/code&gt; controls whether the evaluator:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;runs the full ordered list and collects all matches, or&lt;/li&gt;
&lt;li&gt;stops on the first matching main rule&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That same execution mode is also snapshotted into the shadow-evaluation queue payload so replay stays consistent with the semantics that were active when the event was actually served.&lt;/p&gt;
&lt;h2&gt;What gets audited&lt;/h2&gt;
&lt;p&gt;Reordering is not just a UI shuffle. It is recorded.&lt;/p&gt;
&lt;p&gt;ezrules now includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a dedicated &lt;code&gt;reorder_rules&lt;/code&gt; permission&lt;/li&gt;
&lt;li&gt;reorder actions stored in rule history with &lt;code&gt;action=&#92;&amp;quot;reordered&#92;&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;execution_order&lt;/code&gt; included in the rule audit response&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That matters because ordered execution changes live decision behavior. If order is a production control surface, it needs the same governance and traceability as promotion, pause, or rollback.&lt;/p&gt;
&lt;h2&gt;What this does not change&lt;/h2&gt;
&lt;p&gt;This feature does &lt;strong&gt;not&lt;/strong&gt; turn the whole platform into a single sequential pipeline.&lt;/p&gt;
&lt;p&gt;It does not change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;allowlist-first precedence&lt;/li&gt;
&lt;li&gt;outcome hierarchy semantics for &lt;code&gt;all_matches&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;backtesting into a full multi-rule sequential simulator&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Backtesting in ezrules is still primarily rule-centric. Ordered serving is about the live main-lane evaluator path.&lt;/p&gt;
&lt;h2&gt;Practical guidance&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;all_matches&lt;/code&gt; when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you want broad observability across the rule set&lt;/li&gt;
&lt;li&gt;you care about every triggered rule&lt;/li&gt;
&lt;li&gt;the severity hierarchy is the right final arbiter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use &lt;code&gt;first_match&lt;/code&gt; when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the rule set is intentionally procedural&lt;/li&gt;
&lt;li&gt;precedence matters more than aggregate conflict resolution&lt;/li&gt;
&lt;li&gt;later rules should only run if earlier rules did not already decide the case&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your team debates whether a rule should be &amp;quot;more severe&amp;quot; or &amp;quot;earlier&amp;quot;, that is a sign you are choosing between two different policy models:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;severity hierarchy&lt;/strong&gt; answers &amp;quot;which outcome wins if several rules fire?&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ordered execution&lt;/strong&gt; answers &amp;quot;which rule gets to decide first?&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ezrules now supports both models explicitly.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Related docs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/api-reference/manager-api/&quot;&gt;Manager API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/user-guide/creating-rules/&quot;&gt;Creating Rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/user-guide/allowlist-rules/&quot;&gt;Allowlist Rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.io/blog/rule-lifecycle-management/&quot;&gt;Rule Lifecycle Management&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Automatic Field Type Management</title>
    <link href="https://ezrules.io/blog/field-type-management/"/>
    <updated>2026-04-24T00:00:00Z</updated>
    <id>https://ezrules.io/blog/field-type-management/</id>
    <summary>ezrules normalizes inconsistent JSON field types at the engine level, so rule comparisons stay correct without per-rule casting, with a full audit trail of changes.</summary>
    <content type="html">&lt;p&gt;JSON doesn&#39;t have a strong type system. An &lt;code&gt;amount&lt;/code&gt; field might arrive as &lt;code&gt;15000&lt;/code&gt; in one event and &lt;code&gt;&amp;quot;15000&amp;quot;&lt;/code&gt; in the next, depending on the source system. When a rule does &lt;code&gt;$amount &amp;gt; 10000&lt;/code&gt;, the result depends entirely on which one shows up — and string comparison produces different ordering than numeric comparison. &lt;code&gt;&amp;quot;9999&amp;quot; &amp;gt; &amp;quot;10000&amp;quot;&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt; lexicographically. That&#39;s backwards.&lt;/p&gt;
&lt;p&gt;The usual fix is to cast inside the rule:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if int($amount) &amp;gt; 10000:
    return !HOLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which works until the next person writes a rule without the cast. Now you have inconsistent behavior across the rule set with no easy way to audit it.&lt;/p&gt;
&lt;p&gt;ezrules v0.12 handles this at the engine level.&lt;/p&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;p&gt;Every event that passes through &lt;code&gt;/api/v2/evaluate&lt;/code&gt; is observed. For each field in the payload, we record the JSON type Python saw — &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, &lt;code&gt;str&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;. The same happens in the Test Rule panel, so observations build up during development without needing live traffic.&lt;/p&gt;
&lt;p&gt;The observations show up under &lt;strong&gt;Settings → Field Types&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;amount    int
amount    str
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two rows for &lt;code&gt;amount&lt;/code&gt; means it arrived with two different types. That&#39;s a data quality signal worth investigating, but you don&#39;t have to wait — configure &lt;code&gt;amount&lt;/code&gt; as &lt;code&gt;float&lt;/code&gt; and both variants are handled correctly from that point on.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/field-type-observed-fields.png&quot; alt=&quot;Observed field types with configure actions&quot; /&gt;&lt;/p&gt;
&lt;p&gt;From the same page, click &lt;strong&gt;Configure&lt;/strong&gt; next to any observation, pick the type, save. For datetime fields you also provide a format string (&lt;code&gt;%Y-%m-%dT%H:%M:%S&lt;/code&gt;), and the value will be parsed into a proper &lt;code&gt;datetime&lt;/code&gt; object before rules run.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/field-type-config-form.png&quot; alt=&quot;Field type configuration form&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/field-type-configured-fields.png&quot; alt=&quot;Configured field type table&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After that, the rule stays as written:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if $amount &amp;gt; 10000:
    return !HOLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The comparison is always numeric. No casts inside rules, no special handling per field.&lt;/p&gt;
&lt;p&gt;If a value can&#39;t be cast — say &lt;code&gt;amount&lt;/code&gt; is &lt;code&gt;&amp;quot;not-applicable&amp;quot;&lt;/code&gt; and the configured type is &lt;code&gt;float&lt;/code&gt; — evaluation returns a &lt;code&gt;400&lt;/code&gt; with the specific field and value that failed. That&#39;s better than a silent wrong answer.&lt;/p&gt;
&lt;h2&gt;What this doesn&#39;t do&lt;/h2&gt;
&lt;p&gt;It doesn&#39;t fix upstream data quality. If your source sends malformed values you&#39;ll find out faster (hard failure rather than incorrect evaluation), but you still have to fix the source.&lt;/p&gt;
&lt;p&gt;It&#39;s also not a replacement for schema validation at ingestion if you need that. The scope here is making rule comparisons correct without requiring every rule author to remember casting.&lt;/p&gt;
&lt;h2&gt;Audit trail&lt;/h2&gt;
&lt;p&gt;Every field type configuration change is recorded — who changed it, when, and what the previous value was. If &lt;code&gt;amount&lt;/code&gt; gets changed from &lt;code&gt;integer&lt;/code&gt; to &lt;code&gt;float&lt;/code&gt; and evaluation behavior shifts, you can trace it directly. Available at &lt;code&gt;GET /api/v2/audit/field-types&lt;/code&gt; or in the Audit Trail page under &lt;strong&gt;Field Type History&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Full docs&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/user-guide/field-types/&quot;&gt;Field Type Management guide&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Why Complex Entities Matter in a Fraud Rules Engine</title>
    <link href="https://ezrules.io/blog/complex-entities-in-fraud-rules/"/>
    <updated>2026-04-24T00:00:00Z</updated>
    <id>https://ezrules.io/blog/complex-entities-in-fraud-rules/</id>
    <summary>ezrules supports nested dotted entity paths across rule logic, testing, observations, and backtesting, so fraud events model real customer, sender, and device relationships.</summary>
    <content type="html">&lt;p&gt;Fraud systems rarely evaluate one flat record with a handful of fields.&lt;/p&gt;
&lt;p&gt;Real transaction monitoring usually involves related entities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;customer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;sender&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;device&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;merchant&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;beneficiary&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the payment instrument&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those entities often have their own attributes and sub-attributes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;customer.profile.age&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer.behavior.avg_amount_30d&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sender.origin.country&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sender.device.trust_score&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If a fraud rules engine forces all of that into one flat namespace, the rule layer becomes harder to read, harder to maintain, and easier to break.&lt;/p&gt;
&lt;p&gt;ezrules now supports canonical dotted nested paths in rule logic, test payloads, observations, backtesting, and the Tested Events UI. That makes it possible to model transactions as collections of related fraud entities instead of flattening everything into one long list of loosely related keys.&lt;/p&gt;
&lt;h2&gt;The practical problem with flat fraud payloads&lt;/h2&gt;
&lt;p&gt;Flattening looks simple at first:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;customer_country&amp;quot;: &amp;quot;US&amp;quot;,
  &amp;quot;sender_country&amp;quot;: &amp;quot;BR&amp;quot;,
  &amp;quot;sender_device_trust_score&amp;quot;: 18,
  &amp;quot;customer_profile_age&amp;quot;: 34
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But as a fraud program grows, flat naming starts to work against you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;field names get long and inconsistent&lt;/li&gt;
&lt;li&gt;two teams may flatten the same concept differently&lt;/li&gt;
&lt;li&gt;related attributes lose their grouping&lt;/li&gt;
&lt;li&gt;the payload stops looking like the real business object it represents&lt;/li&gt;
&lt;li&gt;rule authors have to remember naming conventions instead of business meaning&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That matters because fraud rules are not just technical filters. They are operational policy.&lt;/p&gt;
&lt;p&gt;If an analyst reads a rule, they should be able to see the entity model immediately.&lt;/p&gt;
&lt;h2&gt;What complex entities look like in fraud monitoring&lt;/h2&gt;
&lt;p&gt;A more natural event model keeps related attributes together:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;amount&amp;quot;: 875.5,
  &amp;quot;currency&amp;quot;: &amp;quot;USD&amp;quot;,
  &amp;quot;customer&amp;quot;: {
    &amp;quot;id&amp;quot;: &amp;quot;cust_00042&amp;quot;,
    &amp;quot;country&amp;quot;: &amp;quot;US&amp;quot;,
    &amp;quot;profile&amp;quot;: {
      &amp;quot;age&amp;quot;: 34,
      &amp;quot;segment&amp;quot;: &amp;quot;established&amp;quot;
    },
    &amp;quot;behavior&amp;quot;: {
      &amp;quot;avg_amount_30d&amp;quot;: 140.0,
      &amp;quot;std_amount_30d&amp;quot;: 30.0
    }
  },
  &amp;quot;sender&amp;quot;: {
    &amp;quot;id&amp;quot;: &amp;quot;cust_00042&amp;quot;,
    &amp;quot;country&amp;quot;: &amp;quot;US&amp;quot;,
    &amp;quot;origin&amp;quot;: {
      &amp;quot;country&amp;quot;: &amp;quot;BR&amp;quot;
    },
    &amp;quot;device&amp;quot;: {
      &amp;quot;trust_score&amp;quot;: 18,
      &amp;quot;age_days&amp;quot;: 1
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That structure mirrors how fraud teams actually think about an event:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;who is transacting&lt;/li&gt;
&lt;li&gt;from where&lt;/li&gt;
&lt;li&gt;on what device&lt;/li&gt;
&lt;li&gt;against what baseline&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rules become easier to read because the entity model is visible in the syntax itself.&lt;/p&gt;
&lt;h2&gt;Why this is useful in rule authoring&lt;/h2&gt;
&lt;p&gt;With nested-path support, an ezrules author can now write:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if $customer.profile.age &amp;gt;= 21 and $sender.device.trust_score &amp;lt;= 35 and $sender.origin.country != $customer.country:
    return !HOLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is easier to reason about than a flat equivalent such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if $customer_profile_age &amp;gt;= 21 and $sender_device_trust_score &amp;lt;= 35 and $sender_origin_country != $customer_country:
    return !HOLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The dotted version communicates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which fields belong to the customer entity&lt;/li&gt;
&lt;li&gt;which fields belong to the sender entity&lt;/li&gt;
&lt;li&gt;which nested attributes are part of profile, behavior, origin, or device&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In fraud detection work, this matters because many rules are about relationships across entities, not just thresholds on isolated fields.&lt;/p&gt;
&lt;h2&gt;Common fraud use cases for nested entities&lt;/h2&gt;
&lt;p&gt;Nested entity modeling is especially useful for:&lt;/p&gt;
&lt;h3&gt;Cross-entity mismatch checks&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if $sender.origin.country != $customer.country:
    return !HOLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is clearer than comparing two flattened country fields with ambiguous prefixes.&lt;/p&gt;
&lt;h3&gt;Device risk inside a sender context&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if $sender.device.trust_score &amp;lt;= 20 and $amount &amp;gt;= 500:
    return !HOLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The device is explicitly part of the sender entity rather than a standalone global field.&lt;/p&gt;
&lt;h3&gt;Customer baseline logic&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if $customer.behavior.std_amount_30d &amp;gt; 0:
    zscore = ($amount - $customer.behavior.avg_amount_30d) / $customer.behavior.std_amount_30d
    if zscore &amp;gt;= 3:
        return !HOLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This keeps behavioral baselines grouped under the customer rather than spread across unrelated top-level names.&lt;/p&gt;
&lt;h3&gt;Mixed entity and top-level features&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if $txn_type == &amp;quot;card_purchase&amp;quot; and $customer.profile.segment == &amp;quot;new&amp;quot; and $sender.device.age_days &amp;lt;= 1:
    return !HOLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That kind of rule is common in transaction monitoring: some signals are transaction-level, some are customer-level, and some belong to the sender or device context.&lt;/p&gt;
&lt;h2&gt;Why this helps operational teams, not just engineers&lt;/h2&gt;
&lt;p&gt;Nested entities are not only a schema preference. They improve day-to-day fraud operations.&lt;/p&gt;
&lt;h3&gt;1. Rules read more like policy&lt;/h3&gt;
&lt;p&gt;Analysts and fraud managers often review rule logic directly. Dotted paths make it easier to see what a rule is actually saying.&lt;/p&gt;
&lt;h3&gt;2. Event schemas stay closer to source systems&lt;/h3&gt;
&lt;p&gt;Many payment and case-management systems already expose nested objects. Preserving that shape avoids translation layers that create drift between the source event and the rule event.&lt;/p&gt;
&lt;h3&gt;3. It reduces naming collisions&lt;/h3&gt;
&lt;p&gt;Fields such as &lt;code&gt;country&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;, or &lt;code&gt;age&lt;/code&gt; are common across entities. Nested paths make the scope explicit.&lt;/p&gt;
&lt;h3&gt;4. It improves investigation workflows&lt;/h3&gt;
&lt;p&gt;When an event is stored and reviewed later, the entity grouping still exists. That makes it easier to inspect what happened and why a rule fired.&lt;/p&gt;
&lt;h2&gt;What ezrules now supports around nested paths&lt;/h2&gt;
&lt;p&gt;This is not just parser-level syntax support. ezrules now handles canonical dotted paths across the full rule workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;rule logic can reference nested fields such as &lt;code&gt;$customer.profile.age&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;missing nested fields produce path-aware errors&lt;/li&gt;
&lt;li&gt;field observations record canonical dotted paths&lt;/li&gt;
&lt;li&gt;field type configuration can target dotted paths&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;Test Rule&lt;/strong&gt; panel pre-fills nested JSON objects for nested params&lt;/li&gt;
&lt;li&gt;backtesting applies the same nested-path normalization and eligibility logic&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;Tested Events&lt;/strong&gt; page highlights nested referenced fields inside the JSON payload&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/field-type-observed-fields.png&quot; alt=&quot;Observed nested fields available for configuration&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ezrules.io/assets/blog/tested-event-inspector.png&quot; alt=&quot;Tested event payload with nested referenced fields highlighted&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That end-to-end consistency matters. It is not enough to let authors type nested paths if every downstream tool still assumes flat fields.&lt;/p&gt;
&lt;h2&gt;Why this matters for fraud detection quality&lt;/h2&gt;
&lt;p&gt;Fraud rules often degrade when the event model is too lossy.&lt;/p&gt;
&lt;p&gt;A flat payload tends to encourage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;duplicated concepts&lt;/li&gt;
&lt;li&gt;weaker naming discipline&lt;/li&gt;
&lt;li&gt;rules that reference the wrong field because two similar fields look interchangeable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A more structured entity model makes it easier to write rules against the right concept and to explain why a rule fired.&lt;/p&gt;
&lt;p&gt;That can reduce false positives caused by ambiguous field mapping, especially when several related countries, ids, or behavioral features exist in one event.&lt;/p&gt;
&lt;h2&gt;A useful middle ground&lt;/h2&gt;
&lt;p&gt;This does not mean every fraud event needs to become a deeply nested document.&lt;/p&gt;
&lt;p&gt;A useful middle ground is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;keep core transaction attributes at the top level when they truly are event-wide&lt;/li&gt;
&lt;li&gt;group entity-specific attributes under the entity they belong to&lt;/li&gt;
&lt;li&gt;use nested paths where the grouping carries operational meaning&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;top-level: &lt;code&gt;amount&lt;/code&gt;, &lt;code&gt;currency&lt;/code&gt;, &lt;code&gt;txn_type&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;nested: &lt;code&gt;customer.profile.*&lt;/code&gt;, &lt;code&gt;customer.behavior.*&lt;/code&gt;, &lt;code&gt;sender.origin.*&lt;/code&gt;, &lt;code&gt;sender.device.*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That gives rule authors the clarity of a structured model without making every rule overly verbose.&lt;/p&gt;
&lt;h2&gt;Final point&lt;/h2&gt;
&lt;p&gt;Fraud rules engines are not just evaluating values. They are evaluating relationships between entities.&lt;/p&gt;
&lt;p&gt;As soon as the rule set starts asking questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;Is the sender origin different from the customer country?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Is this device low-trust for this sender?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;Is the amount abnormal for this customer&#39;s baseline?&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;you are already reasoning about complex entities.&lt;/p&gt;
&lt;p&gt;At that point, the event model should help you, not hide the structure.&lt;/p&gt;
&lt;p&gt;Nested-path support in ezrules is useful because it lets the fraud event look more like the fraud problem.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Related docs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/user-guide/creating-rules/&quot;&gt;Creating Rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/user-guide/field-types/&quot;&gt;Field Type Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/api-reference/evaluator-api/&quot;&gt;Evaluator API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ezrules.readthedocs.io/en/latest/api-reference/manager-api/&quot;&gt;Manager API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
</feed>
