"""This module provides functions for generating mass balance reports.""" from pathlib import Path import plotly.graph_objects as go from jinja2 import Template from plotly.io import to_html from datetime import datetime, timedelta import yaml import logging from . import plotting import json import numpy as np logger = logging.getLogger(__name__) def mass_balance_report( krig_params: dict, wind_fig: go.Figure, background_fig: go.Figure, threed_fig: go.Figure, krig_fig: go.Figure, windrose_fig: go.Figure, ) -> str: """Generate a mass balance report.""" template_path = Path(__file__).parent / "templates" / "mass_balance_template.html" # Convert the figures to HTML plot_htmls = {} for name, fig in zip( ["3D", "krig", "windrose", "wind", "background"], [threed_fig, krig_fig, windrose_fig, wind_fig, background_fig], strict=False, ): if fig: plot_htmls[name] = to_html(fig, full_html=False) else: plot_htmls[name] = plotting.blank_figure() summary_data = { "Estimated flux": f"{krig_params.get('volume', 0):.3f} kgh⁻¹", } with Path.open(template_path) as f: template_content = f.read() template = Template(template_content) return template.render( title="Mass Balance Report", summary_data=summary_data, threeD=plot_htmls["3D"], krig=plot_htmls["krig"], windrose=plot_htmls["windrose"], wind=plot_htmls["wind"], background=plot_htmls["background"], ) def generate_reports(name: str, processor, config: dict, output_dir: Path): """ Generates reports, configuration files, and processed output variables for gasflux processing runs. Parameters: name (str): The name identifier for the current processing run (task_id). processor (object): The processing object containing report data and output variables. config (dict): Configuration dictionary used for processing. output_dir (Path): Output directory path (already includes task_id) from INI configuration. """ processing_time = datetime.now() + timedelta(hours=8) # Beijing time (UTC+8) # Save directly to the output directory (already includes task_id) output_path = output_dir output_path.mkdir(parents=True, exist_ok=True) # Save reports for gas, report in processor.reports.items(): timestamp_str = processing_time.strftime("%Y%m%d_%H%M%S") report_path = output_path / f"{gas}_report_{timestamp_str}.html" with open(report_path, "w", encoding="utf-8") as file: file.write(report) # Save config header = f"# Gasflux output config for task {name} from processing run at {processing_time}\n" timestamp_str = processing_time.strftime("%Y%m%d_%H%M%S") config_path = output_path / f"config_{timestamp_str}.yaml" with open(config_path, "w") as file: file.write(header) yaml.safe_dump(config, file) # Save DataFrame to Excel if hasattr(processor, 'df') and processor.df is not None: timestamp_str = processing_time.strftime("%Y%m%d_%H%M%S") excel_path = output_path / f"processed_data_{timestamp_str}.xlsx" processor.df.to_excel(excel_path, index=False, engine='openpyxl') logger.info(f"DataFrame saved to {excel_path}") # Save output variables output_vars = processor.output_vars # output_vars = delete_large_arrays(output_vars, threshold_size=50) header = ( f"# Gasflux output variables for task {name} from processing run at {processing_time}\n" ) timestamp_str = processing_time.strftime("%Y%m%d_%H%M%S") filename = output_path / f"output_vars_{timestamp_str}.json" with open(filename, "w") as file: file.write(header) json.dump( output_vars, file, default=lambda item: item.tolist() if isinstance(item, np.ndarray) else item, indent=4 ) logger.info(f"Task {name} results saved to {output_path}") def delete_large_arrays(output_vars: dict, threshold_size: int) -> dict: """ Iterate through the output_vars dictionary and replace large numpy arrays with their metadata (e.g., shape and data type). Parameters: output_vars (dict): The dictionary containing output data including potential numpy arrays. threshold_size (int): The number of elements above which an array is considered large. """ del_keys = [] for key, value in output_vars.items(): if isinstance(value, dict): output_vars[key] = delete_large_arrays(value, threshold_size) # recursive elif isinstance(value, np.ndarray): if value.size > threshold_size: del_keys.append(key) for key in del_keys: del output_vars[key] return output_vars