Skip to content

Spreadsheet number formats are dropped, rendering 7.5% as 0.075 #27

Description

@Michael-WhiteCapData

Summary

Spreadsheet cells carrying a numFmt are rendered from their stored value with the display format dropped. A cell that reads 7.5% in Excel converts to 0.075, and one that reads $1,234.50 converts to 1234.5.

The percent case is the damaging one: it is not a cosmetic loss but a value that is wrong by two orders of magnitude for any consumer reading the Markdown, which for this library is usually an LLM. A conversion-rate column of 0.075 reads as 0.075%, not 7.5%, and nothing in the output signals that a format was dropped.

Dates are handled correctly, which makes the behaviour inconsistent: mm/dd/yyyy renders as 2026-03-15, so a reader could reasonably assume other display formats survive too.

Reproduction

Tested with firecrawl-anydoc==0.1.6 and openpyxl==3.1.5.

import datetime
from pathlib import Path

import anydoc
from openpyxl import Workbook

path = Path("number-formats.xlsx")
workbook = Workbook()
sheet = workbook.active
sheet.title = "Formats"
sheet.append(["kind", "value"])
sheet.append(["percent", 0.075])
sheet.append(["currency", 1234.5])
sheet.append(["date", datetime.date(2026, 3, 15)])
sheet["B2"].number_format = "0.0%"
sheet["B3"].number_format = '"$"#,##0.00'
sheet["B4"].number_format = "mm/dd/yyyy"
workbook.save(path)

print(anydoc.to_markdown(str(path)))

Actual

| kind | value |
| --- | --- |
| percent | 0.075 |
| currency | 1234.5 |
| date | 2026-03-15 |

Expected

The displayed value, as the workbook shows it:

| kind | value |
| --- | --- |
| percent | 7.5% |
| currency | $1,234.50 |
| date | 2026-03-15 |

Cause

format_data in src/formats/sheet/mod.rs switches on calamine's Data enum alone. Dates survive because calamine resolves them to Data::DateTime itself; percent and currency cells arrive as Data::Float and fall through to format_float, which never consults the cell's number format.

calamine 0.36.1 does not appear to expose the numFmt code on its public API, so this likely needs xl/styles.xml read directly — resolving cellXfs[xfId].numFmtId against the built-in format IDs plus any numFmts overrides — which the existing package/XML layer already supports. The built-in IDs alone (9 and 10 for percent, 5-8 and 37-44 for currency and accounting) would cover most real workbooks.

If full format emulation is out of scope, even handling percent alone would remove the wrong-by-100x failure, which is the part that silently corrupts meaning rather than just dropping presentation.

Context

Found while benchmarking anydoc for accuracy across a mixed corpus of real business documents. Text recall was 100% on every file tested and the library was 15-40x faster than the alternative I compared against, so this stood out as the one case where output was confidently wrong rather than merely lossy.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Correctness bug or high-value work, do this nextbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions