{
  "_comment": "Copy to crm-hygiene-rules.json and change only what decisions.md supports. Keys starting with _ are ignored. Lists replace the defaults; freemail_domains_add extends the built-in list. Values for stages, lifecycle and picklists are compared after lowercasing and removing spaces and punctuation, so 'Closed Won' equals 'closedwon'.",
  "version": "1",
  "b2b": true,
  "internal_domains": [],
  "test_domains": [],
  "freemail_domains_add": [],
  "dot_insensitive_domains": ["gmail.com", "googlemail.com"],
  "typo_domains": {},
  "_comment_owners": "integration_owner_patterns are whole words matched in the owner name. Users file kind column (person, integration, queue) wins when given.",
  "integration_owner_patterns": ["integration", "api", "sync", "system", "automation"],
  "_comment_lifecycle": "lifecycle_order earliest first. owner_required_lifecycle stages need an owner (WARN). Open deals always need one (FAIL), and so do customers on the objects in owner_critical_customer_objects (any of contacts, companies, leads). Teams that own customers at company level remove contacts from that list (customer contacts become WARN) or set contact_owner_from_company to true (a contact without an owner counts as owned when an associated company has an active human owner; needs contact to company association columns). customer_evidence_field names a true/false column that proves a customer without a won deal.",
  "lifecycle_order": ["subscriber", "lead", "marketingqualifiedlead", "salesqualifiedlead", "opportunity", "customer", "evangelist", "other"],
  "owner_required_lifecycle": ["salesqualifiedlead", "opportunity", "customer"],
  "owner_critical_customer_objects": ["contacts", "companies"],
  "contact_owner_from_company": false,
  "customer_lifecycle_values": ["customer"],
  "customer_evidence_field": "",
  "closed_won_stages": ["closedwon"],
  "closed_lost_stages": ["closedlost"],
  "open_lead_status_values": ["new", "open", "inprogress", "attemptedtocontact", "connected"],
  "_comment_pipelines": "Map each pipeline label or ID to its stage labels or IDs, for example {\"Sales Pipeline\": [\"Appointment Scheduled\", \"Closed Won\", \"Closed Lost\"]}. Empty means L07 is NOT CHECKED.",
  "pipelines": {},
  "_comment_required": "Per object: field is a canonical field (country, domain, close_date) or an export header. population: all, marketing, customers, owner_required, unconverted (persons); all, customers (companies); all, open, won, lost, closed (deals). Converted Salesforce leads are left out of every lead population. critical true means FAIL at 1.",
  "required": {"contacts": [], "companies": [], "deals": [], "leads": []},
  "_comment_allowed_values": "Keys are object.field, for example \"contacts.lead_status\": [\"New\", \"Open\", \"In Progress\"]. Values in this list are never treated as placeholders.",
  "allowed_values": {},
  "_comment_unique_keys": "Per object, the export header of a key that must be unique, for example {\"contacts\": \"Product User ID\"}. --unique-key on the command line wins.",
  "unique_keys": {},
  "zero_amount_deal_types": [],
  "deal_company_required": false,
  "lost_reason_required": true,
  "_comment_exemptions": "Accepted exceptions by check ID, each with a decisions.md entry, for example {\"L01\": [\"9008\"]}. Exempt records are removed from the finding and counted in its detail.",
  "exemptions": {},
  "thresholds": {
    "_comment": "Defaults and the reasons for them are in references/checks.md. Change one only with a decisions.md entry.",
    "stale_open_deal_days_past_close": 30,
    "stale_open_deal_fail_share": 0.10,
    "no_activity_days": 60,
    "dormant_days": 365,
    "dormant_warn_share": 0.10,
    "never_engaged_days": 180,
    "non_critical_missing_fail_share": 0.20,
    "non_critical_missing_fail_min_records": 20,
    "integration_owner_fail_share": 0.05,
    "customer_without_evidence_fail_share": 0.05,
    "name_similarity_probable": 0.92,
    "name_similarity_possible": 0.88,
    "company_similarity_probable": 0.93,
    "deal_amount_tolerance": 0.05,
    "deal_created_window_days": 30,
    "renewal_close_gap_days": 300,
    "oversized_block": 50,
    "shared_phone_distinct_names": 4,
    "max_group_size": 10,
    "rule_breadth_share": 0.10,
    "rule_breadth_avg_group": 3,
    "queue_max_days": 2,
    "_comment_bands": "Band for each probable duplicate rule: probable or possible. Calibration sets one to possible when its own sampled pairs are more than 1 in 10 false matches (references/matching-rules.md). d04_band is the fix when the false D04 matches are identical names (score 1.0), which no similarity threshold separates.",
    "d03_band": "probable",
    "d04_band": "probable",
    "d05_name_country_band": "probable",
    "d05_name_phone_band": "probable",
    "d06_band": "probable"
  }
}
