Skip to content

nabla v2.3.0

Choose a tag to compare

@ryanduguid ryanduguid released this 18 Aug 14:24
· 51 commits to main since this release

Two things this release does that no earlier one could.

The workbook holds the numbers its own formulas produce

An .xlsx stores a formula and the answer Excel last got from it, and nothing keeps the two in step. The build edits the workbook as XML with no formula engine, so every cell downstream of a value it changes kept the answer it had before. Shifting the sample dates forward two years left 3,193 cached cells across 43 of the 50 sheets holding numbers their own formulas no longer produce; nb.IsOccurrenceDateλ alone had 978.

Nobody ever saw one, because Excel recalculates on open and replaces the lot. That is what made it worth finding: the file could be wrong in a way only a second tool could see, and everything that reads an .xlsx without a formula engine, from a diff to a converter to a web preview, reads the cached answer.

tools/refresh_cache.py recalculates the workbook in Excel and saves it. fullCalcOnLoad comes off with the stale values, so the workbook now opens reading correctly without recalculating, and Excel no longer asks to save a file you never edited. Nothing about the functions changes, measured rather than assumed: all 4,506 worksheet formula cells byte-identical, all 130 defined names byte-identical, the Advanced Formula Environment store byte-identical. The file grows by 247 bytes.

It no longer carries a path off the machine that built it

Excel stamps xl/workbook.xml with the directory a file was last saved from, so every release since v1.2.0 published one. The build strips it, the refresh strips it again because Excel puts it back on every save, and it joins the banned tokens so CI fails rather than trusting either step to have run.

What a full audit found

Thirteen readers across the build, the gates, the workbook, the LAMBDA sources, the prose and the CI, with every finding put to a second pass that tried to refute it.

  • nb.ScheduleValuesByItemsλ and nb.ScheduleRatesByItemsλ compared a raw date against a converted one, the defect their siblings had until v2.2.0. The check added then could not see them: each reads its conversion, but only to hand it to the recursive call, so the first row came from the raw argument and every row below it from the converted one. With text period bounds the first returned 0 where the answer is 100, and the second the 20 January rate where the 1 January rate applies.
  • nb.DebtSculptVariableLRVλ's worked example called its sibling and dropped its closing bracket. verify_signatures was added in v2.1.0 for exactly this defect and structurally could not see it: it skipped the text on the EXAMPLES row itself, leaving 43 of the 119 example blocks unread. It now reads all 119.
  • functions.csv disagreed with the workbook it is generated from: nb.Depreciateλ's signature cut off mid-parameter list, 31 descriptions carrying the raw OOXML escape for a line break, two starting with the help table's delimiter.
  • docProps/app.xml described a 49-sheet workbook that has 50.
  • ATTRIBUTION.md still called the depreciation helpers ATO methods, which v2.1.0 retracted everywhere else.
  • Nine comment banners named or described the wrong function, and nb.MaxColsλ told the reader it returns the minimum. nb.CountColsλ published its signature with an empty second argument.

Gates

Six now, four in CI and two local. tools/verify_cache.py is new: it compares all 20,221 cached values against what the formulas produce, refuses to pass on fewer than 15,000 comparisons, and fails when any sheet contributes none. verify_workbook gained the sheet-list and build-path checks and stopped demanding fullCalcOnLoad. verify_previous_names now checks the two functions.csv columns nothing checked. CI dropped a dependency nothing imported, cancels superseded runs, and asks for read-only access.

All six pass, including 152 Excel assertions. Two cached cells moved against v2.2.0, both corrected help text on its own demonstration sheet.

Known and not fixed

  • nb.PeriodStartλ returns a date in the wrong month for month-end anchors: ("31/1/2026", 1, "5/3/2026") gives 2 March where 28 February is the period start. The fix is an EDATE rewrite, which changes results.
  • nb.TimelineOffsetλ divides by zero on a daily or weekly timeline, and nb.Amortiseλ calls it unconditionally. nb.Depreciateλ carries a second copy of the same inference. Which intervals these suites support is a decision, not a repair.
  • The repository still states no licence for its own tooling. ATTRIBUTION.md is correct about the derived workbook and silent about everything else.

Full detail in CHANGELOG.md. nabla.xlsx is attached below.