mirror of
https://github.com/lukaszraczylo/git-velocity.git
synced 2026-07-02 05:33:45 +00:00
Improve achievements calculation
This commit is contained in:
@@ -46,13 +46,15 @@ type ContributorMetrics struct {
|
||||
IssueComments int `json:"issue_comments"`
|
||||
|
||||
// Activity patterns
|
||||
ActiveDays int `json:"active_days"` // Unique days with activity
|
||||
CurrentStreak int `json:"current_streak"` // Current consecutive days
|
||||
LongestStreak int `json:"longest_streak"` // Longest consecutive days
|
||||
EarlyBirdCount int `json:"early_bird_count"` // Commits before 9am
|
||||
NightOwlCount int `json:"night_owl_count"` // Commits after 9pm
|
||||
MidnightCount int `json:"midnight_count"` // Commits between midnight and 4am
|
||||
WeekendWarrior int `json:"weekend_warrior"` // Weekend commits
|
||||
ActiveDays int `json:"active_days"` // Unique days with activity
|
||||
CurrentStreak int `json:"current_streak"` // Current consecutive days
|
||||
LongestStreak int `json:"longest_streak"` // Longest consecutive days
|
||||
WorkWeekStreak int `json:"work_week_streak"` // Longest consecutive weekdays (Mon-Fri, weekends don't break streak)
|
||||
EarlyBirdCount int `json:"early_bird_count"` // Commits before 9am
|
||||
NightOwlCount int `json:"night_owl_count"` // Commits after 9pm
|
||||
MidnightCount int `json:"midnight_count"` // Commits between midnight and 4am
|
||||
WeekendWarrior int `json:"weekend_warrior"` // Weekend commits
|
||||
OutOfHoursCount int `json:"out_of_hours_count"` // Commits outside 9am-5pm
|
||||
|
||||
// Repository participation
|
||||
RepositoriesContributed []string `json:"repositories_contributed,omitempty"`
|
||||
@@ -79,6 +81,7 @@ type ScoreBreakdown struct {
|
||||
Comments int `json:"comments"` // PR review comments (not code comments)
|
||||
ResponseBonus int `json:"response_bonus"`
|
||||
LineChanges int `json:"line_changes"`
|
||||
OutOfHours int `json:"out_of_hours"` // Bonus for out-of-hours commits
|
||||
}
|
||||
|
||||
// RepositoryMetrics holds aggregated metrics for a single repository
|
||||
|
||||
@@ -164,9 +164,11 @@ func (c *Calculator) calculateScore(cm *models.ContributorMetrics) models.Score
|
||||
// PR points
|
||||
breakdown.PRs = cm.PRsOpened*points.PROpened + cm.PRsMerged*points.PRMerged
|
||||
|
||||
// Review points (PR reviews and PR review comments)
|
||||
breakdown.Reviews = cm.ReviewsGiven*points.PRReviewed +
|
||||
cm.ReviewComments*points.ReviewComment
|
||||
// Review points (PR reviews)
|
||||
breakdown.Reviews = cm.ReviewsGiven * points.PRReviewed
|
||||
|
||||
// Comment points (PR review comments)
|
||||
breakdown.Comments = cm.ReviewComments * points.ReviewComment
|
||||
|
||||
// Response time bonus
|
||||
if cm.ReviewsGiven > 0 && cm.AvgReviewTime > 0 {
|
||||
@@ -179,9 +181,12 @@ func (c *Calculator) calculateScore(cm *models.ContributorMetrics) models.Score
|
||||
}
|
||||
}
|
||||
|
||||
// Out of hours bonus (commits outside 9am-5pm)
|
||||
breakdown.OutOfHours = cm.OutOfHoursCount * points.OutOfHours
|
||||
|
||||
// Calculate total
|
||||
total := breakdown.Commits + breakdown.LineChanges + breakdown.PRs +
|
||||
breakdown.Reviews + breakdown.ResponseBonus + breakdown.Comments
|
||||
breakdown.Reviews + breakdown.ResponseBonus + breakdown.Comments + breakdown.OutOfHours
|
||||
|
||||
return models.Score{
|
||||
Total: total,
|
||||
@@ -193,7 +198,7 @@ func (c *Calculator) checkAchievements(cm *models.ContributorMetrics) []string {
|
||||
// Collect ALL earned achievements (including all tiers)
|
||||
var achievements []string
|
||||
|
||||
for _, ach := range c.config.Scoring.Achievements {
|
||||
for _, ach := range c.config.Scoring.GetAchievements() {
|
||||
earned := false
|
||||
|
||||
switch ach.Condition.Type {
|
||||
@@ -240,6 +245,10 @@ func (c *Calculator) checkAchievements(cm *models.ContributorMetrics) []string {
|
||||
earned = float64(cm.MidnightCount) >= ach.Condition.Threshold
|
||||
case "weekend_warrior":
|
||||
earned = float64(cm.WeekendWarrior) >= ach.Condition.Threshold
|
||||
case "out_of_hours_count":
|
||||
earned = float64(cm.OutOfHoursCount) >= ach.Condition.Threshold
|
||||
case "work_week_streak":
|
||||
earned = float64(cm.WorkWeekStreak) >= ach.Condition.Threshold
|
||||
}
|
||||
|
||||
if earned {
|
||||
|
||||
@@ -290,40 +290,7 @@ func TestCalculator_Achievements(t *testing.T) {
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Scoring.Enabled = true
|
||||
cfg.Scoring.Achievements = []config.AchievementConfig{
|
||||
{
|
||||
ID: "commit-10",
|
||||
Name: "10 Commits",
|
||||
Condition: config.AchievementCondition{
|
||||
Type: "commit_count",
|
||||
Threshold: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "pr-master",
|
||||
Name: "PR Master",
|
||||
Condition: config.AchievementCondition{
|
||||
Type: "pr_opened_count",
|
||||
Threshold: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "reviewer",
|
||||
Name: "Reviewer",
|
||||
Condition: config.AchievementCondition{
|
||||
Type: "review_count",
|
||||
Threshold: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "speed-demon",
|
||||
Name: "Speed Demon",
|
||||
Condition: config.AchievementCondition{
|
||||
Type: "avg_review_time_hours",
|
||||
Threshold: 1.0,
|
||||
},
|
||||
},
|
||||
}
|
||||
// Achievements are now hardcoded, no need to set them
|
||||
calc := NewCalculator(cfg)
|
||||
|
||||
metrics := &models.GlobalMetrics{
|
||||
@@ -333,10 +300,10 @@ func TestCalculator_Achievements(t *testing.T) {
|
||||
Contributors: []models.ContributorMetrics{
|
||||
{
|
||||
Login: "user1",
|
||||
CommitCount: 15,
|
||||
PRsOpened: 6,
|
||||
ReviewsGiven: 5,
|
||||
AvgReviewTime: 0.5,
|
||||
CommitCount: 15, // Should earn commit-1, commit-10
|
||||
PRsOpened: 6, // Should earn pr-1
|
||||
ReviewsGiven: 5, // Should earn review-1
|
||||
AvgReviewTime: 0.5, // Should earn review-time-1h, review-time-4h, review-time-24h
|
||||
RepositoriesContributed: []string{"owner/repo"},
|
||||
},
|
||||
},
|
||||
@@ -347,12 +314,16 @@ func TestCalculator_Achievements(t *testing.T) {
|
||||
result := calc.Calculate(metrics)
|
||||
|
||||
contributor := result.Repositories[0].Contributors[0]
|
||||
// Should have commit-10, pr-master, and speed-demon
|
||||
// Should NOT have reviewer (only 5 reviews, need 10)
|
||||
// Should have hardcoded achievements based on thresholds
|
||||
assert.Contains(t, contributor.Achievements, "commit-1")
|
||||
assert.Contains(t, contributor.Achievements, "commit-10")
|
||||
assert.Contains(t, contributor.Achievements, "pr-master")
|
||||
assert.Contains(t, contributor.Achievements, "speed-demon")
|
||||
assert.NotContains(t, contributor.Achievements, "reviewer")
|
||||
assert.Contains(t, contributor.Achievements, "pr-1")
|
||||
assert.Contains(t, contributor.Achievements, "review-1")
|
||||
assert.Contains(t, contributor.Achievements, "review-time-1h") // 0.5h < 1h threshold
|
||||
// Should NOT have commit-50 (only 15 commits)
|
||||
assert.NotContains(t, contributor.Achievements, "commit-50")
|
||||
// Should NOT have review-10 (only 5 reviews)
|
||||
assert.NotContains(t, contributor.Achievements, "review-10")
|
||||
}
|
||||
|
||||
func TestCalculator_AllAchievementTypes(t *testing.T) {
|
||||
@@ -360,18 +331,7 @@ func TestCalculator_AllAchievementTypes(t *testing.T) {
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Scoring.Enabled = true
|
||||
cfg.Scoring.Achievements = []config.AchievementConfig{
|
||||
{ID: "commits", Condition: config.AchievementCondition{Type: "commit_count", Threshold: 10}},
|
||||
{ID: "prs-opened", Condition: config.AchievementCondition{Type: "pr_opened_count", Threshold: 5}},
|
||||
{ID: "prs-merged", Condition: config.AchievementCondition{Type: "pr_merged_count", Threshold: 3}},
|
||||
{ID: "reviews", Condition: config.AchievementCondition{Type: "review_count", Threshold: 8}},
|
||||
{ID: "comments", Condition: config.AchievementCondition{Type: "comment_count", Threshold: 20}},
|
||||
{ID: "lines-added", Condition: config.AchievementCondition{Type: "lines_added", Threshold: 1000}},
|
||||
{ID: "lines-deleted", Condition: config.AchievementCondition{Type: "lines_deleted", Threshold: 500}},
|
||||
{ID: "fast-review", Condition: config.AchievementCondition{Type: "avg_review_time_hours", Threshold: 2}},
|
||||
{ID: "multi-repo", Condition: config.AchievementCondition{Type: "repo_count", Threshold: 2}},
|
||||
{ID: "team-player", Condition: config.AchievementCondition{Type: "unique_reviewees", Threshold: 5}},
|
||||
}
|
||||
// Achievements are now hardcoded
|
||||
calc := NewCalculator(cfg)
|
||||
|
||||
metrics := &models.GlobalMetrics{
|
||||
@@ -400,18 +360,23 @@ func TestCalculator_AllAchievementTypes(t *testing.T) {
|
||||
result := calc.Calculate(metrics)
|
||||
|
||||
contributor := result.Repositories[0].Contributors[0]
|
||||
// Should have all achievements
|
||||
assert.Len(t, contributor.Achievements, 10)
|
||||
assert.Contains(t, contributor.Achievements, "commits")
|
||||
assert.Contains(t, contributor.Achievements, "prs-opened")
|
||||
assert.Contains(t, contributor.Achievements, "prs-merged")
|
||||
assert.Contains(t, contributor.Achievements, "reviews")
|
||||
assert.Contains(t, contributor.Achievements, "comments")
|
||||
assert.Contains(t, contributor.Achievements, "lines-added")
|
||||
assert.Contains(t, contributor.Achievements, "lines-deleted")
|
||||
assert.Contains(t, contributor.Achievements, "fast-review")
|
||||
assert.Contains(t, contributor.Achievements, "multi-repo")
|
||||
assert.Contains(t, contributor.Achievements, "team-player")
|
||||
// Should have various hardcoded achievements based on thresholds
|
||||
// Check some key achievements are earned
|
||||
assert.Contains(t, contributor.Achievements, "commit-1")
|
||||
assert.Contains(t, contributor.Achievements, "commit-10")
|
||||
assert.Contains(t, contributor.Achievements, "pr-1")
|
||||
assert.Contains(t, contributor.Achievements, "review-1")
|
||||
assert.Contains(t, contributor.Achievements, "review-10")
|
||||
assert.Contains(t, contributor.Achievements, "comment-10")
|
||||
assert.Contains(t, contributor.Achievements, "lines-added-100")
|
||||
assert.Contains(t, contributor.Achievements, "lines-added-1000")
|
||||
assert.Contains(t, contributor.Achievements, "lines-deleted-100")
|
||||
assert.Contains(t, contributor.Achievements, "lines-deleted-500")
|
||||
assert.Contains(t, contributor.Achievements, "review-time-4h") // 1.5h < 4h
|
||||
assert.Contains(t, contributor.Achievements, "repo-2") // 2 repos
|
||||
assert.Contains(t, contributor.Achievements, "reviewees-3") // 7 reviewees >= 3
|
||||
// Should have earned multiple achievements (more than 10)
|
||||
assert.Greater(t, len(contributor.Achievements), 10)
|
||||
}
|
||||
|
||||
func TestCalculator_TopAchievers(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user