# Lead Routing Change Fixture Report (`h_murdock/lead-routing-change-fixtures`) Actor

Compare baseline and candidate ordered literal lead-routing models against finite fixtures. Report owner changes, expected-owner mismatches, missing-field uncertainty and fixture-specific shadow evidence. JSON or mapped CSV; no CRM credentials or writes.

- **URL**: https://apify.com/h_murdock/lead-routing-change-fixtures.md
- **Developed by:** [Gilad Ronen](https://apify.com/h_murdock) (community)
- **Categories:** Marketing, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.25 / completed report

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Lead Routing Change Fixture Report

Compare two declared routing-rule versions before handing off a territory or queue change. Supply a baseline model, a candidate model and finite test fixtures. This Actor produces a reproducible report showing the owner each model selects, which rule wins, later matching rules shadowed on that fixture, owner changes and differences from your expected candidate owner.

The workflow is useful for revenue-operations administrators and consultants who already maintain synthetic routing examples or authorized exports. Re-run it when a declared rule order, territory condition or fallback changes, and keep the downloaded evidence beside your change review. This report tests only the literal models you supply. It does not connect to a CRM or reproduce live Salesforce, HubSpot, Flow, Apex or other automation.

### Input

Use `snapshotId` for your review reference and `fields` for 1–20 unique literal attribute names. Field names start with a letter, followed by letters, digits, underscores, dots or hyphens, up to 64 characters. Dots are literal names rather than nested paths. Reserved prototype names are rejected. Use pseudonymous queue and fixture identifiers; names, email addresses and credentials are unnecessary.

Each `baseline` and `candidate` model requires `version`, `fallbackOwnerId` and `rules`. Each rule has `ruleId`, integer `priority`, `ownerId`, explicit `unconditional` and `predicates`. Smaller priority numbers run first. IDs and priorities must be unique within a model; array position does not define precedence. A predicate maps a declared field to a nonempty list of exact string values. Values within one field are alternatives; different fields are combined with AND. Case and whitespace are preserved. An unconditional rule has `unconditional: true` and empty predicates. A conditional rule has `unconditional: false` and at least one predicate. No regex, formulas, arbitrary code, numeric comparison or fuzzy matching is supported.

```json
{
  "version": "candidate-v2",
  "fallbackOwnerId": "queue-general",
  "rules": [{
    "ruleId": "us-enterprise", "priority": 0,
    "ownerId": "queue-us-enterprise", "unconditional": false,
    "predicates": { "territory": ["US"], "segment": ["enterprise"] }
  }]
}
```

Provide `fixtures` as an array of unique `rowId`, `attributes` and optional `expectedCandidateOwner`. Alternatively, remove the array and provide nonempty `fixturesCsv` with optional `fixtureColumns` mapping. The mapping is `{rowId: "Fixture", expectedCandidateOwner: "Expected", attributes: {territory: "Region", segment: "Segment"}}`. Without mapping, headers are `rowId`, optional `expectedCandidateOwner`, and literal field names. If an attribute has one of those two metadata names, its canonical header is `attribute:rowId` or `attribute:expectedCandidateOwner`. CSV supports BOM and quoted values. Duplicate or unmapped headers, irregular rows and duplicate fixture IDs are rejected. Missing attribute columns represent missing evidence. Blank attribute cells represent exact empty strings; they are not missing. A blank expectation means no expectation was supplied. Header-only CSV and an explicit empty array produce a no-fixtures report.

### Interpretation

The first definite matching rule wins only when no earlier rule is uncertain. If a missing field could change that winner, the owner and winning rule are unknown. Possible winner IDs, relevant missing fields and fallback possibility remain visible. A known false AND condition defeats another missing condition in the same rule. Missing conditions after a definite winner cannot change that winner. The report conservatively keeps a winning rule unknown even when its possible owners happen to coincide.

Shadow evidence applies to each supplied fixture only. A later definite match is called shadowed when the first winning rule is resolved. A rule with no definite matching fixture needs more test coverage; this does not prove that the rule is unreachable. An unchanged-owner conclusion applies only to supplied fixtures and does not establish coverage of all real leads. `reviewRequired` can remain true because of uncovered rules, changes, mismatches, uncertainty or an empty fixture set. Owner changes are evidence for review, not automatically mistakes. Expected-owner mismatches compare your expectation with the declared candidate model, not with a CRM's live behavior.

### Results and limits

One dataset item contains the complete report, all supplied fixtures, both normalized models, comparisons, findings and rule coverage. Download full JSON, flat comparison CSV, findings CSV and standalone HTML. The HTML includes models and fixture evidence and has no external scripts. Download it using its attachment link and open it locally. CSV neutralizes spreadsheet formula prefixes; JSON preserves exact identifiers.

Limits are 100 rules per model, 20 fields, 2,000 fixtures, 4 MB total input JSON and 20,000 matched or unknown rule occurrences across both models. JSON must be under 8 MB and each export under 9 MB. Dense matching models or long identifiers can require smaller batches even within the input limits. All size and evidence caps are checked before a report event is charged.

A completed report costs **$0.25**, including platform usage. Invalid input, insufficient budget or an output-limit rejection produces no report event. A new run is billable again. Resuming the same run checks saved input and report integrity and restores exports without adding a second dataset item or report event. Interrupted export writes can leave partial files; recovery rewrites them before publishing the report. Apify stores cloud input and outputs, so use authorized data and follow your own retention policy. No external APIs, paid models, CRM credentials or writes are used.

# Actor input Schema

## `snapshotId` (type: `string`):

Opaque caller reference for this reproducible rule-change review. Use synthetic or authorized pseudonymous fields.

## `fields` (type: `array`):

1–20 unique field names matching letters followed by letters/digits/underscore/dot/hyphen, max64characters. Dots are literal names, not nested paths. Reserved prototype names rejected. Values remain exact strings including case/whitespace.

## `baseline` (type: `object`):

Explicit version,fallbackOwnerId,rules. Each rule: unique ruleId and integer priority0–1000000, opaque ownerId, explicit unconditional boolean, predicates object mapping declared fields to1–100 unique literal allowed strings. AND across fields; membership within each field. Ascending priority wins. unconditional true exactly when predicates empty. Max100rules.

## `candidate` (type: `object`):

Same finite literal rule format as baseline, with explicit candidate version and fallback. Not live Salesforce/HubSpot semantics: no formulas, Flow/Apex, round-robin, regex or API access. Unknown earlier predicate makes winner unknown.

## `fixtures` (type: `array`):

0–2000 rows: unique rowId, attributes object containing only declared string fields, optional expectedCandidateOwner. Missing field is absent, not empty string. Duplicate IDs rejected even if identical. Supply array OR nonemptyCSV; remove array forCSV. Explicit \[] returns no-fixtures, never a clean conclusion. TotalJSON<=4MB;20,000 matched/unknown rule occurrences across both models; output caps may require smaller batches.

## `fixturesCsv` (type: `string`):

Alternative to array. Remove fixtures array, supply nonemptyCSV. BOM/quoted fields accepted; duplicate or unmapped headers and irregular rows rejected. rowId column required; absent attribute columns mean missing evidence, blank attribute cells mean exact empty string. Empty expectation means not supplied. Header-onlyCSV is explicit zero fixtures.

## `fixtureColumns` (type: `object`):

Optional mapping {rowId:header,expectedCandidateOwner:header,attributes:{declaredField:header}}. Mapped headers distinct. rowId required inCSV; expected/attribute columns may be absent. Canonical mapping is harmless with arrays; a custom mapping requiresCSV. UnknownCSV columns are rejected.

## Actor input object example

```json
{
  "snapshotId": "routing-review-2026-10-03",
  "fields": [
    "territory",
    "segment"
  ],
  "baseline": {
    "version": "baseline-v1",
    "fallbackOwnerId": "queue-general",
    "rules": [
      {
        "ruleId": "general-first",
        "priority": 0,
        "ownerId": "queue-general",
        "unconditional": true,
        "predicates": {}
      },
      {
        "ruleId": "us-specific",
        "priority": 10,
        "ownerId": "queue-us",
        "unconditional": false,
        "predicates": {
          "territory": [
            "US"
          ]
        }
      }
    ]
  },
  "candidate": {
    "version": "candidate-v2",
    "fallbackOwnerId": "queue-general",
    "rules": [
      {
        "ruleId": "us-enterprise",
        "priority": 0,
        "ownerId": "queue-us-enterprise",
        "unconditional": false,
        "predicates": {
          "territory": [
            "US"
          ],
          "segment": [
            "enterprise"
          ]
        }
      },
      {
        "ruleId": "eu",
        "priority": 10,
        "ownerId": "queue-eu",
        "unconditional": false,
        "predicates": {
          "territory": [
            "EU"
          ]
        }
      },
      {
        "ruleId": "general-last",
        "priority": 100,
        "ownerId": "queue-general",
        "unconditional": true,
        "predicates": {}
      }
    ]
  },
  "fixtures": [
    {
      "rowId": "001",
      "attributes": {
        "territory": "US",
        "segment": "enterprise"
      },
      "expectedCandidateOwner": "queue-us-enterprise"
    },
    {
      "rowId": "002",
      "attributes": {
        "territory": "EU",
        "segment": "small"
      },
      "expectedCandidateOwner": "queue-general"
    },
    {
      "rowId": "003",
      "attributes": {
        "territory": "JP",
        "segment": "small"
      }
    },
    {
      "rowId": "004",
      "attributes": {
        "segment": "enterprise"
      },
      "expectedCandidateOwner": "queue-general"
    },
    {
      "rowId": "005",
      "attributes": {
        "territory": "",
        "segment": "small"
      }
    }
  ],
  "fixtureColumns": {
    "rowId": "rowId",
    "expectedCandidateOwner": "expectedCandidateOwner",
    "attributes": {
      "territory": "territory",
      "segment": "segment"
    }
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

One complete report, including all fixtures and both models.

## `json` (type: `string`):

No description

## `evidence` (type: `string`):

No description

## `findings` (type: `string`):

No description

## `html` (type: `string`):

Escaped standalone HTML; download and open locally.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "snapshotId": "routing-review-2026-10-03",
    "fields": [
        "territory",
        "segment"
    ],
    "baseline": {
        "version": "baseline-v1",
        "fallbackOwnerId": "queue-general",
        "rules": [
            {
                "ruleId": "general-first",
                "priority": 0,
                "ownerId": "queue-general",
                "unconditional": true,
                "predicates": {}
            },
            {
                "ruleId": "us-specific",
                "priority": 10,
                "ownerId": "queue-us",
                "unconditional": false,
                "predicates": {
                    "territory": [
                        "US"
                    ]
                }
            }
        ]
    },
    "candidate": {
        "version": "candidate-v2",
        "fallbackOwnerId": "queue-general",
        "rules": [
            {
                "ruleId": "us-enterprise",
                "priority": 0,
                "ownerId": "queue-us-enterprise",
                "unconditional": false,
                "predicates": {
                    "territory": [
                        "US"
                    ],
                    "segment": [
                        "enterprise"
                    ]
                }
            },
            {
                "ruleId": "eu",
                "priority": 10,
                "ownerId": "queue-eu",
                "unconditional": false,
                "predicates": {
                    "territory": [
                        "EU"
                    ]
                }
            },
            {
                "ruleId": "general-last",
                "priority": 100,
                "ownerId": "queue-general",
                "unconditional": true,
                "predicates": {}
            }
        ]
    },
    "fixtures": [
        {
            "rowId": "001",
            "attributes": {
                "territory": "US",
                "segment": "enterprise"
            },
            "expectedCandidateOwner": "queue-us-enterprise"
        },
        {
            "rowId": "002",
            "attributes": {
                "territory": "EU",
                "segment": "small"
            },
            "expectedCandidateOwner": "queue-general"
        },
        {
            "rowId": "003",
            "attributes": {
                "territory": "JP",
                "segment": "small"
            }
        },
        {
            "rowId": "004",
            "attributes": {
                "segment": "enterprise"
            },
            "expectedCandidateOwner": "queue-general"
        },
        {
            "rowId": "005",
            "attributes": {
                "territory": "",
                "segment": "small"
            }
        }
    ],
    "fixturesCsv": "",
    "fixtureColumns": {
        "rowId": "rowId",
        "expectedCandidateOwner": "expectedCandidateOwner",
        "attributes": {
            "territory": "territory",
            "segment": "segment"
        }
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("h_murdock/lead-routing-change-fixtures").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "snapshotId": "routing-review-2026-10-03",
    "fields": [
        "territory",
        "segment",
    ],
    "baseline": {
        "version": "baseline-v1",
        "fallbackOwnerId": "queue-general",
        "rules": [
            {
                "ruleId": "general-first",
                "priority": 0,
                "ownerId": "queue-general",
                "unconditional": True,
                "predicates": {},
            },
            {
                "ruleId": "us-specific",
                "priority": 10,
                "ownerId": "queue-us",
                "unconditional": False,
                "predicates": { "territory": ["US"] },
            },
        ],
    },
    "candidate": {
        "version": "candidate-v2",
        "fallbackOwnerId": "queue-general",
        "rules": [
            {
                "ruleId": "us-enterprise",
                "priority": 0,
                "ownerId": "queue-us-enterprise",
                "unconditional": False,
                "predicates": {
                    "territory": ["US"],
                    "segment": ["enterprise"],
                },
            },
            {
                "ruleId": "eu",
                "priority": 10,
                "ownerId": "queue-eu",
                "unconditional": False,
                "predicates": { "territory": ["EU"] },
            },
            {
                "ruleId": "general-last",
                "priority": 100,
                "ownerId": "queue-general",
                "unconditional": True,
                "predicates": {},
            },
        ],
    },
    "fixtures": [
        {
            "rowId": "001",
            "attributes": {
                "territory": "US",
                "segment": "enterprise",
            },
            "expectedCandidateOwner": "queue-us-enterprise",
        },
        {
            "rowId": "002",
            "attributes": {
                "territory": "EU",
                "segment": "small",
            },
            "expectedCandidateOwner": "queue-general",
        },
        {
            "rowId": "003",
            "attributes": {
                "territory": "JP",
                "segment": "small",
            },
        },
        {
            "rowId": "004",
            "attributes": { "segment": "enterprise" },
            "expectedCandidateOwner": "queue-general",
        },
        {
            "rowId": "005",
            "attributes": {
                "territory": "",
                "segment": "small",
            },
        },
    ],
    "fixturesCsv": "",
    "fixtureColumns": {
        "rowId": "rowId",
        "expectedCandidateOwner": "expectedCandidateOwner",
        "attributes": {
            "territory": "territory",
            "segment": "segment",
        },
    },
}

# Run the Actor and wait for it to finish
run = client.actor("h_murdock/lead-routing-change-fixtures").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "snapshotId": "routing-review-2026-10-03",
  "fields": [
    "territory",
    "segment"
  ],
  "baseline": {
    "version": "baseline-v1",
    "fallbackOwnerId": "queue-general",
    "rules": [
      {
        "ruleId": "general-first",
        "priority": 0,
        "ownerId": "queue-general",
        "unconditional": true,
        "predicates": {}
      },
      {
        "ruleId": "us-specific",
        "priority": 10,
        "ownerId": "queue-us",
        "unconditional": false,
        "predicates": {
          "territory": [
            "US"
          ]
        }
      }
    ]
  },
  "candidate": {
    "version": "candidate-v2",
    "fallbackOwnerId": "queue-general",
    "rules": [
      {
        "ruleId": "us-enterprise",
        "priority": 0,
        "ownerId": "queue-us-enterprise",
        "unconditional": false,
        "predicates": {
          "territory": [
            "US"
          ],
          "segment": [
            "enterprise"
          ]
        }
      },
      {
        "ruleId": "eu",
        "priority": 10,
        "ownerId": "queue-eu",
        "unconditional": false,
        "predicates": {
          "territory": [
            "EU"
          ]
        }
      },
      {
        "ruleId": "general-last",
        "priority": 100,
        "ownerId": "queue-general",
        "unconditional": true,
        "predicates": {}
      }
    ]
  },
  "fixtures": [
    {
      "rowId": "001",
      "attributes": {
        "territory": "US",
        "segment": "enterprise"
      },
      "expectedCandidateOwner": "queue-us-enterprise"
    },
    {
      "rowId": "002",
      "attributes": {
        "territory": "EU",
        "segment": "small"
      },
      "expectedCandidateOwner": "queue-general"
    },
    {
      "rowId": "003",
      "attributes": {
        "territory": "JP",
        "segment": "small"
      }
    },
    {
      "rowId": "004",
      "attributes": {
        "segment": "enterprise"
      },
      "expectedCandidateOwner": "queue-general"
    },
    {
      "rowId": "005",
      "attributes": {
        "territory": "",
        "segment": "small"
      }
    }
  ],
  "fixturesCsv": "",
  "fixtureColumns": {
    "rowId": "rowId",
    "expectedCandidateOwner": "expectedCandidateOwner",
    "attributes": {
      "territory": "territory",
      "segment": "segment"
    }
  }
}' |
apify call h_murdock/lead-routing-change-fixtures --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,h_murdock/lead-routing-change-fixtures"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Xi9RHTPLZZL7wFrhR/builds/3H9Bl3FIH4DRdO4hi/openapi.json
