Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Arrears

Delinquency ladder for self storage. TypeScript, MySQL, no framework.

Live: https://arrears.levelbrook.com

A tenant who stops paying moves through a fixed sequence: courtesy reminder, late fee, overlock, statutory lien notice, advertised sale, auction. That looks like a cron job with a switch statement until you look at what makes it hard.

What it enforces

Lien timelines are rows, not constants. The day thresholds, and whether a step is required at all, are set by state lien law and they differ. In the seeded data Texas allows a lien notice at 30 days past due and California does not until 45, and California does not require overlock at all. Two tenants both 34 days past due sit at different rungs because their states say different things. Encoding this in code means every statutory amendment is a deploy, and an operator running facilities in four states runs one state's rules everywhere.

A state with no rules on file raises rather than falling back to a default. Guessing here means applying some other state's lien timeline to a real tenant.

One step at a time. A ledger ignored for ninety days does not get six notices fired at it in one batch. Several of those steps carry statutory waiting periods between them, and sending them together is legally the same as not sending them.

Steps cannot be skipped or reordered. Every earlier required step is a precondition. The deployed console has a "Send straight to auction" button on every ledger and it is refused every time, with the reason. Skipping a rung is how a unit gets sold out from under someone.

A step is written at most once. Enforced by UNIQUE (ledger_id, step, cycle), not by a preceding "have we already sent this" query, because that check is a race the moment the scheduler runs on two workers. A duplicate lien notice restarts a statutory clock, a duplicate sale advertisement costs money, and a duplicate late fee is how you get a class action.

Only a payment that clears the balance cures the delinquency. Ten dollars against a four hundred dollar balance has not cured anything, and treating it as a reset leaves the unit in limbo while the operator believes it is handled. Curing starts a new ladder cycle rather than deleting the old steps: the notice history is evidence, and if the tenant falls behind again next quarter the operator still has to show what went out the first time.

Seeing it work

  • Send straight to auction on any ledger. Refused, with which precondition is missing or which threshold has not passed.

  • Click it 24 times at once fires 24 simultaneous sends of the due step:

    24 workers tried to send courtesy_reminder
    
      notices written : 1
      refused         : 23
      errors          : none
    
  • Pay $10 against a $425 balance. Posted, still delinquent, ladder does not reset.

  • Compare Rosa (TX) and Priya (CA), both 34 days past due, at different rungs.

Running the tests

mysql -u"$USER" -e "CREATE DATABASE arrears_test;"
npm install
npm test        # 29 tests
npm run typecheck

The database tests skip cleanly when no MySQL is reachable, rather than failing and hiding a real regression behind an environment problem.

One thing the tests caught

completed() originally ordered the step history by taken_at alone. MySQL DATETIME is second-resolution by default, so two steps taken in the same second tied and came back in whatever order the engine chose, which made the sequence assertion flap. That sequence is the audit trail of what was sent to a tenant, so it has to be stable: the insertion id is now the tiebreaker.

Layout

src/ladder.ts   which step is due, and whether a given step may be taken
src/store.ts    persistence, the unique index, payments under a row lock
src/server.ts   the operations console
test/           29 tests, including a 24-way concurrent send

About

Delinquency ladder for self storage. State lien rules as data, ordered steps, exactly-once notices. TypeScript + MySQL.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages