@@ -77,7 +77,9 @@ enum UsageAnalytics {
7777 return explicitSpend
7878 }
7979
80- /// Auto 池花费:接口常不再返回 bonusSpend;优先 aggregated 第一方汇总,其次 total − API 已用
80+ /// Auto 池花费:优先 aggregated 第一方模型汇总。
81+ /// 注意:`bonusSpend` 常是 period.totalSpend − includedSpend(超额/bonus 口径),
82+ /// **不是** Auto+Composer 池花费,不能优先采用(否则会显示成 ~$253 这种偏小值)。
8183 static func resolveAutoSpendCents(
8284 summary: UsageSummary ? ,
8385 period: PeriodUsageResponse ? ,
@@ -87,13 +89,6 @@ enum UsageAnalytics {
8789 ) -> Double ? {
8890 let periodPlan = period? . planUsage
8991
90- if let bonusSpend = periodPlan? . bonusSpend, bonusSpend > 0 {
91- return bonusSpend
92- }
93- if let autoSpend = periodPlan? . autoSpend, autoSpend > 0 {
94- return autoSpend
95- }
96-
9792 if let fromAggregated = poolSpendCents (
9893 from: aggregated,
9994 autoBucketModels: autoBucketModels,
@@ -102,14 +97,23 @@ enum UsageAnalytics {
10297 return fromAggregated
10398 }
10499
105- let totalSpend = periodPlan? . totalSpend
106- ?? aggregated? . totalCostCents
100+ if let autoSpend = periodPlan? . autoSpend, autoSpend > 0 {
101+ return autoSpend
102+ }
103+
104+ let totalSpend = aggregated? . totalCostCents
105+ ?? periodPlan? . totalSpend
107106 ?? summary? . billingPlan? . used
108107 if let totalSpend, let apiUsedCents, totalSpend >= apiUsedCents {
109108 let derived = totalSpend - apiUsedCents
110109 if derived > 0 { return derived }
111110 }
112111
112+ // 仅作最后兜底;多数账号此字段含义已不是 Auto 池
113+ if let bonusSpend = periodPlan? . bonusSpend, bonusSpend > 0 {
114+ return bonusSpend
115+ }
116+
113117 return nil
114118 }
115119
@@ -844,24 +848,97 @@ enum UsageAnalytics {
844848 )
845849 }
846850
847- /// 将最新一页事件合并进缓存:同 id 以 fresh 为准,按时间新→旧排序。
851+ /// 将最新一页合并进缓存。
852+ /// - 新鲜第 1 页覆盖其时间窗(避免轮询把已从服务端消失的中间快照堆成「飞涨」)
853+ /// - 同 identity 保留 Token/金额更大的版本
854+ /// - 折叠同对话+模型下被更大快照取代的进行中中间态
848855 static func mergeEvents( fresh: [ UsageEvent ] , into cached: [ UsageEvent ] ) -> [ UsageEvent ] {
849- guard !fresh. isEmpty else { return cached }
850- guard !cached. isEmpty else { return sortEventsByNewest ( fresh) }
856+ guard !fresh. isEmpty else {
857+ return sortEventsByNewest ( collapseSupersededSnapshots ( cached) )
858+ }
851859
852- var byID : [ String : UsageEvent ] = Dictionary ( uniqueKeysWithValues : cached . map { ( $0 . id , $0 ) } )
853- for event in fresh {
854- byID [ event . id ] = event
860+ let freshDeduped = dedupeByIdentity ( fresh )
861+ guard !cached . isEmpty else {
862+ return sortEventsByNewest ( collapseSupersededSnapshots ( freshDeduped ) )
855863 }
856- return sortEventsByNewest ( Array ( byID. values) )
864+
865+ let freshOldest = freshDeduped. compactMap ( \. eventDate) . min ( ) ?? . distantPast
866+ // 第 1 页是该时间窗内的权威数据:丢掉窗内旧缓存,再写入 fresh
867+ let retainedOlder = cached. filter { ( $0. eventDate ?? . distantPast) < freshOldest }
868+ let merged = dedupeByIdentity ( retainedOlder + freshDeduped)
869+ return sortEventsByNewest ( collapseSupersededSnapshots ( merged) )
870+ }
871+
872+ private static func dedupeByIdentity( _ events: [ UsageEvent ] ) -> [ UsageEvent ] {
873+ var byKey : [ String : UsageEvent ] = [ : ]
874+ for event in events {
875+ let key = event. identityKey
876+ if let existing = byKey [ key] {
877+ byKey [ key] = preferredEvent ( event, existing)
878+ } else {
879+ byKey [ key] = event
880+ }
881+ }
882+ return Array ( byKey. values)
883+ }
884+
885+ private static func preferredEvent( _ a: UsageEvent , _ b: UsageEvent ) -> UsageEvent {
886+ if a. totalTokens != b. totalTokens {
887+ return a. totalTokens > b. totalTokens ? a : b
888+ }
889+ let aCents = a. chargedCents ?? - 1
890+ let bCents = b. chargedCents ?? - 1
891+ if aCents != bCents {
892+ return aCents > bCents ? a : b
893+ }
894+ return a
895+ }
896+
897+ /// 同一 conversation + model 下,短时间内 Token 明显增长的前序事件视为进行中快照并丢弃。
898+ /// 正常连续 tool 调用(Token 接近)不会被折叠。
899+ static func collapseSupersededSnapshots( _ events: [ UsageEvent ] ) -> [ UsageEvent ] {
900+ guard events. count > 1 else { return events }
901+
902+ let grouped = Dictionary ( grouping: events) { event in
903+ [
904+ event. conversationId ?? " " ,
905+ event. model ?? " " ,
906+ event. cloudAgentId ?? " " ,
907+ ] . joined ( separator: " | " )
908+ }
909+
910+ var survivors : [ UsageEvent ] = [ ]
911+ survivors. reserveCapacity ( events. count)
912+
913+ for (_, group) in grouped {
914+ let ordered = group. sorted {
915+ ( $0. eventDate ?? . distantPast) < ( $1. eventDate ?? . distantPast)
916+ }
917+ var kept : [ UsageEvent ] = [ ]
918+ for event in ordered {
919+ kept. removeAll { isSupersededSnapshot ( $0, by: event) }
920+ kept. append ( event)
921+ }
922+ survivors. append ( contentsOf: kept)
923+ }
924+ return survivors
925+ }
926+
927+ private static func isSupersededSnapshot( _ previous: UsageEvent , by next: UsageEvent ) -> Bool {
928+ guard let previousDate = previous. eventDate, let nextDate = next. eventDate else { return false }
929+ let delta = nextDate. timeIntervalSince ( previousDate)
930+ guard delta > 0 , delta <= 120 else { return false }
931+ guard previous. totalTokens > 0 , next. totalTokens > previous. totalTokens else { return false }
932+ let growth = Double ( next. totalTokens) / Double( previous. totalTokens)
933+ return growth >= 1.15
857934 }
858935
859936 private static func sortEventsByNewest( _ events: [ UsageEvent ] ) -> [ UsageEvent ] {
860937 events. sorted { lhs, rhs in
861938 let left = lhs. eventDate ?? . distantPast
862939 let right = rhs. eventDate ?? . distantPast
863940 if left != right { return left > right }
864- return lhs. id > rhs. id
941+ return lhs. identityKey > rhs. identityKey
865942 }
866943 }
867944
0 commit comments