报表自动化:每天自动生成FAB日报
报表自动化:每天自动生成FAB日报
一、问题背景:每天花2小时做日报
生产经理每天早上开晨会,第一件事就是看日报。
日报内容:
- 昨日生产情况(Lot完成数、良率、平均周期)
- 异常事件回顾
- 关键指标趋势
过去的做法:
1. 从MES导出数据
2. 复制到日报模板
3. 计算各项指标
4. 截图贴图
5. 发邮件
问题:
1. 每天重复同样的步骤,太浪费
2. 偶尔手滑,数字贴错了
3. 领导临时要变格式,全部重做
4. 没人想干这活,谁值班谁倒霉
解决方案:用Python自动生成日报。
---
二、技术原理:报表自动化的3个步骤
2.1 数据准备
import pandas as pd from datetime import datetime, timedelta def prepare_daily_data(): """获取昨日数据""" yesterday = datetime.now() - timedelta(days=1) date_str = yesterday.strftime('%Y-%m-%d') # 从MES获取数据(模拟) data = pd.DataFrame({ 'lot_id': [f'FAB-{i:04d}' for i in range(50)], 'process': ['ETCH', 'PHOTO', 'CVD', 'CMP', 'IMP'], 'wafer_count': np.random.randint(12, 26, 50), 'thickness_avg': 1250 + np.random.randn(50) * 3, 'thickness_std': 1 + np.random.rand(50) * 2, 'yield_rate': 95 + np.random.randn(50) * 2, 'cycle_time': np.random.randint(120, 360, 50), # 分钟 'start_time': yesterday, }) return data
2.2 指标计算
def calculate_kpis(data): """计算关键指标""" kpis = { 'total_lots': len(data), 'total_wafers': data['wafer_count'].sum(), 'avg_yield': data['yield_rate'].mean(), 'avg_cycle_time': data['cycle_time'].mean(), 'abnormal_lots': len(data[data['yield_rate'] < 90]), 'avg_thickness': data['thickness_avg'].mean(), } return kpis
2.3 报告生成
def generate_report(kpis, output_path): """生成日报文件""" report = f""" FAB日报 - {datetime.now().strftime('%Y-%m-%d')} {'='*50} 昨日生产概况: 完成Lot: {kpis['total_lots']}批 完成Wafer: {kpis['total_wafers']}片 平均良率: {kpis['avg_yield']:.1f}% 平均周期: {kpis['avg_cycle_time']:.0f}分钟 异常情况: 低良率Lot: {kpis['abnormal_lots']}批 质量指标: 平均厚度: {kpis['avg_thickness']:.2f} A {'='*50} """ with open(output_path, 'w', encoding='utf-8') as f: f.write(report)
---
三、实战案例:完整的日报系统
"""FAB日报自动生成系统(精简版,核心≤80行)""" import pandas as pd import numpy as np from datetime import datetime, timedelta from pathlib import Path class FABDailyReportGenerator: """FAB日报生成器(精简版)""" def __init__(self, output_dir="./daily_reports"): self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) self.data = None self.kpis = {} self.yesterday = datetime.now() - timedelta(days=1) def load_data(self, data_source=None): """加载数据(为什么支持模拟?因为真实MES数据要申请权限,先用手动数据调试)""" if data_source and Path(data_source).exists(): self.data = pd.read_csv(data_source) else: # 模拟MES数据(用固定seed保证每天数据不同但可重复) np.random.seed(int(self.yesterday.timestamp())) n_lots = np.random.randint(30, 60) self.data = pd.DataFrame([{ 'lot_id': f'FAB-{self.yesterday.strftime("%m%d")}-{i+1:04d}', 'process': np.random.choice(['PHOTO', 'ETCH', 'CVD', 'CMP', 'IMP']), 'wafer_count': np.random.randint(12, 26), 'thickness_avg': round(1250 + np.random.randn() * 3, 1), 'yield_rate': round(min(100, max(80, 95 + np.random.randn() * 3)), 1), 'cycle_time': np.random.randint(120, 360), } for i in range(n_lots)]) return self.data def calculate_kpis(self): """计算关键指标(KPI = Key Performance Indicator,领导最关心这几个数)""" df = self.data # 生产概览(总量指标) self.kpis = { 'date': self.yesterday.strftime('%Y-%m-%d'), 'total_lots': len(df), 'total_wafers': df['wafer_count'].sum(), 'avg_yield': round(df['yield_rate'].mean(), 1), 'avg_cycle_time': round(df['cycle_time'].mean(), 1), 'avg_thickness': round(df['thickness_avg'].mean(), 2), 'low_yield_count': len(df[df['yield_rate'] < 90]), # <90%算异常 'abnormal_count': len(df[(df['yield_rate'] < 90) | (df['thickness_std'] > 3)]), } # 分工序统计(groupby是pandas最常用操作,按工序分组算均值) self.kpis['process_stats'] = df.groupby('process').agg({ 'lot_id': 'count', 'yield_rate': 'mean', 'cycle_time': 'mean', }).to_dict('index') return self.kpis def generate_text_report(self): """生成文本格式日报(为什么用文本?因为邮件正文、微信、钉钉都能直接贴)""" date_str = self.yesterday.strftime('%Y年%m月%d日') p = self.kpis['process_stats'] lines = [ "=" * 60, "半导体FAB生产日报", f"日期: {date_str}", "=" * 60, "", "【生产概况】", f" 完成Lot: {self.kpis['total_lots']} 批", f" 完成Wafer: {self.kpis['total_wafers']} 片", f" 平均良率: {self.kpis['avg_yield']}%", f" 平均周期: {self.kpis['avg_cycle_time']} 分钟", f" 平均厚度: {self.kpis['avg_thickness']} A", "", "【质量异常】", f" 低良率Lot: {self.kpis['low_yield_count']} 批", f" 异常合计: {self.kpis['abnormal_count']} 批", "", "【分工序统计】", ] for proc, stats in p.items(): lines.append(f" {proc}: {stats['lot_id']}批, 良率{stats['yield_rate']:.1f}%, 周期{stats['cycle_time']:.0f}分钟") lines.extend(["", "=" * 60, f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}"]) return "\n".join(lines) def run(self, data_source=None): """执行完整流程""" self.load_data(data_source) self.calculate_kpis() return {'text_report': self.generate_text_report()} # 使用示例 if __name__ == '__main__': generator = FABDailyReportGenerator() result = generator.run() print(result['text_report'])
---
四、效果对比
维度 | 手工日报 | 自动日报 | 提升
|------|---------|---------|------|
生成时间 | 2小时 | 3秒 | 2400倍
错误率 | 5% | <0.1% | 98%
格式扩展性 | 差 | 灵活 | 好
离线可用 | 可以 | 需要配置 | -
---
五、实施建议:报表自动化三阶段
我在FAB推行报表自动化时,发现最大的阻力不是技术,而是“习惯”。工程师们习惯了Excel,觉得“手动做更放心”。要成功推行,得按三个阶段来:
第一阶段:手工报表标准化(第1-2周)
在写第一行代码之前,先把现有的手工报表摸清。我刚去生产部时,发现每个人的日报格式都不一样:
- 张工喜欢用表格,李工喜欢用段落
- 有的人把良率放前面,有的人把周期放前面
- 有的人用红色标注异常,有的人用黄色
我的做法:
1. 跟3-5个工程师聊,看他们现在的日报长什么样
2. 提炼出“公共部分”:生产概况、质量异常、分工序统计,这3块是所有人都要的

3. 设计一个“标准模板”,跟生产经理确认后,作为自动化报表的格式基准
4. 把标准模板用Markdown写出来(为什么用Markdown?因为转HTML、转PDF都很方便)
踩过的坑:一开始想“一步到位”,把每个人要的数据都加进去,结果报表太长,没人看。后来改成“核心报表+定制附件”,核心报表只有1页,定制数据放在Excel附件里。
第二阶段:半自动报表(第3-4周)
标准化后,开始做“半自动”——数据还是要手动导出,但计算和排版由代码完成。
具体步骤:
1. 让工程师每天下班前,把MES数据导出成CSV,放到指定文件夹(比如`D:\FAB\\daily_data\\`)
2. Python脚本从这个文件夹读取最新的CSV,计算KPI,生成文本报表
3. 工程师检查报表,确认无误后,复制粘贴到邮件里发出去
这个阶段的关键是“建立信任”。工程师们看到代码算出来的数字跟手动算的一样,才开始放心。
第三阶段:全自动报表(第5周开始)
信任建立后,就可以做全自动了。我设计了一个简单的自动化流程:
1. 用Windows任务计划程序(Task Scheduler),每天早上6点自动运行Python脚本
2. 脚本从MES数据库直接拉取昨日数据(需要IT部门开一个只读账号)
3. 生成文本报表+Excel明细,自动发送到指定邮箱(用`smtplib`库)
4. 如果数据异常(比如良率<90%),额外抄送质量经理
实施要点:
- 全自动不等于“没人管”。我建议每周人工抽查一次,看自动报表跟手动计算是否一致
- 邮件模板要设计好。我一开始把全部数据都放邮件正文,结果被吐槽“太长了,手机上看不全”。后来改成“摘要+附件”,摘要只放关键KPI,详细数据放Excel
- 权限要控制好。生产数据不能乱发,我加了一个“收件人白名单”,只有经理以上才能收到完整数据
---
六、进阶方向:从日报到智能报表系统
基础的日报自动化只是开始,报表系统还有很多可以升级的地方:
1. Jinja2模板引擎:让报表更美观
现在的文本报表虽然能用,但不够美观。可以用Jinja2模板引擎,生成漂亮的HTML报表。Jinja2是Python的模板库,可以把数据和HTML模板分离,方便维护。
from jinja2 import Template # HTML模板(放在单独的文件里) template_str = """ <h1>FAB日报 - {{ date }}</h1> <p>平均良率: <strong>{{ avg_yield }}%</strong></p> {% if abnormal_count > 0 %} <p style="color:red;">异常Lot: {{ abnormal_count }}批</p> {% endif %} """ template = Template(template_str) html_report = template.render(date=kpis['date'], avg_yield=kpis['avg_yield'], abnormal_count=kpis['abnormal_count'])
2. PDF报告生成:适合正式汇报
给高层的报告通常需要PDF格式。可以用`reportlab`库或者`weasyprint`库,把HTML转成PDF。我推荐`weasyprint`,因为它支持CSS,排版更灵活。
# 用weasyprint把HTML转PDF from weasyprint import HTML HTML(string=html_report).write_pdf('daily_report.pdf')
3. 邮件自动发送:定时推送
报表生成后,自动发送到指定邮箱。Python的`smtplib`库可以搞定,但要注意:
- 不要用个人邮箱发,容易被当成垃圾邮件。申请一个企业邮箱专门用来发报表
- 大附件(比如Excel明细)要压缩,不然邮箱拒收
- 如果报表里有异常数据,主题栏要标注`[异常]`,让收件人一眼看到
import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart def send_email(text_report, recipients): msg = MIMEMultipart() msg['Subject'] = 'FAB日报 - ' + datetime.now().strftime('%Y-%m-%d') msg['From'] = 'fab-report@company.com' msg['To'] = ', '.join(recipients) msg.attach(MIMEText(text_report, 'plain', 'utf-8')) with smtplib.SMTP('smtp.company.com') as server: server.send_message(msg)
4. 交互式仪表盘(Dashboard)
比日报更高级的是“实时仪表盘”。可以用`Streamlit`库,几分钟搭一个Web仪表盘,展示实时生产数据。Streamlit是Python的快速Web应用框架,不需要写HTML/CSS/JS,全用Python搞定。
5. 多语言支持
如果公司在海外有工厂,报表需要英文版。可以用`gettext`库做国际化,或者简单点,用一个`lang`字典存储中英文对照。
---
> 📦 专栏VIP资源包:包含本系列40篇全部可运行源码、示例数据集、自动化脚本工具包。在专栏主页点击「VIP资源」即可获取。
七、总结
日报自动化的核心不是写代码,而是设计好数据流——数据从哪里来、怎么算、输出什么。标准化了以后,任何变动都改几行代码就行。
下一篇预告:时间序列分析——预测FAB生产趋势。
---
> 💬 你在实际工作中遇到过类似问题吗?欢迎在评论区聊聊你的经历,或者说说你最想用Python自动化的场景!
>
> 📚 专栏持续更新中,关注不迷路。觉得有用的话,收藏+点赞支持一下~ 👍
>
> 🔧 专栏配套工具包(含本篇完整可运行代码+示例数据)已上传为VIP资源,专栏目录页可下载。




