@@ -73,9 +73,9 @@ func DefaultConfig() Config {
7373 AnnualSalary : 250000.0 ,
7474 BenefitsMultiplier : 1.3 ,
7575 HoursPerYear : 2080.0 ,
76- EventDuration : 20 * time .Minute ,
77- ContextSwitchDuration : 20 * time .Minute ,
78- SessionGapThreshold : 60 * time .Minute ,
76+ EventDuration : 10 * time .Minute , // 10 minutes per GitHub event
77+ ContextSwitchDuration : 20 * time .Minute , // 20 minutes to context switch in/out
78+ SessionGapThreshold : 20 * time .Minute , // Events within 20 min are same session
7979 DeliveryDelayFactor : 0.15 , // 15% opportunity cost
8080 CoordinationFactor : 0.05 , // 5% mental overhead
8181 MaxDelayAfterLastEvent : 14 * 24 * time .Hour , // 14 days (2 weeks) after last event
@@ -592,19 +592,25 @@ func calculateParticipantCosts(data PRData, cfg Config, hourlyRate float64) []Pa
592592// calculateSessionCosts computes GitHub and context switching costs based on event sessions.
593593//
594594// Session Logic:
595- // - Events within SessionGapThreshold (default 60 min) are part of the same session
596- // - Events >60 min apart start a new session
595+ // - Events within SessionGapThreshold (default 20 min) are part of the same session
596+ // - Events >20 min apart start a new session
597597//
598- // Time Calculation per Session:
599- // - First event: ContextSwitchIn + EventTime + GapToNext (or ContextSwitchOut if last)
600- // - Middle events: GapFromPrev + EventTime + GapToNext
601- // - Last event: GapFromPrev + EventTime + ContextSwitchOut
598+ // GitHub Time Calculation:
599+ // - Each event counts as EventDuration (default 10 min)
600+ // - Gaps between events within a session don't add time (assumed to be part of the work)
602601//
603- // Example: 3 events 5 minutes apart
604- // - Event 1: 20 (context in) + 20 (event) + 5 (gap) = 45 min
605- // - Event 2: 5 (gap) + 20 (event) + 5 (gap) = 30 min
606- // - Event 3: 5 (gap) + 20 (event) + 20 (context out) = 45 min
607- // - Total: 120 minutes (60 GitHub, 40 context, 20 gaps).
602+ // Context Switching:
603+ // - First session: ContextSwitchDuration (20 min) at start
604+ // - Between sessions: min(2 × ContextSwitchDuration, gap) to avoid double-counting
605+ // - If gap >= 40 min: full 20 min out + 20 min in
606+ // - If gap < 40 min: split gap evenly (gap/2 out, gap/2 in)
607+ // - Last session: ContextSwitchDuration (20 min) at end
608+ //
609+ // Example: 3 events in one session, then 1 event 30 min later
610+ // - Session 1: 20 (context in) + 3×10 (events) + 20 (context out, but see gap)
611+ // - Gap: 30 min (< 40), so context overhead = 30 min total (15 out, 15 in)
612+ // - Session 2: (15 context in from gap) + 1×10 (event) + 20 (context out)
613+ // - Total context: 20 + 30 + 20 = 70 min
608614func calculateSessionCosts (events []ParticipantEvent , cfg Config ) (githubHours , contextHours float64 , sessions int ) {
609615 if len (events ) == 0 {
610616 return 0 , 0 , 0
@@ -621,18 +627,19 @@ func calculateSessionCosts(events []ParticipantEvent, cfg Config) (githubHours,
621627 contextSwitch := cfg .ContextSwitchDuration
622628 eventDur := cfg .EventDuration
623629
624- var githubTime time.Duration
625- var contextTime time.Duration
626- count := 0
630+ // Group events into sessions
631+ type session struct {
632+ start int
633+ end int
634+ }
635+ var sessionGroups []session
627636
628637 i := 0
629638 for i < len (sorted ) {
630- // Start a new session
631- count ++
632639 start := i
640+ end := start
633641
634642 // Find the end of this session (events within SessionGapThreshold)
635- end := start
636643 for end + 1 < len (sorted ) {
637644 gap := sorted [end + 1 ].Timestamp .Sub (sorted [end ].Timestamp )
638645 if gap > gapThreshold {
@@ -641,35 +648,49 @@ func calculateSessionCosts(events []ParticipantEvent, cfg Config) (githubHours,
641648 end ++
642649 }
643650
644- // Calculate costs for this session
645- // Context switch in at the start
646- contextTime += contextSwitch
647-
648- // First event: use default duration (we don't know how long it took)
649- githubTime += eventDur
650-
651- // Subsequent events within the session:
652- // - If gap <= eventDur (20 min): use actual gap (we know they stayed engaged)
653- // - If gap > eventDur: use eventDur (we don't know what they did during the gap)
654- for j := start ; j < end ; j ++ {
655- gap := sorted [j + 1 ].Timestamp .Sub (sorted [j ].Timestamp )
656- if gap <= eventDur {
657- githubTime += gap
658- } else {
659- githubTime += eventDur
660- }
661- }
651+ sessionGroups = append (sessionGroups , session {start : start , end : end })
652+ i = end + 1
653+ }
662654
663- // Context switch out at the end
664- contextTime += contextSwitch
655+ // Calculate GitHub time (simple: eventDur per event)
656+ var githubTime time.Duration
657+ for _ , sess := range sessionGroups {
658+ eventsInSession := sess .end - sess .start + 1
659+ githubTime += time .Duration (eventsInSession ) * eventDur
660+ }
665661
666- // Move to next session
667- i = end + 1
662+ // Calculate context switching with gap awareness
663+ var contextTime time.Duration
664+
665+ if len (sessionGroups ) == 0 {
666+ return 0 , 0 , 0
668667 }
669668
669+ // First session: context in
670+ contextTime += contextSwitch
671+
672+ // Between sessions: context out + context in, capped by gap
673+ for i := 0 ; i < len (sessionGroups )- 1 ; i ++ {
674+ lastEventOfSession := sorted [sessionGroups [i ].end ].Timestamp
675+ firstEventOfNextSession := sorted [sessionGroups [i + 1 ].start ].Timestamp
676+ gap := firstEventOfNextSession .Sub (lastEventOfSession )
677+
678+ // Maximum context switch is 2 × contextSwitch (out + in)
679+ maxContextSwitch := 2 * contextSwitch
680+ if gap >= maxContextSwitch {
681+ contextTime += maxContextSwitch
682+ } else {
683+ // Cap at gap (implicitly split as gap/2 out + gap/2 in)
684+ contextTime += gap
685+ }
686+ }
687+
688+ // Last session: context out
689+ contextTime += contextSwitch
690+
670691 githubHours = githubTime .Hours ()
671692 contextHours = contextTime .Hours ()
672- sessions = count
693+ sessionCount := len ( sessionGroups )
673694
674- return githubHours , contextHours , sessions
695+ return githubHours , contextHours , sessionCount
675696}
0 commit comments