Case study · Embedded deployment · Commercial collections
ResolveOS
I performed the collections workflow before writing software for it. ResolveOS is what came out: the workflow's unwritten rules translated into a prioritized daily operating system, deployed into the operation it serves.
Product type
Internal collections operating system
Status
Used daily in a live collections workflow
Role
Embedded engineer, operator, and primary user
Deployment model
Built, deployed, and iterated inside the live workflow
Operating context
Account management and commercial collections
Core workflows
Prioritization, follow-ups, settlements, payments, forecasting, and reconciliation
Data sources
CRM and spreadsheet exports normalized into one workspace
The workflow before ResolveOS
The job ran on exports. Account data arrived as spreadsheets with column names that changed depending on which system produced them. Follow-up priority lived in memory, payment dates were tracked by hand, recurring plans were expanded on paper or not at all, and month-end meant copy-pasting totals between files and re-checking them. Settlement opportunities were hard to compare because nothing sat side by side.
The workflow depended on the operator remembering what software should have remembered. I was doing this work, not observing it, and I got tired of being the database.
The product
ResolveOS answers the operator's actual daily questions. The dashboard shows portfolio health and expected cash for the month. The accounts view is a prioritized working queue rather than a flat list: who gets worked today, who is at risk, who has gone quiet. The opportunity pipeline holds settlements in stages so offers can be compared and forecast with confidence weights. The payments view tracks plans and expected receipts, so a missed payment is visible the day it happens instead of at month-end.
Each view exists to make a decision: what to work next, what to offer, what to chase, and what actually cleared.
Dashboard overview
Portfolio health and expected cash for the month, answered before the first call.Synthetic data
Accounts overview
The working queue: every account with its priority, cadence state, and next payment.Synthetic data
Opportunity pipeline
Settlements tracked by stage, with weighted forecasting.Synthetic data
Payments overview
Payment plans by collection day, so a missed payment is visible immediately.Synthetic data
Daily operating loop
Import or refresh exports→
Normalize accounts and schedules→
Compute priority→
Work follow-ups and settlements→
Log actions and payments→
Reconcile expected against actual
The loop ResolveOS runs every working day. A workflow view, not an interface.
Embedded ownership
I was embedded in this operating environment in the most literal sense: I do the collections work, and the software was built from inside it. The business rules were unwritten. Which accounts get touched today, when a settlement offer is worth comparing, what counts as at risk, none of it existed as a spec. Discovering the rules meant doing the job, and encoding them meant translating how the work actually happens into a data model and priority logic.
The embedded loop
Observed→
Scoped→
Modeled→
Built→
Deployed→
Iterated
Observed as the operator, scoped from the bottlenecks, modeled and built, deployed into the live workflow, iterated from daily use.
I owned product definition from doing the work, interface and workflow design, import normalization, the data model, priority logic, payment scheduling, settlement forecasting, reconciliation, deployment, and maintenance. Because I am also the primary user, weak assumptions show up immediately in the next working day.
Engineering decisions that mattered
01
Normalize messy exports at the boundary
Every import is translated into one internal representation before the application sees it.
Exports arrive with drifting column names, so a fuzzy header mapper collapses 28 real-world aliases into 12 canonical fields, over a hand-written parser that survives quoted fields and embedded newlines. Import complexity stays at the boundary.
ResultThe rest of the application works against clean data it can trust.
02
Prioritize work instead of displaying records
A flat account list tells you nothing about what to do next.
Every account carries a derived priority and an SLA state computed from age and last touch, recomputed live. Fresh accounts decay fastest, so they get the tightest cadence.
P00 to 14 daystouch every day
P115 to 60 daystouch every 3 days
P261 to 179 daystouch every 3 days
P3180+ daystouch every 3 days
The follow-up ladder encodes how collections actually work: fresh accounts are the most winnable.
ResultThe queue answers the daily question directly: who gets worked today.
03
Expand recurring schedules into explicit obligations
A payment plan is not a rule; it is a list of dated expectations.
Recurring agreements expand into dated expected-payment records, with hand-written rules per frequency: bi-weekly anchored to the start weekday, semi-monthly clamped to day 28, monthly clamped to short months.
ResultForecasting, missed-payment detection, and reconciliation all read the same explicit records.
04
Treat timezones as business logic
Spreadsheet dates shifted by a day depending on who was looking.
Imported dates crossed midnight during UTC conversion. The fix: store local-midday timestamps and do all day math on date keys, never on ISO strings.
ResultScheduling stopped drifting, and the principle carried into later products.
05
Separate expected cash from actual receipts
Forecasts are estimates; receipts are facts. The workspace keeps them distinct.
Weighted forecasts come from pipeline stages, expected payments come from schedules, and actual payments are logged against accounts, with prior months archived into denormalized history that survives account deletion.
ResultMonth-end became checking differences, not rebuilding totals.
Architecture and data flow
The shape is simple on purpose: a Next.js application on Supabase, with the complexity concentrated at the import boundary.
01
CRM and spreadsheet exports
source data, inconsistent columns and formats
02
Import normalization
parser plus header aliasing; the only place raw data exists
03
Application data model
normalized accounts, plans, and opportunities in Postgres
actuals recorded, prior months archived into history
From messy source exports to normalized operating state. Raw data exists only at the boundary.
What went wrong
The first import pipeline trusted the exports too much, and real files broke it: duplicate rows inside one file, re-imports creating copies, amounts arriving as text. Deduplication now runs on a composite key against both existing accounts and rows within the same file, and replace-mode teardown happens in dependency order with error short-circuits.
Other corrections came from daily use: renames like Touched to Worked and Status to Collection Day taught the software the operator's language, and a fix to preserve manually set worked dates came from the field, not a spec.
None of this was a dramatic outage. It was daily-use feedback finding weak assumptions fast, which is exactly what an internal tool with its builder as primary user is good at.
Outcome
ResolveOS replaced the spreadsheet and memory workflow it was built inside. It is the primary user's daily workspace: one view of accounts, opportunities, schedules, and expected payments, with month-end reconciliation reduced to checking differences instead of rebuilding totals.
What I would improve next
Part of this rebuild is already underway: a 2026 refactor split the original oversized page into extracted views, hooks, and contexts, and scaffolded an organization model (creditors, debtors, cases) with schema-as-code migrations, the groundwork for use beyond one operator. Still on the list: automated tests, money in integer cents instead of parsed text, deduplication enforced by database constraints rather than client-side checks, and import validation with real error reporting plus a synthetic dataset for safe testing. ResolveOS is where those lessons were learned first; the Velocity platform is where they were applied, down to its schema-first migrations and integer-cent money.
Supporting technical detail
Import normalization rulesHow messy spreadsheets become clean records
A hand-written RFC-4180-style parser handles quoted fields, escaped quotes, and embedded newlines. Fuzzy header mapping collapses 28 observed header aliases into 12 canonical fields. Dates parse from multiple formats with round-trip validation; money values are normalized from text. Imports run in append mode, which dedupes on a composite key of merchant, client, start date, and normalized amount, or replace mode, which tears down in dependency order and short-circuits on error.
Data model and stackWhat it runs on
Next.js and React on a compact seven-table Supabase Postgres model, with per-user scoping applied on queries and real authentication. For scale context only: the original build was roughly 5,600 lines over 31 days, small on purpose for a single-operator tool, and a 2026 refactor has since restructured it into extracted views, hooks, and contexts with schema-as-code migrations.
Current limitationsKnown gaps, stated plainly
No automated tests, amounts persisted as text rather than integer cents, and deduplication enforced client-side rather than by a database constraint. One active operator by design; the in-progress organization model is the path to multi-user use, with permissions and audit history designed in rather than added on.