Metadata-Version: 2.4
Name: tablewise
Version: 1.1.1
Summary: Spreadsheet-grade document to Markdown with provenance, ready for RAG and LLM ingestion
Author: Luis Francisco Palomares Gamarra
License: MIT License
        
        Copyright (c) 2026 Luis Francisco Palomares Gamarra
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://tablewise.dev
Project-URL: Documentation, https://tablewise.dev/docs
Project-URL: Repository, https://github.com/lfpalomaresg/tablewise
Project-URL: Changelog, https://github.com/lfpalomaresg/tablewise/blob/main/CHANGELOG.md
Keywords: markdown,rag,pdf,docx,xlsx,markitdown,docling
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Environment :: MacOS X
Classifier: Environment :: Win32 (MS Windows)
Classifier: Environment :: X11 Applications
Classifier: Topic :: Text Processing :: Markup :: Markdown
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: excel
Requires-Dist: openpyxl>=3.1; extra == "excel"
Provides-Extra: docling
Requires-Dist: docling; extra == "docling"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: openpyxl>=3.1; extra == "dev"
Requires-Dist: PyYAML>=6; extra == "dev"
Dynamic: license-file

# 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:

```markdown
---
source: "2026-budget.xlsx"
engine: "excel"
converted: 2026-10-22
---
```

Three front ends, one decision engine:

- **`tablewise` CLI** — one document in, one `.md` out. 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](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.00` never 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

```bash
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:

```bash
python tablewise_app.py
```

The API:

```bash
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 `.md` without `--force`.
- Exit codes: `0` ok · `1` the engine produced no file · `2` invalid 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](PRICING.md) and on the website.

## Development

Runner: `pytest`, using the engines virtualenv (it carries openpyxl and PyYAML):

```bash
.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.html` and `web/dpa.html`.
- macOS-only pieces: `textutil` (`.doc`/`.rtf`/`.odt`) and the Tkinter app.
  LibreOffice is needed for `.xls`/`.ppt`.

## License

MIT. See [LICENSE](LICENSE).

---

Spanish engineering log, kept for continuity: [README.es.md](README.es.md).
