Source code for energytrackr.plot.builtin_page_sections.summary_box
"""SummaryBox - first HTML section in the default report.Renders a quick statistics overview for the active energy column. Uses a Jinjapartial `summary_box.html` that ships with *plot*; users can override byencoding their own PageObj in YAML."""from__future__importannotationsfromdataclassesimportdataclassfrompathlibimportPathfromtypingimportAnyimportnumpyasnpimportpandasaspdfromjinja2importEnvironmentfromenergytrackr.plot.core.contextimportContextfromenergytrackr.plot.core.interfacesimportConfigurable,PageObjfromenergytrackr.utils.loggerimportloggerfromenergytrackr.utils.utilsimportget_local_env
[docs]@dataclass(frozen=True)classSummaryBoxConfig:"""Configuration for the SummaryBox page section."""template:str=str(Path(__file__).with_name("templates")/"summary_box.html")
[docs]classSummaryBox(PageObj,Configurable[SummaryBoxConfig]):"""Render general & statistical summary values."""def__init__(self,**params:dict[str,Any])->None:"""Initialize the SummaryBox with a template path."""super().__init__(SummaryBoxConfig,**params)@propertydeftemplate_path(self)->str:"""Get the path to the template file."""returnself.config.template
[docs]defrender(self,env:Environment,ctx:Context)->str:"""Renders the summary box using a Jinja2 template. This method checks if the specified template file exists. If not, it logs an error and returns an HTML error message. If the template is outside the default environment's loader path, it creates a new Jinja2 Environment with the appropriate loader. It then loads the template, computes the overall summary for the first energy field in the context, and renders the template with the computed summary and the column name. Args: env (Environment): The Jinja2 environment to use for template rendering. ctx (Context): The context containing energy fields and other relevant data. Returns: str: The rendered HTML string for the summary box, or an error message if the template is missing. """ifnotPath(self.template_path).is_file():logger.error("SummaryBox: template '%s' not found.",self.template_path)return"<p><strong>Error:</strong> summary template missing.</p>"tmpl=get_local_env(env,self.template_path).get_template(Path(self.template_path).name)summary=self._compute_overall_summary(ctx.energy_fields[0],ctx)returntmpl.render(column=ctx.energy_fields[0],**summary)
@staticmethoddef_compute_overall_summary(energy_column:str,ctx:Context)->dict[str,Any]:"""Compute an overall summary of energy-related statistics for a given column. Args: energy_column (str): The name of the column containing energy data. ctx (Context): The context containing artefacts and statistics. Returns: dict: A dictionary containing the following summary statistics: - total_commits (int): Total number of data points in the distribution. - significant_changes (int): Number of significant change events. - regressions (int): Count of changes with an "increase" direction. - improvements (int): Count of changes with a "decrease" direction. - mean_energy (float): Mean value of the energy column. - median_energy (float): Median value of the energy column. - std_energy (float): Standard deviation of the energy column. - max_increase (float): Maximum severity of "increase" changes. - max_decrease (float): Maximum severity of "decrease" changes. - avg_cohens_d (float): Average absolute Cohen's d value for changes. - normal_count (int): Number of normality flags set to True. - non_normal_count (int): Number of normality flags set to False. - outliers_removed (int): Number of outliers removed from the dataset. """normality=ctx.artefacts["normality_flags"]changes=ctx.artefacts["change_events"]if(df:=ctx.artefacts.get("df",pd.DataFrame()))isnotNone:outliers_removed_count=ctx.stats.get("commits_removed",0)mean_energy=df[energy_column].mean()median_energy=df[energy_column].median()std_energy=df[energy_column].std()else:outliers_removed_count=0mean_energy=Nonemedian_energy=Nonestd_energy=Nonevalid_commits=ctx.stats["valid_commits"]oldest_commit=valid_commits[-1]ifvalid_commitselseNonelatest_commit=valid_commits[0]ifvalid_commitselseNonesummary={"project_name":ctx.artefacts["project_name"],"oldest_commit_hash":oldest_commit[:7]ifoldest_commitelseNone,"oldest_commit_date":ctx.artefacts["commit_details"].get(oldest_commit,{}).get("commit_date")ifoldest_commitelseNone,"latest_commit_hash":latest_commit[:7]iflatest_commitelseNone,"latest_commit_date":ctx.artefacts["commit_details"].get(latest_commit,{}).get("commit_date")iflatest_commitelseNone,"total_commits":len(ctx.stats["valid_commits"]),"significant_changes":sum(1foreinchangesife.level>0ande.direction=="increase")+sum(1foreinchangesife.level>0ande.direction=="decrease"),"regressions":sum(1foreinchangesife.level>0ande.direction=="increase"),"improvements":sum(1foreinchangesife.level>0ande.direction=="decrease"),"mean_energy":mean_energy,"median_energy":median_energy,"std_energy":std_energy,"avg_cohens_d":np.mean([abs(e.effect_size.cohen_d)foreinchanges])ifchangeselse0.0,"normal_count":sum(normality),"non_normal_count":len(normality)-sum(normality),"outliers_removed":outliers_removed_count,}returnsummary