← Custodian of Records Work wiki

The required-document model

Which documents an account needs is a function of product, seller, and jurisdiction, with exceptions, and the production data showed the function is sparse, not a cube.


The first step of every check in the records system is resolving which documents this account needs. Get that wrong and everything downstream is precisely evidenced and beside the point. The model for it went through three versions in three months, each driven by a business artefact arriving.

Version one: a catalogue

A document-type catalogue (each type with its index code, whether it is always or conditionally required) and an exception matrix for seller and portfolio rows: this originator provides no statements; that product has no account agreement. Presence is a set comparison between the resolved set and the inventory, with exceptions applied last.

Version two: a product axis

A validation grid from the team showed the required set and the data points on each document differ by product: a credit card, an instalment loan and an auto deficiency need different things. The catalogue gained a product axis. The grid also grounded a second distinction: account-level documents, validated per account, versus pool-level documents (the chain-of-title paperwork for a purchased portfolio) which cannot be exported per account and are validated once per pool, then looked up by pool ID. An account inherits its pool’s result. Unless the seller requires every entity in the portfolio to be covered individually, which is a config flag that sends the account down the per-account path regardless.

Version three: jurisdiction, and the collapse to sparse

The legal-placement scrub logic, when documented, showed the required set is further keyed by state and county: in one state a particular affidavit substitutes for a contract; in another, accounts after a certain date need a statement type they otherwise wouldn’t. The obvious model was a three-axis cube, product × seller-or-pool × jurisdiction, and that is how it was first drawn.

Then the actual rules-engine export arrived, a few hundred rows, and the cube was wrong in a useful way:

  • The county axis applies to exactly one document type. Everything else resolves state-wide.
  • The pool axis applies to exactly one other document type, for two seller families, which makes it a seller rule, not a jurisdiction rule, and it moved axes.
  • Rules are effective-dated, with start and end timestamps; a tenth of the rows were superseded.
  • Statement documents additionally carry date-shift windows anchored on the account’s own charge-off, last-pay and last-purchase dates: presence alone is insufficient.

So the config became a sparse state map plus a small county overlay for one document type, effective-dated, with the pool rule on the seller axis. It is a mirror of the legal rules engine, versioned and validated against exports of it: deliberately not a live dependency on the legal team’s staging tables, so the records system can’t be broken by someone else’s deploy and the rule in force on a given date is reproducible.

Resolution order

product  →  seller / pool (incl. the pool-only rule)  →  state  →  county overlay (one doc type)
   →  effective date filter  →  seller / portfolio exceptions  →  required set

Then presence, then date-window validation for statement types, then, in pre-legal mode only, everything on balance support.

The lesson

Design the config shape after seeing the data, or design it to be thrown away. The dense cube was the correct generalisation of the documentation and would have shipped a maintenance burden a hundred times the size of the real rule set. The sparse map came from three hundred rows in a spreadsheet, and it is what an expert can actually review.