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编号信息:
+
+
+ | Cell编号 |
+ 数据点 |
+ 均值 |
+ 标准差 |
+ 最小值 |
+ 最大值 |
+
+ {% for cell_no, stats_dict in sn_plot.cell_info.items() %}
+
+ | {{ 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) }} |
+
+ {% endfor %}
+
+
+
+ {% endif %}
{% 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