Forráskód Böngészése

增强:新增3C面板行业化诊断 + 区域分类 + 缺陷模式识别

- classify_panel_zone(): 坐标映射7个区域(边缘/角落/FPC/中心等)
- DEFECT_SOP_RECOMMENDATIONS: 8种缺陷类型SOP排查建议
- detect_industry_patterns(): 区域集中/跨面板重复/线状分布/批次集中
- generate_industry_diagnosis(): 组合诊断headline+排查建议
- 诊断驾驶舱新增3C面板行业诊断结论卡片
- 单元测试 5→7 项
leod 1 hete
szülő
commit
098b8b97d5
3 módosított fájl, 205 hozzáadás és 0 törlés
  1. 32 0
      app.py
  2. 123 0
      app_utils.py
  3. 50 0
      tests/test_app_utils.py

+ 32 - 0
app.py

@@ -22,6 +22,7 @@ from app_utils import (
     build_diagnostic_dashboard,
     calculate_kpis,
     calculate_spc_metrics,
+    generate_industry_diagnosis,
 )
 
 # --- 中文字体设置 ---
@@ -297,6 +298,7 @@ _t = get_tab("🧭 诊断驾驶舱")
 if _t:
     with _t:
         dashboard = build_diagnostic_dashboard(filtered_df)
+        industry_diagnosis = generate_industry_diagnosis(filtered_df, dashboard)
         level_colors = {
             "严重": ("#7f1d1d", "#fee2e2"),
             "关注": ("#92400e", "#fef3c7"),
@@ -405,6 +407,36 @@ if _t:
                 unsafe_allow_html=True,
             )
 
+        st.markdown(
+            f"""
+            <div style="
+                margin-top: 16px;
+                padding: 18px 20px;
+                border-radius: 18px;
+                border: 1px solid #c7d2fe;
+                background: linear-gradient(135deg, #eef2ff 0%, #f8fafc 55%, #ecfeff 100%);
+            ">
+                <div style="font-size: 13px; color: #475569; font-weight: 700; margin-bottom: 6px;">
+                    3C 面板行业诊断结论
+                </div>
+                <div style="font-size: 18px; color: #0f172a; font-weight: 800;">
+                    {industry_diagnosis["headline"]}
+                </div>
+            </div>
+            """,
+            unsafe_allow_html=True,
+        )
+
+        diag_col1, diag_col2 = st.columns([1, 1])
+        with diag_col1:
+            st.subheader("识别到的缺陷模式")
+            for pattern in industry_diagnosis["patterns"]:
+                st.markdown(f"- {pattern}")
+        with diag_col2:
+            st.subheader("行业化排查建议")
+            for idx, recommendation in enumerate(industry_diagnosis["recommendations"], 1):
+                st.markdown(f"{idx}. {recommendation}")
+
         st.divider()
         left, right = st.columns([1.25, 1])
         with left:

+ 123 - 0
app_utils.py

@@ -4,6 +4,18 @@ import numpy as np
 import pandas as pd
 
 
+DEFECT_SOP_RECOMMENDATIONS = {
+    "划痕": ["检查搬运轨道、吸嘴和治具接触面", "复核清洗滚刷与擦拭工位是否有硬质颗粒"],
+    "气泡": ["检查贴合压力、真空度、OCA 状态和贴合速度", "复核贴合前清洁与材料开封时长"],
+    "漏光": ["检查边缘贴合、背光组装、框胶和压合均匀性", "复核四角/边缘区应力与夹持状态"],
+    "色差": ["检查背光、偏光片批次、贴合应力和老化条件", "对比同批材料与相邻工艺参数"],
+    "异物": ["检查洁净度、清洗段、静电控制和材料暴露时间", "追溯同批材料与工位环境记录"],
+    "亮点": ["复核点灯/AOI 判定、TFT 像素缺陷和异物压伤", "抽查高发区域是否存在压接或污染"],
+    "暗点": ["复核点灯/AOI 判定、TFT 像素缺陷和异物压伤", "检查绑定/驱动相关区域异常"],
+    "裂纹": ["立即检查切割、搬运、夹持和跌落冲击风险", "对同批面板执行 Hold 与复检"],
+}
+
+
 def normalize_date_bounds(start_date, end_date):
     """把日期范围转换成左闭右开的时间边界,确保结束日期整天被包含。"""
     start_ts = pd.Timestamp(start_date).normalize()
@@ -42,6 +54,37 @@ def apply_defect_filters(
     return df[mask].copy()
 
 
+def classify_panel_zone(df):
+    """按 3C 面板行业常用语义把坐标映射到关键区域。"""
+    width = df.get("panel_width_mm", pd.Series(155.0, index=df.index)).replace(0, np.nan)
+    height = df.get("panel_height_mm", pd.Series(340.0, index=df.index)).replace(0, np.nan)
+    x = df.get("x_mm", width * 0.5)
+    y = df.get("y_mm", height * 0.5)
+    x_norm = x / width
+    y_norm = y / height
+
+    zones = []
+    for x, y in zip(x_norm.fillna(0.5), y_norm.fillna(0.5)):
+        labels = []
+        if x <= 0.1:
+            labels.append("左边缘区")
+        if x >= 0.9:
+            labels.append("右边缘区")
+        if y <= 0.1:
+            labels.append("下边缘区")
+        if y >= 0.9:
+            labels.append("上边缘区")
+        if (x <= 0.12 or x >= 0.88) and (y <= 0.12 or y >= 0.88):
+            labels.append("角落区")
+        if 0.68 <= y <= 0.88 and 0.25 <= x <= 0.75:
+            labels.append("FPC/绑定区")
+        if not labels:
+            labels.append("显示中心区")
+        zones.append(" / ".join(labels))
+
+    return pd.Series(zones, index=df.index, name="panel_zone")
+
+
 def calculate_kpis(source_df, filtered_df):
     """基于当前筛选结果计算页面 KPI。"""
     total_panels_inspected = filtered_df["panel_id"].nunique()
@@ -120,8 +163,12 @@ def build_diagnostic_dashboard(df):
         }
 
     type_counts = df["defect_type"].value_counts()
+    zones = classify_panel_zone(df)
+    zone_counts = zones.value_counts()
     top_defect_type = type_counts.index[0]
     top_defect_share = float(type_counts.iloc[0] / total_defects)
+    top_zone = zone_counts.index[0]
+    top_zone_share = float(zone_counts.iloc[0] / total_defects)
     serious_share = float((df["severity"] == "严重").sum() / total_defects)
 
     root_causes = (
@@ -182,9 +229,85 @@ def build_diagnostic_dashboard(df):
         "severity_level": severity_level,
         "top_defect_type": top_defect_type,
         "top_defect_share": top_defect_share,
+        "top_zone": top_zone,
+        "top_zone_share": top_zone_share,
+        "zone_distribution": zone_counts.rename_axis("区域").reset_index(name="缺陷数"),
         "serious_share": serious_share,
         "root_causes": root_causes,
         "daily_trend": daily_trend,
         "pareto": pareto,
         "primary_recommendation": primary_recommendation,
     }
+
+
+def detect_industry_patterns(df):
+    """识别面板行业常见缺陷模式。"""
+    if df.empty:
+        return []
+
+    patterns = []
+    zones = classify_panel_zone(df)
+    zone_share = zones.value_counts(normalize=True)
+    if any(idx != "显示中心区" and share >= 0.35 for idx, share in zone_share.items()):
+        patterns.append(f"区域集中: {zone_share.index[0]} 占比 {zone_share.iloc[0]:.1%}")
+
+    coord_df = df.copy()
+    coord_df["x_bin"] = (coord_df["x_mm"] // 5).astype(int)
+    coord_df["y_bin"] = (coord_df["y_mm"] // 5).astype(int)
+    repeat = coord_df.groupby(["x_bin", "y_bin"])["panel_id"].nunique().max()
+    if repeat >= min(3, max(2, df["panel_id"].nunique())):
+        patterns.append("跨面板重复坐标: 疑似治具、吸嘴、压头或固定接触点异常")
+
+    if df["x_mm"].nunique() >= 3 and df["y_mm"].nunique() >= 3 and len(df) >= 6:
+        corr = abs(pd.Series(df["x_mm"]).corr(pd.Series(df["y_mm"])))
+        if pd.notna(corr) and corr >= 0.85:
+            patterns.append("线状分布: 疑似搬运划伤、滚轮轨迹或线性压伤")
+
+    batch_share = df["batch_id"].value_counts(normalize=True).iloc[0]
+    if batch_share >= 0.5 and df["batch_id"].nunique() > 1:
+        patterns.append(f"批次集中: {df['batch_id'].value_counts().index[0]} 占比 {batch_share:.1%}")
+
+    return patterns or ["随机点状分布: 更偏向材料、环境尘埃或偶发检出"]
+
+
+def generate_industry_diagnosis(df, dashboard):
+    """生成 3C 面板行业化诊断结论和排查建议。"""
+    if df.empty:
+        return {
+            "headline": "当前筛选条件下没有可诊断缺陷。",
+            "patterns": [],
+            "recommendations": ["放宽筛选条件或上传更多检测记录后再诊断。"],
+        }
+
+    top_type = dashboard["top_defect_type"]
+    top_zone = dashboard.get("top_zone", classify_panel_zone(df).value_counts().index[0])
+    top_root = dashboard["root_causes"].iloc[0]["根因候选"] if len(dashboard["root_causes"]) else "当前筛选范围"
+    patterns = detect_industry_patterns(df)
+
+    recommendations = []
+    if top_type in DEFECT_SOP_RECOMMENDATIONS:
+        recommendations.extend(DEFECT_SOP_RECOMMENDATIONS[top_type])
+    if "边缘" in top_zone or "角落" in top_zone:
+        recommendations.append("优先复核边缘贴合、切割/搬运夹持、吸附接触面和四角应力状态")
+    if "FPC" in top_zone or "绑定" in top_zone:
+        recommendations.append("重点检查绑定压力、FPC/COF 区域异物、压接参数和 AOI 复判样本")
+    if any("跨面板重复" in p for p in patterns):
+        recommendations.append("对高发座号对应治具、吸嘴、压头做点检,并抽查同坐标复现样本")
+    if dashboard["serious_share"] >= 0.2:
+        recommendations.append("严重缺陷占比较高,建议对相关批次执行 Hold、复检或加严抽样")
+
+    deduped = []
+    for item in recommendations:
+        if item not in deduped:
+            deduped.append(item)
+
+    headline = (
+        f"{top_zone} 的 {top_type} 最突出,首要候选为 {top_root}。"
+        f"建议按工序链路优先排查材料、贴合/搬运接触面和对应治具状态。"
+    )
+
+    return {
+        "headline": headline,
+        "patterns": patterns,
+        "recommendations": deduped[:5],
+    }

+ 50 - 0
tests/test_app_utils.py

@@ -10,8 +10,10 @@ import pandas as pd
 from app_utils import (
     apply_defect_filters,
     build_diagnostic_dashboard,
+    classify_panel_zone,
     calculate_kpis,
     calculate_spc_metrics,
+    generate_industry_diagnosis,
 )
 
 
@@ -114,6 +116,54 @@ class AppUtilsTest(unittest.TestCase):
         self.assertEqual("E1 / S-hot", top["根因候选"])
         self.assertGreater(top["异常倍数"], 1.0)
 
+    def test_classify_panel_zone_uses_3c_panel_regions(self):
+        zones = classify_panel_zone(
+            pd.DataFrame(
+                {
+                    "x_mm": [2.0, 77.5, 150.0, 80.0],
+                    "y_mm": [335.0, 255.0, 170.0, 20.0],
+                    "panel_width_mm": [155.0] * 4,
+                    "panel_height_mm": [340.0] * 4,
+                }
+            )
+        )
+
+        self.assertIn("角落区", zones.iloc[0])
+        self.assertIn("FPC/绑定区", zones.iloc[1])
+        self.assertIn("右边缘区", zones.iloc[2])
+        self.assertIn("下边缘区", zones.iloc[3])
+
+    def test_industry_diagnosis_generates_panel_sop_recommendation(self):
+        rows = []
+        for i in range(12):
+            rows.append(
+                {
+                    "defect_id": f"D{i}",
+                    "panel_id": f"P{i}",
+                    "batch_id": "B1",
+                    "equipment_id": "LAM-A01",
+                    "seat_id": "R2C3",
+                    "timestamp": pd.Timestamp("2026-04-01"),
+                    "defect_type": "气泡",
+                    "severity": "严重" if i < 4 else "中等",
+                    "x_mm": 5.0 + i * 0.3,
+                    "y_mm": 250.0,
+                    "panel_width_mm": 155.0,
+                    "panel_height_mm": 340.0,
+                    "shift": "白班",
+                    "day": "2026-04-01",
+                }
+            )
+        df = pd.DataFrame(rows)
+        dashboard = build_diagnostic_dashboard(df)
+
+        diagnosis = generate_industry_diagnosis(df, dashboard)
+
+        self.assertIn("边缘", diagnosis["headline"])
+        self.assertIn("气泡", diagnosis["headline"])
+        self.assertTrue(any("贴合" in item for item in diagnosis["recommendations"]))
+        self.assertTrue(any("跨面板重复" in pattern for pattern in diagnosis["patterns"]))
+
 
 if __name__ == "__main__":
     unittest.main()