{
  "_comment": "Starter routing policy in the format scripts/route_dry_run.py reads. First matching rule wins; every condition in 'when' must hold and at least one in 'when_any' when present. Field names are columns of the episodes CSV (see assets/example-acme-episodes.csv); _replay is set by the script. Replace pool members, thresholds, the sequence name and the fallback owner with values from decisions.md, keep the precedence, then dry-run it on the last 30 days or 200 episodes, whichever is larger, plus a test row for every rule that never fires. Keys starting with _ are ignored.",
  "policy_id": "<workflow-name>-routing",
  "version": "1",
  "fallback_owner": "duty-rep",
  "pools": {
    "ae": ["AE-1", "AE-2"],
    "sdr": ["SDR-1", "SDR-2"]
  },
  "rules": [
    {
      "id": "R01",
      "_comment": "Replay. A repeat delivery of an event key never acts twice; it adds evidence to the open item.",
      "when": [{"field": "_replay", "op": "eq", "value": true}],
      "route": "replay",
      "reason": "repeat delivery of an event key"
    },
    {
      "id": "R02",
      "_comment": "Suppressed in any scope: no proactive action. Direct requests fall through so a human still answers them.",
      "when": [
        {"field": "suppressed", "op": "eq", "value": true},
        {"field": "direct_request", "op": "ne", "value": true}
      ],
      "route": "no_action",
      "reason": "suppressed"
    },
    {
      "id": "R02a",
      "_comment": "Evidence past its expiry creates no work for anyone. Direct requests never expire, so they fall through.",
      "when": [
        {"field": "direct_request", "op": "ne", "value": true},
        {"field": "evidence_age_days", "op": "gt", "value_field": "expiry_days"}
      ],
      "route": "no_action",
      "reason": "expired"
    },
    {
      "id": "R03",
      "_comment": "Service intent (support, billing, account access) goes to the service queue, never to sales.",
      "when": [{"field": "service_intent", "op": "eq", "value": true}],
      "route": "service",
      "reason": "service intent"
    },
    {
      "id": "R04",
      "_comment": "Existing customers go to the account team. Use the customer owner column your CRM keeps.",
      "when": [{"field": "customer", "op": "eq", "value": true}],
      "route": "cs_task",
      "owner_from": "account_owner",
      "cover_from": "covering_rep",
      "reason": "existing customer, account team task"
    },
    {
      "id": "R05",
      "_comment": "Open opportunity: a note or task for the opportunity owner, above every pool and sequence rule.",
      "when": [{"field": "open_opportunity", "op": "eq", "value": true}],
      "route": "opp_owner_task",
      "owner_from": "opportunity_owner",
      "cover_from": "covering_rep",
      "reason": "open opportunity"
    },
    {
      "id": "R06a",
      "_comment": "A direct request whose identity is not matched still reaches a human through the monitored fallback.",
      "when": [
        {"field": "identity_status", "op": "ne", "value": "matched"},
        {"field": "direct_request", "op": "eq", "value": true}
      ],
      "route": "fallback",
      "reason": "direct request without a matched account"
    },
    {
      "id": "R06b",
      "_comment": "Ambiguous, personal-domain or unmatched identity: hold with the candidate IDs. Never create or enroll.",
      "when": [{"field": "identity_status", "op": "ne", "value": "matched"}],
      "route": "hold",
      "reason": "identity unresolved"
    },
    {
      "id": "R06c",
      "_comment": "Unknown source date or no expiry: research only, never a new task or sequence. It sits below R04 and R05, so a customer or open-opportunity row reaches its owner as a note to verify the date, and below R06b, because research needs a matched account.",
      "when": [{"field": "direct_request", "op": "ne", "value": true}],
      "when_any": [
        {"field": "evidence_age_days", "op": "empty"},
        {"field": "expiry_days", "op": "empty"}
      ],
      "route": "research",
      "reason": "unknown_source_date"
    },
    {
      "id": "R07",
      "_comment": "Named account owner keeps the account. The covering rep acts while the owner is away; ownership does not change.",
      "when": [{"field": "account_owner", "op": "not_empty"}],
      "route": "owner_task",
      "owner_from": "account_owner",
      "cover_from": "covering_rep",
      "reason": "named account owner"
    },
    {
      "id": "R08",
      "_comment": "Active sequence or a human reply inside the reply window (default 30 days): attach to the existing work.",
      "when": [{"field": "direct_request", "op": "ne", "value": true}],
      "when_any": [
        {"field": "active_sequence", "op": "eq", "value": true},
        {"field": "last_human_reply_days", "op": "lte", "value": 30}
      ],
      "route": "attach_existing",
      "reason": "active conversation"
    },
    {
      "id": "R09a",
      "_comment": "No territory means no pool can own it: monitored fallback with a deadline, never a silent drop.",
      "when": [{"field": "territory", "op": "empty"}],
      "route": "fallback",
      "reason": "territory_missing"
    },
    {
      "id": "R09",
      "_comment": "New-business direct request: territory pool. It never waits for enrichment or a score.",
      "when": [{"field": "direct_request", "op": "eq", "value": true}],
      "route": "pool_task",
      "pool": "ae",
      "method": "round_robin",
      "reason": "new-business direct request"
    },
    {
      "id": "R10",
      "_comment": "Sequence only when the enrollment gate passes. Thresholds come from the score spec; inferred signals need corroboration.",
      "when": [
        {"field": "suppressed", "op": "eq", "value": false},
        {"field": "direct_request", "op": "eq", "value": false},
        {"field": "fit", "op": "gte", "value": 35},
        {"field": "engagement", "op": "gte", "value": 20},
        {"field": "fit_complete", "op": "eq", "value": true},
        {"field": "email_validation", "op": "eq", "value": "valid"},
        {"field": "message_version_approved", "op": "eq", "value": true},
        {"field": "evidence_age_days", "op": "lte", "value_field": "expiry_days"}
      ],
      "when_any": [
        {"field": "signal_class", "op": "ne", "value": "inferred"},
        {"field": "corroborated", "op": "eq", "value": true}
      ],
      "route": "sequence",
      "pool": "sdr",
      "method": "round_robin",
      "sequence": "<approved-sequence-name-and-version>",
      "reason": "proactive band, enrollment gate passed"
    },
    {
      "id": "R11",
      "_comment": "In the band but the gate failed (catch-all email, unapproved message): an SDR task, not a sequence.",
      "when": [
        {"field": "fit", "op": "gte", "value": 35},
        {"field": "engagement", "op": "gte", "value": 20},
        {"field": "fit_complete", "op": "eq", "value": true},
        {"field": "signal_class", "op": "ne", "value": "inferred"},
        {"field": "evidence_age_days", "op": "not_empty"}
      ],
      "route": "pool_task",
      "pool": "sdr",
      "method": "round_robin",
      "reason": "proactive band, sequence gate not met"
    },
    {
      "id": "R12",
      "_comment": "Incomplete fit data, inferred signals and public changes outside the gate: account research.",
      "when_any": [
        {"field": "fit_complete", "op": "eq", "value": false},
        {"field": "signal_class", "op": "eq", "value": "inferred"},
        {"field": "signal_class", "op": "eq", "value": "public_change"}
      ],
      "route": "research",
      "reason": "research band"
    },
    {
      "id": "R13",
      "_comment": "Below the band with some engagement: nurture. Nurture is marketing email, so R02 must stay above it.",
      "when": [
        {"field": "engagement", "op": "gt", "value": 0},
        {"field": "territory", "op": "not_empty"}
      ],
      "route": "nurture",
      "reason": "below band"
    },
    {
      "id": "R99",
      "_comment": "Monitored fallback. Name the duty owner and the escalation deadline in the spec.",
      "route": "fallback",
      "reason": "no other rule matched"
    }
  ]
}
