cost

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2025 License: Apache-2.0 Imports: 10 Imported by: 0

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.

func Calculate

func Calculate(data PRData, cfg Config) Breakdown

Calculate computes the total cost of a pull request with detailed breakdowns.

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

type PRSummaryInfo struct {
	UpdatedAt time.Time
	Owner     string
	Repo      string
	Number    int
}

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.

type ParticipantEvent

type ParticipantEvent struct {
	Timestamp time.Time
	Actor     string
	Kind      string // Event type: "commit", "review", "comment", etc.
}

ParticipantEvent represents a single event by a participant.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL