Collections automation stays compliant when the conduct rules live in a policy engine the model cannot bypass. The model ranks accounts and proposes actions; it sends nothing. A separate layer enforces frequency caps, quiet hours, cease-contact flags, and dispute holds against a current view of the account, then decides what is allowed to go out. Keep those two jobs apart and most conduct failures never reach a debtor.
The reason this matters more in collections than almost anywhere else in lending is that the regulated unit is the contact itself. A credit decision is a single event you can review after the fact. Collections is a stream of actions against a person who is already under financial stress, governed in the US by the FDCPA and its Regulation F implementation, and in the UK by FCA CONC rules and the Consumer Duty. A call placed after 9pm local time, an eighth phone attempt inside a seven-day window, a demand on a debt the consumer already disputed in writing: each is a violation on its own, and automation lets you commit it thousands of times before anyone notices.
Put the rules where the model cannot reach them
The mistake we see is teams asking a model to learn the rules from data. It will not learn quiet hours reliably, and even if it did, you cannot show a supervisor a weight matrix and call it a control. Contact rules are deterministic and they belong in a deterministic layer.
Concretely, we split the system into a ranking model and a policy engine. The model scores accounts on likelihood to cure, best channel, and expected recovery. It never sends anything. Its output is a ranked list of proposed actions. The policy engine takes each proposed action and checks it against hard constraints:
- Frequency caps per channel and in aggregate, counted over the exact windows the regulation specifies rather than a rolling approximation. Regulation F’s presumption of harassment, for instance, turns on more than seven calls in seven days, so the count has to be right to the call.
- Quiet hours in the debtor’s local time zone, which means you need a reliable time zone per account and a fallback when you do not.
- Cease-contact and cease-and-desist flags, which suppress a channel or all channels the moment they are recorded.
- Dispute holds, which stop collection activity on the disputed amount until the dispute is resolved and the required validation has been sent.
- Cooling-off periods after a payment arrangement is agreed or broken.
If any constraint fails, the action is dropped or deferred, and the reason is written to the record. The model’s score does not get a vote on whether a suppressed contact goes out. This is the single most important design decision in the whole system, and it is boring on purpose.
Point-in-time state is the real failure mode
Once the rules layer exists, the next thing that breaks is the data feeding it. The policy engine can be perfect and still authorize a call to someone who paid yesterday, because the feature it read was computed at last night’s batch and the payment posted this morning.
Collections state moves fast. Payments post, arrangements are agreed and broken, disputes are filed, hardship is declared, an account is sold or recalled. Every one of these changes what treatment is lawful. So the engineering problem is point-in-time correctness: the decision must read the account as it is at the moment of action, not as it was at feature-compute time.
- Build the feature store so that contact-eligibility features are either computed on read or invalidated the instant the underlying event lands. A stale eligibility flag is a compliance defect, not a latency inconvenience.
- Watch for leakage in the training data too. If your cure-propensity model was trained on features that silently included post-decision information, its scores are optimistic and it will push contact toward accounts that were always going to self-cure.
- Keep lineage from the raw event to the feature to the action. When something goes wrong you want to trace a specific call back to the exact payment record, dispute record, and flag state that were live when the policy engine ran.
Entity resolution sits underneath all of this. If two accounts belong to the same person and you treat them as separate, your frequency caps are wrong by construction. Collections books are full of duplicate and near-duplicate parties, so resolving them belongs to the compliance surface and cannot wait for a later data-quality pass.
Test the treatment paths, not just the model
Model teams evaluate the ranker with the usual metrics. That tells you nothing about whether the automated treatment stays inside the rules. You need an eval set built around the constraints themselves.
We construct scenario cases that each target one rule: an account that crosses a frequency cap on the next action, one that enters quiet hours mid-sequence, one where a dispute lands between scoring and sending, one with a cease-contact flag set an hour ago. The system passes only if the policy engine suppresses the right action in every case. These run in CI, so a change to the ranking model or the feature pipeline cannot quietly re-enable a suppressed path.
Two more things earn their keep here. First, a false-positive budget on contact: automation makes it cheap to reach more people, and reaching people who were about to cure on their own is both wasteful and a conduct risk under the Consumer Duty’s requirement to avoid foreseeable harm. Hold the system to a stated ceiling on unnecessary contacts. Second, drift monitoring on the inputs that gate treatment, especially time zone coverage, dispute rates, and the share of accounts hitting caps. When those move, your compliance posture has moved with them, whether or not the model’s accuracy metric noticed.
Straight-through processing is the goal for the routine majority of accounts, and it is achievable. But straight-through only means the human is removed from the send, not from the design. The audit trail, the policy versions, the suppression records: those are what a supervisor and a regulator read when they ask whether the machine behaved. Build them first, and the automation holds up. Bolt them on afterward and you are reconstructing intent from logs that were never meant to carry it.
FAQ
Can a model decide contact timing and channel on its own?
It can propose them, but the frequency caps, quiet hours, and cease-contact flags have to be enforced by a rules layer the model cannot override. The model ranks; the policy engine decides what is allowed to send.
How do you prove an automated collections action was compliant?
Every outbound action carries an audit record: the account state read, the policy version that authorized it, the model score, and the timestamp. If a regulator asks why a debtor received a call at a given time, you replay the record rather than reconstruct intent.
What breaks first when you automate treatment paths?
Usually the account state feeding the decision is stale. A payment posted or a dispute was filed after the feature was computed, and the system acts on a picture of the account that is no longer true. Point-in-time freshness matters more than model accuracy here.