Builds one- and two-variable sensitivity (Data Table) analysis on a model output in a .xlsx with live formulas and a recalc check, then delivers the workbook.
Library skill — the default version is maintained in GitHub; edits you make live in your own clone.
Use when a strategy or finance model has a single key output and you want to show how that output responds as one or two drivers vary across a range. Typical drivers are price, volume, discount rate, growth, churn, or cost per unit. This skill BUILDS a downloadable .xlsx from scratch in the Claude app (openpyxl plus numpy), with live formulas and a recalc-and-verify pass. It is not the Claude for Excel add-in.
A single Sensitivity tab that attaches to an existing model. It references the model's input cells and output cell, lays out one-variable and two-variable grids, and color-scales the result block. If the source model lives on another sheet, the grid links to it with cross-sheet references so it stays live.
Model!B40 holding NPV) and the one or two input cells that drive it (for example Model!B6 discount rate, Model!B7 growth).{=TABLE(r,c)} array (see spec).Pick a layout direction. For inputs running DOWN a column:
- B3: the output formula, referencing the live output: =Model!B40. This is the corner cell.
- A4:A10: the input values you sweep (blue inputs), for example 6%, 8%, 10%, 12%, 14%.
- B4:B10: the Data Table results column.
- Select A3:B10, then Data > What-If Analysis > Data Table, leaving Row input cell blank and setting Column input cell to Model!B6. Excel writes {=TABLE(,Model!B6)} into B4:B10.
- openpyxl can write the array formula string =TABLE(,Model!B6) into the block and the =Model!B40 corner, but the values only populate after recalculation in a real engine.
Code-computed fallback for the same block (recommended when generating via code), written to a labeled matrix below, say starting A14:
- A14: label "Discount rate". B14: "NPV (computed)".
- For each input value in A15:A21, Python sets Model!B6 to that value, recomputes the model logic in numpy, and writes the resulting NPV as a plain value in column B.
- Keep both: the native Data Table proves it is live; the computed matrix guarantees populated numbers even before the user opens Excel.
E3: =Model!B40 (the live output).E4:E10: row-input values DOWN the left edge (first driver, for example growth 0%, 1%, ... 6%).F3:L3: column-input values ACROSS the top row (second driver, for example discount rate 6% ... 14%).E3:L10, Data > What-If Analysis > Data Table, Row input cell = Model!B7 (the across driver), Column input cell = Model!B6 (the down driver). Excel fills F4:L10 with {=TABLE(Model!B7,Model!B6)}.ws["B3"] = "=Model!B40". Do not paste the current numeric value of the output.=TABLE(...) text, but a real engine must recalculate to fill the block; treat the array as a placeholder until the recalc step runs.ws.cell(row=r, column=c, value=float(npv))) and keep them visually distinct from the live block with a header note such as "computed snapshot".openpyxl.formatting.rule.ColorScaleRule over the result block, mapping min to one color and max to another through a midpoint.=Model!B40.=Model!B40, never the typed base-case number.0.0%, currency outputs as #,##0, ratios as 0.00. Axis values match the unit of the input cell they replace.=(B3=Model!B40) returns TRUE.=(<center cell>=Model!B40) TRUE.Save with openpyxl, then recalc headless (for example libreoffice --headless --convert-to xlsx) so =TABLE(...) and cross-sheet links populate. Reload and scan every cell for #REF!, #DIV/0!, #VALUE!, #NAME?. Confirm the three checks return TRUE. If any error or FALSE appears, fix the reference or range and loop until clean, then deliver.
Hypothetical model: Model!B40 = project NPV, driven by Model!B6 discount rate (base 10%) and Model!B7 revenue growth (base 3%). One-variable block sweeps discount rate 6% to 14% by 2%, giving NPV of (for example) 1,210 / 980 / 760 / 560 / 380 in hypothetical units. Two-variable block crosses growth 0% to 6% against discount rate 6% to 14%; the center cell at 3% growth and 10% discount equals Model!B40 of 760, confirming the base case sits in the middle of the grid.