Skip to content

fix(report-summary): multiply Developer Proceeds by Units in sales summary#5

Open
gonzalobandeira wants to merge 1 commit into
zelentsov-dev:developfrom
gonzalobandeira:fix/sales-summary-proceeds-units
Open

fix(report-summary): multiply Developer Proceeds by Units in sales summary#5
gonzalobandeira wants to merge 1 commit into
zelentsov-dev:developfrom
gonzalobandeira:fix/sales-summary-proceeds-units

Conversation

@gonzalobandeira
Copy link
Copy Markdown

Summary

Fixes a bug in ReportSummary.salesSummary (and the symmetric path in subscriberSummary) where Developer Proceeds was summed without multiplying by Units, causing total proceeds to be undercounted by a factor equal to the average Units-per-row.

The bug

Apple's SALES report aggregates transactions by [Country Code, Product Type, Customer Price, ...], producing rows like:

Country Currency Units Customer Price Developer Proceeds
FR EUR 41 1.99 1.41
GB GBP 10 0.99 0.70

Developer Proceeds is the per-unit amount the developer receives. The row's contribution to total proceeds is Units × Developer Proceeds, not Developer Proceeds alone.

The current code on develop (ReportSummary.swift:52 and :68):

let proceeds = Double(row["Developer Proceeds"] ?? "") ?? 0.0
if !currency.isEmpty {
    proceedsByCurrency[currency, default: 0.0] += proceeds   // ← missing × units
}

Impact

Real-world example from an app with ~$650 in monthly proceeds:

Tool reports (buggy) Actual
Total proceeds (USD eq.) $114.63 $651.67

About 5.7× undercount. The factor depends on the average Units-per-row in the dataset, which is roughly proportional to a row's homogeneity — e.g. a single price tier in one country with many sales will have very high Units, magnifying the undercount.

The bug is not visible to anyone who only looks at total_units, by_product_type, or top_countries (those use units correctly). It only affects proceeds_by_currency (both top-level and inside each by_app entry).

The fix

In salesSummary:

let proceedsPerUnit = Double(row["Developer Proceeds"] ?? "") ?? 0.0
let rowProceeds = proceedsPerUnit * Double(units)
proceedsByCurrency[currency, default: 0.0] += rowProceeds

Same treatment applied to the per-app accumulator (appStats[...].proceedsByCurrency) and to subscriberSummary for symmetry (SUBSCRIBER rows are typically Units=1, so this is mostly defensive).

financialSummary is not affected — it uses Partner Share, which Apple already provides as the row total.

Tests

  • salesSummary_proceedsByCurrency: previously asserted the buggy values (it summed per-unit amounts, which made the test pass even though the production behaviour was wrong). Updated to assert Units × Developer Proceeds.
  • New salesSummary_proceedsScalesWithUnits: regression test with a single Units=100 row that would catch any reintroduction of the bug.
  • New salesSummary_perAppProceedsScalesWithUnits: same regression for the by_app path.

All 24 tests in ReportSummary Tests pass on this branch (swift test --filter ReportSummaryTests).

How to verify against real data

After applying this fix and calling analytics_sales_report with summary_only: true, the proceeds_by_currency should now match what you get by manually computing sum(Units × Developer Proceeds) per currency from the raw rows (which is also what App Store Connect's "Sales and Trends" dashboard displays in your home currency, modulo FX).

…mary

Apple's SALES report `Developer Proceeds` column is the per-unit
amount, not the row total. Each TSV row represents `Units` sales at
`Developer Proceeds` each, so the row's contribution to total proceeds
is Units × Developer Proceeds.

The previous implementation summed the column value directly, ignoring
Units, which undercounted any row with Units > 1. The undercount factor
equals the average Units-per-row for the dataset — typically 5-10x on
monthly reports for actively-selling apps.

Same fix applied to subscriberSummary for symmetry, though SUBSCRIBER
rows are per-transaction so Units is usually 1.

Tests updated:
- salesSummary_proceedsByCurrency: previously asserted the buggy values
  (sum of per-unit prices) — now asserts Units × proceeds.
- Added salesSummary_proceedsScalesWithUnits as a regression test with
  a single Units=100 row.
- Added salesSummary_perAppProceedsScalesWithUnits for the per-app
  accumulator path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant