← All work

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

ResolveOS dashboard with expected cash-in, at-risk value, pipeline forecast, collected total, monthly goal, revenue drivers, workload and risk counts, stage totals, and projection versus collected, populated with synthetic data
Portfolio health and expected cash for the month, answered before the first call.Synthetic data

Accounts overview

ResolveOS accounts list with merchants, funders, collection day, start date, amount, frequency, age, priority bucket, last-worked and follow-up status, and next payment dates, populated with synthetic data
The working queue: every account with its priority, cadence state, and next payment.Synthetic data

Opportunity pipeline

ResolveOS opportunity pipeline board with settlement opportunities in stages from lead through payment plan made, each with amounts, funders, and confidence weights, populated with synthetic data
Settlements tracked by stage, with weighted forecasting.Synthetic data

Payments overview

ResolveOS payments board organizing accounts by collection day and status with amounts, frequencies, follow-up dates, next payments, and month totals, populated with synthetic data
Payment plans by collection day, so a missed payment is visible immediately.Synthetic data

Daily operating loop

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 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

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.

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.

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.

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.

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.

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.

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.