Primera build enviada

This commit is contained in:
2026-01-19 14:40:43 +01:00
parent c6be398e5a
commit b03d35194f
36 changed files with 1641 additions and 561 deletions
@@ -43,9 +43,15 @@ class CalculationService {
let monthChange = calculatePeriodChange(sources: sources, from: monthAgo)
let yearChange = calculatePeriodChange(sources: sources, from: yearAgo)
let allTimeReturn = totalValue - totalContributions
let allTimeReturnPercentage = totalContributions > 0
? NSDecimalNumber(decimal: allTimeReturn / totalContributions).doubleValue * 100
let baselineTotal = sources.reduce(Decimal.zero) { partial, source in
if let firstSnapshot = source.sortedSnapshotsByDateAscending.first {
return partial + firstSnapshot.decimalValue
}
return partial + source.latestValue
}
let allTimeReturn = totalValue - baselineTotal
let allTimeReturnPercentage = baselineTotal > 0
? NSDecimalNumber(decimal: allTimeReturn / baselineTotal).doubleValue * 100
: 0
let lastUpdated = snapshots.map { $0.date }.max()
@@ -458,57 +464,36 @@ class CalculationService {
private func buildCategorySeries(from snapshots: [Snapshot]) -> [SeriesPoint] {
let sortedSnapshots = snapshots.sorted { $0.date < $1.date }
let uniqueDates = Array(Set(sortedSnapshots.map { Calendar.current.startOfDay(for: $0.date) }))
.sorted()
guard !uniqueDates.isEmpty else { return [] }
var snapshotsBySource: [UUID: [(date: Date, value: Decimal)]] = [:]
var contributionsByDate: [Date: Decimal] = [:]
for snapshot in sortedSnapshots {
guard let sourceId = snapshot.source?.id else { continue }
snapshotsBySource[sourceId, default: []].append(
(date: snapshot.date, value: snapshot.decimalValue)
)
let day = Calendar.current.startOfDay(for: snapshot.date)
contributionsByDate[day, default: 0] += snapshot.decimalContribution
let groupedByMonth = Dictionary(grouping: sortedSnapshots) { snapshot -> DateComponents in
let components = Calendar.current.dateComponents([.year, .month], from: snapshot.date)
return DateComponents(year: components.year, month: components.month)
}
var indices: [UUID: Int] = [:]
var series: [SeriesPoint] = []
series.reserveCapacity(groupedByMonth.count)
for (index, date) in uniqueDates.enumerated() {
let nextDate = index + 1 < uniqueDates.count
? uniqueDates[index + 1]
: Date.distantFuture
var total: Decimal = 0
for (key, monthSnapshots) in groupedByMonth {
var latestBySource: [UUID: Snapshot] = [:]
var contributions: Decimal = 0
for (sourceId, sourceSnapshots) in snapshotsBySource {
var currentIndex = indices[sourceId] ?? 0
var latest: (date: Date, value: Decimal)?
while currentIndex < sourceSnapshots.count && sourceSnapshots[currentIndex].date < nextDate {
latest = sourceSnapshots[currentIndex]
currentIndex += 1
}
indices[sourceId] = currentIndex
if let latest {
total += latest.value
for snapshot in monthSnapshots {
contributions += snapshot.decimalContribution
guard let sourceId = snapshot.source?.id else { continue }
if let existing = latestBySource[sourceId] {
if snapshot.date > existing.date {
latestBySource[sourceId] = snapshot
}
} else {
latestBySource[sourceId] = snapshot
}
}
series.append(
SeriesPoint(
date: date,
value: total,
contribution: contributionsByDate[date] ?? 0
)
)
let total = latestBySource.values.reduce(Decimal.zero) { $0 + $1.decimalValue }
let date = Calendar.current.date(from: key) ?? Date()
series.append(SeriesPoint(date: date, value: total, contribution: contributions))
}
return series
return series.sorted { $0.date < $1.date }
}
private func calculateMonthlyReturns(from series: [SeriesPoint]) -> [InvestmentMetrics.MonthlyReturn] {