Tablewise
Spreadsheet-grade document conversion to Markdown, with provenance. PDF, Word, Excel and PowerPoint in; clean, citable Markdown out — built for RAG, LLM ingestion and audit trails.
Every output file starts with a header that tells your retrieval layer where the text came from:
---
source: "2026-budget.xlsx"
engine: "excel"
converted: 2026-10-22
---
Three front ends, one decision engine:
tablewiseCLI — one document in, one.mdout. Scripts, agents, batches.- Desktop app (Tkinter) — for people who do not live in a terminal.
- Hosted API —
POST /v1/convert, per-month plans, no install. See PRICING.md.
The problem it solves
Most converters flatten a financial table into something no model can use. A
three-level header (Hotel · 2026 · Real/Budget · Jan..Dec) comes out as 70
columns named Unnamed: 12, formulas vanish, and a percentage becomes 0.8769.
A RAG built on that answers with the wrong number and no way to prove it.
Tablewise routes spreadsheets to an engine that reads the sheet as a sheet:
| Hotel | 2026 | 2026 | 2026 | | | Real | Budget | Last year | | | Jan | Feb | Jan | | ADR | 120.50 | 110 | 105.25 | | Occupancy | 87.69% | | |
- Merged cells and multi-level headers preserved as a real Markdown table.
- Dates as
YYYY-MM-DD, percentages with their format,-0.00never appears. - Hidden sheets kept but labelled
(hidden sheet). - A formula that was never recalculated says
(formula not calculated)instead of an empty cell that a model reads as zero. - Deterministic: no AI, no network, same bytes in → same Markdown out.
Quickstart
pip install -e . # installs the `tablewise` command
tablewise report.pdf # -> ~/md-repo/report.md
tablewise budget.xlsx -o ~/md-repo -n "2026 budget"
tablewise contract.docx --dry-run # print the engine command, do not run it
The desktop app:
python tablewise_app.py
The API:
curl -X POST https://api.tablewise.dev/v1/convert \
-H "Authorization: Bearer $TABLEWISE_API_KEY" \
-F "file=@budget.xlsx"
Why not just MarkItDown / Docling?
They are good engines and Tablewise uses both. The difference is the routing, the table fidelity and the provenance — not a new parser.
| MarkItDown | Docling | Tablewise | |
|---|---|---|---|
| Excel with merged / multi-level header | flattened | partial | read as a sheet |
| Formula never recalculated | empty cell | empty cell | labelled |
| Provenance header on every file | no | no | yes |
| Deterministic, no AI, no network | yes | yes (heavy) | yes |
| Desktop GUI for non-technical users | no | no | yes |
| Hosted API with per-month plans and batch | no | no | yes |
Engine routing
Each extension has exactly one owner:
.xlsx.xlsm.xltx.xltm→motor_excel.py(openpyxl, local, no AI).pdf.docx.pptx→motor_docling.py(Docling, local, no AI).doc.rtf.odt→textutil(macOS) → HTML →motor_docling.py.xls.ppt(Office 97-2003) → LibreOffice →.xlsx/.pptx→motor_excel.py/motor_docling.py- everything else (CSV, HTML, EPUB, …) → MarkItDown
The two own engines are motor_excel.py and motor_docling.py. A format
without an available engine is refused with an explicit message, never silently
degraded.
CLI reference
tablewise DOCUMENT [-o DIR] [-n NAME] [--python PATH] [--markitdown PATH]
[--force] [--dry-run] [-q] [--version]
- Never overwrites an existing
.mdwithout--force. - Exit codes:
0ok ·1the engine produced no file ·2invalid input. --python/TABLEWISE_PYTHON: interpreter that carries docling/openpyxl.--markitdown/TABLEWISE_MARKITDOWN: MarkItDown executable.
Hosted API
The API is the same engine behind HTTP, for teams that cannot install anything:
| Plan | Units / month | Price |
|---|---|---|
| Free | 50 | $0 |
| Starter | 1,000 | $19 |
| Pro | 5,000 | $49 |
| Business | 25,000 | $149 |
One unit = one PDF page, one spreadsheet sheet, one slide, or one document. Overage $0.015/unit. Annual −20%. The full contract (quota, DPA, refunds) is in PRICING.md and on the website.
Development
Runner: pytest, using the engines virtualenv (it carries openpyxl and PyYAML):
.venv/bin/python3 -m pytest -q # 432 tests
.venv/bin/python3 -m pytest server/tests -q # 46 API tests
| File | What it covers |
|---|---|
tests/test_core.py |
tablewise_core.py: extension routing, safe names, engine command (shlex, ~), YAML frontmatter, atomic write, free name |
tests/test_motor_excel.py |
motor_excel.py on synthetic books: merged cells, unevaluated formulas, empty rows/columns, dates, hidden sheets, safety ceiling |
tests/test_cli_motores.py |
engine CLI contract by subprocess (exit code + message, never a traceback) |
tests/test_motor_docling.py |
.doc/.rtf/.odt preconversion to HTML with textutil (no Docling import; skipped off macOS) |
tests/test_preconversion.py |
LibreOffice (soffice): lookup outside PATH and .xls → .xlsx keeping merges (skipped without LibreOffice) |
tests/test_preconversion_errores.py |
preconversion.py with simulated subprocess.run: timeout, tool that cannot start, exit 0 without file, error code, own profile |
tests/test_motor_docling_convertir.py |
motor_docling.convertir with an injected fake Docling: empty output, temp cleanup, .ppt routing, uppercase extensions |
tests/test_entradas_externas.py |
.xls path, malformed array formulas, backslashes in the YAML header, non-UTF-8 output, BOM and CRLF |
tests/test_preconversion_ejecutables_falsos.py |
textutil/LibreOffice boundary with fake executables |
tests/test_app_convertir.py |
App.convert() with fake tkinter and engine: existing .md (cancel / numbered copy / overwrite), failing engine, non-UTF-8 output, partial writes |
tests/test_motor_docling_cli.py |
motor_docling CLI with a fake docling package |
tests/test_red_sintaxis.py |
regression net: every application file is valid Python and the app imports without opening a window |
tests/test_cli.py |
tablewise CLI: same decisions as the app, --dry-run, no overwrite without --force, exit codes |
tests/test_docs.py |
drift net: this README lists every test file and every routed extension |
No test uses real documents.
Limits and security
- Upload limit 25 MB and 2,000 units per request (configurable).
- Engines run as subprocesses with a timeout; a hung converter cannot take the worker down.
- API keys are stored as SHA-256 hashes and shown once.
- No document content is logged. See
web/privacy.htmlandweb/dpa.html. - macOS-only pieces:
textutil(.doc/.rtf/.odt) and the Tkinter app. LibreOffice is needed for.xls/.ppt.
License
MIT. See LICENSE.
Spanish engineering log, kept for continuity: README.es.md.
Metadata
Release files for tablewise 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| tablewise-1.1.0.tar.gz | 62.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tablewise-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 94.9 kB
Release files / tablewise-1.1.0.tar.gz
| Download URL | tablewise-1.1.0.tar.gz |
|---|---|
| Size | 62.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9f65582fa9301a389e8de586acb8c68edea1ac5ffcd9a04fa6f2f97f9535722c
|
|
BLAKE2b-256 checksum How to use checksums |
14c4aa4010243d09297b61ce0b1b6a3058cb6a3e8177ac57537d10d74d15b02c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / tablewise-1.1.0-py3-none-any.whl
| Download URL | tablewise-1.1.0-py3-none-any.whl |
|---|---|
| Size | 32.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2fa615eb7d6d8cf5cadc0c73395dcf21a7d5db8645a7ed7dfebc507f06c67e10
|
|
BLAKE2b-256 checksum How to use checksums |
631785c3000e444b5815777ee5e55d4531abfbf04e925627592518daa863046c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Security for tablewise 1.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.