diff --git a/copilot-worktree-2026-01-14T11-31-32.code-workspace b/copilot-worktree-2026-01-14T11-31-32.code-workspace new file mode 100644 index 0000000..2327ec2 --- /dev/null +++ b/copilot-worktree-2026-01-14T11-31-32.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "jdk.telemetry.enabled": true, + "redhat.telemetry.enabled": true + } +} \ No newline at end of file diff --git a/examples/advanced_examples.py b/examples/advanced_examples.py index f887ac1..30d12b9 100644 --- a/examples/advanced_examples.py +++ b/examples/advanced_examples.py @@ -18,17 +18,19 @@ def example_1_basic_workflow(): """Example 1: Basic analysis workflow.""" - print("="*70) + print("=" * 70) print("Example 1: Basic Analysis Workflow") - print("="*70) - + print("=" * 70) + # Load data loader = CodeFrequencyLoader() data = loader.load() - + print(f"\nšŸ“Š Loaded {len(data)} records") - print(f"Date range: {data['DateTime'].min().date()} to {data['DateTime'].max().date()}") - + print( + f"Date range: {data['DateTime'].min().date()} to {data['DateTime'].max().date()}" + ) + # Get summary summary = loader.get_summary() print(f"\nšŸ“ˆ Summary:") @@ -39,47 +41,49 @@ def example_1_basic_workflow(): def example_2_sprint_analysis(): """Example 2: Detailed sprint analysis.""" - print("\n" + "="*70) + print("\n" + "=" * 70) print("Example 2: Sprint Detection and Analysis") - print("="*70) - + print("=" * 70) + loader = CodeFrequencyLoader() analyzer = CodeFrequencyAnalyzer(loader) - + # Detect sprints with custom parameters sprints = analyzer.detect_sprints(window_weeks=3, threshold_multiplier=1.5) - + print(f"\nšŸš€ Detected {len(sprints)} coding sprints:\n") - + for i, sprint in enumerate(sprints[:5], 1): # Show top 5 print(f"Sprint {i}:") print(f" Duration: {sprint['duration_weeks']} weeks") print(f" Period: {sprint['start_date'].date()} to {sprint['end_date'].date()}") - print(f" Total changes: {sprint['total_additions'] + sprint['total_deletions']:,}") + print( + f" Total changes: {sprint['total_additions'] + sprint['total_deletions']:,}" + ) print(f" Avg weekly churn: {sprint['avg_weekly_churn']:,.0f}") print() def example_3_time_analysis(): """Example 3: Time-based analysis.""" - print("="*70) + print("=" * 70) print("Example 3: Time-Based Analysis") - print("="*70) - + print("=" * 70) + loader = CodeFrequencyLoader() analyzer = CodeFrequencyAnalyzer(loader) - + # Yearly statistics yearly = analyzer.get_yearly_stats() print("\nšŸ“… Top 3 Most Productive Years:") - top_years = yearly.nlargest(3, 'Additions_sum') - + top_years = yearly.nlargest(3, "Additions_sum") + for year, row in top_years.iterrows(): print(f"\n{year}:") print(f" Additions: {int(row['Additions_sum']):,}") print(f" Deletions: {int(abs(row['Deletions_sum'])):,}") print(f" Net: {int(row['net_changes']):,}") - + # Activity patterns print(f"\nšŸ“Š Activity Statistics:") print(f" Activity ratio: {analyzer.calculate_activity_ratio():.2%}") @@ -90,74 +94,76 @@ def example_3_time_analysis(): def example_4_custom_visualization(): """Example 4: Custom visualization.""" - print("\n" + "="*70) + print("\n" + "=" * 70) print("Example 4: Custom Visualization") - print("="*70) - + print("=" * 70) + loader = CodeFrequencyLoader() loader.load() data = loader.data.copy() - + # Create custom analysis - data['NetChanges'] = data['Additions'] + data['Deletions'] - data['Year'] = data['DateTime'].dt.year - + data["NetChanges"] = data["Additions"] + data["Deletions"] + data["Year"] = data["DateTime"].dt.year + # Calculate moving average - data['MA_30'] = data['Additions'].rolling(window=30, center=True).mean() - + data["MA_30"] = data["Additions"].rolling(window=30, center=True).mean() + # Create custom plot fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 8)) - + # Plot 1: Additions with moving average - ax1.plot(data['DateTime'], data['Additions'], alpha=0.3, label='Additions') - ax1.plot(data['DateTime'], data['MA_30'], 'r-', linewidth=2, label='30-week MA') - ax1.set_ylabel('Lines Added') - ax1.set_title('Code Additions with 30-Week Moving Average') + ax1.plot(data["DateTime"], data["Additions"], alpha=0.3, label="Additions") + ax1.plot(data["DateTime"], data["MA_30"], "r-", linewidth=2, label="30-week MA") + ax1.set_ylabel("Lines Added") + ax1.set_title("Code Additions with 30-Week Moving Average") ax1.legend() ax1.grid(True, alpha=0.3) - + # Plot 2: Net changes by year - yearly_net = data.groupby('Year')['NetChanges'].sum() - ax2.bar(yearly_net.index, yearly_net.values, color='steelblue', alpha=0.7) - ax2.axhline(y=0, color='black', linestyle='-', linewidth=0.5) - ax2.set_xlabel('Year') - ax2.set_ylabel('Net Changes') - ax2.set_title('Net Code Changes by Year') - ax2.grid(True, alpha=0.3, axis='y') - + yearly_net = data.groupby("Year")["NetChanges"].sum() + ax2.bar(yearly_net.index, yearly_net.values, color="steelblue", alpha=0.7) + ax2.axhline(y=0, color="black", linestyle="-", linewidth=0.5) + ax2.set_xlabel("Year") + ax2.set_ylabel("Net Changes") + ax2.set_title("Net Code Changes by Year") + ax2.grid(True, alpha=0.3, axis="y") + plt.tight_layout() - + # Save plot - output_path = Path('output/visualizations/custom_analysis.png') + output_path = Path("output/visualizations/custom_analysis.png") output_path.parent.mkdir(parents=True, exist_ok=True) - plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.savefig(output_path, dpi=300, bbox_inches="tight") print(f"\nāœ… Custom visualization saved to {output_path}") plt.close() def example_5_filtering_analysis(): """Example 5: Filtering and conditional analysis.""" - print("\n" + "="*70) + print("\n" + "=" * 70) print("Example 5: Filtering and Conditional Analysis") - print("="*70) - + print("=" * 70) + loader = CodeFrequencyLoader() loader.load() data = loader.data.copy() - + # Analyze only high-activity periods - data['AbsChanges'] = data['Additions'] + abs(data['Deletions']) - high_activity = data[data['AbsChanges'] > data['AbsChanges'].quantile(0.75)] - + data["AbsChanges"] = data["Additions"] + abs(data["Deletions"]) + high_activity = data[data["AbsChanges"] > data["AbsChanges"].quantile(0.75)] + print(f"\nšŸ“Š High Activity Periods (top 25%):") print(f" Total records: {len(high_activity)}") - print(f" Date range: {high_activity['DateTime'].min().date()} to {high_activity['DateTime'].max().date()}") + print( + f" Date range: {high_activity['DateTime'].min().date()} to {high_activity['DateTime'].max().date()}" + ) print(f" Total changes: {high_activity['AbsChanges'].sum():,.0f}") - + # Analyze recent activity (last 2 years) - recent_date = data['DateTime'].max() - pd.Timedelta(days=730) - recent_data = data[data['DateTime'] >= recent_date] - + recent_date = data["DateTime"].max() - pd.Timedelta(days=730) + recent_data = data[data["DateTime"] >= recent_date] + print(f"\nšŸ“… Recent Activity (last 2 years):") print(f" Records: {len(recent_data)}") print(f" Total additions: {recent_data['Additions'].sum():,}") @@ -167,16 +173,16 @@ def example_5_filtering_analysis(): def example_6_comparison_analysis(): """Example 6: Comparative analysis.""" - print("\n" + "="*70) + print("\n" + "=" * 70) print("Example 6: Comparative Analysis") - print("="*70) - + print("=" * 70) + loader = CodeFrequencyLoader() analyzer = CodeFrequencyAnalyzer(loader) - + # Compare productivity across time periods trends = analyzer.get_productivity_trends(periods=4) - + print("\nšŸ“Š Productivity Trends (4 periods):") for period, row in trends.iterrows(): print(f"\nPeriod {period + 1}:") @@ -193,7 +199,7 @@ def main(): print("ā•‘ šŸ“š RunTime Advanced Examples šŸ“š ā•‘") print("ā•‘ ā•‘") print("ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n") - + try: example_1_basic_workflow() example_2_sprint_analysis() @@ -201,11 +207,11 @@ def main(): example_4_custom_visualization() example_5_filtering_analysis() example_6_comparison_analysis() - - print("\n" + "="*70) + + print("\n" + "=" * 70) print("āœ… All examples completed successfully!") - print("="*70) - + print("=" * 70) + except Exception as e: print(f"\nāŒ Error: {e}") raise diff --git a/notebooks/exploration.ipynb b/notebooks/exploration.ipynb index ee49cac..8abacfe 100644 --- a/notebooks/exploration.ipynb +++ b/notebooks/exploration.ipynb @@ -610,80 +610,19 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 1, "id": "7be2de20", "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Found 8 coding sprints:\n", - "\n", - "Sprint 1:\n", - " start_date: 2018-03-11 00:00:00\n", - " end_date: 2018-04-22 00:00:00\n", - " duration_weeks: 7\n", - " total_additions: 817757\n", - " total_deletions: 219662\n", - " avg_weekly_churn: 148202.7142857143\n", - "\n", - "Sprint 2:\n", - " start_date: 2018-05-20 00:00:00\n", - " end_date: 2018-06-10 00:00:00\n", - " duration_weeks: 4\n", - " total_additions: 135273\n", - " total_deletions: 527\n", - " avg_weekly_churn: 33950.0\n", - "\n", - "Sprint 3:\n", - " start_date: 2018-07-22 00:00:00\n", - " end_date: 2018-08-12 00:00:00\n", - " duration_weeks: 4\n", - " total_additions: 138173\n", - " total_deletions: 110270\n", - " avg_weekly_churn: 62110.75\n", - "\n", - "Sprint 4:\n", - " start_date: 2019-06-02 00:00:00\n", - " end_date: 2019-06-23 00:00:00\n", - " duration_weeks: 4\n", - " total_additions: 62\n", - " total_deletions: 219657\n", - " avg_weekly_churn: 54929.75\n", - "\n", - "Sprint 5:\n", - " start_date: 2019-11-03 00:00:00\n", - " end_date: 2019-11-24 00:00:00\n", - " duration_weeks: 4\n", - " total_additions: 44861\n", - " total_deletions: 767311\n", - " avg_weekly_churn: 203043.0\n", - "\n", - "Sprint 6:\n", - " start_date: 2021-05-23 00:00:00\n", - " end_date: 2021-06-13 00:00:00\n", - " duration_weeks: 4\n", - " total_additions: 88404\n", - " total_deletions: 77108\n", - " avg_weekly_churn: 41378.0\n", - "\n", - "Sprint 7:\n", - " start_date: 2021-11-28 00:00:00\n", - " end_date: 2021-11-28 00:00:00\n", - " duration_weeks: 1\n", - " total_additions: 0\n", - " total_deletions: 0\n", - " avg_weekly_churn: 0.0\n", - "\n", - "Sprint 8:\n", - " start_date: 2022-02-13 00:00:00\n", - " end_date: 2022-03-06 00:00:00\n", - " duration_weeks: 4\n", - " total_additions: 9613\n", - " total_deletions: 54794\n", - " avg_weekly_churn: 16101.75\n", - "\n" + "ename": "NameError", + "evalue": "name 'analyzer' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m sprints = \u001b[43manalyzer\u001b[49m.detect_sprints()\n\u001b[32m 3\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mFound \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mlen\u001b[39m(sprints)\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m coding sprints:\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m\"\u001b[39m)\n\u001b[32m 4\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m i, sprint \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(sprints, \u001b[32m1\u001b[39m):\n", + "\u001b[31mNameError\u001b[39m: name 'analyzer' is not defined" ] } ], @@ -816,7 +755,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, diff --git a/src/__pycache__/load_data.cpython-312.pyc b/src/__pycache__/load_data.cpython-312.pyc index 5d062a4..363f102 100644 Binary files a/src/__pycache__/load_data.cpython-312.pyc and b/src/__pycache__/load_data.cpython-312.pyc differ diff --git a/src/cli.py b/src/cli.py index 6323cb9..f564f53 100644 --- a/src/cli.py +++ b/src/cli.py @@ -21,8 +21,8 @@ def load_command(args): data = loader.load() print(f"āœ… Successfully loaded {len(data)} records") - date_min = data['DateTime'].min().date() - date_max = data['DateTime'].max().date() + date_min = data["DateTime"].min().date() + date_max = data["DateTime"].max().date() print(f"\nDate range: {date_min} to {date_max}") if args.summary: @@ -82,7 +82,7 @@ def analyze_command(args): for i, sprint in enumerate(sprints, 1): print(f"\nSprint {i}:") for key, value in sprint.items(): - key_title = key.replace('_', ' ').title() + key_title = key.replace("_", " ").title() if hasattr(value, "date"): print(f" {key_title}: {value.date()}") elif isinstance(value, int): @@ -151,18 +151,12 @@ def main(): """, ) - parser.add_argument( - "--version", action="version", version="RunTime 1.0.0" - ) + parser.add_argument("--version", action="version", version="RunTime 1.0.0") - subparsers = parser.add_subparsers( - dest="command", help="Available commands" - ) + subparsers = parser.add_subparsers(dest="command", help="Available commands") # Load command - load_parser = subparsers.add_parser( - "load", help="Load and display data" - ) + load_parser = subparsers.add_parser("load", help="Load and display data") load_parser.add_argument( "-f", "--file", @@ -170,12 +164,9 @@ def main(): help="CSV file to load (default: Code frequency.csv)", ) load_parser.add_argument( - "-s", "--summary", action="store_true", - help="Show summary statistics" - ) - load_parser.add_argument( - "--head", type=int, metavar="N", help="Show first N rows" + "-s", "--summary", action="store_true", help="Show summary statistics" ) + load_parser.add_argument("--head", type=int, metavar="N", help="Show first N rows") load_parser.set_defaults(func=load_command) # Analyze command @@ -183,12 +174,10 @@ def main(): "analyze", help="Analyze code frequency patterns" ) analyze_parser.add_argument( - "-f", "--file", default="Code frequency.csv", - help="CSV file to analyze" + "-f", "--file", default="Code frequency.csv", help="CSV file to analyze" ) analyze_parser.add_argument( - "-a", "--all", action="store_true", - help="Show all analysis" + "-a", "--all", action="store_true", help="Show all analysis" ) analyze_parser.add_argument( "--activity", action="store_true", help="Show activity ratio" @@ -208,12 +197,9 @@ def main(): analyze_parser.set_defaults(func=analyze_command) # Visualize command - viz_parser = subparsers.add_parser( - "visualize", help="Create visualizations" - ) + viz_parser = subparsers.add_parser("visualize", help="Create visualizations") viz_parser.add_argument( - "-f", "--file", default="Code frequency.csv", - help="CSV file to visualize" + "-f", "--file", default="Code frequency.csv", help="CSV file to visualize" ) viz_parser.add_argument( "-o", @@ -222,12 +208,10 @@ def main(): help="Output directory (default: output/visualizations)", ) viz_parser.add_argument( - "-a", "--all", action="store_true", - help="Create all visualizations" + "-a", "--all", action="store_true", help="Create all visualizations" ) viz_parser.add_argument( - "-d", "--dashboard", action="store_true", - help="Create complete dashboard" + "-d", "--dashboard", action="store_true", help="Create complete dashboard" ) viz_parser.add_argument( "--timeline", action="store_true", help="Create timeline plot" diff --git a/src/code_frequency_analyzer.py b/src/code_frequency_analyzer.py index 1f629ee..47682c0 100644 --- a/src/code_frequency_analyzer.py +++ b/src/code_frequency_analyzer.py @@ -65,12 +65,8 @@ def get_yearly_stats(self) -> pd.DataFrame: .round(2) ) - yearly.columns = [ - "_".join(col).strip() for col in yearly.columns.values - ] - yearly["net_changes"] = ( - yearly["Additions_sum"] + yearly["Deletions_sum"] - ) + yearly.columns = ["_".join(col).strip() for col in yearly.columns.values] + yearly["net_changes"] = yearly["Additions_sum"] + yearly["Deletions_sum"] return yearly @@ -83,16 +79,10 @@ def get_monthly_stats(self) -> pd.DataFrame: df = self.data.copy() df["YearMonth"] = df["DateTime"].dt.to_period("M") - monthly = df.groupby("YearMonth").agg( - {"Additions": "sum", "Deletions": "sum"} - ) + monthly = df.groupby("YearMonth").agg({"Additions": "sum", "Deletions": "sum"}) - monthly["net_changes"] = ( - monthly["Additions"] + monthly["Deletions"] - ) - monthly["abs_changes"] = ( - monthly["Additions"] + abs(monthly["Deletions"]) - ) + monthly["net_changes"] = monthly["Additions"] + monthly["Deletions"] + monthly["abs_changes"] = monthly["Additions"] + abs(monthly["Deletions"]) return monthly @@ -202,12 +192,8 @@ def get_productivity_trends(self, periods: int = 4) -> pd.DataFrame: {"Additions": ["sum", "mean"], "Deletions": ["sum", "mean"]} ) - trends.columns = [ - "_".join(col).strip() for col in trends.columns.values - ] - trends["net_changes"] = ( - trends["Additions_sum"] + trends["Deletions_sum"] - ) + trends.columns = ["_".join(col).strip() for col in trends.columns.values] + trends["net_changes"] = trends["Additions_sum"] + trends["Deletions_sum"] return trends diff --git a/src/code_frequency_loader.py b/src/code_frequency_loader.py index c1011b8..6daaf43 100644 --- a/src/code_frequency_loader.py +++ b/src/code_frequency_loader.py @@ -74,8 +74,8 @@ def main(): print(f"Total Additions: {summary['total_additions']:,}") print(f"Total Deletions: {summary['total_deletions']:,}") print(f"Net Changes: {summary['net_changes']:,}") - start_date = summary['date_range']['start'].date() - end_date = summary['date_range']['end'].date() + start_date = summary["date_range"]["start"].date() + end_date = summary["date_range"]["end"].date() print(f"Date Range: {start_date} to {end_date}") print(f"Number of Records: {summary['num_records']}") diff --git a/src/code_frequency_visualizer.py b/src/code_frequency_visualizer.py index 6bdf4e3..f39a0a5 100644 --- a/src/code_frequency_visualizer.py +++ b/src/code_frequency_visualizer.py @@ -22,9 +22,7 @@ class CodeFrequencyVisualizer: """Visualizer for code frequency data.""" def __init__( - self, - loader: CodeFrequencyLoader = None, - style: str = "seaborn-v0_8-darkgrid" + self, loader: CodeFrequencyLoader = None, style: str = "seaborn-v0_8-darkgrid" ): """Initialize the visualizer. @@ -48,9 +46,7 @@ def __init__( sns.set_palette("husl") def plot_timeline( - self, - figsize: Tuple[int, int] = (15, 6), - save_path: Optional[str] = None + self, figsize: Tuple[int, int] = (15, 6), save_path: Optional[str] = None ) -> plt.Figure: """Plot additions and deletions over time. @@ -83,11 +79,7 @@ def plot_timeline( ax.set_xlabel("Date", fontsize=12) ax.set_ylabel("Lines of Code", fontsize=12) - ax.set_title( - "Code Frequency Over Time", - fontsize=14, - fontweight="bold" - ) + ax.set_title("Code Frequency Over Time", fontsize=14, fontweight="bold") ax.legend(loc="upper left") ax.grid(True, alpha=0.3) @@ -105,9 +97,7 @@ def plot_timeline( return fig def plot_net_changes( - self, - figsize: Tuple[int, int] = (15, 6), - save_path: Optional[str] = None + self, figsize: Tuple[int, int] = (15, 6), save_path: Optional[str] = None ) -> plt.Figure: """Plot net changes over time. @@ -123,22 +113,12 @@ def plot_net_changes( net_changes = self.data["Additions"] + self.data["Deletions"] colors = ["green" if x >= 0 else "red" for x in net_changes] - ax.bar( - self.data["DateTime"], - net_changes, - color=colors, - alpha=0.6, - width=5 - ) + ax.bar(self.data["DateTime"], net_changes, color=colors, alpha=0.6, width=5) ax.axhline(y=0, color="black", linestyle="-", linewidth=0.5) ax.set_xlabel("Date", fontsize=12) ax.set_ylabel("Net Changes (Lines)", fontsize=12) - ax.set_title( - "Net Code Changes Over Time", - fontsize=14, - fontweight="bold" - ) + ax.set_title("Net Code Changes Over Time", fontsize=14, fontweight="bold") ax.grid(True, alpha=0.3, axis="y") # Format x-axis @@ -155,9 +135,7 @@ def plot_net_changes( return fig def plot_yearly_summary( - self, - figsize: Tuple[int, int] = (12, 6), - save_path: Optional[str] = None + self, figsize: Tuple[int, int] = (12, 6), save_path: Optional[str] = None ) -> plt.Figure: """Plot yearly summary statistics. @@ -196,9 +174,7 @@ def plot_yearly_summary( ax1.set_xlabel("Year", fontsize=12) ax1.set_ylabel("Total Lines", fontsize=12) - ax1.set_title( - "Total Changes by Year", fontsize=12, fontweight="bold" - ) + ax1.set_title("Total Changes by Year", fontsize=12, fontweight="bold") ax1.set_xticks(x_pos) ax1.set_xticklabels(x, rotation=45) ax1.legend() @@ -222,9 +198,7 @@ def plot_yearly_summary( return fig def plot_activity_heatmap( - self, - figsize: Tuple[int, int] = (14, 8), - save_path: Optional[str] = None + self, figsize: Tuple[int, int] = (14, 8), save_path: Optional[str] = None ) -> plt.Figure: """Plot activity heatmap by year and month. @@ -250,9 +224,7 @@ def plot_activity_heatmap( ) fig, ax = plt.subplots(figsize=figsize) - sns.heatmap( - pivot, cmap="YlOrRd", ax=ax, cbar_kws={"label": "Total Changes"} - ) + sns.heatmap(pivot, cmap="YlOrRd", ax=ax, cbar_kws={"label": "Total Changes"}) ax.set_xlabel("Year", fontsize=12) ax.set_ylabel("Month", fontsize=12) @@ -305,15 +277,11 @@ def create_dashboard(self, save_dir: Optional[str] = None) -> None: self.plot_net_changes(save_path=net_path) # Yearly summary - yearly_path = ( - str(save_dir / "yearly_summary.png") if save_dir else None - ) + yearly_path = str(save_dir / "yearly_summary.png") if save_dir else None self.plot_yearly_summary(save_path=yearly_path) # Activity heatmap - heatmap_path = ( - str(save_dir / "activity_heatmap.png") if save_dir else None - ) + heatmap_path = str(save_dir / "activity_heatmap.png") if save_dir else None self.plot_activity_heatmap(save_path=heatmap_path) if not save_dir: