dsh-office-kit 0.2.0 → 0.3.1

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 (197) hide show
  1. package/CAPABILITY.md +147 -68
  2. package/README.md +3 -1
  3. package/assets/skills/excel-live-control/SKILL.md +234 -0
  4. package/assets/skills/excel-live-control/container_tools/live_excel.py +1589 -0
  5. package/assets/skills/excel-live-control/domain_guidance/financial_models.md +86 -0
  6. package/assets/skills/excel-live-control/domain_guidance/healthcare.md +58 -0
  7. package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +53 -0
  8. package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +53 -0
  9. package/assets/skills/excel-live-control/references/office-automation.md +207 -0
  10. package/assets/skills/excel-live-control/references/read_only_qna.md +59 -0
  11. package/assets/skills/excel-live-control/style_guidelines.md +87 -0
  12. package/assets/skills/excel-live-control/workflows/create_workflows.md +78 -0
  13. package/assets/skills/excel-live-control/workflows/edit_workflows.md +72 -0
  14. package/assets/skills/office-documents/SKILL.md +90 -77
  15. package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +86 -0
  16. package/assets/skills/office-documents/ooxml/comments.md +40 -0
  17. package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +66 -0
  18. package/assets/skills/office-documents/ooxml/rels_and_content_types.md +62 -0
  19. package/assets/skills/office-documents/ooxml/tracked_changes.md +53 -0
  20. package/assets/skills/office-documents/references/docx-structure.md +83 -0
  21. package/assets/skills/office-documents/references/template-elicitation.md +49 -0
  22. package/assets/skills/office-documents/references/writing-quality.md +49 -0
  23. package/assets/skills/office-documents/render_docx.py +99 -0
  24. package/assets/skills/office-documents/scripts/a11y_audit.py +201 -0
  25. package/assets/skills/office-documents/scripts/accept_tracked_changes.py +158 -0
  26. package/assets/skills/office-documents/scripts/add_tracked_replacements.py +155 -0
  27. package/assets/skills/office-documents/scripts/append_blocks.py +115 -0
  28. package/assets/skills/office-documents/scripts/apply_template_styles.py +111 -0
  29. package/assets/skills/office-documents/scripts/captions_and_crossrefs.py +109 -0
  30. package/assets/skills/office-documents/scripts/comments_add.py +143 -0
  31. package/assets/skills/office-documents/scripts/comments_apply_patch.py +121 -0
  32. package/assets/skills/office-documents/scripts/comments_extract.py +92 -0
  33. package/assets/skills/office-documents/scripts/comments_strip.py +81 -0
  34. package/assets/skills/office-documents/scripts/content_controls.py +199 -0
  35. package/assets/skills/office-documents/scripts/core_properties.py +131 -0
  36. package/assets/skills/office-documents/scripts/docx_kit.py +594 -0
  37. package/assets/skills/office-documents/scripts/docx_ooxml_patch.py +232 -0
  38. package/assets/skills/office-documents/scripts/docx_table_to_csv.py +106 -0
  39. package/assets/skills/office-documents/scripts/fields_materialize.py +73 -0
  40. package/assets/skills/office-documents/scripts/fields_report.py +66 -0
  41. package/assets/skills/office-documents/scripts/flatten_ref_fields.py +69 -0
  42. package/assets/skills/office-documents/scripts/footnotes_report.py +84 -0
  43. package/assets/skills/office-documents/scripts/google_docs_title_sanitize.py +103 -0
  44. package/assets/skills/office-documents/scripts/heading_audit.py +88 -0
  45. package/assets/skills/office-documents/scripts/images_audit.py +87 -0
  46. package/assets/skills/office-documents/scripts/insert_note.py +133 -0
  47. package/assets/skills/office-documents/scripts/insert_ref_fields.py +97 -0
  48. package/assets/skills/office-documents/scripts/insert_toc.py +91 -0
  49. package/assets/skills/office-documents/scripts/internal_nav.py +153 -0
  50. package/assets/skills/office-documents/scripts/make_fixtures.py +176 -0
  51. package/assets/skills/office-documents/scripts/merge_docx_append.py +246 -0
  52. package/assets/skills/office-documents/scripts/privacy_scrub.py +138 -0
  53. package/assets/skills/office-documents/scripts/redact_docx.py +120 -0
  54. package/assets/skills/office-documents/scripts/render_and_diff.py +108 -0
  55. package/assets/skills/office-documents/scripts/replace_text.py +161 -0
  56. package/assets/skills/office-documents/scripts/section_audit.py +98 -0
  57. package/assets/skills/office-documents/scripts/set_protection.py +99 -0
  58. package/assets/skills/office-documents/scripts/style_lint.py +118 -0
  59. package/assets/skills/office-documents/scripts/style_normalize.py +110 -0
  60. package/assets/skills/office-documents/scripts/table_geometry.py +174 -0
  61. package/assets/skills/office-documents/scripts/watermark_add.py +55 -0
  62. package/assets/skills/office-documents/scripts/watermark_audit_remove.py +74 -0
  63. package/assets/skills/office-documents/scripts/xlsx_to_docx_table.py +124 -0
  64. package/assets/skills/office-documents/tasks/accessibility_a11y.md +45 -0
  65. package/assets/skills/office-documents/tasks/captions_crossrefs.md +49 -0
  66. package/assets/skills/office-documents/tasks/clean_tracked_changes.md +49 -0
  67. package/assets/skills/office-documents/tasks/comments_manage.md +59 -0
  68. package/assets/skills/office-documents/tasks/compare_diff.md +40 -0
  69. package/assets/skills/office-documents/tasks/create_edit.md +107 -0
  70. package/assets/skills/office-documents/tasks/equations.md +59 -0
  71. package/assets/skills/office-documents/tasks/fields_update.md +44 -0
  72. package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +50 -0
  73. package/assets/skills/office-documents/tasks/footnotes_endnotes.md +38 -0
  74. package/assets/skills/office-documents/tasks/forms_content_controls.md +48 -0
  75. package/assets/skills/office-documents/tasks/headings_numbering.md +55 -0
  76. package/assets/skills/office-documents/tasks/images_figures.md +63 -0
  77. package/assets/skills/office-documents/tasks/multi_doc_merge.md +43 -0
  78. package/assets/skills/office-documents/tasks/navigation_internal_links.md +38 -0
  79. package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +45 -0
  80. package/assets/skills/office-documents/tasks/protection_restrict_editing.md +41 -0
  81. package/assets/skills/office-documents/tasks/read_review.md +50 -0
  82. package/assets/skills/office-documents/tasks/redaction_anonymization.md +42 -0
  83. package/assets/skills/office-documents/tasks/sections_layout.md +54 -0
  84. package/assets/skills/office-documents/tasks/style_lint_normalize.md +44 -0
  85. package/assets/skills/office-documents/tasks/tables_spreadsheets.md +64 -0
  86. package/assets/skills/office-documents/tasks/templates_style_packs.md +53 -0
  87. package/assets/skills/office-documents/tasks/toc_workflow.md +39 -0
  88. package/assets/skills/office-documents/tasks/verify_render.md +50 -0
  89. package/assets/skills/office-documents/tasks/watermarks_background.md +35 -0
  90. package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +47 -0
  91. package/assets/skills/office-documents/troubleshooting/run_splitting.md +36 -0
  92. package/assets/skills/office-pdf/SKILL.md +73 -56
  93. package/assets/skills/office-pdf/references/form-fields.md +56 -0
  94. package/assets/skills/office-pdf/references/geometry.md +44 -0
  95. package/assets/skills/office-pdf/references/layout-quality.md +41 -0
  96. package/assets/skills/office-pdf/references/limits.md +58 -0
  97. package/assets/skills/office-pdf/scripts/extract_text.py +172 -0
  98. package/assets/skills/office-pdf/scripts/forms.py +556 -0
  99. package/assets/skills/office-pdf/scripts/metadata.py +161 -0
  100. package/assets/skills/office-pdf/scripts/page_ops.py +198 -0
  101. package/assets/skills/office-pdf/scripts/redact_region.py +347 -0
  102. package/assets/skills/office-pdf/scripts/render_pages.py +207 -0
  103. package/assets/skills/office-pdf/scripts/split_pages.py +155 -0
  104. package/assets/skills/office-pdf/scripts/stamp_overlay.py +199 -0
  105. package/assets/skills/office-pdf/tasks/annotations.md +98 -0
  106. package/assets/skills/office-pdf/tasks/create.md +75 -0
  107. package/assets/skills/office-pdf/tasks/edit-pages.md +71 -0
  108. package/assets/skills/office-pdf/tasks/forms.md +67 -0
  109. package/assets/skills/office-pdf/tasks/read.md +62 -0
  110. package/assets/skills/office-pdf/tasks/redact.md +72 -0
  111. package/assets/skills/office-pdf/tasks/stamp.md +60 -0
  112. package/assets/skills/office-presentations/SKILL.md +141 -87
  113. package/assets/skills/office-presentations/container_tools/add_slides.py +143 -0
  114. package/assets/skills/office-presentations/container_tools/core_properties.py +127 -0
  115. package/assets/skills/office-presentations/container_tools/create_montage.py +133 -0
  116. package/assets/skills/office-presentations/container_tools/inspect_presentation_layout_geometry.py +292 -0
  117. package/assets/skills/office-presentations/container_tools/inspect_presentation_package_integrity.py +164 -0
  118. package/assets/skills/office-presentations/container_tools/native_chart_title_gate.py +151 -0
  119. package/assets/skills/office-presentations/container_tools/native_quantitative_chart_gate.py +206 -0
  120. package/assets/skills/office-presentations/container_tools/native_table_arithmetic_gate.py +219 -0
  121. package/assets/skills/office-presentations/container_tools/ooxml_helpers.py +352 -0
  122. package/assets/skills/office-presentations/container_tools/preview_render.py +382 -0
  123. package/assets/skills/office-presentations/container_tools/render_slides.py +146 -0
  124. package/assets/skills/office-presentations/container_tools/replace_text.py +175 -0
  125. package/assets/skills/office-presentations/container_tools/template_source_fidelity_gate.py +236 -0
  126. package/assets/skills/office-presentations/domain_guidance/finance.md +66 -0
  127. package/assets/skills/office-presentations/recipes/README.md +71 -0
  128. package/assets/skills/office-presentations/recipes/autolayout.md +88 -0
  129. package/assets/skills/office-presentations/recipes/connectors.md +83 -0
  130. package/assets/skills/office-presentations/recipes/images.md +80 -0
  131. package/assets/skills/office-presentations/recipes/master-and-layouts.md +127 -0
  132. package/assets/skills/office-presentations/recipes/native-charts.md +87 -0
  133. package/assets/skills/office-presentations/recipes/rich-text-runs.md +86 -0
  134. package/assets/skills/office-presentations/recipes/speaker-notes.md +65 -0
  135. package/assets/skills/office-presentations/recipes/tables.md +88 -0
  136. package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +71 -0
  137. package/assets/skills/office-presentations/recipes/theme-tokens.md +87 -0
  138. package/assets/skills/office-presentations/references/authoring-workflow.md +65 -0
  139. package/assets/skills/office-presentations/references/finalization.md +63 -0
  140. package/assets/skills/office-presentations/references/native-bullets.md +78 -0
  141. package/assets/skills/office-presentations/references/native-evidence.md +67 -0
  142. package/assets/skills/office-presentations/references/style-guidelines.md +66 -0
  143. package/assets/skills/office-presentations/references/templates.md +76 -0
  144. package/assets/skills/office-presentations/routing/google_slides.md +45 -0
  145. package/assets/skills/office-presentations/tasks/apply-annotations.md +83 -0
  146. package/assets/skills/office-presentations/tasks/charts-tables-notes.md +79 -0
  147. package/assets/skills/office-presentations/tasks/create-deck.md +106 -0
  148. package/assets/skills/office-presentations/tasks/edit-deck.md +93 -0
  149. package/assets/skills/office-presentations/tasks/inspect-and-verify.md +107 -0
  150. package/assets/skills/office-presentations/tasks/template-following.md +132 -0
  151. package/assets/skills/office-presentations/template_following_scripts/check_template_fidelity.py +183 -0
  152. package/assets/skills/office-presentations/template_following_scripts/inspect_template_deck.py +161 -0
  153. package/assets/skills/office-presentations/template_following_scripts/make_contact_sheet.py +118 -0
  154. package/assets/skills/office-presentations/template_following_scripts/prepare_template_starter_deck.py +246 -0
  155. package/assets/skills/office-presentations/template_following_scripts/validate_template_plan.py +192 -0
  156. package/assets/skills/office-spreadsheets/SKILL.md +105 -101
  157. package/assets/skills/office-spreadsheets/container_tools/add_chart.py +243 -0
  158. package/assets/skills/office-spreadsheets/container_tools/add_image.py +114 -0
  159. package/assets/skills/office-spreadsheets/container_tools/append_rows.py +129 -0
  160. package/assets/skills/office-spreadsheets/container_tools/diff_workbooks.py +313 -0
  161. package/assets/skills/office-spreadsheets/container_tools/inspect_workbook.py +405 -0
  162. package/assets/skills/office-spreadsheets/container_tools/recalc_gate.py +287 -0
  163. package/assets/skills/office-spreadsheets/container_tools/set_cells.py +142 -0
  164. package/assets/skills/office-spreadsheets/container_tools/sheet_ops.py +141 -0
  165. package/assets/skills/office-spreadsheets/container_tools/style_cells.py +140 -0
  166. package/assets/skills/office-spreadsheets/recipes/charts.md +66 -0
  167. package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +69 -0
  168. package/assets/skills/office-spreadsheets/recipes/data-tables.md +58 -0
  169. package/assets/skills/office-spreadsheets/recipes/named-ranges.md +41 -0
  170. package/assets/skills/office-spreadsheets/recipes/number-formats.md +72 -0
  171. package/assets/skills/office-spreadsheets/recipes/sparklines.md +43 -0
  172. package/assets/skills/office-spreadsheets/references/charts.md +63 -0
  173. package/assets/skills/office-spreadsheets/references/domain/financial-models.md +40 -0
  174. package/assets/skills/office-spreadsheets/references/domain/healthcare.md +22 -0
  175. package/assets/skills/office-spreadsheets/references/domain/marketing.md +28 -0
  176. package/assets/skills/office-spreadsheets/references/domain/research.md +22 -0
  177. package/assets/skills/office-spreadsheets/references/formula-patterns.md +78 -0
  178. package/assets/skills/office-spreadsheets/references/style-guidelines.md +69 -0
  179. package/assets/skills/office-spreadsheets/references/workbook-structure.md +60 -0
  180. package/assets/skills/office-spreadsheets/routing/google-sheets.md +36 -0
  181. package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +37 -0
  182. package/assets/skills/office-spreadsheets/tasks/annotations.md +51 -0
  183. package/assets/skills/office-spreadsheets/tasks/create-workbook.md +66 -0
  184. package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +38 -0
  185. package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +69 -0
  186. package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +38 -0
  187. package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +57 -0
  188. package/assets/skills/shared/annotation-recipes.md +252 -0
  189. package/assets/skills/shared/check_runtime.py +260 -0
  190. package/assets/skills/shared/common-workflow.md +139 -0
  191. package/assets/skills/shared/scripts/ensure_raster_image.py +187 -0
  192. package/dist/client.js +4 -4
  193. package/dist/client.js.map +1 -1
  194. package/dist/index.js +246 -89
  195. package/package.json +1 -1
  196. package/postinstall.mjs +92 -82
  197. package/python/office_cli.py +213 -254
@@ -0,0 +1,86 @@
1
+ # Financial models
2
+
3
+ Financial modelling has a short list of distinctions that decide whether a sheet is
4
+ right, and a live edit makes each of them visible to a user who is watching.
5
+
6
+ ## The distinctions that matter
7
+
8
+ | Looks similar | Actually different |
9
+ | --- | --- |
10
+ | Revenue (earned) | Collections (cash received) — timing, bad debt, deferred revenue |
11
+ | Payroll expense | Cash paid to employees — accruals, timing, employer costs |
12
+ | Capex | Depreciation / amortisation — cash out once, expense over life |
13
+ | Earnings | Cash flow — working capital, non-cash items, financing |
14
+ | Budget | Forecast — one is a target, the other an expectation; never mix them silently |
15
+
16
+ Also, before typing a number: which fiscal calendar, which horizon (monthly / quarterly /
17
+ annual), and which grain (total company, segment, product)? A number without those three
18
+ is not a model input.
19
+
20
+ Actuals and forecast must be visibly different (a divide, a label, a different fill). The
21
+ reader has to know where history stops.
22
+
23
+ ## Schedules
24
+
25
+ Build a driver schedule per line, then a statement, then the output. Each step is a range
26
+ the user can audit:
27
+
28
+ - **Revenue**: volume × price, with mix if there are segments; recognition timing if
29
+ contracts span periods. Do not model revenue as a straight growth percentage unless the
30
+ user asked for that — it hides the drivers.
31
+ - **Payroll**: headcount, FTE and paid FTE are three different numbers. Prorate hires and
32
+ exits by month; do not divide an annual salary by 12 twice (an annual figure already
33
+ contains the monthly rate); make the commission and employer-cost basis explicit.
34
+ - **Opex**: units × rate, fixed amounts, or one-time costs — say which. A growing opex line
35
+ needs a driver, not a percent of revenue, unless that is the stated policy.
36
+ - **D&A**: cost, residual value, in-service date, useful life, convention. Reconcile gross
37
+ asset and accumulated depreciation roll-forwards to the prior period.
38
+ - **Statements**: retained earnings rolls from opening + net income − dividends; the
39
+ cash-flow statement's ending cash equals the balance sheet's cash. If it does not, there
40
+ is a plug, and a plug is a finding, not a fix.
41
+
42
+ A worked unit check, done in the cells: actual salary cost $60,000 across 10 paid FTE is
43
+ $6,000 per FTE; a forecast of $6,300 per FTE on 11 FTE gives $69,300 against $66,000 for
44
+ the prior per-FTE rate — the difference is rate *and* headcount, and both drivers belong
45
+ on separate rows. If either missing pay or zero FTE is in the source, the implied rate
46
+ cannot be established; mark it unavailable, do not divide by zero and call it 0.
47
+
48
+ ## Refreshes and merges
49
+
50
+ A refresh merges complete periods on the business key (a date that exists in the calendar,
51
+ a product code that exists in the master). Never append a partial period as if it were
52
+ complete, and never let a merge drop a dimension silently — report rows that did not match.
53
+
54
+ ## Variances and audit
55
+
56
+ - Variances need an amount **and** a percent. When the base is zero or missing, the percent
57
+ is unavailable — write `n.a.`, not `0%`.
58
+ - Check totals: parts sum to the whole, schedules tie to statements, the bridge closes to
59
+ zero. Compare to two decimals, including `0.00`; a check that passes at 0.01 is not
60
+ passing.
61
+ - Failed checks: light red fill (`#FFC7CE`) with bold red text (`#9C0006`), neutral for
62
+ passes. Missing or untested data is "unavailable", never "PASS".
63
+
64
+ ## Valuation, if it comes up
65
+
66
+ - Build the method the user asked for. A DCF needs drivers, tax, D&A, capex, working
67
+ capital, unlevered free cash flow, the discounting convention, a terminal value method
68
+ and the enterprise-to-equity bridge — skipping two of these is not a shortcut, it is a
69
+ wrong answer.
70
+ - IRR/XIRR need cash flows with valid signs and dates; a series with no sign change has no
71
+ IRR, and the function returning `#NUM!` is the correct answer to report.
72
+ - Sensitivities the user asks for belong in a native what-if data table, not in a grid of
73
+ hand-typed scenarios that will drift.
74
+
75
+ ## Doing it live
76
+
77
+ - Read the workbook before you model: `info` for tabs and objects, then
78
+ `read_range --formulas` across the rows you are extending. Match the existing anchoring
79
+ (`$C$4` for a rate, `C$9` for a period header) and the existing aggregation style
80
+ (`SUMIFS` over hand-rolled sums).
81
+ - Add one driver row at a time, each with a formula you can point at, and verify after each
82
+ block; a 200-cell scenario grid written blind is the expensive way to fail.
83
+ - If the model has a check tab, extend the checks to cover your new rows. If it has none
84
+ and the model is small, add one labelled verification row next to the output.
85
+ - Never write a hard-coded total when a formula can tie it. And never "fix" an existing
86
+ `#REF!` — report it.
@@ -0,0 +1,58 @@
1
+ # Healthcare
2
+
3
+ Two kinds of workbook live in a health organisation, and they carry different rules:
4
+
5
+ - **Clinical and patient records**: appointments, encounters, observations, results.
6
+ The source of truth is a clinical system; a workbook holds an extract.
7
+ - **Administrative and financial work**: budgets, staffing, throughput, cost per case.
8
+ Money follows `financial_models.md` even when the organisation is a hospital.
9
+
10
+ Most healthcare spreadsheet tasks are the second kind built on top of the first. Keep the
11
+ extract and the analysis visibly separate.
12
+
13
+ ## Handling the raw extract
14
+
15
+ - Put raw data on its own sheet or block, untouched, one table. Everything you compute
16
+ lives *beside* it or in a separate prepared block — never by overwriting a measurement.
17
+ - Descriptive names help the next reader: `Appointments_RawData` beats `Sheet2`;
18
+ `Clinic KPI Dashboard` beats `Sheet5`.
19
+ - Keep the source's definitions with the data: the period it covers, the extract date, the
20
+ source system, the denominator used. Length of stay, readmission rate and utilisation
21
+ are meaningless without them, and definitions differ between systems.
22
+ - Preserve identifiers exactly as supplied (MRN formats vary), and keep dates as dates with
23
+ one format down the column.
24
+ - Units belong in the header (`Weight (kg)`, `Temperature (°C)`, `HbA1c (%)`, `Heart rate
25
+ (bpm)`), not in the cells as text. A column of `"72 bpm"` strings cannot be averaged.
26
+
27
+ ## Building on it
28
+
29
+ - Highlight the fields staff must complete (amber fill) and keep unused placeholders out.
30
+ - If a threshold drives a flag, use one named cell for it (`BMI_threshold`) and reference
31
+ it, so the cut-off is visible and changeable. A number buried in a formula is an
32
+ invisible policy.
33
+ - Flags come only from a stated source or protocol threshold, and keep the severity the
34
+ source gives (normal / elevated / high), with a legend on the sheet. Do not invent
35
+ clinical ranges, do not round a borderline value into a category.
36
+ - A codebook (code → meaning, units, abbreviations) next to the data when the extract uses
37
+ codes, or a legend when it uses abbreviations.
38
+ - Keep a `Corrected value` column or an audit log for corrections. Never edit the original
39
+ measurement in place; the original and the correction are both evidence.
40
+
41
+ ## Consequences of getting it wrong
42
+
43
+ Clinical arithmetic and clinical judgement are different things. Compute what the source
44
+ supports; verify a critical calculation (dosing, risk score, threshold classification)
45
+ against the authoritative definition when one is available, and if it is not available say
46
+ so instead of asserting the number is right. Do not give clinical advice, and do not
47
+ present a screening flag as a diagnosis.
48
+
49
+ ## Doing it live
50
+
51
+ - The user may have the extract open with their own formatting. Read before writing:
52
+ `info` for the sheets and used ranges, `read_range --formulas --display` for the block
53
+ you are extending.
54
+ - Add derived columns to the right of the raw block with a clear header, or to a separate
55
+ prepared area; never insert a column into the middle of an extract that other formulas
56
+ or a pivot already reference.
57
+ - After writing, verify with `read_range --formulas` and check the `errors` array — a
58
+ healthcare sheet with a `#DIV/0!` in a rate column is a report that will be misread.
@@ -0,0 +1,53 @@
1
+ # Marketing and advertising
2
+
3
+ Campaign, funnel, lead, CRM and ad-performance data. The failures here are almost always
4
+ grain, denominator or freshness — not arithmetic.
5
+
6
+ ## Know what the export is
7
+
8
+ - **Extract freshness vs analysis time.** An export pulled Tuesday and analysed Friday is
9
+ Tuesday's data. Record the extract date and the as-of date once, in the input area, and
10
+ do not compare a Friday figure with a Tuesday one as if the difference were performance.
11
+ - **Channel setup.** A campaign may be split across an ad platform, a CRM and a billing
12
+ system. Name which one each figure comes from before combining them.
13
+ - **Grain.** Daily rows are not campaign rows. Impressions are not sessions; clicks are not
14
+ leads; leads are not qualified leads; a pipeline total is not revenue.
15
+ - **Denominators.** CTR uses impressions, CVR uses sessions or clicks depending on the
16
+ platform's definition, CPA uses conversions attributed that period. Carry the platform's
17
+ definition into the label; two vendors' "conversion rate" differ.
18
+
19
+ ## Layout
20
+
21
+ - Name tabs by content and period: `Leads_Q1_2025`, `Pipeline Analysis`, `ROI Dashboard`.
22
+ - Keep the raw import intact on its own sheet; clean in derived columns or a prepared
23
+ area, and record what you excluded or mapped (non-target region, test account,
24
+ renamed source) so the exclusion is visible.
25
+ - One typed time-series date column with one format. Add an ISO helper column only when a
26
+ downstream import needs it, and label it as such.
27
+ - Targets and traffic lights only when the source supplies a target and a direction. No
28
+ invented targets, no green/amber/red on numbers nobody set a goal for.
29
+ - Every dashboard figure traces back to a cell in the prepared data. If a number cannot be
30
+ traced, it does not go on the dashboard.
31
+
32
+ ## Metrics
33
+
34
+ - Define abbreviations where they are used (CPC, CPM, CPA, ROAS, MQL) — a legend on the
35
+ analysis sheet, not a glossary tab nobody opens.
36
+ - Keep the comparison basis visible: week over week, month over month, or the same period
37
+ last year. Mixing bases across one chart is the most common misleading dashboard.
38
+ - Attribution: say which model (last click, first click, linear, platform-reported) and
39
+ keep it consistent within a table. Platform-reported and analytics-reported conversions
40
+ never reconcile exactly; report both rather than forcing a match.
41
+ - Charts only when they make a trend, a comparison or a funnel clearer than a table. A
42
+ chart of six categories is a table with extra steps.
43
+
44
+ ## Doing it live
45
+
46
+ - Read first: `info` (which sheets, which used ranges, whether the user is mid-edit) then
47
+ `read_range --formulas` across the block you will extend.
48
+ - Append new periods as rows below the existing table and extend the table/conditional
49
+ formatting/chart source with it. Do not create a second table beside the first.
50
+ - When a figure must be flagged for refresh (stale extract, missing platform), use the
51
+ amber input fill and a note in the cell beside it, not a colour nobody explained.
52
+ - Verify with `read_range --formulas`: this is where a misaligned denominator shows up as a
53
+ rate that is off by a factor of ten.
@@ -0,0 +1,53 @@
1
+ # Scientific research data
2
+
3
+ The spreadsheet is often the only record of an experiment. Treat the observations as
4
+ irreplaceable: a cleaned value that overwrote a raw measurement is data loss, and a live
5
+ edit is one keystroke away from it.
6
+
7
+ ## Keep the raw table raw
8
+
9
+ - One flat table: one row per observation, one variable per column, one unit per column.
10
+ No merged cells, no multi-row headers, no calculation rows mixed into the data block.
11
+ - Do not overwrite a measurement. Transformations live in separate columns (clearly headed
12
+ `log10(x)`, `normalised`) or in a separate processed block/sheet.
13
+ - Record the cleaning: which rows were excluded and why, which values were imputed, which
14
+ units were converted. A protocol note on the sheet or a short note beside the block.
15
+ - Missing observations stay visibly missing (blank or `n.a.` as the table's convention
16
+ dictates) — never silently zero-filled. Randomised and simulated values are labelled as
17
+ such; they are not observations.
18
+
19
+ ## Presentation tables are a different object
20
+
21
+ When the sheet is a report of the data rather than the data itself:
22
+
23
+ - One type of data per column, units in the header (`Concentration (mg/L)`), never inside
24
+ the cell.
25
+ - Never encode meaning only in colour or bold — add a labelled value column (`Flag:
26
+ excluded`, `Status: control`). A reader with a monochrome printout must still get the
27
+ full information.
28
+ - Notes belong in a notes column, not in cell comments that a co-author will never see.
29
+ - No merged cells in a table you expect anyone to sort or filter.
30
+
31
+ ## Analysis
32
+
33
+ - Prefer a transparent formula to a clever one. Someone has to defend this number in a
34
+ review six months from now.
35
+ - Show the calculation steps: the intermediate columns are the evidence. Hiding them in a
36
+ single formula makes the result unfalsifiable.
37
+ - A circular reference or iterative calculation is off-limits unless the user asks for it
38
+ and documents it. A simulation's random seed and method are part of the result.
39
+ - Distinguish measurement, derived value, flag and assumption — by label, not by font
40
+ colour alone.
41
+ - If the design of the analysis changes (an exclusion added, a normalisation changed), say
42
+ so in the reply. A quiet change to a denominator rewrites the conclusion.
43
+
44
+ ## Doing it live
45
+
46
+ - Before touching anything: `info`, then `read_range --formulas --display` on the block.
47
+ Raw data and report blocks are usually on different sheets; confirm which is which.
48
+ - Append observations as rows at the bottom of the table, extend any table object to cover
49
+ them, and keep the header row exactly as it is.
50
+ - Verify with `read_range --formulas`; the `errors` array catches the `#DIV/0!` and
51
+ `#VALUE!` that appear when a text value lands in a numeric column.
52
+ - Never delete rows from the middle of a data table; a delete shifts every downstream
53
+ formula and every chart range with it.
@@ -0,0 +1,207 @@
1
+ # The COM object model behind a live edit
2
+
3
+ What this skill talks to is not a file. It is an out-of-process COM server that happens
4
+ to own a window: every property read and every write is a remote call across process
5
+ boundaries, and every object you hold is a proxy that re-reads state when you ask it.
6
+ That explains most of the rules below.
7
+
8
+ `container_tools/live_excel.py` wraps the common cases. Read this when the CLI has no verb
9
+ for what the task needs, when a call fails in a way the JSON does not explain, or when you
10
+ are extending the tool.
11
+
12
+ ## Reaching the running instance
13
+
14
+ ```powershell
15
+ $app = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
16
+ ```
17
+
18
+ - This is the only supported way to find a *running* instance. `New-Object -ComObject
19
+ Excel.Application` creates a new, invisible one — never do that for user work.
20
+ - It exists in Windows PowerShell 5.1 (`powershell.exe`). PowerShell 7 (`pwsh`) removed
21
+ the method, which is why the tool shells out to `powershell.exe` and not `pwsh`.
22
+ - With nothing running it raises `0x800401E3 (MK_E_UNAVAILABLE)`. That is the signal to
23
+ ask the user to open the workbook, not to launch anything.
24
+ - WPS Spreadsheets registers both `Ket.Application` and `Excel.Application`, so a
25
+ successful attach to `Excel.Application` does not prove Microsoft Excel is running.
26
+ `Application.Name` and `Application.Version` are worse than useless here — WPS reports
27
+ `Microsoft Excel` / `12.0`. Infer the product from `Application.Path`.
28
+ - The running-object table is per user session and survives the process that created the
29
+ instance. A hidden instance launched for a self-test stays attachable from later calls;
30
+ it stays invisible until something shows it, and it leaks until `Application.Quit()`.
31
+
32
+ ## Addressing
33
+
34
+ `Application` → `Workbooks` → `Workbook` → `Sheets` → `Worksheet` → `Range`.
35
+
36
+ - Collections are **1-based**: `$book.Sheets.Item(1)`. `Item(0)` throws.
37
+ - `$sheet.Cells` is the whole sheet; `$sheet.UsedRange` is the rectangle that has content
38
+ or formatting. `UsedRange` is not trimmed by deleting a value in its last cell; treat
39
+ it as a hint for what to read, never as the boundary of an edit.
40
+ - A1 strings: `A1`, `B4:D9`, `$B$14`, `Sheet1!A1:C9`, `'Build Detail'!E14` (quote sheet
41
+ names containing spaces). `Range.Address($row_absolute, $column_absolute)` prints one
42
+ back, e.g. `$r.Address(0,0)` gives `A1:C3`. Prefer reporting the absolute form the user
43
+ can paste into the Name Box.
44
+ - Sheet names are case-insensitive in .NET-side matching but must match for COM:
45
+ `$book.Sheets.Item('Build')` throws on `build`. Use `Worksheet.Name` for the exact
46
+ spelling.
47
+ - Multi-area ranges (`A1:A5,C1:C5`) exist; the tool refuses them for writes because a
48
+ rectangular `Value2` assignment cannot fill them.
49
+
50
+ ## Cells: what you write and what comes back
51
+
52
+ | Property | Use it for | Reads back |
53
+ | --- | --- | --- |
54
+ | `Range.Value2` | every value: numbers, text, dates (as serials), booleans, arrays | typed scalars / `Object[,]` |
55
+ | `Range.Formula` | formulas in A1 notation; also the way to read them back | `String`, e.g. `=SUM(B2:B9)` |
56
+ | `Range.FormulaR1C1` | fills where you think in offsets | `String` |
57
+ | `Range.Text` | the *displayed* string (after number format) | per-cell only |
58
+
59
+ - **`Value2` is type-preserving and locale-blind.** Assign a `Double` and the cell is a
60
+ number; assign a `String` and it is text — except that the application may *parse* a
61
+ string that looks like a date, a currency amount, or `=1+1`. Writing `"2026-01-31"`
62
+ through `Value2` produced the serial `46053` in a date-formatted cell. If the user meant
63
+ the literal text, store the serial (or the number) and set a number format, or prefix
64
+ the apostrophe; do not rely on the parse.
65
+ - **`$null` empties a cell.** That is the supported way to clear one value inside a block
66
+ you are writing.
67
+ - A multi-cell read returns a 2-D `Object[,]` with **1-based** indices. A single cell
68
+ returns a scalar, so code that always indexes `[1,1]` fails on one-cell ranges.
69
+ - **Errors come back as integers.** A live value of `#DIV/0!` is the Int32 SCODE
70
+ `-2146826281`; `#VALUE!` is `-2146826273`, `#REF!` `-2146826265`, `#NAME?`
71
+ `-2146826259`, `#NUM!` `-2146826252`, `#N/A` `-2146826246`. In WPS,
72
+ `WorksheetFunction.IsError` returns `False` for them and `SpecialCells(16)` throws, so
73
+ the practical test is "is this value an Int32?" — then read `Range.Text`,
74
+ `Range.Formula` and `Range.Address(0,0)` for that cell alone.
75
+ - `Range.Text` is a per-cell property. On a multi-cell range WPS returns `DBNull`, so the
76
+ tool reads it cell by cell (that is why `--display` is capped).
77
+ - **Assigning one formula to a range fills it with relative adjustment.** Setting
78
+ `A1:A3` to `=B1` gives `=B1`, `=B2`, `=B3`. This is the cheap way to fill a column; it
79
+ is also the way to accidentally create a formula you did not intend when the target is
80
+ more than one cell.
81
+ - Dates are serials (`days since 1899-12-30`, the 1900 date system). `DateSerial(y,m,d)`
82
+ gives the number; a `NumberFormat` gives the look.
83
+ - Formulas are text. There is no calculation on write; the value updates when the
84
+ application recalculates, which is why `recalc` exists.
85
+
86
+ ## Number formats
87
+
88
+ `Range.NumberFormat` takes format *codes*, not locales: `#,##0`, `#,##0.00`, `0.0%`,
89
+ `"$"#,##0.00`, `mmm-yy`, `d-mmm`, `@`. Use invariant ASCII codes — the dot is always the
90
+ decimal point inside the code, whatever the machine's `Application.DecimalSeparator`
91
+ says. WPS returns empty strings for `DecimalSeparator`/`ThousandsSeparator`, so do not
92
+ read locale from the app and do not build codes from locale data.
93
+
94
+ - Format is display only. The stored value keeps full precision; never round data to make
95
+ it read right.
96
+ - One format per column. Mixing `#,##0` and `#,##0.00` down a single column is the most
97
+ common visual defect in a live sheet.
98
+ - Percentages carry precision: one decimal for analytical tables, zero for dashboards,
99
+ two when the differences are small. The code encodes that: `0.0%`, `0%`, `0.00%`.
100
+ - `###` in the window means the column is too narrow for the formatted value, not that
101
+ the value is wrong. Widen with `ColumnWidth` (points-ish, default about 8.43) or
102
+ `EntireColumn.AutoFit()`, and check the result with `--display`.
103
+
104
+ ## Colour is BGR
105
+
106
+ `Interior.Color` and `Font.Color` are OLE colour longs written `0xBBGGRR`, while every
107
+ spec in this repo is `#RRGGBB`. Convert explicitly:
108
+
109
+ ```
110
+ ole = red + green * 256 + blue * 65536 # #RRGGBB -> BGR long
111
+ ```
112
+
113
+ `#C00000` becomes `192`; a pure blue fill becomes `16711680`. Setting `ColorIndex = -4142`
114
+ removes a fill (`Interior.Color` then reads back `16777215`). Theme colours and tints are
115
+ not addressable this way: if the workbook's palette matters, use the workbook's own
116
+ existing colours rather than inventing one.
117
+
118
+ ## Font, borders, alignment, layout
119
+
120
+ - `Range.Font`: `Name`, `Size`, `Bold`, `Italic`, `Color`, `Underline`, `Strikethrough`.
121
+ One family across the sheet; the repo default is Helvetica Neue → Helvetica → Arial →
122
+ Aptos, whichever the workbook already uses.
123
+ - `Range.Borders.Item(index)`: 7 left, 8 top, 9 bottom, 10 right; `LineStyle` (1 continuous),
124
+ `Weight` (2 thin), `Color`. `BorderAround(1, 2)` draws a thin outline in one call.
125
+ - `HorizontalAlignment` / `VerticalAlignment` take `xlHAlign*` constants (`-4131` left,
126
+ `-4108` center, `-4152` right) and `xlVAlign*` (`-4160` top, `-4108` center, `-4104`
127
+ bottom). The tool maps the readable names for you.
128
+ - `Range.Merge()`, `Range.UnMerge()`, `Range.WrapText`, `Range.IndentLevel`,
129
+ `EntireColumn.ColumnWidth`, `Tab.Color`, `Rows.Hidden`.
130
+ - `FreezePanes` lives on `Window`, not on the sheet, and did not stick on a hidden
131
+ instance in WPS. The tool deliberately has no freeze-pane verb; if the user needs panes
132
+ frozen, tell them to do it, or do it visibly in a shown window.
133
+ - Whole-sheet `AutoFit` and `Select`/`Activate` are traps: they change the user's view,
134
+ can take seconds on a large sheet, and `ScreenUpdating` does not hide the flicker from a
135
+ user who is looking at the window.
136
+
137
+ ## Objects beside the cells
138
+
139
+ - **Tables**: `$sheet.ListObjects.Add(1, $range, [Type]::Missing, $has_headers)` creates
140
+ a `ListObject`. PowerShell cannot reproduce VBA's empty argument positions — pass
141
+ `[Type]::Missing` for the `Destination` slot. Created tables get localized default
142
+ names (`表1`), so name them. With `has_headers = 0` the application *generates a header
143
+ row above the data*, which shifts the values down one row and makes the table one row
144
+ taller than the range you named. Extend an existing table with `ListObject.Resize`
145
+ rather than adding a second one over the same cells.
146
+ - **Charts**: `$sheet.ChartObjects().Add(left, top, width, height)` → `.Chart` →
147
+ `.SetSourceData($range)` + `.ChartType`. `HasTitle`/`ChartTitle.Text` set the title.
148
+ `ChartType` numbers: column 51, stacked column 52, bar 57, stacked bar 58, line 4, line
149
+ with markers 65, area 1, stacked area 76, pie 5, doughnut -4120, scatter -4169,
150
+ scatter with lines 74. A chart over an empty range renders blank — check the source
151
+ cells before blaming the chart.
152
+ - **Shapes**: `$sheet.Shapes.AddShape(...)`, `AddTextbox`, `AddPicture`. They float over
153
+ cells and are not part of the used range; anchor them to a cell (`TopLeftCell`) so they
154
+ move with the data.
155
+ - **Pivots**: `PivotTables`, `PivotFields`. Read and report them; never rebuild one
156
+ through COM unless the user asked for a new pivot — the field layout is not recoverable
157
+ from a failed attempt.
158
+ - **Names**: `$workbook.Names`, `Range.Name`. Names break when you delete the cells they
159
+ point at (`#REF!` in the name), so a rename or a row delete is a contract change.
160
+ - **Conditional formats**: `Range.FormatConditions`. Appending a condition to a range
161
+ that already has one edits the *existing* set; a "fix" here can silently change rules
162
+ the user depends on. Read first, and keep the range bounded.
163
+ - **Validation**: `Range.Validation` — same rule. Read the existing rule before writing a
164
+ new one.
165
+
166
+ ## Calculation and freshness
167
+
168
+ - `Application.Calculation`: `-4105` automatic, `-4135` manual. A workbook left in manual
169
+ mode shows stale numbers after your edit; `recalc` runs `CalculateFull` across every
170
+ open workbook and reports the mode it found.
171
+ - `CalculateFull` recalculates dependencies, not just the active sheet. `CalculateFullRebuild`
172
+ rebuilds the dependency tree — use it only when a value looks impossible, because it is
173
+ slow on a large model.
174
+ - Iterative calculation (`Application.Iteration`) is off by default. Circular references
175
+ that "worked" for the user are a sign it was on; do not turn it on to make an edit
176
+ succeed.
177
+
178
+ ## Latency, failures, retries
179
+
180
+ - Every property access is an RPC. A trivial call (`info`, three reads) took about 1.2–1.5 s
181
+ on this host; a call that formats 100 cells one at a time can take tens of seconds.
182
+ **Batch**: one `write_range` for a block, one `style_range` for a range, one `set_formula`
183
+ for a column. Do not loop calls per cell.
184
+ - The default timeout in the tool is 180 s. Raising it is legitimate for a big paste;
185
+ lowering it below a second will fail on a busy application.
186
+ - Failures you will meet:
187
+ - `0x800401E3` — nothing is running. Remediate by asking the user to open the workbook.
188
+ - `0x8001010A` (RPC_E_SERVERCALL_RETRYLATER) — the application is busy (a modal dialog,
189
+ a recalculation, a user dragging a window). Read again, and if it persists tell the
190
+ user there is a dialog blocking the app.
191
+ - "Object required" / a stale proxy — usually after the user closed the workbook you
192
+ were holding. Re-attach by name; never reuse the old object.
193
+ - A method that exists in Excel raising "does not contain a method named …" in WPS — the
194
+ WPS object model is a subset. Fall back to a simpler route (formatting via
195
+ `Value2`/`NumberFormat` instead of a specialised helper).
196
+ - **Nothing is transactional.** A call that fails halfway leaves the cells it already
197
+ wrote. The retry protocol is: `read_range` the block, compare with intent, write only
198
+ what is missing or wrong. Replaying the same batch is how a live document gets
199
+ duplicated rows.
200
+
201
+ ## The one rule that outranks the rest
202
+
203
+ A live edit mutates a document the user is looking at and may have unsaved changes in.
204
+ **Read and diff before writing.** `read_range` the target with `--formulas`; note whether
205
+ `info` says the workbook is `saved: false`; then make the smallest write that satisfies the
206
+ request. If the diff shows values you did not expect, stop and ask — the user's unsaved
207
+ work is more important than finishing the task.
@@ -0,0 +1,59 @@
1
+ # Answering questions about a workbook the user has open
2
+
3
+ Read-only work: no writes, no saves, no restyling. The deliverable is an answer with cell
4
+ references the user can check, not a changed document.
5
+
6
+ ## Reading
7
+
8
+ - `info` first: which workbook, which sheets, which used ranges, tables, charts, pivots.
9
+ That tells you where to look and whether the thing the user is asking about even exists.
10
+ - `read_range --formulas` for the cells that matter. You almost always need both: the
11
+ value tells you *what*, the formula tells you *why*. A number with no formula behind it
12
+ is an input the user typed; a formula with no precedent is a number you cannot trust.
13
+ - `read_range --display` when the question is about how something *reads* (`###`, a
14
+ truncated label, a date that shows as a serial). Displayed text follows the number
15
+ format; the value does not.
16
+ - Read the workbook's own labels — row and column headers, period names, "Actual" vs
17
+ "Forecast" markers, units — before interpreting a cell. `D14` is only meaningful when
18
+ you know it is "Q3 advertising spend, USD".
19
+ - The window may be ahead of the disk file. If `info` shows `saved: false`, the numbers
20
+ you read are the user's unsaved state; say so if the answer would differ from the file.
21
+
22
+ ## Answering
23
+
24
+ 1. **Locate the output by its labels**, not by guesswork: find the row/column that names
25
+ the metric and the period the user asked about, then read the intersection.
26
+ 2. **Trace the precedent chain** back from the output to labelled assumptions or raw
27
+ inputs. `read_range --formulas` on each step; stop at cells whose value is typed rather
28
+ than computed. Reporting an intermediate total as the cause is the classic error.
29
+ 3. **Reconcile before asserting.** If a total does not equal the sum of its parts, that is
30
+ the finding — report the gap and the cells, do not average it away.
31
+ 4. **Compute from the underlying values.** A cell showing `1.2` may hold `1.2345`; the
32
+ answer to "what is the margin" uses the stored value, and if the requested precision
33
+ needs the rounded number, say that you are using the displayed value.
34
+ 5. **Rank drivers** by absolute contribution when the question is "why did it move".
35
+ Report the top two or three with their cells and amounts, not a list of everything.
36
+ 6. **Cite cells** in the reply: `` `Summary!D14` shows 1,234 units because `Build!F7:F12`
37
+ sums to that ``. A claim without an address is not verifiable.
38
+ 7. If two sheets disagree, say which one you believe and why (more recent period, source
39
+ data rather than a copy, a formula chain rather than a paste), and do not "fix" either
40
+ without being asked.
41
+
42
+ ## Sensitive reviews
43
+
44
+ Clinical records, payroll and individual compensation, customer-level data: answer at the
45
+ level the user asked for and no lower. Inspect structure, formats, dependency chains and
46
+ formulas as evidence, and do not echo restricted values into the chat unless the user
47
+ needs that exact figure to act. If the question cannot be answered without exposing
48
+ personal data, say what you can answer instead.
49
+
50
+ ## Limits
51
+
52
+ - Do not equate the live window with the saved file, and do not present a read-only
53
+ question as if you had verified the disk copy.
54
+ - If a formula depends on external links, volatile functions (`NOW()`, `RAND()`) or a
55
+ pivot cache that has not been refreshed, say the number is provisional.
56
+ - If the workbook is protect-structured or the sheet is protected, reads still work;
57
+ report the protection rather than working around it.
58
+ - `#REF!`, `#DIV/0!` and friends in the cells are part of the answer, not noise: name the
59
+ cell, the formula and what it broke.
@@ -0,0 +1,87 @@
1
+ # Style guidelines for a live sheet
2
+
3
+ Style is subordinate to content and to the user's own file. Precedence, strongest first:
4
+
5
+ 1. What the user asked for, in this conversation.
6
+ 2. The workbook's existing conventions — fonts, colours, borders, tab colours, number
7
+ formats. Match them; do not impose a house style on someone else's document.
8
+ 3. The domain guidance for the kind of model (`domain_guidance/`).
9
+ 4. The defaults below.
10
+
11
+ Look before you paint: `info` for tab colours and structure, `read_range` for the formats
12
+ a neighbouring block already uses. Restyling a user's sheet because your defaults are
13
+ tidier is a defect, not a service.
14
+
15
+ ## Defaults that are safe anywhere
16
+
17
+ - **Font family**: one per workbook. If it has none, Helvetica Neue → Helvetica → Arial →
18
+ Aptos. **Two sizes**: 10 pt body, 14 pt title, 16 pt ceiling. Never introduce a third
19
+ family or a decorative size.
20
+ - **Alignment**: text left, numbers right, headers centred horizontally and middle
21
+ vertically. Do not centre numbers to look neat; they lose their decimal alignment.
22
+ - **Headers**: bold, one rule under the last header row (`border: "outline"` on the header
23
+ block, or a bottom border on the row). No merged cells in working headers — merge only a
24
+ single spanning title, and prefer Center Across Selection for that.
25
+ - **Inputs vs calculations**: an input cell the user is expected to type into gets a light
26
+ fill (`#FFF2CC` amber) and a thin border; calculated cells keep a plain fill. If the
27
+ whole block is inputs, one header note beats painting every cell.
28
+ - **Gridlines**: a clean output or dashboard sheet hides them (`ActiveWindow.DisplayGridlines
29
+ = $false`, a view setting, not a cell format) and uses thin structural borders instead.
30
+ A working sheet keeps them.
31
+ - **Borders**: outline a block, hairline between header and body. No box around every cell,
32
+ no double lines, no thick decorative frames.
33
+ - **Column widths**: set them once per block (`column_width` in `style_range`), not per
34
+ cell. Widen for the formatted value, then check with `read_range --display` for `###`.
35
+ - **Conditional formatting**: bounded to the data range, one or two rules. Never a whole
36
+ column, never a rule that paints a user input cell.
37
+ - **Freeze panes** are not a styling nicety; the tool cannot set them on a hidden window.
38
+ If scrolling is the complaint, say so and let the user freeze the row.
39
+
40
+ ## Model layouts
41
+
42
+ - Summary above detail, outputs before workings, assumptions grouped in one block rather
43
+ than scattered. A reader should be able to look at the top-left and understand the sheet.
44
+ - One blank row at the top of a new output sheet, the title in an unfilled cell on the
45
+ left, a thin rule under it. No logo, no merged banner, no slogan.
46
+ - Rows of commentary go to the right of the numbers, separated by one blank column; they
47
+ never sit between a label and its value.
48
+ - Helper rows: italic, grouped under the block they serve, collapsed if the workbook
49
+ already uses grouping. Helper columns live outside the printed range or on a separate
50
+ helper sheet.
51
+ - A case or scenario selector is a visible label plus a validated cell: `Case Selected:`
52
+ and the value on display. Do not hide the control in a defined name.
53
+ - Section bands (a filled row spanning the table) get one outline around the whole
54
+ section, not per row.
55
+ - Dark header fill with white text, or plain text with a rule. Pick one per workbook.
56
+
57
+ ## Finance conventions
58
+
59
+ These are long-standing reader expectations in financial models; a reviewer notices when
60
+ they are violated.
61
+
62
+ - **Colour encodes provenance.** Blue font for editable hard-coded inputs, black for
63
+ formulas on the same sheet, green for links to another sheet, red for links to an
64
+ external workbook. Results on an output view stay black or the brand dark colour.
65
+ - **Number formats**: `#,##0` for amounts, `#,##0.00` for per-share and price data,
66
+ `0.0%` for rates, `( )` for negatives and `-` for zero in accounting views
67
+ (`#,##0;(#,##0);"-"`). Currency symbol on the first monetary row and on totals, not on
68
+ every row.
69
+ - **Indent formats** (` @` for text labels, `_(`-style spacing) map a hierarchy without
70
+ empty columns.
71
+ - **Totals** sit flush left in the label column, bold, with the rule above them. A total
72
+ that is a hard number rather than a formula is a finding, not a style choice.
73
+ - **Period labels** follow `mmm-yy`, `d-mmm`, `yyyy`, `Q#:YY`, `FY:YY` as the workbook
74
+ already does. A date column has one format down its whole length.
75
+ - **Actuals vs forecast**: the change of basis is visible — a divide, a different fill, or
76
+ a label — and never inferred from a colour alone.
77
+ - A model that needs a gutter between the row labels and the numbers gets columns A:B
78
+ reserved for it. Do not retrofit a gutter onto a sheet that does not have one.
79
+
80
+ ## Warning colour, used sparingly
81
+
82
+ - Input that must be refreshed: amber fill (`#FFF2CC`), normal font.
83
+ - Critical error or failed check: light red fill (`#FFC7CE`) plus bold red text
84
+ (`#9C0006`).
85
+ - Passing checks: neutral. A green wash over every PASS makes the one red row invisible.
86
+ - Never encode a value only in colour — a reader with a monochrome printout, or a colour
87
+ vision deficiency, must still see the meaning. Put the flag in a cell.