Documentation
¶
Overview ¶
Package cost calculates the real-world cost of GitHub pull requests. Costs are broken down into detailed components with itemized inputs.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AnalysisRequest ¶ added in v0.8.0
type AnalysisRequest struct {
Fetcher PRFetcher // PR data fetcher
Config Config // Cost calculation configuration
Samples []PRSummaryInfo // PRs to analyze
Logger *slog.Logger // Optional logger for progress
Concurrency int // Number of concurrent fetches (0 = sequential)
}
AnalysisRequest contains parameters for analyzing a set of PRs.
type AnalysisResult ¶ added in v0.8.0
type AnalysisResult struct {
Breakdowns []Breakdown
Skipped int // Number of PRs that failed to fetch
}
AnalysisResult contains the breakdowns from analyzed PRs.
func AnalyzePRs ¶ added in v0.8.0
func AnalyzePRs(ctx context.Context, req *AnalysisRequest) (*AnalysisResult, error)
AnalyzePRs processes a set of PRs and returns their cost breakdowns. This is the shared code path used by both CLI and server.
type AuthorCostDetail ¶
type AuthorCostDetail struct {
NewCodeCost float64 `json:"new_code_cost"` // COCOMO cost for new development (net new lines)
AdaptationCost float64 `json:"adaptation_cost"` // COCOMO cost for code adaptation (modified lines)
GitHubCost float64 `json:"github_cost"` // Cost of GitHub interactions (commits, comments, etc.)
GitHubContextCost float64 `json:"github_context_cost"` // Cost of context switching for GitHub sessions
// Supporting details
NewLines int `json:"new_lines"` // Net new lines of code
ModifiedLines int `json:"modified_lines"` // Lines modified from existing code
LinesAdded int `json:"lines_added"` // Total lines added (new + modified)
Events int `json:"events"` // Number of author events
Sessions int `json:"sessions"` // Number of GitHub work sessions
NewCodeHours float64 `json:"new_code_hours"` // Hours for new development (COCOMO)
AdaptationHours float64 `json:"adaptation_hours"` // Hours for code adaptation (COCOMO)
GitHubHours float64 `json:"github_hours"` // Hours spent on GitHub interactions
GitHubContextHours float64 `json:"github_context_hours"` // Hours spent context switching for GitHub
TotalHours float64 `json:"total_hours"` // Total hours (sum of above)
TotalCost float64 `json:"total_cost"` // Total author cost
}
AuthorCostDetail breaks down the author's costs.
type Breakdown ¶
type Breakdown struct {
PRAuthor string `json:"pr_author"`
Participants []ParticipantCostDetail `json:"participants"`
Author AuthorCostDetail `json:"author"`
DelayCostDetail DelayCostDetail `json:"delay_cost_detail"`
AnnualSalary float64 `json:"annual_salary"`
HourlyRate float64 `json:"hourly_rate"`
DelayHours float64 `json:"delay_hours"`
BenefitsMultiplier float64 `json:"benefits_multiplier"`
DelayCost float64 `json:"delay_cost"`
PRDuration float64 `json:"pr_duration"`
TotalCost float64 `json:"total_cost"`
AuthorBot bool `json:"author_bot"`
DelayCapped bool `json:"delay_capped"`
}
Breakdown shows fully itemized costs for a pull request.
type Config ¶
type Config struct {
// Annual salary used for calculating hourly rate (default: $249,000)
// Source: Average Staff Software Engineer salary, 2025 Glassdoor
// https://www.glassdoor.com/Salaries/staff-software-engineer-salary-SRCH_KO0,23.htm
AnnualSalary float64
// Benefits multiplier applied to salary (default: 1.3 = 30% benefits)
BenefitsMultiplier float64
// Hours per year for calculating hourly rate (default: 2080)
HoursPerYear float64
// Time per GitHub event (default: 10 minutes)
EventDuration time.Duration
// Time for context switching in - starting a new session (default: 3 minutes)
// Source: Microsoft Research - Iqbal & Horvitz (2007)
// "Disruption and Recovery of Computing Tasks: Field Study, Analysis, and Directions"
// https://erichorvitz.com/CHI_2007_Iqbal_Horvitz.pdf
ContextSwitchInDuration time.Duration
// Time for context switching out - ending a session (default: 16 minutes 33 seconds)
// Source: Microsoft Research - Iqbal & Horvitz (2007)
// "Disruption and Recovery of Computing Tasks: Field Study, Analysis, and Directions"
// https://erichorvitz.com/CHI_2007_Iqbal_Horvitz.pdf
ContextSwitchOutDuration time.Duration
// Session gap threshold (default: 20 minutes)
// Events within this gap are considered part of the same session
SessionGapThreshold time.Duration
// Delivery delay factor as percentage of hourly rate (default: 0.15 = 15%)
// Represents opportunity cost of blocked value delivery
DeliveryDelayFactor float64
// Automated updates factor for bot-authored PRs (default: 0.01 = 1%)
// Represents overhead of tracking automated dependency updates and bot-driven changes
AutomatedUpdatesFactor float64
// PR tracking cost in minutes per day (default: 1.0 minute per day)
// Applied to PRs open >24 hours to represent ongoing triage/tracking overhead
PRTrackingMinutesPerDay float64
// Maximum time after last event to count for project delay (default: 14 days / 2 weeks)
// Only counts delay costs up to this many days after the last event on the PR
MaxDelayAfterLastEvent time.Duration
// Maximum total project delay duration (default: 90 days / 3 months)
// Absolute cap on project delay costs regardless of PR age
MaxProjectDelay time.Duration
// Maximum duration for code drift calculation (default: 90 days / 3 months)
// Code drift is capped at this duration (affects rework percentage)
MaxCodeDrift time.Duration
// Code review inspection rate in lines per hour (default: 275 LOC/hour)
// Based on IEEE/Fagan inspection research showing optimal rates of 150-400 LOC/hour
// - Fagan inspection (thorough): ~22 LOC/hour
// - Industry standard: 150-200 LOC/hour
// - Fast/lightweight: up to 400 LOC/hour
// - Average: 275 LOC/hour (midpoint of optimal range)
// Used for both past and future review time estimates
// Formula: review_hours = LOC / inspection_rate
ReviewInspectionRate float64
// ModificationCostFactor is the cost multiplier for modified code vs new code (default: 0.4)
// Based on COCOMO II research showing that modifying existing code is cheaper than writing new code.
// - New code: 1.0x (full cost)
// - Modified code: 0.2-0.4x (20-40% of new code cost)
// Default of 0.4 (40%) represents the upper end of the typical range.
// Modification is cheaper because architecture is established and patterns are known.
ModificationCostFactor float64
// COCOMO configuration for estimating code writing effort
COCOMO cocomo.Config
}
Config holds all tunable parameters for cost calculation.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns reasonable defaults for cost calculation.
type DelayCostDetail ¶
type DelayCostDetail struct {
DeliveryDelayCost float64 `json:"delivery_delay_cost"` // Opportunity cost - blocked value delivery (15% factor)
CodeChurnCost float64 `json:"code_churn_cost"` // COCOMO cost for rework/merge conflicts
AutomatedUpdatesCost float64 `json:"automated_updates_cost"` // Overhead for bot-authored PRs (1% factor)
PRTrackingCost float64 `json:"pr_tracking_cost"` // Daily tracking cost for PRs open >24 hours (1 min/day)
// Future costs (estimated for open PRs) - split across 2 people
FutureReviewCost float64 `json:"future_review_cost"` // Cost for future review events (2 events × 20 min)
FutureMergeCost float64 `json:"future_merge_cost"` // Cost for future merge event (1 event × 20 min)
FutureContextCost float64 `json:"future_context_cost"` // Cost for future context switching (3 events × 40 min)
// Supporting details
DeliveryDelayHours float64 `json:"delivery_delay_hours"` // Hours of delivery delay
CodeChurnHours float64 `json:"code_churn_hours"` // Hours for code churn
AutomatedUpdatesHours float64 `json:"automated_updates_hours"` // Hours of automated update tracking
PRTrackingHours float64 `json:"pr_tracking_hours"` // Hours of PR tracking (for PRs open >24 hours)
FutureReviewHours float64 `json:"future_review_hours"` // Hours for future review events
FutureMergeHours float64 `json:"future_merge_hours"` // Hours for future merge event
FutureContextHours float64 `json:"future_context_hours"` // Hours for future context switching
ReworkPercentage float64 `json:"rework_percentage"` // Percentage of code requiring rework (1%-41%)
TotalDelayCost float64 `json:"total_delay_cost"` // Total delay cost (sum of above)
TotalDelayHours float64 `json:"total_delay_hours"` // Total delay hours
}
DelayCostDetail holds itemized delay costs.
type ExtrapolatedBreakdown ¶ added in v0.7.0
type ExtrapolatedBreakdown struct {
// Sample metadata
TotalPRs int `json:"total_prs"` // Total number of PRs in the population
HumanPRs int `json:"human_prs"` // Number of human-authored PRs
BotPRs int `json:"bot_prs"` // Number of bot-authored PRs
SampledPRs int `json:"sampled_prs"` // Number of PRs successfully sampled
SuccessfulSamples int `json:"successful_samples"` // Number of samples that processed successfully
UniqueAuthors int `json:"unique_authors"` // Number of unique PR authors (excluding bots) in sample
TotalAuthors int `json:"total_authors"` // Total unique authors across all PRs (not just samples)
WasteHoursPerWeek float64 `json:"waste_hours_per_week"` // Preventable hours wasted per week (organizational)
WasteCostPerWeek float64 `json:"waste_cost_per_week"` // Preventable cost wasted per week (organizational)
WasteHoursPerAuthorPerWeek float64 `json:"waste_hours_per_author_per_week"` // Preventable hours wasted per author per week
WasteCostPerAuthorPerWeek float64 `json:"waste_cost_per_author_per_week"` // Preventable cost wasted per author per week
AvgPRDurationHours float64 `json:"avg_pr_duration_hours"` // Average PR open time in hours (all PRs)
AvgHumanPRDurationHours float64 `json:"avg_human_pr_duration_hours"` // Average human PR open time in hours
AvgBotPRDurationHours float64 `json:"avg_bot_pr_duration_hours"` // Average bot PR open time in hours
// Author costs (extrapolated)
AuthorNewCodeCost float64 `json:"author_new_code_cost"`
AuthorAdaptationCost float64 `json:"author_adaptation_cost"`
AuthorGitHubCost float64 `json:"author_github_cost"`
AuthorGitHubContextCost float64 `json:"author_github_context_cost"`
AuthorTotalCost float64 `json:"author_total_cost"`
// Author hours (extrapolated)
AuthorNewCodeHours float64 `json:"author_new_code_hours"`
AuthorAdaptationHours float64 `json:"author_adaptation_hours"`
AuthorGitHubHours float64 `json:"author_github_hours"`
AuthorGitHubContextHours float64 `json:"author_github_context_hours"`
AuthorTotalHours float64 `json:"author_total_hours"`
// Author activity metrics (extrapolated)
AuthorEvents int `json:"author_events"` // Total GitHub events by authors
AuthorSessions int `json:"author_sessions"` // Total GitHub work sessions by authors
// LOC metrics (extrapolated totals)
TotalNewLines int `json:"total_new_lines"` // Total net new lines across all PRs
TotalModifiedLines int `json:"total_modified_lines"` // Total modified lines across all PRs
BotNewLines int `json:"bot_new_lines"` // Total net new lines from bot PRs
BotModifiedLines int `json:"bot_modified_lines"` // Total modified lines from bot PRs
OpenPRs int `json:"open_prs"` // Number of currently open PRs
// Participant costs (extrapolated, combined across all reviewers)
ParticipantReviewCost float64 `json:"participant_review_cost"`
ParticipantGitHubCost float64 `json:"participant_github_cost"`
ParticipantContextCost float64 `json:"participant_context_cost"`
ParticipantTotalCost float64 `json:"participant_total_cost"`
// Participant hours (extrapolated)
ParticipantReviewHours float64 `json:"participant_review_hours"`
ParticipantGitHubHours float64 `json:"participant_github_hours"`
ParticipantContextHours float64 `json:"participant_context_hours"`
ParticipantTotalHours float64 `json:"participant_total_hours"`
// Participant activity metrics (extrapolated)
ParticipantEvents int `json:"participant_events"` // Total GitHub events by participants
ParticipantSessions int `json:"participant_sessions"` // Total GitHub work sessions by participants
ParticipantReviews int `json:"participant_reviews"` // Total number of reviews performed
// Delay costs (extrapolated)
DeliveryDelayCost float64 `json:"delivery_delay_cost"`
CodeChurnCost float64 `json:"code_churn_cost"`
AutomatedUpdatesCost float64 `json:"automated_updates_cost"`
PRTrackingCost float64 `json:"pr_tracking_cost"`
FutureReviewCost float64 `json:"future_review_cost"`
FutureMergeCost float64 `json:"future_merge_cost"`
FutureContextCost float64 `json:"future_context_cost"`
DelayTotalCost float64 `json:"delay_total_cost"`
// Delay hours (extrapolated)
DeliveryDelayHours float64 `json:"delivery_delay_hours"`
CodeChurnHours float64 `json:"code_churn_hours"`
AutomatedUpdatesHours float64 `json:"automated_updates_hours"`
PRTrackingHours float64 `json:"pr_tracking_hours"`
FutureReviewHours float64 `json:"future_review_hours"`
FutureMergeHours float64 `json:"future_merge_hours"`
FutureContextHours float64 `json:"future_context_hours"`
DelayTotalHours float64 `json:"delay_total_hours"`
// Counts for future costs (extrapolated)
CodeChurnPRCount int `json:"code_churn_pr_count"` // Number of PRs with code churn
FutureReviewPRCount int `json:"future_review_pr_count"` // Number of PRs with future review costs
FutureMergePRCount int `json:"future_merge_pr_count"` // Number of PRs with future merge costs
FutureContextSessions int `json:"future_context_sessions"` // Estimated future context switching sessions
AvgReworkPercentage float64 `json:"avg_rework_percentage"` // Average code drift/rework percentage
// Grand totals
TotalCost float64 `json:"total_cost"`
TotalHours float64 `json:"total_hours"`
// R2R cost savings calculation
UniqueNonBotUsers int `json:"unique_non_bot_users"` // Count of unique non-bot users (authors + participants)
R2RSavings float64 `json:"r2r_savings"` // Annual savings if R2R cuts PR time to 40 minutes
}
ExtrapolatedBreakdown represents cost estimates extrapolated from a sample of PRs to estimate total costs across a larger population.
func ExtrapolateFromSamples ¶ added in v0.7.0
func ExtrapolateFromSamples(breakdowns []Breakdown, totalPRs, totalAuthors, actualOpenPRs int, daysInPeriod int, cfg Config) ExtrapolatedBreakdown
ExtrapolateFromSamples calculates extrapolated cost estimates from a sample of PR breakdowns to estimate costs across a larger population.
Parameters:
- breakdowns: Slice of Breakdown structs from successfully processed samples
- totalPRs: Total number of PRs in the population
- totalAuthors: Total number of unique authors across all PRs (not just samples)
- daysInPeriod: Number of days the sample covers (for per-week calculations)
- cfg: Configuration for hourly rate and hours per week calculation
Returns:
- ExtrapolatedBreakdown with averaged costs scaled to total population
The function computes the average cost per PR from the samples, then multiplies by the total PR count to estimate population-wide costs.
type PRData ¶
type PRData struct {
CreatedAt time.Time
ClosedAt time.Time
Author string
Events []ParticipantEvent
LinesAdded int
LinesDeleted int
AuthorBot bool
}
PRData contains all information needed to calculate PR costs.
type PRFetcher ¶ added in v0.8.0
type PRFetcher interface {
// FetchPRData fetches full PR data for analysis.
FetchPRData(ctx context.Context, prURL string, updatedAt time.Time) (PRData, error)
}
PRFetcher is an interface for fetching PR data. This allows different implementations (with/without caching, different data sources).
type PRSummaryInfo ¶ added in v0.8.0
PRSummaryInfo contains basic PR information needed for fetching.
type ParticipantCostDetail ¶
type ParticipantCostDetail struct {
Actor string `json:"actor"` // Participant username
ReviewCost float64 `json:"review_cost"` // Cost of code review (LOC-based, once per reviewer)
GitHubCost float64 `json:"github_cost"` // Cost of other GitHub events (non-review)
GitHubContextCost float64 `json:"github_context_cost"` // Cost of context switching for GitHub sessions
// Supporting details
Events int `json:"events"` // Number of participant events
Sessions int `json:"sessions"` // Number of GitHub work sessions
ReviewHours float64 `json:"review_hours"` // Hours spent reviewing code (LOC-based)
GitHubHours float64 `json:"github_hours"` // Hours spent on other GitHub events
GitHubContextHours float64 `json:"github_context_hours"` // Hours spent context switching for GitHub
TotalHours float64 `json:"total_hours"` // Total hours (sum of above)
TotalCost float64 `json:"total_cost"` // Total participant cost
}
ParticipantCostDetail breaks down a participant's costs.