@@ -177,10 +177,21 @@ type MonthlyStats struct {
177177 // Repository counts
178178 RepoCountContributed int `json:"repoCountContributed"`
179179 RepoCountCreated int `json:"repoCountCreated"`
180+
181+ // Repository lists for cross-period aggregation
182+ // Repositories contains all repos with commits this month (owner/repo format)
183+ Repositories []string `json:"repositories,omitempty"`
180184}
181185
182186// ToMonthlyStats converts MonthlyActivity to a JSON-serializable MonthlyStats.
183187func (m * MonthlyActivity ) ToMonthlyStats () MonthlyStats {
188+ // Extract repository names from CommitsByRepo map
189+ repos := make ([]string , 0 , len (m .CommitsByRepo ))
190+ for repo := range m .CommitsByRepo {
191+ repos = append (repos , repo )
192+ }
193+ sort .Strings (repos ) // Deterministic output
194+
184195 return MonthlyStats {
185196 Year : m .Year ,
186197 Month : int (m .Month ),
@@ -195,6 +206,7 @@ func (m *MonthlyActivity) ToMonthlyStats() MonthlyStats {
195206 NetAdditions : m .NetAdditions (),
196207 RepoCountContributed : m .CommitRepoCount (),
197208 RepoCountCreated : m .RepoCountCreated (),
209+ Repositories : repos ,
198210 }
199211}
200212
@@ -312,3 +324,62 @@ func (t *ActivityTimeline) GetMonthStats(year int, month time.Month) *MonthlySta
312324 stats := m .ToMonthlyStats ()
313325 return & stats
314326}
327+
328+ // AggregateMonthlyStats combines multiple MonthlyStats into an aggregate summary.
329+ // This is useful for computing quarterly or yearly statistics from monthly data.
330+ // The returned stats contain unique repositories across all input months.
331+ func AggregateMonthlyStats (months []MonthlyStats ) AggregatedStats {
332+ agg := AggregatedStats {
333+ repoSet : make (map [string ]struct {}),
334+ }
335+
336+ for _ , m := range months {
337+ agg .Commits += m .Commits
338+ agg .Issues += m .Issues
339+ agg .PRs += m .PRs
340+ agg .Reviews += m .Reviews
341+ agg .Releases += m .Releases
342+ agg .Additions += m .Additions
343+ agg .Deletions += m .Deletions
344+
345+ for _ , repo := range m .Repositories {
346+ agg .repoSet [repo ] = struct {}{}
347+ }
348+ }
349+
350+ agg .NetAdditions = agg .Additions - agg .Deletions
351+ agg .UniqueRepoCount = len (agg .repoSet )
352+
353+ // Build sorted list of unique repos
354+ agg .UniqueRepositories = make ([]string , 0 , len (agg .repoSet ))
355+ for repo := range agg .repoSet {
356+ agg .UniqueRepositories = append (agg .UniqueRepositories , repo )
357+ }
358+ sort .Strings (agg .UniqueRepositories )
359+
360+ return agg
361+ }
362+
363+ // AggregatedStats represents statistics aggregated across multiple months.
364+ type AggregatedStats struct {
365+ // Contribution counts (summed)
366+ Commits int `json:"commits"`
367+ Issues int `json:"issues"`
368+ PRs int `json:"prs"`
369+ Reviews int `json:"reviews"`
370+ Releases int `json:"releases"`
371+
372+ // Code changes (summed)
373+ Additions int `json:"additions"`
374+ Deletions int `json:"deletions"`
375+ NetAdditions int `json:"netAdditions"`
376+
377+ // Unique repository count across all months
378+ UniqueRepoCount int `json:"uniqueRepoCount"`
379+
380+ // List of unique repositories (owner/repo format, sorted)
381+ UniqueRepositories []string `json:"uniqueRepositories,omitempty"`
382+
383+ // Internal: used during aggregation
384+ repoSet map [string ]struct {} `json:"-"`
385+ }
0 commit comments