@@ -1592,26 +1592,33 @@ <h3>Why calculate PR costs?</h3>
15921592 output += ' ───────────\n' ;
15931593 const cappedLabel = b . delay_capped ? ' (capped)' : '' ;
15941594 output += ` Workstream blockage ${ formatCurrency ( b . delay_cost_detail . delivery_delay_cost ) . padStart ( 12 ) } ${ formatTimeUnit ( b . delay_cost_detail . delivery_delay_hours ) } ${ cappedLabel } \n` ;
1595- const mergeDelayCost = b . delay_cost_detail . delivery_delay_cost + b . delay_cost_detail . code_churn_cost + b . delay_cost_detail . automated_updates_cost + b . delay_cost_detail . pr_tracking_cost ;
1596- const mergeDelayHours = b . delay_cost_detail . delivery_delay_hours + b . delay_cost_detail . code_churn_hours + b . delay_cost_detail . automated_updates_hours + b . delay_cost_detail . pr_tracking_hours ;
1595+ const mergeDelayCost = b . delay_cost_detail . delivery_delay_cost + b . delay_cost_detail . automated_updates_cost + b . delay_cost_detail . pr_tracking_cost ;
1596+ const mergeDelayHours = b . delay_cost_detail . delivery_delay_hours + b . delay_cost_detail . automated_updates_hours + b . delay_cost_detail . pr_tracking_hours ;
15971597 output += ' ────────────\n' ;
15981598 pct = ( mergeDelayCost / b . total_cost ) * 100 ;
15991599 output += formatSubtotalLine ( "Subtotal" , mergeDelayCost , formatTimeUnit ( mergeDelayHours ) , `(${ pct . toFixed ( 1 ) } %)` ) ;
16001600 output += '\n' ;
16011601
1602+ // Preventable Future Costs
1603+ if ( b . delay_cost_detail . rework_percentage > 0 ) {
1604+ output += ' Preventable Future Costs\n' ;
1605+ output += ' ────────────────────────\n' ;
1606+ output += ` Rework due to churn (${ Math . round ( b . delay_cost_detail . rework_percentage ) } % drift) ${ formatCurrency ( b . delay_cost_detail . code_churn_cost ) . padStart ( 12 ) } ${ formatTimeUnit ( b . delay_cost_detail . code_churn_hours ) } \n` ;
1607+ output += ' ────────────\n' ;
1608+ pct = ( b . delay_cost_detail . code_churn_cost / b . total_cost ) * 100 ;
1609+ output += formatSubtotalLine ( "Subtotal" , b . delay_cost_detail . code_churn_cost , formatTimeUnit ( b . delay_cost_detail . code_churn_hours ) , `(${ pct . toFixed ( 1 ) } %)` ) ;
1610+ output += '\n' ;
1611+ }
1612+
16021613 // Future Costs
1603- const hasFuture = b . delay_cost_detail . rework_percentage > 0 ||
1604- b . delay_cost_detail . future_review_cost > 0 ||
1614+ const hasFuture = b . delay_cost_detail . future_review_cost > 0 ||
16051615 b . delay_cost_detail . future_merge_cost > 0 ||
16061616 b . delay_cost_detail . future_context_cost > 0 ;
16071617
16081618 if ( hasFuture ) {
16091619 output += ' Future Costs\n' ;
16101620 output += ' ────────────\n' ;
16111621
1612- if ( b . delay_cost_detail . rework_percentage > 0 ) {
1613- output += ` Code Churn (${ Math . round ( b . delay_cost_detail . rework_percentage ) } % drift) ${ formatCurrency ( b . delay_cost_detail . code_churn_cost ) . padStart ( 12 ) } ${ formatTimeUnit ( b . delay_cost_detail . code_churn_hours ) } \n` ;
1614- }
16151622 if ( b . delay_cost_detail . future_review_cost > 0 ) {
16161623 output += ` Review ${ formatCurrency ( b . delay_cost_detail . future_review_cost ) . padStart ( 12 ) } ${ formatTimeUnit ( b . delay_cost_detail . future_review_hours ) } \n` ;
16171624 }
@@ -1622,9 +1629,9 @@ <h3>Why calculate PR costs?</h3>
16221629 output += ` Context Switching ${ formatCurrency ( b . delay_cost_detail . future_context_cost ) . padStart ( 12 ) } ${ formatTimeUnit ( b . delay_cost_detail . future_context_hours ) } \n` ;
16231630 }
16241631
1625- const futureCost = b . delay_cost_detail . code_churn_cost + b . delay_cost_detail . future_review_cost +
1632+ const futureCost = b . delay_cost_detail . future_review_cost +
16261633 b . delay_cost_detail . future_merge_cost + b . delay_cost_detail . future_context_cost ;
1627- const futureHours = b . delay_cost_detail . code_churn_hours + b . delay_cost_detail . future_review_hours +
1634+ const futureHours = b . delay_cost_detail . future_review_hours +
16281635 b . delay_cost_detail . future_merge_hours + b . delay_cost_detail . future_context_hours ;
16291636 output += ' ────────────\n' ;
16301637 pct = ( futureCost / b . total_cost ) * 100 ;
@@ -1840,23 +1847,31 @@ <h3>Why calculate PR costs?</h3>
18401847 if ( avgPRTrackingCost > 0.01 ) {
18411848 output += formatItemLine ( "PR Tracking" , avgPRTrackingCost , formatTimeUnit ( avgPRTrackingHours ) , `(${ e . open_prs } open PRs)` ) ;
18421849 }
1843- const avgMergeDelayCost = avgDeliveryDelayCost + avgCodeChurnCost + avgAutomatedUpdatesCost + avgPRTrackingCost ;
1844- const avgMergeDelayHours = avgDeliveryDelayHours + avgCodeChurnHours + avgAutomatedUpdatesHours + avgPRTrackingHours ;
1850+ const avgMergeDelayCost = avgDeliveryDelayCost + avgAutomatedUpdatesCost + avgPRTrackingCost ;
1851+ const avgMergeDelayHours = avgDeliveryDelayHours + avgAutomatedUpdatesHours + avgPRTrackingHours ;
18451852 output += ' ──────────\n' ;
18461853 pct = ( avgMergeDelayCost / avgTotalCost ) * 100 ;
18471854 output += formatSubtotalLine ( "Subtotal" , avgMergeDelayCost , formatTimeUnit ( avgMergeDelayHours ) , `(${ pct . toFixed ( 1 ) } %)` ) ;
18481855 output += '\n' ;
18491856
1857+ // Preventable Future Costs
1858+ if ( e . code_churn_cost > 0.01 ) {
1859+ output += ' Preventable Future Costs\n' ;
1860+ output += ' ────────────────────────\n' ;
1861+ const avgReworkPct = e . avg_rework_percentage || 0 ;
1862+ const label = avgReworkPct > 0 ? `Rework due to churn (${ avgReworkPct . toFixed ( 0 ) } % drift)` : 'Rework due to churn' ;
1863+ output += formatItemLine ( label , avgCodeChurnCost , formatTimeUnit ( avgCodeChurnHours ) , `(${ e . code_churn_pr_count } PRs)` ) ;
1864+ output += ' ──────────\n' ;
1865+ pct = ( avgCodeChurnCost / avgTotalCost ) * 100 ;
1866+ output += formatSubtotalLine ( "Subtotal" , avgCodeChurnCost , formatTimeUnit ( avgCodeChurnHours ) , `(${ pct . toFixed ( 1 ) } %)` ) ;
1867+ output += '\n' ;
1868+ }
1869+
18501870 // Future Costs
1851- const hasFuture = e . code_churn_cost > 0.01 || e . future_review_cost > 0.01 || e . future_merge_cost > 0.01 || e . future_context_cost > 0.01 ;
1871+ const hasFuture = e . future_review_cost > 0.01 || e . future_merge_cost > 0.01 || e . future_context_cost > 0.01 ;
18521872 if ( hasFuture ) {
18531873 output += ' Future Costs\n' ;
18541874 output += ' ────────────\n' ;
1855- if ( e . code_churn_cost > 0.01 ) {
1856- const avgReworkPct = e . avg_rework_percentage || 0 ;
1857- const label = avgReworkPct > 0 ? `Code Churn (${ avgReworkPct . toFixed ( 0 ) } % drift)` : 'Code Churn' ;
1858- output += formatItemLine ( label , avgCodeChurnCost , formatTimeUnit ( avgCodeChurnHours ) , `(${ e . code_churn_pr_count } PRs)` ) ;
1859- }
18601875 if ( e . future_review_cost > 0.01 ) {
18611876 output += formatItemLine ( "Review" , avgFutureReviewCost , formatTimeUnit ( avgFutureReviewHours ) , `(${ e . future_review_pr_count } PRs)` ) ;
18621877 }
@@ -1867,8 +1882,8 @@ <h3>Why calculate PR costs?</h3>
18671882 const avgFutureContextSessions = e . future_context_sessions / totalPRs ;
18681883 output += formatItemLine ( "Context Switching" , avgFutureContextCost , formatTimeUnit ( avgFutureContextHours ) , `(${ avgFutureContextSessions . toFixed ( 1 ) } sessions)` ) ;
18691884 }
1870- const avgFutureCost = avgCodeChurnCost + avgFutureReviewCost + avgFutureMergeCost + avgFutureContextCost ;
1871- const avgFutureHours = avgCodeChurnHours + avgFutureReviewHours + avgFutureMergeHours + avgFutureContextHours ;
1885+ const avgFutureCost = avgFutureReviewCost + avgFutureMergeCost + avgFutureContextCost ;
1886+ const avgFutureHours = avgFutureReviewHours + avgFutureMergeHours + avgFutureContextHours ;
18721887 output += ' ──────────\n' ;
18731888 pct = ( avgFutureCost / avgTotalCost ) * 100 ;
18741889 output += formatSubtotalLine ( "Subtotal" , avgFutureCost , formatTimeUnit ( avgFutureHours ) , `(${ pct . toFixed ( 1 ) } %)` ) ;
@@ -1951,21 +1966,29 @@ <h3>Why calculate PR costs?</h3>
19511966 output += formatItemLine ( "PR Tracking" , e . pr_tracking_cost , formatTimeUnit ( e . pr_tracking_hours ) , `(${ e . open_prs || 0 } open PRs)` ) ;
19521967 }
19531968
1954- const mergeDelayCost = ( e . delivery_delay_cost || 0 ) + ( e . code_churn_cost || 0 ) + ( e . automated_updates_cost || 0 ) + ( e . pr_tracking_cost || 0 ) ;
1955- const mergeDelayHours = ( e . delivery_delay_hours || 0 ) + ( e . code_churn_hours || 0 ) + ( e . automated_updates_hours || 0 ) + ( e . pr_tracking_hours || 0 ) ;
1969+ const mergeDelayCost = ( e . delivery_delay_cost || 0 ) + ( e . automated_updates_cost || 0 ) + ( e . pr_tracking_cost || 0 ) ;
1970+ const mergeDelayHours = ( e . delivery_delay_hours || 0 ) + ( e . automated_updates_hours || 0 ) + ( e . pr_tracking_hours || 0 ) ;
19561971 output += ' ──────────\n' ;
19571972 pct = ( mergeDelayCost / e . total_cost ) * 100 ;
19581973 output += formatSubtotalLine ( "Subtotal" , mergeDelayCost , formatTimeUnit ( mergeDelayHours ) , `(${ pct . toFixed ( 1 ) } %)` ) ;
19591974 output += '\n' ;
19601975
1976+ // Preventable Future Costs
1977+ if ( ( e . code_churn_cost || 0 ) > 0.01 ) {
1978+ output += ' Preventable Future Costs\n' ;
1979+ output += ' ────────────────────────\n' ;
1980+ output += formatItemLine ( "Rework due to churn" , e . code_churn_cost , formatTimeUnit ( e . code_churn_hours ) , `(${ e . code_churn_pr_count || 0 } PRs)` ) ;
1981+ output += ' ──────────\n' ;
1982+ pct = ( e . code_churn_cost / e . total_cost ) * 100 ;
1983+ output += formatSubtotalLine ( "Subtotal" , e . code_churn_cost , formatTimeUnit ( e . code_churn_hours ) , `(${ pct . toFixed ( 1 ) } %)` ) ;
1984+ output += '\n' ;
1985+ }
1986+
19611987 // Future Costs
1962- const hasFuture = ( e . code_churn_cost || 0 ) > 0.01 || ( e . future_review_cost || 0 ) > 0.01 || ( e . future_merge_cost || 0 ) > 0.01 || ( e . future_context_cost || 0 ) > 0.01 ;
1988+ const hasFuture = ( e . future_review_cost || 0 ) > 0.01 || ( e . future_merge_cost || 0 ) > 0.01 || ( e . future_context_cost || 0 ) > 0.01 ;
19631989 if ( hasFuture ) {
19641990 output += ' Future Costs\n' ;
19651991 output += ' ────────────\n' ;
1966- if ( ( e . code_churn_cost || 0 ) > 0.01 ) {
1967- output += formatItemLine ( "Code Churn" , e . code_churn_cost , formatTimeUnit ( e . code_churn_hours ) , "" ) ;
1968- }
19691992 if ( ( e . future_review_cost || 0 ) > 0.01 ) {
19701993 const openPRs = e . open_prs || 0 ;
19711994 output += formatItemLine ( "Review" , e . future_review_cost , formatTimeUnit ( e . future_review_hours ) , `(${ openPRs } PRs)` ) ;
@@ -1977,8 +2000,8 @@ <h3>Why calculate PR costs?</h3>
19772000 if ( ( e . future_context_cost || 0 ) > 0.01 ) {
19782001 output += formatItemLine ( "Context Switching" , e . future_context_cost , formatTimeUnit ( e . future_context_hours ) , `(${ e . future_context_sessions || 0 } sessions)` ) ;
19792002 }
1980- const futureCost = ( e . code_churn_cost || 0 ) + ( e . future_review_cost || 0 ) + ( e . future_merge_cost || 0 ) + ( e . future_context_cost || 0 ) ;
1981- const futureHours = ( e . code_churn_hours || 0 ) + ( e . future_review_hours || 0 ) + ( e . future_merge_hours || 0 ) + ( e . future_context_hours || 0 ) ;
2003+ const futureCost = ( e . future_review_cost || 0 ) + ( e . future_merge_cost || 0 ) + ( e . future_context_cost || 0 ) ;
2004+ const futureHours = ( e . future_review_hours || 0 ) + ( e . future_merge_hours || 0 ) + ( e . future_context_hours || 0 ) ;
19822005 output += ' ──────────\n' ;
19832006 pct = ( futureCost / e . total_cost ) * 100 ;
19842007 output += formatSubtotalLine ( "Subtotal" , futureCost , formatTimeUnit ( futureHours ) , `(${ pct . toFixed ( 1 ) } %)` ) ;
0 commit comments