{"record":{"id":"26ad65561a407aac","repo":"docling-project/docling","slug":"could-not-load-box-note-document-with-hash-self-d","errorCode":null,"errorMessage":"Could not load Box Note document with hash {self.document_hash}.","messagePattern":"Could not load Box Note document with hash (.+?)\\.","errorType":"exception","errorClass":"DocumentLoadError","httpStatus":null,"severity":"error","filePath":"docling/backend/boxnote_backend.py","lineNumber":64,"sourceCode":"    \"\"\"\n\n    @override\n    def __init__(self, in_doc: InputDocument, path_or_stream: BytesIO | Path):\n        super().__init__(in_doc, path_or_stream)\n\n        self.data: dict[str, Any] = {}\n        try:\n            raw = \"\"\n            if isinstance(self.path_or_stream, BytesIO):\n                raw = self.path_or_stream.getvalue().decode(\"utf-8\")\n            elif isinstance(self.path_or_stream, Path):\n                raw = self.path_or_stream.read_text(encoding=\"utf-8\")\n            if raw.strip():\n                loaded = json.loads(raw)\n                if isinstance(loaded, dict):\n                    self.data = loaded\n        except (json.JSONDecodeError, UnicodeDecodeError, OSError) as e:\n            raise DocumentLoadError(\n                f\"Could not load Box Note document with hash {self.document_hash}.\"\n            ) from e\n\n        if \"atext\" in self.data and not self.is_valid():\n            raise DocumentLoadError(\n                \"Legacy Box Notes (the pre-August-2022 atext/pool format) are not \"\n                \"supported yet; only the current Box Note format can be converted.\"\n            )\n\n    @override\n    def is_valid(self) -> bool:\n        return isinstance(self.data.get(\"doc\"), dict)\n\n    @classmethod\n    @override\n    def supports_pagination(cls) -> bool:\n        return False\n","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/backend/boxnote_backend.py#L46-L82","documentation":"Raised by the benchmark's post-conversion assertion in _run_benchmark() after converting a generated single-sheet workbook. The script builds exactly one worksheet whose merged ranges should be parsed by Docling's XLSX pipeline into exactly one TableModel, and treats any other table count as a failure of the assumption being benchmarked. It fires when DocumentConverter.convert() on the temporary .xlsx yields zero tables (conversion produced no table structure) or more than one (the sheet was split into multiple tables, e.g. per merged range or per region).","triggerScenarios":"Calling converter.convert(workbook_path).document on the generated workbook and inspecting len(document.tables) after a Docling upgrade that changes XLSX table segmentation (one table per worksheet vs. one per merged range/row group); an XLSX backend or openpyxl version change that stops emitting the sheet as a single table; the file being misdetected as a non-XLSX format so tables is empty; passing merge_count values that trigger different backend code paths (e.g. very large sheets hitting a splitting heuristic).","commonSituations":"Running the perf script on a Docling branch/PR that changes MsPowerpointDocumentBackend/XlsxDocumentBackend table emission; benchmarking after a dependency bump (docling-core, openpyxl, python-docx stack) where the XLSX backend's tables-per-sheet contract changed; running the script against an installed docling that differs from the checkout; adding multi-sheet or differently-shaped workbooks to the benchmark without updating the assertion.","solutions":["Print or inspect document.tables (count, origins, and each table's data.grid) before the raise to see whether the sheet was split or dropped entirely — that tells you which side of the contract changed.","If you intentionally changed the XLSX backend's segmentation (e.g. one table per merged range), update the benchmark's expectation: replace the strict `len(document.tables) != 1` check with the new expected count, or assert against the sum of all tables' rows.","If tables is empty, verify the installed docling matches this checkout (`pip show docling`, run via `uv run` from the repo) and that InputFormat.XLSX conversion isn't being skipped or raising a swallowed error; check converter.convert() status/errors on the ConversionResult.","Pin/upgrade the docling version whose XLSX backend is known to emit one table per worksheet for this workbook shape, since the assertion encodes that version's behavior."],"exampleFix":"// before (perfs/xlsx_merged_cells.py:148-152)\nif len(document.tables) != 1:\n    raise RuntimeError(\n        f\"expected one table for {merge_count} merges, got {len(document.tables)}\"\n    )\ntable = document.tables[0]\n\n// after - tolerate a backend that splits the sheet, assert on totals\ntable = document.tables[0] if document.tables else None\nif table is None:\n    raise RuntimeError(f\"no tables produced for {merge_count} merges\")\nexpected_rows = merge_count * len(document.tables)\nif sum(t.data.num_rows for t in document.tables) != expected_rows:\n    raise RuntimeError(\n        f\"expected {expected_rows} total rows for {merge_count} merges \"\n        f\"across {len(document.tables)} tables\"\n    )","handlingStrategy":"validation","validationCode":"document = converter.convert(workbook_path).document\ntable_count = len(document.tables)\nif table_count == 0:\n    raise SystemExit(\"XLSX conversion produced no tables; check backend/format detection\")\n# expected: one table per worksheet in the current backend contract\nif table_count != 1:\n    print(f\"warning: sheet split into {table_count} tables\", file=sys.stderr)","typeGuard":"def has_single_table(document) -> bool:\n    \"\"\"Narrow a DoclingDocument expected to hold exactly one XLSX worksheet table.\"\"\"\n    return len(document.tables) == 1 and document.tables[0].data is not None","tryCatchPattern":"try:\n    document = converter.convert(workbook_path).document\nexcept ConversionError as exc:\n    raise SystemExit(f\"conversion failed for {workbook_path}: {exc}\") from exc\nif len(document.tables) != 1:\n    # record the deviation instead of aborting the whole benchmark sweep\n    tqdm.write(f\"table count {len(document.tables)} for merge_count={merge_count}\", file=sys.stderr)","preventionTips":["Pin the docling version you are benchmarking and record it (the script already emits docling_version + commit_sha per result line — diff those when results change).","When upgrading docling or its backend dependencies, re-run the smallest merge_count first and inspect document.tables before launching the full sweep.","Treat these assertions as the benchmark's contract: if you intentionally change XLSX table segmentation, update the assertion in the same commit."],"tags":["benchmark","xlsx","assertion","table-extraction","version-drift"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}