ソースを参照

优化:完善 HTML 报告图表摘要

DESKTOP-74CLTRG\Leol 3 日 前
コミット
b38c2f1979
3 ファイル変更75 行追加2 行削除
  1. 8 0
      app.py
  2. 38 2
      app_utils.py
  3. 29 0
      tests/test_app_utils.py

+ 8 - 0
app.py

@@ -31,6 +31,7 @@ from app_utils import (
     build_diagnostic_dashboard,
     build_html_report,
     build_ml_factor_insights,
+    build_report_chart_summaries,
     calculate_kpis,
     calculate_spc_metrics,
     generate_industry_diagnosis,
@@ -2596,6 +2597,12 @@ if current_config["show_export"]:
     # 7. 生成报告图表
     daily_for_chart = filtered_df.groupby("day").size().rename("缺陷数").reset_index() if len(filtered_df) >= 2 else None
     report_charts = generate_report_charts(filtered_df, daily_trend_df=daily_for_chart)
+    chart_summaries = build_report_chart_summaries(
+        type_counts_r,
+        equipment_counts=eq_counts,
+        severity_counts=filtered_df["severity"].value_counts() if "severity" in filtered_df.columns else None,
+        trend_summary=trend_summary,
+    )
 
     full_report_html = build_html_report(
         generated_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
@@ -2611,6 +2618,7 @@ if current_config["show_export"]:
         anomaly_rows=anomaly_rows,
         recommendations=recommendations_r,
         charts=report_charts,
+        chart_summaries=chart_summaries,
     )
 
     col_exp1, col_exp2, col_exp3 = st.columns(3)

+ 38 - 2
app_utils.py

@@ -421,7 +421,7 @@ def _fig_to_base64(fig, *, dpi=120):
 
 
 def generate_report_charts(filtered_df, *, daily_trend_df=None):
-    """生成报告内嵌的张核心图表,返回 dict of base64 data URIs。"""
+    """生成报告内嵌的张核心图表,返回 dict of base64 data URIs。"""
     charts = {}
 
     # --- 1. 缺陷类型分布条形图 ---
@@ -491,7 +491,7 @@ def generate_report_charts(filtered_df, *, daily_trend_df=None):
         sev_counts = filtered_df["severity"].value_counts()
         if not sev_counts.empty:
             fig, ax = plt.subplots(figsize=(4.5, 3.5))
-            sev_colors = {"轻微": "#22c55e", "一般": "#f59e0b", "严重": "#ef4444"}
+            sev_colors = {"轻微": "#22c55e", "中等": "#f59e0b", "严重": "#ef4444"}
             colors = [sev_colors.get(name, "#94a3b8") for name in sev_counts.index]
             wedges, texts, autotexts = ax.pie(
                 sev_counts.values, labels=sev_counts.index,
@@ -516,6 +516,32 @@ def _series_rows(series):
     return list(series.items())
 
 
+def build_report_chart_summaries(type_counts, equipment_counts=None, severity_counts=None, trend_summary="-"):
+    """生成图表旁的结构化摘要,便于报告快速阅读和追溯。"""
+    summaries = []
+    type_rows = _series_rows(type_counts)
+    if type_rows:
+        total = max(sum(int(count) for _, count in type_rows), 1)
+        name, count = type_rows[0]
+        summaries.append(f"TOP1 缺陷类型:{name},{int(count)} 个,占比 {count / total:.1%}")
+
+    equipment_rows = _series_rows(equipment_counts)
+    if equipment_rows:
+        name, count = equipment_rows[0]
+        summaries.append(f"最高缺陷设备:{name},{int(count)} 个缺陷")
+
+    severity_rows = _series_rows(severity_counts)
+    if severity_rows:
+        total = max(sum(int(count) for _, count in severity_rows), 1)
+        serious_count = int(dict(severity_rows).get("严重", 0))
+        summaries.append(f"严重缺陷占比:{serious_count / total:.1%}")
+
+    if trend_summary and trend_summary != "-":
+        summaries.append(str(trend_summary))
+
+    return summaries
+
+
 def build_html_report(
     *,
     generated_at,
@@ -531,11 +557,13 @@ def build_html_report(
     anomaly_rows=None,
     recommendations=None,
     charts=None,
+    chart_summaries=None,
 ):
     """生成可直接在浏览器打开的自包含综合 HTML 报告。"""
     anomaly_rows = anomaly_rows or []
     recommendations = recommendations or []
     charts = charts or {}
+    chart_summaries = chart_summaries or []
     type_rows = _series_rows(type_counts)
     equipment_rows = _series_rows(equipment_counts)
     seat_rows = _series_rows(seat_top)
@@ -566,6 +594,9 @@ def build_html_report(
     recommendation_items = "\n".join(
         f"<li>{_escape(item)}</li>" for item in recommendations
     ) or "<li>暂无建议</li>"
+    chart_summary_items = "\n".join(
+        f"<li>{_escape(item)}</li>" for item in chart_summaries
+    ) or "<li>暂无图表摘要</li>"
 
     return f"""<!doctype html>
 <html lang="zh-CN">
@@ -656,6 +687,11 @@ def build_html_report(
           <tr><td>严重缺陷</td><td>{int(kpis.get('critical_defects', 0))} 个</td></tr>
         </table>
       </div>
+      <div class="card">
+        <h2>图表摘要</h2>
+        <ul>{chart_summary_items}</ul>
+        <p class="note">摘要由导出时的筛选数据自动生成,适合会议或邮件快速阅读。</p>
+      </div>
       <div class="card">
         <h2>2. 趋势分析</h2>
         <p>{_escape(trend_summary)}</p>

+ 29 - 0
tests/test_app_utils.py

@@ -12,6 +12,7 @@ from app_utils import (
     calculate_kpis,
     calculate_spc_metrics,
     generate_industry_diagnosis,
+    generate_report_charts,
     normalize_defect_schema,
 )
 
@@ -265,12 +266,40 @@ class AppUtilsTest(unittest.TestCase):
             trend_summary="缺陷数趋势: 上升",
             anomaly_rows=[{"equipment": "LAM-A01", "seat": "R1C1", "count": 4}],
             recommendations=["重点关注气泡"],
+            chart_summaries=["TOP1 缺陷类型:气泡,占比 58.3%"],
         )
 
         self.assertIn("<!doctype html>", html.lower())
         self.assertIn("缺陷集中性分析综合报告", html)
         self.assertIn("气泡&lt;script&gt;", html)
         self.assertNotIn("气泡<script>", html)
+        self.assertIn("图表摘要", html)
+        self.assertIn("TOP1 缺陷类型:气泡,占比 58.3%", html)
+
+    def test_generate_report_charts_creates_four_offline_images(self):
+        df = pd.DataFrame(
+            {
+                "defect_id": ["D1", "D2", "D3", "D4"],
+                "panel_id": ["P1", "P2", "P3", "P4"],
+                "batch_id": ["B1"] * 4,
+                "equipment_id": ["LAM-A01", "LAM-A01", "LAM-B01", "LAM-B01"],
+                "seat_id": ["R1C1", "R1C2", "R2C1", "R2C2"],
+                "timestamp": pd.to_datetime(["2026-04-01", "2026-04-02", "2026-04-02", "2026-04-03"]),
+                "defect_type": ["气泡", "气泡", "划痕", "异物"],
+                "severity": ["轻微", "中等", "严重", "中等"],
+                "shift": ["白班"] * 4,
+                "day": ["2026-04-01", "2026-04-02", "2026-04-02", "2026-04-03"],
+            }
+        )
+        daily = df.groupby("day").size().rename("缺陷数").reset_index()
+
+        charts = generate_report_charts(df, daily_trend_df=daily)
+
+        self.assertEqual(
+            {"type_distribution", "daily_trend", "equipment_distribution", "severity_pie"},
+            set(charts),
+        )
+        self.assertTrue(all(value.startswith("data:image/png;base64,") for value in charts.values()))
 
 
 if __name__ == "__main__":