|
@@ -29,6 +29,7 @@ from defect_analysis.cases import (
|
|
|
from app_utils import (
|
|
from app_utils import (
|
|
|
apply_defect_filters,
|
|
apply_defect_filters,
|
|
|
build_diagnostic_dashboard,
|
|
build_diagnostic_dashboard,
|
|
|
|
|
+ build_html_report,
|
|
|
build_ml_factor_insights,
|
|
build_ml_factor_insights,
|
|
|
calculate_kpis,
|
|
calculate_kpis,
|
|
|
calculate_spc_metrics,
|
|
calculate_spc_metrics,
|
|
@@ -2538,63 +2539,39 @@ if current_config["show_export"]:
|
|
|
|
|
|
|
|
# 综合报告导出
|
|
# 综合报告导出
|
|
|
st.subheader("📋 一键导出综合报告")
|
|
st.subheader("📋 一键导出综合报告")
|
|
|
- st.markdown("包含所有分析模块的关键结论,适合汇报和存档。")
|
|
|
|
|
-
|
|
|
|
|
- report_parts = []
|
|
|
|
|
- report_parts.append("# 缺陷集中性分析综合报告\n")
|
|
|
|
|
- report_parts.append(f"**生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
|
|
|
- report_parts.append(f"**数据范围**: {start_date.strftime('%Y-%m-%d')} ~ {end_date.strftime('%Y-%m-%d')}")
|
|
|
|
|
- report_parts.append(f"**筛选后缺陷数**: {len(filtered_df)} 条")
|
|
|
|
|
- report_parts.append(f"**涉及面板**: {filtered_df['panel_id'].nunique()} 块")
|
|
|
|
|
- report_parts.append(f"**视图模式**: {view_mode}\n")
|
|
|
|
|
|
|
+ st.markdown("包含所有分析模块的关键结论,下载后可直接用浏览器打开、归档或打印为 PDF。")
|
|
|
|
|
|
|
|
# 1. KPI 摘要
|
|
# 1. KPI 摘要
|
|
|
- report_parts.append("## 1. KPI 摘要\n")
|
|
|
|
|
report_kpis = calculate_kpis(df, filtered_df)
|
|
report_kpis = calculate_kpis(df, filtered_df)
|
|
|
total_panels_inspected_r = report_kpis["total_panels_inspected"]
|
|
total_panels_inspected_r = report_kpis["total_panels_inspected"]
|
|
|
defective_panels_r = report_kpis["defective_panels"]
|
|
defective_panels_r = report_kpis["defective_panels"]
|
|
|
yield_rate_r = report_kpis["yield_rate"]
|
|
yield_rate_r = report_kpis["yield_rate"]
|
|
|
- report_parts.append(f"- 检测面板数: {total_panels_inspected_r} 块")
|
|
|
|
|
defective_rate_r = defective_panels_r / max(total_panels_inspected_r, 1) * 100
|
|
defective_rate_r = defective_panels_r / max(total_panels_inspected_r, 1) * 100
|
|
|
- report_parts.append(f"- 不良面板数: {defective_panels_r} 块 ({defective_rate_r:.1f}%)")
|
|
|
|
|
- report_parts.append(f"- 综合良率: {yield_rate_r:.1f}%")
|
|
|
|
|
- report_parts.append(f"- 缺陷总数: {len(filtered_df)} 个")
|
|
|
|
|
- report_parts.append(f"- 严重缺陷: {(filtered_df['severity']=='严重').sum()} 个\n")
|
|
|
|
|
|
|
|
|
|
# 2. 缺陷类型
|
|
# 2. 缺陷类型
|
|
|
- report_parts.append("## 2. 缺陷类型分布\n")
|
|
|
|
|
type_counts_r = filtered_df["defect_type"].value_counts()
|
|
type_counts_r = filtered_df["defect_type"].value_counts()
|
|
|
- for t, c in type_counts_r.items():
|
|
|
|
|
- report_parts.append(f"- {t}: {c} ({c/len(filtered_df)*100:.1f}%)")
|
|
|
|
|
- report_parts.append("")
|
|
|
|
|
|
|
|
|
|
# 3. 设备/座号
|
|
# 3. 设备/座号
|
|
|
|
|
+ eq_counts = pd.Series(dtype=int)
|
|
|
|
|
+ seat_top = pd.Series(dtype=int)
|
|
|
if "equipment_id" in filtered_df.columns:
|
|
if "equipment_id" in filtered_df.columns:
|
|
|
- report_parts.append("## 3. 设备与座号分布\n")
|
|
|
|
|
eq_counts = filtered_df["equipment_id"].value_counts()
|
|
eq_counts = filtered_df["equipment_id"].value_counts()
|
|
|
- for e, c in eq_counts.items():
|
|
|
|
|
- report_parts.append(f"- {e}: {c} 个缺陷")
|
|
|
|
|
seat_top = filtered_df["seat_id"].value_counts().head(5)
|
|
seat_top = filtered_df["seat_id"].value_counts().head(5)
|
|
|
- report_parts.append(f"\n**缺陷座号 TOP5**:")
|
|
|
|
|
- for i, (s, c) in enumerate(seat_top.items(), 1):
|
|
|
|
|
- report_parts.append(f" {i}. {s}: {c} 个")
|
|
|
|
|
- report_parts.append("")
|
|
|
|
|
|
|
|
|
|
# 4. 趋势
|
|
# 4. 趋势
|
|
|
- report_parts.append("## 4. 趋势分析\n")
|
|
|
|
|
|
|
+ trend_summary = "缺陷数趋势: 样本天数不足,暂不判断趋势"
|
|
|
daily_r = filtered_df.groupby("day").size()
|
|
daily_r = filtered_df.groupby("day").size()
|
|
|
if len(daily_r) >= 2:
|
|
if len(daily_r) >= 2:
|
|
|
x_r = np.arange(len(daily_r))
|
|
x_r = np.arange(len(daily_r))
|
|
|
coeffs_r = np.polyfit(x_r, daily_r.values.astype(float), 1)
|
|
coeffs_r = np.polyfit(x_r, daily_r.values.astype(float), 1)
|
|
|
slope_r = coeffs_r[0]
|
|
slope_r = coeffs_r[0]
|
|
|
if slope_r > 0:
|
|
if slope_r > 0:
|
|
|
- report_parts.append(f"- 缺陷数趋势: **上升** (斜率 {slope_r:.1f}/天)")
|
|
|
|
|
|
|
+ trend_summary = f"缺陷数趋势: 上升 (斜率 {slope_r:.1f}/天)"
|
|
|
else:
|
|
else:
|
|
|
- report_parts.append(f"- 缺陷数趋势: **下降** (斜率 {slope_r:.1f}/天)")
|
|
|
|
|
- report_parts.append("")
|
|
|
|
|
|
|
+ trend_summary = f"缺陷数趋势: 下降 (斜率 {slope_r:.1f}/天)"
|
|
|
|
|
|
|
|
# 5. 异常座号
|
|
# 5. 异常座号
|
|
|
- report_parts.append("## 5. 异常检测\n")
|
|
|
|
|
|
|
+ anomaly_rows = []
|
|
|
if "seat_id" in filtered_df.columns:
|
|
if "seat_id" in filtered_df.columns:
|
|
|
all_seat_stats_r = filtered_df.groupby(["equipment_id", "seat_id"]).size()
|
|
all_seat_stats_r = filtered_df.groupby(["equipment_id", "seat_id"]).size()
|
|
|
mean_r = all_seat_stats_r.mean()
|
|
mean_r = all_seat_stats_r.mean()
|
|
@@ -2602,33 +2579,41 @@ if current_config["show_export"]:
|
|
|
threshold_2x_r = mean_r + 2 * std_r
|
|
threshold_2x_r = mean_r + 2 * std_r
|
|
|
critical_r = all_seat_stats_r[all_seat_stats_r > threshold_2x_r]
|
|
critical_r = all_seat_stats_r[all_seat_stats_r > threshold_2x_r]
|
|
|
if len(critical_r) > 0:
|
|
if len(critical_r) > 0:
|
|
|
- report_parts.append(f"- ⚠️ 2σ 异常座号: {len(critical_r)} 个")
|
|
|
|
|
for (eq, seat), count in critical_r.items():
|
|
for (eq, seat), count in critical_r.items():
|
|
|
- report_parts.append(f" - {eq}/{seat}: {count} 个缺陷")
|
|
|
|
|
- else:
|
|
|
|
|
- report_parts.append("- ✅ 无 2σ 异常座号")
|
|
|
|
|
- report_parts.append("")
|
|
|
|
|
|
|
+ anomaly_rows.append({"equipment": eq, "seat": seat, "count": count})
|
|
|
|
|
|
|
|
# 6. 建议
|
|
# 6. 建议
|
|
|
- report_parts.append("## 6. 建议\n")
|
|
|
|
|
top_type = type_counts_r.index[0] if len(type_counts_r) > 0 else "-"
|
|
top_type = type_counts_r.index[0] if len(type_counts_r) > 0 else "-"
|
|
|
top_eq = eq_counts.index[0] if len(eq_counts) > 0 else "-"
|
|
top_eq = eq_counts.index[0] if len(eq_counts) > 0 else "-"
|
|
|
- report_parts.append(f"- 重点关注缺陷类型: **{top_type}**")
|
|
|
|
|
- report_parts.append(f"- 重点关注设备: **{top_eq}**")
|
|
|
|
|
- report_parts.append("- 建议查看 SPC 控制图确认趋势状态")
|
|
|
|
|
- report_parts.append("- 建议检查设备健康评分\n")
|
|
|
|
|
-
|
|
|
|
|
- report_parts.append("---\n*本报告由缺陷集中性分析系统自动生成*")
|
|
|
|
|
|
|
+ recommendations_r = [
|
|
|
|
|
+ f"重点关注缺陷类型: {top_type}",
|
|
|
|
|
+ f"重点关注设备: {top_eq}",
|
|
|
|
|
+ "建议查看 SPC 控制图确认趋势状态",
|
|
|
|
|
+ "建议检查设备健康评分",
|
|
|
|
|
+ ]
|
|
|
|
|
|
|
|
- full_report = "\n".join(report_parts)
|
|
|
|
|
|
|
+ full_report_html = build_html_report(
|
|
|
|
|
+ generated_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
|
|
+ date_range_text=f"{start_date.strftime('%Y-%m-%d')} ~ {end_date.strftime('%Y-%m-%d')}",
|
|
|
|
|
+ view_mode=view_mode,
|
|
|
|
|
+ defect_count=len(filtered_df),
|
|
|
|
|
+ panel_count=filtered_df["panel_id"].nunique(),
|
|
|
|
|
+ kpis=report_kpis,
|
|
|
|
|
+ type_counts=type_counts_r,
|
|
|
|
|
+ equipment_counts=eq_counts,
|
|
|
|
|
+ seat_top=seat_top,
|
|
|
|
|
+ trend_summary=trend_summary,
|
|
|
|
|
+ anomaly_rows=anomaly_rows,
|
|
|
|
|
+ recommendations=recommendations_r,
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
col_exp1, col_exp2, col_exp3 = st.columns(3)
|
|
col_exp1, col_exp2, col_exp3 = st.columns(3)
|
|
|
with col_exp1:
|
|
with col_exp1:
|
|
|
st.download_button(
|
|
st.download_button(
|
|
|
- label="📥 综合报告 (MD)",
|
|
|
|
|
- data=full_report.encode("utf-8"),
|
|
|
|
|
- file_name=f"defect_report_{datetime.now().strftime('%Y%m%d')}.md",
|
|
|
|
|
- mime="text/markdown",
|
|
|
|
|
|
|
+ label="📥 综合报告 (HTML网页)",
|
|
|
|
|
+ data=full_report_html.encode("utf-8"),
|
|
|
|
|
+ file_name=f"defect_report_{datetime.now().strftime('%Y%m%d')}.html",
|
|
|
|
|
+ mime="text/html",
|
|
|
use_container_width=True
|
|
use_container_width=True
|
|
|
)
|
|
)
|
|
|
with col_exp2:
|
|
with col_exp2:
|