1 hdr_style(ws, row: int, bg: str = CLR_HEADER_BLUE, bold=True, fg='FFFFFF'):
2 for cell in ws[row]:
3 [Link] = Font(bold=bold, color=fg, size=10)
4 [Link] = PatternFill(start_color=bg, end_color=bg, fill_type='solid')
5 [Link] = Alignment(horizontal='left', vertical='center', wrap_text=True)
6
7
8 def _auto_width(ws, min_w=10, max_w=80):
9 for col in [Link]:
10 col_letter = get_column_letter(col[0].column)
11 best = min_w
12 for cell in col:
13 if [Link]:
14 val_len = len(str([Link]))
15 if val_len > best:
16 best = val_len
17 ws.column_dimensions[col_letter].width = min(best + 2, max_w)
18
19
20 def _alt_row(ws, row: int, ncols: int):
21 if row % 2 == 0:
22 for c in range(1, ncols + 1):
23 [Link](row=row, column=c).fill = PatternFill(
24 start_color=CLR_ROW_ALT, end_color=CLR_ROW_ALT, fill_type='solid')
25
26
27 def _status_fill(status: str) -> PatternFill:
28 s = str(status).lower()
29 if 'success' in s or 'auto' in s or 'created' in s:
30 return PatternFill(start_color=CLR_SUCCESS, end_color=CLR_SUCCESS,
fill_type='solid')
31 if 'partial' in s or 'manual' in s or 'simulated' in s:
32 return PatternFill(start_color=CLR_PARTIAL, end_color=CLR_PARTIAL,
fill_type='solid')
33 return PatternFill(start_color=CLR_FAILED, end_color=CLR_FAILED, fill_type='solid')
34
35
36 class ReportGenerator:
37 def __init__(self, output_dir: Path, logger=None):
38 self.output_dir = output_dir
39 [Link] = logger
40 self.output_dir.mkdir(exist_ok=True)
41
42 def _log(self, level, message):
43 if [Link]:
44 getattr([Link], level)(message)
45
46 # ──────────────────────────── Public API ────────────────────────────────
47
48 def generate_all_reports(self, results: Dict, metadata: Dict, session_id: str) ->
Dict:
49 self._log('info', "GENERATING EXCEL REPORTS")
50 report_files = {}
51
52 try:
53 fn = self._gen_conversion_summary(results, metadata, session_id)
54 report_files['conversion_summary'] = fn
55 self._log('info', f" ✓ {fn}")
56 except Exception as e:
57 self._log('error', f" ✗ Summary report: {e}")
58
59 try:
60 fn = self._gen_metadata_export(metadata, session_id)
61 report_files['metadata_export'] = fn
62 self._log('info', f" ✓ {fn}")
63 except Exception as e:
64 self._log('error', f" ✗ Metadata report: {e}")
65
66 try:
67 fn = self._gen_manual_steps(results, session_id)
68 report_files['manual_steps'] = fn
69 self._log('info', f" ✓ {fn}")
70 except Exception as e:
71 self._log('error', f" ✗ Manual steps report: {e}")
72
73 try:
74 fn = self._gen_dax_report(results, metadata, session_id)
75 report_files['dax_formulas'] = fn
76 self._log('info', f" ✓ {fn}")
77 except Exception as e:
78 self._log('error', f" ✗ DAX report: {e}")
79
80 try:
81 fn = self._gen_field_catalog(metadata, session_id)
82 report_files['field_catalog'] = fn
83 self._log('info', f" ✓ {fn}")
84 except Exception as e:
85 self._log('error', f" ✗ Field catalog: {e}")
86
87 self._log('info', f"Generated {len(report_files)} reports")
88 return report_files
89
90 # ────────────────────── Report 1: Conversion Summary ────────────────────
91
92 def _gen_conversion_summary(self, results: Dict, metadata: Dict, session_id: str) ->
str:
93 fname = f"conversion_summary_{session_id}.xlsx"
94 fpath = self.output_dir / fname
95 wb = Workbook()
96 [Link]([Link])
97
98 self._summary_overview(wb.create_sheet("Overview"), results, metadata,
session_id)
99 self._summary_object_details(wb.create_sheet("All Objects"), results)
100 self._summary_pbi_objects(wb.create_sheet("PBI Objects Created"), results)
101 self._summary_connection_map(wb.create_sheet("Connection Mapping"), metadata)
102 self._summary_successful(wb.create_sheet("Successful"), results)
103 self._summary_partial(wb.create_sheet("Partial"), results)
104 self._summary_failed(wb.create_sheet("Failed"), results)
105
106 [Link](fpath)
107 return fname
108
109 def _summary_overview(self, ws, results, metadata, session_id):
110 stats = [Link]('statistics', {})
111 total = [Link]('total', 0)
112 s = [Link]('successful', 0)
113 p = [Link]('partial', 0)
114 f = [Link]('failed', 0)
115 rate = [Link]('success_rate', 0)
116
117 [Link] = "Overview"
118 ws['A1'] = 'TABLEAU → POWER BI CONVERSION SUMMARY'
119 ws['A1'].font = Font(size=14, bold=True, color='FFFFFF')
120 ws['A1'].fill = PatternFill(start_color=CLR_HEADER_BLUE,
end_color=CLR_HEADER_BLUE, fill_type='solid')
121 ws.merge_cells('A1:D1')
122
123 rows = [
124 ['SESSION ID', session_id],
125 ['Run Date', [Link]().strftime('%Y-%m-%d %H:%M:%S')],
126 ['', ''],
127 ['── CONVERSION STATISTICS ──', ''],
128 ['Total Objects', total],
129 ['✓ Successful', s],
130 ['◑ Partial', p],
131 ['✗ Failed', f],
132 ['Automation Rate', f"{rate:.1f}%"],
133 ['', ''],
134 ['── OBJECT BREAKDOWN (metadata) ──', ''],
135 ['Workbooks', len([Link]('workbooks', []))],
136 ['Datasources', len([Link]('datasources', []))],
137 ['Views', len([Link]('views', []))],
138 ['', ''],
139 ['── METADATA TOTALS ──', ''],
140 ['Total Worksheets', [Link]('summary', {}).get('total_worksheets', 0)],
141 ['Total Dashboards', [Link]('summary', {}).get('total_dashboards', 0)],
142 ['Total Calculated Fields', [Link]('summary',
{}).get('total_calculations', 0)],
143 ['Total Columns/Fields', [Link]('summary', {}).get('total_columns',
0)],
144 ['Total Connections', [Link]('summary', {}).get('total_connections',
0)],
145 ['Total Parameters', [Link]('summary', {}).get('total_parameters', 0)],
146 ['', ''],
147 ['── POWER BI OBJECTS CREATED ──', ''],
148 ['Datasets Created', sum(1 for d in [Link]('conversion_details', [])
149 if [Link]('result', {}).get('datasets_created', 0) >
0)],
150 ['Reports Created', sum(1 for d in [Link]('conversion_details', [])
151 if [Link]('result', {}).get('reports_created', 0) >
0)],
152 ['Auto-Converted Measures', sum(
153 len([c for c in [Link]('result', {}).get('converted_components', [])
154 if [Link]('type') == 'measure'])
155 for d in [Link]('conversion_details', []))],
156 ['Manual Measures Needed', sum(
157 len([c for c in [Link]('result', {}).get('unconverted_components', [])
158 if [Link]('type') == 'measure'])
159 for d in [Link]('conversion_details', []))],
160 ]
161
162 for i, row_data in enumerate(rows, start=2):
163 [Link](row=i, column=1, value=row_data[0]).font = Font(bold=True, size=10)
164 [Link](row=i, column=2, value=row_data[1]).font = Font(size=10)
165
166 ws.column_dimensions['A'].width = 35
167 ws.column_dimensions['B'].width = 30
168
169 def _summary_object_details(self, ws, results):
170 headers = ['Object Name', 'Type', 'Status', 'Conversion Rate',
171 'Components Auto', 'Components Manual',
172 'PBI Objects', 'Datasets Created', 'Measures Created', 'Reports
Created']
173 [Link](headers)
174 _hdr_style(ws, 1)
175 for i, det in enumerate([Link]('conversion_details', []), start=2):
176 r = [Link]('result', {})
177 auto = len([c for c in [Link]('converted_components', []) if [Link]('type') !=
'dataset'])
178 manual = len([Link]('unconverted_components', []))
179 row = [
180 [Link]('object', ''),
181 [Link]('type', '').upper(),
182 [Link]('status', '').upper(),
183 f"{[Link]('conversion_rate', 0):.1f}%",
184 auto,
185 manual,
186 len([Link]('powerbi_objects', [])),
187 [Link]('datasets_created', 0),
188 [Link]('measures_created', 0),
189 [Link]('reports_created', 0),
190 ]
191 [Link](row)
192 status_cell = [Link](row=i, column=3)
193 status_cell.fill = _status_fill([Link]('status', ''))
194 _alt_row(ws, i, len(headers))
195 _auto_width(ws)
196
197 def _summary_pbi_objects(self, ws, results):
198 headers = ['Tableau Object', 'Type', 'PBI Object Name', 'PBI Object Type',
199 'PBI Object ID', 'Tables/Pages', 'Columns', 'Status']
200 [Link](headers)
201 _hdr_style(ws, 1)
202 row_idx = 2
203 for det in [Link]('conversion_details', []):
204 obj_name = [Link]('object', '')
205 obj_type = [Link]('type', '')
206 for pbi in [Link]('result', {}).get('powerbi_objects', []):
207 [Link]([
208 obj_name,
209 obj_type.upper(),
210 [Link]('name', ''),
211 [Link]('type', '').upper(),
212 [Link]('id', ''),
213 [Link]('table_count', [Link]('pages_needed', '')),
214 [Link]('column_count', ''),
215 [Link]('status', ''),
216 ])
217 [Link](row=row_idx, column=8).fill = _status_fill([Link]('status', ''))
218 row_idx += 1
219 _auto_width(ws)
220
221 def _summary_connection_map(self, ws, metadata):
222 headers = ['Workbook / Datasource', 'Tableau Connection Type', 'Power BI
Equivalent',
223 'Server', 'Database', 'Port', 'Schema', 'Action Required']
224 [Link](headers)
225 _hdr_style(ws, 1, CLR_HEADER_PURPLE)
226 row_idx = 2