Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions Sources/asc-mcp/Helpers/ReportSummary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ enum ReportSummary {
totalUnits += units

let currency = row["Currency of Proceeds"] ?? row["Customer Currency"] ?? ""
let proceeds = Double(row["Developer Proceeds"] ?? "") ?? 0.0
// Apple's SALES report `Developer Proceeds` column is per-unit, not the row total.
// Multiply by Units to get the row's contribution to total proceeds.
let proceedsPerUnit = Double(row["Developer Proceeds"] ?? "") ?? 0.0
let rowProceeds = proceedsPerUnit * Double(units)
if !currency.isEmpty {
proceedsByCurrency[currency, default: 0.0] += proceeds
proceedsByCurrency[currency, default: 0.0] += rowProceeds
}

let country = row["Country Code"] ?? ""
Expand All @@ -65,7 +68,7 @@ enum ReportSummary {
let title = row["Title"] ?? ""
if !title.isEmpty {
appStats[title, default: AppSalesStats()].units += units
appStats[title, default: AppSalesStats()].proceedsByCurrency[currency, default: 0.0] += proceeds
appStats[title, default: AppSalesStats()].proceedsByCurrency[currency, default: 0.0] += rowProceeds
}
}

Expand Down Expand Up @@ -247,9 +250,12 @@ enum ReportSummary {
}

let currency = row["Proceeds Currency"] ?? row["Customer Currency"] ?? ""
let proceeds = Double(row["Developer Proceeds"] ?? "") ?? 0.0
// Apple's SUBSCRIBER report `Developer Proceeds` is per-unit; multiply by Units
// to get the row total (typically Units=1 per row, but be defensive).
let proceedsPerUnit = Double(row["Developer Proceeds"] ?? "") ?? 0.0
let rowProceeds = proceedsPerUnit * Double(units)
if !currency.isEmpty {
proceedsByCurrency[currency, default: 0.0] += proceeds
proceedsByCurrency[currency, default: 0.0] += rowProceeds
}

let country = row["Country"] ?? ""
Expand Down
38 changes: 35 additions & 3 deletions Tests/ASCMCPTests/HelperTests/ReportSummaryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,48 @@ struct ReportSummaryTests {
}

@Test func salesSummary_proceedsByCurrency() {
// Apple's `Developer Proceeds` column is per-unit. Each row represents `Units`
// sales at `Developer Proceeds` each, so the row's contribution to the total is
// Units × Developer Proceeds.
let rows: [[String: String]] = [
["Units": "10", "Developer Proceeds": "69.90", "Currency of Proceeds": "USD"],
["Units": "5", "Developer Proceeds": "34.95", "Currency of Proceeds": "EUR"],
["Units": "3", "Developer Proceeds": "20.10", "Currency of Proceeds": "USD"]
["Units": "10", "Developer Proceeds": "6.99", "Currency of Proceeds": "USD"],
["Units": "5", "Developer Proceeds": "6.99", "Currency of Proceeds": "EUR"],
["Units": "3", "Developer Proceeds": "6.70", "Currency of Proceeds": "USD"]
]
let summary = ReportSummary.salesSummary(from: rows)
let proceeds = summary["proceeds_by_currency"] as? [String: Double]
// USD: 10 × 6.99 + 3 × 6.70 = 69.90 + 20.10 = 90.00
#expect(proceeds?["USD"] == 90.0)
// EUR: 5 × 6.99 = 34.95
#expect(proceeds?["EUR"] == 34.95)
}

@Test func salesSummary_proceedsScalesWithUnits() {
// Regression test: previously `Developer Proceeds` was summed directly,
// ignoring `Units`, which undercounted multi-unit rows.
let rows: [[String: String]] = [
// 100 sales at $0.70 each in one aggregated row.
["Units": "100", "Developer Proceeds": "0.70", "Currency of Proceeds": "USD"]
]
let summary = ReportSummary.salesSummary(from: rows)
let proceeds = summary["proceeds_by_currency"] as? [String: Double]
#expect(proceeds?["USD"] == 70.0) // 100 × 0.70, not 0.70
}

@Test func salesSummary_perAppProceedsScalesWithUnits() {
// Same regression for the per-app breakdown (by_app).
let rows: [[String: String]] = [
["Title": "MyApp", "Units": "10", "Developer Proceeds": "0.70", "Currency of Proceeds": "USD"],
["Title": "MyApp", "Units": "5", "Developer Proceeds": "0.62", "Currency of Proceeds": "EUR"]
]
let summary = ReportSummary.salesSummary(from: rows)
let byApp = summary["by_app"] as? [[String: Any]]
#expect(byApp?.count == 1)
let stats = byApp?.first?["proceeds_by_currency"] as? [String: Double]
#expect(stats?["USD"] == 7.0) // 10 × 0.70
#expect(stats?["EUR"] == 3.10) // 5 × 0.62
}

@Test func salesSummary_topCountries() {
let rows: [[String: String]] = [
["Units": "50", "Country Code": "US"],
Expand Down Expand Up @@ -65,6 +96,7 @@ struct ReportSummaryTests {
}

@Test func salesSummary_proceedsRounding() {
// Each row has Units=1 so row proceeds == per-unit proceeds.
let rows: [[String: String]] = [
["Units": "1", "Developer Proceeds": "1.111", "Currency of Proceeds": "USD"],
["Units": "1", "Developer Proceeds": "2.222", "Currency of Proceeds": "USD"]
Expand Down