Skip to content

Commit 12bc9b3

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
Readability improvements
1 parent 2f532ae commit 12bc9b3

File tree

10 files changed

+438
-190
lines changed

10 files changed

+438
-190
lines changed

cmd/prcost/main.go

Lines changed: 3 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -481,72 +481,6 @@ func formatLOC(kloc float64) string {
481481
return fmt.Sprintf("%.0fk LOC", kloc)
482482
}
483483

484-
// efficiencyGrade returns a letter grade and message based on efficiency percentage (MIT scale).
485-
func efficiencyGrade(efficiencyPct float64) (grade, message string) {
486-
switch {
487-
case efficiencyPct >= 97:
488-
return "A+", "Impeccable"
489-
case efficiencyPct >= 93:
490-
return "A", "Excellent"
491-
case efficiencyPct >= 90:
492-
return "A-", "Nearly excellent"
493-
case efficiencyPct >= 87:
494-
return "B+", "Acceptable+"
495-
case efficiencyPct >= 83:
496-
return "B", "Acceptable"
497-
case efficiencyPct >= 80:
498-
return "B-", "Nearly acceptable"
499-
case efficiencyPct >= 70:
500-
return "C", "Average"
501-
case efficiencyPct >= 60:
502-
return "D", "Not good my friend."
503-
default:
504-
return "F", "Failing"
505-
}
506-
}
507-
508-
// mergeVelocityGrade returns a grade based on average PR open time in days.
509-
// A+: 4h, A: 8h, A-: 12h, B+: 18h, B: 24h, B-: 36h, C: 100h, D: 120h, F: 120h+.
510-
func mergeVelocityGrade(avgOpenDays float64) (grade, message string) {
511-
switch {
512-
case avgOpenDays <= 0.1667: // 4 hours
513-
return "A+", "Impeccable"
514-
case avgOpenDays <= 0.3333: // 8 hours
515-
return "A", "Excellent"
516-
case avgOpenDays <= 0.5: // 12 hours
517-
return "A-", "Nearly excellent"
518-
case avgOpenDays <= 0.75: // 18 hours
519-
return "B+", "Acceptable+"
520-
case avgOpenDays <= 1.0: // 24 hours
521-
return "B", "Acceptable"
522-
case avgOpenDays <= 1.5: // 36 hours
523-
return "B-", "Nearly acceptable"
524-
case avgOpenDays <= 4.1667: // 100 hours
525-
return "C", "Average"
526-
case avgOpenDays <= 5.0: // 120 hours
527-
return "D", "Not good my friend."
528-
default:
529-
return "F", "Failing"
530-
}
531-
}
532-
533-
// mergeRateGrade returns a grade based on merge success rate percentage.
534-
// A: >90%, B: >80%, C: >70%, D: >60%, F: ≤60%.
535-
func mergeRateGrade(mergeRatePct float64) (grade, message string) {
536-
switch {
537-
case mergeRatePct > 90:
538-
return "A", "Excellent"
539-
case mergeRatePct > 80:
540-
return "B", "Good"
541-
case mergeRatePct > 70:
542-
return "C", "Acceptable"
543-
case mergeRatePct > 60:
544-
return "D", "Low"
545-
default:
546-
return "F", "Poor"
547-
}
548-
}
549-
550484
// printMergeTimeModelingCallout prints a callout showing potential savings from reduced merge time.
551485
func printMergeTimeModelingCallout(breakdown *cost.Breakdown, cfg cost.Config) {
552486
targetHours := cfg.TargetMergeTimeHours
@@ -649,11 +583,10 @@ func printEfficiency(breakdown *cost.Breakdown) {
649583
efficiencyPct = 100.0
650584
}
651585

652-
grade, message := efficiencyGrade(efficiencyPct)
586+
grade, message := cost.EfficiencyGrade(efficiencyPct)
653587

654-
// Calculate merge velocity grade based on PR duration
655-
prDurationDays := breakdown.PRDuration / 24.0
656-
velocityGrade, velocityMessage := mergeVelocityGrade(prDurationDays)
588+
// Calculate merge velocity grade based on PR duration (in hours)
589+
velocityGrade, velocityMessage := cost.MergeVelocityGrade(breakdown.PRDuration)
657590

658591
fmt.Println(" ┌─────────────────────────────────────────────────────────────┐")
659592
headerText := fmt.Sprintf("DEVELOPMENT EFFICIENCY: %s (%.1f%%) - %s", grade, efficiencyPct, message)

cmd/prcost/repository.go

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,19 @@ func analyzeRepository(ctx context.Context, owner, repo string, sampleSize, days
9999
openPRCount = 0
100100
}
101101

102-
// Convert PRSummary to PRMergeStatus for merge rate calculation
103-
prStatuses := make([]cost.PRMergeStatus, len(prs))
102+
// Convert PRSummary to PRSummaryInfo for extrapolation
103+
prSummaryInfos := make([]cost.PRSummaryInfo, len(prs))
104104
for i, pr := range prs {
105-
prStatuses[i] = cost.PRMergeStatus{
105+
prSummaryInfos[i] = cost.PRSummaryInfo{
106+
Owner: pr.Owner,
107+
Repo: pr.Repo,
106108
Merged: pr.Merged,
107109
State: pr.State,
108110
}
109111
}
110112

111-
// Extrapolate costs from samples using library function
112-
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, openPRCount, actualDays, cfg, prStatuses)
113+
// Extrapolate costs from samples using library function (pass nil for visibility since single-repo = public)
114+
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, openPRCount, actualDays, cfg, prSummaryInfos, nil)
113115

114116
// Display results in itemized format
115117
printExtrapolatedResults(fmt.Sprintf("%s/%s", owner, repo), actualDays, &extrapolated, cfg)
@@ -208,17 +210,19 @@ func analyzeOrganization(ctx context.Context, org string, sampleSize, days int,
208210
}
209211
slog.Info("Counted total open PRs across organization", "org", org, "open_prs", totalOpenPRs)
210212

211-
// Convert PRSummary to PRMergeStatus for merge rate calculation
212-
prStatuses := make([]cost.PRMergeStatus, len(prs))
213+
// Convert PRSummary to PRSummaryInfo for extrapolation
214+
prSummaryInfos := make([]cost.PRSummaryInfo, len(prs))
213215
for i, pr := range prs {
214-
prStatuses[i] = cost.PRMergeStatus{
216+
prSummaryInfos[i] = cost.PRSummaryInfo{
217+
Owner: pr.Owner,
218+
Repo: pr.Repo,
215219
Merged: pr.Merged,
216220
State: pr.State,
217221
}
218222
}
219223

220-
// Extrapolate costs from samples using library function
221-
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, totalOpenPRs, actualDays, cfg, prStatuses)
224+
// Extrapolate costs from samples using library function (CLI doesn't fetch visibility, assume public)
225+
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, totalOpenPRs, actualDays, cfg, prSummaryInfos, nil)
222226

223227
// Display results in itemized format
224228
printExtrapolatedResults(fmt.Sprintf("%s (organization)", org), actualDays, &extrapolated, cfg)
@@ -637,19 +641,19 @@ func printExtrapolatedEfficiency(ext *cost.ExtrapolatedBreakdown, days int, cfg
637641
preventableHours := ext.CodeChurnHours + ext.DeliveryDelayHours + ext.AutomatedUpdatesHours + ext.PRTrackingHours
638642
preventableCost := ext.CodeChurnCost + ext.DeliveryDelayCost + ext.AutomatedUpdatesCost + ext.PRTrackingCost
639643

640-
// Calculate efficiency
644+
// Calculate efficiency (for display purposes - grade comes from backend)
641645
var efficiencyPct float64
642646
if ext.TotalHours > 0 {
643647
efficiencyPct = 100.0 * (ext.TotalHours - preventableHours) / ext.TotalHours
644648
} else {
645649
efficiencyPct = 100.0
646650
}
647651

648-
grade, message := efficiencyGrade(efficiencyPct)
649-
650-
// Calculate merge velocity grade based on average PR duration
651-
avgDurationDays := ext.AvgPRDurationHours / 24.0
652-
velocityGrade, velocityMessage := mergeVelocityGrade(avgDurationDays)
652+
// Use grades computed by backend (single source of truth)
653+
grade := ext.EfficiencyGrade
654+
message := ext.EfficiencyMessage
655+
velocityGrade := ext.MergeVelocityGrade
656+
velocityMessage := ext.MergeVelocityMessage
653657

654658
// Calculate annual waste
655659
annualMultiplier := 365.0 / float64(days)
@@ -674,11 +678,11 @@ func printExtrapolatedEfficiency(ext *cost.ExtrapolatedBreakdown, days int, cfg
674678
fmt.Printf(" │ %-60s│\n", velocityHeader)
675679
fmt.Println(" └─────────────────────────────────────────────────────────────┘")
676680

677-
// Merge Rate box (if data available)
681+
// Merge Success Rate box (if data available)
678682
if ext.MergedPRs+ext.UnmergedPRs > 0 {
679-
mergeRateGradeStr, mergeRateMessage := mergeRateGrade(ext.MergeRate)
683+
// Use grade computed by backend (single source of truth)
680684
fmt.Println(" ┌─────────────────────────────────────────────────────────────┐")
681-
mergeRateHeader := fmt.Sprintf("MERGE RATE: %s (%.1f%%) - %s", mergeRateGradeStr, ext.MergeRate, mergeRateMessage)
685+
mergeRateHeader := fmt.Sprintf("MERGE SUCCESS RATE: %s (%.1f%%) - %s", ext.MergeRateGrade, ext.MergeRate, ext.MergeRateGradeMessage)
682686
if len(mergeRateHeader) > innerWidth {
683687
mergeRateHeader = mergeRateHeader[:innerWidth]
684688
}

internal/server/server.go

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,17 +1659,19 @@ func (s *Server) processRepoSample(ctx context.Context, req *RepoSampleRequest,
16591659
openPRCount = 0
16601660
}
16611661

1662-
// Convert PRSummary to PRMergeStatus for merge rate calculation
1663-
prStatuses := make([]cost.PRMergeStatus, len(prs))
1662+
// Convert PRSummary to PRSummaryInfo for extrapolation
1663+
prSummaryInfos := make([]cost.PRSummaryInfo, len(prs))
16641664
for i, pr := range prs {
1665-
prStatuses[i] = cost.PRMergeStatus{
1665+
prSummaryInfos[i] = cost.PRSummaryInfo{
1666+
Owner: pr.Owner,
1667+
Repo: pr.Repo,
16661668
Merged: pr.Merged,
16671669
State: pr.State,
16681670
}
16691671
}
16701672

16711673
// Extrapolate costs from samples
1672-
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, openPRCount, actualDays, cfg, prStatuses)
1674+
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, openPRCount, actualDays, cfg, prSummaryInfos, nil)
16731675

16741676
// Only include seconds_in_state if we have data (turnserver only)
16751677
var secondsInState map[string]int
@@ -1721,6 +1723,23 @@ func (s *Server) processOrgSample(ctx context.Context, req *OrgSampleRequest, to
17211723
return nil, fmt.Errorf("no PRs found in the last %d days", req.Days)
17221724
}
17231725

1726+
// Fetch repository visibility for the organization (2x the time period for comprehensive coverage)
1727+
reposSince := time.Now().AddDate(0, 0, -req.Days*2)
1728+
repoVisibilityData, err := github.FetchOrgRepositoriesWithActivity(ctx, req.Org, reposSince, token)
1729+
if err != nil {
1730+
s.logger.WarnContext(ctx, "Failed to fetch repository visibility, assuming all public", "error", err)
1731+
repoVisibilityData = nil
1732+
}
1733+
1734+
// Convert RepoVisibility map to bool map (repo name -> isPrivate)
1735+
var repoVisibility map[string]bool
1736+
if repoVisibilityData != nil {
1737+
repoVisibility = make(map[string]bool, len(repoVisibilityData))
1738+
for name, visibility := range repoVisibilityData {
1739+
repoVisibility[name] = visibility.IsPrivate
1740+
}
1741+
}
1742+
17241743
// Calculate actual time window (may be less than requested if we hit API limit)
17251744
actualDays, _ = github.CalculateActualTimeWindow(prs, req.Days)
17261745

@@ -1788,17 +1807,19 @@ func (s *Server) processOrgSample(ctx context.Context, req *OrgSampleRequest, to
17881807
}
17891808
s.logger.InfoContext(ctx, "Counted total open PRs across organization", "org", req.Org, "open_prs", totalOpenPRs)
17901809

1791-
// Convert PRSummary to PRMergeStatus for merge rate calculation
1792-
prStatuses := make([]cost.PRMergeStatus, len(prs))
1810+
// Convert PRSummary to PRSummaryInfo for extrapolation
1811+
prSummaryInfos := make([]cost.PRSummaryInfo, len(prs))
17931812
for i, pr := range prs {
1794-
prStatuses[i] = cost.PRMergeStatus{
1813+
prSummaryInfos[i] = cost.PRSummaryInfo{
1814+
Owner: pr.Owner,
1815+
Repo: pr.Repo,
17951816
Merged: pr.Merged,
17961817
State: pr.State,
17971818
}
17981819
}
17991820

18001821
// Extrapolate costs from samples
1801-
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, totalOpenPRs, actualDays, cfg, prStatuses)
1822+
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, totalOpenPRs, actualDays, cfg, prSummaryInfos, repoVisibility)
18021823

18031824
// Only include seconds_in_state if we have data (turnserver only)
18041825
var secondsInState map[string]int
@@ -2194,17 +2215,19 @@ func (s *Server) processRepoSampleWithProgress(ctx context.Context, req *RepoSam
21942215
openPRCount = 0
21952216
}
21962217

2197-
// Convert PRSummary to PRMergeStatus for merge rate calculation
2198-
prStatuses := make([]cost.PRMergeStatus, len(prs))
2218+
// Convert PRSummary to PRSummaryInfo for extrapolation
2219+
prSummaryInfos := make([]cost.PRSummaryInfo, len(prs))
21992220
for i, pr := range prs {
2200-
prStatuses[i] = cost.PRMergeStatus{
2221+
prSummaryInfos[i] = cost.PRSummaryInfo{
2222+
Owner: pr.Owner,
2223+
Repo: pr.Repo,
22012224
Merged: pr.Merged,
22022225
State: pr.State,
22032226
}
22042227
}
22052228

22062229
// Extrapolate costs from samples
2207-
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, openPRCount, actualDays, cfg, prStatuses)
2230+
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, openPRCount, actualDays, cfg, prSummaryInfos, nil)
22082231

22092232
// Only include seconds_in_state if we have data (turnserver only)
22102233
var secondsInState map[string]int
@@ -2353,17 +2376,19 @@ func (s *Server) processOrgSampleWithProgress(ctx context.Context, req *OrgSampl
23532376
}
23542377
s.logger.InfoContext(ctx, "Counted total open PRs across organization", "open_prs", totalOpenPRs, "org", req.Org)
23552378

2356-
// Convert PRSummary to PRMergeStatus for merge rate calculation
2357-
prStatuses := make([]cost.PRMergeStatus, len(prs))
2379+
// Convert PRSummary to PRSummaryInfo for extrapolation
2380+
prSummaryInfos := make([]cost.PRSummaryInfo, len(prs))
23582381
for i, pr := range prs {
2359-
prStatuses[i] = cost.PRMergeStatus{
2382+
prSummaryInfos[i] = cost.PRSummaryInfo{
2383+
Owner: pr.Owner,
2384+
Repo: pr.Repo,
23602385
Merged: pr.Merged,
23612386
State: pr.State,
23622387
}
23632388
}
23642389

23652390
// Extrapolate costs from samples
2366-
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, totalOpenPRs, actualDays, cfg, prStatuses)
2391+
extrapolated := cost.ExtrapolateFromSamples(breakdowns, len(prs), totalAuthors, totalOpenPRs, actualDays, cfg, prSummaryInfos, nil)
23672392

23682393
// Only include seconds_in_state if we have data (turnserver only)
23692394
var secondsInState map[string]int

0 commit comments

Comments
 (0)