From 4e4d132066340c5fdd33d72c96d1add2a7103979 Mon Sep 17 00:00:00 2001 From: panxiang <1275280643@qq.com> Date: Fri, 27 Mar 2026 14:32:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20=E6=B1=87=E6=80=BB?= =?UTF-8?q?=E5=9B=BE=E5=92=8C=E5=8D=95=E7=8B=AC=E5=9B=BE=E7=9A=84=20Cell?= =?UTF-8?q?=E6=8E=92=E5=BA=8F=E5=92=8CSN=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../htmlReportProcess_picHtml_2kV1.py | 262 +++++++++++++++--- 1 file changed, 223 insertions(+), 39 deletions(-) diff --git a/htmlProcess/htmlReportProcess_picHtml/htmlReportProcess_picHtml_2kV1.py b/htmlProcess/htmlReportProcess_picHtml/htmlReportProcess_picHtml_2kV1.py index 947a94a..2b4529a 100644 --- a/htmlProcess/htmlReportProcess_picHtml/htmlReportProcess_picHtml_2kV1.py +++ b/htmlProcess/htmlReportProcess_picHtml/htmlReportProcess_picHtml_2kV1.py @@ -316,13 +316,46 @@ HTML_TEMPLATE = """
{% for sn_plot in test.sn_plot_images %}
-
SN: {{ sn_plot.sn }}
+
+ {% if sn_plot.cell_no %} + Cell: {{ sn_plot.cell_no }}, SN: {{ sn_plot.sn_no }} + {% else %} + SN: {{ sn_plot.sn }} + {% endif %} +
+ {% if sn_plot.has_cell_data %} +
+ Cell编号信息: + + + + + + + + + + {% for cell_no, stats_dict in sn_plot.cell_info.items() %} + + + + + + + + + {% endfor %} + +
Cell编号数据点均值标准差最小值最大值
{{ cell_no }}{{ stats_dict.count }}{{ "%.4f"|format(stats_dict.mean) }}{{ "%.4f"|format(stats_dict.std) }}{{ "%.4f"|format(stats_dict.min) }}{{ "%.4f"|format(stats_dict.max) }}
+
+ {% endif %} {{ test.name }} - SN {{ sn_plot.sn }} 散点图
{% endfor %}
{% endif %} + {% if not loop.last %}
{% endif %} @@ -398,7 +431,17 @@ class MultiFileTestReportScatterPlotter: self.folder_path: Optional[str] = None self.df: Optional[pd.DataFrame] = None self.output_dir: Optional[str] = None - self.required_columns = ["Test Name New", "SN", "Measurement", "Test Time", "Lower Limit", "Upper Limit"] + # self.required_columns = ["Test Name New", "SN", "Measurement", "Test Time", "Lower Limit", "Upper Limit"] + # 更新required_columns,增加Cell编号 + self.required_columns = [ + "Test Name New", + "SN", + "Measurement", + "Test Time", + "Lower Limit", + "Upper Limit", + "Cell" # 新增Cell编号列 + ] self.col_lower: Optional[str] = None self.col_upper: Optional[str] = None self.html_report_path: Optional[str] = None @@ -919,6 +962,26 @@ class MultiFileTestReportScatterPlotter: test_data['TestTime_dt'] = self._clean_and_convert_series( test_data['Test Time'], 'datetime' ) + # 确保Cell编号存在(如果原数据中有) + if 'Cell' in test_data.columns: + # 清理数据:去除首尾空格,并尝试转换为数值类型 + test_data['Cell编号'] = test_data['Cell'].astype(str).str.strip() + + # 尝试将清理后的字符串转换为数值(例如整数) + # errors='coerce' 会将无法转换的值设为NaN(非数字) + test_data['Cell编号_数值'] = pd.to_numeric(test_data['Cell编号'], errors='coerce') + + # 检查是否存在转换失败的值(即NaN) + failed_conversions = test_data['Cell编号_数值'].isna().sum() + if failed_conversions > 0: + print(f"警告:发现 {failed_conversions} 个 'Cell' 值无法转换为数字,这些条目将保留为字符串或根据业务逻辑处理。") + # 业务决策:对于无法转换的,可以保留原字符串,或使用一个默认值 + # 例如,将无法转换的条目其数值编号设为-1或一个特定的标识值 + # test_data.loc[test_data['Cell编号_数值'].isna(), 'Cell编号_数值'] = -1 + + # 此时,您可以根据需求选择使用 'Cell编号'(字符串)或 'Cell编号_数值'(数字)进行后续分组和可视化 + # 对于绘图着色和排序,使用 'Cell编号_数值' 列 + grouping_column = 'Cell编号_数值' # 去除无效数据 valid_data = test_data.dropna(subset=['Measurement_num', 'TestTime_dt']) @@ -952,11 +1015,37 @@ class MultiFileTestReportScatterPlotter: """创建汇总图(所有SN在一个图中)""" fig, ax = plt.subplots(figsize=(12, 8)) + # 检查是否有Cell编号列 + has_cell_no = 'Cell编号' in test_data.columns + # 分组绘制 - groups = list(test_data.groupby("SN")) if "SN" in test_data.columns else [("Unknown_SN", test_data)] - for sn, group in groups: - ax.scatter(group['TestTime_dt'], group['Measurement_num'], - label=str(sn), alpha=0.7, s=25) + if has_cell_no and not test_data['Cell编号'].isna().all(): + # 先按Cell编号_数值排序,再按SN排序 + test_data_sorted = test_data.sort_values(['Cell编号_数值', 'SN']) + + # 按Cell编号_数值和SN分组 + cell_sn_groups = list(test_data_sorted.groupby(['Cell编号_数值', 'SN'])) + + # 生成颜色映射 + colors = plt.cm.Set3(np.linspace(0, 1, len(cell_sn_groups))) + + for idx, ((cell_no, sn), group) in enumerate(cell_sn_groups): + label = f"Cell:{cell_no}, SN:{sn}" + ax.scatter(group['TestTime_dt'], group['Measurement_num'], + color=colors[idx], alpha=0.7, s=25, label=label) + else: + # 只按SN分组排序 + test_data_sorted = test_data.sort_values('SN') + sn_groups = [(f"{sn}_no_cell", group) for sn, group in test_data_sorted.groupby("SN")] + + # 生成颜色映射 + colors = plt.cm.Set3(np.linspace(0, 1, len(sn_groups))) + + for idx, (group_key, group) in enumerate(sn_groups): + sn = str(group_key).replace('_no_cell', '') + label = f"SN: {sn}" + ax.scatter(group['TestTime_dt'], group['Measurement_num'], + color=colors[idx], alpha=0.7, s=25, label=label) # 计算统计信息 y_data = test_data['Measurement_num'] @@ -977,7 +1066,10 @@ class MultiFileTestReportScatterPlotter: linestyles='-.', linewidth=1.5, alpha=0.7, label='Median') # 设置图形属性 - ax.set_title(f"汇总图 - {test_name}") + title = f"汇总图 - {test_name}" + if has_cell_no and not test_data['Cell编号'].isna().all(): + title += " (按Cell→SN排序)" + ax.set_title(title) ax.set_xlabel("Test Time") ax.set_ylabel("Measurement Value") ax.grid(True, alpha=0.3) @@ -994,47 +1086,139 @@ class MultiFileTestReportScatterPlotter: if "SN" not in test_data.columns: return sn_plots - sn_groups = test_data.groupby("SN") + # 检查是否有Cell编号列 + has_cell_no = 'Cell编号' in test_data.columns - for sn, group in sn_groups: - if group.empty: - continue + # 如果有Cell编号,先按Cell编号_数值排序,再按SN排序 + if has_cell_no and not test_data['Cell编号'].isna().all(): + # 先按Cell编号_数值排序,再按SN排序 + test_data_sorted = test_data.sort_values(['Cell编号_数值', 'SN']) - fig, ax = plt.subplots(figsize=(10, 6)) + # 按Cell编号_数值分组,然后对每个Cell内的数据按SN排序 + cell_groups = test_data_sorted.groupby('Cell编号_数值') - # 绘制当前SN的数据点 - ax.scatter(group['TestTime_dt'], group['Measurement_num'], - color='blue', alpha=0.7, s=30, label=f"SN: {sn}") + for cell_no, cell_group in cell_groups: + if cell_group.empty: + continue - # 计算当前SN的统计信息 - y_data = group['Measurement_num'] - stats = self._calculate_statistics(y_data) + # 对当前Cell内的数据按SN排序 + cell_group_sorted = cell_group.sort_values('SN') - # 绘制限值线 - x_min, x_max = group['TestTime_dt'].min(), group['TestTime_dt'].max() + # 按SN分组 + sn_groups = cell_group_sorted.groupby('SN') - if lower_plot is not None: - ax.axhline(y=lower_plot, color='green', linestyle='--', linewidth=1.2, label="Lower Limit") - if upper_plot is not None: - ax.axhline(y=upper_plot, color='red', linestyle='--', linewidth=1.2, label="Upper Limit") + for sn, group in sn_groups: + if group.empty: + continue - # 添加统计线 - ax.hlines(y=stats['mean'], xmin=x_min, xmax=x_max, colors='orange', - linestyles='-', linewidth=1.5, alpha=0.7, label='Mean') - ax.hlines(y=stats['median'], xmin=x_min, xmax=x_max, colors='purple', - linestyles='-.', linewidth=1.5, alpha=0.7, label='Median') + fig, ax = plt.subplots(figsize=(10, 6)) - # 设置图形属性 - ax.set_title(f"SN独立图 - {test_name} (SN: {sn})") - ax.set_xlabel("Test Time") - ax.set_ylabel("Measurement Value") - ax.grid(True, alpha=0.3) - ax.tick_params(axis='x', rotation=45) - ax.legend() + # 绘制当前SN和Cell的数据点 + ax.scatter(group['TestTime_dt'], group['Measurement_num'], + color='blue', alpha=0.7, s=30, label=f"SN:{sn}, Cell:{cell_no}") - # 转换为base64 - plot_image = self._plot_to_base64(fig) - sn_plots.append({"sn": str(sn), "image": plot_image}) + # 计算当前SN和Cell组合的统计信息 + y_data = group['Measurement_num'] + stats = self._calculate_statistics(y_data) + + # 绘制限值线 + x_min, x_max = group['TestTime_dt'].min(), group['TestTime_dt'].max() + + if lower_plot is not None: + ax.axhline(y=lower_plot, color='green', linestyle='--', linewidth=1.2, label="Lower Limit") + if upper_plot is not None: + ax.axhline(y=upper_plot, color='red', linestyle='--', linewidth=1.2, label="Upper Limit") + + # 添加统计线 + ax.hlines(y=stats['mean'], xmin=x_min, xmax=x_max, colors='orange', + linestyles='-', linewidth=1.5, alpha=0.7, label='Mean') + ax.hlines(y=stats['median'], xmin=x_min, xmax=x_max, colors='purple', + linestyles='-.', linewidth=1.5, alpha=0.7, label='Median') + + # 设置图形属性 + ax.set_title(f"SN独立图 - {test_name} (Cell: {cell_no}, SN: {sn})") + ax.set_xlabel("Test Time") + ax.set_ylabel("Measurement Value") + ax.grid(True, alpha=0.3) + ax.tick_params(axis='x', rotation=45) + ax.legend() + + # 转换为base64 + plot_image = self._plot_to_base64(fig) + + # 收集当前Cell编号的统计信息 - 修复格式 + cell_info = {} + if has_cell_no: + # 计算当前Cell编号的详细统计信息 + cell_stats_dict = { + 'count': len(group), + 'mean': float(group['Measurement_num'].mean()), + 'std': float(group['Measurement_num'].std()), + 'min': float(group['Measurement_num'].min()), + 'max': float(group['Measurement_num'].max()) + } + cell_info = {str(cell_no): cell_stats_dict} + + sn_plots.append({ + "sn": f"Cell_{cell_no}_SN_{sn}", + "image": plot_image, + "cell_info": cell_info if cell_info else None, + "has_cell_data": True, + "cell_no": str(cell_no), + "sn_no": str(sn) + }) + else: + # 没有Cell编号,只按SN分组排序 + test_data_sorted = test_data.sort_values('SN') + sn_groups = test_data_sorted.groupby("SN") + + for sn, group in sn_groups: + if group.empty: + continue + + fig, ax = plt.subplots(figsize=(10, 6)) + + # 没有Cell编号,按SN着色 + ax.scatter(group['TestTime_dt'], group['Measurement_num'], + color='blue', alpha=0.7, s=30, label=f"SN: {sn}") + + # 计算当前SN的统计信息 + y_data = group['Measurement_num'] + stats = self._calculate_statistics(y_data) + + # 绘制限值线 + x_min, x_max = group['TestTime_dt'].min(), group['TestTime_dt'].max() + + if lower_plot is not None: + ax.axhline(y=lower_plot, color='green', linestyle='--', linewidth=1.2, label="Lower Limit") + if upper_plot is not None: + ax.axhline(y=upper_plot, color='red', linestyle='--', linewidth=1.2, label="Upper Limit") + + # 添加统计线 + ax.hlines(y=stats['mean'], xmin=x_min, xmax=x_max, colors='orange', + linestyles='-', linewidth=1.5, alpha=0.7, label='Mean') + ax.hlines(y=stats['median'], xmin=x_min, xmax=x_max, colors='purple', + linestyles='-.', linewidth=1.5, alpha=0.7, label='Median') + + # 设置图形属性 + ax.set_title(f"SN独立图 - {test_name} (SN: {sn})") + ax.set_xlabel("Test Time") + ax.set_ylabel("Measurement Value") + ax.grid(True, alpha=0.3) + ax.tick_params(axis='x', rotation=45) + ax.legend() + + # 转换为base64 + plot_image = self._plot_to_base64(fig) + + sn_plots.append({ + "sn": str(sn), + "image": plot_image, + "cell_info": None, + "has_cell_data": False, + "cell_no": None, + "sn_no": str(sn) + }) return sn_plots