@nomad-e/bluma-cli 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +62 -12
  2. package/dist/config/native_tools.json +7 -0
  3. package/dist/config/skills/git-commit/LICENSE.txt +18 -0
  4. package/dist/config/skills/git-commit/SKILL.md +258 -0
  5. package/dist/config/skills/git-commit/references/REFERENCE.md +249 -0
  6. package/dist/config/skills/git-commit/scripts/validate_commit_msg.py +163 -0
  7. package/dist/config/skills/git-pr/LICENSE.txt +18 -0
  8. package/dist/config/skills/git-pr/SKILL.md +293 -0
  9. package/dist/config/skills/git-pr/references/REFERENCE.md +256 -0
  10. package/dist/config/skills/git-pr/scripts/validate_commits.py +112 -0
  11. package/dist/config/skills/pdf/LICENSE.txt +26 -0
  12. package/dist/config/skills/pdf/SKILL.md +327 -0
  13. package/dist/config/skills/pdf/references/FORMS.md +69 -0
  14. package/dist/config/skills/pdf/references/REFERENCE.md +52 -0
  15. package/dist/config/skills/pdf/scripts/create_report.py +59 -0
  16. package/dist/config/skills/pdf/scripts/merge_pdfs.py +39 -0
  17. package/dist/config/skills/skill-creator/LICENSE.txt +26 -0
  18. package/dist/config/skills/skill-creator/SKILL.md +229 -0
  19. package/dist/config/skills/xlsx/LICENSE.txt +18 -0
  20. package/dist/config/skills/xlsx/SKILL.md +298 -0
  21. package/dist/config/skills/xlsx/references/REFERENCE.md +337 -0
  22. package/dist/config/skills/xlsx/scripts/office/__init__.py +2 -0
  23. package/dist/config/skills/xlsx/scripts/office/__pycache__/__init__.cpython-312.pyc +0 -0
  24. package/dist/config/skills/xlsx/scripts/office/__pycache__/pack.cpython-312.pyc +0 -0
  25. package/dist/config/skills/xlsx/scripts/office/__pycache__/soffice.cpython-312.pyc +0 -0
  26. package/dist/config/skills/xlsx/scripts/office/__pycache__/unpack.cpython-312.pyc +0 -0
  27. package/dist/config/skills/xlsx/scripts/office/__pycache__/validate.cpython-312.pyc +0 -0
  28. package/dist/config/skills/xlsx/scripts/office/pack.py +58 -0
  29. package/dist/config/skills/xlsx/scripts/office/soffice.py +180 -0
  30. package/dist/config/skills/xlsx/scripts/office/unpack.py +63 -0
  31. package/dist/config/skills/xlsx/scripts/office/validate.py +122 -0
  32. package/dist/config/skills/xlsx/scripts/recalc.py +143 -0
  33. package/dist/main.js +275 -89
  34. package/package.json +1 -1
  35. package/dist/config/example.bluma-mcp.json.txt +0 -14
  36. package/dist/config/models_config.json +0 -78
  37. package/dist/skills/git-conventional/LICENSE.txt +0 -3
  38. package/dist/skills/git-conventional/SKILL.md +0 -83
  39. package/dist/skills/skill-creator/SKILL.md +0 -495
  40. package/dist/skills/testing/LICENSE.txt +0 -3
  41. package/dist/skills/testing/SKILL.md +0 -114
@@ -0,0 +1,298 @@
1
+ ---
2
+ name: xlsx
3
+ description: >
4
+ Use this skill any time a spreadsheet file is the primary input or output.
5
+ Triggers: open, read, edit, create, fix, or convert .xlsx, .xlsm, .csv,
6
+ or .tsv files — including adding columns, computing formulas, formatting,
7
+ charting, cleaning messy data, restructuring tabular data, or building
8
+ financial models. Also trigger when the user references a spreadsheet by
9
+ name or path and wants something done to it. The deliverable must be a
10
+ spreadsheet file. Do NOT trigger when the primary deliverable is a Word
11
+ document, HTML report, or standalone script.
12
+ license: Proprietary. LICENSE.txt has complete terms
13
+ ---
14
+
15
+ # XLSX — Spreadsheet Creation, Editing & Analysis
16
+
17
+ ## Core Principle
18
+
19
+ > **Formulas, not hardcodes.** Every calculated value MUST be an Excel
20
+ > formula so the spreadsheet stays dynamic. Never compute in Python and
21
+ > paste the result into a cell.
22
+
23
+ ## Output Quality Standards
24
+
25
+ ### Professional Appearance
26
+ - Consistent font throughout (Arial or Calibri, 10-11pt)
27
+ - Headers bold with subtle background fill
28
+ - Column widths auto-fitted to content
29
+ - Number formatting applied to all numeric cells
30
+ - Borders and alignment used consistently
31
+
32
+ ### Zero Formula Errors
33
+ Every deliverable MUST have ZERO formula errors. After writing formulas,
34
+ always run `scripts/recalc.py` and fix any errors before delivering.
35
+
36
+ ### Preserve Existing Templates
37
+ When modifying an existing file, study and EXACTLY match its format,
38
+ style, and conventions. Existing patterns override these guidelines.
39
+
40
+ ## Reading & Analyzing Data
41
+
42
+ ### Quick Analysis with pandas
43
+
44
+ ```python
45
+ import pandas as pd
46
+
47
+ df = pd.read_excel('file.xlsx')
48
+ all_sheets = pd.read_excel('file.xlsx', sheet_name=None)
49
+
50
+ df.head()
51
+ df.info()
52
+ df.describe()
53
+
54
+ df.to_excel('output.xlsx', index=False)
55
+ ```
56
+
57
+ ### Reading with openpyxl (preserves formulas)
58
+
59
+ ```python
60
+ from openpyxl import load_workbook
61
+
62
+ wb = load_workbook('file.xlsx')
63
+ ws = wb.active
64
+
65
+ for row in ws.iter_rows(min_row=1, max_row=5, values_only=True):
66
+ print(row)
67
+
68
+ wb_data = load_workbook('file.xlsx', data_only=True)
69
+ ```
70
+
71
+ **Warning**: Never open with `data_only=True` and save — formulas are
72
+ permanently replaced with cached values.
73
+
74
+ ## Creating New Excel Files
75
+
76
+ ```python
77
+ from openpyxl import Workbook
78
+ from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
79
+ from openpyxl.utils import get_column_letter
80
+
81
+ wb = Workbook()
82
+ ws = wb.active
83
+ ws.title = "Data"
84
+
85
+ header_font = Font(bold=True, size=11, name='Arial')
86
+ header_fill = PatternFill('solid', fgColor='D9E1F2')
87
+ header_align = Alignment(horizontal='center', vertical='center')
88
+
89
+ headers = ['Item', 'Qty', 'Unit Price', 'Total']
90
+ for col, h in enumerate(headers, 1):
91
+ cell = ws.cell(row=1, column=col, value=h)
92
+ cell.font = header_font
93
+ cell.fill = header_fill
94
+ cell.alignment = header_align
95
+
96
+ ws['D2'] = '=B2*C2'
97
+
98
+ for col in range(1, len(headers) + 1):
99
+ ws.column_dimensions[get_column_letter(col)].width = 15
100
+
101
+ wb.save('output.xlsx')
102
+ ```
103
+
104
+ ## Editing Existing Files
105
+
106
+ ```python
107
+ from openpyxl import load_workbook
108
+
109
+ wb = load_workbook('existing.xlsx')
110
+ ws = wb['SheetName']
111
+
112
+ ws['A1'] = 'New Value'
113
+ ws.insert_rows(2)
114
+ ws.delete_cols(3)
115
+
116
+ new_ws = wb.create_sheet('Summary')
117
+ new_ws['A1'] = '=SUM(Data!B2:B100)'
118
+
119
+ wb.save('modified.xlsx')
120
+ ```
121
+
122
+ ## Formula Rules
123
+
124
+ ### Always Use Excel Formulas
125
+
126
+ ```python
127
+ # WRONG — hardcoded calculation
128
+ total = sum(values)
129
+ ws['B10'] = total
130
+
131
+ # CORRECT — Excel formula
132
+ ws['B10'] = '=SUM(B2:B9)'
133
+ ```
134
+
135
+ This applies to ALL calculations: totals, averages, percentages,
136
+ growth rates, ratios, conditional aggregations.
137
+
138
+ ### Common Formula Patterns
139
+
140
+ | Operation | Formula |
141
+ |-----------|---------|
142
+ | Sum | `=SUM(B2:B9)` |
143
+ | Average | `=AVERAGE(B2:B9)` |
144
+ | Count non-empty | `=COUNTA(B2:B9)` |
145
+ | Percentage | `=B2/B$10` |
146
+ | Year-over-year growth | `=(C2-B2)/B2` |
147
+ | Conditional sum | `=SUMIF(A:A,"Category",B:B)` |
148
+ | Lookup | `=VLOOKUP(A2,Data!A:C,3,FALSE)` |
149
+ | If/then | `=IF(B2>0,B2*C2,0)` |
150
+ | Error handling | `=IFERROR(B2/C2,0)` |
151
+
152
+ ### Cross-Sheet References
153
+
154
+ ```python
155
+ ws['A1'] = "=Summary!B5"
156
+ ws['A2'] = "=VLOOKUP(A1,Data!A:C,3,FALSE)"
157
+ ```
158
+
159
+ ## Recalculating Formulas (MANDATORY)
160
+
161
+ After creating or editing formulas with openpyxl, values are NOT
162
+ calculated. You MUST run recalculation:
163
+
164
+ ```bash
165
+ python scripts/recalc.py output.xlsx
166
+ ```
167
+
168
+ The script returns JSON:
169
+
170
+ ```json
171
+ {
172
+ "status": "success",
173
+ "total_errors": 0,
174
+ "total_formulas": 42,
175
+ "sheets_processed": ["Sheet1", "Summary"]
176
+ }
177
+ ```
178
+
179
+ If errors are found:
180
+
181
+ ```json
182
+ {
183
+ "status": "errors_found",
184
+ "total_errors": 3,
185
+ "total_formulas": 42,
186
+ "error_summary": {
187
+ "#REF!": {"count": 2, "locations": ["Sheet1!B5", "Sheet1!C10"]},
188
+ "#DIV/0!": {"count": 1, "locations": ["Summary!D4"]}
189
+ }
190
+ }
191
+ ```
192
+
193
+ **Fix all errors and recalculate again until status is "success".**
194
+
195
+ Common error fixes:
196
+ - `#REF!` — Invalid cell reference; check if rows/columns were deleted
197
+ - `#DIV/0!` — Wrap with `=IFERROR(formula, 0)`
198
+ - `#VALUE!` — Wrong data type; check cell contents
199
+ - `#NAME?` — Typo in function name or missing quotes on strings
200
+ - `#N/A` — VLOOKUP/MATCH found no result; use `=IFERROR()`
201
+
202
+ ## Financial Model Standards
203
+
204
+ ### Color Coding (Industry Standard)
205
+
206
+ | Color | RGB | Usage |
207
+ |-------|-----|-------|
208
+ | Blue text | `0000FF` | Hardcoded inputs and assumptions |
209
+ | Black text | `000000` | All formulas and calculations |
210
+ | Green text | `008000` | Links to other sheets in same workbook |
211
+ | Red text | `FF0000` | External links to other files |
212
+ | Yellow background | `FFFF00` | Key assumptions needing attention |
213
+
214
+ ```python
215
+ from openpyxl.styles import Font, PatternFill
216
+
217
+ input_font = Font(color='0000FF', name='Arial', size=11)
218
+ formula_font = Font(color='000000', name='Arial', size=11)
219
+ crossref_font = Font(color='008000', name='Arial', size=11)
220
+ attention_fill = PatternFill('solid', fgColor='FFFF00')
221
+ ```
222
+
223
+ ### Number Formatting
224
+
225
+ | Data Type | Format Code | Example |
226
+ |-----------|-------------|---------|
227
+ | Currency | `$#,##0;($#,##0);"-"` | $1,500 / ($200) / - |
228
+ | Percentage | `0.0%` | 15.3% |
229
+ | Multiples | `0.0x` | 8.5x |
230
+ | Years | `@` (text) | 2024 (not 2,024) |
231
+ | Large numbers | `$#,##0.0,,"mm"` | $1.5mm |
232
+ | Integers | `#,##0;(#,##0);"-"` | 1,500 / (200) / - |
233
+
234
+ ```python
235
+ ws['B2'].number_format = '$#,##0;($#,##0);"-"'
236
+ ws['C2'].number_format = '0.0%'
237
+ ws['D2'].number_format = '0.0x'
238
+ ```
239
+
240
+ ### Assumptions Placement
241
+
242
+ Place ALL assumptions in dedicated cells with blue text. Never
243
+ hardcode values inside formulas:
244
+
245
+ ```python
246
+ ws['B3'] = 0.05
247
+ ws['B3'].font = input_font
248
+ ws['B3'].number_format = '0.0%'
249
+ ws['B3'].comment = openpyxl.comments.Comment(
250
+ 'Revenue growth assumption — Source: Management guidance FY2025',
251
+ 'BluMa'
252
+ )
253
+
254
+ ws['C5'] = '=C4*(1+$B$3)'
255
+ ws['C5'].font = formula_font
256
+ ```
257
+
258
+ ### Documentation for Hardcoded Values
259
+
260
+ Every hardcoded number needs a source comment:
261
+ - `Source: Company 10-K, FY2024, Page 45, Revenue Note`
262
+ - `Source: Bloomberg Terminal, 2025-08-15, AAPL US Equity`
263
+ - `Source: FactSet, Consensus Estimates, 2025-08-20`
264
+
265
+ ## Verification Checklist
266
+
267
+ Before delivering any xlsx file:
268
+
269
+ 1. Run `scripts/recalc.py` — status must be "success"
270
+ 2. Spot-check 3-5 formulas against expected values
271
+ 3. Verify column mappings (Excel columns are 1-indexed)
272
+ 4. Check row offsets (DataFrame row 5 = Excel row 6 with header)
273
+ 5. Confirm no NaN or None values leaked into cells
274
+ 6. Validate number formatting on all numeric columns
275
+ 7. Ensure all hardcodes have source comments (financial models)
276
+ 8. Test edge cases: zero values, empty cells, negative numbers
277
+
278
+ ## Library Selection Guide
279
+
280
+ | Task | Use |
281
+ |------|-----|
282
+ | Data analysis, filtering, grouping | pandas |
283
+ | Formulas, formatting, styles | openpyxl |
284
+ | Complex formatting + data analysis | Both (pandas to process, openpyxl to format) |
285
+ | Read calculated values (no edit) | openpyxl with `data_only=True` |
286
+ | Very large files (read-only) | openpyxl with `read_only=True` |
287
+
288
+ ## Available References
289
+
290
+ - REFERENCE.md: Advanced financial modeling patterns, chart creation, pivot tables, conditional formatting, data validation, print layout
291
+
292
+ ## Available Scripts
293
+
294
+ - recalc.py: Recalculate all formulas via LibreOffice and scan for errors (MANDATORY after formula changes)
295
+ - office/soffice.py: LibreOffice process management for sandboxed environments (internal)
296
+ - office/pack.py: Compress Office XML files into .xlsx (internal)
297
+ - office/unpack.py: Decompress .xlsx into Office XML structure (internal)
298
+ - office/validate.py: Validate Office XML structure against schemas (internal)
@@ -0,0 +1,337 @@
1
+ # XLSX — Advanced Patterns Reference
2
+
3
+ ## Chart Creation with openpyxl
4
+
5
+ ### Bar Chart
6
+
7
+ ```python
8
+ from openpyxl.chart import BarChart, Reference
9
+
10
+ chart = BarChart()
11
+ chart.type = "col"
12
+ chart.title = "Revenue by Quarter"
13
+ chart.y_axis.title = "Revenue ($mm)"
14
+ chart.x_axis.title = "Quarter"
15
+ chart.style = 10
16
+
17
+ data = Reference(ws, min_col=2, min_row=1, max_col=5, max_row=10)
18
+ cats = Reference(ws, min_col=1, min_row=2, max_row=10)
19
+ chart.add_data(data, titles_from_data=True)
20
+ chart.set_categories(cats)
21
+ chart.shape = 4
22
+
23
+ ws.add_chart(chart, "G2")
24
+ ```
25
+
26
+ ### Line Chart (Time Series)
27
+
28
+ ```python
29
+ from openpyxl.chart import LineChart, Reference
30
+
31
+ chart = LineChart()
32
+ chart.title = "Revenue Trend"
33
+ chart.y_axis.title = "Revenue ($mm)"
34
+ chart.x_axis.title = "Year"
35
+ chart.style = 13
36
+ chart.width = 20
37
+ chart.height = 12
38
+
39
+ data = Reference(ws, min_col=2, min_row=1, max_col=2, max_row=10)
40
+ cats = Reference(ws, min_col=1, min_row=2, max_row=10)
41
+ chart.add_data(data, titles_from_data=True)
42
+ chart.set_categories(cats)
43
+
44
+ series = chart.series[0]
45
+ series.graphicalProperties.line.width = 25000
46
+
47
+ ws.add_chart(chart, "D2")
48
+ ```
49
+
50
+ ### Pie Chart
51
+
52
+ ```python
53
+ from openpyxl.chart import PieChart, Reference
54
+
55
+ chart = PieChart()
56
+ chart.title = "Market Share"
57
+ chart.style = 26
58
+
59
+ data = Reference(ws, min_col=2, min_row=1, max_row=6)
60
+ cats = Reference(ws, min_col=1, min_row=2, max_row=6)
61
+ chart.add_data(data, titles_from_data=True)
62
+ chart.set_categories(cats)
63
+
64
+ ws.add_chart(chart, "D2")
65
+ ```
66
+
67
+ ## Conditional Formatting
68
+
69
+ ### Color Scale (Heatmap)
70
+
71
+ ```python
72
+ from openpyxl.formatting.rule import ColorScaleRule
73
+
74
+ ws.conditional_formatting.add(
75
+ 'B2:B100',
76
+ ColorScaleRule(
77
+ start_type='min', start_color='F8696B',
78
+ mid_type='percentile', mid_value=50, mid_color='FFEB84',
79
+ end_type='max', end_color='63BE7B'
80
+ )
81
+ )
82
+ ```
83
+
84
+ ### Data Bars
85
+
86
+ ```python
87
+ from openpyxl.formatting.rule import DataBarRule
88
+
89
+ ws.conditional_formatting.add(
90
+ 'C2:C100',
91
+ DataBarRule(
92
+ start_type='min', end_type='max',
93
+ color='5B9BD5', showValue=True
94
+ )
95
+ )
96
+ ```
97
+
98
+ ### Highlight Cells by Condition
99
+
100
+ ```python
101
+ from openpyxl.formatting.rule import CellIsRule
102
+ from openpyxl.styles import PatternFill
103
+
104
+ red_fill = PatternFill('solid', fgColor='FFC7CE')
105
+ green_fill = PatternFill('solid', fgColor='C6EFCE')
106
+
107
+ ws.conditional_formatting.add(
108
+ 'D2:D100',
109
+ CellIsRule(operator='lessThan', formula=['0'], fill=red_fill)
110
+ )
111
+ ws.conditional_formatting.add(
112
+ 'D2:D100',
113
+ CellIsRule(operator='greaterThan', formula=['0'], fill=green_fill)
114
+ )
115
+ ```
116
+
117
+ ## Data Validation (Dropdowns)
118
+
119
+ ```python
120
+ from openpyxl.worksheet.datavalidation import DataValidation
121
+
122
+ dv = DataValidation(
123
+ type="list",
124
+ formula1='"Option A,Option B,Option C"',
125
+ allow_blank=True
126
+ )
127
+ dv.error = "Invalid selection"
128
+ dv.errorTitle = "Input Error"
129
+ dv.prompt = "Select from the list"
130
+ dv.promptTitle = "Choose Option"
131
+
132
+ ws.add_data_validation(dv)
133
+ dv.add('E2:E100')
134
+ ```
135
+
136
+ ### Numeric Range Validation
137
+
138
+ ```python
139
+ dv_num = DataValidation(
140
+ type="decimal",
141
+ operator="between",
142
+ formula1=0,
143
+ formula2=1
144
+ )
145
+ dv_num.error = "Value must be between 0% and 100%"
146
+ ws.add_data_validation(dv_num)
147
+ dv_num.add('F2:F100')
148
+ ```
149
+
150
+ ## Pivot-Style Summary Tables
151
+
152
+ openpyxl does not have native pivot tables. Build them manually:
153
+
154
+ ```python
155
+ import pandas as pd
156
+ from openpyxl import load_workbook
157
+ from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
158
+
159
+ df = pd.read_excel('data.xlsx')
160
+ pivot = df.pivot_table(
161
+ index='Category',
162
+ columns='Year',
163
+ values='Revenue',
164
+ aggfunc='sum',
165
+ margins=True,
166
+ margins_name='Total'
167
+ )
168
+
169
+ wb = load_workbook('data.xlsx')
170
+ ws = wb.create_sheet('Pivot')
171
+
172
+ header_font = Font(bold=True, name='Arial', size=11)
173
+ header_fill = PatternFill('solid', fgColor='4472C4')
174
+ header_font_white = Font(bold=True, name='Arial', size=11, color='FFFFFF')
175
+ border = Border(
176
+ bottom=Side(style='thin', color='D9D9D9')
177
+ )
178
+
179
+ ws.cell(row=1, column=1, value='Category').font = header_font_white
180
+ ws['A1'].fill = header_fill
181
+ for j, col_name in enumerate(pivot.columns, 2):
182
+ cell = ws.cell(row=1, column=j, value=str(col_name))
183
+ cell.font = header_font_white
184
+ cell.fill = header_fill
185
+ cell.alignment = Alignment(horizontal='center')
186
+
187
+ for i, (idx, row_data) in enumerate(pivot.iterrows(), 2):
188
+ ws.cell(row=i, column=1, value=idx).font = Font(
189
+ bold=(idx == 'Total'), name='Arial', size=11
190
+ )
191
+ for j, val in enumerate(row_data, 2):
192
+ cell = ws.cell(row=i, column=j, value=val)
193
+ cell.number_format = '$#,##0;($#,##0);"-"'
194
+ cell.border = border
195
+ if idx == 'Total':
196
+ cell.font = Font(bold=True, name='Arial', size=11)
197
+
198
+ wb.save('data.xlsx')
199
+ ```
200
+
201
+ ## Print Layout Configuration
202
+
203
+ ```python
204
+ ws.page_setup.orientation = 'landscape'
205
+ ws.page_setup.paperSize = ws.PAPERSIZE_A4
206
+ ws.page_setup.fitToWidth = 1
207
+ ws.page_setup.fitToHeight = 0
208
+
209
+ ws.sheet_properties.pageSetUpPr.fitToPage = True
210
+
211
+ ws.oddHeader.center.text = "Company Name — Confidential"
212
+ ws.oddFooter.left.text = "Page &P of &N"
213
+ ws.oddFooter.right.text = "&D"
214
+
215
+ ws.print_title_rows = '1:2'
216
+ ws.print_area = 'A1:H50'
217
+
218
+ ws.page_margins = openpyxl.worksheet.page.PageMargins(
219
+ left=0.5, right=0.5, top=0.75, bottom=0.75,
220
+ header=0.3, footer=0.3
221
+ )
222
+ ```
223
+
224
+ ## Freeze Panes and Auto-Filter
225
+
226
+ ```python
227
+ ws.freeze_panes = 'B2'
228
+
229
+ ws.auto_filter.ref = f'A1:{get_column_letter(ws.max_column)}{ws.max_row}'
230
+ ```
231
+
232
+ ## Named Ranges
233
+
234
+ ```python
235
+ from openpyxl.workbook.defined_name import DefinedName
236
+
237
+ ref = f"'{ws.title}'!$B$3"
238
+ defn = DefinedName('GrowthRate', attr_text=ref)
239
+ wb.defined_names.add(defn)
240
+
241
+ ws['C5'] = '=C4*(1+GrowthRate)'
242
+ ```
243
+
244
+ ## Merging Cells and Grouped Headers
245
+
246
+ ```python
247
+ ws.merge_cells('A1:D1')
248
+ ws['A1'] = 'Quarterly Revenue Summary'
249
+ ws['A1'].font = Font(bold=True, size=14, name='Arial')
250
+ ws['A1'].alignment = Alignment(horizontal='center')
251
+
252
+ ws.merge_cells('B2:C2')
253
+ ws['B2'] = 'Q1-Q2'
254
+ ws.merge_cells('D2:E2')
255
+ ws['D2'] = 'Q3-Q4'
256
+ ```
257
+
258
+ ## Working with Large Datasets
259
+
260
+ ### Write-Only Mode (memory efficient)
261
+
262
+ ```python
263
+ wb = Workbook(write_only=True)
264
+ ws = wb.create_sheet()
265
+
266
+ for row_data in generate_rows():
267
+ ws.append(row_data)
268
+
269
+ wb.save('large_output.xlsx')
270
+ ```
271
+
272
+ ### Read-Only Mode
273
+
274
+ ```python
275
+ wb = load_workbook('large_file.xlsx', read_only=True)
276
+ ws = wb.active
277
+
278
+ for row in ws.iter_rows(min_row=2, values_only=True):
279
+ process(row)
280
+
281
+ wb.close()
282
+ ```
283
+
284
+ ## Financial Model Templates
285
+
286
+ ### DCF Valuation Layout
287
+
288
+ ```
289
+ Sheet: Assumptions
290
+ B3: Revenue Growth Rate (blue, input)
291
+ B4: EBITDA Margin (blue, input)
292
+ B5: Tax Rate (blue, input)
293
+ B6: WACC (blue, input)
294
+ B7: Terminal Growth (blue, input)
295
+
296
+ Sheet: Projections
297
+ Row 1: Headers (Year 1, Year 2, ... Year 5)
298
+ Row 3: Revenue = =Prior*(1+Assumptions!$B$3)
299
+ Row 4: EBITDA = =B3*Assumptions!$B$4
300
+ Row 5: D&A (blue, input)
301
+ Row 6: EBIT = =B4-B5
302
+ Row 7: Tax = =B6*Assumptions!$B$5
303
+ Row 8: NOPAT = =B6-B7
304
+ Row 9: CapEx (blue, input)
305
+ Row 10: Change in WC (blue, input)
306
+ Row 11: FCF = =B8+B5-B9-B10
307
+
308
+ Sheet: Valuation
309
+ B3: PV of FCFs = =NPV(Assumptions!$B$6, Projections!B11:F11)
310
+ B4: Terminal Value = =Projections!F11*(1+Assumptions!$B$7)/(Assumptions!$B$6-Assumptions!$B$7)
311
+ B5: PV of Terminal = =B4/(1+Assumptions!$B$6)^5
312
+ B6: Enterprise Value = =B3+B5
313
+ B7: Less: Net Debt (blue, input)
314
+ B8: Equity Value = =B6-B7
315
+ B9: Shares Outstanding (blue, input)
316
+ B10: Implied Price = =B8/B9
317
+ ```
318
+
319
+ ### Sensitivity Table
320
+
321
+ ```python
322
+ base_row = 15
323
+ scenarios = [-0.02, -0.01, 0, 0.01, 0.02]
324
+
325
+ for i, delta in enumerate(scenarios):
326
+ col = i + 2
327
+ ws.cell(row=base_row, column=col, value=base_wacc + delta)
328
+ ws.cell(row=base_row, column=col).number_format = '0.0%'
329
+
330
+ for j, tg_delta in enumerate(scenarios):
331
+ row = base_row + 1 + j
332
+ ws.cell(row=row, column=1, value=base_tg + tg_delta)
333
+ ws.cell(row=row, column=1).number_format = '0.0%'
334
+ ws.cell(row=row, column=col).value = (
335
+ f'=Projections!F11*(1+A{row})/({get_column_letter(col)}{base_row}-A{row})'
336
+ )
337
+ ```
@@ -0,0 +1,2 @@
1
+ # office — Shared utilities for LibreOffice-based document processing.
2
+ # Used internally by xlsx, docx, and pptx skills.
@@ -0,0 +1,58 @@
1
+ """
2
+ Pack an unpacked Office Open XML directory back into an .xlsx/.docx/.pptx file.
3
+
4
+ Office files are ZIP archives containing XML. This module compresses a
5
+ directory structure back into a valid Office file, respecting the required
6
+ entry order ([Content_Types].xml first).
7
+ """
8
+
9
+ import os
10
+ import zipfile
11
+
12
+
13
+ CONTENT_TYPES = '[Content_Types].xml'
14
+
15
+
16
+ def pack(source_dir: str, output_path: str) -> str:
17
+ """
18
+ Compress a directory into an Office Open XML file.
19
+
20
+ The [Content_Types].xml file is written first as required by the
21
+ Office Open XML specification.
22
+
23
+ Args:
24
+ source_dir: Path to the unpacked directory.
25
+ output_path: Destination .xlsx/.docx/.pptx file path.
26
+
27
+ Returns:
28
+ Absolute path to the created file.
29
+ """
30
+ abs_source = os.path.abspath(source_dir)
31
+ abs_output = os.path.abspath(output_path)
32
+
33
+ if not os.path.isdir(abs_source):
34
+ raise FileNotFoundError(f'Source directory not found: {abs_source}')
35
+
36
+ entries = []
37
+ ct_path = None
38
+
39
+ for root, _, files in os.walk(abs_source):
40
+ for fname in files:
41
+ full = os.path.join(root, fname)
42
+ arcname = os.path.relpath(full, abs_source)
43
+
44
+ if arcname == CONTENT_TYPES:
45
+ ct_path = full
46
+ else:
47
+ entries.append((full, arcname))
48
+
49
+ entries.sort(key=lambda x: x[1])
50
+
51
+ with zipfile.ZipFile(abs_output, 'w', zipfile.ZIP_DEFLATED) as zf:
52
+ if ct_path:
53
+ zf.write(ct_path, CONTENT_TYPES)
54
+
55
+ for full_path, arcname in entries:
56
+ zf.write(full_path, arcname)
57
+
58
+ return abs_output