Skip to main content

Inventory. Revenue. Vendors. Supply chain. Cash flow. Cross-system failures are hiding across all of it. Sign up — your critical intelligence tabs are waiting.

All Articles
Finance 8 min read 2026-07-07

Weighted DSO Drift Detection: The Math Behind Automated AR Aging Insights

DuluthPath Platform Team

Published 2026-07-07

# Weighted DSO Drift Detection: The Math Behind Automated AR Aging Insights

Every finance team calculates Days Sales Outstanding. Most calculate it wrong for their operational reality — not because the formula is complicated, but because "DSO" collapses a distribution into a single number. When that distribution is bimodal (a fast-paying enterprise book plus a slow-paying mid-market tail), the single number lies about both segments.

We built our Cash Flow Agent's live AR signal around a weighted-DSO calculation that keeps the shape of the distribution visible. This is what it does, why it matters, and how much working capital it typically unlocks.

The naive formula

Textbook DSO is:

``` DSO = (AR / Revenue) × Period days ```

It's mathematically fine and operationally useless past a certain scale. On a $50M/year business with $6M of open AR, textbook DSO is 43.8 days. Sounds healthy. But if half of that AR is 15 days old and the other half is 95 days old, you have a segment problem the aggregate can't show.

The weighted approach

We aggregate `ar_aging` — one row per open invoice — and compute:

``` weighted_DSO = Σ(days_outstanding × amount_usd) / Σ(amount_usd) ```

The trick: dollars vote proportionally. A $500K invoice 90 days old contributes 90× as much weight as a $500K invoice 1 day old, which is exactly the operational reality of trapped cash. Take the same $6M/50M example, and compute weighted DSO against a real aging bucket:

| Bucket | AR ($) | Days | Contribution | |--------|--------|------|--------------| | 0–30d | 2.4M | 15 | 36.0M day-dollars | | 31–60d | 1.8M | 45 | 81.0M day-dollars | | 61–90d | 1.2M | 75 | 90.0M day-dollars | | >90d | 0.6M | 110 | 66.0M day-dollars |

Total: 273M day-dollars / 6.0M = 45.5 days weighted DSO. Naive formula said 43.8. That 1.7-day gap is where the 61+ bucket lives, and it's exactly the segment where recovery actions actually generate cash.

The drift signal

A single weighted-DSO number is still a snapshot. The agent's job is to spot movement. We compare weighted DSO against a target of 42 days — the industry median for mid-market B2B — and open an investigation when:

  • Drift ≥ 6 days, OR
  • AR > 60d exceeds $25K in absolute terms

Both thresholds have to be tunable. On a $50M/year business, $25K of stuck AR is a rounding error; on a $5M/year business, it's a meaningful chunk of quarterly free cash. We store the thresholds per-tenant in a config document; the defaults ship with the platform and cover most sub-$200M subscribers out of the box.

The trapped-cash estimate

Here's where the executive summary gets its dollar figure:

``` trapped_cash ≈ total_AR × (drift_days / weighted_DSO) ```

On the same $6M / 45.5-day weighted DSO example against a 42-day target:

  • Drift = 3.5 days
  • Trapped cash ≈ $6M × (3.5 / 45.5) ≈ $461K

That $461K is the *working capital your collections cadence would recover if it pulled DSO back to target*. Not a fantasy number, not the whole AR balance — the actual delta between where you are and where the industry median sits.

The CFO doesn't need to trust the formula. They need to trust that the number is derived, transparent, and reproducible. So every investigation carries the exact aggregation payload — total AR, weighted-days sum, over-60d bucket, invoice count — right on the page. Click the finding, see the math, argue with the numbers if you want.

Signal quality tuning

Two failure modes we hit in early tenants:

Ghost invoices. Some ERPs (looking at you, older NetSuite instances) leave paid invoices flagged open for days after clearing. We filter for invoices with `days_outstanding > 0` AND `payment_status != "paid"` AND no partial-payment allocations that would zero the balance. Solves 90% of the noise.

Credit-note offsets. A customer with $200K of open invoices and a $180K unused credit note effectively owes $20K. Weighted DSO on gross AR overstates the exposure. Our aggregation joins `credit_notes` and nets before hashing bucket contributions.

What the agent does with the signal

Every DSO-drift trigger opens an investigation the CFO can approve or reject. The agent proposes 2 actions:

  1. 1Auto-fire a 4-step collections cadence on AR > 60d — email → email → call → early-pay incentive. Estimated recovery: 35% of trapped cash inside 30 days.
  2. 2Extend AP terms with top 20 vendors — NET-30 → NET-45. Doesn't recover AR but adds equivalent DPO. Estimated recovery: 20% of trapped cash equivalent.

Both actions are pre-drafted; nothing writes to the ERP until the CFO signs off through the `/authorizations` page. Every action lands in the SOC 2 audit chain. If the CFO reverses course later, the audit trail proves exactly what fired, when, and by whom.

What the math doesn't tell you

Weighted DSO is a lagging indicator. It tells you what already happened, in dollar-weighted terms, over the current aging window. It doesn't tell you *why* payments slowed. For that we hook the Cash Flow Agent's finding into the MBB causal-reasoning engine, which walks the dependency graph — pricing changes, customer-mix shifts, invoice-format issues, disputed line items — and returns a $-quantified root cause list.

But that's a different post. If you want to see the weighted-DSO calc running live on a sanitized dataset, the shortest path is the `/product-tour` — 5 minutes, no signup.

DSOAR AgingWorking CapitalCash FlowAgent Framework

See DuluthPath in action

Book a personalized demo and explore how AI-powered decision intelligence transforms enterprise operations.