|
|
@@ -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],
|
|
|
+ }
|