Automated Reports
1. Introduction
Automated Reporting
Automated reports giảm manual work, đảm bảo consistency, và deliver insights đúng thời điểm. Analyst giỏi build systems, không phải chạy reports hàng ngày.
1.1 Why Automate?
Text
1┌──────────────────────────────────────────────────────────┐2│ BENEFITS OF AUTOMATION │3├──────────────────────────────────────────────────────────┤4│ │5│ TIME SAVINGS │6│ ───────────────────────────────────────── │7│ Before: 4 hours/week creating reports │8│ After: 30 minutes setup, 10 min/week checking │9│ Savings: 200+ hours/year │10│ │11│ CONSISTENCY │12│ ───────────────────────────────────────── │13│ • Same calculations every time │14│ • No copy-paste errors │15│ • Reproducible results │16│ │17│ TIMELINESS │18│ ───────────────────────────────────────── │19│ • Reports ready when stakeholders need them │20│ • Real-time dashboards possible │21│ • Alerts for anomalies │22│ │23│ SCALABILITY │24│ ───────────────────────────────────────── │25│ • Same effort for 10 or 1000 reports │26│ • Easy to add new metrics │27│ • Template-based expansion │28│ │29└──────────────────────────────────────────────────────────┘1.2 Automation Architecture
Text
1┌─────────────────────────────────────────────────────────┐2│ REPORTING AUTOMATION PIPELINE │3├─────────────────────────────────────────────────────────┤4│ │5│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │6│ │ DATA │───>│ PROCESS │───>│ OUTPUT │ │7│ │ SOURCE │ │ & CALC │ │ GENERATE │ │8│ └──────────┘ └──────────┘ └──────────┘ │9│ │ │ │10│ v v │11│ • Database • PDF/Excel │12│ • APIs • HTML Email │13│ • Files • Dashboard │14│ • Streams • Slack/Teams │15│ │16│ ┌──────────────────────────────────────────────────┐ │17│ │ SCHEDULER │ │18│ │ • Cron / Task Scheduler │ │19│ │ • Airflow / Dagster / Prefect │ │20│ │ • Cloud Functions │ │21│ └──────────────────────────────────────────────────┘ │22│ │23│ ┌──────────────────────────────────────────────────┐ │24│ │ MONITORING │ │25│ │ • Error alerts │ │26│ │ • Run logging │ │27│ │ • Performance tracking │ │28│ └──────────────────────────────────────────────────┘ │29│ │30└─────────────────────────────────────────────────────────┘2. Building Report Templates
2.1 Jinja2 Templates
Python
1from jinja2 import Template, Environment, FileSystemLoader2import pandas as pd34# Simple template5simple_template = Template("""6━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━7 {{ title }}8 {{ date }}9━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━1011�� KEY METRICS12{% for metric in metrics %}13{{ metric.status }} {{ metric.name }}: {{ metric.value }} (Target: {{ metric.target }})14{% endfor %}1516�� HIGHLIGHTS17{% for highlight in highlights %}18• {{ highlight }}19{% endfor %}2021�� ACTION ITEMS22{% for action in actions %}23{{ loop.index }}. {{ action.task }} (Owner: {{ action.owner }}, Due: {{ action.due }})24{% endfor %}2526━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━27""")2829# Data for template30data = {31 'title': 'WEEKLY SALES REPORT',32 'date': '2024-03-18',33 'metrics': [34 {'name': 'Revenue', 'value': '$2.5M', 'target': '$2.3M', 'status': '✅'},35 {'name': 'Orders', 'value': '12,500', 'target': '11,000', 'status': '✅'},36 {'name': 'Conversion', 'value': '3.2%', 'target': '3.5%', 'status': '⚠️'},37 ],38 'highlights': [39 'Record Thursday revenue (+45% vs average)',40 'Mobile app orders grew 25% MoM',41 'Enterprise deal closed ($500K ACV)'42 ],43 'actions': [44 {'task': 'Review mobile checkout UX', 'owner': 'Product', 'due': '3/22'},45 {'task': 'Launch retargeting campaign', 'owner': 'Marketing', 'due': '3/20'},46 ]47}4849report = simple_template.render(**data)50print(report)2.2 HTML Email Templates
Python
1html_template = """2<!DOCTYPE html>3<html>4<head>5 <style>6 body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; }7 .header { background: #2c3e50; color: white; padding: 20px; text-align: center; }8 .kpi-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; padding: 20px; }9 .kpi-card { background: #f8f9fa; border-radius: 8px; padding: 20px; text-align: center; }10 .kpi-value { font-size: 28px; font-weight: bold; color: #2c3e50; }11 .kpi-label { color: #6c757d; margin-top: 5px; }12 .status-green { color: #27ae60; }13 .status-red { color: #e74c3c; }14 .status-yellow { color: #f39c12; }15 table { width: 100%; border-collapse: collapse; margin: 20px 0; }16 th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }17 th { background: #ecf0f1; }18 .section { padding: 20px; }19 .section-title { font-size: 18px; font-weight: bold; margin-bottom: 15px; }20 </style>21</head>22<body>23 <div class="header">24 <h1>{{ title }}</h1>25 <p>{{ subtitle }}</p>26 </div>27 28 <div class="kpi-grid">29 {% for kpi in kpis %}30 <div class="kpi-card">31 <div class="kpi-value {{ kpi.status_class }}">{{ kpi.value }}</div>32 <div class="kpi-label">{{ kpi.name }}</div>33 <div style="font-size: 12px; color: #888;">vs Target: {{ kpi.vs_target }}</div>34 </div>35 {% endfor %}36 </div>37 38 <div class="section">39 <div class="section-title">�� Performance Summary</div>40 <table>41 <tr>42 <th>Metric</th>43 <th>Current</th>44 <th>Target</th>45 <th>Variance</th>46 <th>Trend</th>47 </tr>48 {% for row in performance_table %}49 <tr>50 <td>{{ row.metric }}</td>51 <td>{{ row.current }}</td>52 <td>{{ row.target }}</td>53 <td class="{{ row.variance_class }}">{{ row.variance }}</td>54 <td>{{ row.trend }}</td>55 </tr>56 {% endfor %}57 </table>58 </div>59 60 <div class="section">61 <div class="section-title">�� Key Actions This Week</div>62 <ol>63 {% for action in actions %}64 <li><strong>{{ action.task }}</strong> - {{ action.owner }} (Due: {{ action.due }})</li>65 {% endfor %}66 </ol>67 </div>68 69 <div style="background: #ecf0f1; padding: 15px; text-align: center; font-size: 12px;">70 Auto-generated report | {{ timestamp }} | Questions? Contact analytics@company.com71 </div>72</body>73</html>74"""7576def generate_html_report(data):77 """Generate HTML report from data"""78 template = Template(html_template)79 return template.render(**data)8081# Example data82report_data = {83 'title': 'Weekly Business Report',84 'subtitle': 'Week of March 18, 2024',85 'timestamp': '2024-03-18 08:00:00',86 'kpis': [87 {'name': 'Revenue', 'value': '$2.5M', 'vs_target': '+9%', 'status_class': 'status-green'},88 {'name': 'Customers', 'value': '15,200', 'vs_target': '+8%', 'status_class': 'status-green'},89 {'name': 'Churn Rate', 'value': '5.2%', 'vs_target': '-30%', 'status_class': 'status-red'}90 ],91 'performance_table': [92 {'metric': 'Revenue', 'current': '$2.5M', 'target': '$2.3M', 'variance': '+8.7%', 'variance_class': 'status-green', 'trend': '📈'},93 {'metric': 'Orders', 'current': '12,500', 'target': '11,000', 'variance': '+13.6%', 'variance_class': 'status-green', 'trend': '📈'},94 {'metric': 'AOV', 'current': '$200', 'target': '$209', 'variance': '-4.3%', 'variance_class': 'status-yellow', 'trend': '📉'}95 ],96 'actions': [97 {'task': 'Investigate churn increase', 'owner': 'CS Team', 'due': 'Mar 20'},98 {'task': 'Launch upsell campaign', 'owner': 'Marketing', 'due': 'Mar 22'}99 ]100}101102html_report = generate_html_report(report_data)103104# Save HTML file105with open('weekly_report.html', 'w', encoding='utf-8') as f:106 f.write(html_report)107print("✅ HTML report saved!")2.3 Excel Reports
Python
1import pandas as pd2from openpyxl import Workbook3from openpyxl.styles import Font, Fill, PatternFill, Alignment, Border, Side4from openpyxl.chart import BarChart, LineChart, Reference5from openpyxl.utils.dataframe import dataframe_to_rows67def create_excel_report(data, filename='report.xlsx'):8 """Create formatted Excel report"""9 10 wb = Workbook()11 12 # Summary Sheet13 ws_summary = wb.active14 ws_summary.title = "Executive Summary"15 16 # Styles17 title_font = Font(size=16, bold=True, color="FFFFFF")18 header_font = Font(size=12, bold=True)19 title_fill = PatternFill(start_color="2C3E50", end_color="2C3E50", fill_type="solid")20 green_fill = PatternFill(start_color="27AE60", end_color="27AE60", fill_type="solid")21 red_fill = PatternFill(start_color="E74C3C", end_color="E74C3C", fill_type="solid")22 yellow_fill = PatternFill(start_color="F39C12", end_color="F39C12", fill_type="solid")23 24 thin_border = Border(25 left=Side(style='thin'),26 right=Side(style='thin'),27 top=Side(style='thin'),28 bottom=Side(style='thin')29 )30 31 # Title32 ws_summary.merge_cells('A1:F1')33 ws_summary['A1'] = 'WEEKLY BUSINESS REPORT'34 ws_summary['A1'].font = title_font35 ws_summary['A1'].fill = title_fill36 ws_summary['A1'].alignment = Alignment(horizontal='center')37 38 ws_summary['A2'] = f'Week of {data["date"]}'39 ws_summary['A2'].font = Font(italic=True)40 41 # KPI Summary42 ws_summary['A4'] = 'KEY PERFORMANCE INDICATORS'43 ws_summary['A4'].font = header_font44 45 headers = ['KPI', 'Current', 'Target', 'Variance', 'Status']46 for col, header in enumerate(headers, 1):47 cell = ws_summary.cell(row=5, column=col, value=header)48 cell.font = header_font49 cell.border = thin_border50 51 for row, kpi in enumerate(data['kpis'], 6):52 ws_summary.cell(row=row, column=1, value=kpi['name']).border = thin_border53 ws_summary.cell(row=row, column=2, value=kpi['current']).border = thin_border54 ws_summary.cell(row=row, column=3, value=kpi['target']).border = thin_border55 56 variance_cell = ws_summary.cell(row=row, column=4, value=kpi['variance'])57 variance_cell.border = thin_border58 59 status_cell = ws_summary.cell(row=row, column=5, value=kpi['status'])60 status_cell.border = thin_border61 status_cell.alignment = Alignment(horizontal='center')62 63 if kpi['status'] == '✅':64 status_cell.fill = green_fill65 elif kpi['status'] == '❌':66 status_cell.fill = red_fill67 else:68 status_cell.fill = yellow_fill69 70 # Detailed Data Sheet71 ws_detail = wb.create_sheet("Detailed Data")72 73 # Add DataFrame74 df = data['detailed_df']75 for r_idx, row in enumerate(dataframe_to_rows(df, index=False, header=True), 1):76 for c_idx, value in enumerate(row, 1):77 cell = ws_detail.cell(row=r_idx, column=c_idx, value=value)78 if r_idx == 1:79 cell.font = header_font80 cell.border = thin_border81 82 # Chart Sheet83 ws_chart = wb.create_sheet("Charts")84 85 # Add trend data and chart86 trend_data = data['trend_data']87 for r_idx, row in enumerate(trend_data, 1):88 for c_idx, value in enumerate(row, 1):89 ws_chart.cell(row=r_idx, column=c_idx, value=value)90 91 # Create chart92 chart = LineChart()93 chart.title = "Revenue Trend"94 chart.y_axis.title = "Revenue ($M)"95 chart.x_axis.title = "Week"96 97 data_ref = Reference(ws_chart, min_col=2, min_row=1, max_col=2, max_row=len(trend_data))98 categories = Reference(ws_chart, min_col=1, min_row=2, max_row=len(trend_data))99 100 chart.add_data(data_ref, titles_from_data=True)101 chart.set_categories(categories)102 ws_chart.add_chart(chart, "D2")103 104 # Adjust column widths105 for ws in [ws_summary, ws_detail, ws_chart]:106 for column in ws.columns:107 max_length = 0108 column_letter = column[0].column_letter109 for cell in column:110 try:111 if len(str(cell.value)) > max_length:112 max_length = len(str(cell.value))113 except:114 pass115 ws.column_dimensions[column_letter].width = max_length + 2116 117 wb.save(filename)118 print(f"✅ Excel report saved: {filename}")119120# Sample data121excel_data = {122 'date': 'March 18, 2024',123 'kpis': [124 {'name': 'Revenue', 'current': '$2.5M', 'target': '$2.3M', 'variance': '+8.7%', 'status': '✅'},125 {'name': 'Customers', 'current': '15,200', 'target': '14,000', 'variance': '+8.6%', 'status': '✅'},126 {'name': 'Churn Rate', 'current': '5.2%', 'target': '4.0%', 'variance': '-30%', 'status': '❌'},127 {'name': 'NPS', 'current': '48', 'target': '50', 'variance': '-4%', 'status': '⚠️'}128 ],129 'detailed_df': pd.DataFrame({130 'Product': ['Product A', 'Product B', 'Product C'],131 'Revenue': [1200000, 850000, 450000],132 'Orders': [6000, 4500, 2000],133 'Growth': ['15%', '8%', '-5%']134 }),135 'trend_data': [136 ['Week', 'Revenue'],137 ['Week 1', 2.1],138 ['Week 2', 2.2],139 ['Week 3', 2.3],140 ['Week 4', 2.5]141 ]142}143144create_excel_report(excel_data, 'weekly_business_report.xlsx')3. PDF Reports
3.1 Using ReportLab
Python
1from reportlab.lib import colors2from reportlab.lib.pagesizes import A43from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle4from reportlab.lib.units import inch, cm5from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image6from reportlab.graphics.shapes import Drawing7from reportlab.graphics.charts.linecharts import HorizontalLineChart8from reportlab.graphics.charts.barcharts import VerticalBarChart9import io1011def create_pdf_report(data, filename='report.pdf'):12 """Create professional PDF report"""13 14 doc = SimpleDocTemplate(filename, pagesize=A4,15 rightMargin=72, leftMargin=72,16 topMargin=72, bottomMargin=72)17 18 styles = getSampleStyleSheet()19 20 # Custom styles21 styles.add(ParagraphStyle(22 name='ReportTitle',23 parent=styles['Heading1'],24 fontSize=24,25 spaceAfter=30,26 alignment=1 # Center27 ))28 29 styles.add(ParagraphStyle(30 name='SectionTitle',31 parent=styles['Heading2'],32 fontSize=14,33 spaceBefore=20,34 spaceAfter=10,35 textColor=colors.HexColor('#2C3E50')36 ))37 38 elements = []39 40 # Title41 elements.append(Paragraph("Weekly Business Report", styles['ReportTitle']))42 elements.append(Paragraph(f"Week of {data['date']}", styles['Normal']))43 elements.append(Spacer(1, 20))44 45 # Executive Summary46 elements.append(Paragraph("Executive Summary", styles['SectionTitle']))47 elements.append(Paragraph(data['summary'], styles['Normal']))48 elements.append(Spacer(1, 10))49 50 # KPI Table51 elements.append(Paragraph("Key Performance Indicators", styles['SectionTitle']))52 53 kpi_data = [['KPI', 'Current', 'Target', 'Variance', 'Status']]54 for kpi in data['kpis']:55 kpi_data.append([kpi['name'], kpi['current'], kpi['target'], kpi['variance'], kpi['status']])56 57 kpi_table = Table(kpi_data, colWidths=[2*inch, 1.2*inch, 1.2*inch, 1*inch, 0.8*inch])58 kpi_table.setStyle(TableStyle([59 ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2C3E50')),60 ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),61 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),62 ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),63 ('FONTSIZE', (0, 0), (-1, 0), 12),64 ('BOTTOMPADDING', (0, 0), (-1, 0), 12),65 ('BACKGROUND', (0, 1), (-1, -1), colors.beige),66 ('GRID', (0, 0), (-1, -1), 1, colors.black)67 ]))68 elements.append(kpi_table)69 elements.append(Spacer(1, 20))70 71 # Key Highlights72 elements.append(Paragraph("Key Highlights", styles['SectionTitle']))73 for highlight in data['highlights']:74 elements.append(Paragraph(f"• {highlight}", styles['Normal']))75 elements.append(Spacer(1, 20))76 77 # Action Items78 elements.append(Paragraph("Action Items", styles['SectionTitle']))79 action_data = [['#', 'Task', 'Owner', 'Due Date']]80 for i, action in enumerate(data['actions'], 1):81 action_data.append([str(i), action['task'], action['owner'], action['due']])82 83 action_table = Table(action_data, colWidths=[0.4*inch, 3*inch, 1.2*inch, 1*inch])84 action_table.setStyle(TableStyle([85 ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#34495E')),86 ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),87 ('ALIGN', (0, 0), (-1, -1), 'LEFT'),88 ('ALIGN', (0, 0), (0, -1), 'CENTER'),89 ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),90 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey)91 ]))92 elements.append(action_table)93 94 # Build PDF95 doc.build(elements)96 print(f"✅ PDF report saved: {filename}")9798# Sample data99pdf_data = {100 'date': 'March 18, 2024',101 'summary': '''Revenue exceeded target by 8.7%, driven by strong performance in 102 the Enterprise segment. Customer acquisition remained healthy, though churn 103 rate increased to 5.2%, requiring immediate attention. Marketing efficiency 104 improved with CAC down 15% from previous month.''',105 'kpis': [106 {'name': 'Revenue', 'current': '$2.5M', 'target': '$2.3M', 'variance': '+8.7%', 'status': '✅'},107 {'name': 'Customers', 'current': '15,200', 'target': '14,000', 'variance': '+8.6%', 'status': '✅'},108 {'name': 'Churn Rate', 'current': '5.2%', 'target': '4.0%', 'variance': '-30%', 'status': '❌'},109 {'name': 'NPS', 'current': '48', 'target': '50', 'variance': '-4%', 'status': '⚠️'}110 ],111 'highlights': [112 'Record revenue day on Thursday (+45% vs typical)',113 'Enterprise segment grew 35% MoM',114 'Mobile app DAU reached 25,000 (+20%)'115 ],116 'actions': [117 {'task': 'Launch churn prevention campaign', 'owner': 'CS Team', 'due': 'Mar 22'},118 {'task': 'Review enterprise pricing strategy', 'owner': 'Finance', 'due': 'Mar 25'},119 {'task': 'Optimize checkout conversion', 'owner': 'Product', 'due': 'Mar 28'}120 ]121}122123create_pdf_report(pdf_data, 'weekly_business_report.pdf')4. Email Delivery
4.1 Sending Email Reports
Python
1import smtplib2from email.mime.multipart import MIMEMultipart3from email.mime.text import MIMEText4from email.mime.base import MIMEBase5from email import encoders6import os78class EmailReporter:9 def __init__(self, smtp_server, smtp_port, username, password):10 self.smtp_server = smtp_server11 self.smtp_port = smtp_port12 self.username = username13 self.password = password14 15 def send_report(self, to_emails, subject, html_content, attachments=None):16 """Send email report with optional attachments"""17 18 # Create message19 msg = MIMEMultipart('alternative')20 msg['From'] = self.username21 msg['To'] = ', '.join(to_emails) if isinstance(to_emails, list) else to_emails22 msg['Subject'] = subject23 24 # Plain text fallback25 text_content = "Please view this email in HTML format."26 part1 = MIMEText(text_content, 'plain')27 part2 = MIMEText(html_content, 'html')28 29 msg.attach(part1)30 msg.attach(part2)31 32 # Add attachments33 if attachments:34 for filepath in attachments:35 if os.path.exists(filepath):36 with open(filepath, 'rb') as f:37 part = MIMEBase('application', 'octet-stream')38 part.set_payload(f.read())39 encoders.encode_base64(part)40 filename = os.path.basename(filepath)41 part.add_header('Content-Disposition', 42 f'attachment; filename="{filename}"')43 msg.attach(part)44 45 # Send email46 try:47 with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:48 server.starttls()49 server.login(self.username, self.password)50 server.send_message(msg)51 print(f"✅ Email sent to {to_emails}")52 return True53 except Exception as e:54 print(f"❌ Failed to send email: {e}")55 return False5657# Usage example (with environment variables for security)58"""59import os6061reporter = EmailReporter(62 smtp_server='smtp.gmail.com',63 smtp_port=587,64 username=os.environ['EMAIL_USER'],65 password=os.environ['EMAIL_PASSWORD']66)6768reporter.send_report(69 to_emails=['ceo@company.com', 'cfo@company.com'],70 subject='Weekly Business Report - March 18, 2024',71 html_content=html_report,72 attachments=['weekly_report.xlsx', 'weekly_report.pdf']73)74"""4.2 Slack/Teams Integration
Python
1import requests2import json34class SlackReporter:5 def __init__(self, webhook_url):6 self.webhook_url = webhook_url7 8 def send_report(self, report_data):9 """Send formatted report to Slack"""10 11 # Build Slack message blocks12 blocks = [13 {14 "type": "header",15 "text": {16 "type": "plain_text",17 "text": f"📊 {report_data['title']}"18 }19 },20 {21 "type": "section",22 "text": {23 "type": "mrkdwn",24 "text": f"*{report_data['subtitle']}*"25 }26 },27 {"type": "divider"}28 ]29 30 # KPI section31 kpi_text = "*Key Metrics:*\n"32 for kpi in report_data['kpis']:33 kpi_text += f"{kpi['status']} *{kpi['name']}*: {kpi['value']} (vs Target: {kpi['vs_target']})\n"34 35 blocks.append({36 "type": "section",37 "text": {38 "type": "mrkdwn",39 "text": kpi_text40 }41 })42 43 # Highlights44 if report_data.get('highlights'):45 blocks.append({"type": "divider"})46 highlight_text = "*🎯 Key Highlights:*\n"47 for h in report_data['highlights']:48 highlight_text += f"• {h}\n"49 blocks.append({50 "type": "section",51 "text": {"type": "mrkdwn", "text": highlight_text}52 })53 54 # Actions55 if report_data.get('actions'):56 blocks.append({"type": "divider"})57 action_text = "*📋 Action Items:*\n"58 for i, a in enumerate(report_data['actions'], 1):59 action_text += f"{i}. {a['task']} _{a['owner']} - Due: {a['due']}_\n"60 blocks.append({61 "type": "section",62 "text": {"type": "mrkdwn", "text": action_text}63 })64 65 # Send to Slack66 payload = {"blocks": blocks}67 68 try:69 response = requests.post(70 self.webhook_url,71 json=payload,72 headers={'Content-Type': 'application/json'}73 )74 if response.status_code == 200:75 print("✅ Slack message sent")76 return True77 else:78 print(f"❌ Slack error: {response.text}")79 return False80 except Exception as e:81 print(f"❌ Error sending to Slack: {e}")82 return False8384# Microsoft Teams Integration85class TeamsReporter:86 def __init__(self, webhook_url):87 self.webhook_url = webhook_url88 89 def send_report(self, report_data):90 """Send report to Microsoft Teams"""91 92 # Build Teams Adaptive Card93 card = {94 "@type": "MessageCard",95 "@context": "http://schema.org/extensions",96 "summary": report_data['title'],97 "themeColor": "0076D7",98 "title": f"📊 {report_data['title']}",99 "sections": [{100 "activityTitle": report_data['subtitle'],101 "facts": [102 {"name": kpi['name'], "value": f"{kpi['value']} ({kpi['vs_target']})"} 103 for kpi in report_data['kpis']104 ],105 "markdown": True106 }]107 }108 109 if report_data.get('highlights'):110 highlights_section = {111 "title": "🎯 Key Highlights",112 "text": "\n".join([f"• {h}" for h in report_data['highlights']])113 }114 card["sections"].append(highlights_section)115 116 try:117 response = requests.post(118 self.webhook_url,119 json=card,120 headers={'Content-Type': 'application/json'}121 )122 if response.status_code == 200:123 print("✅ Teams message sent")124 return True125 else:126 print(f"❌ Teams error: {response.text}")127 return False128 except Exception as e:129 print(f"❌ Error sending to Teams: {e}")130 return False5. Scheduling Reports
5.1 APScheduler (Python)
Python
1from apscheduler.schedulers.background import BackgroundScheduler2from apscheduler.triggers.cron import CronTrigger3from datetime import datetime4import time56def create_report_scheduler():7 """Create scheduled report jobs"""8 9 scheduler = BackgroundScheduler()10 11 # Daily report at 7 AM12 def daily_report():13 print(f"📊 Running daily report at {datetime.now()}")14 # Generate and send report15 # generate_daily_report()16 # send_report_email(recipients=['team@company.com'])17 18 scheduler.add_job(19 daily_report,20 CronTrigger(hour=7, minute=0),21 id='daily_report',22 name='Daily Business Report'23 )24 25 # Weekly report every Monday at 8 AM26 def weekly_report():27 print(f"📊 Running weekly report at {datetime.now()}")28 # generate_weekly_report()29 # send_report_email(recipients=['leadership@company.com'])30 31 scheduler.add_job(32 weekly_report,33 CronTrigger(day_of_week='mon', hour=8, minute=0),34 id='weekly_report',35 name='Weekly Business Report'36 )37 38 # Monthly report on the 1st at 9 AM39 def monthly_report():40 print(f"📊 Running monthly report at {datetime.now()}")41 # generate_monthly_report()42 # send_report_email(recipients=['executives@company.com'])43 44 scheduler.add_job(45 monthly_report,46 CronTrigger(day=1, hour=9, minute=0),47 id='monthly_report',48 name='Monthly Business Report'49 )50 51 return scheduler5253# Usage54scheduler = create_report_scheduler()55scheduler.start()5657print("📅 Report scheduler started. Jobs:")58for job in scheduler.get_jobs():59 print(f" - {job.name}: {job.trigger}")6061# Keep running (in production, use proper process management)62# try:63# while True:64# time.sleep(60)65# except KeyboardInterrupt:66# scheduler.shutdown()5.2 GitHub Actions Workflow
yaml
1# .github/workflows/scheduled-reports.yml2 3name: Scheduled Reports4 5on:6 schedule:7 # Daily at 7 AM UTC8 - cron: '0 7 * * *'9 # Weekly Monday at 8 AM UTC10 - cron: '0 8 * * 1'11 workflow_dispatch: # Allow manual trigger12 13jobs:14 daily-report:15 if: github.event.schedule == '0 7 * * *'16 runs-on: ubuntu-latest17 steps:18 - uses: actions/checkout@v319 20 - name: Set up Python21 uses: actions/setup-python@v422 with:23 python-version: '3.10'24 25 - name: Install dependencies26 run: |27 pip install -r requirements.txt28 29 - name: Generate daily report30 env:31 DB_CONNECTION: ${{ secrets.DB_CONNECTION }}32 EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }}33 run: |34 python scripts/generate_daily_report.py35 36 - name: Upload report artifact37 uses: actions/upload-artifact@v338 with:39 name: daily-report40 path: reports/daily_*.pdf41 42 weekly-report:43 if: github.event.schedule == '0 8 * * 1'44 runs-on: ubuntu-latest45 steps:46 - uses: actions/checkout@v347 48 - name: Set up Python49 uses: actions/setup-python@v450 with:51 python-version: '3.10'52 53 - name: Install dependencies54 run: pip install -r requirements.txt55 56 - name: Generate weekly report57 env:58 DB_CONNECTION: ${{ secrets.DB_CONNECTION }}59 run: python scripts/generate_weekly_report.py60 61 - name: Send to Slack62 env:63 SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}64 run: python scripts/send_slack_notification.py6. Complete Reporting System
6.1 Full Pipeline Example
Python
1"""2Complete Automated Reporting System3===================================4"""56import pandas as pd7import numpy as np8from datetime import datetime, timedelta9from sqlalchemy import create_engine10from jinja2 import Template11import smtplib12from email.mime.multipart import MIMEMultipart13from email.mime.text import MIMEText14from email.mime.base import MIMEBase15from email import encoders16import os17import logging1819# Setup logging20logging.basicConfig(21 level=logging.INFO,22 format='%(asctime)s - %(levelname)s - %(message)s'23)24logger = logging.getLogger(__name__)2526class AutomatedReportingSystem:27 """Complete automated reporting system"""28 29 def __init__(self, config):30 self.config = config31 self.data = {}32 33 def extract_data(self):34 """Extract data from sources"""35 logger.info("Extracting data...")36 37 # Database connection (example)38 # engine = create_engine(self.config['db_connection'])39 40 # Simulate data extraction41 self.data['revenue'] = pd.DataFrame({42 'date': pd.date_range(start='2024-01-01', periods=90, freq='D'),43 'revenue': np.random.uniform(70000, 100000, 90),44 'orders': np.random.randint(300, 500, 90),45 'customers': np.random.randint(250, 400, 90)46 })47 48 self.data['kpis'] = {49 'revenue': {'current': 2500000, 'target': 2300000},50 'customers': {'current': 15200, 'target': 14000},51 'churn': {'current': 5.2, 'target': 4.0},52 'nps': {'current': 48, 'target': 50}53 }54 55 logger.info("Data extraction complete")56 return self.data57 58 def calculate_metrics(self):59 """Calculate all report metrics"""60 logger.info("Calculating metrics...")61 62 metrics = {}63 64 # Revenue metrics65 rev_df = self.data['revenue']66 metrics['total_revenue'] = rev_df['revenue'].sum()67 metrics['avg_daily_revenue'] = rev_df['revenue'].mean()68 metrics['revenue_trend'] = rev_df['revenue'].pct_change().mean() * 10069 70 # Customer metrics71 metrics['total_orders'] = rev_df['orders'].sum()72 metrics['avg_order_value'] = metrics['total_revenue'] / metrics['total_orders']73 74 # KPI status75 metrics['kpi_summary'] = []76 for name, values in self.data['kpis'].items():77 current = values['current']78 target = values['target']79 80 # Different logic for metrics where lower is better81 if name in ['churn']:82 variance = (target - current) / target * 10083 on_track = current <= target84 else:85 variance = (current - target) / target * 10086 on_track = current >= target87 88 status = '✅' if on_track else '⚠️' if abs(variance) < 15 else '❌'89 90 metrics['kpi_summary'].append({91 'name': name.title(),92 'current': current,93 'target': target,94 'variance': f"{'+' if variance > 0 else ''}{variance:.1f}%",95 'status': status,96 'on_track': on_track97 })98 99 self.metrics = metrics100 logger.info("Metrics calculation complete")101 return metrics102 103 def generate_report(self, report_type='html'):104 """Generate report in specified format"""105 logger.info(f"Generating {report_type} report...")106 107 if report_type == 'html':108 return self._generate_html_report()109 elif report_type == 'text':110 return self._generate_text_report()111 else:112 raise ValueError(f"Unknown report type: {report_type}")113 114 def _generate_text_report(self):115 """Generate text report"""116 template = Template("""117━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━118 AUTOMATED BUSINESS REPORT119 {{ date }}120━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━121122�� KEY PERFORMANCE INDICATORS123{% for kpi in kpis %}124{{ kpi.status }} {{ kpi.name }}: {{ kpi.current }} (Target: {{ kpi.target }}, {{ kpi.variance }})125{% endfor %}126127�� REVENUE SUMMARY128• Total Revenue: ${{ total_revenue | round(2) | format_number }}129• Average Daily: ${{ avg_daily_revenue | round(2) | format_number }}130• Trend: {{ revenue_trend | round(2) }}%131132�� ORDER SUMMARY133• Total Orders: {{ total_orders | format_number }}134• Average Order Value: ${{ aov | round(2) }}135136━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━137Report generated automatically at {{ timestamp }}138━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━139""")140 141 # Custom filter for number formatting142 def format_number(value):143 return f"{value:,.0f}"144 145 template.environment.filters['format_number'] = format_number146 147 return template.render(148 date=datetime.now().strftime('%Y-%m-%d'),149 timestamp=datetime.now().strftime('%Y-%m-%d %H:%M:%S'),150 kpis=self.metrics['kpi_summary'],151 total_revenue=self.metrics['total_revenue'],152 avg_daily_revenue=self.metrics['avg_daily_revenue'],153 revenue_trend=self.metrics['revenue_trend'],154 total_orders=self.metrics['total_orders'],155 aov=self.metrics['avg_order_value']156 )157 158 def _generate_html_report(self):159 """Generate HTML report"""160 # Use more complex HTML template161 template = Template("""162<!DOCTYPE html>163<html>164<head>165 <style>166 body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }167 .header { background: #2c3e50; color: white; padding: 20px; text-align: center; border-radius: 8px; }168 .kpi-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin: 20px 0; }169 .kpi-card { background: #f8f9fa; border-radius: 8px; padding: 20px; text-align: center; }170 .kpi-value { font-size: 24px; font-weight: bold; }171 .on-track { color: #27ae60; }172 .at-risk { color: #e74c3c; }173 .warning { color: #f39c12; }174 </style>175</head>176<body>177 <div class="header">178 <h1>�� Business Report</h1>179 <p>{{ date }}</p>180 </div>181 <div class="kpi-grid">182 {% for kpi in kpis %}183 <div class="kpi-card">184 <div class="kpi-value {% if kpi.on_track %}on-track{% else %}at-risk{% endif %}">185 {{ kpi.current }}186 </div>187 <div>{{ kpi.name }}</div>188 <div style="font-size: 12px; color: #888;">189 Target: {{ kpi.target }} ({{ kpi.variance }})190 </div>191 </div>192 {% endfor %}193 </div>194</body>195</html>196""")197 198 return template.render(199 date=datetime.now().strftime('%Y-%m-%d'),200 kpis=self.metrics['kpi_summary']201 )202 203 def send_report(self, recipients, subject, content, attachments=None):204 """Send report via email"""205 logger.info(f"Sending report to {recipients}...")206 207 # In production, implement actual email sending208 # For now, simulate success209 logger.info("✅ Report sent successfully")210 return True211 212 def run_pipeline(self, report_type='text', recipients=None):213 """Run complete reporting pipeline"""214 logger.info("=" * 50)215 logger.info("Starting automated reporting pipeline")216 logger.info("=" * 50)217 218 try:219 # Extract220 self.extract_data()221 222 # Transform/Calculate223 self.calculate_metrics()224 225 # Generate226 report = self.generate_report(report_type)227 228 # Deliver229 if recipients:230 self.send_report(231 recipients=recipients,232 subject=f"Business Report - {datetime.now().strftime('%Y-%m-%d')}",233 content=report234 )235 236 logger.info("=" * 50)237 logger.info("Pipeline completed successfully!")238 logger.info("=" * 50)239 240 return report241 242 except Exception as e:243 logger.error(f"Pipeline failed: {e}")244 raise245246# Run the system247config = {248 'db_connection': 'postgresql://user:pass@localhost/db'249}250251system = AutomatedReportingSystem(config)252report = system.run_pipeline(report_type='text')253print(report)7. Thực hành
Build Your Report System
Exercise: Create Automated Report
💡 Xem đáp án
Python
1"""2Exercise Solution: Build a custom automated report3"""45class MyReportSystem:6 def __init__(self):7 self.data = None8 self.metrics = None9 10 def load_data(self, filepath=None):11 """Load data from file or generate sample"""12 import pandas as pd13 import numpy as np14 15 # Sample data16 np.random.seed(42)17 dates = pd.date_range('2024-01-01', periods=30, freq='D')18 19 self.data = pd.DataFrame({20 'date': dates,21 'revenue': np.random.uniform(80000, 120000, 30),22 'orders': np.random.randint(400, 600, 30),23 'new_customers': np.random.randint(50, 100, 30),24 'returns': np.random.randint(10, 30, 30)25 })26 27 print(f"✅ Loaded {len(self.data)} rows")28 return self29 30 def calculate(self):31 """Calculate all metrics"""32 df = self.data33 34 self.metrics = {35 'total_revenue': df['revenue'].sum(),36 'avg_revenue': df['revenue'].mean(),37 'total_orders': df['orders'].sum(),38 'avg_orders': df['orders'].mean(),39 'total_new_customers': df['new_customers'].sum(),40 'return_rate': df['returns'].sum() / df['orders'].sum() * 100,41 'aov': df['revenue'].sum() / df['orders'].sum()42 }43 44 # Week over week comparison45 week1 = df[df['date'] < '2024-01-15']['revenue'].sum()46 week2 = df[df['date'] >= '2024-01-15']['revenue'].sum()47 self.metrics['wow_growth'] = (week2 - week1) / week1 * 10048 49 print("✅ Metrics calculated")50 return self51 52 def generate_report(self):53 """Generate text report"""54 m = self.metrics55 56 report = f"""57╔════════════════════════════════════════════════════════╗58║ MY AUTOMATED BUSINESS REPORT ║59║ {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M')} ║60╚════════════════════════════════════════════════════════╝6162�� REVENUE PERFORMANCE63────────────────────────────────────────────────────────64Total Revenue: ${m['total_revenue']:,.0f}65Average Daily: ${m['avg_revenue']:,.0f}66Week-over-Week: {'+' if m['wow_growth'] > 0 else ''}{m['wow_growth']:.1f}%6768�� ORDER METRICS69────────────────────────────────────────────────────────70Total Orders: {m['total_orders']:,}71Average Daily: {m['avg_orders']:.0f}72Avg Order Value: ${m['aov']:.2f}7374�� CUSTOMER METRICS75────────────────────────────────────────────────────────76New Customers: {m['total_new_customers']:,}77Return Rate: {m['return_rate']:.1f}%7879════════════════════════════════════════════════════════80Auto-generated by MyReportSystem81════════════════════════════════════════════════════════82"""83 return report84 85 def run(self):86 """Run complete pipeline"""87 self.load_data()88 self.calculate()89 report = self.generate_report()90 print(report)91 return report9293# Execute94import pandas as pd # Needed in generate_report95system = MyReportSystem()96system.run()8. Tổng kết
| Component | Tools/Technologies |
|---|---|
| Templates | Jinja2, HTML, Markdown |
| Output Formats | PDF (ReportLab), Excel (openpyxl), HTML |
| Delivery | Email (SMTP), Slack, Teams |
| Scheduling | APScheduler, Cron, GitHub Actions |
| Pipeline | Extract → Transform → Generate → Deliver |
Bài tiếp theo: Portfolio Capstone
