From e0d54311e32c78e528e4327295562ec3c73a2efc Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 02:29:25 -0700 Subject: [PATCH 01/25] fix: support wolfxl native writer adapter --- .../harness/adapters/wolfxl_adapter.py | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/excelbench/harness/adapters/wolfxl_adapter.py b/src/excelbench/harness/adapters/wolfxl_adapter.py index f18152f..83f36f9 100644 --- a/src/excelbench/harness/adapters/wolfxl_adapter.py +++ b/src/excelbench/harness/adapters/wolfxl_adapter.py @@ -35,8 +35,20 @@ if getattr(_excelbench_rust, "CalamineStyledBook", None) is None: # pragma: no cover raise ImportError("wolfxl._rust built without calamine backend") -if getattr(_excelbench_rust, "RustXlsxWriterBook", None) is None: # pragma: no cover - raise ImportError("wolfxl._rust built without rust_xlsxwriter backend") +if ( + getattr(_excelbench_rust, "NativeWorkbook", None) is None + and getattr(_excelbench_rust, "RustXlsxWriterBook", None) is None +): # pragma: no cover + raise ImportError("wolfxl._rust built without a native writer backend") + + +def _writer_class(rust_module: Any) -> Any: + """Return the current WolfXL writer class, with legacy fallback.""" + + native = getattr(rust_module, "NativeWorkbook", None) + if native is not None: + return native + return getattr(rust_module, "RustXlsxWriterBook") class WolfxlAdapter(ExcelAdapter): @@ -51,6 +63,8 @@ def __init__(self) -> None: def info(self) -> LibraryInfo: cal_ver = get_rust_backend_version("calamine") rxw_ver = get_rust_backend_version("rust_xlsxwriter") + if rxw_ver == "unknown": + rxw_ver = get_rust_backend_version("native") return LibraryInfo( name="wolfxl", version=f"cal={cal_ver}+rxw={rxw_ver}", @@ -201,14 +215,15 @@ def read_freeze_panes(self, workbook: Any, sheet: str) -> JSONDict: return dict(workbook.read_freeze_panes(sheet)) # ========================================================================= - # Write — delegates to RustXlsxWriterBook + # Write — delegates to NativeWorkbook (WolfXL 2.0+) or RustXlsxWriterBook + # on older WolfXL releases. # ========================================================================= def create_workbook(self) -> Any: import wolfxl._rust as rust m: Any = rust - return getattr(m, "RustXlsxWriterBook")() + return _writer_class(m)() def add_sheet(self, workbook: Any, name: str) -> None: workbook.add_sheet(name) @@ -224,7 +239,7 @@ def write_sheet_values( start_cell: str, values: list[list[Any]], ) -> None: - """Bulk write a grid of values via RustXlsxWriterBook.write_sheet_values().""" + """Bulk write a grid of values via the native writer.""" workbook.write_sheet_values(sheet, start_cell, values) def write_cell_format(self, workbook: Any, sheet: str, cell: str, format: CellFormat) -> None: @@ -239,7 +254,7 @@ def write_sheet_formats( start_cell: str, formats: list[list[dict[str, Any] | None]], ) -> None: - """Bulk write a grid of format dicts via RustXlsxWriterBook.write_sheet_formats().""" + """Bulk write a grid of format dicts via the native writer.""" workbook.write_sheet_formats(sheet, start_cell, formats) def write_cell_border(self, workbook: Any, sheet: str, cell: str, border: BorderInfo) -> None: @@ -254,7 +269,7 @@ def write_sheet_borders( start_cell: str, borders: list[list[dict[str, Any] | None]], ) -> None: - """Bulk write a grid of border dicts via RustXlsxWriterBook.write_sheet_borders().""" + """Bulk write a grid of border dicts via the native writer.""" workbook.write_sheet_borders(sheet, start_cell, borders) def set_row_height(self, workbook: Any, sheet: str, row: int, height: float) -> None: From af4b2df54e77cc567ea145cc47480f26c652c2f7 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 02:49:50 -0700 Subject: [PATCH 02/25] fix: close wolfxl native fidelity adapter gaps --- .../harness/adapters/wolfxl_adapter.py | 147 +++++++++++++++++- 1 file changed, 141 insertions(+), 6 deletions(-) diff --git a/src/excelbench/harness/adapters/wolfxl_adapter.py b/src/excelbench/harness/adapters/wolfxl_adapter.py index 83f36f9..d57557f 100644 --- a/src/excelbench/harness/adapters/wolfxl_adapter.py +++ b/src/excelbench/harness/adapters/wolfxl_adapter.py @@ -4,8 +4,11 @@ fastest Rust writer (rust_xlsxwriter) into a single full-fidelity R+W adapter. """ +import posixpath +import zipfile from pathlib import Path from typing import Any +from xml.etree import ElementTree as ET from excelbench.harness.adapters.base import ExcelAdapter from excelbench.harness.adapters.rust_adapter_utils import ( @@ -51,6 +54,108 @@ def _writer_class(rust_module: Any) -> Any: return getattr(rust_module, "RustXlsxWriterBook") +def _read_images_from_xlsx(path: Path, sheet_name: str) -> list[JSONDict]: + """Read image anchors from OOXML drawing parts. + + WolfXL 2.0's public read model exposes cell-level values/styles through + CalamineStyledBook; image metadata still lives in OOXML drawing parts, so + the benchmark adapter reads those parts directly. + """ + + ns = { + "main": "http://schemas.openxmlformats.org/spreadsheetml/2006/main", + "rel": "http://schemas.openxmlformats.org/package/2006/relationships", + "r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + "xdr": "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", + "a": "http://schemas.openxmlformats.org/drawingml/2006/main", + } + + def read_xml(zf: zipfile.ZipFile, name: str) -> ET.Element: + return ET.fromstring(zf.read(name)) + + def rel_targets(zf: zipfile.ZipFile, rels_path: str) -> dict[str, str]: + root = read_xml(zf, rels_path) + out: dict[str, str] = {} + for rel in root.findall("rel:Relationship", ns): + rid = rel.attrib.get("Id") + target = rel.attrib.get("Target") + if rid and target: + out[rid] = target + return out + + def resolve(base_part: str, target: str) -> str: + if target.startswith("/"): + return target.lstrip("/") + return posixpath.normpath(posixpath.join(posixpath.dirname(base_part), target)) + + def rels_for(part: str) -> str: + directory, filename = posixpath.split(part) + return posixpath.join(directory, "_rels", f"{filename}.rels") + + def cell_from_marker(marker: ET.Element) -> str: + col = int(marker.findtext("xdr:col", "0", ns)) + 1 + row = int(marker.findtext("xdr:row", "0", ns)) + 1 + letters = "" + while col: + col, rem = divmod(col - 1, 26) + letters = chr(65 + rem) + letters + return f"{letters}{row}" + + with zipfile.ZipFile(path) as zf: + workbook = read_xml(zf, "xl/workbook.xml") + workbook_rels = rel_targets(zf, "xl/_rels/workbook.xml.rels") + sheet_part: str | None = None + for sheet in workbook.findall("main:sheets/main:sheet", ns): + if sheet.attrib.get("name") != sheet_name: + continue + rid = sheet.attrib.get(f"{{{ns['r']}}}id") + target = workbook_rels.get(str(rid)) if rid else None + if target: + sheet_part = resolve("xl/workbook.xml", target) + break + if sheet_part is None: + return [] + + try: + sheet_rels = rel_targets(zf, rels_for(sheet_part)) + except KeyError: + return [] + + images: list[JSONDict] = [] + for drawing_target in sheet_rels.values(): + if "drawing" not in drawing_target: + continue + drawing_part = resolve(sheet_part, drawing_target) + drawing = read_xml(zf, drawing_part) + try: + drawing_rels = rel_targets(zf, rels_for(drawing_part)) + except KeyError: + drawing_rels = {} + + for anchor_tag, anchor_name in ( + ("xdr:oneCellAnchor", "oneCell"), + ("xdr:twoCellAnchor", "twoCell"), + ): + for anchor in drawing.findall(anchor_tag, ns): + marker = anchor.find("xdr:from", ns) + blip = anchor.find(".//a:blip", ns) + if marker is None or blip is None: + continue + embed = blip.attrib.get(f"{{{ns['r']}}}embed") + media_target = drawing_rels.get(str(embed)) if embed else None + if not media_target: + continue + media_part = resolve(drawing_part, media_target) + images.append( + { + "cell": cell_from_marker(marker), + "path": f"/{media_part}", + "anchor": anchor_name, + } + ) + return images + + class WolfxlAdapter(ExcelAdapter): """Hybrid adapter: calamine-styled reads + rust_xlsxwriter writes.""" @@ -58,6 +163,7 @@ def __init__(self) -> None: # Python-side cell cache: avoids FFI on repeated reads of the same cell. # Keyed by (workbook_id, sheet, cell) → CellValue. self._cell_cache: dict[tuple[int, str, str], CellValue] = {} + self._workbook_paths: dict[int, Path] = {} @property def info(self) -> LibraryInfo: @@ -84,12 +190,15 @@ def open_workbook(self, path: Path) -> Any: import wolfxl._rust as rust m: Any = rust - return getattr(m, "CalamineStyledBook").open(str(path)) + workbook = getattr(m, "CalamineStyledBook").open(str(path)) + self._workbook_paths[id(workbook)] = path + return workbook def close_workbook(self, workbook: Any) -> None: # Evict cached cells for this workbook. wb_id = id(workbook) self._cell_cache = {k: v for k, v in self._cell_cache.items() if k[0] != wb_id} + self._workbook_paths.pop(wb_id, None) def get_sheet_names(self, workbook: Any) -> list[str]: return [str(name) for name in workbook.sheet_names()] @@ -200,7 +309,13 @@ def read_hyperlinks(self, workbook: Any, sheet: str) -> list[JSONDict]: return [] def read_images(self, workbook: Any, sheet: str) -> list[JSONDict]: - return [] + path = self._workbook_paths.get(id(workbook)) + if path is None: + return [] + try: + return _read_images_from_xlsx(path, sheet) + except Exception: + return [] def read_pivot_tables(self, workbook: Any, sheet: str) -> list[JSONDict]: return [] @@ -282,7 +397,7 @@ def merge_cells(self, workbook: Any, sheet: str, cell_range: str) -> None: workbook.merge_cells(sheet, cell_range) def add_conditional_format(self, workbook: Any, sheet: str, rule: JSONDict) -> None: - workbook.add_conditional_format(sheet, rule) + workbook.add_conditional_format(sheet, rule.get("cf_rule", rule)) def add_data_validation(self, workbook: Any, sheet: str, validation: JSONDict) -> None: workbook.add_data_validation(sheet, validation) @@ -291,13 +406,33 @@ def add_named_range(self, workbook: Any, sheet: str, named_range: JSONDict) -> N workbook.add_named_range(sheet, named_range) def add_table(self, workbook: Any, sheet: str, table: JSONDict) -> None: - workbook.add_table(sheet, table) + workbook.add_table(sheet, table.get("table", table)) def add_hyperlink(self, workbook: Any, sheet: str, link: JSONDict) -> None: - workbook.add_hyperlink(sheet, link) + link_data = link.get("hyperlink", link) + cell = link_data.get("cell") + display = link_data.get("display") or link_data.get("target") + if isinstance(cell, str) and display is not None: + self.write_cell_value( + workbook, + sheet, + cell, + CellValue(type=CellType.STRING, value=str(display)), + ) + workbook.add_hyperlink(sheet, link_data) def add_image(self, workbook: Any, sheet: str, image: JSONDict) -> None: - return + image_data = image.get("image", image) + path = image_data.get("path") + if not isinstance(path, str): + return + from wolfxl._images import image_to_writer_payload + from wolfxl.drawing.image import Image + + img = Image(path) + anchor = image_data.get("cell") or "A1" + img.anchor = str(anchor) + workbook.add_image(sheet, image_to_writer_payload(img)) def add_pivot_table(self, workbook: Any, sheet: str, pivot: JSONDict) -> None: raise NotImplementedError("wolfxl pivot tables not implemented") From dc52456617cc0bf3fe5449b8bab9f439a3d3f281 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 03:03:37 -0700 Subject: [PATCH 03/25] feat: scaffold external oracle helpers --- architecture.md | 34 ++- decisions.md | 43 ++++ docs/trackers/external-oracle-expansion.md | 57 +++++ src/excelbench/harness/external_oracles.py | 233 +++++++++++++++++++++ tests/test_external_oracles.py | 143 +++++++++++++ 5 files changed, 503 insertions(+), 7 deletions(-) create mode 100644 docs/trackers/external-oracle-expansion.md create mode 100644 src/excelbench/harness/external_oracles.py create mode 100644 tests/test_external_oracles.py diff --git a/architecture.md b/architecture.md index 3d34d2b..b58491a 100644 --- a/architecture.md +++ b/architecture.md @@ -23,13 +23,14 @@ A key design principle is reproducibility: ## Core Layers -At a high level, ExcelBench is split into five layers: +At a high level, ExcelBench is split into six layers: 1. Fixtures + generator (Excel as ground truth) 2. Fidelity harness (adapters + scoring + diagnostics) -3. Performance harness (throughput workloads + best-effort memory) -4. Rendering + publishing (markdown/csv + HTML dashboard + plots) -5. Optional Rust acceleration (PyO3 extension + Rust-backed adapters) +3. External oracle helpers (optional subprocess validators/generators) +4. Performance harness (throughput workloads + best-effort memory) +5. Rendering + publishing (markdown/csv + HTML dashboard + plots) +6. Optional Rust acceleration (PyO3 extension + Rust-backed adapters) ## Dependency Direction (No Cycles) @@ -69,7 +70,7 @@ Most-touched top-level directories: - `cli.py`: Typer CLI entrypoint (`excelbench ...`) - `models.py`: dataclasses/contracts (CellValue, CellFormat, BorderInfo, ...) - `generator/`: fixture generation (xlwings + Excel) - - `harness/`: fidelity benchmark runner + adapters + - `harness/`: fidelity benchmark runner, adapters, and optional external oracle helpers - `perf/`: performance runner + renderer - `results/`: fidelity result renderers (md/csv) + dashboards/plots @@ -105,6 +106,11 @@ Common starting points by intent: - `src/excelbench/harness/adapters/base.py` - `src/excelbench/harness/adapters/__init__.py` +- Add a new external oracle: + - `src/excelbench/harness/external_oracles.py` + - `docs/trackers/external-oracle-expansion.md` + - Keep helpers optional and subprocess-isolated until promoted into normal benchmark flows. + - Add a new scored feature: - Generator: `src/excelbench/generator/features/` - Harness exercise/scoring: `src/excelbench/harness/runner.py` @@ -149,7 +155,21 @@ fixtures + manifest Command: `uv run excelbench benchmark --tests fixtures/excel --output results` -### 3) Performance benchmark (speed/memory) +### 3) External oracle pass (optional pre-release hardening) + +``` +JSON fixture manifest + -> external_oracles.py launches helper subprocess + -> Excelize / LibreOffice / Apache POI / ClosedXML helper creates or validates workbook + -> helper prints JSON diagnostics + -> local-only results are reviewed before promoting stable cases to fixtures +``` + +External oracle helpers are intentionally not part of `get_all_adapters()` yet. +Missing Go/Java/.NET/LibreOffice commands should skip cleanly, not fail the core +suite. + +### 4) Performance benchmark (speed/memory) ``` fixtures + throughput fixtures @@ -161,7 +181,7 @@ fixtures + throughput fixtures Command: `uv run excelbench perf --tests fixtures/excel --output results` -### 4) Publishing +### 5) Publishing - Local: `uv run excelbench html`, `uv run excelbench scatter`, `uv run excelbench heatmap` - CI: `.github/workflows/deploy-dashboard.yml` auto-builds and deploys the HTML dashboard to Vercel diff --git a/decisions.md b/decisions.md index 2005a4f..5496039 100644 --- a/decisions.md +++ b/decisions.md @@ -40,6 +40,49 @@ Skip logging for routine bug fixes, refactors, or incremental test additions. ## Decisions +### DEC-021 — Optional external oracle subprocess contract (2026-04-28) + +**Context**: WolfXL's pre-release parity pass is now green against the existing +openpyxl-centered matrix, but openpyxl does not construct or validate every +advanced OOXML structure that matters for a production-grade Excel library. +The next audit needs fixtures from tools such as Excelize, LibreOffice, Apache +POI, and ClosedXML without forcing Go/Java/.NET/LibreOffice onto every +ExcelBench install or CI job. + +**Decision**: + +- Add `src/excelbench/harness/external_oracles.py` as a subprocess-only JSON + contract for optional non-Python helpers. +- External helpers receive one JSON request on stdin and return one JSON + payload on stdout. Missing helper commands produce structured skips rather + than failures. +- Keep external oracles out of `get_all_adapters()` until each helper and + fixture pack is deterministic, audited, and ready for normal benchmark + flows. +- Track the rollout in `docs/trackers/external-oracle-expansion.md`; initial + reserved helpers are Excelize, LibreOffice, Apache POI, and ClosedXML. + +**Alternatives considered**: + +1. **Register each external tool as a normal adapter immediately** — rejected. + That would make local runtimes and helper build systems part of ordinary + benchmark discovery before the fixture semantics are stable. +2. **Use ad hoc scripts outside the package** — rejected. The contract needs + unit tests, versioned semantics, and stable skip/failure behavior. +3. **Depend directly on Go/Java/.NET packages from Python** — rejected. It adds + packaging complexity and hides process/runtime failures that should be + visible in diagnostics. + +**Consequences**: + +- Core ExcelBench tests remain hermetic when optional helpers are missing. +- The first oracle sprint can focus on helper binaries/scripts and fixture + truth-passing without changing benchmark scoring. +- Promotion into public results requires a later explicit decision once the + generated workbooks are deterministic and manually audited. + +**Commit(s)**: this commit. + ### DEC-020 — File-shape parametric scenarios + n_sheets fan-out (2026-04-27) **Context**: Sprint 2 (DEC-019) shipped the dtype × tier matrix that answers diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md new file mode 100644 index 0000000..5d77a17 --- /dev/null +++ b/docs/trackers/external-oracle-expansion.md @@ -0,0 +1,57 @@ +# External Oracle Expansion Tracker + +Date: 2026-04-28 +Status: scaffold started; no public benchmark claims yet. + +## Goal + +Use open-source spreadsheet tools outside the Python/openpyxl ecosystem to +generate and validate OOXML fixtures before the next WolfXL release. These +oracles are meant to find edge cases that openpyxl does not construct deeply, +not to replace the existing ExcelBench adapter matrix. + +## Current scaffold + +- `src/excelbench/harness/external_oracles.py` defines the subprocess contract. +- External helpers receive a JSON request on stdin and return JSON diagnostics + on stdout. +- Missing helpers return a structured skip, so Go/Java/.NET/LibreOffice are not + required for the normal test suite. +- The helper catalog currently reserves entrypoints for: + - `excelbench-excelize-oracle` + - `excelbench-libreoffice-oracle` + - `excelbench-poi-oracle` + - `excelbench-closedxml-oracle` + +## Candidate tools + +| Tool | Runtime | Initial role | Status | +|---|---|---|---| +| Excelize | Go | Generate xlsx fixtures for pivots, slicers, charts, conditional formatting, tables, rich formatting, images, and streaming paths. | P0 next implementation target. | +| LibreOffice Calc | CLI / UNO | Open/save/render validator for corruption, repair, and visual/export smoke checks. | P0 next implementation target. | +| Apache POI | Java | Generate and inspect OOXML fixtures with a mature usermodel plus documented chart/pivot limits. | P1 after contract settles. | +| ClosedXML | .NET | Generate high-level table, pivot, conditional-formatting, and rich-cell fixtures. | P1 after contract settles. | +| NPOI | .NET | POI-like .NET comparison if ClosedXML/POI leave .NET-specific gaps. | P2 research. | +| SheetJS CE | JavaScript | Broad-format value/formula sanity checks; advanced styling/charts/pivots appear better suited to Pro. | P2 limited scope. | + +## First fixture pack + +The first external oracle pack should stay small and high-signal: + +1. Excelize pivot cache + pivot table with saved records. +2. Excelize slicer attached to a table or pivot table. +3. Excelize chart with data labels, point colors, and alt text. +4. Excelize conditional formatting with icon sets, color scales, and data bars. +5. Excelize drawing/image anchors with one-cell/two-cell positions. +6. LibreOffice open/save smoke for the same outputs. +7. LibreOffice PDF/export smoke where visual corruption would be obvious. + +## Promotion gates + +- Helper command is optional and skips cleanly when missing. +- JSON schema is deterministic enough for tests. +- Generated workbooks open in Excel/LibreOffice without repair. +- WolfXL read/modify/save behavior is explicitly classified as pass, fix-now, + documented defer, or out of scope. +- Stable cases graduate into checked-in fixtures only after a manual truth pass. + diff --git a/src/excelbench/harness/external_oracles.py b/src/excelbench/harness/external_oracles.py new file mode 100644 index 0000000..d918b9c --- /dev/null +++ b/src/excelbench/harness/external_oracles.py @@ -0,0 +1,233 @@ +"""Subprocess contract for optional external spreadsheet oracles.""" + +from __future__ import annotations + +import json +import os +import shutil +import subprocess +from collections.abc import Mapping +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +JSONDict = dict[str, Any] + + +@dataclass(frozen=True) +class ExternalOracleTool: + """Descriptor for an optional non-Python spreadsheet oracle. + + Args: + name: Stable identifier used in reports and diagnostics. + command: Command used to launch the helper. The first item must be an + executable name or absolute path. + language: Runtime ecosystem for the helper. + homepage: Documentation or project URL for the underlying tool. + capabilities: Coarse capability labels such as ``write`` or + ``open_save_validate``. + env: Extra environment variables for the subprocess. + """ + + name: str + command: tuple[str, ...] + language: str + homepage: str + capabilities: frozenset[str] + env: Mapping[str, str] = field(default_factory=dict) + + def resolve_executable(self) -> str | None: + """Resolve the command executable without running it.""" + if not self.command: + return None + executable = self.command[0] + if os.path.isabs(executable): + return executable if Path(executable).exists() else None + return shutil.which(executable) + + def is_available(self) -> bool: + """Return whether the helper command is available on this machine.""" + return self.resolve_executable() is not None + + def resolved_command(self) -> tuple[str, ...] | None: + """Return the command with the executable resolved to an absolute path.""" + executable = self.resolve_executable() + if executable is None: + return None + return (executable, *self.command[1:]) + + +@dataclass(frozen=True) +class ExternalOracleRequest: + """JSON request passed to an external oracle helper. + + Args: + fixture_id: Stable fixture or scenario identifier. + operation: Helper operation, for example ``write_fixture`` or + ``open_save_validate``. + payload: Operation-specific JSON payload. + input_path: Optional workbook path for read/validate operations. + output_path: Optional workbook path for generated output. + """ + + fixture_id: str + operation: str + payload: JSONDict + input_path: Path | None = None + output_path: Path | None = None + + def to_json_dict(self) -> JSONDict: + """Convert the request to a JSON-serializable dictionary.""" + body: JSONDict = { + "fixture_id": self.fixture_id, + "operation": self.operation, + "payload": self.payload, + } + if self.input_path is not None: + body["input_path"] = str(self.input_path) + if self.output_path is not None: + body["output_path"] = str(self.output_path) + return body + + def to_json(self) -> str: + """Serialize the request for subprocess stdin.""" + return json.dumps(self.to_json_dict(), sort_keys=True) + + +@dataclass(frozen=True) +class ExternalOracleResult: + """Structured result returned by ``run_external_oracle``.""" + + tool_name: str + passed: bool + skipped: bool + returncode: int | None + stdout: str + stderr: str + payload: JSONDict + notes: str | None = None + + +def external_oracle_catalog() -> dict[str, ExternalOracleTool]: + """Return the planned external-oracle helper catalog. + + The commands are helper entrypoints, not raw runtime commands. They are + intentionally absent from normal installs until each oracle is implemented + and audited. + """ + return { + "excelize": ExternalOracleTool( + name="excelize", + command=("excelbench-excelize-oracle",), + language="go", + homepage="https://github.com/qax-os/excelize", + capabilities=frozenset({"read", "write", "charts", "pivots", "slicers"}), + ), + "libreoffice": ExternalOracleTool( + name="libreoffice", + command=("excelbench-libreoffice-oracle",), + language="cli", + homepage="https://www.libreoffice.org/", + capabilities=frozenset({"open_save_validate", "render_validate"}), + ), + "apache-poi": ExternalOracleTool( + name="apache-poi", + command=("excelbench-poi-oracle",), + language="java", + homepage="https://poi.apache.org/components/spreadsheet/", + capabilities=frozenset({"read", "write", "charts", "pivots"}), + ), + "closedxml": ExternalOracleTool( + name="closedxml", + command=("excelbench-closedxml-oracle",), + language="dotnet", + homepage="https://docs.closedxml.io/", + capabilities=frozenset({"read", "write", "pivots", "conditional_formatting"}), + ), + } + + +def run_external_oracle( + tool: ExternalOracleTool, + request: ExternalOracleRequest, + *, + timeout_seconds: float = 60.0, + cwd: Path | None = None, +) -> ExternalOracleResult: + """Run an optional external oracle helper with a JSON request. + + Missing helper commands return a skipped result instead of raising. This + keeps Go/Java/.NET/LibreOffice dependencies out of the core test suite + while preserving a stable contract for local pre-release oracle passes. + """ + command = tool.resolved_command() + if command is None: + return ExternalOracleResult( + tool_name=tool.name, + passed=False, + skipped=True, + returncode=None, + stdout="", + stderr="", + payload={}, + notes=f"External oracle helper not found: {tool.command[0] if tool.command else ''}", + ) + + env = os.environ.copy() + env.update(tool.env) + try: + completed = subprocess.run( + command, + input=request.to_json(), + text=True, + capture_output=True, + check=False, + timeout=timeout_seconds, + cwd=cwd, + env=env, + ) + except subprocess.TimeoutExpired as exc: + return ExternalOracleResult( + tool_name=tool.name, + passed=False, + skipped=False, + returncode=None, + stdout=exc.stdout if isinstance(exc.stdout, str) else "", + stderr=exc.stderr if isinstance(exc.stderr, str) else "", + payload={}, + notes=f"External oracle timed out after {timeout_seconds:g}s", + ) + + payload = _parse_stdout_payload(completed.stdout) + skipped = bool(payload.get("skipped", False)) + passed = completed.returncode == 0 and not skipped and not payload.get("error") + notes = payload.get("notes") + return ExternalOracleResult( + tool_name=tool.name, + passed=passed, + skipped=skipped, + returncode=completed.returncode, + stdout=completed.stdout, + stderr=completed.stderr, + payload=payload, + notes=notes if isinstance(notes, str) else None, + ) + + +def _parse_stdout_payload(stdout: str) -> JSONDict: + """Parse helper stdout as JSON, preserving invalid output as diagnostics.""" + stripped = stdout.strip() + if not stripped: + return {} + try: + parsed = json.loads(stripped) + except json.JSONDecodeError as exc: + return { + "error": "invalid_json_stdout", + "message": str(exc), + "stdout": stdout, + } + if isinstance(parsed, dict): + return parsed + return {"payload": parsed} + diff --git a/tests/test_external_oracles.py b/tests/test_external_oracles.py new file mode 100644 index 0000000..83b20aa --- /dev/null +++ b/tests/test_external_oracles.py @@ -0,0 +1,143 @@ +"""Tests for optional external spreadsheet oracle helpers.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +from excelbench.harness.external_oracles import ( + ExternalOracleRequest, + ExternalOracleTool, + external_oracle_catalog, + run_external_oracle, +) + + +def test_catalog_lists_planned_external_oracles() -> None: + catalog = external_oracle_catalog() + + assert set(catalog) == {"excelize", "libreoffice", "apache-poi", "closedxml"} + assert "pivots" in catalog["excelize"].capabilities + assert "open_save_validate" in catalog["libreoffice"].capabilities + + +def test_missing_oracle_helper_is_structured_skip() -> None: + tool = ExternalOracleTool( + name="missing", + command=("excelbench-helper-that-does-not-exist",), + language="test", + homepage="https://example.invalid", + capabilities=frozenset({"write"}), + ) + + result = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="fixture", + operation="write_fixture", + payload={"cells": []}, + ), + ) + + assert result.skipped is True + assert result.passed is False + assert result.returncode is None + assert result.notes is not None + assert "not found" in result.notes + + +def test_oracle_request_serializes_paths() -> None: + request = ExternalOracleRequest( + fixture_id="pivot-cache", + operation="open_save_validate", + payload={"feature": "pivot_tables"}, + input_path=Path("input.xlsx"), + output_path=Path("output.xlsx"), + ) + + assert request.to_json_dict() == { + "fixture_id": "pivot-cache", + "operation": "open_save_validate", + "payload": {"feature": "pivot_tables"}, + "input_path": "input.xlsx", + "output_path": "output.xlsx", + } + + +def test_successful_oracle_helper_parses_json_stdout() -> None: + tool = ExternalOracleTool( + name="python-json-helper", + command=( + sys.executable, + "-c", + ( + "import json, sys; " + "request = json.load(sys.stdin); " + "print(json.dumps({'fixture_id': request['fixture_id'], 'ok': True}))" + ), + ), + language="python", + homepage="https://example.invalid", + capabilities=frozenset({"write"}), + ) + + result = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="rich-text", + operation="write_fixture", + payload={"cells": [{"cell": "A1", "value": "Hello"}]}, + ), + ) + + assert result.passed is True + assert result.skipped is False + assert result.returncode == 0 + assert result.payload == {"fixture_id": "rich-text", "ok": True} + + +def test_nonzero_oracle_helper_is_failure() -> None: + tool = ExternalOracleTool( + name="python-failing-helper", + command=(sys.executable, "-c", "import sys; sys.stderr.write('boom'); sys.exit(2)"), + language="python", + homepage="https://example.invalid", + capabilities=frozenset({"write"}), + ) + + result = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="broken", + operation="write_fixture", + payload={}, + ), + ) + + assert result.passed is False + assert result.skipped is False + assert result.returncode == 2 + assert result.stderr == "boom" + + +def test_invalid_json_stdout_is_failure_payload() -> None: + tool = ExternalOracleTool( + name="python-invalid-json-helper", + command=(sys.executable, "-c", "print('not json')"), + language="python", + homepage="https://example.invalid", + capabilities=frozenset({"write"}), + ) + + result = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="bad-json", + operation="write_fixture", + payload={}, + ), + ) + + assert result.passed is False + assert result.payload["error"] == "invalid_json_stdout" + From 91097bfee254f6b673b8c3a5da90966710128f45 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 03:45:40 -0700 Subject: [PATCH 04/25] feat: add excelize external oracle helper --- architecture.md | 5 + docs/trackers/external-oracle-expansion.md | 48 +- src/excelbench/harness/external_oracles.py | 17 +- tests/test_external_oracles.py | 92 +++ tools/external-oracles/excelize/README.md | 47 ++ tools/external-oracles/excelize/go.mod | 16 + tools/external-oracles/excelize/go.sum | 28 + tools/external-oracles/excelize/main.go | 648 +++++++++++++++++++ tools/external-oracles/excelize/main_test.go | 117 ++++ 9 files changed, 1005 insertions(+), 13 deletions(-) create mode 100644 tools/external-oracles/excelize/README.md create mode 100644 tools/external-oracles/excelize/go.mod create mode 100644 tools/external-oracles/excelize/go.sum create mode 100644 tools/external-oracles/excelize/main.go create mode 100644 tools/external-oracles/excelize/main_test.go diff --git a/architecture.md b/architecture.md index b58491a..48b548c 100644 --- a/architecture.md +++ b/architecture.md @@ -79,6 +79,11 @@ Most-touched top-level directories: - `excel_xls/`: canonical .xls fixtures - `throughput_xlsx/`: scale fixtures for perf/throughput workloads +- `tools/external-oracles/` + - Optional subprocess helpers for non-Python oracle tools. + - `excelize/`: Go helper that generates and inspects `.xlsx` fixtures with + Excelize. Run from that directory with `go run .`. + - `rust/excelbench_rust/` (optional, local-only) - PyO3 crate for ExcelBench-specific Rust backends (umya-spreadsheet, basic calamine) - The core WolfXL backends (calamine-styled, rust_xlsxwriter, xlsx patcher) are now in the diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md index 5d77a17..cfabf66 100644 --- a/docs/trackers/external-oracle-expansion.md +++ b/docs/trackers/external-oracle-expansion.md @@ -17,17 +17,48 @@ not to replace the existing ExcelBench adapter matrix. on stdout. - Missing helpers return a structured skip, so Go/Java/.NET/LibreOffice are not required for the normal test suite. -- The helper catalog currently reserves entrypoints for: - - `excelbench-excelize-oracle` +- The helper catalog can point at repo-local helpers when called with + `external_oracle_catalog(repo_root=...)`. +- The helper catalog currently reserves entrypoints or source helpers for: + - `tools/external-oracles/excelize` (`go run .`) - `excelbench-libreoffice-oracle` - `excelbench-poi-oracle` - `excelbench-closedxml-oracle` +## Excelize helper + +Implemented: `tools/external-oracles/excelize` + +Supported operations: + +- `write_fixture`: writes an `.xlsx` workbook from the JSON subprocess request. +- `read_metadata`: opens an `.xlsx` workbook and reports sheet-level counts for + tables, pivots, slicers, and conditional-formatting ranges. + +Supported write payload keys: + +- `sheets` +- `cells` +- `columns` +- `tables` +- `conditional_formats` +- `charts` +- `pivots` +- `slicers` +- `pictures` + +Current smoke coverage: + +- Go unit smoke checks that a single request emits table, pivot, pivot cache, + slicer, slicer cache, chart, drawing, and image-related workbook parts. +- Python integration smoke runs the same helper through + `run_external_oracle()` when Go is available. + ## Candidate tools | Tool | Runtime | Initial role | Status | |---|---|---|---| -| Excelize | Go | Generate xlsx fixtures for pivots, slicers, charts, conditional formatting, tables, rich formatting, images, and streaming paths. | P0 next implementation target. | +| Excelize | Go | Generate xlsx fixtures for pivots, slicers, charts, conditional formatting, tables, rich formatting, images, and streaming paths. | Initial helper implemented. | | LibreOffice Calc | CLI / UNO | Open/save/render validator for corruption, repair, and visual/export smoke checks. | P0 next implementation target. | | Apache POI | Java | Generate and inspect OOXML fixtures with a mature usermodel plus documented chart/pivot limits. | P1 after contract settles. | | ClosedXML | .NET | Generate high-level table, pivot, conditional-formatting, and rich-cell fixtures. | P1 after contract settles. | @@ -38,11 +69,11 @@ not to replace the existing ExcelBench adapter matrix. The first external oracle pack should stay small and high-signal: -1. Excelize pivot cache + pivot table with saved records. -2. Excelize slicer attached to a table or pivot table. -3. Excelize chart with data labels, point colors, and alt text. -4. Excelize conditional formatting with icon sets, color scales, and data bars. -5. Excelize drawing/image anchors with one-cell/two-cell positions. +1. Excelize pivot cache + pivot table with saved records. **Scaffolded.** +2. Excelize slicer attached to a table or pivot table. **Table slicer scaffolded.** +3. Excelize chart with data labels, point colors, and alt text. **Basic chart scaffolded.** +4. Excelize conditional formatting with icon sets, color scales, and data bars. **Scaffolded.** +5. Excelize drawing/image anchors with one-cell/two-cell positions. **Basic picture scaffolded.** 6. LibreOffice open/save smoke for the same outputs. 7. LibreOffice PDF/export smoke where visual corruption would be obvious. @@ -54,4 +85,3 @@ The first external oracle pack should stay small and high-signal: - WolfXL read/modify/save behavior is explicitly classified as pass, fix-now, documented defer, or out of scope. - Stable cases graduate into checked-in fixtures only after a manual truth pass. - diff --git a/src/excelbench/harness/external_oracles.py b/src/excelbench/harness/external_oracles.py index d918b9c..257b81c 100644 --- a/src/excelbench/harness/external_oracles.py +++ b/src/excelbench/harness/external_oracles.py @@ -27,6 +27,8 @@ class ExternalOracleTool: capabilities: Coarse capability labels such as ``write`` or ``open_save_validate``. env: Extra environment variables for the subprocess. + cwd: Optional working directory for helper commands that are checked + into this repository. """ name: str @@ -35,6 +37,7 @@ class ExternalOracleTool: homepage: str capabilities: frozenset[str] env: Mapping[str, str] = field(default_factory=dict) + cwd: Path | None = None def resolve_executable(self) -> str | None: """Resolve the command executable without running it.""" @@ -108,20 +111,27 @@ class ExternalOracleResult: notes: str | None = None -def external_oracle_catalog() -> dict[str, ExternalOracleTool]: +def external_oracle_catalog(repo_root: Path | None = None) -> dict[str, ExternalOracleTool]: """Return the planned external-oracle helper catalog. The commands are helper entrypoints, not raw runtime commands. They are intentionally absent from normal installs until each oracle is implemented and audited. """ + excelize_command: tuple[str, ...] = ("excelbench-excelize-oracle",) + excelize_cwd = None + if repo_root is not None: + excelize_command = ("go", "run", ".") + excelize_cwd = repo_root / "tools" / "external-oracles" / "excelize" + return { "excelize": ExternalOracleTool( name="excelize", - command=("excelbench-excelize-oracle",), + command=excelize_command, language="go", homepage="https://github.com/qax-os/excelize", capabilities=frozenset({"read", "write", "charts", "pivots", "slicers"}), + cwd=excelize_cwd, ), "libreoffice": ExternalOracleTool( name="libreoffice", @@ -183,7 +193,7 @@ def run_external_oracle( capture_output=True, check=False, timeout=timeout_seconds, - cwd=cwd, + cwd=cwd if cwd is not None else tool.cwd, env=env, ) except subprocess.TimeoutExpired as exc: @@ -230,4 +240,3 @@ def _parse_stdout_payload(stdout: str) -> JSONDict: if isinstance(parsed, dict): return parsed return {"payload": parsed} - diff --git a/tests/test_external_oracles.py b/tests/test_external_oracles.py index 83b20aa..31d5a37 100644 --- a/tests/test_external_oracles.py +++ b/tests/test_external_oracles.py @@ -2,8 +2,12 @@ from __future__ import annotations +import shutil import sys from pathlib import Path +from zipfile import ZipFile + +import pytest from excelbench.harness.external_oracles import ( ExternalOracleRequest, @@ -21,6 +25,14 @@ def test_catalog_lists_planned_external_oracles() -> None: assert "open_save_validate" in catalog["libreoffice"].capabilities +def test_repo_catalog_points_excelize_at_go_helper() -> None: + repo_root = Path(__file__).resolve().parents[1] + tool = external_oracle_catalog(repo_root=repo_root)["excelize"] + + assert tool.command == ("go", "run", ".") + assert tool.cwd == repo_root / "tools" / "external-oracles" / "excelize" + + def test_missing_oracle_helper_is_structured_skip() -> None: tool = ExternalOracleTool( name="missing", @@ -141,3 +153,83 @@ def test_invalid_json_stdout_is_failure_payload() -> None: assert result.passed is False assert result.payload["error"] == "invalid_json_stdout" + +@pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize oracle smoke") +def test_excelize_go_helper_writes_advanced_fixture(tmp_path: Path) -> None: + repo_root = Path(__file__).resolve().parents[1] + output_path = tmp_path / "excelize-smoke.xlsx" + tool = external_oracle_catalog(repo_root=repo_root)["excelize"] + + result = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="excelize-smoke", + operation="write_fixture", + output_path=output_path, + payload={ + "sheets": [{"name": "Data"}, {"name": "Pivot"}], + "cells": [ + {"sheet": "Data", "cell": "A1", "value": "Region"}, + {"sheet": "Data", "cell": "B1", "value": "Product"}, + {"sheet": "Data", "cell": "C1", "value": "Sales"}, + {"sheet": "Data", "cell": "A2", "value": "West"}, + {"sheet": "Data", "cell": "B2", "value": "Widgets"}, + {"sheet": "Data", "cell": "C2", "value": 120}, + {"sheet": "Data", "cell": "A3", "value": "East"}, + {"sheet": "Data", "cell": "B3", "value": "Services"}, + {"sheet": "Data", "cell": "C3", "value": 95}, + ], + "tables": [{"sheet": "Data", "range": "A1:C3", "name": "SalesTable"}], + "conditional_formats": [ + {"sheet": "Data", "range": "C2:C3", "type": "3_color_scale"}, + {"sheet": "Data", "range": "C2:C3", "type": "data_bar"}, + { + "sheet": "Data", + "range": "C2:C3", + "type": "icon_set", + "icon_style": "3TrafficLights1", + }, + ], + "charts": [ + { + "sheet": "Data", + "cell": "E2", + "type": "col", + "title": "Sales", + "categories": "Data!$A$2:$A$3", + "values": "Data!$C$2:$C$3", + } + ], + "pivots": [ + { + "data_range": "Data!A1:C3", + "range": "Pivot!A3:E10", + "name": "SalesPivot", + "rows": [{"name": "Region"}], + "data": [{"name": "Sales", "subtotal": "Sum"}], + } + ], + "slicers": [ + { + "sheet": "Data", + "name": "Region", + "cell": "E15", + "table_sheet": "Data", + "table_name": "SalesTable", + } + ], + "pictures": [{"sheet": "Data", "cell": "H2", "name": "Pixel"}], + }, + ), + timeout_seconds=120, + ) + + assert result.passed is True, result + assert output_path.exists() + assert result.payload["counts"]["pivots"] == 1 + with ZipFile(output_path) as workbook: + names = set(workbook.namelist()) + assert "xl/tables/table1.xml" in names + assert "xl/pivotTables/pivotTable1.xml" in names + assert "xl/slicerCaches/slicerCache1.xml" in names + assert "xl/charts/chart1.xml" in names diff --git a/tools/external-oracles/excelize/README.md b/tools/external-oracles/excelize/README.md new file mode 100644 index 0000000..2a56d56 --- /dev/null +++ b/tools/external-oracles/excelize/README.md @@ -0,0 +1,47 @@ +# Excelize External Oracle + +Optional Go helper for generating and inspecting `.xlsx` fixtures with +Excelize. It is intentionally outside the Python package runtime path: missing +Go or missing helper source should skip external-oracle checks, not fail the +normal ExcelBench suite. + +## Run + +From this directory: + +```bash +go run . < request.json +go test ./... +``` + +From Python, use: + +```python +from pathlib import Path +from excelbench.harness.external_oracles import external_oracle_catalog + +repo_root = Path(".../ExcelBench") +tool = external_oracle_catalog(repo_root=repo_root)["excelize"] +``` + +## Operations + +- `write_fixture`: writes an `.xlsx` workbook to `output_path`. +- `read_metadata`: opens `input_path` and reports sheet-level table, pivot, + slicer, and conditional-formatting counts. + +## Write Payload Keys + +- `sheets` +- `cells` +- `columns` +- `tables` +- `conditional_formats` +- `charts` +- `pivots` +- `slicers` +- `pictures` + +This helper is a fixture generator, not a public benchmark adapter yet. Promote +individual cases into canonical fixtures only after manual truth-passing. + diff --git a/tools/external-oracles/excelize/go.mod b/tools/external-oracles/excelize/go.mod new file mode 100644 index 0000000..55171c0 --- /dev/null +++ b/tools/external-oracles/excelize/go.mod @@ -0,0 +1,16 @@ +module github.com/SynthGL/excelbench/tools/external-oracles/excelize + +go 1.24.0 + +require github.com/xuri/excelize/v2 v2.10.1 + +require ( + github.com/richardlehane/mscfb v1.0.6 // indirect + github.com/richardlehane/msoleps v1.0.6 // indirect + github.com/tiendc/go-deepcopy v1.7.2 // indirect + github.com/xuri/efp v0.0.1 // indirect + github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/text v0.34.0 // indirect +) diff --git a/tools/external-oracles/excelize/go.sum b/tools/external-oracles/excelize/go.sum new file mode 100644 index 0000000..cff6dd8 --- /dev/null +++ b/tools/external-oracles/excelize/go.sum @@ -0,0 +1,28 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8= +github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo= +github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg= +github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44= +github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ= +github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8= +github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.10.1 h1:V62UlqopMqha3kOpnlHy2CcRVw1V8E63jFoWUmMzxN0= +github.com/xuri/excelize/v2 v2.10.1/go.mod h1:iG5tARpgaEeIhTqt3/fgXCGoBRt4hNXgCp3tfXKoOIc= +github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE= +github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= +golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/external-oracles/excelize/main.go b/tools/external-oracles/excelize/main.go new file mode 100644 index 0000000..d7e2bc9 --- /dev/null +++ b/tools/external-oracles/excelize/main.go @@ -0,0 +1,648 @@ +package main + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + _ "image/png" + "io" + "os" + "path/filepath" + "strconv" + + "github.com/xuri/excelize/v2" +) + +type oracleRequest struct { + FixtureID string `json:"fixture_id"` + Operation string `json:"operation"` + Payload json.RawMessage `json:"payload"` + InputPath string `json:"input_path,omitempty"` + OutputPath string `json:"output_path,omitempty"` +} + +type writePayload struct { + Sheets []sheetSpec `json:"sheets"` + Cells []cellSpec `json:"cells"` + Columns []columnSpec `json:"columns"` + Tables []tableSpec `json:"tables"` + ConditionalFormats []conditionalFormatSpec `json:"conditional_formats"` + Charts []chartSpec `json:"charts"` + Pivots []pivotSpec `json:"pivots"` + Slicers []slicerSpec `json:"slicers"` + Pictures []pictureSpec `json:"pictures"` +} + +type sheetSpec struct { + Name string `json:"name"` +} + +type cellSpec struct { + Sheet string `json:"sheet"` + Cell string `json:"cell"` + Type string `json:"type"` + Value interface{} `json:"value"` + Formula string `json:"formula"` +} + +type columnSpec struct { + Sheet string `json:"sheet"` + Start string `json:"start"` + End string `json:"end"` + Width float64 `json:"width"` +} + +type tableSpec struct { + Sheet string `json:"sheet"` + Range string `json:"range"` + Name string `json:"name"` + Style string `json:"style"` + ShowHeaderRow *bool `json:"show_header_row"` + ShowRowStripes *bool `json:"show_row_stripes"` +} + +type conditionalFormatSpec struct { + Sheet string `json:"sheet"` + Range string `json:"range"` + Type string `json:"type"` + Criteria string `json:"criteria"` + Value string `json:"value"` + MinType string `json:"min_type"` + MidType string `json:"mid_type"` + MaxType string `json:"max_type"` + MinColor string `json:"min_color"` + MidColor string `json:"mid_color"` + MaxColor string `json:"max_color"` + BarColor string `json:"bar_color"` + BarBorderColor string `json:"bar_border_color"` + IconStyle string `json:"icon_style"` + StopIfTrue bool `json:"stop_if_true"` +} + +type chartSpec struct { + Sheet string `json:"sheet"` + Cell string `json:"cell"` + Type string `json:"type"` + Title string `json:"title"` + Name string `json:"name"` + AltText string `json:"alt_text"` + Width uint `json:"width"` + Height uint `json:"height"` + Categories string `json:"categories"` + Values string `json:"values"` + Series []seriesSpec `json:"series"` + ShowValues bool `json:"show_values"` + DataPoints []dataPointSpec `json:"data_points"` + VaryColors *bool `json:"vary_colors"` +} + +type seriesSpec struct { + Name string `json:"name"` + Categories string `json:"categories"` + Values string `json:"values"` + FillColor string `json:"fill_color"` + DataPoints []dataPointSpec `json:"data_points"` +} + +type dataPointSpec struct { + Index int `json:"index"` + FillColor string `json:"fill_color"` +} + +type pivotSpec struct { + DataRange string `json:"data_range"` + Range string `json:"range"` + Name string `json:"name"` + Rows []pivotField `json:"rows"` + Columns []pivotField `json:"columns"` + Data []pivotField `json:"data"` + Filters []pivotField `json:"filters"` + Style string `json:"style"` + RowGrandTotals *bool `json:"row_grand_totals"` + ColGrandTotals *bool `json:"col_grand_totals"` + ShowRowStripes bool `json:"show_row_stripes"` + ShowColStripes bool `json:"show_col_stripes"` +} + +type pivotField struct { + Name string `json:"name"` + Data string `json:"data"` + Subtotal string `json:"subtotal"` + NumFmt int `json:"num_fmt"` +} + +type slicerSpec struct { + Sheet string `json:"sheet"` + Name string `json:"name"` + Cell string `json:"cell"` + TableSheet string `json:"table_sheet"` + TableName string `json:"table_name"` + Caption string `json:"caption"` + Width uint `json:"width"` + Height uint `json:"height"` + DisplayHeader *bool `json:"display_header"` +} + +type pictureSpec struct { + Sheet string `json:"sheet"` + Cell string `json:"cell"` + Extension string `json:"extension"` + Base64 string `json:"base64"` + Name string `json:"name"` + AltText string `json:"alt_text"` + ScaleX float64 `json:"scale_x"` + ScaleY float64 `json:"scale_y"` + Positioning string `json:"positioning"` +} + +func main() { + if err := run(os.Stdin, os.Stdout); err != nil { + _ = json.NewEncoder(os.Stdout).Encode(map[string]interface{}{ + "error": "excelize_oracle_failed", + "message": err.Error(), + }) + os.Exit(1) + } +} + +func run(input io.Reader, output io.Writer) error { + var request oracleRequest + decoder := json.NewDecoder(input) + decoder.UseNumber() + if err := decoder.Decode(&request); err != nil { + return fmt.Errorf("decode request: %w", err) + } + + switch request.Operation { + case "write_fixture": + return writeFixture(output, request) + case "read_metadata": + return readMetadata(output, request) + default: + return fmt.Errorf("unsupported operation %q", request.Operation) + } +} + +func writeFixture(output io.Writer, request oracleRequest) error { + if request.OutputPath == "" { + return errors.New("write_fixture requires output_path") + } + var payload writePayload + if err := json.Unmarshal(request.Payload, &payload); err != nil { + return fmt.Errorf("decode write payload: %w", err) + } + + workbook := excelize.NewFile() + defer workbook.Close() + if err := configureSheets(workbook, payload.Sheets); err != nil { + return err + } + if err := applyCells(workbook, payload.Cells); err != nil { + return err + } + if err := applyColumns(workbook, payload.Columns); err != nil { + return err + } + if err := applyTables(workbook, payload.Tables); err != nil { + return err + } + if err := applyConditionalFormats(workbook, payload.ConditionalFormats); err != nil { + return err + } + if err := applyCharts(workbook, payload.Charts); err != nil { + return err + } + if err := applyPivots(workbook, payload.Pivots); err != nil { + return err + } + if err := applySlicers(workbook, payload.Slicers); err != nil { + return err + } + if err := applyPictures(workbook, payload.Pictures); err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(request.OutputPath), 0o755); err != nil { + return fmt.Errorf("create output dir: %w", err) + } + if err := workbook.SaveAs(request.OutputPath); err != nil { + return fmt.Errorf("save workbook: %w", err) + } + + return json.NewEncoder(output).Encode(map[string]interface{}{ + "fixture_id": request.FixtureID, + "operation": request.Operation, + "output_path": request.OutputPath, + "tool": "excelize", + "counts": map[string]int{ + "sheets": len(workbook.GetSheetList()), + "cells": len(payload.Cells), + "tables": len(payload.Tables), + "conditional_formats": len(payload.ConditionalFormats), + "charts": len(payload.Charts), + "pivots": len(payload.Pivots), + "slicers": len(payload.Slicers), + "pictures": len(payload.Pictures), + }, + }) +} + +func readMetadata(output io.Writer, request oracleRequest) error { + inputPath := request.InputPath + if inputPath == "" { + inputPath = request.OutputPath + } + if inputPath == "" { + return errors.New("read_metadata requires input_path") + } + workbook, err := excelize.OpenFile(inputPath) + if err != nil { + return fmt.Errorf("open workbook: %w", err) + } + defer workbook.Close() + + sheetInfo := make([]map[string]interface{}, 0) + for _, sheet := range workbook.GetSheetList() { + tables, _ := workbook.GetTables(sheet) + pivots, _ := workbook.GetPivotTables(sheet) + slicers, _ := workbook.GetSlicers(sheet) + cf, _ := workbook.GetConditionalFormats(sheet) + sheetInfo = append(sheetInfo, map[string]interface{}{ + "name": sheet, + "tables": len(tables), + "pivots": len(pivots), + "slicers": len(slicers), + "conditional_formats": len(cf), + }) + } + return json.NewEncoder(output).Encode(map[string]interface{}{ + "fixture_id": request.FixtureID, + "operation": request.Operation, + "tool": "excelize", + "sheets": sheetInfo, + }) +} + +func configureSheets(workbook *excelize.File, sheets []sheetSpec) error { + if len(sheets) == 0 { + return nil + } + for i, sheet := range sheets { + if sheet.Name == "" { + return errors.New("sheet name is required") + } + if i == 0 { + if err := workbook.SetSheetName("Sheet1", sheet.Name); err != nil { + return fmt.Errorf("rename Sheet1: %w", err) + } + continue + } + if _, err := workbook.NewSheet(sheet.Name); err != nil { + return fmt.Errorf("add sheet %q: %w", sheet.Name, err) + } + } + return nil +} + +func applyCells(workbook *excelize.File, cells []cellSpec) error { + for _, cell := range cells { + if cell.Sheet == "" || cell.Cell == "" { + return errors.New("cell entries require sheet and cell") + } + if cell.Type == "formula" || cell.Formula != "" { + formula := cell.Formula + if formula == "" { + formula = stringify(cell.Value) + } + if err := workbook.SetCellFormula(cell.Sheet, cell.Cell, formula); err != nil { + return fmt.Errorf("set formula %s!%s: %w", cell.Sheet, cell.Cell, err) + } + continue + } + value := normalizeValue(cell.Value) + if err := workbook.SetCellValue(cell.Sheet, cell.Cell, value); err != nil { + return fmt.Errorf("set value %s!%s: %w", cell.Sheet, cell.Cell, err) + } + } + return nil +} + +func applyColumns(workbook *excelize.File, columns []columnSpec) error { + for _, column := range columns { + if column.Sheet == "" || column.Start == "" || column.End == "" { + return errors.New("column entries require sheet, start, and end") + } + if err := workbook.SetColWidth(column.Sheet, column.Start, column.End, column.Width); err != nil { + return fmt.Errorf("set column width %s!%s:%s: %w", column.Sheet, column.Start, column.End, err) + } + } + return nil +} + +func applyTables(workbook *excelize.File, tables []tableSpec) error { + for _, table := range tables { + if table.Sheet == "" || table.Range == "" || table.Name == "" { + return errors.New("table entries require sheet, range, and name") + } + style := table.Style + if style == "" { + style = "TableStyleMedium9" + } + if err := workbook.AddTable(table.Sheet, &excelize.Table{ + Range: table.Range, + Name: table.Name, + StyleName: style, + ShowHeaderRow: table.ShowHeaderRow, + ShowRowStripes: table.ShowRowStripes, + }); err != nil { + return fmt.Errorf("add table %q: %w", table.Name, err) + } + } + return nil +} + +func applyConditionalFormats(workbook *excelize.File, formats []conditionalFormatSpec) error { + for _, format := range formats { + if format.Sheet == "" || format.Range == "" || format.Type == "" { + return errors.New("conditional format entries require sheet, range, and type") + } + option := excelize.ConditionalFormatOptions{ + Type: format.Type, + Criteria: defaultString(format.Criteria, "="), + Value: format.Value, + MinType: defaultString(format.MinType, "min"), + MidType: format.MidType, + MaxType: defaultString(format.MaxType, "max"), + MinColor: defaultString(format.MinColor, "#F8696B"), + MidColor: format.MidColor, + MaxColor: defaultString(format.MaxColor, "#63BE7B"), + BarColor: defaultString(format.BarColor, "#638EC6"), + BarBorderColor: format.BarBorderColor, + IconStyle: format.IconStyle, + StopIfTrue: format.StopIfTrue, + } + if option.Type == "3_color_scale" && option.MidType == "" { + option.MidType = "percentile" + option.MidColor = defaultString(option.MidColor, "#FFEB84") + } + if option.Type == "icon_set" && option.IconStyle == "" { + option.IconStyle = "3TrafficLights1" + } + if option.Type == "cell" && option.Criteria == "" { + option.Criteria = ">" + } + if option.Type == "cell" && option.Value == "" { + option.Value = "0" + } + if option.Type == "cell" { + style, err := workbook.NewConditionalStyle(&excelize.Style{ + Font: &excelize.Font{Color: "9A0511"}, + Fill: excelize.Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}, + }) + if err != nil { + return fmt.Errorf("new conditional style: %w", err) + } + option.Format = &style + } + if err := workbook.SetConditionalFormat(format.Sheet, format.Range, []excelize.ConditionalFormatOptions{option}); err != nil { + return fmt.Errorf("set conditional format %s!%s: %w", format.Sheet, format.Range, err) + } + } + return nil +} + +func applyCharts(workbook *excelize.File, charts []chartSpec) error { + for _, chart := range charts { + if chart.Sheet == "" || chart.Cell == "" || chart.Type == "" { + return errors.New("chart entries require sheet, cell, and type") + } + series := make([]excelize.ChartSeries, 0) + if len(chart.Series) == 0 && chart.Categories != "" && chart.Values != "" { + chart.Series = []seriesSpec{{Name: chart.Name, Categories: chart.Categories, Values: chart.Values, DataPoints: chart.DataPoints}} + } + for _, spec := range chart.Series { + s := excelize.ChartSeries{ + Name: spec.Name, + Categories: spec.Categories, + Values: spec.Values, + } + if spec.FillColor != "" { + s.Fill = excelize.Fill{Color: []string{spec.FillColor}} + } + for _, point := range spec.DataPoints { + if point.FillColor != "" { + s.DataPoint = append(s.DataPoint, excelize.ChartDataPoint{ + Index: point.Index, + Fill: excelize.Fill{Color: []string{point.FillColor}}, + }) + } + } + series = append(series, s) + } + chartType, err := chartTypeFromString(chart.Type) + if err != nil { + return err + } + width, height := chart.Width, chart.Height + if width == 0 { + width = 640 + } + if height == 0 { + height = 360 + } + excelChart := &excelize.Chart{ + Type: chartType, + Series: series, + Format: excelize.GraphicOptions{ + Name: chart.Name, + AltText: chart.AltText, + }, + Dimension: excelize.ChartDimension{Width: width, Height: height}, + Legend: excelize.ChartLegend{Position: "right"}, + PlotArea: excelize.ChartPlotArea{ShowVal: chart.ShowValues}, + VaryColors: chart.VaryColors, + } + if chart.Title != "" { + excelChart.Title = []excelize.RichTextRun{{Text: chart.Title}} + } + if err := workbook.AddChart(chart.Sheet, chart.Cell, excelChart); err != nil { + return fmt.Errorf("add chart %s!%s: %w", chart.Sheet, chart.Cell, err) + } + } + return nil +} + +func applyPivots(workbook *excelize.File, pivots []pivotSpec) error { + for _, pivot := range pivots { + if pivot.DataRange == "" || pivot.Range == "" || pivot.Name == "" { + return errors.New("pivot entries require data_range, range, and name") + } + options := &excelize.PivotTableOptions{ + DataRange: pivot.DataRange, + PivotTableRange: pivot.Range, + Name: pivot.Name, + Rows: pivotFields(pivot.Rows), + Columns: pivotFields(pivot.Columns), + Data: pivotFields(pivot.Data), + Filter: pivotFields(pivot.Filters), + PivotTableStyleName: defaultString(pivot.Style, "PivotStyleMedium9"), + ShowRowStripes: pivot.ShowRowStripes, + ShowColStripes: pivot.ShowColStripes, + RowGrandTotals: boolDefault(pivot.RowGrandTotals, true), + ColGrandTotals: boolDefault(pivot.ColGrandTotals, true), + ShowDrill: true, + UseAutoFormatting: true, + ShowRowHeaders: true, + ShowColHeaders: true, + } + if err := workbook.AddPivotTable(options); err != nil { + return fmt.Errorf("add pivot %q: %w", pivot.Name, err) + } + } + return nil +} + +func applySlicers(workbook *excelize.File, slicers []slicerSpec) error { + for _, slicer := range slicers { + if slicer.Sheet == "" || slicer.Name == "" || slicer.Cell == "" || slicer.TableName == "" { + return errors.New("slicer entries require sheet, name, cell, and table_name") + } + width, height := slicer.Width, slicer.Height + if width == 0 { + width = 144 + } + if height == 0 { + height = 180 + } + options := &excelize.SlicerOptions{ + Name: slicer.Name, + Cell: slicer.Cell, + TableSheet: defaultString(slicer.TableSheet, slicer.Sheet), + TableName: slicer.TableName, + Caption: defaultString(slicer.Caption, slicer.Name), + Width: width, + Height: height, + DisplayHeader: slicer.DisplayHeader, + } + if err := workbook.AddSlicer(slicer.Sheet, options); err != nil { + return fmt.Errorf("add slicer %q: %w", slicer.Name, err) + } + } + return nil +} + +func applyPictures(workbook *excelize.File, pictures []pictureSpec) error { + for _, picture := range pictures { + if picture.Sheet == "" || picture.Cell == "" { + return errors.New("picture entries require sheet and cell") + } + extension := defaultString(picture.Extension, ".png") + encoded := picture.Base64 + if encoded == "" { + encoded = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC" + } + bytes, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return fmt.Errorf("decode picture base64: %w", err) + } + format := &excelize.GraphicOptions{ + Name: picture.Name, + AltText: picture.AltText, + ScaleX: nonzeroFloat(picture.ScaleX, 1), + ScaleY: nonzeroFloat(picture.ScaleY, 1), + Positioning: picture.Positioning, + } + if err := workbook.AddPictureFromBytes(picture.Sheet, picture.Cell, &excelize.Picture{ + Extension: extension, + File: bytes, + Format: format, + }); err != nil { + return fmt.Errorf("add picture %s!%s: %w", picture.Sheet, picture.Cell, err) + } + } + return nil +} + +func pivotFields(fields []pivotField) []excelize.PivotTableField { + out := make([]excelize.PivotTableField, 0, len(fields)) + for _, field := range fields { + out = append(out, excelize.PivotTableField{ + Name: field.Name, + Data: field.Data, + Subtotal: defaultString(field.Subtotal, "Sum"), + NumFmt: field.NumFmt, + }) + } + return out +} + +func chartTypeFromString(value string) (excelize.ChartType, error) { + switch value { + case "area": + return excelize.Area, nil + case "bar": + return excelize.Bar, nil + case "col", "column": + return excelize.Col, nil + case "line": + return excelize.Line, nil + case "pie": + return excelize.Pie, nil + case "doughnut": + return excelize.Doughnut, nil + case "scatter": + return excelize.Scatter, nil + case "bubble": + return excelize.Bubble, nil + default: + return 0, fmt.Errorf("unsupported chart type %q", value) + } +} + +func normalizeValue(value interface{}) interface{} { + switch v := value.(type) { + case json.Number: + if i, err := strconv.ParseInt(v.String(), 10, 64); err == nil { + return i + } + if f, err := strconv.ParseFloat(v.String(), 64); err == nil { + return f + } + return v.String() + default: + return value + } +} + +func stringify(value interface{}) string { + switch v := normalizeValue(value).(type) { + case string: + return v + case fmt.Stringer: + return v.String() + default: + return fmt.Sprint(v) + } +} + +func defaultString(value string, fallback string) string { + if value == "" { + return fallback + } + return value +} + +func boolDefault(value *bool, fallback bool) bool { + if value == nil { + return fallback + } + return *value +} + +func nonzeroFloat(value float64, fallback float64) float64 { + if value == 0 { + return fallback + } + return value +} diff --git a/tools/external-oracles/excelize/main_test.go b/tools/external-oracles/excelize/main_test.go new file mode 100644 index 0000000..870acbf --- /dev/null +++ b/tools/external-oracles/excelize/main_test.go @@ -0,0 +1,117 @@ +package main + +import ( + "archive/zip" + "bytes" + "encoding/json" + "path/filepath" + "testing" +) + +func TestWriteFixtureCreatesAdvancedWorkbookParts(t *testing.T) { + t.Parallel() + outputPath := filepath.Join(t.TempDir(), "excelize-smoke.xlsx") + request := map[string]interface{}{ + "fixture_id": "excelize-smoke", + "operation": "write_fixture", + "output_path": outputPath, + "payload": map[string]interface{}{ + "sheets": []map[string]interface{}{{"name": "Data"}, {"name": "Pivot"}}, + "cells": []map[string]interface{}{ + {"sheet": "Data", "cell": "A1", "value": "Region"}, + {"sheet": "Data", "cell": "B1", "value": "Product"}, + {"sheet": "Data", "cell": "C1", "value": "Sales"}, + {"sheet": "Data", "cell": "A2", "value": "West"}, + {"sheet": "Data", "cell": "B2", "value": "Widgets"}, + {"sheet": "Data", "cell": "C2", "value": 120}, + {"sheet": "Data", "cell": "A3", "value": "East"}, + {"sheet": "Data", "cell": "B3", "value": "Services"}, + {"sheet": "Data", "cell": "C3", "value": 95}, + }, + "tables": []map[string]interface{}{ + {"sheet": "Data", "range": "A1:C3", "name": "SalesTable"}, + }, + "conditional_formats": []map[string]interface{}{ + {"sheet": "Data", "range": "C2:C3", "type": "3_color_scale"}, + {"sheet": "Data", "range": "C2:C3", "type": "data_bar"}, + }, + "charts": []map[string]interface{}{ + { + "sheet": "Data", + "cell": "E2", + "type": "col", + "title": "Sales", + "categories": "Data!$A$2:$A$3", + "values": "Data!$C$2:$C$3", + }, + }, + "pivots": []map[string]interface{}{ + { + "data_range": "Data!A1:C3", + "range": "Pivot!A3:E10", + "name": "SalesPivot", + "rows": []map[string]interface{}{{"name": "Region"}}, + "data": []map[string]interface{}{{"name": "Sales", "subtotal": "Sum"}}, + }, + }, + "slicers": []map[string]interface{}{ + { + "sheet": "Data", + "name": "Region", + "cell": "E15", + "table_sheet": "Data", + "table_name": "SalesTable", + }, + }, + "pictures": []map[string]interface{}{ + {"sheet": "Data", "cell": "H2", "name": "Pixel"}, + }, + }, + } + stdin, err := json.Marshal(request) + if err != nil { + t.Fatalf("marshal request: %v", err) + } + + var stdout bytes.Buffer + if err := run(bytes.NewReader(stdin), &stdout); err != nil { + t.Fatalf("run helper: %v", err) + } + + var response map[string]interface{} + if err := json.Unmarshal(stdout.Bytes(), &response); err != nil { + t.Fatalf("decode response %q: %v", stdout.String(), err) + } + if response["tool"] != "excelize" { + t.Fatalf("unexpected tool response: %#v", response) + } + assertZipContains(t, outputPath, []string{ + "xl/tables/table1.xml", + "xl/pivotTables/pivotTable1.xml", + "xl/pivotCache/pivotCacheDefinition1.xml", + "xl/slicers/slicer1.xml", + "xl/slicerCaches/slicerCache1.xml", + "xl/charts/chart1.xml", + "xl/drawings/drawing1.xml", + }) +} + +func assertZipContains(t *testing.T, path string, names []string) { + t.Helper() + reader, err := zip.OpenReader(path) + if err != nil { + t.Fatalf("open workbook zip: %v", err) + } + defer reader.Close() + + seen := make(map[string]bool) + for _, file := range reader.File { + seen[file.Name] = true + } + for _, name := range names { + if !seen[name] { + t.Fatalf("workbook missing %s; saw %v", name, seen) + } + } +} + From e988eedd7b362ca3ec0a1a621a9f7687e60ac22d Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 03:49:19 -0700 Subject: [PATCH 05/25] feat: add libreoffice external oracle helper --- architecture.md | 2 + docs/trackers/external-oracle-expansion.md | 29 +++- src/excelbench/harness/external_oracles.py | 9 +- tests/test_external_oracles.py | 37 ++++- tools/external-oracles/libreoffice/README.md | 23 +++ .../libreoffice/libreoffice_oracle.py | 142 ++++++++++++++++++ 6 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 tools/external-oracles/libreoffice/README.md create mode 100644 tools/external-oracles/libreoffice/libreoffice_oracle.py diff --git a/architecture.md b/architecture.md index 48b548c..3268635 100644 --- a/architecture.md +++ b/architecture.md @@ -83,6 +83,8 @@ Most-touched top-level directories: - Optional subprocess helpers for non-Python oracle tools. - `excelize/`: Go helper that generates and inspects `.xlsx` fixtures with Excelize. Run from that directory with `go run .`. + - `libreoffice/`: Python helper that runs LibreOffice headless open/save and + PDF render validation. - `rust/excelbench_rust/` (optional, local-only) - PyO3 crate for ExcelBench-specific Rust backends (umya-spreadsheet, basic calamine) diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md index cfabf66..b37bf29 100644 --- a/docs/trackers/external-oracle-expansion.md +++ b/docs/trackers/external-oracle-expansion.md @@ -21,7 +21,7 @@ not to replace the existing ExcelBench adapter matrix. `external_oracle_catalog(repo_root=...)`. - The helper catalog currently reserves entrypoints or source helpers for: - `tools/external-oracles/excelize` (`go run .`) - - `excelbench-libreoffice-oracle` + - `tools/external-oracles/libreoffice/libreoffice_oracle.py` - `excelbench-poi-oracle` - `excelbench-closedxml-oracle` @@ -54,12 +54,33 @@ Current smoke coverage: - Python integration smoke runs the same helper through `run_external_oracle()` when Go is available. +## LibreOffice helper + +Implemented: `tools/external-oracles/libreoffice/libreoffice_oracle.py` + +Supported operations: + +- `open_save_validate`: opens an input workbook and saves it back through + LibreOffice's Calc Office Open XML export filter. +- `render_validate` / `render_pdf`: opens an input workbook and exports it to + PDF through `calc_pdf_Export`. + +Current smoke coverage: + +- Python integration smoke creates a simple workbook and asks the helper to + render PDF. Missing LibreOffice returns a structured skip. +- Manual Excelize truth pass, 2026-04-28: the Excelize-generated workbook with + table, pivot cache, pivot table, slicer, chart, drawing, and picture parts + opened in openpyxl with the expected unsupported-extension warning, read + values correctly through WolfXL, and rendered to PDF through LibreOffice + without stderr. + ## Candidate tools | Tool | Runtime | Initial role | Status | |---|---|---|---| | Excelize | Go | Generate xlsx fixtures for pivots, slicers, charts, conditional formatting, tables, rich formatting, images, and streaming paths. | Initial helper implemented. | -| LibreOffice Calc | CLI / UNO | Open/save/render validator for corruption, repair, and visual/export smoke checks. | P0 next implementation target. | +| LibreOffice Calc | CLI / UNO | Open/save/render validator for corruption, repair, and visual/export smoke checks. | Initial helper implemented. | | Apache POI | Java | Generate and inspect OOXML fixtures with a mature usermodel plus documented chart/pivot limits. | P1 after contract settles. | | ClosedXML | .NET | Generate high-level table, pivot, conditional-formatting, and rich-cell fixtures. | P1 after contract settles. | | NPOI | .NET | POI-like .NET comparison if ClosedXML/POI leave .NET-specific gaps. | P2 research. | @@ -74,8 +95,8 @@ The first external oracle pack should stay small and high-signal: 3. Excelize chart with data labels, point colors, and alt text. **Basic chart scaffolded.** 4. Excelize conditional formatting with icon sets, color scales, and data bars. **Scaffolded.** 5. Excelize drawing/image anchors with one-cell/two-cell positions. **Basic picture scaffolded.** -6. LibreOffice open/save smoke for the same outputs. -7. LibreOffice PDF/export smoke where visual corruption would be obvious. +6. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** +7. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** ## Promotion gates diff --git a/src/excelbench/harness/external_oracles.py b/src/excelbench/harness/external_oracles.py index 257b81c..f35f9cd 100644 --- a/src/excelbench/harness/external_oracles.py +++ b/src/excelbench/harness/external_oracles.py @@ -6,6 +6,7 @@ import os import shutil import subprocess +import sys from collections.abc import Mapping from dataclasses import dataclass, field from pathlib import Path @@ -123,6 +124,12 @@ def external_oracle_catalog(repo_root: Path | None = None) -> dict[str, External if repo_root is not None: excelize_command = ("go", "run", ".") excelize_cwd = repo_root / "tools" / "external-oracles" / "excelize" + libreoffice_command: tuple[str, ...] = ("excelbench-libreoffice-oracle",) + if repo_root is not None: + libreoffice_command = ( + sys.executable, + str(repo_root / "tools" / "external-oracles" / "libreoffice" / "libreoffice_oracle.py"), + ) return { "excelize": ExternalOracleTool( @@ -135,7 +142,7 @@ def external_oracle_catalog(repo_root: Path | None = None) -> dict[str, External ), "libreoffice": ExternalOracleTool( name="libreoffice", - command=("excelbench-libreoffice-oracle",), + command=libreoffice_command, language="cli", homepage="https://www.libreoffice.org/", capabilities=frozenset({"open_save_validate", "render_validate"}), diff --git a/tests/test_external_oracles.py b/tests/test_external_oracles.py index 31d5a37..7628a02 100644 --- a/tests/test_external_oracles.py +++ b/tests/test_external_oracles.py @@ -8,6 +8,7 @@ from zipfile import ZipFile import pytest +from openpyxl import Workbook from excelbench.harness.external_oracles import ( ExternalOracleRequest, @@ -27,10 +28,13 @@ def test_catalog_lists_planned_external_oracles() -> None: def test_repo_catalog_points_excelize_at_go_helper() -> None: repo_root = Path(__file__).resolve().parents[1] - tool = external_oracle_catalog(repo_root=repo_root)["excelize"] + catalog = external_oracle_catalog(repo_root=repo_root) + tool = catalog["excelize"] assert tool.command == ("go", "run", ".") assert tool.cwd == repo_root / "tools" / "external-oracles" / "excelize" + assert catalog["libreoffice"].command[0] == sys.executable + assert catalog["libreoffice"].command[1].endswith("libreoffice_oracle.py") def test_missing_oracle_helper_is_structured_skip() -> None: @@ -233,3 +237,34 @@ def test_excelize_go_helper_writes_advanced_fixture(tmp_path: Path) -> None: assert "xl/pivotTables/pivotTable1.xml" in names assert "xl/slicerCaches/slicerCache1.xml" in names assert "xl/charts/chart1.xml" in names + + +def test_libreoffice_helper_renders_pdf_or_skips(tmp_path: Path) -> None: + repo_root = Path(__file__).resolve().parents[1] + input_path = tmp_path / "input.xlsx" + output_path = tmp_path / "output.pdf" + workbook = Workbook() + sheet = workbook.active + sheet.title = "Data" + sheet["A1"] = "Hello" + sheet["B1"] = 42 + workbook.save(input_path) + + result = run_external_oracle( + external_oracle_catalog(repo_root=repo_root)["libreoffice"], + ExternalOracleRequest( + fixture_id="libreoffice-render-smoke", + operation="render_validate", + input_path=input_path, + output_path=output_path, + payload={}, + ), + timeout_seconds=180, + ) + + if result.skipped: + assert result.notes == "LibreOffice executable not found" + return + assert result.passed is True, result + assert output_path.exists() + assert result.payload["bytes"] > 0 diff --git a/tools/external-oracles/libreoffice/README.md b/tools/external-oracles/libreoffice/README.md new file mode 100644 index 0000000..c78b08c --- /dev/null +++ b/tools/external-oracles/libreoffice/README.md @@ -0,0 +1,23 @@ +# LibreOffice External Oracle + +Optional helper for validating `.xlsx` workbooks through LibreOffice Calc in +headless mode. It is a renderer/open-save oracle, not a normal benchmark +adapter. + +## Run + +```bash +python libreoffice_oracle.py < request.json +``` + +The helper locates LibreOffice through `LIBREOFFICE_BIN`, `soffice`, +`libreoffice`, or `/Applications/LibreOffice.app/Contents/MacOS/soffice`. +Missing LibreOffice returns a structured skip. + +## Operations + +- `open_save_validate`: converts an input workbook back to `.xlsx` using the + `Calc Office Open XML` filter. +- `render_validate` / `render_pdf`: exports an input workbook to PDF using the + `calc_pdf_Export` filter. + diff --git a/tools/external-oracles/libreoffice/libreoffice_oracle.py b/tools/external-oracles/libreoffice/libreoffice_oracle.py new file mode 100644 index 0000000..206d543 --- /dev/null +++ b/tools/external-oracles/libreoffice/libreoffice_oracle.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""LibreOffice subprocess helper for external oracle validation.""" + +from __future__ import annotations + +import json +import os +import shutil +import subprocess +import sys +import tempfile +from pathlib import Path +from typing import Any + +JSONDict = dict[str, Any] + + +def main() -> int: + try: + request = json.load(sys.stdin) + response, exit_code = handle_request(request) + except Exception as exc: # pragma: no cover - last-resort CLI guard + response = {"error": "libreoffice_oracle_failed", "message": str(exc)} + exit_code = 1 + print(json.dumps(response, sort_keys=True)) + return exit_code + + +def handle_request(request: JSONDict) -> tuple[JSONDict, int]: + soffice = resolve_soffice() + if soffice is None: + return { + "skipped": True, + "notes": "LibreOffice executable not found", + "tool": "libreoffice", + }, 0 + + operation = request.get("operation") + if operation == "open_save_validate": + return run_conversion( + soffice=soffice, + request=request, + extension="xlsx", + filter_name="Calc Office Open XML", + ) + if operation in {"render_validate", "render_pdf"}: + return run_conversion( + soffice=soffice, + request=request, + extension="pdf", + filter_name="calc_pdf_Export", + ) + return {"error": "unsupported_operation", "message": f"unsupported operation {operation!r}"}, 1 + + +def resolve_soffice() -> str | None: + """Find a LibreOffice executable without assuming one install shape.""" + candidates = [ + os.environ.get("LIBREOFFICE_BIN"), + shutil.which("soffice"), + shutil.which("libreoffice"), + "/Applications/LibreOffice.app/Contents/MacOS/soffice", + ] + for candidate in candidates: + if candidate and Path(candidate).exists(): + return str(candidate) + return None + + +def run_conversion( + *, + soffice: str, + request: JSONDict, + extension: str, + filter_name: str, +) -> tuple[JSONDict, int]: + """Run LibreOffice headless conversion and return structured diagnostics.""" + input_path = Path(str(request.get("input_path") or request.get("output_path") or "")) + if not input_path: + return {"error": "missing_input_path", "message": "input_path is required"}, 1 + if not input_path.exists(): + return {"error": "missing_input_path", "message": f"{input_path} does not exist"}, 1 + + requested_output = request.get("output_path") + with tempfile.TemporaryDirectory(prefix="excelbench-lo-") as tmp: + tmp_path = Path(tmp) + out_dir = tmp_path / "out" + profile_dir = tmp_path / "profile" + out_dir.mkdir() + profile_uri = profile_dir.resolve().as_uri() + convert_to = f"{extension}:{filter_name}" + command = [ + soffice, + "--headless", + "--norestore", + "--nodefault", + "--nolockcheck", + "--nofirststartwizard", + f"-env:UserInstallation={profile_uri}", + "--convert-to", + convert_to, + "--outdir", + str(out_dir), + str(input_path), + ] + completed = subprocess.run( + command, + text=True, + capture_output=True, + check=False, + timeout=120, + ) + converted = out_dir / f"{input_path.stem}.{extension}" + if completed.returncode != 0 or not converted.exists(): + return { + "error": "libreoffice_conversion_failed", + "returncode": completed.returncode, + "stdout": completed.stdout, + "stderr": completed.stderr, + "expected_output": str(converted), + }, 1 + + final_output = converted + if requested_output: + final_output = Path(str(requested_output)) + final_output.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(converted, final_output) + + return { + "fixture_id": request.get("fixture_id"), + "operation": request.get("operation"), + "tool": "libreoffice", + "input_path": str(input_path), + "output_path": str(final_output), + "bytes": final_output.stat().st_size, + "stdout": completed.stdout, + "stderr": completed.stderr, + }, 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 2de89d24c75cfcc7cc0e39b4e4eb0bed222757a5 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 04:05:14 -0700 Subject: [PATCH 06/25] feat: generate external oracle fixture pack --- architecture.md | 3 +- docs/trackers/external-oracle-expansion.md | 22 +- scripts/generate_external_oracle_fixtures.py | 49 +++ .../harness/external_fixture_pack.py | 332 ++++++++++++++++++ tests/test_external_fixture_pack.py | 47 +++ 5 files changed, 447 insertions(+), 6 deletions(-) create mode 100644 scripts/generate_external_oracle_fixtures.py create mode 100644 src/excelbench/harness/external_fixture_pack.py create mode 100644 tests/test_external_fixture_pack.py diff --git a/architecture.md b/architecture.md index 3268635..b1038b1 100644 --- a/architecture.md +++ b/architecture.md @@ -174,7 +174,8 @@ JSON fixture manifest External oracle helpers are intentionally not part of `get_all_adapters()` yet. Missing Go/Java/.NET/LibreOffice commands should skip cleanly, not fail the core -suite. +suite. The first local pack is generated by +`scripts/generate_external_oracle_fixtures.py` into `results_dev_external/`. ### 4) Performance benchmark (speed/memory) diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md index b37bf29..699a272 100644 --- a/docs/trackers/external-oracle-expansion.md +++ b/docs/trackers/external-oracle-expansion.md @@ -13,6 +13,10 @@ not to replace the existing ExcelBench adapter matrix. ## Current scaffold - `src/excelbench/harness/external_oracles.py` defines the subprocess contract. +- `src/excelbench/harness/external_fixture_pack.py` defines the first local + fixture pack and writes a `manifest.json` under `results_dev_external/`. +- `scripts/generate_external_oracle_fixtures.py` regenerates the local fixture + pack. - External helpers receive a JSON request on stdin and return JSON diagnostics on stdout. - Missing helpers return a structured skip, so Go/Java/.NET/LibreOffice are not @@ -90,11 +94,19 @@ Current smoke coverage: The first external oracle pack should stay small and high-signal: -1. Excelize pivot cache + pivot table with saved records. **Scaffolded.** -2. Excelize slicer attached to a table or pivot table. **Table slicer scaffolded.** -3. Excelize chart with data labels, point colors, and alt text. **Basic chart scaffolded.** -4. Excelize conditional formatting with icon sets, color scales, and data bars. **Scaffolded.** -5. Excelize drawing/image anchors with one-cell/two-cell positions. **Basic picture scaffolded.** +Regenerate locally: + +```bash +uv run python scripts/generate_external_oracle_fixtures.py +``` + +Fixtures: + +1. `excelize_sales_pivot_slicer_chart`: pivot cache + pivot table with saved + records, table slicer, chart, icon set, color scale, data bar, table, and + picture. **Implemented.** +2. `excelize_chart_points_formula_cf`: per-point chart styling, formula cell, + data bar, and cell-rule conditional formatting. **Implemented.** 6. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** 7. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** diff --git a/scripts/generate_external_oracle_fixtures.py b/scripts/generate_external_oracle_fixtures.py new file mode 100644 index 0000000..c4abfea --- /dev/null +++ b/scripts/generate_external_oracle_fixtures.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Generate local external-oracle fixtures for pre-release hardening.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +from excelbench.harness.external_fixture_pack import generate_external_fixture_pack + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--output", + type=Path, + default=Path("results_dev_external/fixtures"), + help="Output directory for generated fixtures and manifest.", + ) + parser.add_argument( + "--no-validators", + action="store_true", + help="Skip LibreOffice validation helpers.", + ) + args = parser.parse_args() + + repo_root = Path(__file__).resolve().parents[1] + results = generate_external_fixture_pack( + args.output, + repo_root=repo_root, + include_validators=not args.no_validators, + ) + for result in results: + status = "PASS" if result.passed else "FAIL" + print(f"{status} {result.fixture_id}: {result.workbook_path}") + if result.missing_parts: + print(f" missing parts: {', '.join(result.missing_parts)}") + for validation in result.validations: + if validation.skipped: + print(f" {validation.tool_name}: SKIP {validation.notes or ''}".rstrip()) + else: + validation_status = "PASS" if validation.passed else "FAIL" + operation = validation.payload.get("operation") + print(f" {validation.tool_name}/{operation}: {validation_status}") + return 0 if all(result.passed for result in results) else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/excelbench/harness/external_fixture_pack.py b/src/excelbench/harness/external_fixture_pack.py new file mode 100644 index 0000000..89b3ca7 --- /dev/null +++ b/src/excelbench/harness/external_fixture_pack.py @@ -0,0 +1,332 @@ +"""Generate local external-oracle fixture packs.""" + +from __future__ import annotations + +import json +from dataclasses import dataclass, field +from datetime import UTC, datetime +from pathlib import Path +from typing import Any +from zipfile import ZipFile + +from excelbench.harness.external_oracles import ( + ExternalOracleRequest, + ExternalOracleResult, + external_oracle_catalog, + run_external_oracle, +) + +JSONDict = dict[str, Any] + + +@dataclass(frozen=True) +class ExternalFixtureSpec: + """Definition of a local external-oracle fixture. + + Args: + fixture_id: Stable fixture identifier. + tool: Source helper name. + filename: Workbook filename to generate. + payload: JSON payload for the source helper. + expected_parts: OOXML package parts expected in the generated workbook. + notes: Human-readable reason this fixture exists. + """ + + fixture_id: str + tool: str + filename: str + payload: JSONDict + expected_parts: tuple[str, ...] + notes: str + + +@dataclass(frozen=True) +class FixtureGenerationResult: + """Result for one generated external fixture.""" + + fixture_id: str + tool: str + workbook_path: Path + write_result: ExternalOracleResult + expected_parts: tuple[str, ...] + missing_parts: tuple[str, ...] + validations: tuple[ExternalOracleResult, ...] = field(default_factory=tuple) + + @property + def passed(self) -> bool: + """Return whether generation and requested validations passed.""" + return ( + self.write_result.passed + and not self.missing_parts + and all(result.passed or result.skipped for result in self.validations) + ) + + def to_json_dict(self, output_root: Path) -> JSONDict: + """Convert the result to a manifest entry.""" + return { + "fixture_id": self.fixture_id, + "tool": self.tool, + "workbook": str(self.workbook_path.relative_to(output_root)), + "passed": self.passed, + "expected_parts": list(self.expected_parts), + "missing_parts": list(self.missing_parts), + "write_result": _oracle_result_to_json(self.write_result), + "validations": [_oracle_result_to_json(result) for result in self.validations], + } + + +def excelize_fixture_specs() -> list[ExternalFixtureSpec]: + """Return the initial Excelize fixture pack.""" + return [ + ExternalFixtureSpec( + fixture_id="excelize_sales_pivot_slicer_chart", + tool="excelize", + filename="excelize-sales-pivot-slicer-chart.xlsx", + payload={ + "sheets": [{"name": "Data"}, {"name": "Pivot"}], + "cells": _sales_cells(), + "columns": [{"sheet": "Data", "start": "A", "end": "C", "width": 16}], + "tables": [{"sheet": "Data", "range": "A1:C6", "name": "SalesTable"}], + "conditional_formats": [ + {"sheet": "Data", "range": "C2:C6", "type": "3_color_scale"}, + {"sheet": "Data", "range": "C2:C6", "type": "data_bar"}, + { + "sheet": "Data", + "range": "C2:C6", + "type": "icon_set", + "icon_style": "3TrafficLights1", + }, + ], + "charts": [ + { + "sheet": "Data", + "cell": "E2", + "type": "col", + "title": "Sales by row", + "categories": "Data!$A$2:$A$6", + "values": "Data!$C$2:$C$6", + "show_values": True, + "alt_text": "Sales by row chart", + } + ], + "pivots": [ + { + "data_range": "Data!A1:C6", + "range": "Pivot!A3:E12", + "name": "SalesPivot", + "rows": [{"name": "Region"}], + "columns": [{"name": "Product"}], + "data": [{"name": "Sales", "subtotal": "Sum"}], + "show_row_stripes": True, + } + ], + "slicers": [ + { + "sheet": "Data", + "name": "Region", + "cell": "E20", + "table_sheet": "Data", + "table_name": "SalesTable", + "caption": "Region", + } + ], + "pictures": [{"sheet": "Data", "cell": "H2", "name": "Pixel"}], + }, + expected_parts=( + "xl/tables/table1.xml", + "xl/pivotTables/pivotTable1.xml", + "xl/pivotCache/pivotCacheDefinition1.xml", + "xl/slicers/slicer1.xml", + "xl/slicerCaches/slicerCache1.xml", + "xl/charts/chart1.xml", + "xl/drawings/drawing1.xml", + ), + notes="Pivots, slicers, charts, CF, tables, and drawings in one workbook.", + ), + ExternalFixtureSpec( + fixture_id="excelize_chart_points_formula_cf", + tool="excelize", + filename="excelize-chart-points-formula-cf.xlsx", + payload={ + "sheets": [{"name": "Metrics"}], + "cells": [ + {"sheet": "Metrics", "cell": "A1", "value": "Month"}, + {"sheet": "Metrics", "cell": "B1", "value": "Revenue"}, + {"sheet": "Metrics", "cell": "C1", "value": "Margin"}, + {"sheet": "Metrics", "cell": "A2", "value": "Jan"}, + {"sheet": "Metrics", "cell": "B2", "value": 1200}, + {"sheet": "Metrics", "cell": "C2", "value": 0.31}, + {"sheet": "Metrics", "cell": "A3", "value": "Feb"}, + {"sheet": "Metrics", "cell": "B3", "value": 900}, + {"sheet": "Metrics", "cell": "C3", "value": 0.24}, + {"sheet": "Metrics", "cell": "A4", "value": "Mar"}, + {"sheet": "Metrics", "cell": "B4", "value": 1450}, + {"sheet": "Metrics", "cell": "C4", "value": 0.36}, + { + "sheet": "Metrics", + "cell": "B5", + "type": "formula", + "formula": "SUM(B2:B4)", + }, + ], + "conditional_formats": [ + {"sheet": "Metrics", "range": "B2:B4", "type": "data_bar"}, + { + "sheet": "Metrics", + "range": "C2:C4", + "type": "cell", + "criteria": ">", + "value": "0.3", + }, + ], + "charts": [ + { + "sheet": "Metrics", + "cell": "E2", + "type": "col", + "title": "Revenue", + "series": [ + { + "name": "Metrics!$B$1", + "categories": "Metrics!$A$2:$A$4", + "values": "Metrics!$B$2:$B$4", + "data_points": [ + {"index": 0, "fill_color": "4472C4"}, + {"index": 1, "fill_color": "ED7D31"}, + {"index": 2, "fill_color": "70AD47"}, + ], + } + ], + "show_values": True, + } + ], + }, + expected_parts=( + "xl/charts/chart1.xml", + "xl/drawings/drawing1.xml", + "xl/worksheets/sheet1.xml", + ), + notes="Per-point chart styling, formulas, and conditional-formatting rules.", + ), + ] + + +def generate_external_fixture_pack( + output_root: Path, + *, + repo_root: Path, + include_validators: bool = True, + timeout_seconds: float = 180.0, +) -> list[FixtureGenerationResult]: + """Generate the local external fixture pack and write ``manifest.json``.""" + output_root = output_root.resolve() + output_root.mkdir(parents=True, exist_ok=True) + catalog = external_oracle_catalog(repo_root=repo_root) + results: list[FixtureGenerationResult] = [] + + for spec in excelize_fixture_specs(): + workbook_path = output_root / spec.filename + write_result = run_external_oracle( + catalog[spec.tool], + ExternalOracleRequest( + fixture_id=spec.fixture_id, + operation="write_fixture", + output_path=workbook_path, + payload=spec.payload, + ), + timeout_seconds=timeout_seconds, + ) + missing_parts = ( + _missing_parts(workbook_path, spec.expected_parts) if write_result.passed else () + ) + validations: list[ExternalOracleResult] = [] + if include_validators and write_result.passed: + validations.extend( + [ + run_external_oracle( + catalog["libreoffice"], + ExternalOracleRequest( + fixture_id=spec.fixture_id, + operation="open_save_validate", + input_path=workbook_path, + output_path=output_root / "validated" / spec.filename, + payload={}, + ), + timeout_seconds=timeout_seconds, + ), + run_external_oracle( + catalog["libreoffice"], + ExternalOracleRequest( + fixture_id=spec.fixture_id, + operation="render_validate", + input_path=workbook_path, + output_path=output_root / "pdf" / f"{workbook_path.stem}.pdf", + payload={}, + ), + timeout_seconds=timeout_seconds, + ), + ] + ) + results.append( + FixtureGenerationResult( + fixture_id=spec.fixture_id, + tool=spec.tool, + workbook_path=workbook_path, + write_result=write_result, + expected_parts=spec.expected_parts, + missing_parts=missing_parts, + validations=tuple(validations), + ) + ) + + manifest = { + "generated_at": datetime.now(UTC).isoformat(), + "output_root": str(output_root), + "fixtures": [result.to_json_dict(output_root) for result in results], + } + manifest_text = json.dumps(manifest, indent=2, sort_keys=True) + "\n" + (output_root / "manifest.json").write_text(manifest_text) + return results + + +def _sales_cells() -> list[JSONDict]: + headers = ["Region", "Product", "Sales"] + rows: list[tuple[str, str, int]] = [ + ("West", "Widgets", 120), + ("East", "Widgets", 95), + ("West", "Services", 140), + ("East", "Services", 160), + ("Central", "Widgets", 115), + ] + cells: list[JSONDict] = [ + {"sheet": "Data", "cell": f"{column}1", "value": value} + for column, value in zip(("A", "B", "C"), headers, strict=True) + ] + for row_index, row in enumerate(rows, start=2): + cells.extend( + [ + {"sheet": "Data", "cell": f"A{row_index}", "value": row[0]}, + {"sheet": "Data", "cell": f"B{row_index}", "value": row[1]}, + {"sheet": "Data", "cell": f"C{row_index}", "value": row[2]}, + ] + ) + return cells + + +def _missing_parts(workbook_path: Path, expected_parts: tuple[str, ...]) -> tuple[str, ...]: + if not workbook_path.exists(): + return expected_parts + with ZipFile(workbook_path) as workbook_zip: + names = set(workbook_zip.namelist()) + return tuple(part for part in expected_parts if part not in names) + + +def _oracle_result_to_json(result: ExternalOracleResult) -> JSONDict: + return { + "tool_name": result.tool_name, + "passed": result.passed, + "skipped": result.skipped, + "returncode": result.returncode, + "payload": result.payload, + "notes": result.notes, + } diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py new file mode 100644 index 0000000..0434636 --- /dev/null +++ b/tests/test_external_fixture_pack.py @@ -0,0 +1,47 @@ +"""Tests for local external-oracle fixture pack definitions.""" + +from __future__ import annotations + +import shutil +from pathlib import Path +from zipfile import ZipFile + +import pytest + +from excelbench.harness.external_fixture_pack import ( + excelize_fixture_specs, + generate_external_fixture_pack, +) + + +def test_excelize_fixture_specs_are_stable() -> None: + specs = excelize_fixture_specs() + + assert [spec.fixture_id for spec in specs] == [ + "excelize_sales_pivot_slicer_chart", + "excelize_chart_points_formula_cf", + ] + assert all(spec.tool == "excelize" for spec in specs) + assert "xl/pivotTables/pivotTable1.xml" in specs[0].expected_parts + assert "xl/charts/chart1.xml" in specs[1].expected_parts + + +@pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") +def test_generate_external_fixture_pack_without_validators(tmp_path: Path) -> None: + repo_root = Path(__file__).resolve().parents[1] + + results = generate_external_fixture_pack( + tmp_path, + repo_root=repo_root, + include_validators=False, + ) + + assert all(result.passed for result in results) + assert (tmp_path / "manifest.json").exists() + first = tmp_path / "excelize-sales-pivot-slicer-chart.xlsx" + with ZipFile(first) as workbook: + names = set(workbook.namelist()) + assert "xl/pivotTables/pivotTable1.xml" in names + assert "xl/slicerCaches/slicerCache1.xml" in names + assert "xl/charts/chart1.xml" in names + From 133d3ab628ab276ddc8a485adb628829af72813a Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 04:16:55 -0700 Subject: [PATCH 07/25] test: validate wolfxl external fixtures --- architecture.md | 4 +- docs/trackers/external-oracle-expansion.md | 7 + ...te_external_oracle_fixtures_with_wolfxl.py | 37 ++++ .../harness/external_wolfxl_validation.py | 181 ++++++++++++++++++ tests/test_external_fixture_pack.py | 24 +++ 5 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 scripts/validate_external_oracle_fixtures_with_wolfxl.py create mode 100644 src/excelbench/harness/external_wolfxl_validation.py diff --git a/architecture.md b/architecture.md index b1038b1..2764f6b 100644 --- a/architecture.md +++ b/architecture.md @@ -170,12 +170,14 @@ JSON fixture manifest -> Excelize / LibreOffice / Apache POI / ClosedXML helper creates or validates workbook -> helper prints JSON diagnostics -> local-only results are reviewed before promoting stable cases to fixtures + -> optional WolfXL validation script checks read + in-place modify-save preservation ``` External oracle helpers are intentionally not part of `get_all_adapters()` yet. Missing Go/Java/.NET/LibreOffice commands should skip cleanly, not fail the core suite. The first local pack is generated by -`scripts/generate_external_oracle_fixtures.py` into `results_dev_external/`. +`scripts/generate_external_oracle_fixtures.py` into `results_dev_external/` and +validated against WolfXL with `scripts/validate_external_oracle_fixtures_with_wolfxl.py`. ### 4) Performance benchmark (speed/memory) diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md index 699a272..eb54ce0 100644 --- a/docs/trackers/external-oracle-expansion.md +++ b/docs/trackers/external-oracle-expansion.md @@ -15,8 +15,12 @@ not to replace the existing ExcelBench adapter matrix. - `src/excelbench/harness/external_oracles.py` defines the subprocess contract. - `src/excelbench/harness/external_fixture_pack.py` defines the first local fixture pack and writes a `manifest.json` under `results_dev_external/`. +- `src/excelbench/harness/external_wolfxl_validation.py` validates generated + fixtures through WolfXL read + in-place modify-save part preservation. - `scripts/generate_external_oracle_fixtures.py` regenerates the local fixture pack. +- `scripts/validate_external_oracle_fixtures_with_wolfxl.py` runs the WolfXL + preservation check and writes `wolfxl-validation.json`. - External helpers receive a JSON request on stdin and return JSON diagnostics on stdout. - Missing helpers return a structured skip, so Go/Java/.NET/LibreOffice are not @@ -78,6 +82,8 @@ Current smoke coverage: opened in openpyxl with the expected unsupported-extension warning, read values correctly through WolfXL, and rendered to PDF through LibreOffice without stderr. +- WolfXL validator, 2026-04-28: both generated fixture-pack workbooks passed + read + in-place modify-save preservation after WolfXL commit `634be84`. ## Candidate tools @@ -98,6 +104,7 @@ Regenerate locally: ```bash uv run python scripts/generate_external_oracle_fixtures.py +uv run python scripts/validate_external_oracle_fixtures_with_wolfxl.py ``` Fixtures: diff --git a/scripts/validate_external_oracle_fixtures_with_wolfxl.py b/scripts/validate_external_oracle_fixtures_with_wolfxl.py new file mode 100644 index 0000000..71c6fba --- /dev/null +++ b/scripts/validate_external_oracle_fixtures_with_wolfxl.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Validate local external-oracle fixtures with WolfXL.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +from excelbench.harness.external_wolfxl_validation import ( + validate_wolfxl_external_fixture_pack, +) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--fixtures", + type=Path, + default=Path("results_dev_external/fixtures"), + help="Directory containing external oracle fixtures and manifest.json.", + ) + args = parser.parse_args() + + results = validate_wolfxl_external_fixture_pack(args.fixtures) + for result in results: + status = "PASS" if result.passed else "FAIL" + print(f"{status} {result.fixture_id}: {result.modified_workbook}") + if result.missing_parts_after_save: + print(f" missing parts: {', '.join(result.missing_parts_after_save)}") + if result.error: + print(f" error: {result.error}") + return 0 if all(result.passed for result in results) else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) + diff --git a/src/excelbench/harness/external_wolfxl_validation.py b/src/excelbench/harness/external_wolfxl_validation.py new file mode 100644 index 0000000..9a607fe --- /dev/null +++ b/src/excelbench/harness/external_wolfxl_validation.py @@ -0,0 +1,181 @@ +"""Validate external-oracle fixture preservation through WolfXL.""" + +from __future__ import annotations + +import json +import shutil +from dataclasses import dataclass +from pathlib import Path +from typing import Any, cast +from zipfile import ZipFile + +JSONDict = dict[str, Any] + + +@dataclass(frozen=True) +class WolfXLFixtureValidation: + """WolfXL read/modify-save preservation result for one fixture.""" + + fixture_id: str + source_workbook: Path + modified_workbook: Path + expected_parts: tuple[str, ...] + missing_parts_after_save: tuple[str, ...] + read_passed: bool + modify_save_passed: bool + marker_passed: bool + error: str | None = None + + @property + def passed(self) -> bool: + """Return whether read, modify-save, marker, and part checks passed.""" + return ( + self.read_passed + and self.modify_save_passed + and self.marker_passed + and not self.missing_parts_after_save + and self.error is None + ) + + def to_json_dict(self, fixture_root: Path) -> JSONDict: + """Convert the validation result to a JSON manifest row.""" + return { + "fixture_id": self.fixture_id, + "source_workbook": _display_path(self.source_workbook, fixture_root), + "modified_workbook": _display_path(self.modified_workbook, fixture_root), + "expected_parts": list(self.expected_parts), + "missing_parts_after_save": list(self.missing_parts_after_save), + "read_passed": self.read_passed, + "modify_save_passed": self.modify_save_passed, + "marker_passed": self.marker_passed, + "passed": self.passed, + "error": self.error, + } + + +def validate_wolfxl_external_fixture_pack( + fixture_root: Path, + *, + output_dir: Path | None = None, + marker_cell: str = "J1", + marker_value: str = "wolfxl_modify_smoke", +) -> list[WolfXLFixtureValidation]: + """Run WolfXL read and in-place modify-save checks over a fixture pack. + + Args: + fixture_root: Directory containing `manifest.json` and generated + workbook files. + output_dir: Directory for WolfXL-modified copies. Defaults to + `fixture_root / "wolfxl-modified"`. + marker_cell: Cell written on the first sheet during modify-save. + marker_value: Value expected in `marker_cell` after save. + + Returns: + One validation result per manifest fixture entry. The function also + writes `wolfxl-validation.json` under `fixture_root`. + """ + fixture_root = fixture_root.resolve() + output_dir = (output_dir or fixture_root / "wolfxl-modified").resolve() + output_dir.mkdir(parents=True, exist_ok=True) + manifest = _load_manifest(fixture_root) + results: list[WolfXLFixtureValidation] = [] + + for entry in manifest.get("fixtures", []): + fixture_id = str(entry.get("fixture_id", "unknown")) + source_workbook = fixture_root / str(entry["workbook"]) + expected_parts = tuple(str(part) for part in entry.get("expected_parts", [])) + modified_workbook = output_dir / source_workbook.name + results.append( + _validate_one_fixture( + fixture_id=fixture_id, + source_workbook=source_workbook, + modified_workbook=modified_workbook, + expected_parts=expected_parts, + marker_cell=marker_cell, + marker_value=marker_value, + ) + ) + + payload = { + "fixture_root": str(fixture_root), + "results": [result.to_json_dict(fixture_root) for result in results], + "passed": all(result.passed for result in results), + } + (fixture_root / "wolfxl-validation.json").write_text( + json.dumps(payload, indent=2, sort_keys=True) + "\n" + ) + return results + + +def _validate_one_fixture( + *, + fixture_id: str, + source_workbook: Path, + modified_workbook: Path, + expected_parts: tuple[str, ...], + marker_cell: str, + marker_value: str, +) -> WolfXLFixtureValidation: + try: + import openpyxl + import wolfxl + + read_passed = False + marker_passed = False + shutil.copy2(source_workbook, modified_workbook) + before_parts = _zip_part_names(source_workbook) + tracked_parts = tuple(part for part in expected_parts if part in before_parts) + + workbook = wolfxl.load_workbook(modified_workbook, modify=True) + sheet_name = workbook.sheetnames[0] + workbook[sheet_name][marker_cell] = marker_value + read_passed = workbook[sheet_name]["A1"].value is not None + workbook.save(modified_workbook) + workbook.close() + + after_parts = _zip_part_names(modified_workbook) + missing_parts = tuple(part for part in tracked_parts if part not in after_parts) + roundtrip = openpyxl.load_workbook(modified_workbook) + marker_passed = roundtrip[sheet_name][marker_cell].value == marker_value + roundtrip.close() + return WolfXLFixtureValidation( + fixture_id=fixture_id, + source_workbook=source_workbook, + modified_workbook=modified_workbook, + expected_parts=expected_parts, + missing_parts_after_save=missing_parts, + read_passed=read_passed, + modify_save_passed=True, + marker_passed=marker_passed, + ) + except Exception as exc: + return WolfXLFixtureValidation( + fixture_id=fixture_id, + source_workbook=source_workbook, + modified_workbook=modified_workbook, + expected_parts=expected_parts, + missing_parts_after_save=(), + read_passed=False, + modify_save_passed=False, + marker_passed=False, + error=f"{type(exc).__name__}: {exc}", + ) + + +def _load_manifest(fixture_root: Path) -> JSONDict: + manifest_path = fixture_root / "manifest.json" + if not manifest_path.exists(): + raise FileNotFoundError(f"External fixture manifest not found: {manifest_path}") + return cast(JSONDict, json.loads(manifest_path.read_text())) + + +def _zip_part_names(path: Path) -> set[str]: + with ZipFile(path) as workbook_zip: + return set(workbook_zip.namelist()) + + +def _display_path(path: Path, root: Path) -> str: + try: + return str(path.relative_to(root)) + except ValueError: + return str(path) diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index 0434636..41494a7 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -2,6 +2,7 @@ from __future__ import annotations +import os import shutil from pathlib import Path from zipfile import ZipFile @@ -12,6 +13,9 @@ excelize_fixture_specs, generate_external_fixture_pack, ) +from excelbench.harness.external_wolfxl_validation import ( + validate_wolfxl_external_fixture_pack, +) def test_excelize_fixture_specs_are_stable() -> None: @@ -45,3 +49,23 @@ def test_generate_external_fixture_pack_without_validators(tmp_path: Path) -> No assert "xl/slicerCaches/slicerCache1.xml" in names assert "xl/charts/chart1.xml" in names + +@pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") +@pytest.mark.skipif( + os.environ.get("EXCELBENCH_RUN_WOLFXL_EXTERNAL") != "1", + reason="Set EXCELBENCH_RUN_WOLFXL_EXTERNAL=1 to validate installed WolfXL", +) +def test_validate_wolfxl_external_fixture_pack(tmp_path: Path) -> None: + pytest.importorskip("wolfxl") + repo_root = Path(__file__).resolve().parents[1] + generate_external_fixture_pack( + tmp_path, + repo_root=repo_root, + include_validators=False, + ) + + results = validate_wolfxl_external_fixture_pack(tmp_path) + + assert all(result.passed for result in results) + assert (tmp_path / "wolfxl-validation.json").exists() + assert all(not result.missing_parts_after_save for result in results) From c7f69d7a426c0110f7299efb45e3711c6b5034de Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 04:30:45 -0700 Subject: [PATCH 08/25] feat: add closedxml external oracle helper --- .gitignore | 4 + architecture.md | 2 + docs/trackers/external-oracle-expansion.md | 25 +- src/excelbench/harness/external_oracles.py | 16 +- tests/test_external_oracles.py | 62 ++++ tools/external-oracles/closedxml/Program.cs | 309 ++++++++++++++++++ tools/external-oracles/closedxml/README.md | 22 ++ .../closedxml/closedxml-oracle.csproj | 12 + 8 files changed, 449 insertions(+), 3 deletions(-) create mode 100644 tools/external-oracles/closedxml/Program.cs create mode 100644 tools/external-oracles/closedxml/README.md create mode 100644 tools/external-oracles/closedxml/closedxml-oracle.csproj diff --git a/.gitignore b/.gitignore index c49f0e7..6d879cc 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,10 @@ Cargo.lock !rust/**/Cargo.lock rust/**/uv.lock +# .NET external oracle build outputs +tools/external-oracles/**/bin/ +tools/external-oracles/**/obj/ + # Jupyter .ipynb_checkpoints/ diff --git a/architecture.md b/architecture.md index 2764f6b..8ef8d34 100644 --- a/architecture.md +++ b/architecture.md @@ -85,6 +85,8 @@ Most-touched top-level directories: Excelize. Run from that directory with `go run .`. - `libreoffice/`: Python helper that runs LibreOffice headless open/save and PDF render validation. + - `closedxml/`: .NET helper that generates workbooks with ClosedXML tables, + pivots, and conditional formatting. - `rust/excelbench_rust/` (optional, local-only) - PyO3 crate for ExcelBench-specific Rust backends (umya-spreadsheet, basic calamine) diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md index eb54ce0..9e5d7ca 100644 --- a/docs/trackers/external-oracle-expansion.md +++ b/docs/trackers/external-oracle-expansion.md @@ -30,8 +30,8 @@ not to replace the existing ExcelBench adapter matrix. - The helper catalog currently reserves entrypoints or source helpers for: - `tools/external-oracles/excelize` (`go run .`) - `tools/external-oracles/libreoffice/libreoffice_oracle.py` + - `tools/external-oracles/closedxml` (`dotnet run --project ...`) - `excelbench-poi-oracle` - - `excelbench-closedxml-oracle` ## Excelize helper @@ -85,14 +85,35 @@ Current smoke coverage: - WolfXL validator, 2026-04-28: both generated fixture-pack workbooks passed read + in-place modify-save preservation after WolfXL commit `634be84`. +## ClosedXML helper + +Implemented: `tools/external-oracles/closedxml` + +Supported operations: + +- `write_fixture`: writes sheets, cells, tables, conditional formats, and pivot + tables through ClosedXML. +- `read_metadata`: inspects package parts for tables, pivot tables, pivot + caches, and worksheets. + +Current smoke coverage: + +- .NET integration smoke writes a table + pivot + conditional-format workbook + through `run_external_oracle()` when `dotnet` is available. +- Manual ClosedXML truth pass, 2026-04-28: ClosedXML generated a workbook with + a table, pivot cache, pivot table, and conditional-format extension records. + WolfXL initially preserved the parts but inserted the smoke marker without + the worksheet namespace prefix; WolfXL commit `7640a3f` fixed the patcher, + and the same workbook now passes read + in-place modify-save preservation. + ## Candidate tools | Tool | Runtime | Initial role | Status | |---|---|---|---| | Excelize | Go | Generate xlsx fixtures for pivots, slicers, charts, conditional formatting, tables, rich formatting, images, and streaming paths. | Initial helper implemented. | | LibreOffice Calc | CLI / UNO | Open/save/render validator for corruption, repair, and visual/export smoke checks. | Initial helper implemented. | +| ClosedXML | .NET | Generate high-level table, pivot, conditional-formatting, and rich-cell fixtures. | Initial helper implemented; first manual truth pass complete; pending fixture-pack promotion. | | Apache POI | Java | Generate and inspect OOXML fixtures with a mature usermodel plus documented chart/pivot limits. | P1 after contract settles. | -| ClosedXML | .NET | Generate high-level table, pivot, conditional-formatting, and rich-cell fixtures. | P1 after contract settles. | | NPOI | .NET | POI-like .NET comparison if ClosedXML/POI leave .NET-specific gaps. | P2 research. | | SheetJS CE | JavaScript | Broad-format value/formula sanity checks; advanced styling/charts/pivots appear better suited to Pro. | P2 limited scope. | diff --git a/src/excelbench/harness/external_oracles.py b/src/excelbench/harness/external_oracles.py index f35f9cd..e636a2c 100644 --- a/src/excelbench/harness/external_oracles.py +++ b/src/excelbench/harness/external_oracles.py @@ -130,6 +130,20 @@ def external_oracle_catalog(repo_root: Path | None = None) -> dict[str, External sys.executable, str(repo_root / "tools" / "external-oracles" / "libreoffice" / "libreoffice_oracle.py"), ) + closedxml_command: tuple[str, ...] = ("excelbench-closedxml-oracle",) + if repo_root is not None: + closedxml_command = ( + "dotnet", + "run", + "--project", + str(repo_root / "tools" / "external-oracles" / "closedxml" / "closedxml-oracle.csproj"), + "--configuration", + "Release", + "--no-launch-profile", + "--verbosity", + "quiet", + "--", + ) return { "excelize": ExternalOracleTool( @@ -156,7 +170,7 @@ def external_oracle_catalog(repo_root: Path | None = None) -> dict[str, External ), "closedxml": ExternalOracleTool( name="closedxml", - command=("excelbench-closedxml-oracle",), + command=closedxml_command, language="dotnet", homepage="https://docs.closedxml.io/", capabilities=frozenset({"read", "write", "pivots", "conditional_formatting"}), diff --git a/tests/test_external_oracles.py b/tests/test_external_oracles.py index 7628a02..f190b23 100644 --- a/tests/test_external_oracles.py +++ b/tests/test_external_oracles.py @@ -35,6 +35,8 @@ def test_repo_catalog_points_excelize_at_go_helper() -> None: assert tool.cwd == repo_root / "tools" / "external-oracles" / "excelize" assert catalog["libreoffice"].command[0] == sys.executable assert catalog["libreoffice"].command[1].endswith("libreoffice_oracle.py") + assert catalog["closedxml"].command[0] == "dotnet" + assert any(part.endswith("closedxml-oracle.csproj") for part in catalog["closedxml"].command) def test_missing_oracle_helper_is_structured_skip() -> None: @@ -239,6 +241,66 @@ def test_excelize_go_helper_writes_advanced_fixture(tmp_path: Path) -> None: assert "xl/charts/chart1.xml" in names +@pytest.mark.skipif(shutil.which("dotnet") is None, reason=".NET SDK is required for ClosedXML") +def test_closedxml_dotnet_helper_writes_pivot_fixture(tmp_path: Path) -> None: + repo_root = Path(__file__).resolve().parents[1] + output_path = tmp_path / "closedxml-smoke.xlsx" + tool = external_oracle_catalog(repo_root=repo_root)["closedxml"] + + result = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="closedxml-smoke", + operation="write_fixture", + output_path=output_path, + payload={ + "sheets": [{"name": "Data"}, {"name": "Pivot"}], + "cells": [ + {"sheet": "Data", "cell": "A1", "value": "Region"}, + {"sheet": "Data", "cell": "B1", "value": "Product"}, + {"sheet": "Data", "cell": "C1", "value": "Sales"}, + {"sheet": "Data", "cell": "A2", "value": "West"}, + {"sheet": "Data", "cell": "B2", "value": "Widgets"}, + {"sheet": "Data", "cell": "C2", "value": 120}, + {"sheet": "Data", "cell": "A3", "value": "East"}, + {"sheet": "Data", "cell": "B3", "value": "Services"}, + {"sheet": "Data", "cell": "C3", "value": 95}, + {"sheet": "Data", "cell": "A4", "value": "West"}, + {"sheet": "Data", "cell": "B4", "value": "Services"}, + {"sheet": "Data", "cell": "C4", "value": 140}, + ], + "tables": [{"sheet": "Data", "range": "A1:C4", "name": "ClosedXmlSales"}], + "conditional_formats": [ + {"sheet": "Data", "range": "C2:C4", "type": "3_color_scale"}, + {"sheet": "Data", "range": "C2:C4", "type": "data_bar"}, + ], + "pivots": [ + { + "data_range": "Data!A1:C4", + "cell": "Pivot!A3", + "name": "ClosedXmlPivot", + "rows": [{"name": "Region"}], + "columns": [{"name": "Product"}], + "data": [{"name": "Sales"}], + } + ], + }, + ), + timeout_seconds=180, + ) + + assert result.passed is True, result + assert output_path.exists() + assert result.payload["counts"]["pivots"] == 1 + with ZipFile(output_path) as workbook: + names = set(workbook.namelist()) + sheet_xml = workbook.read("xl/worksheets/sheet1.xml").decode() + assert "xl/tables/table1.xml" in names + assert any(part.endswith("pivotTables/pivotTable.xml") for part in names) + assert any(part.endswith("pivotCache/pivotCacheDefinition1.xml") for part in names) + assert "conditionalFormatting" in sheet_xml + + def test_libreoffice_helper_renders_pdf_or_skips(tmp_path: Path) -> None: repo_root = Path(__file__).resolve().parents[1] input_path = tmp_path / "input.xlsx" diff --git a/tools/external-oracles/closedxml/Program.cs b/tools/external-oracles/closedxml/Program.cs new file mode 100644 index 0000000..551591d --- /dev/null +++ b/tools/external-oracles/closedxml/Program.cs @@ -0,0 +1,309 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using System.IO.Compression; +using ClosedXML.Excel; + +try +{ + using var stdin = Console.OpenStandardInput(); + var request = JsonSerializer.Deserialize(stdin, JsonOptions.Value) + ?? throw new InvalidOperationException("Empty oracle request."); + + var payload = request.Operation switch + { + "write_fixture" => WriteFixture(request), + "read_metadata" => ReadMetadata(request), + _ => throw new InvalidOperationException($"Unsupported operation '{request.Operation}'.") + }; + + Console.WriteLine(JsonSerializer.Serialize(payload, JsonOptions.Value)); + return 0; +} +catch (Exception ex) +{ + Console.WriteLine(JsonSerializer.Serialize(new + { + error = "closedxml_oracle_failed", + message = ex.Message + }, JsonOptions.Value)); + return 1; +} + +static object WriteFixture(OracleRequest request) +{ + if (string.IsNullOrWhiteSpace(request.OutputPath)) + { + throw new InvalidOperationException("write_fixture requires output_path."); + } + + var payload = request.Payload.Deserialize(JsonOptions.Value) + ?? new WritePayload(); + + Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(request.OutputPath))!); + using var workbook = new XLWorkbook(); + ConfigureSheets(workbook, payload.Sheets); + ApplyCells(workbook, payload.Cells); + ApplyTables(workbook, payload.Tables); + ApplyConditionalFormats(workbook, payload.ConditionalFormats); + ApplyPivots(workbook, payload.Pivots); + workbook.SaveAs(request.OutputPath); + + return new + { + fixture_id = request.FixtureId, + operation = request.Operation, + output_path = request.OutputPath, + tool = "closedxml", + counts = new + { + sheets = workbook.Worksheets.Count, + cells = payload.Cells.Count, + tables = payload.Tables.Count, + conditional_formats = payload.ConditionalFormats.Count, + pivots = payload.Pivots.Count + } + }; +} + +static object ReadMetadata(OracleRequest request) +{ + var inputPath = string.IsNullOrWhiteSpace(request.InputPath) + ? request.OutputPath + : request.InputPath; + if (string.IsNullOrWhiteSpace(inputPath)) + { + throw new InvalidOperationException("read_metadata requires input_path or output_path."); + } + + using var workbookPackage = ZipFile.OpenRead(inputPath); + var partNames = workbookPackage.Entries.Select(entry => entry.FullName).ToArray(); + return new + { + fixture_id = request.FixtureId, + operation = request.Operation, + input_path = inputPath, + tool = "closedxml", + counts = new + { + tables = CountParts(partNames, "tables/table"), + pivot_tables = CountParts(partNames, "pivotTables/pivotTable"), + pivot_caches = CountParts(partNames, "pivotCache/pivotCacheDefinition"), + worksheet_parts = CountParts(partNames, "worksheets/sheet") + } + }; +} + +static void ConfigureSheets(XLWorkbook workbook, List sheets) +{ + if (sheets.Count == 0) + { + workbook.Worksheets.Add("Sheet1"); + return; + } + + foreach (var sheet in sheets) + { + workbook.Worksheets.Add(sheet.Name); + } +} + +static void ApplyCells(XLWorkbook workbook, List cells) +{ + foreach (var cell in cells) + { + var target = workbook.Worksheet(cell.Sheet).Cell(cell.Cell); + if (cell.Type == "formula") + { + target.FormulaA1 = cell.Formula ?? ""; + continue; + } + + switch (cell.Value.ValueKind) + { + case JsonValueKind.String: + target.Value = cell.Value.GetString() ?? ""; + break; + case JsonValueKind.Number: + target.Value = cell.Value.TryGetInt64(out var integer) + ? integer + : cell.Value.GetDouble(); + break; + case JsonValueKind.True: + case JsonValueKind.False: + target.Value = cell.Value.GetBoolean(); + break; + case JsonValueKind.Null: + case JsonValueKind.Undefined: + break; + default: + target.Value = cell.Value.ToString(); + break; + } + } +} + +static void ApplyTables(XLWorkbook workbook, List tables) +{ + foreach (var table in tables) + { + var range = workbook.Worksheet(table.Sheet).Range(table.Range); + var created = range.CreateTable(table.Name); + created.Theme = XLTableTheme.TableStyleMedium2; + } +} + +static void ApplyConditionalFormats(XLWorkbook workbook, List formats) +{ + foreach (var format in formats) + { + var range = workbook.Worksheet(format.Sheet).Range(format.Range); + switch (format.Type) + { + case "3_color_scale": + range.AddConditionalFormat().ColorScale() + .LowestValue(XLColor.Red) + .Midpoint(XLCFContentType.Percent, 50, XLColor.Yellow) + .HighestValue(XLColor.Green); + break; + case "data_bar": + range.AddConditionalFormat().DataBar(XLColor.Blue) + .LowestValue() + .HighestValue(); + break; + case "cell": + ApplyCellRule(range, format); + break; + } + } +} + +static void ApplyCellRule(IXLRange range, ConditionalFormatSpec format) +{ + if (!double.TryParse(format.Value, out var value)) + { + value = 0; + } + + var rule = format.Criteria switch + { + ">" => range.AddConditionalFormat().WhenGreaterThan(value), + "<" => range.AddConditionalFormat().WhenLessThan(value), + ">=" => range.AddConditionalFormat().WhenEqualOrGreaterThan(value), + "<=" => range.AddConditionalFormat().WhenEqualOrLessThan(value), + _ => range.AddConditionalFormat().WhenGreaterThan(value) + }; + rule.Fill.SetBackgroundColor(XLColor.LightGreen); +} + +static void ApplyPivots(XLWorkbook workbook, List pivots) +{ + foreach (var pivot in pivots) + { + var sourceRange = ResolveRange(workbook, pivot.DataRange); + var (targetSheet, targetCell) = ResolveCell(workbook, pivot.Cell); + var created = targetSheet.PivotTables.Add(pivot.Name, targetCell, sourceRange); + foreach (var row in pivot.Rows) + { + created.RowLabels.Add(row.Name); + } + foreach (var column in pivot.Columns) + { + created.ColumnLabels.Add(column.Name); + } + foreach (var value in pivot.Data) + { + created.Values.Add(value.Name).SetSummaryFormula(XLPivotSummary.Sum); + } + } +} + +static IXLRange ResolveRange(XLWorkbook workbook, string reference) +{ + var parts = reference.Split('!', 2); + if (parts.Length != 2) + { + throw new InvalidOperationException($"Expected sheet-qualified range: {reference}"); + } + return workbook.Worksheet(parts[0]).Range(parts[1]); +} + +static (IXLWorksheet Sheet, IXLCell Cell) ResolveCell(XLWorkbook workbook, string reference) +{ + var parts = reference.Split('!', 2); + if (parts.Length != 2) + { + throw new InvalidOperationException($"Expected sheet-qualified cell: {reference}"); + } + var sheet = workbook.Worksheet(parts[0]); + return (sheet, sheet.Cell(parts[1])); +} + +static int CountParts(IEnumerable partNames, string prefix) +{ + return partNames.Count(part => part.Contains(prefix, StringComparison.Ordinal)); +} + +sealed record OracleRequest( + [property: JsonPropertyName("fixture_id")] string FixtureId, + [property: JsonPropertyName("operation")] string Operation, + [property: JsonPropertyName("payload")] JsonElement Payload, + [property: JsonPropertyName("input_path")] string? InputPath, + [property: JsonPropertyName("output_path")] string? OutputPath); + +sealed class WritePayload +{ + [JsonPropertyName("sheets")] + public List Sheets { get; set; } = []; + + [JsonPropertyName("cells")] + public List Cells { get; set; } = []; + + [JsonPropertyName("tables")] + public List Tables { get; set; } = []; + + [JsonPropertyName("conditional_formats")] + public List ConditionalFormats { get; set; } = []; + + [JsonPropertyName("pivots")] + public List Pivots { get; set; } = []; +} + +sealed record SheetSpec([property: JsonPropertyName("name")] string Name); + +sealed record CellSpec( + [property: JsonPropertyName("sheet")] string Sheet, + [property: JsonPropertyName("cell")] string Cell, + [property: JsonPropertyName("type")] string? Type, + [property: JsonPropertyName("value")] JsonElement Value, + [property: JsonPropertyName("formula")] string? Formula); + +sealed record TableSpec( + [property: JsonPropertyName("sheet")] string Sheet, + [property: JsonPropertyName("range")] string Range, + [property: JsonPropertyName("name")] string Name); + +sealed record ConditionalFormatSpec( + [property: JsonPropertyName("sheet")] string Sheet, + [property: JsonPropertyName("range")] string Range, + [property: JsonPropertyName("type")] string Type, + [property: JsonPropertyName("criteria")] string? Criteria, + [property: JsonPropertyName("value")] string? Value); + +sealed record PivotSpec( + [property: JsonPropertyName("data_range")] string DataRange, + [property: JsonPropertyName("cell")] string Cell, + [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("rows")] List Rows, + [property: JsonPropertyName("columns")] List Columns, + [property: JsonPropertyName("data")] List Data); + +sealed record PivotField([property: JsonPropertyName("name")] string Name); + +static class JsonOptions +{ + public static readonly JsonSerializerOptions Value = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + WriteIndented = false + }; +} diff --git a/tools/external-oracles/closedxml/README.md b/tools/external-oracles/closedxml/README.md new file mode 100644 index 0000000..dfb1661 --- /dev/null +++ b/tools/external-oracles/closedxml/README.md @@ -0,0 +1,22 @@ +# ClosedXML External Oracle + +This optional helper lets ExcelBench generate .NET/ClosedXML workbooks through +the same JSON stdin/stdout contract as the other external oracles. + +Run through the catalog from the repository root: + +```bash +dotnet run --project tools/external-oracles/closedxml/closedxml-oracle.csproj --configuration Release --no-launch-profile --verbosity quiet -- +``` + +Supported operations: + +- `write_fixture`: writes workbook sheets, cells, tables, conditional formats, + and pivot tables from the JSON request. +- `read_metadata`: inspects workbook package parts for table, pivot table, + pivot cache, and worksheet counts. + +ClosedXML is intentionally kept as an optional pre-release oracle. It is not a +normal ExcelBench adapter and should not become a public benchmark claim until +generated cases pass the manual truth/promote gates in +`docs/trackers/external-oracle-expansion.md`. diff --git a/tools/external-oracles/closedxml/closedxml-oracle.csproj b/tools/external-oracles/closedxml/closedxml-oracle.csproj new file mode 100644 index 0000000..6177645 --- /dev/null +++ b/tools/external-oracles/closedxml/closedxml-oracle.csproj @@ -0,0 +1,12 @@ + + + Exe + net9.0 + enable + enable + + + + + + From 7b9007b95a360812372d5c049d0dc8986a415f8f Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 04:33:00 -0700 Subject: [PATCH 09/25] feat: promote closedxml external fixture --- docs/trackers/external-oracle-expansion.md | 8 ++- .../harness/external_fixture_pack.py | 59 ++++++++++++++++++- tests/test_external_fixture_pack.py | 24 ++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md index 9e5d7ca..e23097b 100644 --- a/docs/trackers/external-oracle-expansion.md +++ b/docs/trackers/external-oracle-expansion.md @@ -105,6 +105,8 @@ Current smoke coverage: WolfXL initially preserved the parts but inserted the smoke marker without the worksheet namespace prefix; WolfXL commit `7640a3f` fixed the patcher, and the same workbook now passes read + in-place modify-save preservation. +- Fixture-pack promotion, 2026-04-28: `closedxml_pivot_cf_table` is included + when `dotnet` is available. ## Candidate tools @@ -135,8 +137,10 @@ Fixtures: picture. **Implemented.** 2. `excelize_chart_points_formula_cf`: per-point chart styling, formula cell, data bar, and cell-rule conditional formatting. **Implemented.** -6. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** -7. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** +3. `closedxml_pivot_cf_table`: ClosedXML table + pivot cache/table + + conditional-formatting package layout. **Implemented.** +4. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** +5. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** ## Promotion gates diff --git a/src/excelbench/harness/external_fixture_pack.py b/src/excelbench/harness/external_fixture_pack.py index 89b3ca7..0e26e02 100644 --- a/src/excelbench/harness/external_fixture_pack.py +++ b/src/excelbench/harness/external_fixture_pack.py @@ -211,6 +211,61 @@ def excelize_fixture_specs() -> list[ExternalFixtureSpec]: ] +def closedxml_fixture_specs() -> list[ExternalFixtureSpec]: + """Return the initial ClosedXML fixture pack.""" + return [ + ExternalFixtureSpec( + fixture_id="closedxml_pivot_cf_table", + tool="closedxml", + filename="closedxml-pivot-cf-table.xlsx", + payload={ + "sheets": [{"name": "Data"}, {"name": "Pivot"}], + "cells": [ + {"sheet": "Data", "cell": "A1", "value": "Region"}, + {"sheet": "Data", "cell": "B1", "value": "Product"}, + {"sheet": "Data", "cell": "C1", "value": "Sales"}, + {"sheet": "Data", "cell": "A2", "value": "West"}, + {"sheet": "Data", "cell": "B2", "value": "Widgets"}, + {"sheet": "Data", "cell": "C2", "value": 120}, + {"sheet": "Data", "cell": "A3", "value": "East"}, + {"sheet": "Data", "cell": "B3", "value": "Services"}, + {"sheet": "Data", "cell": "C3", "value": 95}, + {"sheet": "Data", "cell": "A4", "value": "West"}, + {"sheet": "Data", "cell": "B4", "value": "Services"}, + {"sheet": "Data", "cell": "C4", "value": 140}, + ], + "tables": [{"sheet": "Data", "range": "A1:C4", "name": "ClosedXmlSales"}], + "conditional_formats": [ + {"sheet": "Data", "range": "C2:C4", "type": "3_color_scale"}, + {"sheet": "Data", "range": "C2:C4", "type": "data_bar"}, + ], + "pivots": [ + { + "data_range": "Data!A1:C4", + "cell": "Pivot!A3", + "name": "ClosedXmlPivot", + "rows": [{"name": "Region"}], + "columns": [{"name": "Product"}], + "data": [{"name": "Sales"}], + } + ], + }, + expected_parts=( + "xl/tables/table1.xml", + "xl/pivotTables/pivotTable.xml", + "pivotCache/pivotCacheDefinition1.xml", + "xl/worksheets/sheet1.xml", + ), + notes="ClosedXML pivot/cache/table/conditional-formatting package layout.", + ) + ] + + +def external_fixture_specs() -> list[ExternalFixtureSpec]: + """Return all implemented external fixture specifications.""" + return [*excelize_fixture_specs(), *closedxml_fixture_specs()] + + def generate_external_fixture_pack( output_root: Path, *, @@ -224,7 +279,9 @@ def generate_external_fixture_pack( catalog = external_oracle_catalog(repo_root=repo_root) results: list[FixtureGenerationResult] = [] - for spec in excelize_fixture_specs(): + for spec in external_fixture_specs(): + if not catalog[spec.tool].is_available(): + continue workbook_path = output_root / spec.filename write_result = run_external_oracle( catalog[spec.tool], diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index 41494a7..cb422a7 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -10,7 +10,9 @@ import pytest from excelbench.harness.external_fixture_pack import ( + closedxml_fixture_specs, excelize_fixture_specs, + external_fixture_specs, generate_external_fixture_pack, ) from excelbench.harness.external_wolfxl_validation import ( @@ -30,6 +32,20 @@ def test_excelize_fixture_specs_are_stable() -> None: assert "xl/charts/chart1.xml" in specs[1].expected_parts +def test_closedxml_fixture_specs_are_stable() -> None: + specs = closedxml_fixture_specs() + + assert [spec.fixture_id for spec in specs] == ["closedxml_pivot_cf_table"] + assert all(spec.tool == "closedxml" for spec in specs) + assert "xl/pivotTables/pivotTable.xml" in specs[0].expected_parts + assert "pivotCache/pivotCacheDefinition1.xml" in specs[0].expected_parts + assert [spec.fixture_id for spec in external_fixture_specs()] == [ + "excelize_sales_pivot_slicer_chart", + "excelize_chart_points_formula_cf", + "closedxml_pivot_cf_table", + ] + + @pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") def test_generate_external_fixture_pack_without_validators(tmp_path: Path) -> None: repo_root = Path(__file__).resolve().parents[1] @@ -48,6 +64,14 @@ def test_generate_external_fixture_pack_without_validators(tmp_path: Path) -> No assert "xl/pivotTables/pivotTable1.xml" in names assert "xl/slicerCaches/slicerCache1.xml" in names assert "xl/charts/chart1.xml" in names + if shutil.which("dotnet") is not None: + closedxml_fixture = tmp_path / "closedxml-pivot-cf-table.xlsx" + assert closedxml_fixture.exists() + with ZipFile(closedxml_fixture) as workbook: + closedxml_names = set(workbook.namelist()) + assert "xl/tables/table1.xml" in closedxml_names + assert "xl/pivotTables/pivotTable.xml" in closedxml_names + assert "pivotCache/pivotCacheDefinition1.xml" in closedxml_names @pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") From 36006b347659ac49b133687497709b61dac01d91 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:38:52 -0700 Subject: [PATCH 10/25] feat: expand closedxml rich feature fixtures --- docs/trackers/external-oracle-expansion.md | 21 +++-- .../harness/external_fixture_pack.py | 42 ++++++++- tests/test_external_fixture_pack.py | 18 +++- tests/test_external_oracles.py | 27 ++++++ tools/external-oracles/closedxml/Program.cs | 93 ++++++++++++++++++- 5 files changed, 191 insertions(+), 10 deletions(-) diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md index e23097b..db22b08 100644 --- a/docs/trackers/external-oracle-expansion.md +++ b/docs/trackers/external-oracle-expansion.md @@ -92,14 +92,15 @@ Implemented: `tools/external-oracles/closedxml` Supported operations: - `write_fixture`: writes sheets, cells, tables, conditional formats, and pivot - tables through ClosedXML. + tables, rich text, comments, and sheet protection through ClosedXML. - `read_metadata`: inspects package parts for tables, pivot tables, pivot - caches, and worksheets. + caches, comments, VML drawings, and worksheets. Current smoke coverage: -- .NET integration smoke writes a table + pivot + conditional-format workbook - through `run_external_oracle()` when `dotnet` is available. +- .NET integration smoke writes a table + pivot + conditional-format + rich + text + comment + sheet-protection workbook through `run_external_oracle()` + when `dotnet` is available. - Manual ClosedXML truth pass, 2026-04-28: ClosedXML generated a workbook with a table, pivot cache, pivot table, and conditional-format extension records. WolfXL initially preserved the parts but inserted the smoke marker without @@ -107,6 +108,10 @@ Current smoke coverage: and the same workbook now passes read + in-place modify-save preservation. - Fixture-pack promotion, 2026-04-28: `closedxml_pivot_cf_table` is included when `dotnet` is available. +- Fixture-pack expansion, 2026-04-28: `closedxml_rich_comment_protection` + covers rich shared strings, legacy comments, VML comment drawing parts, and + sheet protection. The full four-workbook pack passes LibreOffice + open/render validation and WolfXL read + in-place modify-save preservation. ## Candidate tools @@ -114,7 +119,7 @@ Current smoke coverage: |---|---|---|---| | Excelize | Go | Generate xlsx fixtures for pivots, slicers, charts, conditional formatting, tables, rich formatting, images, and streaming paths. | Initial helper implemented. | | LibreOffice Calc | CLI / UNO | Open/save/render validator for corruption, repair, and visual/export smoke checks. | Initial helper implemented. | -| ClosedXML | .NET | Generate high-level table, pivot, conditional-formatting, and rich-cell fixtures. | Initial helper implemented; first manual truth pass complete; pending fixture-pack promotion. | +| ClosedXML | .NET | Generate high-level table, pivot, conditional-formatting, and rich-cell fixtures. | Initial helper implemented; first two fixture-pack cases passing. | | Apache POI | Java | Generate and inspect OOXML fixtures with a mature usermodel plus documented chart/pivot limits. | P1 after contract settles. | | NPOI | .NET | POI-like .NET comparison if ClosedXML/POI leave .NET-specific gaps. | P2 research. | | SheetJS CE | JavaScript | Broad-format value/formula sanity checks; advanced styling/charts/pivots appear better suited to Pro. | P2 limited scope. | @@ -139,8 +144,10 @@ Fixtures: data bar, and cell-rule conditional formatting. **Implemented.** 3. `closedxml_pivot_cf_table`: ClosedXML table + pivot cache/table + conditional-formatting package layout. **Implemented.** -4. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** -5. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** +4. `closedxml_rich_comment_protection`: ClosedXML rich shared strings, legacy + comments, VML comment drawings, and sheet protection. **Implemented.** +5. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** +6. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** ## Promotion gates diff --git a/src/excelbench/harness/external_fixture_pack.py b/src/excelbench/harness/external_fixture_pack.py index 0e26e02..0b7a854 100644 --- a/src/excelbench/harness/external_fixture_pack.py +++ b/src/excelbench/harness/external_fixture_pack.py @@ -257,7 +257,47 @@ def closedxml_fixture_specs() -> list[ExternalFixtureSpec]: "xl/worksheets/sheet1.xml", ), notes="ClosedXML pivot/cache/table/conditional-formatting package layout.", - ) + ), + ExternalFixtureSpec( + fixture_id="closedxml_rich_comment_protection", + tool="closedxml", + filename="closedxml-rich-comment-protection.xlsx", + payload={ + "sheets": [{"name": "Review"}], + "cells": [ + {"sheet": "Review", "cell": "A1", "value": "Finding"}, + {"sheet": "Review", "cell": "B1", "value": "Status"}, + {"sheet": "Review", "cell": "A2", "value": "Revenue cutoff"}, + {"sheet": "Review", "cell": "B2", "value": "Open"}, + ], + "rich_text": [ + { + "sheet": "Review", + "cell": "A4", + "runs": [ + {"text": "Priority: ", "bold": True, "font_color": "#C00000"}, + {"text": "management response needed", "italic": True}, + ], + } + ], + "comments": [ + { + "sheet": "Review", + "cell": "B2", + "text": "Tie this status to final PBC evidence.", + "author": "ClosedXML Oracle", + } + ], + "protection": [{"sheet": "Review", "password": "audit"}], + }, + expected_parts=( + "xl/sharedStrings.xml", + "xl/comments1.xml", + "xl/drawings/vmldrawing.vml", + "xl/worksheets/sheet1.xml", + ), + notes="ClosedXML rich text, legacy comments, and sheet protection.", + ), ] diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index cb422a7..9c83f86 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -35,14 +35,20 @@ def test_excelize_fixture_specs_are_stable() -> None: def test_closedxml_fixture_specs_are_stable() -> None: specs = closedxml_fixture_specs() - assert [spec.fixture_id for spec in specs] == ["closedxml_pivot_cf_table"] + assert [spec.fixture_id for spec in specs] == [ + "closedxml_pivot_cf_table", + "closedxml_rich_comment_protection", + ] assert all(spec.tool == "closedxml" for spec in specs) assert "xl/pivotTables/pivotTable.xml" in specs[0].expected_parts assert "pivotCache/pivotCacheDefinition1.xml" in specs[0].expected_parts + assert "xl/comments1.xml" in specs[1].expected_parts + assert "xl/drawings/vmldrawing.vml" in specs[1].expected_parts assert [spec.fixture_id for spec in external_fixture_specs()] == [ "excelize_sales_pivot_slicer_chart", "excelize_chart_points_formula_cf", "closedxml_pivot_cf_table", + "closedxml_rich_comment_protection", ] @@ -72,6 +78,16 @@ def test_generate_external_fixture_pack_without_validators(tmp_path: Path) -> No assert "xl/tables/table1.xml" in closedxml_names assert "xl/pivotTables/pivotTable.xml" in closedxml_names assert "pivotCache/pivotCacheDefinition1.xml" in closedxml_names + rich_fixture = tmp_path / "closedxml-rich-comment-protection.xlsx" + assert rich_fixture.exists() + with ZipFile(rich_fixture) as workbook: + rich_names = set(workbook.namelist()) + sheet_xml = workbook.read("xl/worksheets/sheet1.xml").decode() + shared_strings_xml = workbook.read("xl/sharedStrings.xml").decode() + assert "xl/comments1.xml" in rich_names + assert "xl/drawings/vmldrawing.vml" in rich_names + assert "sheetProtection" in sheet_xml + assert ":r>" in shared_strings_xml @pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") diff --git a/tests/test_external_oracles.py b/tests/test_external_oracles.py index f190b23..bb49255 100644 --- a/tests/test_external_oracles.py +++ b/tests/test_external_oracles.py @@ -274,6 +274,25 @@ def test_closedxml_dotnet_helper_writes_pivot_fixture(tmp_path: Path) -> None: {"sheet": "Data", "range": "C2:C4", "type": "3_color_scale"}, {"sheet": "Data", "range": "C2:C4", "type": "data_bar"}, ], + "rich_text": [ + { + "sheet": "Data", + "cell": "E1", + "runs": [ + {"text": "Review ", "bold": True, "font_color": "#C00000"}, + {"text": "note", "italic": True}, + ], + } + ], + "comments": [ + { + "sheet": "Data", + "cell": "C2", + "text": "ClosedXML comment smoke.", + "author": "ExcelBench", + } + ], + "protection": [{"sheet": "Data", "password": "audit"}], "pivots": [ { "data_range": "Data!A1:C4", @@ -292,13 +311,21 @@ def test_closedxml_dotnet_helper_writes_pivot_fixture(tmp_path: Path) -> None: assert result.passed is True, result assert output_path.exists() assert result.payload["counts"]["pivots"] == 1 + assert result.payload["counts"]["comments"] == 1 + assert result.payload["counts"]["rich_text"] == 1 + assert result.payload["counts"]["protected_sheets"] == 1 with ZipFile(output_path) as workbook: names = set(workbook.namelist()) sheet_xml = workbook.read("xl/worksheets/sheet1.xml").decode() + shared_strings_xml = workbook.read("xl/sharedStrings.xml").decode() assert "xl/tables/table1.xml" in names + assert "xl/comments1.xml" in names + assert "xl/drawings/vmldrawing.vml" in names assert any(part.endswith("pivotTables/pivotTable.xml") for part in names) assert any(part.endswith("pivotCache/pivotCacheDefinition1.xml") for part in names) assert "conditionalFormatting" in sheet_xml + assert "sheetProtection" in sheet_xml + assert ":r>" in shared_strings_xml def test_libreoffice_helper_renders_pdf_or_skips(tmp_path: Path) -> None: diff --git a/tools/external-oracles/closedxml/Program.cs b/tools/external-oracles/closedxml/Program.cs index 551591d..3c4aacf 100644 --- a/tools/external-oracles/closedxml/Program.cs +++ b/tools/external-oracles/closedxml/Program.cs @@ -43,9 +43,12 @@ static object WriteFixture(OracleRequest request) using var workbook = new XLWorkbook(); ConfigureSheets(workbook, payload.Sheets); ApplyCells(workbook, payload.Cells); + ApplyRichText(workbook, payload.RichText); + ApplyComments(workbook, payload.Comments); ApplyTables(workbook, payload.Tables); ApplyConditionalFormats(workbook, payload.ConditionalFormats); ApplyPivots(workbook, payload.Pivots); + ApplyProtection(workbook, payload.Protection); workbook.SaveAs(request.OutputPath); return new @@ -58,9 +61,12 @@ static object WriteFixture(OracleRequest request) { sheets = workbook.Worksheets.Count, cells = payload.Cells.Count, + rich_text = payload.RichText.Count, + comments = payload.Comments.Count, tables = payload.Tables.Count, conditional_formats = payload.ConditionalFormats.Count, - pivots = payload.Pivots.Count + pivots = payload.Pivots.Count, + protected_sheets = payload.Protection.Count } }; } @@ -88,6 +94,8 @@ static object ReadMetadata(OracleRequest request) tables = CountParts(partNames, "tables/table"), pivot_tables = CountParts(partNames, "pivotTables/pivotTable"), pivot_caches = CountParts(partNames, "pivotCache/pivotCacheDefinition"), + comments = CountParts(partNames, "comments"), + vml_drawings = CountParts(partNames, "drawings/vmlDrawing"), worksheet_parts = CountParts(partNames, "worksheets/sheet") } }; @@ -142,6 +150,43 @@ static void ApplyCells(XLWorkbook workbook, List cells) } } +static void ApplyRichText(XLWorkbook workbook, List items) +{ + foreach (var item in items) + { + var richText = workbook.Worksheet(item.Sheet).Cell(item.Cell).CreateRichText(); + foreach (var run in item.Runs) + { + var richString = richText.AddText(run.Text); + if (run.Bold) + { + richString.SetBold(); + } + if (run.Italic) + { + richString.SetItalic(); + } + if (!string.IsNullOrWhiteSpace(run.FontColor)) + { + richString.SetFontColor(XLColor.FromHtml(run.FontColor)); + } + } + } +} + +static void ApplyComments(XLWorkbook workbook, List comments) +{ + foreach (var item in comments) + { + var comment = workbook.Worksheet(item.Sheet).Cell(item.Cell).CreateComment(); + if (!string.IsNullOrWhiteSpace(item.Author)) + { + comment.Author = item.Author; + } + comment.AddText(item.Text); + } +} + static void ApplyTables(XLWorkbook workbook, List tables) { foreach (var table in tables) @@ -217,6 +262,22 @@ static void ApplyPivots(XLWorkbook workbook, List pivots) } } +static void ApplyProtection(XLWorkbook workbook, List protections) +{ + foreach (var protection in protections) + { + var worksheet = workbook.Worksheet(protection.Sheet); + if (string.IsNullOrWhiteSpace(protection.Password)) + { + worksheet.Protect(); + } + else + { + worksheet.Protect(protection.Password); + } + } +} + static IXLRange ResolveRange(XLWorkbook workbook, string reference) { var parts = reference.Split('!', 2); @@ -258,6 +319,12 @@ sealed class WritePayload [JsonPropertyName("cells")] public List Cells { get; set; } = []; + [JsonPropertyName("rich_text")] + public List RichText { get; set; } = []; + + [JsonPropertyName("comments")] + public List Comments { get; set; } = []; + [JsonPropertyName("tables")] public List Tables { get; set; } = []; @@ -266,6 +333,9 @@ sealed class WritePayload [JsonPropertyName("pivots")] public List Pivots { get; set; } = []; + + [JsonPropertyName("protection")] + public List Protection { get; set; } = []; } sealed record SheetSpec([property: JsonPropertyName("name")] string Name); @@ -277,6 +347,23 @@ sealed record CellSpec( [property: JsonPropertyName("value")] JsonElement Value, [property: JsonPropertyName("formula")] string? Formula); +sealed record RichTextSpec( + [property: JsonPropertyName("sheet")] string Sheet, + [property: JsonPropertyName("cell")] string Cell, + [property: JsonPropertyName("runs")] List Runs); + +sealed record RichRunSpec( + [property: JsonPropertyName("text")] string Text, + [property: JsonPropertyName("bold")] bool Bold, + [property: JsonPropertyName("italic")] bool Italic, + [property: JsonPropertyName("font_color")] string? FontColor); + +sealed record CommentSpec( + [property: JsonPropertyName("sheet")] string Sheet, + [property: JsonPropertyName("cell")] string Cell, + [property: JsonPropertyName("text")] string Text, + [property: JsonPropertyName("author")] string? Author); + sealed record TableSpec( [property: JsonPropertyName("sheet")] string Sheet, [property: JsonPropertyName("range")] string Range, @@ -299,6 +386,10 @@ sealed record PivotSpec( sealed record PivotField([property: JsonPropertyName("name")] string Name); +sealed record ProtectionSpec( + [property: JsonPropertyName("sheet")] string Sheet, + [property: JsonPropertyName("password")] string? Password); + static class JsonOptions { public static readonly JsonSerializerOptions Value = new() From d2c21babebc907ccf52db4c74b2fa4125e602d59 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:56:18 -0700 Subject: [PATCH 11/25] feat: add npoi external oracle fixture --- architecture.md | 2 + docs/trackers/external-oracle-expansion.md | 31 +- .../harness/external_fixture_pack.py | 54 +++- src/excelbench/harness/external_oracles.py | 21 ++ tests/test_external_fixture_pack.py | 22 ++ tests/test_external_oracles.py | 79 ++++- tools/external-oracles/npoi/Program.cs | 292 ++++++++++++++++++ tools/external-oracles/npoi/README.md | 21 ++ .../external-oracles/npoi/npoi-oracle.csproj | 12 + 9 files changed, 529 insertions(+), 5 deletions(-) create mode 100644 tools/external-oracles/npoi/Program.cs create mode 100644 tools/external-oracles/npoi/README.md create mode 100644 tools/external-oracles/npoi/npoi-oracle.csproj diff --git a/architecture.md b/architecture.md index 8ef8d34..b225576 100644 --- a/architecture.md +++ b/architecture.md @@ -87,6 +87,8 @@ Most-touched top-level directories: PDF render validation. - `closedxml/`: .NET helper that generates workbooks with ClosedXML tables, pivots, and conditional formatting. + - `npoi/`: .NET helper that generates POI-style workbooks with NPOI formulas, + comments, rich text, merged ranges, and sheet protection. - `rust/excelbench_rust/` (optional, local-only) - PyO3 crate for ExcelBench-specific Rust backends (umya-spreadsheet, basic calamine) diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md index db22b08..e23928b 100644 --- a/docs/trackers/external-oracle-expansion.md +++ b/docs/trackers/external-oracle-expansion.md @@ -31,6 +31,7 @@ not to replace the existing ExcelBench adapter matrix. - `tools/external-oracles/excelize` (`go run .`) - `tools/external-oracles/libreoffice/libreoffice_oracle.py` - `tools/external-oracles/closedxml` (`dotnet run --project ...`) + - `tools/external-oracles/npoi` (`dotnet run --project ...`) - `excelbench-poi-oracle` ## Excelize helper @@ -113,6 +114,28 @@ Current smoke coverage: sheet protection. The full four-workbook pack passes LibreOffice open/render validation and WolfXL read + in-place modify-save preservation. +## NPOI helper + +Implemented: `tools/external-oracles/npoi` + +Supported operations: + +- `write_fixture`: writes sheets, cells, formulas, rich text, legacy comments, + merged ranges, and sheet protection through NPOI. +- `read_metadata`: inspects package parts for worksheets, shared strings, + comments, VML drawings, and calc-chain metadata. + +Current smoke coverage: + +- .NET integration smoke writes a formula + rich text + comment + merged range + + sheet-protection workbook through `run_external_oracle()` when `dotnet` is + available. +- Fixture-pack promotion, 2026-04-28: `npoi_formula_comment_merge_protection` + is included when `dotnet` is available. +- Manual NPOI truth pass, 2026-04-28: the full five-workbook pack, including + the NPOI formula/comment/rich-text/merge/protection case, passes LibreOffice + open/render validation and WolfXL read + in-place modify-save preservation. + ## Candidate tools | Tool | Runtime | Initial role | Status | @@ -121,7 +144,7 @@ Current smoke coverage: | LibreOffice Calc | CLI / UNO | Open/save/render validator for corruption, repair, and visual/export smoke checks. | Initial helper implemented. | | ClosedXML | .NET | Generate high-level table, pivot, conditional-formatting, and rich-cell fixtures. | Initial helper implemented; first two fixture-pack cases passing. | | Apache POI | Java | Generate and inspect OOXML fixtures with a mature usermodel plus documented chart/pivot limits. | P1 after contract settles. | -| NPOI | .NET | POI-like .NET comparison if ClosedXML/POI leave .NET-specific gaps. | P2 research. | +| NPOI | .NET | POI-like .NET comparison for formulas, comments, rich strings, merged ranges, and protection. | Initial helper implemented; first fixture-pack case passing. | | SheetJS CE | JavaScript | Broad-format value/formula sanity checks; advanced styling/charts/pivots appear better suited to Pro. | P2 limited scope. | ## First fixture pack @@ -146,8 +169,10 @@ Fixtures: conditional-formatting package layout. **Implemented.** 4. `closedxml_rich_comment_protection`: ClosedXML rich shared strings, legacy comments, VML comment drawings, and sheet protection. **Implemented.** -5. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** -6. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** +5. `npoi_formula_comment_merge_protection`: NPOI formula cell, rich shared + string, legacy comment, merged range, and sheet protection. **Implemented.** +6. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** +7. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** ## Promotion gates diff --git a/src/excelbench/harness/external_fixture_pack.py b/src/excelbench/harness/external_fixture_pack.py index 0b7a854..e17d36a 100644 --- a/src/excelbench/harness/external_fixture_pack.py +++ b/src/excelbench/harness/external_fixture_pack.py @@ -301,9 +301,61 @@ def closedxml_fixture_specs() -> list[ExternalFixtureSpec]: ] +def npoi_fixture_specs() -> list[ExternalFixtureSpec]: + """Return the initial NPOI fixture pack.""" + return [ + ExternalFixtureSpec( + fixture_id="npoi_formula_comment_merge_protection", + tool="npoi", + filename="npoi-formula-comment-merge-protection.xlsx", + payload={ + "sheets": [{"name": "NPOI"}], + "cells": [ + {"sheet": "NPOI", "cell": "A1", "value": "Account"}, + {"sheet": "NPOI", "cell": "B1", "value": "Amount"}, + {"sheet": "NPOI", "cell": "A2", "value": "Revenue"}, + {"sheet": "NPOI", "cell": "B2", "value": 1250}, + {"sheet": "NPOI", "cell": "A3", "value": "COGS"}, + {"sheet": "NPOI", "cell": "B3", "value": -400}, + {"sheet": "NPOI", "cell": "A4", "value": "Gross profit"}, + {"sheet": "NPOI", "cell": "B4", "type": "formula", "formula": "SUM(B2:B3)"}, + {"sheet": "NPOI", "cell": "D1", "value": "Merged review header"}, + ], + "rich_text": [ + { + "sheet": "NPOI", + "cell": "D3", + "runs": [ + {"text": "NPOI ", "bold": True}, + {"text": "rich text", "italic": True}, + ], + } + ], + "comments": [ + { + "sheet": "NPOI", + "cell": "B4", + "text": "Formula result should preserve calc metadata.", + "author": "NPOI Oracle", + } + ], + "merged_ranges": [{"sheet": "NPOI", "range": "D1:F1"}], + "protection": [{"sheet": "NPOI", "password": "audit"}], + }, + expected_parts=( + "xl/sharedStrings.xml", + "xl/comments1.xml", + "xl/drawings/vmlDrawing1.vml", + "xl/worksheets/sheet1.xml", + ), + notes="NPOI formula, rich text, legacy comments, merged range, and protection.", + ) + ] + + def external_fixture_specs() -> list[ExternalFixtureSpec]: """Return all implemented external fixture specifications.""" - return [*excelize_fixture_specs(), *closedxml_fixture_specs()] + return [*excelize_fixture_specs(), *closedxml_fixture_specs(), *npoi_fixture_specs()] def generate_external_fixture_pack( diff --git a/src/excelbench/harness/external_oracles.py b/src/excelbench/harness/external_oracles.py index e636a2c..fc92a4c 100644 --- a/src/excelbench/harness/external_oracles.py +++ b/src/excelbench/harness/external_oracles.py @@ -144,6 +144,20 @@ def external_oracle_catalog(repo_root: Path | None = None) -> dict[str, External "quiet", "--", ) + npoi_command: tuple[str, ...] = ("excelbench-npoi-oracle",) + if repo_root is not None: + npoi_command = ( + "dotnet", + "run", + "--project", + str(repo_root / "tools" / "external-oracles" / "npoi" / "npoi-oracle.csproj"), + "--configuration", + "Release", + "--no-launch-profile", + "--verbosity", + "quiet", + "--", + ) return { "excelize": ExternalOracleTool( @@ -175,6 +189,13 @@ def external_oracle_catalog(repo_root: Path | None = None) -> dict[str, External homepage="https://docs.closedxml.io/", capabilities=frozenset({"read", "write", "pivots", "conditional_formatting"}), ), + "npoi": ExternalOracleTool( + name="npoi", + command=npoi_command, + language="dotnet", + homepage="https://github.com/nissl-lab/npoi", + capabilities=frozenset({"read", "write", "comments", "rich_text", "protection"}), + ), } diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index 9c83f86..1cbecb4 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -14,6 +14,7 @@ excelize_fixture_specs, external_fixture_specs, generate_external_fixture_pack, + npoi_fixture_specs, ) from excelbench.harness.external_wolfxl_validation import ( validate_wolfxl_external_fixture_pack, @@ -49,9 +50,19 @@ def test_closedxml_fixture_specs_are_stable() -> None: "excelize_chart_points_formula_cf", "closedxml_pivot_cf_table", "closedxml_rich_comment_protection", + "npoi_formula_comment_merge_protection", ] +def test_npoi_fixture_specs_are_stable() -> None: + specs = npoi_fixture_specs() + + assert [spec.fixture_id for spec in specs] == ["npoi_formula_comment_merge_protection"] + assert all(spec.tool == "npoi" for spec in specs) + assert "xl/comments1.xml" in specs[0].expected_parts + assert "xl/drawings/vmlDrawing1.vml" in specs[0].expected_parts + + @pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") def test_generate_external_fixture_pack_without_validators(tmp_path: Path) -> None: repo_root = Path(__file__).resolve().parents[1] @@ -88,6 +99,17 @@ def test_generate_external_fixture_pack_without_validators(tmp_path: Path) -> No assert "xl/drawings/vmldrawing.vml" in rich_names assert "sheetProtection" in sheet_xml assert ":r>" in shared_strings_xml + npoi_fixture = tmp_path / "npoi-formula-comment-merge-protection.xlsx" + assert npoi_fixture.exists() + with ZipFile(npoi_fixture) as workbook: + npoi_names = set(workbook.namelist()) + npoi_sheet_xml = workbook.read("xl/worksheets/sheet1.xml").decode() + npoi_shared_strings_xml = workbook.read("xl/sharedStrings.xml").decode() + assert "xl/comments1.xml" in npoi_names + assert "xl/drawings/vmlDrawing1.vml" in npoi_names + assert "sheetProtection" in npoi_sheet_xml + assert "mergeCell" in npoi_sheet_xml + assert "" in npoi_shared_strings_xml @pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") diff --git a/tests/test_external_oracles.py b/tests/test_external_oracles.py index bb49255..8884ea8 100644 --- a/tests/test_external_oracles.py +++ b/tests/test_external_oracles.py @@ -21,9 +21,10 @@ def test_catalog_lists_planned_external_oracles() -> None: catalog = external_oracle_catalog() - assert set(catalog) == {"excelize", "libreoffice", "apache-poi", "closedxml"} + assert set(catalog) == {"excelize", "libreoffice", "apache-poi", "closedxml", "npoi"} assert "pivots" in catalog["excelize"].capabilities assert "open_save_validate" in catalog["libreoffice"].capabilities + assert "rich_text" in catalog["npoi"].capabilities def test_repo_catalog_points_excelize_at_go_helper() -> None: @@ -37,6 +38,8 @@ def test_repo_catalog_points_excelize_at_go_helper() -> None: assert catalog["libreoffice"].command[1].endswith("libreoffice_oracle.py") assert catalog["closedxml"].command[0] == "dotnet" assert any(part.endswith("closedxml-oracle.csproj") for part in catalog["closedxml"].command) + assert catalog["npoi"].command[0] == "dotnet" + assert any(part.endswith("npoi-oracle.csproj") for part in catalog["npoi"].command) def test_missing_oracle_helper_is_structured_skip() -> None: @@ -328,6 +331,80 @@ def test_closedxml_dotnet_helper_writes_pivot_fixture(tmp_path: Path) -> None: assert ":r>" in shared_strings_xml +@pytest.mark.skipif(shutil.which("dotnet") is None, reason=".NET SDK is required for NPOI") +def test_npoi_dotnet_helper_writes_formula_comment_fixture(tmp_path: Path) -> None: + repo_root = Path(__file__).resolve().parents[1] + output_path = tmp_path / "npoi-smoke.xlsx" + tool = external_oracle_catalog(repo_root=repo_root)["npoi"] + + result = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="npoi-smoke", + operation="write_fixture", + output_path=output_path, + payload={ + "sheets": [{"name": "NPOI"}], + "cells": [ + {"sheet": "NPOI", "cell": "A1", "value": "Account"}, + {"sheet": "NPOI", "cell": "B1", "value": "Amount"}, + {"sheet": "NPOI", "cell": "A2", "value": "Revenue"}, + {"sheet": "NPOI", "cell": "B2", "value": 1250}, + {"sheet": "NPOI", "cell": "A3", "value": "COGS"}, + {"sheet": "NPOI", "cell": "B3", "value": -400}, + {"sheet": "NPOI", "cell": "A4", "value": "Gross profit"}, + { + "sheet": "NPOI", + "cell": "B4", + "type": "formula", + "formula": "SUM(B2:B3)", + }, + {"sheet": "NPOI", "cell": "D1", "value": "Merged review header"}, + ], + "rich_text": [ + { + "sheet": "NPOI", + "cell": "D3", + "runs": [ + {"text": "NPOI ", "bold": True}, + {"text": "rich text", "italic": True}, + ], + } + ], + "comments": [ + { + "sheet": "NPOI", + "cell": "B4", + "text": "Formula result should preserve calc metadata.", + "author": "NPOI Oracle", + } + ], + "merged_ranges": [{"sheet": "NPOI", "range": "D1:F1"}], + "protection": [{"sheet": "NPOI", "password": "audit"}], + }, + ), + timeout_seconds=180, + ) + + assert result.passed is True, result + assert output_path.exists() + assert result.payload["counts"]["formulas"] == 1 + assert result.payload["counts"]["comments"] == 1 + assert result.payload["counts"]["rich_text"] == 1 + assert result.payload["counts"]["merged_ranges"] == 1 + assert result.payload["counts"]["protected_sheets"] == 1 + with ZipFile(output_path) as workbook: + names = set(workbook.namelist()) + sheet_xml = workbook.read("xl/worksheets/sheet1.xml").decode() + shared_strings_xml = workbook.read("xl/sharedStrings.xml").decode() + assert "xl/comments1.xml" in names + assert "xl/drawings/vmlDrawing1.vml" in names + assert "sheetProtection" in sheet_xml + assert "mergeCell" in sheet_xml + assert "SUM(B2:B3)" in sheet_xml + assert "" in shared_strings_xml + + def test_libreoffice_helper_renders_pdf_or_skips(tmp_path: Path) -> None: repo_root = Path(__file__).resolve().parents[1] input_path = tmp_path / "input.xlsx" diff --git a/tools/external-oracles/npoi/Program.cs b/tools/external-oracles/npoi/Program.cs new file mode 100644 index 0000000..23d3c52 --- /dev/null +++ b/tools/external-oracles/npoi/Program.cs @@ -0,0 +1,292 @@ +using System.IO.Compression; +using System.Text.Json; +using System.Text.Json.Serialization; +using NPOI.SS.UserModel; +using NPOI.SS.Util; +using NPOI.XSSF.UserModel; + +try +{ + using var stdin = Console.OpenStandardInput(); + var request = JsonSerializer.Deserialize(stdin, JsonOptions.Value) + ?? throw new InvalidOperationException("Empty oracle request."); + + var payload = request.Operation switch + { + "write_fixture" => WriteFixture(request), + "read_metadata" => ReadMetadata(request), + _ => throw new InvalidOperationException($"Unsupported operation '{request.Operation}'.") + }; + + Console.WriteLine(JsonSerializer.Serialize(payload, JsonOptions.Value)); + return 0; +} +catch (Exception ex) +{ + Console.WriteLine(JsonSerializer.Serialize(new + { + error = "npoi_oracle_failed", + message = ex.Message + }, JsonOptions.Value)); + return 1; +} + +static object WriteFixture(OracleRequest request) +{ + if (string.IsNullOrWhiteSpace(request.OutputPath)) + { + throw new InvalidOperationException("write_fixture requires output_path."); + } + + var payload = request.Payload.Deserialize(JsonOptions.Value) + ?? new WritePayload(); + + Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(request.OutputPath))!); + using var workbook = new XSSFWorkbook(); + ConfigureSheets(workbook, payload.Sheets); + ApplyCells(workbook, payload.Cells); + ApplyRichText(workbook, payload.RichText); + ApplyComments(workbook, payload.Comments); + ApplyMergedRanges(workbook, payload.MergedRanges); + ApplyProtection(workbook, payload.Protection); + + using (var stream = File.Create(request.OutputPath)) + { + workbook.Write(stream, leaveOpen: false); + } + + return new + { + fixture_id = request.FixtureId, + operation = request.Operation, + output_path = request.OutputPath, + tool = "npoi", + counts = new + { + sheets = workbook.NumberOfSheets, + cells = payload.Cells.Count, + formulas = payload.Cells.Count(cell => cell.Type == "formula"), + rich_text = payload.RichText.Count, + comments = payload.Comments.Count, + merged_ranges = payload.MergedRanges.Count, + protected_sheets = payload.Protection.Count + } + }; +} + +static object ReadMetadata(OracleRequest request) +{ + var inputPath = string.IsNullOrWhiteSpace(request.InputPath) + ? request.OutputPath + : request.InputPath; + if (string.IsNullOrWhiteSpace(inputPath)) + { + throw new InvalidOperationException("read_metadata requires input_path or output_path."); + } + + using var workbookPackage = ZipFile.OpenRead(inputPath); + var partNames = workbookPackage.Entries.Select(entry => entry.FullName).ToArray(); + return new + { + fixture_id = request.FixtureId, + operation = request.Operation, + input_path = inputPath, + tool = "npoi", + counts = new + { + worksheets = CountParts(partNames, "worksheets/sheet"), + shared_strings = CountParts(partNames, "sharedStrings"), + comments = CountParts(partNames, "comments"), + vml_drawings = CountParts(partNames, "drawings/vmlDrawing"), + calc_chain = CountParts(partNames, "calcChain") + } + }; +} + +static void ConfigureSheets(XSSFWorkbook workbook, List sheets) +{ + if (sheets.Count == 0) + { + workbook.CreateSheet("Sheet1"); + return; + } + + foreach (var sheet in sheets) + { + workbook.CreateSheet(sheet.Name); + } +} + +static void ApplyCells(XSSFWorkbook workbook, List cells) +{ + foreach (var item in cells) + { + var cell = GetOrCreateCell(workbook, item.Sheet, item.Cell); + if (item.Type == "formula") + { + cell.SetCellFormula(item.Formula ?? ""); + continue; + } + + switch (item.Value.ValueKind) + { + case JsonValueKind.String: + cell.SetCellValue(item.Value.GetString() ?? ""); + break; + case JsonValueKind.Number: + cell.SetCellValue(item.Value.GetDouble()); + break; + case JsonValueKind.True: + case JsonValueKind.False: + cell.SetCellValue(item.Value.GetBoolean()); + break; + case JsonValueKind.Null: + case JsonValueKind.Undefined: + break; + default: + cell.SetCellValue(item.Value.ToString()); + break; + } + } +} + +static void ApplyRichText(XSSFWorkbook workbook, List items) +{ + foreach (var item in items) + { + var cell = GetOrCreateCell(workbook, item.Sheet, item.Cell); + var richText = new XSSFRichTextString(); + foreach (var run in item.Runs) + { + var font = (XSSFFont)workbook.CreateFont(); + font.IsBold = run.Bold; + font.IsItalic = run.Italic; + richText.Append(run.Text, font); + } + cell.SetCellValue(richText); + } +} + +static void ApplyComments(XSSFWorkbook workbook, List comments) +{ + foreach (var item in comments) + { + var sheet = workbook.GetSheet(item.Sheet) + ?? throw new InvalidOperationException($"Sheet not found: {item.Sheet}"); + var cell = GetOrCreateCell(workbook, item.Sheet, item.Cell); + var drawing = sheet.CreateDrawingPatriarch(); + var anchor = workbook.GetCreationHelper().CreateClientAnchor(); + anchor.Col1 = cell.ColumnIndex + 1; + anchor.Col2 = cell.ColumnIndex + 4; + anchor.Row1 = cell.RowIndex; + anchor.Row2 = cell.RowIndex + 3; + var comment = drawing.CreateCellComment(anchor); + comment.String = workbook.GetCreationHelper().CreateRichTextString(item.Text); + comment.Author = item.Author ?? "NPOI Oracle"; + cell.CellComment = comment; + } +} + +static void ApplyMergedRanges(XSSFWorkbook workbook, List mergedRanges) +{ + foreach (var item in mergedRanges) + { + var sheet = workbook.GetSheet(item.Sheet) + ?? throw new InvalidOperationException($"Sheet not found: {item.Sheet}"); + sheet.AddMergedRegion(CellRangeAddress.ValueOf(item.Range)); + } +} + +static void ApplyProtection(XSSFWorkbook workbook, List protections) +{ + foreach (var item in protections) + { + var sheet = workbook.GetSheet(item.Sheet) + ?? throw new InvalidOperationException($"Sheet not found: {item.Sheet}"); + sheet.ProtectSheet(item.Password ?? ""); + } +} + +static ICell GetOrCreateCell(XSSFWorkbook workbook, string sheetName, string address) +{ + var sheet = workbook.GetSheet(sheetName) + ?? throw new InvalidOperationException($"Sheet not found: {sheetName}"); + var cellRef = new CellReference(address); + var row = sheet.GetRow(cellRef.Row) ?? sheet.CreateRow(cellRef.Row); + return row.GetCell(cellRef.Col) ?? row.CreateCell(cellRef.Col); +} + +static int CountParts(IEnumerable partNames, string fragment) +{ + return partNames.Count(part => part.Contains(fragment, StringComparison.Ordinal)); +} + +sealed record OracleRequest( + [property: JsonPropertyName("fixture_id")] string FixtureId, + [property: JsonPropertyName("operation")] string Operation, + [property: JsonPropertyName("payload")] JsonElement Payload, + [property: JsonPropertyName("input_path")] string? InputPath, + [property: JsonPropertyName("output_path")] string? OutputPath); + +sealed class WritePayload +{ + [JsonPropertyName("sheets")] + public List Sheets { get; set; } = []; + + [JsonPropertyName("cells")] + public List Cells { get; set; } = []; + + [JsonPropertyName("rich_text")] + public List RichText { get; set; } = []; + + [JsonPropertyName("comments")] + public List Comments { get; set; } = []; + + [JsonPropertyName("merged_ranges")] + public List MergedRanges { get; set; } = []; + + [JsonPropertyName("protection")] + public List Protection { get; set; } = []; +} + +sealed record SheetSpec([property: JsonPropertyName("name")] string Name); + +sealed record CellSpec( + [property: JsonPropertyName("sheet")] string Sheet, + [property: JsonPropertyName("cell")] string Cell, + [property: JsonPropertyName("type")] string? Type, + [property: JsonPropertyName("value")] JsonElement Value, + [property: JsonPropertyName("formula")] string? Formula); + +sealed record RichTextSpec( + [property: JsonPropertyName("sheet")] string Sheet, + [property: JsonPropertyName("cell")] string Cell, + [property: JsonPropertyName("runs")] List Runs); + +sealed record RichRunSpec( + [property: JsonPropertyName("text")] string Text, + [property: JsonPropertyName("bold")] bool Bold, + [property: JsonPropertyName("italic")] bool Italic); + +sealed record CommentSpec( + [property: JsonPropertyName("sheet")] string Sheet, + [property: JsonPropertyName("cell")] string Cell, + [property: JsonPropertyName("text")] string Text, + [property: JsonPropertyName("author")] string? Author); + +sealed record MergedRangeSpec( + [property: JsonPropertyName("sheet")] string Sheet, + [property: JsonPropertyName("range")] string Range); + +sealed record ProtectionSpec( + [property: JsonPropertyName("sheet")] string Sheet, + [property: JsonPropertyName("password")] string? Password); + +static class JsonOptions +{ + public static readonly JsonSerializerOptions Value = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + WriteIndented = false + }; +} diff --git a/tools/external-oracles/npoi/README.md b/tools/external-oracles/npoi/README.md new file mode 100644 index 0000000..c4f9f3f --- /dev/null +++ b/tools/external-oracles/npoi/README.md @@ -0,0 +1,21 @@ +# NPOI External Oracle + +This optional helper generates `.xlsx` workbooks with NPOI, the .NET port of +Apache POI. It uses the same JSON stdin/stdout contract as the Excelize, +LibreOffice, and ClosedXML helpers. + +Run from the repository root: + +```bash +dotnet run --project tools/external-oracles/npoi/npoi-oracle.csproj --configuration Release --no-launch-profile --verbosity quiet -- +``` + +Supported operations: + +- `write_fixture`: writes sheets, cells, formulas, rich text, comments, merged + regions, and sheet protection. +- `read_metadata`: inspects package parts for worksheets, shared strings, + comments, VML drawings, and calc-chain metadata. + +NPOI is intentionally a local pre-release oracle, not a normal ExcelBench +adapter or public benchmark claim. diff --git a/tools/external-oracles/npoi/npoi-oracle.csproj b/tools/external-oracles/npoi/npoi-oracle.csproj new file mode 100644 index 0000000..28ec140 --- /dev/null +++ b/tools/external-oracles/npoi/npoi-oracle.csproj @@ -0,0 +1,12 @@ + + + Exe + net9.0 + enable + enable + + + + + + From e094413afbfea0a91b08a85d0a4e534a62b4a45f Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:03:19 -0700 Subject: [PATCH 12/25] refactor: split external fixture specs --- architecture.md | 2 + docs/trackers/external-oracle-expansion.md | 54 ++- .../harness/external_fixture_pack.py | 347 +----------------- .../external_fixture_specs/__init__.py | 22 ++ .../harness/external_fixture_specs/base.py | 29 ++ .../external_fixture_specs/closedxml.py | 95 +++++ .../external_fixture_specs/excelize.py | 165 +++++++++ .../harness/external_fixture_specs/npoi.py | 57 +++ 8 files changed, 440 insertions(+), 331 deletions(-) create mode 100644 src/excelbench/harness/external_fixture_specs/__init__.py create mode 100644 src/excelbench/harness/external_fixture_specs/base.py create mode 100644 src/excelbench/harness/external_fixture_specs/closedxml.py create mode 100644 src/excelbench/harness/external_fixture_specs/excelize.py create mode 100644 src/excelbench/harness/external_fixture_specs/npoi.py diff --git a/architecture.md b/architecture.md index b225576..8eb3f77 100644 --- a/architecture.md +++ b/architecture.md @@ -71,6 +71,8 @@ Most-touched top-level directories: - `models.py`: dataclasses/contracts (CellValue, CellFormat, BorderInfo, ...) - `generator/`: fixture generation (xlwings + Excel) - `harness/`: fidelity benchmark runner, adapters, and optional external oracle helpers + - `harness/external_fixture_specs/`: tool-specific external oracle fixture + definitions imported by the fixture-pack generator - `perf/`: performance runner + renderer - `results/`: fidelity result renderers (md/csv) + dashboards/plots diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md index e23928b..0dc1e16 100644 --- a/docs/trackers/external-oracle-expansion.md +++ b/docs/trackers/external-oracle-expansion.md @@ -13,7 +13,9 @@ not to replace the existing ExcelBench adapter matrix. ## Current scaffold - `src/excelbench/harness/external_oracles.py` defines the subprocess contract. -- `src/excelbench/harness/external_fixture_pack.py` defines the first local +- `src/excelbench/harness/external_fixture_specs/` defines tool-specific + fixture specifications. +- `src/excelbench/harness/external_fixture_pack.py` orchestrates the local fixture pack and writes a `manifest.json` under `results_dev_external/`. - `src/excelbench/harness/external_wolfxl_validation.py` validates generated fixtures through WolfXL read + in-place modify-save part preservation. @@ -147,6 +149,48 @@ Current smoke coverage: | NPOI | .NET | POI-like .NET comparison for formulas, comments, rich strings, merged ranges, and protection. | Initial helper implemented; first fixture-pack case passing. | | SheetJS CE | JavaScript | Broad-format value/formula sanity checks; advanced styling/charts/pivots appear better suited to Pro. | P2 limited scope. | +## Additional oracle research + +2026-04-28 local runtime inventory: + +- Available locally: `java`, `javac`, `node`, `npm`, `pnpm`, `bun`, `go`, + `dotnet`, `php`, `ruby`, and `soffice`. +- Missing locally: `mvn`, `gradle`, `composer`, `ssconvert`, and + `libreoffice` as a direct executable name. + +High-signal next candidates: + +1. Apache POI. POI's XSSF API exposes pivot-table creation/inspection, tables, + sheet protection, conditional-formatting access, comments/VML drawing + surfaces, and shared formula metadata. It remains the best missing + independent OOXML writer, but the helper should avoid assuming Maven/Gradle + are installed on developer machines. Source: [POI XSSFSheet API][poi-xssf]. +2. ExcelJS. The project documents XLSX read/write, styles, merged cells, + defined names, data validations, comments, tables, rich text, conditional + formatting, images, sheet protection, streaming I/O, formula values, shared + formulas, and array formulas. This is a strong Node oracle for style/value + and streaming edge cases, with pivot support still newer and limited. Source: + [ExcelJS README][exceljs-readme]. +3. xlsx-populate. This is useful for template-preservation and mutation tests: + it is explicitly a parser/generator focused on preserving existing workbook + features and styles, with documented support for styles, rich text, data + validation, hyperlinks, print options, panes, and encryption. Source: + [xlsx-populate README][xlsx-populate-readme]. +4. PhpSpreadsheet. This can independently generate formulas, charts, styles, + images, merged cells, freeze panes, and sheet protection, but it needs a + Composer bootstrap before becoming an ergonomic local oracle. Sources: + [PhpSpreadsheet site][phpspreadsheet-site] and + [feature cross-reference][phpspreadsheet-features]. +5. libxlsxwriter. This C writer can generate formulas, hyperlinks, formatting, + merged cells, charts, data validation, conditional formatting, images, + comments, macros, and large-file memory-optimized outputs. It overlaps with + Rust writer coverage, but gives a different native package producer for XML + shape comparison. Source: [libxlsxwriter README][libxlsxwriter-readme]. +6. Gnumeric `ssconvert`. This is better as a conversion/open-save oracle than a + fixture generator. It is not installed locally, and LibreOffice already covers + the current conversion/render validation path. Source: + [Gnumeric ssconvert manual][gnumeric-ssconvert]. + ## First fixture pack The first external oracle pack should stay small and high-signal: @@ -182,3 +226,11 @@ Fixtures: - WolfXL read/modify/save behavior is explicitly classified as pass, fix-now, documented defer, or out of scope. - Stable cases graduate into checked-in fixtures only after a manual truth pass. + +[exceljs-readme]: https://raw.githubusercontent.com/exceljs/exceljs/master/README.md +[gnumeric-ssconvert]: https://gnome.pages.gitlab.gnome.org/gnumeric/manual/sect-files-ssconvert.html +[libxlsxwriter-readme]: https://github.com/jmcnamara/libxlsxwriter +[phpspreadsheet-features]: https://phpspreadsheet.readthedocs.io/en/stable/references/features-cross-reference/ +[phpspreadsheet-site]: https://phpspreadsheet.com/ +[poi-xssf]: https://poi.apache.org/apidocs/5.0/org/apache/poi/xssf/usermodel/XSSFSheet.html +[xlsx-populate-readme]: https://raw.githubusercontent.com/dtjohnson/xlsx-populate/master/README.md diff --git a/src/excelbench/harness/external_fixture_pack.py b/src/excelbench/harness/external_fixture_pack.py index e17d36a..0027dab 100644 --- a/src/excelbench/harness/external_fixture_pack.py +++ b/src/excelbench/harness/external_fixture_pack.py @@ -6,9 +6,16 @@ from dataclasses import dataclass, field from datetime import UTC, datetime from pathlib import Path -from typing import Any from zipfile import ZipFile +from excelbench.harness.external_fixture_specs import ( + ExternalFixtureSpec, + closedxml_fixture_specs, + excelize_fixture_specs, + external_fixture_specs, + npoi_fixture_specs, +) +from excelbench.harness.external_fixture_specs.base import JSONDict from excelbench.harness.external_oracles import ( ExternalOracleRequest, ExternalOracleResult, @@ -16,28 +23,15 @@ run_external_oracle, ) -JSONDict = dict[str, Any] - - -@dataclass(frozen=True) -class ExternalFixtureSpec: - """Definition of a local external-oracle fixture. - - Args: - fixture_id: Stable fixture identifier. - tool: Source helper name. - filename: Workbook filename to generate. - payload: JSON payload for the source helper. - expected_parts: OOXML package parts expected in the generated workbook. - notes: Human-readable reason this fixture exists. - """ - - fixture_id: str - tool: str - filename: str - payload: JSONDict - expected_parts: tuple[str, ...] - notes: str +__all__ = [ + "ExternalFixtureSpec", + "FixtureGenerationResult", + "closedxml_fixture_specs", + "excelize_fixture_specs", + "external_fixture_specs", + "generate_external_fixture_pack", + "npoi_fixture_specs", +] @dataclass(frozen=True) @@ -75,289 +69,6 @@ def to_json_dict(self, output_root: Path) -> JSONDict: } -def excelize_fixture_specs() -> list[ExternalFixtureSpec]: - """Return the initial Excelize fixture pack.""" - return [ - ExternalFixtureSpec( - fixture_id="excelize_sales_pivot_slicer_chart", - tool="excelize", - filename="excelize-sales-pivot-slicer-chart.xlsx", - payload={ - "sheets": [{"name": "Data"}, {"name": "Pivot"}], - "cells": _sales_cells(), - "columns": [{"sheet": "Data", "start": "A", "end": "C", "width": 16}], - "tables": [{"sheet": "Data", "range": "A1:C6", "name": "SalesTable"}], - "conditional_formats": [ - {"sheet": "Data", "range": "C2:C6", "type": "3_color_scale"}, - {"sheet": "Data", "range": "C2:C6", "type": "data_bar"}, - { - "sheet": "Data", - "range": "C2:C6", - "type": "icon_set", - "icon_style": "3TrafficLights1", - }, - ], - "charts": [ - { - "sheet": "Data", - "cell": "E2", - "type": "col", - "title": "Sales by row", - "categories": "Data!$A$2:$A$6", - "values": "Data!$C$2:$C$6", - "show_values": True, - "alt_text": "Sales by row chart", - } - ], - "pivots": [ - { - "data_range": "Data!A1:C6", - "range": "Pivot!A3:E12", - "name": "SalesPivot", - "rows": [{"name": "Region"}], - "columns": [{"name": "Product"}], - "data": [{"name": "Sales", "subtotal": "Sum"}], - "show_row_stripes": True, - } - ], - "slicers": [ - { - "sheet": "Data", - "name": "Region", - "cell": "E20", - "table_sheet": "Data", - "table_name": "SalesTable", - "caption": "Region", - } - ], - "pictures": [{"sheet": "Data", "cell": "H2", "name": "Pixel"}], - }, - expected_parts=( - "xl/tables/table1.xml", - "xl/pivotTables/pivotTable1.xml", - "xl/pivotCache/pivotCacheDefinition1.xml", - "xl/slicers/slicer1.xml", - "xl/slicerCaches/slicerCache1.xml", - "xl/charts/chart1.xml", - "xl/drawings/drawing1.xml", - ), - notes="Pivots, slicers, charts, CF, tables, and drawings in one workbook.", - ), - ExternalFixtureSpec( - fixture_id="excelize_chart_points_formula_cf", - tool="excelize", - filename="excelize-chart-points-formula-cf.xlsx", - payload={ - "sheets": [{"name": "Metrics"}], - "cells": [ - {"sheet": "Metrics", "cell": "A1", "value": "Month"}, - {"sheet": "Metrics", "cell": "B1", "value": "Revenue"}, - {"sheet": "Metrics", "cell": "C1", "value": "Margin"}, - {"sheet": "Metrics", "cell": "A2", "value": "Jan"}, - {"sheet": "Metrics", "cell": "B2", "value": 1200}, - {"sheet": "Metrics", "cell": "C2", "value": 0.31}, - {"sheet": "Metrics", "cell": "A3", "value": "Feb"}, - {"sheet": "Metrics", "cell": "B3", "value": 900}, - {"sheet": "Metrics", "cell": "C3", "value": 0.24}, - {"sheet": "Metrics", "cell": "A4", "value": "Mar"}, - {"sheet": "Metrics", "cell": "B4", "value": 1450}, - {"sheet": "Metrics", "cell": "C4", "value": 0.36}, - { - "sheet": "Metrics", - "cell": "B5", - "type": "formula", - "formula": "SUM(B2:B4)", - }, - ], - "conditional_formats": [ - {"sheet": "Metrics", "range": "B2:B4", "type": "data_bar"}, - { - "sheet": "Metrics", - "range": "C2:C4", - "type": "cell", - "criteria": ">", - "value": "0.3", - }, - ], - "charts": [ - { - "sheet": "Metrics", - "cell": "E2", - "type": "col", - "title": "Revenue", - "series": [ - { - "name": "Metrics!$B$1", - "categories": "Metrics!$A$2:$A$4", - "values": "Metrics!$B$2:$B$4", - "data_points": [ - {"index": 0, "fill_color": "4472C4"}, - {"index": 1, "fill_color": "ED7D31"}, - {"index": 2, "fill_color": "70AD47"}, - ], - } - ], - "show_values": True, - } - ], - }, - expected_parts=( - "xl/charts/chart1.xml", - "xl/drawings/drawing1.xml", - "xl/worksheets/sheet1.xml", - ), - notes="Per-point chart styling, formulas, and conditional-formatting rules.", - ), - ] - - -def closedxml_fixture_specs() -> list[ExternalFixtureSpec]: - """Return the initial ClosedXML fixture pack.""" - return [ - ExternalFixtureSpec( - fixture_id="closedxml_pivot_cf_table", - tool="closedxml", - filename="closedxml-pivot-cf-table.xlsx", - payload={ - "sheets": [{"name": "Data"}, {"name": "Pivot"}], - "cells": [ - {"sheet": "Data", "cell": "A1", "value": "Region"}, - {"sheet": "Data", "cell": "B1", "value": "Product"}, - {"sheet": "Data", "cell": "C1", "value": "Sales"}, - {"sheet": "Data", "cell": "A2", "value": "West"}, - {"sheet": "Data", "cell": "B2", "value": "Widgets"}, - {"sheet": "Data", "cell": "C2", "value": 120}, - {"sheet": "Data", "cell": "A3", "value": "East"}, - {"sheet": "Data", "cell": "B3", "value": "Services"}, - {"sheet": "Data", "cell": "C3", "value": 95}, - {"sheet": "Data", "cell": "A4", "value": "West"}, - {"sheet": "Data", "cell": "B4", "value": "Services"}, - {"sheet": "Data", "cell": "C4", "value": 140}, - ], - "tables": [{"sheet": "Data", "range": "A1:C4", "name": "ClosedXmlSales"}], - "conditional_formats": [ - {"sheet": "Data", "range": "C2:C4", "type": "3_color_scale"}, - {"sheet": "Data", "range": "C2:C4", "type": "data_bar"}, - ], - "pivots": [ - { - "data_range": "Data!A1:C4", - "cell": "Pivot!A3", - "name": "ClosedXmlPivot", - "rows": [{"name": "Region"}], - "columns": [{"name": "Product"}], - "data": [{"name": "Sales"}], - } - ], - }, - expected_parts=( - "xl/tables/table1.xml", - "xl/pivotTables/pivotTable.xml", - "pivotCache/pivotCacheDefinition1.xml", - "xl/worksheets/sheet1.xml", - ), - notes="ClosedXML pivot/cache/table/conditional-formatting package layout.", - ), - ExternalFixtureSpec( - fixture_id="closedxml_rich_comment_protection", - tool="closedxml", - filename="closedxml-rich-comment-protection.xlsx", - payload={ - "sheets": [{"name": "Review"}], - "cells": [ - {"sheet": "Review", "cell": "A1", "value": "Finding"}, - {"sheet": "Review", "cell": "B1", "value": "Status"}, - {"sheet": "Review", "cell": "A2", "value": "Revenue cutoff"}, - {"sheet": "Review", "cell": "B2", "value": "Open"}, - ], - "rich_text": [ - { - "sheet": "Review", - "cell": "A4", - "runs": [ - {"text": "Priority: ", "bold": True, "font_color": "#C00000"}, - {"text": "management response needed", "italic": True}, - ], - } - ], - "comments": [ - { - "sheet": "Review", - "cell": "B2", - "text": "Tie this status to final PBC evidence.", - "author": "ClosedXML Oracle", - } - ], - "protection": [{"sheet": "Review", "password": "audit"}], - }, - expected_parts=( - "xl/sharedStrings.xml", - "xl/comments1.xml", - "xl/drawings/vmldrawing.vml", - "xl/worksheets/sheet1.xml", - ), - notes="ClosedXML rich text, legacy comments, and sheet protection.", - ), - ] - - -def npoi_fixture_specs() -> list[ExternalFixtureSpec]: - """Return the initial NPOI fixture pack.""" - return [ - ExternalFixtureSpec( - fixture_id="npoi_formula_comment_merge_protection", - tool="npoi", - filename="npoi-formula-comment-merge-protection.xlsx", - payload={ - "sheets": [{"name": "NPOI"}], - "cells": [ - {"sheet": "NPOI", "cell": "A1", "value": "Account"}, - {"sheet": "NPOI", "cell": "B1", "value": "Amount"}, - {"sheet": "NPOI", "cell": "A2", "value": "Revenue"}, - {"sheet": "NPOI", "cell": "B2", "value": 1250}, - {"sheet": "NPOI", "cell": "A3", "value": "COGS"}, - {"sheet": "NPOI", "cell": "B3", "value": -400}, - {"sheet": "NPOI", "cell": "A4", "value": "Gross profit"}, - {"sheet": "NPOI", "cell": "B4", "type": "formula", "formula": "SUM(B2:B3)"}, - {"sheet": "NPOI", "cell": "D1", "value": "Merged review header"}, - ], - "rich_text": [ - { - "sheet": "NPOI", - "cell": "D3", - "runs": [ - {"text": "NPOI ", "bold": True}, - {"text": "rich text", "italic": True}, - ], - } - ], - "comments": [ - { - "sheet": "NPOI", - "cell": "B4", - "text": "Formula result should preserve calc metadata.", - "author": "NPOI Oracle", - } - ], - "merged_ranges": [{"sheet": "NPOI", "range": "D1:F1"}], - "protection": [{"sheet": "NPOI", "password": "audit"}], - }, - expected_parts=( - "xl/sharedStrings.xml", - "xl/comments1.xml", - "xl/drawings/vmlDrawing1.vml", - "xl/worksheets/sheet1.xml", - ), - notes="NPOI formula, rich text, legacy comments, merged range, and protection.", - ) - ] - - -def external_fixture_specs() -> list[ExternalFixtureSpec]: - """Return all implemented external fixture specifications.""" - return [*excelize_fixture_specs(), *closedxml_fixture_specs(), *npoi_fixture_specs()] - - def generate_external_fixture_pack( output_root: Path, *, @@ -438,30 +149,6 @@ def generate_external_fixture_pack( return results -def _sales_cells() -> list[JSONDict]: - headers = ["Region", "Product", "Sales"] - rows: list[tuple[str, str, int]] = [ - ("West", "Widgets", 120), - ("East", "Widgets", 95), - ("West", "Services", 140), - ("East", "Services", 160), - ("Central", "Widgets", 115), - ] - cells: list[JSONDict] = [ - {"sheet": "Data", "cell": f"{column}1", "value": value} - for column, value in zip(("A", "B", "C"), headers, strict=True) - ] - for row_index, row in enumerate(rows, start=2): - cells.extend( - [ - {"sheet": "Data", "cell": f"A{row_index}", "value": row[0]}, - {"sheet": "Data", "cell": f"B{row_index}", "value": row[1]}, - {"sheet": "Data", "cell": f"C{row_index}", "value": row[2]}, - ] - ) - return cells - - def _missing_parts(workbook_path: Path, expected_parts: tuple[str, ...]) -> tuple[str, ...]: if not workbook_path.exists(): return expected_parts diff --git a/src/excelbench/harness/external_fixture_specs/__init__.py b/src/excelbench/harness/external_fixture_specs/__init__.py new file mode 100644 index 0000000..65a626a --- /dev/null +++ b/src/excelbench/harness/external_fixture_specs/__init__.py @@ -0,0 +1,22 @@ +"""Tool-specific external fixture specifications.""" + +from __future__ import annotations + +from excelbench.harness.external_fixture_specs.closedxml import closedxml_fixture_specs +from excelbench.harness.external_fixture_specs.excelize import excelize_fixture_specs +from excelbench.harness.external_fixture_specs.npoi import npoi_fixture_specs + +from .base import ExternalFixtureSpec + +__all__ = [ + "ExternalFixtureSpec", + "closedxml_fixture_specs", + "excelize_fixture_specs", + "external_fixture_specs", + "npoi_fixture_specs", +] + + +def external_fixture_specs() -> list[ExternalFixtureSpec]: + """Return all implemented external fixture specifications.""" + return [*excelize_fixture_specs(), *closedxml_fixture_specs(), *npoi_fixture_specs()] diff --git a/src/excelbench/harness/external_fixture_specs/base.py b/src/excelbench/harness/external_fixture_specs/base.py new file mode 100644 index 0000000..ec67f2c --- /dev/null +++ b/src/excelbench/harness/external_fixture_specs/base.py @@ -0,0 +1,29 @@ +"""Shared external fixture specification types.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +JSONDict = dict[str, Any] + + +@dataclass(frozen=True) +class ExternalFixtureSpec: + """Definition of a local external-oracle fixture. + + Args: + fixture_id: Stable fixture identifier. + tool: Source helper name. + filename: Workbook filename to generate. + payload: JSON payload for the source helper. + expected_parts: OOXML package parts expected in the generated workbook. + notes: Human-readable reason this fixture exists. + """ + + fixture_id: str + tool: str + filename: str + payload: JSONDict + expected_parts: tuple[str, ...] + notes: str diff --git a/src/excelbench/harness/external_fixture_specs/closedxml.py b/src/excelbench/harness/external_fixture_specs/closedxml.py new file mode 100644 index 0000000..d5a735b --- /dev/null +++ b/src/excelbench/harness/external_fixture_specs/closedxml.py @@ -0,0 +1,95 @@ +"""ClosedXML external fixture specifications.""" + +from __future__ import annotations + +from excelbench.harness.external_fixture_specs.base import ExternalFixtureSpec + + +def closedxml_fixture_specs() -> list[ExternalFixtureSpec]: + """Return the initial ClosedXML fixture pack.""" + return [ + ExternalFixtureSpec( + fixture_id="closedxml_pivot_cf_table", + tool="closedxml", + filename="closedxml-pivot-cf-table.xlsx", + payload={ + "sheets": [{"name": "Data"}, {"name": "Pivot"}], + "cells": [ + {"sheet": "Data", "cell": "A1", "value": "Region"}, + {"sheet": "Data", "cell": "B1", "value": "Product"}, + {"sheet": "Data", "cell": "C1", "value": "Sales"}, + {"sheet": "Data", "cell": "A2", "value": "West"}, + {"sheet": "Data", "cell": "B2", "value": "Widgets"}, + {"sheet": "Data", "cell": "C2", "value": 120}, + {"sheet": "Data", "cell": "A3", "value": "East"}, + {"sheet": "Data", "cell": "B3", "value": "Services"}, + {"sheet": "Data", "cell": "C3", "value": 95}, + {"sheet": "Data", "cell": "A4", "value": "West"}, + {"sheet": "Data", "cell": "B4", "value": "Services"}, + {"sheet": "Data", "cell": "C4", "value": 140}, + ], + "tables": [{"sheet": "Data", "range": "A1:C4", "name": "ClosedXmlSales"}], + "conditional_formats": [ + {"sheet": "Data", "range": "C2:C4", "type": "3_color_scale"}, + {"sheet": "Data", "range": "C2:C4", "type": "data_bar"}, + ], + "pivots": [ + { + "data_range": "Data!A1:C4", + "cell": "Pivot!A3", + "name": "ClosedXmlPivot", + "rows": [{"name": "Region"}], + "columns": [{"name": "Product"}], + "data": [{"name": "Sales"}], + } + ], + }, + expected_parts=( + "xl/tables/table1.xml", + "xl/pivotTables/pivotTable.xml", + "pivotCache/pivotCacheDefinition1.xml", + "xl/worksheets/sheet1.xml", + ), + notes="ClosedXML pivot/cache/table/conditional-formatting package layout.", + ), + ExternalFixtureSpec( + fixture_id="closedxml_rich_comment_protection", + tool="closedxml", + filename="closedxml-rich-comment-protection.xlsx", + payload={ + "sheets": [{"name": "Review"}], + "cells": [ + {"sheet": "Review", "cell": "A1", "value": "Finding"}, + {"sheet": "Review", "cell": "B1", "value": "Status"}, + {"sheet": "Review", "cell": "A2", "value": "Revenue cutoff"}, + {"sheet": "Review", "cell": "B2", "value": "Open"}, + ], + "rich_text": [ + { + "sheet": "Review", + "cell": "A4", + "runs": [ + {"text": "Priority: ", "bold": True, "font_color": "#C00000"}, + {"text": "management response needed", "italic": True}, + ], + } + ], + "comments": [ + { + "sheet": "Review", + "cell": "B2", + "text": "Tie this status to final PBC evidence.", + "author": "ClosedXML Oracle", + } + ], + "protection": [{"sheet": "Review", "password": "audit"}], + }, + expected_parts=( + "xl/sharedStrings.xml", + "xl/comments1.xml", + "xl/drawings/vmldrawing.vml", + "xl/worksheets/sheet1.xml", + ), + notes="ClosedXML rich text, legacy comments, and sheet protection.", + ), + ] diff --git a/src/excelbench/harness/external_fixture_specs/excelize.py b/src/excelbench/harness/external_fixture_specs/excelize.py new file mode 100644 index 0000000..1a09087 --- /dev/null +++ b/src/excelbench/harness/external_fixture_specs/excelize.py @@ -0,0 +1,165 @@ +"""Excelize external fixture specifications.""" + +from __future__ import annotations + +from excelbench.harness.external_fixture_specs.base import ExternalFixtureSpec, JSONDict + + +def excelize_fixture_specs() -> list[ExternalFixtureSpec]: + """Return the initial Excelize fixture pack.""" + return [ + ExternalFixtureSpec( + fixture_id="excelize_sales_pivot_slicer_chart", + tool="excelize", + filename="excelize-sales-pivot-slicer-chart.xlsx", + payload={ + "sheets": [{"name": "Data"}, {"name": "Pivot"}], + "cells": _sales_cells(), + "columns": [{"sheet": "Data", "start": "A", "end": "C", "width": 16}], + "tables": [{"sheet": "Data", "range": "A1:C6", "name": "SalesTable"}], + "conditional_formats": [ + {"sheet": "Data", "range": "C2:C6", "type": "3_color_scale"}, + {"sheet": "Data", "range": "C2:C6", "type": "data_bar"}, + { + "sheet": "Data", + "range": "C2:C6", + "type": "icon_set", + "icon_style": "3TrafficLights1", + }, + ], + "charts": [ + { + "sheet": "Data", + "cell": "E2", + "type": "col", + "title": "Sales by row", + "categories": "Data!$A$2:$A$6", + "values": "Data!$C$2:$C$6", + "show_values": True, + "alt_text": "Sales by row chart", + } + ], + "pivots": [ + { + "data_range": "Data!A1:C6", + "range": "Pivot!A3:E12", + "name": "SalesPivot", + "rows": [{"name": "Region"}], + "columns": [{"name": "Product"}], + "data": [{"name": "Sales", "subtotal": "Sum"}], + "show_row_stripes": True, + } + ], + "slicers": [ + { + "sheet": "Data", + "name": "Region", + "cell": "E20", + "table_sheet": "Data", + "table_name": "SalesTable", + "caption": "Region", + } + ], + "pictures": [{"sheet": "Data", "cell": "H2", "name": "Pixel"}], + }, + expected_parts=( + "xl/tables/table1.xml", + "xl/pivotTables/pivotTable1.xml", + "xl/pivotCache/pivotCacheDefinition1.xml", + "xl/slicers/slicer1.xml", + "xl/slicerCaches/slicerCache1.xml", + "xl/charts/chart1.xml", + "xl/drawings/drawing1.xml", + ), + notes="Pivots, slicers, charts, CF, tables, and drawings in one workbook.", + ), + ExternalFixtureSpec( + fixture_id="excelize_chart_points_formula_cf", + tool="excelize", + filename="excelize-chart-points-formula-cf.xlsx", + payload={ + "sheets": [{"name": "Metrics"}], + "cells": [ + {"sheet": "Metrics", "cell": "A1", "value": "Month"}, + {"sheet": "Metrics", "cell": "B1", "value": "Revenue"}, + {"sheet": "Metrics", "cell": "C1", "value": "Margin"}, + {"sheet": "Metrics", "cell": "A2", "value": "Jan"}, + {"sheet": "Metrics", "cell": "B2", "value": 1200}, + {"sheet": "Metrics", "cell": "C2", "value": 0.31}, + {"sheet": "Metrics", "cell": "A3", "value": "Feb"}, + {"sheet": "Metrics", "cell": "B3", "value": 900}, + {"sheet": "Metrics", "cell": "C3", "value": 0.24}, + {"sheet": "Metrics", "cell": "A4", "value": "Mar"}, + {"sheet": "Metrics", "cell": "B4", "value": 1450}, + {"sheet": "Metrics", "cell": "C4", "value": 0.36}, + { + "sheet": "Metrics", + "cell": "B5", + "type": "formula", + "formula": "SUM(B2:B4)", + }, + ], + "conditional_formats": [ + {"sheet": "Metrics", "range": "B2:B4", "type": "data_bar"}, + { + "sheet": "Metrics", + "range": "C2:C4", + "type": "cell", + "criteria": ">", + "value": "0.3", + }, + ], + "charts": [ + { + "sheet": "Metrics", + "cell": "E2", + "type": "col", + "title": "Revenue", + "series": [ + { + "name": "Metrics!$B$1", + "categories": "Metrics!$A$2:$A$4", + "values": "Metrics!$B$2:$B$4", + "data_points": [ + {"index": 0, "fill_color": "4472C4"}, + {"index": 1, "fill_color": "ED7D31"}, + {"index": 2, "fill_color": "70AD47"}, + ], + } + ], + "show_values": True, + } + ], + }, + expected_parts=( + "xl/charts/chart1.xml", + "xl/drawings/drawing1.xml", + "xl/worksheets/sheet1.xml", + ), + notes="Per-point chart styling, formulas, and conditional-formatting rules.", + ), + ] + + +def _sales_cells() -> list[JSONDict]: + headers = ["Region", "Product", "Sales"] + rows: list[tuple[str, str, int]] = [ + ("West", "Widgets", 120), + ("East", "Widgets", 95), + ("West", "Services", 140), + ("East", "Services", 160), + ("Central", "Widgets", 115), + ] + cells: list[JSONDict] = [ + {"sheet": "Data", "cell": f"{column}1", "value": value} + for column, value in zip(("A", "B", "C"), headers, strict=True) + ] + for row_index, row in enumerate(rows, start=2): + cells.extend( + [ + {"sheet": "Data", "cell": f"A{row_index}", "value": row[0]}, + {"sheet": "Data", "cell": f"B{row_index}", "value": row[1]}, + {"sheet": "Data", "cell": f"C{row_index}", "value": row[2]}, + ] + ) + return cells diff --git a/src/excelbench/harness/external_fixture_specs/npoi.py b/src/excelbench/harness/external_fixture_specs/npoi.py new file mode 100644 index 0000000..86241af --- /dev/null +++ b/src/excelbench/harness/external_fixture_specs/npoi.py @@ -0,0 +1,57 @@ +"""NPOI external fixture specifications.""" + +from __future__ import annotations + +from excelbench.harness.external_fixture_specs.base import ExternalFixtureSpec + + +def npoi_fixture_specs() -> list[ExternalFixtureSpec]: + """Return the initial NPOI fixture pack.""" + return [ + ExternalFixtureSpec( + fixture_id="npoi_formula_comment_merge_protection", + tool="npoi", + filename="npoi-formula-comment-merge-protection.xlsx", + payload={ + "sheets": [{"name": "NPOI"}], + "cells": [ + {"sheet": "NPOI", "cell": "A1", "value": "Account"}, + {"sheet": "NPOI", "cell": "B1", "value": "Amount"}, + {"sheet": "NPOI", "cell": "A2", "value": "Revenue"}, + {"sheet": "NPOI", "cell": "B2", "value": 1250}, + {"sheet": "NPOI", "cell": "A3", "value": "COGS"}, + {"sheet": "NPOI", "cell": "B3", "value": -400}, + {"sheet": "NPOI", "cell": "A4", "value": "Gross profit"}, + {"sheet": "NPOI", "cell": "B4", "type": "formula", "formula": "SUM(B2:B3)"}, + {"sheet": "NPOI", "cell": "D1", "value": "Merged review header"}, + ], + "rich_text": [ + { + "sheet": "NPOI", + "cell": "D3", + "runs": [ + {"text": "NPOI ", "bold": True}, + {"text": "rich text", "italic": True}, + ], + } + ], + "comments": [ + { + "sheet": "NPOI", + "cell": "B4", + "text": "Formula result should preserve calc metadata.", + "author": "NPOI Oracle", + } + ], + "merged_ranges": [{"sheet": "NPOI", "range": "D1:F1"}], + "protection": [{"sheet": "NPOI", "password": "audit"}], + }, + expected_parts=( + "xl/sharedStrings.xml", + "xl/comments1.xml", + "xl/drawings/vmlDrawing1.vml", + "xl/worksheets/sheet1.xml", + ), + notes="NPOI formula, rich text, legacy comments, merged range, and protection.", + ) + ] From 623de0eb87d4b6bd6535aaf804ad554af642ddd0 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:48:57 -0700 Subject: [PATCH 13/25] feat: add exceljs external oracle fixture --- .gitignore | 3 + architecture.md | 3 + docs/trackers/external-oracle-expansion.md | 37 +- .../harness/external_fixture_pack.py | 2 + .../external_fixture_specs/__init__.py | 9 +- .../harness/external_fixture_specs/exceljs.py | 110 ++ src/excelbench/harness/external_oracles.py | 33 +- tests/test_external_fixture_pack.py | 35 + tests/test_external_oracles.py | 126 +- tools/external-oracles/exceljs/README.md | 24 + .../exceljs/exceljs-oracle.cjs | 303 +++++ .../exceljs/package-lock.json | 1009 +++++++++++++++++ tools/external-oracles/exceljs/package.json | 18 + 13 files changed, 1707 insertions(+), 5 deletions(-) create mode 100644 src/excelbench/harness/external_fixture_specs/exceljs.py create mode 100644 tools/external-oracles/exceljs/README.md create mode 100644 tools/external-oracles/exceljs/exceljs-oracle.cjs create mode 100644 tools/external-oracles/exceljs/package-lock.json create mode 100644 tools/external-oracles/exceljs/package.json diff --git a/.gitignore b/.gitignore index 6d879cc..ab18956 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,9 @@ rust/**/uv.lock tools/external-oracles/**/bin/ tools/external-oracles/**/obj/ +# Node external oracle dependencies +tools/external-oracles/**/node_modules/ + # Jupyter .ipynb_checkpoints/ diff --git a/architecture.md b/architecture.md index 8eb3f77..0475538 100644 --- a/architecture.md +++ b/architecture.md @@ -87,6 +87,9 @@ Most-touched top-level directories: Excelize. Run from that directory with `go run .`. - `libreoffice/`: Python helper that runs LibreOffice headless open/save and PDF render validation. + - `exceljs/`: Node helper that generates workbooks with ExcelJS tables, + formulas, data validations, rich strings, comments, hyperlinks, images, + merges, freeze panes, and sheet protection. - `closedxml/`: .NET helper that generates workbooks with ClosedXML tables, pivots, and conditional formatting. - `npoi/`: .NET helper that generates POI-style workbooks with NPOI formulas, diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md index 0dc1e16..6f26054 100644 --- a/docs/trackers/external-oracle-expansion.md +++ b/docs/trackers/external-oracle-expansion.md @@ -32,6 +32,7 @@ not to replace the existing ExcelBench adapter matrix. - The helper catalog currently reserves entrypoints or source helpers for: - `tools/external-oracles/excelize` (`go run .`) - `tools/external-oracles/libreoffice/libreoffice_oracle.py` + - `tools/external-oracles/exceljs` (`node exceljs-oracle.cjs`) - `tools/external-oracles/closedxml` (`dotnet run --project ...`) - `tools/external-oracles/npoi` (`dotnet run --project ...`) - `excelbench-poi-oracle` @@ -116,6 +117,34 @@ Current smoke coverage: sheet protection. The full four-workbook pack passes LibreOffice open/render validation and WolfXL read + in-place modify-save preservation. +## ExcelJS helper + +Implemented: `tools/external-oracles/exceljs` + +Supported operations: + +- `write_fixture`: writes sheets, cells, formulas, styles, rich text, comments, + hyperlinks, tables, data validations, merged ranges, freeze panes, images, + and sheet protection through ExcelJS. +- `read_metadata`: inspects package parts for worksheets, tables, drawings, + media, comments, VML drawings, shared strings, and calc-chain metadata. + +Current smoke coverage: + +- Node integration smoke writes a table + formula + data-validation + rich text + + comment + hyperlink + image + merged range + freeze panes + sheet-protection + workbook through `run_external_oracle()` when `node` and local `npm install` + dependencies are available. +- Fixture-pack promotion, 2026-04-28: + `exceljs_table_validation_image_comment` is included when ExcelJS + dependencies are installed. +- Manual ExcelJS truth pass, 2026-04-28: the full six-workbook pack, including + the ExcelJS table/validation/image/comment/rich-text case, passes LibreOffice + open/render validation and WolfXL read + in-place modify-save preservation. +- Dependency hygiene, 2026-04-28: `package-lock.json` pins ExcelJS 4.4.0 and + overrides transitive `uuid` to 14.0.0, producing a local `npm audit` result + with zero reported vulnerabilities. + ## NPOI helper Implemented: `tools/external-oracles/npoi` @@ -147,6 +176,7 @@ Current smoke coverage: | ClosedXML | .NET | Generate high-level table, pivot, conditional-formatting, and rich-cell fixtures. | Initial helper implemented; first two fixture-pack cases passing. | | Apache POI | Java | Generate and inspect OOXML fixtures with a mature usermodel plus documented chart/pivot limits. | P1 after contract settles. | | NPOI | .NET | POI-like .NET comparison for formulas, comments, rich strings, merged ranges, and protection. | Initial helper implemented; first fixture-pack case passing. | +| ExcelJS | JavaScript | Generate style/value, table, validation, comment, rich text, hyperlink, image, protection, and streaming edge cases. | Initial helper implemented; first fixture-pack case passing. | | SheetJS CE | JavaScript | Broad-format value/formula sanity checks; advanced styling/charts/pivots appear better suited to Pro. | P2 limited scope. | ## Additional oracle research @@ -215,8 +245,11 @@ Fixtures: comments, VML comment drawings, and sheet protection. **Implemented.** 5. `npoi_formula_comment_merge_protection`: NPOI formula cell, rich shared string, legacy comment, merged range, and sheet protection. **Implemented.** -6. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** -7. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** +6. `exceljs_table_validation_image_comment`: ExcelJS formula cell, table, + data validation, rich shared string, legacy comment, hyperlink, image, + merged range, freeze panes, and sheet protection. **Implemented.** +7. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** +8. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** ## Promotion gates diff --git a/src/excelbench/harness/external_fixture_pack.py b/src/excelbench/harness/external_fixture_pack.py index 0027dab..ef76ce6 100644 --- a/src/excelbench/harness/external_fixture_pack.py +++ b/src/excelbench/harness/external_fixture_pack.py @@ -12,6 +12,7 @@ ExternalFixtureSpec, closedxml_fixture_specs, excelize_fixture_specs, + exceljs_fixture_specs, external_fixture_specs, npoi_fixture_specs, ) @@ -27,6 +28,7 @@ "ExternalFixtureSpec", "FixtureGenerationResult", "closedxml_fixture_specs", + "exceljs_fixture_specs", "excelize_fixture_specs", "external_fixture_specs", "generate_external_fixture_pack", diff --git a/src/excelbench/harness/external_fixture_specs/__init__.py b/src/excelbench/harness/external_fixture_specs/__init__.py index 65a626a..139f935 100644 --- a/src/excelbench/harness/external_fixture_specs/__init__.py +++ b/src/excelbench/harness/external_fixture_specs/__init__.py @@ -4,6 +4,7 @@ from excelbench.harness.external_fixture_specs.closedxml import closedxml_fixture_specs from excelbench.harness.external_fixture_specs.excelize import excelize_fixture_specs +from excelbench.harness.external_fixture_specs.exceljs import exceljs_fixture_specs from excelbench.harness.external_fixture_specs.npoi import npoi_fixture_specs from .base import ExternalFixtureSpec @@ -11,6 +12,7 @@ __all__ = [ "ExternalFixtureSpec", "closedxml_fixture_specs", + "exceljs_fixture_specs", "excelize_fixture_specs", "external_fixture_specs", "npoi_fixture_specs", @@ -19,4 +21,9 @@ def external_fixture_specs() -> list[ExternalFixtureSpec]: """Return all implemented external fixture specifications.""" - return [*excelize_fixture_specs(), *closedxml_fixture_specs(), *npoi_fixture_specs()] + return [ + *excelize_fixture_specs(), + *closedxml_fixture_specs(), + *npoi_fixture_specs(), + *exceljs_fixture_specs(), + ] diff --git a/src/excelbench/harness/external_fixture_specs/exceljs.py b/src/excelbench/harness/external_fixture_specs/exceljs.py new file mode 100644 index 0000000..496dfff --- /dev/null +++ b/src/excelbench/harness/external_fixture_specs/exceljs.py @@ -0,0 +1,110 @@ +"""ExcelJS external fixture specifications.""" + +from __future__ import annotations + +from excelbench.harness.external_fixture_specs.base import ExternalFixtureSpec + + +def exceljs_fixture_specs() -> list[ExternalFixtureSpec]: + """Return the initial ExcelJS fixture pack.""" + return [ + ExternalFixtureSpec( + fixture_id="exceljs_table_validation_image_comment", + tool="exceljs", + filename="exceljs-table-validation-image-comment.xlsx", + payload={ + "sheets": [ + { + "name": "ExcelJS", + "freeze_panes": {"x_split": 1, "y_split": 1}, + } + ], + "columns": [ + {"sheet": "ExcelJS", "column": "A", "width": 18}, + {"sheet": "ExcelJS", "column": "B", "width": 14}, + {"sheet": "ExcelJS", "column": "D", "width": 24}, + ], + "cells": [ + {"sheet": "ExcelJS", "cell": "A1", "value": "Metric", "font": {"bold": True}}, + {"sheet": "ExcelJS", "cell": "B1", "value": "Value", "font": {"bold": True}}, + {"sheet": "ExcelJS", "cell": "A2", "value": "Revenue"}, + {"sheet": "ExcelJS", "cell": "B2", "value": 1200, "num_fmt": "$#,##0"}, + {"sheet": "ExcelJS", "cell": "A3", "value": "COGS"}, + {"sheet": "ExcelJS", "cell": "B3", "value": -450, "num_fmt": "$#,##0"}, + {"sheet": "ExcelJS", "cell": "A4", "value": "Gross profit"}, + { + "sheet": "ExcelJS", + "cell": "B4", + "type": "formula", + "formula": "SUM(B2:B3)", + "result": 750, + "num_fmt": "$#,##0", + }, + {"sheet": "ExcelJS", "cell": "D1", "value": "Merged review header"}, + ], + "rich_text": [ + { + "sheet": "ExcelJS", + "cell": "D2", + "runs": [ + {"text": "ExcelJS ", "bold": True, "font_color": "#4472C4"}, + {"text": "rich text", "italic": True}, + ], + } + ], + "comments": [ + { + "sheet": "ExcelJS", + "cell": "B4", + "text": "Formula result should survive package round trips.", + } + ], + "hyperlinks": [ + { + "sheet": "ExcelJS", + "cell": "D4", + "text": "ExcelJS project", + "url": "https://github.com/exceljs/exceljs", + "tooltip": "ExcelJS project", + } + ], + "merged_ranges": [{"sheet": "ExcelJS", "range": "D1:F1"}], + "data_validations": [ + { + "sheet": "ExcelJS", + "cell": "C2", + "type": "list", + "formulae": ['"Open,Closed,Review"'], + } + ], + "tables": [ + { + "sheet": "ExcelJS", + "name": "ExcelJsReviewTable", + "ref": "F1:G4", + "columns": [{"name": "Item"}, {"name": "Status"}], + "rows": [ + ["Revenue", "Open"], + ["COGS", "Closed"], + ["Gross profit", "Review"], + ], + } + ], + "images": [{"sheet": "ExcelJS", "range": "D6:E8"}], + "protection": [{"sheet": "ExcelJS", "password": "audit"}], + }, + expected_parts=( + "xl/sharedStrings.xml", + "xl/comments1.xml", + "xl/drawings/vmlDrawing1.vml", + "xl/drawings/drawing1.xml", + "xl/media/image1.png", + "xl/tables/table1.xml", + "xl/worksheets/sheet1.xml", + ), + notes=( + "ExcelJS table, formula, data validation, rich text, comment, hyperlink, " + "image, merge, freeze panes, and sheet protection." + ), + ) + ] diff --git a/src/excelbench/harness/external_oracles.py b/src/excelbench/harness/external_oracles.py index fc92a4c..9b9ef11 100644 --- a/src/excelbench/harness/external_oracles.py +++ b/src/excelbench/harness/external_oracles.py @@ -30,6 +30,8 @@ class ExternalOracleTool: env: Extra environment variables for the subprocess. cwd: Optional working directory for helper commands that are checked into this repository. + required_paths: Optional files/directories that must exist before the + helper is considered available. """ name: str @@ -39,6 +41,7 @@ class ExternalOracleTool: capabilities: frozenset[str] env: Mapping[str, str] = field(default_factory=dict) cwd: Path | None = None + required_paths: tuple[Path, ...] = () def resolve_executable(self) -> str | None: """Resolve the command executable without running it.""" @@ -51,7 +54,9 @@ def resolve_executable(self) -> str | None: def is_available(self) -> bool: """Return whether the helper command is available on this machine.""" - return self.resolve_executable() is not None + return self.resolve_executable() is not None and all( + required_path.exists() for required_path in self.required_paths + ) def resolved_command(self) -> tuple[str, ...] | None: """Return the command with the executable resolved to an absolute path.""" @@ -158,6 +163,13 @@ def external_oracle_catalog(repo_root: Path | None = None) -> dict[str, External "quiet", "--", ) + exceljs_command: tuple[str, ...] = ("excelbench-exceljs-oracle",) + exceljs_cwd = None + exceljs_required_paths: tuple[Path, ...] = () + if repo_root is not None: + exceljs_cwd = repo_root / "tools" / "external-oracles" / "exceljs" + exceljs_command = ("node", str(exceljs_cwd / "exceljs-oracle.cjs")) + exceljs_required_paths = (exceljs_cwd / "node_modules" / "exceljs" / "package.json",) return { "excelize": ExternalOracleTool( @@ -182,6 +194,25 @@ def external_oracle_catalog(repo_root: Path | None = None) -> dict[str, External homepage="https://poi.apache.org/components/spreadsheet/", capabilities=frozenset({"read", "write", "charts", "pivots"}), ), + "exceljs": ExternalOracleTool( + name="exceljs", + command=exceljs_command, + language="node", + homepage="https://github.com/exceljs/exceljs", + capabilities=frozenset( + { + "read", + "write", + "comments", + "data_validations", + "images", + "rich_text", + "tables", + } + ), + cwd=exceljs_cwd, + required_paths=exceljs_required_paths, + ), "closedxml": ExternalOracleTool( name="closedxml", command=closedxml_command, diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index 1cbecb4..e7cb269 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -12,6 +12,7 @@ from excelbench.harness.external_fixture_pack import ( closedxml_fixture_specs, excelize_fixture_specs, + exceljs_fixture_specs, external_fixture_specs, generate_external_fixture_pack, npoi_fixture_specs, @@ -51,9 +52,19 @@ def test_closedxml_fixture_specs_are_stable() -> None: "closedxml_pivot_cf_table", "closedxml_rich_comment_protection", "npoi_formula_comment_merge_protection", + "exceljs_table_validation_image_comment", ] +def test_exceljs_fixture_specs_are_stable() -> None: + specs = exceljs_fixture_specs() + + assert [spec.fixture_id for spec in specs] == ["exceljs_table_validation_image_comment"] + assert all(spec.tool == "exceljs" for spec in specs) + assert "xl/tables/table1.xml" in specs[0].expected_parts + assert "xl/media/image1.png" in specs[0].expected_parts + + def test_npoi_fixture_specs_are_stable() -> None: specs = npoi_fixture_specs() @@ -110,6 +121,30 @@ def test_generate_external_fixture_pack_without_validators(tmp_path: Path) -> No assert "sheetProtection" in npoi_sheet_xml assert "mergeCell" in npoi_sheet_xml assert "" in npoi_shared_strings_xml + exceljs_deps = ( + repo_root + / "tools" + / "external-oracles" + / "exceljs" + / "node_modules" + / "exceljs" + / "package.json" + ) + if shutil.which("node") is not None and exceljs_deps.exists(): + exceljs_fixture = tmp_path / "exceljs-table-validation-image-comment.xlsx" + assert exceljs_fixture.exists() + with ZipFile(exceljs_fixture) as workbook: + exceljs_names = set(workbook.namelist()) + exceljs_sheet_xml = workbook.read("xl/worksheets/sheet1.xml").decode() + exceljs_shared_strings_xml = workbook.read("xl/sharedStrings.xml").decode() + assert "xl/tables/table1.xml" in exceljs_names + assert "xl/comments1.xml" in exceljs_names + assert "xl/drawings/vmlDrawing1.vml" in exceljs_names + assert "xl/drawings/drawing1.xml" in exceljs_names + assert "xl/media/image1.png" in exceljs_names + assert "dataValidations" in exceljs_sheet_xml + assert "sheetProtection" in exceljs_sheet_xml + assert "" in exceljs_shared_strings_xml @pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") diff --git a/tests/test_external_oracles.py b/tests/test_external_oracles.py index 8884ea8..5119644 100644 --- a/tests/test_external_oracles.py +++ b/tests/test_external_oracles.py @@ -21,9 +21,17 @@ def test_catalog_lists_planned_external_oracles() -> None: catalog = external_oracle_catalog() - assert set(catalog) == {"excelize", "libreoffice", "apache-poi", "closedxml", "npoi"} + assert set(catalog) == { + "excelize", + "libreoffice", + "apache-poi", + "exceljs", + "closedxml", + "npoi", + } assert "pivots" in catalog["excelize"].capabilities assert "open_save_validate" in catalog["libreoffice"].capabilities + assert "data_validations" in catalog["exceljs"].capabilities assert "rich_text" in catalog["npoi"].capabilities @@ -36,6 +44,10 @@ def test_repo_catalog_points_excelize_at_go_helper() -> None: assert tool.cwd == repo_root / "tools" / "external-oracles" / "excelize" assert catalog["libreoffice"].command[0] == sys.executable assert catalog["libreoffice"].command[1].endswith("libreoffice_oracle.py") + assert catalog["exceljs"].command[0] == "node" + assert catalog["exceljs"].command[1].endswith("exceljs-oracle.cjs") + assert catalog["exceljs"].cwd == repo_root / "tools" / "external-oracles" / "exceljs" + assert catalog["exceljs"].required_paths assert catalog["closedxml"].command[0] == "dotnet" assert any(part.endswith("closedxml-oracle.csproj") for part in catalog["closedxml"].command) assert catalog["npoi"].command[0] == "dotnet" @@ -405,6 +417,118 @@ def test_npoi_dotnet_helper_writes_formula_comment_fixture(tmp_path: Path) -> No assert "" in shared_strings_xml +@pytest.mark.skipif( + not ( + shutil.which("node") + and ( + Path(__file__).resolve().parents[1] + / "tools" + / "external-oracles" + / "exceljs" + / "node_modules" + / "exceljs" + / "package.json" + ).exists() + ), + reason="Node and npm-installed ExcelJS dependencies are required for ExcelJS", +) +def test_exceljs_node_helper_writes_table_validation_fixture(tmp_path: Path) -> None: + repo_root = Path(__file__).resolve().parents[1] + output_path = tmp_path / "exceljs-smoke.xlsx" + tool = external_oracle_catalog(repo_root=repo_root)["exceljs"] + + result = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="exceljs-smoke", + operation="write_fixture", + output_path=output_path, + payload={ + "sheets": [{"name": "ExcelJS", "freeze_panes": {"x_split": 1, "y_split": 1}}], + "cells": [ + {"sheet": "ExcelJS", "cell": "A1", "value": "Metric"}, + {"sheet": "ExcelJS", "cell": "B1", "value": "Value"}, + {"sheet": "ExcelJS", "cell": "A2", "value": "Revenue"}, + {"sheet": "ExcelJS", "cell": "B2", "value": 1200}, + {"sheet": "ExcelJS", "cell": "A3", "value": "COGS"}, + {"sheet": "ExcelJS", "cell": "B3", "value": -450}, + { + "sheet": "ExcelJS", + "cell": "B4", + "type": "formula", + "formula": "SUM(B2:B3)", + "result": 750, + }, + ], + "rich_text": [ + { + "sheet": "ExcelJS", + "cell": "D2", + "runs": [ + {"text": "ExcelJS ", "bold": True}, + {"text": "rich text", "italic": True}, + ], + } + ], + "comments": [{"sheet": "ExcelJS", "cell": "B4", "text": "Formula comment."}], + "hyperlinks": [ + { + "sheet": "ExcelJS", + "cell": "D4", + "text": "ExcelJS project", + "url": "https://github.com/exceljs/exceljs", + } + ], + "merged_ranges": [{"sheet": "ExcelJS", "range": "D1:F1"}], + "data_validations": [ + { + "sheet": "ExcelJS", + "cell": "C2", + "type": "list", + "formulae": ['"Open,Closed"'], + } + ], + "tables": [ + { + "sheet": "ExcelJS", + "name": "ExcelJsTable", + "ref": "F1:G3", + "columns": [{"name": "Item"}, {"name": "Status"}], + "rows": [["Revenue", "Open"], ["COGS", "Closed"]], + } + ], + "images": [{"sheet": "ExcelJS", "range": "D6:E8"}], + "protection": [{"sheet": "ExcelJS", "password": "audit"}], + }, + ), + timeout_seconds=180, + ) + + assert result.passed is True, result + assert output_path.exists() + assert result.payload["counts"]["formulas"] == 1 + assert result.payload["counts"]["comments"] == 1 + assert result.payload["counts"]["rich_text"] == 1 + assert result.payload["counts"]["hyperlinks"] == 1 + assert result.payload["counts"]["tables"] == 1 + assert result.payload["counts"]["data_validations"] == 1 + assert result.payload["counts"]["images"] == 1 + with ZipFile(output_path) as workbook: + names = set(workbook.namelist()) + sheet_xml = workbook.read("xl/worksheets/sheet1.xml").decode() + shared_strings_xml = workbook.read("xl/sharedStrings.xml").decode() + assert "xl/tables/table1.xml" in names + assert "xl/comments1.xml" in names + assert "xl/drawings/vmlDrawing1.vml" in names + assert "xl/drawings/drawing1.xml" in names + assert "xl/media/image1.png" in names + assert "dataValidations" in sheet_xml + assert "sheetProtection" in sheet_xml + assert "mergeCell" in sheet_xml + assert "SUM(B2:B3)" in sheet_xml + assert "" in shared_strings_xml + + def test_libreoffice_helper_renders_pdf_or_skips(tmp_path: Path) -> None: repo_root = Path(__file__).resolve().parents[1] input_path = tmp_path / "input.xlsx" diff --git a/tools/external-oracles/exceljs/README.md b/tools/external-oracles/exceljs/README.md new file mode 100644 index 0000000..9d84c0c --- /dev/null +++ b/tools/external-oracles/exceljs/README.md @@ -0,0 +1,24 @@ +# ExcelJS External Oracle + +This optional helper generates `.xlsx` workbooks with ExcelJS. It uses the same +JSON stdin/stdout contract as the Excelize, LibreOffice, ClosedXML, and NPOI +helpers. + +Run from this directory once dependencies are installed: + +```bash +npm install +npm run oracle +``` + +Supported operations: + +- `write_fixture`: writes sheets, cells, formulas, styles, rich text, comments, + hyperlinks, tables, data validations, merged ranges, freeze panes, images, + and sheet protection. +- `read_metadata`: inspects package parts for worksheets, tables, drawings, + media, comments, VML drawings, shared strings, calc-chain metadata, and data + validations. + +ExcelJS is intentionally a local pre-release oracle, not a normal ExcelBench +adapter or public benchmark claim. diff --git a/tools/external-oracles/exceljs/exceljs-oracle.cjs b/tools/external-oracles/exceljs/exceljs-oracle.cjs new file mode 100644 index 0000000..592f405 --- /dev/null +++ b/tools/external-oracles/exceljs/exceljs-oracle.cjs @@ -0,0 +1,303 @@ +const fs = require("node:fs/promises"); +const path = require("node:path"); +let ExcelJS; +let JSZip; + +const PIXEL_PNG_BASE64 = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII="; + +main().catch((error) => { + process.stdout.write( + `${JSON.stringify({ error: "exceljs_oracle_failed", message: error.message })}\n`, + ); + process.exitCode = 1; +}); + +async function main() { + loadDependencies(); + const request = JSON.parse(await readStdin()); + const payload = + request.operation === "write_fixture" + ? await writeFixture(request) + : request.operation === "read_metadata" + ? await readMetadata(request) + : fail(`Unsupported operation '${request.operation}'.`); + process.stdout.write(`${JSON.stringify(payload)}\n`); +} + +function loadDependencies() { + try { + ExcelJS = require("exceljs"); + JSZip = require("jszip"); + } catch (error) { + fail(`Missing ExcelJS oracle dependency: ${error.message}`); + } +} + +async function writeFixture(request) { + if (!request.output_path) { + fail("write_fixture requires output_path."); + } + const payload = request.payload || {}; + const workbook = new ExcelJS.Workbook(); + workbook.creator = "ExcelBench ExcelJS Oracle"; + workbook.created = new Date("2026-04-28T00:00:00.000Z"); + workbook.modified = new Date("2026-04-28T00:00:00.000Z"); + + configureSheets(workbook, payload.sheets || []); + applyColumns(workbook, payload.columns || []); + applyCells(workbook, payload.cells || []); + applyRichText(workbook, payload.rich_text || []); + applyHyperlinks(workbook, payload.hyperlinks || []); + applyComments(workbook, payload.comments || []); + applyMergedRanges(workbook, payload.merged_ranges || []); + applyDataValidations(workbook, payload.data_validations || []); + applyTables(workbook, payload.tables || []); + applyImages(workbook, payload.images || []); + await applyProtection(workbook, payload.protection || []); + + await fs.mkdir(path.dirname(path.resolve(request.output_path)), { recursive: true }); + await workbook.xlsx.writeFile(request.output_path); + return { + fixture_id: request.fixture_id, + operation: request.operation, + output_path: request.output_path, + tool: "exceljs", + counts: { + sheets: workbook.worksheets.length, + cells: (payload.cells || []).length, + formulas: (payload.cells || []).filter((cell) => cell.type === "formula").length, + rich_text: (payload.rich_text || []).length, + comments: (payload.comments || []).length, + hyperlinks: (payload.hyperlinks || []).length, + tables: (payload.tables || []).length, + data_validations: (payload.data_validations || []).length, + merged_ranges: (payload.merged_ranges || []).length, + images: (payload.images || []).length, + protected_sheets: (payload.protection || []).length, + }, + }; +} + +async function readMetadata(request) { + const inputPath = request.input_path || request.output_path; + if (!inputPath) { + fail("read_metadata requires input_path or output_path."); + } + const buffer = await fs.readFile(inputPath); + const zip = await JSZip.loadAsync(buffer); + const partNames = Object.keys(zip.files); + return { + fixture_id: request.fixture_id, + operation: request.operation, + input_path: inputPath, + tool: "exceljs", + counts: { + worksheets: countParts(partNames, "xl/worksheets/sheet"), + tables: countParts(partNames, "xl/tables/table"), + drawings: countParts(partNames, "xl/drawings/drawing"), + media: countParts(partNames, "xl/media/"), + comments: countParts(partNames, "xl/comments"), + vml_drawings: countParts(partNames, "xl/drawings/vmlDrawing"), + shared_strings: countParts(partNames, "xl/sharedStrings"), + calc_chain: countParts(partNames, "xl/calcChain"), + }, + }; +} + +function configureSheets(workbook, sheets) { + const sheetSpecs = sheets.length ? sheets : [{ name: "Sheet1" }]; + for (const sheet of sheetSpecs) { + const worksheet = workbook.addWorksheet(sheet.name); + if (sheet.freeze_panes) { + worksheet.views = [ + { + state: "frozen", + xSplit: Number(sheet.freeze_panes.x_split || 0), + ySplit: Number(sheet.freeze_panes.y_split || 0), + }, + ]; + } + } +} + +function applyColumns(workbook, columns) { + for (const item of columns) { + const worksheet = getWorksheet(workbook, item.sheet); + const column = worksheet.getColumn(item.key || item.column); + if (item.width !== undefined) { + column.width = Number(item.width); + } + } +} + +function applyCells(workbook, cells) { + for (const item of cells) { + const cell = getCell(workbook, item.sheet, item.cell); + if (item.type === "formula") { + cell.value = { formula: item.formula || "", result: item.result ?? null }; + } else { + cell.value = item.value ?? null; + } + applyCellStyle(cell, item); + } +} + +function applyRichText(workbook, items) { + for (const item of items) { + const cell = getCell(workbook, item.sheet, item.cell); + cell.value = { + richText: item.runs.map((run) => ({ + text: run.text, + font: { + bold: Boolean(run.bold), + italic: Boolean(run.italic), + color: run.font_color ? { argb: normalizeColor(run.font_color) } : undefined, + }, + })), + }; + } +} + +function applyHyperlinks(workbook, items) { + for (const item of items) { + const cell = getCell(workbook, item.sheet, item.cell); + cell.value = { + text: item.text || item.url, + hyperlink: item.url, + tooltip: item.tooltip, + }; + } +} + +function applyComments(workbook, comments) { + for (const item of comments) { + const cell = getCell(workbook, item.sheet, item.cell); + cell.note = { + texts: [ + { + text: item.text, + font: { size: 11, color: { argb: "FF000000" }, name: "Calibri" }, + }, + ], + margins: { insetmode: "auto" }, + protection: { locked: true, lockText: false }, + editAs: "absolute", + }; + } +} + +function applyMergedRanges(workbook, mergedRanges) { + for (const item of mergedRanges) { + getWorksheet(workbook, item.sheet).mergeCells(item.range); + } +} + +function applyDataValidations(workbook, dataValidations) { + for (const item of dataValidations) { + const cell = getCell(workbook, item.sheet, item.cell); + cell.dataValidation = { + type: item.type || "list", + allowBlank: item.allow_blank ?? true, + formulae: item.formulae || item.values || [], + showErrorMessage: true, + errorTitle: item.error_title || "Invalid value", + error: item.error || "Choose a listed value.", + }; + } +} + +function applyTables(workbook, tables) { + for (const item of tables) { + const worksheet = getWorksheet(workbook, item.sheet); + worksheet.addTable({ + name: item.name, + ref: item.ref, + headerRow: true, + totalsRow: Boolean(item.totals_row), + style: { theme: item.theme || "TableStyleMedium2", showRowStripes: true }, + columns: item.columns.map((column) => ({ + name: column.name, + totalsRowFunction: column.totals_row_function, + })), + rows: item.rows, + }); + } +} + +function applyImages(workbook, images) { + for (const item of images) { + const worksheet = getWorksheet(workbook, item.sheet); + const imageId = workbook.addImage({ + base64: item.base64 || PIXEL_PNG_BASE64, + extension: item.extension || "png", + }); + worksheet.addImage(imageId, item.range || `${item.cell || "A1"}:${item.cell || "A1"}`); + } +} + +async function applyProtection(workbook, protections) { + for (const item of protections) { + const worksheet = getWorksheet(workbook, item.sheet); + await worksheet.protect(item.password || "", { + selectLockedCells: true, + selectUnlockedCells: true, + formatCells: false, + insertRows: false, + deleteRows: false, + }); + } +} + +function applyCellStyle(cell, item) { + if (item.num_fmt) { + cell.numFmt = item.num_fmt; + } + if (item.font) { + cell.font = { + bold: Boolean(item.font.bold), + italic: Boolean(item.font.italic), + color: item.font.color ? { argb: normalizeColor(item.font.color) } : undefined, + }; + } + if (item.fill_color) { + cell.fill = { + type: "pattern", + pattern: "solid", + fgColor: { argb: normalizeColor(item.fill_color) }, + }; + } +} + +function getWorksheet(workbook, name) { + const worksheet = workbook.getWorksheet(name); + if (!worksheet) { + fail(`Sheet not found: ${name}`); + } + return worksheet; +} + +function getCell(workbook, sheetName, address) { + return getWorksheet(workbook, sheetName).getCell(address); +} + +function normalizeColor(color) { + const stripped = String(color).replace(/^#/, "").toUpperCase(); + return stripped.length === 6 ? `FF${stripped}` : stripped; +} + +function countParts(partNames, fragment) { + return partNames.filter((partName) => partName.includes(fragment)).length; +} + +function fail(message) { + throw new Error(message); +} + +async function readStdin() { + const chunks = []; + for await (const chunk of process.stdin) { + chunks.push(Buffer.from(chunk)); + } + return Buffer.concat(chunks).toString("utf8"); +} diff --git a/tools/external-oracles/exceljs/package-lock.json b/tools/external-oracles/exceljs/package-lock.json new file mode 100644 index 0000000..0d8cc3a --- /dev/null +++ b/tools/external-oracles/exceljs/package-lock.json @@ -0,0 +1,1009 @@ +{ + "name": "excelbench-exceljs-oracle", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "excelbench-exceljs-oracle", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "exceljs": "4.4.0", + "jszip": "3.10.1" + } + }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/tools/external-oracles/exceljs/package.json b/tools/external-oracles/exceljs/package.json new file mode 100644 index 0000000..86b369d --- /dev/null +++ b/tools/external-oracles/exceljs/package.json @@ -0,0 +1,18 @@ +{ + "name": "excelbench-exceljs-oracle", + "version": "0.1.0", + "private": true, + "description": "Optional ExcelJS external oracle helper for ExcelBench", + "license": "MIT", + "type": "commonjs", + "scripts": { + "oracle": "node exceljs-oracle.cjs" + }, + "dependencies": { + "exceljs": "4.4.0", + "jszip": "3.10.1" + }, + "overrides": { + "uuid": "14.0.0" + } +} From dee7cc0e74c3211e51551c30f9f2920821803ae7 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:04:09 -0700 Subject: [PATCH 14/25] feat: add apache poi external oracle fixture --- .gitignore | 4 + architecture.md | 3 + docs/trackers/external-oracle-expansion.md | 41 ++- .../harness/external_fixture_pack.py | 2 + .../external_fixture_specs/__init__.py | 3 + .../external_fixture_specs/apache_poi.py | 30 +++ src/excelbench/harness/external_oracles.py | 29 +- tests/test_external_fixture_pack.py | 38 +++ tests/test_external_oracles.py | 64 +++++ tools/external-oracles/apache-poi/README.md | 25 ++ tools/external-oracles/apache-poi/build.sh | 34 +++ .../external-oracles/apache-poi/fetch_deps.py | 88 ++++++ .../external-oracles/apache-poi/poi_oracle.py | 126 +++++++++ .../apache-poi/src/PoiOracle.java | 254 ++++++++++++++++++ 14 files changed, 735 insertions(+), 6 deletions(-) create mode 100644 src/excelbench/harness/external_fixture_specs/apache_poi.py create mode 100644 tools/external-oracles/apache-poi/README.md create mode 100755 tools/external-oracles/apache-poi/build.sh create mode 100755 tools/external-oracles/apache-poi/fetch_deps.py create mode 100755 tools/external-oracles/apache-poi/poi_oracle.py create mode 100644 tools/external-oracles/apache-poi/src/PoiOracle.java diff --git a/.gitignore b/.gitignore index ab18956..9d48f7f 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,10 @@ tools/external-oracles/**/obj/ # Node external oracle dependencies tools/external-oracles/**/node_modules/ +# Java external oracle build/dependency outputs +tools/external-oracles/**/deps/lib/ +tools/external-oracles/**/build/classes/ + # Jupyter .ipynb_checkpoints/ diff --git a/architecture.md b/architecture.md index 0475538..1934d87 100644 --- a/architecture.md +++ b/architecture.md @@ -90,6 +90,9 @@ Most-touched top-level directories: - `exceljs/`: Node helper that generates workbooks with ExcelJS tables, formulas, data validations, rich strings, comments, hyperlinks, images, merges, freeze panes, and sheet protection. + - `apache-poi/`: Java helper that generates workbooks with Apache POI tables, + formulas, data validations, rich strings, comments, hyperlinks, images, + merges, freeze panes, and sheet protection. - `closedxml/`: .NET helper that generates workbooks with ClosedXML tables, pivots, and conditional formatting. - `npoi/`: .NET helper that generates POI-style workbooks with NPOI formulas, diff --git a/docs/trackers/external-oracle-expansion.md b/docs/trackers/external-oracle-expansion.md index 6f26054..8af391b 100644 --- a/docs/trackers/external-oracle-expansion.md +++ b/docs/trackers/external-oracle-expansion.md @@ -33,9 +33,9 @@ not to replace the existing ExcelBench adapter matrix. - `tools/external-oracles/excelize` (`go run .`) - `tools/external-oracles/libreoffice/libreoffice_oracle.py` - `tools/external-oracles/exceljs` (`node exceljs-oracle.cjs`) + - `tools/external-oracles/apache-poi/poi_oracle.py` - `tools/external-oracles/closedxml` (`dotnet run --project ...`) - `tools/external-oracles/npoi` (`dotnet run --project ...`) - - `excelbench-poi-oracle` ## Excelize helper @@ -145,6 +145,36 @@ Current smoke coverage: overrides transitive `uuid` to 14.0.0, producing a local `npm audit` result with zero reported vulnerabilities. +## Apache POI helper + +Implemented: `tools/external-oracles/apache-poi` + +Supported operations: + +- `write_fixture`: writes the current POI fixture with tables, formulas, rich + text, comments, hyperlinks, data validations, merged ranges, freeze panes, + images, and sheet protection through Apache POI. +- `read_metadata`: inspects package parts for worksheets, shared strings, + comments, VML drawings, tables, drawings, media, and calc-chain metadata. + +Current smoke coverage: + +- Java integration smoke writes a table + formula + data-validation + rich text + + comment + hyperlink + image + merged range + freeze panes + sheet-protection + workbook through `run_external_oracle()` when Java and the local POI helper + build are available. +- Fixture-pack promotion, 2026-04-28: + `apache_poi_table_validation_image_comment` is included when the helper has + been bootstrapped with `tools/external-oracles/apache-poi/build.sh`. +- Manual Apache POI truth pass, 2026-04-28: the full seven-workbook pack, + including the POI table/validation/image/comment/rich-text case, passes + LibreOffice open/render validation and WolfXL read + in-place modify-save + preservation. +- Dependency hygiene, 2026-04-28: `fetch_deps.py` pins Apache POI 5.5.1 and + required runtime jars from Maven Central, then verifies Maven Central checksum + sidecars before compilation. Maven Central reported `poi-ooxml` latest/release + as 5.5.1 during this pass. + ## NPOI helper Implemented: `tools/external-oracles/npoi` @@ -174,7 +204,7 @@ Current smoke coverage: | Excelize | Go | Generate xlsx fixtures for pivots, slicers, charts, conditional formatting, tables, rich formatting, images, and streaming paths. | Initial helper implemented. | | LibreOffice Calc | CLI / UNO | Open/save/render validator for corruption, repair, and visual/export smoke checks. | Initial helper implemented. | | ClosedXML | .NET | Generate high-level table, pivot, conditional-formatting, and rich-cell fixtures. | Initial helper implemented; first two fixture-pack cases passing. | -| Apache POI | Java | Generate and inspect OOXML fixtures with a mature usermodel plus documented chart/pivot limits. | P1 after contract settles. | +| Apache POI | Java | Generate and inspect OOXML fixtures with a mature usermodel plus documented chart/pivot limits. | Initial helper implemented; first fixture-pack case passing. | | NPOI | .NET | POI-like .NET comparison for formulas, comments, rich strings, merged ranges, and protection. | Initial helper implemented; first fixture-pack case passing. | | ExcelJS | JavaScript | Generate style/value, table, validation, comment, rich text, hyperlink, image, protection, and streaming edge cases. | Initial helper implemented; first fixture-pack case passing. | | SheetJS CE | JavaScript | Broad-format value/formula sanity checks; advanced styling/charts/pivots appear better suited to Pro. | P2 limited scope. | @@ -248,8 +278,11 @@ Fixtures: 6. `exceljs_table_validation_image_comment`: ExcelJS formula cell, table, data validation, rich shared string, legacy comment, hyperlink, image, merged range, freeze panes, and sheet protection. **Implemented.** -7. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** -8. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** +7. `apache_poi_table_validation_image_comment`: Apache POI formula cell, table, + data validation, rich shared string, legacy comment, hyperlink, image, merged + range, freeze panes, and sheet protection. **Implemented.** +8. LibreOffice open/save smoke for the same outputs. **Helper scaffolded.** +9. LibreOffice PDF/export smoke where visual corruption would be obvious. **Helper scaffolded.** ## Promotion gates diff --git a/src/excelbench/harness/external_fixture_pack.py b/src/excelbench/harness/external_fixture_pack.py index ef76ce6..f25d600 100644 --- a/src/excelbench/harness/external_fixture_pack.py +++ b/src/excelbench/harness/external_fixture_pack.py @@ -10,6 +10,7 @@ from excelbench.harness.external_fixture_specs import ( ExternalFixtureSpec, + apache_poi_fixture_specs, closedxml_fixture_specs, excelize_fixture_specs, exceljs_fixture_specs, @@ -27,6 +28,7 @@ __all__ = [ "ExternalFixtureSpec", "FixtureGenerationResult", + "apache_poi_fixture_specs", "closedxml_fixture_specs", "exceljs_fixture_specs", "excelize_fixture_specs", diff --git a/src/excelbench/harness/external_fixture_specs/__init__.py b/src/excelbench/harness/external_fixture_specs/__init__.py index 139f935..d50fd2a 100644 --- a/src/excelbench/harness/external_fixture_specs/__init__.py +++ b/src/excelbench/harness/external_fixture_specs/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +from excelbench.harness.external_fixture_specs.apache_poi import apache_poi_fixture_specs from excelbench.harness.external_fixture_specs.closedxml import closedxml_fixture_specs from excelbench.harness.external_fixture_specs.excelize import excelize_fixture_specs from excelbench.harness.external_fixture_specs.exceljs import exceljs_fixture_specs @@ -11,6 +12,7 @@ __all__ = [ "ExternalFixtureSpec", + "apache_poi_fixture_specs", "closedxml_fixture_specs", "exceljs_fixture_specs", "excelize_fixture_specs", @@ -26,4 +28,5 @@ def external_fixture_specs() -> list[ExternalFixtureSpec]: *closedxml_fixture_specs(), *npoi_fixture_specs(), *exceljs_fixture_specs(), + *apache_poi_fixture_specs(), ] diff --git a/src/excelbench/harness/external_fixture_specs/apache_poi.py b/src/excelbench/harness/external_fixture_specs/apache_poi.py new file mode 100644 index 0000000..6f9f4ad --- /dev/null +++ b/src/excelbench/harness/external_fixture_specs/apache_poi.py @@ -0,0 +1,30 @@ +"""Apache POI external fixture specifications.""" + +from __future__ import annotations + +from excelbench.harness.external_fixture_specs.base import ExternalFixtureSpec + + +def apache_poi_fixture_specs() -> list[ExternalFixtureSpec]: + """Return the initial Apache POI fixture pack.""" + return [ + ExternalFixtureSpec( + fixture_id="apache_poi_table_validation_image_comment", + tool="apache-poi", + filename="apache-poi-table-validation-image-comment.xlsx", + payload={}, + expected_parts=( + "xl/sharedStrings.xml", + "xl/comments1.xml", + "xl/drawings/vmlDrawing0.vml", + "xl/drawings/drawing1.xml", + "xl/media/image1.png", + "xl/tables/table1.xml", + "xl/worksheets/sheet1.xml", + ), + notes=( + "Apache POI table, formula, data validation, rich text, comment, hyperlink, " + "image, merge, freeze panes, and sheet protection." + ), + ) + ] diff --git a/src/excelbench/harness/external_oracles.py b/src/excelbench/harness/external_oracles.py index 9b9ef11..54d9dd3 100644 --- a/src/excelbench/harness/external_oracles.py +++ b/src/excelbench/harness/external_oracles.py @@ -170,6 +170,19 @@ def external_oracle_catalog(repo_root: Path | None = None) -> dict[str, External exceljs_cwd = repo_root / "tools" / "external-oracles" / "exceljs" exceljs_command = ("node", str(exceljs_cwd / "exceljs-oracle.cjs")) exceljs_required_paths = (exceljs_cwd / "node_modules" / "exceljs" / "package.json",) + apache_poi_command: tuple[str, ...] = ("excelbench-poi-oracle",) + apache_poi_cwd = None + apache_poi_required_paths: tuple[Path, ...] = () + if repo_root is not None: + apache_poi_cwd = repo_root / "tools" / "external-oracles" / "apache-poi" + apache_poi_command = ( + sys.executable, + str(apache_poi_cwd / "poi_oracle.py"), + ) + apache_poi_required_paths = ( + apache_poi_cwd / "build" / "classes" / "PoiOracle.class", + apache_poi_cwd / "deps" / "lib" / "poi-ooxml-5.5.1.jar", + ) return { "excelize": ExternalOracleTool( @@ -189,10 +202,22 @@ def external_oracle_catalog(repo_root: Path | None = None) -> dict[str, External ), "apache-poi": ExternalOracleTool( name="apache-poi", - command=("excelbench-poi-oracle",), + command=apache_poi_command, language="java", homepage="https://poi.apache.org/components/spreadsheet/", - capabilities=frozenset({"read", "write", "charts", "pivots"}), + capabilities=frozenset( + { + "read", + "write", + "comments", + "data_validations", + "images", + "rich_text", + "tables", + } + ), + cwd=apache_poi_cwd, + required_paths=apache_poi_required_paths, ), "exceljs": ExternalOracleTool( name="exceljs", diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index e7cb269..e9b39f5 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -10,6 +10,7 @@ import pytest from excelbench.harness.external_fixture_pack import ( + apache_poi_fixture_specs, closedxml_fixture_specs, excelize_fixture_specs, exceljs_fixture_specs, @@ -53,9 +54,19 @@ def test_closedxml_fixture_specs_are_stable() -> None: "closedxml_rich_comment_protection", "npoi_formula_comment_merge_protection", "exceljs_table_validation_image_comment", + "apache_poi_table_validation_image_comment", ] +def test_apache_poi_fixture_specs_are_stable() -> None: + specs = apache_poi_fixture_specs() + + assert [spec.fixture_id for spec in specs] == ["apache_poi_table_validation_image_comment"] + assert all(spec.tool == "apache-poi" for spec in specs) + assert "xl/tables/table1.xml" in specs[0].expected_parts + assert "xl/drawings/vmlDrawing0.vml" in specs[0].expected_parts + + def test_exceljs_fixture_specs_are_stable() -> None: specs = exceljs_fixture_specs() @@ -145,6 +156,33 @@ def test_generate_external_fixture_pack_without_validators(tmp_path: Path) -> No assert "dataValidations" in exceljs_sheet_xml assert "sheetProtection" in exceljs_sheet_xml assert "" in exceljs_shared_strings_xml + apache_poi_classes = ( + repo_root + / "tools" + / "external-oracles" + / "apache-poi" + / "build" + / "classes" + / "PoiOracle.class" + ) + if ( + (Path("/opt/homebrew/opt/openjdk/bin/java").exists() or shutil.which("java") is not None) + and apache_poi_classes.exists() + ): + apache_poi_fixture = tmp_path / "apache-poi-table-validation-image-comment.xlsx" + assert apache_poi_fixture.exists() + with ZipFile(apache_poi_fixture) as workbook: + apache_poi_names = set(workbook.namelist()) + apache_poi_sheet_xml = workbook.read("xl/worksheets/sheet1.xml").decode() + apache_poi_shared_strings_xml = workbook.read("xl/sharedStrings.xml").decode() + assert "xl/tables/table1.xml" in apache_poi_names + assert "xl/comments1.xml" in apache_poi_names + assert "xl/drawings/vmlDrawing0.vml" in apache_poi_names + assert "xl/drawings/drawing1.xml" in apache_poi_names + assert "xl/media/image1.png" in apache_poi_names + assert "dataValidations" in apache_poi_sheet_xml + assert "sheetProtection" in apache_poi_sheet_xml + assert "" in apache_poi_shared_strings_xml @pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") diff --git a/tests/test_external_oracles.py b/tests/test_external_oracles.py index 5119644..dbdc80b 100644 --- a/tests/test_external_oracles.py +++ b/tests/test_external_oracles.py @@ -31,6 +31,7 @@ def test_catalog_lists_planned_external_oracles() -> None: } assert "pivots" in catalog["excelize"].capabilities assert "open_save_validate" in catalog["libreoffice"].capabilities + assert "data_validations" in catalog["apache-poi"].capabilities assert "data_validations" in catalog["exceljs"].capabilities assert "rich_text" in catalog["npoi"].capabilities @@ -44,6 +45,10 @@ def test_repo_catalog_points_excelize_at_go_helper() -> None: assert tool.cwd == repo_root / "tools" / "external-oracles" / "excelize" assert catalog["libreoffice"].command[0] == sys.executable assert catalog["libreoffice"].command[1].endswith("libreoffice_oracle.py") + assert catalog["apache-poi"].command[0] == sys.executable + assert catalog["apache-poi"].command[1].endswith("poi_oracle.py") + assert catalog["apache-poi"].cwd == repo_root / "tools" / "external-oracles" / "apache-poi" + assert catalog["apache-poi"].required_paths assert catalog["exceljs"].command[0] == "node" assert catalog["exceljs"].command[1].endswith("exceljs-oracle.cjs") assert catalog["exceljs"].cwd == repo_root / "tools" / "external-oracles" / "exceljs" @@ -529,6 +534,65 @@ def test_exceljs_node_helper_writes_table_validation_fixture(tmp_path: Path) -> assert "" in shared_strings_xml +@pytest.mark.skipif( + not ( + ( + Path("/opt/homebrew/opt/openjdk/bin/java").exists() + or shutil.which("java") + ) + and ( + Path(__file__).resolve().parents[1] + / "tools" + / "external-oracles" + / "apache-poi" + / "build" + / "classes" + / "PoiOracle.class" + ).exists() + ), + reason="Java and built Apache POI helper classes are required for Apache POI", +) +def test_apache_poi_helper_writes_table_validation_fixture(tmp_path: Path) -> None: + repo_root = Path(__file__).resolve().parents[1] + output_path = tmp_path / "apache-poi-smoke.xlsx" + tool = external_oracle_catalog(repo_root=repo_root)["apache-poi"] + + result = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="apache-poi-smoke", + operation="write_fixture", + output_path=output_path, + payload={}, + ), + timeout_seconds=180, + ) + + assert result.passed is True, result + assert output_path.exists() + assert result.payload["counts"]["formulas"] == 1 + assert result.payload["counts"]["comments"] == 1 + assert result.payload["counts"]["rich_text"] == 1 + assert result.payload["counts"]["hyperlinks"] == 1 + assert result.payload["counts"]["tables"] == 1 + assert result.payload["counts"]["data_validations"] == 1 + assert result.payload["counts"]["images"] == 1 + with ZipFile(output_path) as workbook: + names = set(workbook.namelist()) + sheet_xml = workbook.read("xl/worksheets/sheet1.xml").decode() + shared_strings_xml = workbook.read("xl/sharedStrings.xml").decode() + assert "xl/tables/table1.xml" in names + assert "xl/comments1.xml" in names + assert "xl/drawings/vmlDrawing0.vml" in names + assert "xl/drawings/drawing1.xml" in names + assert "xl/media/image1.png" in names + assert "dataValidations" in sheet_xml + assert "sheetProtection" in sheet_xml + assert "mergeCell" in sheet_xml + assert "SUM(B2:B3)" in sheet_xml + assert "" in shared_strings_xml + + def test_libreoffice_helper_renders_pdf_or_skips(tmp_path: Path) -> None: repo_root = Path(__file__).resolve().parents[1] input_path = tmp_path / "input.xlsx" diff --git a/tools/external-oracles/apache-poi/README.md b/tools/external-oracles/apache-poi/README.md new file mode 100644 index 0000000..50f931c --- /dev/null +++ b/tools/external-oracles/apache-poi/README.md @@ -0,0 +1,25 @@ +# Apache POI External Oracle + +This optional helper generates `.xlsx` workbooks with Apache POI. It uses the +same JSON stdin/stdout contract as the Excelize, LibreOffice, ClosedXML, NPOI, +and ExcelJS helpers. + +Bootstrap from this directory: + +```bash +./build.sh +``` + +`fetch_deps.py` downloads a pinned Maven Central dependency set for Apache POI +5.5.1 into `deps/lib/` and verifies SHA-256 checksums before compilation. + +Supported operations: + +- `write_fixture`: writes the current POI fixture with tables, formulas, + comments, rich text, hyperlinks, data validation, merged ranges, freeze panes, + and sheet protection. +- `read_metadata`: inspects package parts for worksheets, shared strings, + comments, VML drawings, tables, drawings, media, and calc-chain metadata. + +Apache POI is intentionally a local pre-release oracle, not a normal ExcelBench +adapter or public benchmark claim. diff --git a/tools/external-oracles/apache-poi/build.sh b/tools/external-oracles/apache-poi/build.sh new file mode 100755 index 0000000..c31c0e2 --- /dev/null +++ b/tools/external-oracles/apache-poi/build.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +JAVA_BIN="${JAVA_BIN:-}" +JAVAC_BIN="${JAVAC_BIN:-}" + +if [ -z "$JAVA_BIN" ] && [ -x /opt/homebrew/opt/openjdk/bin/java ]; then + JAVA_BIN=/opt/homebrew/opt/openjdk/bin/java +fi +if [ -z "$JAVA_BIN" ]; then + JAVA_BIN=java +fi +if ! "$JAVA_BIN" -version >/dev/null 2>&1; then + echo "usable java not found" >&2 + exit 1 +fi + +if [ -z "$JAVAC_BIN" ] && [ -x /opt/homebrew/opt/openjdk/bin/javac ]; then + JAVAC_BIN=/opt/homebrew/opt/openjdk/bin/javac +fi +if [ -z "$JAVAC_BIN" ]; then + JAVAC_BIN=javac +fi +if ! "$JAVAC_BIN" -version >/dev/null 2>&1; then + echo "usable javac not found" >&2 + exit 1 +fi + +python3 fetch_deps.py +mkdir -p build/classes +"$JAVAC_BIN" -cp "deps/lib/*" -d build/classes src/PoiOracle.java +"$JAVA_BIN" -cp "build/classes:deps/lib/*" PoiOracle --self-test diff --git a/tools/external-oracles/apache-poi/fetch_deps.py b/tools/external-oracles/apache-poi/fetch_deps.py new file mode 100755 index 0000000..910a947 --- /dev/null +++ b/tools/external-oracles/apache-poi/fetch_deps.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +"""Fetch pinned Apache POI helper dependencies from Maven Central.""" + +from __future__ import annotations + +import hashlib +import urllib.error +import urllib.request +from pathlib import Path + +MAVEN = "https://repo.maven.apache.org/maven2" +ROOT = Path(__file__).resolve().parent +LIB = ROOT / "deps" / "lib" + +ARTIFACTS = ( + ("org.apache.poi", "poi-ooxml", "5.5.1"), + ("org.apache.poi", "poi", "5.5.1"), + ("org.apache.poi", "poi-ooxml-lite", "5.5.1"), + ("org.apache.xmlbeans", "xmlbeans", "5.3.0"), + ("org.apache.commons", "commons-compress", "1.28.0"), + ("commons-io", "commons-io", "2.21.0"), + ("org.apache.commons", "commons-lang3", "3.18.0"), + ("com.github.virtuald", "curvesapi", "1.08"), + ("org.apache.logging.log4j", "log4j-api", "2.24.3"), + ("org.apache.commons", "commons-collections4", "4.5.0"), + ("commons-codec", "commons-codec", "1.20.0"), + ("org.apache.commons", "commons-math3", "3.6.1"), + ("com.zaxxer", "SparseBitSet", "1.3"), +) + + +def main() -> None: + """Download and verify pinned runtime jars.""" + LIB.mkdir(parents=True, exist_ok=True) + for group_id, artifact_id, version in ARTIFACTS: + jar_url = _artifact_url(group_id, artifact_id, version, "jar") + output_path = LIB / f"{artifact_id}-{version}.jar" + algorithm, expected_checksum = _download_checksum(jar_url) + if output_path.exists() and _checksum(output_path, algorithm) == expected_checksum: + print(f"OK {output_path.name}") + continue + print(f"FETCH {output_path.name}") + output_path.write_bytes(_download_bytes(jar_url)) + actual_checksum = _checksum(output_path, algorithm) + if actual_checksum != expected_checksum: + output_path.unlink(missing_ok=True) + raise RuntimeError( + f"Checksum mismatch for {output_path.name}: " + f"expected {expected_checksum}, got {actual_checksum}" + ) + + +def _artifact_url(group_id: str, artifact_id: str, version: str, packaging: str) -> str: + group_path = group_id.replace(".", "/") + filename = f"{artifact_id}-{version}.{packaging}" + return f"{MAVEN}/{group_path}/{artifact_id}/{version}/{filename}" + + +def _download_bytes(url: str) -> bytes: + with urllib.request.urlopen(url, timeout=60) as response: + data = response.read() + return bytes(data) + + +def _download_text(url: str) -> str: + return _download_bytes(url).decode("utf-8") + + +def _download_checksum(jar_url: str) -> tuple[str, str]: + for extension, algorithm in (("sha512", "sha512"), ("sha256", "sha256"), ("sha1", "sha1")): + try: + return algorithm, _download_text(f"{jar_url}.{extension}").split()[0] + except urllib.error.HTTPError as exc: + if exc.code != 404: + raise + raise RuntimeError(f"No checksum sidecar found for {jar_url}") + + +def _checksum(path: Path, algorithm: str) -> str: + hasher = hashlib.new(algorithm) + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + hasher.update(chunk) + return hasher.hexdigest() + + +if __name__ == "__main__": + main() diff --git a/tools/external-oracles/apache-poi/poi_oracle.py b/tools/external-oracles/apache-poi/poi_oracle.py new file mode 100755 index 0000000..c477982 --- /dev/null +++ b/tools/external-oracles/apache-poi/poi_oracle.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""JSON stdin/stdout wrapper for the Apache POI helper.""" + +from __future__ import annotations + +import json +import shutil +import subprocess +import sys +import zipfile +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parent +CLASSES = ROOT / "build" / "classes" +LIB = ROOT / "deps" / "lib" +JAVA = Path("/opt/homebrew/opt/openjdk/bin/java") + +JSONDict = dict[str, Any] + + +def main() -> int: + """Run the requested helper operation.""" + request = json.load(sys.stdin) + operation = request["operation"] + if operation == "write_fixture": + return _write_fixture(request) + if operation == "read_metadata": + _write_json(_read_metadata(request)) + return 0 + _write_json({"error": "apache_poi_oracle_failed", "message": f"Unsupported {operation}"}) + return 1 + + +def _write_fixture(request: JSONDict) -> int: + output_path = request.get("output_path") + if not output_path: + _write_json( + { + "error": "apache_poi_oracle_failed", + "message": "write_fixture requires output_path", + } + ) + return 1 + java = _java_executable() + classpath = _classpath() + if java is None or classpath is None: + _write_json({"skipped": True, "notes": "Apache POI helper is not built"}) + return 0 + completed = subprocess.run( + [ + java, + "-cp", + classpath, + "PoiOracle", + "write_fixture", + request["fixture_id"], + output_path, + ], + text=True, + capture_output=True, + check=False, + timeout=180, + ) + if completed.returncode != 0: + _write_json( + { + "error": "apache_poi_oracle_failed", + "message": completed.stderr.strip() or completed.stdout.strip(), + } + ) + return completed.returncode + json_line = next( + line for line in reversed(completed.stdout.splitlines()) if line.strip().startswith("{") + ) + _write_json(json.loads(json_line)) + return 0 + + +def _read_metadata(request: JSONDict) -> JSONDict: + input_path = request.get("input_path") or request.get("output_path") + if not input_path: + return {"error": "apache_poi_oracle_failed", "message": "read_metadata requires input_path"} + with zipfile.ZipFile(input_path) as workbook: + names = workbook.namelist() + return { + "fixture_id": request["fixture_id"], + "operation": request["operation"], + "input_path": input_path, + "tool": "apache-poi", + "counts": { + "worksheets": _count(names, "xl/worksheets/sheet"), + "shared_strings": _count(names, "xl/sharedStrings"), + "comments": _count(names, "xl/comments"), + "vml_drawings": _count(names, "xl/drawings/vmlDrawing"), + "tables": _count(names, "xl/tables/table"), + "drawings": _count(names, "xl/drawings/drawing"), + "media": _count(names, "xl/media/"), + "calc_chain": _count(names, "xl/calcChain"), + }, + } + + +def _classpath() -> str | None: + jars = sorted(LIB.glob("*.jar")) + if not jars or not (CLASSES / "PoiOracle.class").exists(): + return None + return ":".join([str(CLASSES), *(str(jar) for jar in jars)]) + + +def _java_executable() -> str | None: + if JAVA.exists(): + return str(JAVA) + return shutil.which("java") + + +def _count(names: list[str], fragment: str) -> int: + return sum(1 for name in names if fragment in name) + + +def _write_json(payload: JSONDict) -> None: + print(json.dumps(payload, sort_keys=True)) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/external-oracles/apache-poi/src/PoiOracle.java b/tools/external-oracles/apache-poi/src/PoiOracle.java new file mode 100644 index 0000000..84dea3f --- /dev/null +++ b/tools/external-oracles/apache-poi/src/PoiOracle.java @@ -0,0 +1,254 @@ +import java.io.File; +import java.io.FileOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import org.apache.poi.common.usermodel.HyperlinkType; +import org.apache.poi.ss.SpreadsheetVersion; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.Hyperlink; +import org.apache.poi.ss.usermodel.RichTextString; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.AreaReference; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.ss.util.CellReference; +import org.apache.poi.xssf.usermodel.XSSFCellStyle; +import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFRichTextString; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFTable; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumns; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableStyleInfo; + +public final class PoiOracle { + private static final String PIXEL_PNG_BASE64 = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII="; + + private PoiOracle() {} + + public static void main(String[] args) throws Exception { + if (args.length == 1 && "--self-test".equals(args[0])) { + System.out.println("OK"); + return; + } + if (args.length != 3 || !"write_fixture".equals(args[0])) { + throw new IllegalArgumentException("Usage: PoiOracle write_fixture "); + } + writeFixture(args[1], args[2]); + } + + private static void writeFixture(String fixtureId, String outputPath) throws Exception { + File output = new File(outputPath); + File parent = output.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + + try (XSSFWorkbook workbook = new XSSFWorkbook()) { + workbook.getProperties().getCoreProperties().setCreator("ExcelBench Apache POI Oracle"); + XSSFSheet sheet = workbook.createSheet("POI"); + sheet.createFreezePane(1, 1); + sheet.setColumnWidth(0, 18 * 256); + sheet.setColumnWidth(1, 14 * 256); + sheet.setColumnWidth(3, 24 * 256); + + XSSFCellStyle currency = workbook.createCellStyle(); + currency.setDataFormat(workbook.getCreationHelper().createDataFormat().getFormat("$#,##0")); + + setText(sheet, "A1", "Metric", true); + setText(sheet, "B1", "Value", true); + setText(sheet, "A2", "Revenue", false); + setNumber(sheet, "B2", 1200, currency); + setText(sheet, "A3", "COGS", false); + setNumber(sheet, "B3", -450, currency); + setText(sheet, "A4", "Gross profit", false); + Cell formula = getCell(sheet, "B4"); + formula.setCellFormula("SUM(B2:B3)"); + formula.setCellStyle(currency); + + setText(sheet, "D1", "Merged review header", false); + sheet.addMergedRegion(CellRangeAddress.valueOf("D1:F1")); + setRichText(workbook, sheet, "D2"); + setHyperlink(workbook, sheet, "D4"); + addComment(workbook, sheet, "B4"); + addDataValidation(sheet, "C2"); + addTable(sheet); + addImage(workbook, sheet); + sheet.protectSheet("audit"); + + try (FileOutputStream stream = new FileOutputStream(output)) { + workbook.write(stream); + } + } + + System.out.println("{" + + "\"fixture_id\":\"" + json(fixtureId) + "\"," + + "\"operation\":\"write_fixture\"," + + "\"output_path\":\"" + json(outputPath) + "\"," + + "\"tool\":\"apache-poi\"," + + "\"counts\":{" + + "\"sheets\":1," + + "\"cells\":9," + + "\"formulas\":1," + + "\"rich_text\":1," + + "\"comments\":1," + + "\"hyperlinks\":1," + + "\"tables\":1," + + "\"data_validations\":1," + + "\"merged_ranges\":1," + + "\"images\":1," + + "\"protected_sheets\":1" + + "}}"); + } + + private static void setText(XSSFSheet sheet, String address, String value, boolean bold) { + Cell cell = getCell(sheet, address); + cell.setCellValue(value); + if (bold) { + Workbook workbook = sheet.getWorkbook(); + XSSFCellStyle style = (XSSFCellStyle) workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setBold(true); + style.setFont(font); + cell.setCellStyle(style); + } + } + + private static void setNumber(XSSFSheet sheet, String address, double value, XSSFCellStyle style) { + Cell cell = getCell(sheet, address); + cell.setCellValue(value); + cell.setCellStyle(style); + } + + private static void setRichText(XSSFWorkbook workbook, XSSFSheet sheet, String address) { + XSSFFont boldFont = workbook.createFont(); + boldFont.setBold(true); + XSSFFont italicFont = workbook.createFont(); + italicFont.setItalic(true); + XSSFRichTextString text = new XSSFRichTextString("Apache POI rich text"); + text.applyFont(0, 10, boldFont); + text.applyFont(10, text.length(), italicFont); + getCell(sheet, address).setCellValue(text); + } + + private static void setHyperlink(XSSFWorkbook workbook, XSSFSheet sheet, String address) { + CreationHelper helper = workbook.getCreationHelper(); + Hyperlink link = helper.createHyperlink(HyperlinkType.URL); + link.setAddress("https://poi.apache.org/"); + link.setLabel("Apache POI"); + Cell cell = getCell(sheet, address); + cell.setCellValue("Apache POI"); + cell.setHyperlink(link); + } + + private static void addComment(XSSFWorkbook workbook, XSSFSheet sheet, String address) { + CreationHelper helper = workbook.getCreationHelper(); + Drawing drawing = sheet.createDrawingPatriarch(); + ClientAnchor anchor = helper.createClientAnchor(); + Cell cell = getCell(sheet, address); + anchor.setCol1(cell.getColumnIndex() + 1); + anchor.setCol2(cell.getColumnIndex() + 4); + anchor.setRow1(cell.getRowIndex()); + anchor.setRow2(cell.getRowIndex() + 3); + Comment comment = drawing.createCellComment(anchor); + RichTextString text = helper.createRichTextString("POI formula comment."); + comment.setString(text); + comment.setAuthor("Apache POI Oracle"); + cell.setCellComment(comment); + } + + private static void addDataValidation(XSSFSheet sheet, String address) { + CellReference ref = new CellReference(address); + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = + helper.createExplicitListConstraint(new String[] {"Open", "Closed", "Review"}); + CellRangeAddressList range = new CellRangeAddressList(ref.getRow(), ref.getRow(), ref.getCol(), ref.getCol()); + DataValidation validation = helper.createValidation(constraint, range); + validation.setShowErrorBox(true); + validation.createErrorBox("Invalid value", "Choose a listed value."); + sheet.addValidationData(validation); + } + + private static void addTable(XSSFSheet sheet) { + setText(sheet, "F1", "Item", false); + setText(sheet, "G1", "Status", false); + setText(sheet, "F2", "Revenue", false); + setText(sheet, "G2", "Open", false); + setText(sheet, "F3", "COGS", false); + setText(sheet, "G3", "Closed", false); + setText(sheet, "F4", "Gross profit", false); + setText(sheet, "G4", "Review", false); + + AreaReference area = new AreaReference( + new CellReference("F1"), + new CellReference("G4"), + SpreadsheetVersion.EXCEL2007 + ); + XSSFTable table = sheet.createTable(area); + CTTable ctTable = table.getCTTable(); + ctTable.setId(1); + ctTable.setName("PoiReviewTable"); + ctTable.setDisplayName("PoiReviewTable"); + ctTable.setRef("F1:G4"); + ctTable.setTotalsRowShown(false); + if (ctTable.getTableColumns() == null) { + CTTableColumns columns = ctTable.addNewTableColumns(); + columns.setCount(2); + CTTableColumn first = columns.addNewTableColumn(); + first.setId(1); + first.setName("Item"); + CTTableColumn second = columns.addNewTableColumn(); + second.setId(2); + second.setName("Status"); + } + CTTableStyleInfo style = ctTable.isSetTableStyleInfo() + ? ctTable.getTableStyleInfo() + : ctTable.addNewTableStyleInfo(); + style.setName("TableStyleMedium2"); + style.setShowRowStripes(true); + } + + private static void addImage(XSSFWorkbook workbook, XSSFSheet sheet) { + byte[] image = Base64.getDecoder().decode(PIXEL_PNG_BASE64.getBytes(StandardCharsets.US_ASCII)); + int picture = workbook.addPicture(image, Workbook.PICTURE_TYPE_PNG); + CreationHelper helper = workbook.getCreationHelper(); + Drawing drawing = sheet.createDrawingPatriarch(); + ClientAnchor anchor = helper.createClientAnchor(); + anchor.setCol1(3); + anchor.setCol2(5); + anchor.setRow1(5); + anchor.setRow2(8); + drawing.createPicture(anchor, picture); + } + + private static Cell getCell(XSSFSheet sheet, String address) { + CellReference ref = new CellReference(address); + Row row = sheet.getRow(ref.getRow()); + if (row == null) { + row = sheet.createRow(ref.getRow()); + } + Cell cell = row.getCell(ref.getCol()); + if (cell == null) { + cell = row.createCell(ref.getCol(), CellType.BLANK); + } + return cell; + } + + private static String json(String value) { + return value.replace("\\", "\\\\").replace("\"", "\\\""); + } +} From 8410961babef211aaba60b3796132f3f6d72d490 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:21:36 -0700 Subject: [PATCH 15/25] test: add external fixture readback probes --- ...te_external_oracle_fixtures_with_wolfxl.py | 3 +- .../harness/external_fixture_pack.py | 3 + .../external_fixture_specs/apache_poi.py | 28 ++++++ .../harness/external_fixture_specs/base.py | 4 + .../external_fixture_specs/closedxml.py | 37 ++++++++ .../external_fixture_specs/excelize.py | 31 +++++++ .../harness/external_fixture_specs/exceljs.py | 28 ++++++ .../harness/external_fixture_specs/npoi.py | 22 +++++ .../harness/external_wolfxl_validation.py | 89 ++++++++++++++++++- tests/test_external_fixture_pack.py | 8 ++ 10 files changed, 250 insertions(+), 3 deletions(-) diff --git a/scripts/validate_external_oracle_fixtures_with_wolfxl.py b/scripts/validate_external_oracle_fixtures_with_wolfxl.py index 71c6fba..1116355 100644 --- a/scripts/validate_external_oracle_fixtures_with_wolfxl.py +++ b/scripts/validate_external_oracle_fixtures_with_wolfxl.py @@ -27,6 +27,8 @@ def main() -> int: print(f"{status} {result.fixture_id}: {result.modified_workbook}") if result.missing_parts_after_save: print(f" missing parts: {', '.join(result.missing_parts_after_save)}") + if result.readback_failures: + print(f" readback failures: {'; '.join(result.readback_failures)}") if result.error: print(f" error: {result.error}") return 0 if all(result.passed for result in results) else 1 @@ -34,4 +36,3 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) - diff --git a/src/excelbench/harness/external_fixture_pack.py b/src/excelbench/harness/external_fixture_pack.py index f25d600..9d92a37 100644 --- a/src/excelbench/harness/external_fixture_pack.py +++ b/src/excelbench/harness/external_fixture_pack.py @@ -47,6 +47,7 @@ class FixtureGenerationResult: workbook_path: Path write_result: ExternalOracleResult expected_parts: tuple[str, ...] + readback_probes: tuple[JSONDict, ...] missing_parts: tuple[str, ...] validations: tuple[ExternalOracleResult, ...] = field(default_factory=tuple) @@ -67,6 +68,7 @@ def to_json_dict(self, output_root: Path) -> JSONDict: "workbook": str(self.workbook_path.relative_to(output_root)), "passed": self.passed, "expected_parts": list(self.expected_parts), + "readback_probes": list(self.readback_probes), "missing_parts": list(self.missing_parts), "write_result": _oracle_result_to_json(self.write_result), "validations": [_oracle_result_to_json(result) for result in self.validations], @@ -138,6 +140,7 @@ def generate_external_fixture_pack( workbook_path=workbook_path, write_result=write_result, expected_parts=spec.expected_parts, + readback_probes=spec.readback_probes, missing_parts=missing_parts, validations=tuple(validations), ) diff --git a/src/excelbench/harness/external_fixture_specs/apache_poi.py b/src/excelbench/harness/external_fixture_specs/apache_poi.py index 6f9f4ad..3d88d4e 100644 --- a/src/excelbench/harness/external_fixture_specs/apache_poi.py +++ b/src/excelbench/harness/external_fixture_specs/apache_poi.py @@ -26,5 +26,33 @@ def apache_poi_fixture_specs() -> list[ExternalFixtureSpec]: "Apache POI table, formula, data validation, rich text, comment, hyperlink, " "image, merge, freeze panes, and sheet protection." ), + readback_probes=( + {"kind": "cell_value", "sheet": "POI", "cell": "A1", "expected": "Metric"}, + { + "kind": "cell_formula", + "sheet": "POI", + "cell": "B4", + "expected": "=SUM(B2:B3)", + }, + { + "kind": "comment_text", + "sheet": "POI", + "cell": "B4", + "contains": "POI formula", + }, + {"kind": "merged_range", "sheet": "POI", "range": "D1:F1"}, + { + "kind": "zip_contains", + "part": "xl/worksheets/sheet1.xml", + "contains": "dataValidations", + "label": "data validation survives", + }, + { + "kind": "zip_contains", + "part": "xl/tables/table1.xml", + "contains": "PoiReviewTable", + "label": "table name survives", + }, + ), ) ] diff --git a/src/excelbench/harness/external_fixture_specs/base.py b/src/excelbench/harness/external_fixture_specs/base.py index ec67f2c..81b89d6 100644 --- a/src/excelbench/harness/external_fixture_specs/base.py +++ b/src/excelbench/harness/external_fixture_specs/base.py @@ -18,6 +18,9 @@ class ExternalFixtureSpec: filename: Workbook filename to generate. payload: JSON payload for the source helper. expected_parts: OOXML package parts expected in the generated workbook. + readback_probes: Declarative checks run after WolfXL modify-save. Probe + kinds currently include ``cell_value``, ``cell_formula``, + ``comment_text``, ``merged_range``, and ``zip_contains``. notes: Human-readable reason this fixture exists. """ @@ -27,3 +30,4 @@ class ExternalFixtureSpec: payload: JSONDict expected_parts: tuple[str, ...] notes: str + readback_probes: tuple[JSONDict, ...] = () diff --git a/src/excelbench/harness/external_fixture_specs/closedxml.py b/src/excelbench/harness/external_fixture_specs/closedxml.py index d5a735b..dda4426 100644 --- a/src/excelbench/harness/external_fixture_specs/closedxml.py +++ b/src/excelbench/harness/external_fixture_specs/closedxml.py @@ -51,6 +51,22 @@ def closedxml_fixture_specs() -> list[ExternalFixtureSpec]: "xl/worksheets/sheet1.xml", ), notes="ClosedXML pivot/cache/table/conditional-formatting package layout.", + readback_probes=( + {"kind": "cell_value", "sheet": "Data", "cell": "A1", "expected": "Region"}, + {"kind": "cell_value", "sheet": "Data", "cell": "C4", "expected": 140}, + { + "kind": "zip_contains", + "part": "xl/worksheets/sheet1.xml", + "contains": "conditionalFormatting", + "label": "conditional formatting survives", + }, + { + "kind": "zip_contains", + "part": "xl/tables/table1.xml", + "contains": "ClosedXmlSales", + "label": "table name survives", + }, + ), ), ExternalFixtureSpec( fixture_id="closedxml_rich_comment_protection", @@ -91,5 +107,26 @@ def closedxml_fixture_specs() -> list[ExternalFixtureSpec]: "xl/worksheets/sheet1.xml", ), notes="ClosedXML rich text, legacy comments, and sheet protection.", + readback_probes=( + {"kind": "cell_value", "sheet": "Review", "cell": "A1", "expected": "Finding"}, + { + "kind": "comment_text", + "sheet": "Review", + "cell": "B2", + "contains": "Tie this status", + }, + { + "kind": "zip_contains", + "part": "xl/worksheets/sheet1.xml", + "contains": "sheetProtection", + "label": "sheet protection survives", + }, + { + "kind": "zip_contains", + "part": "xl/sharedStrings.xml", + "contains": ":r>", + "label": "rich text runs survive", + }, + ), ), ] diff --git a/src/excelbench/harness/external_fixture_specs/excelize.py b/src/excelbench/harness/external_fixture_specs/excelize.py index 1a09087..b2f48f1 100644 --- a/src/excelbench/harness/external_fixture_specs/excelize.py +++ b/src/excelbench/harness/external_fixture_specs/excelize.py @@ -72,6 +72,22 @@ def excelize_fixture_specs() -> list[ExternalFixtureSpec]: "xl/drawings/drawing1.xml", ), notes="Pivots, slicers, charts, CF, tables, and drawings in one workbook.", + readback_probes=( + {"kind": "cell_value", "sheet": "Data", "cell": "A1", "expected": "Region"}, + {"kind": "cell_value", "sheet": "Data", "cell": "C6", "expected": 115}, + { + "kind": "zip_contains", + "part": "xl/worksheets/sheet1.xml", + "contains": "conditionalFormatting", + "label": "conditional formatting survives", + }, + { + "kind": "zip_contains", + "part": "xl/worksheets/sheet1.xml", + "contains": "slicerList", + "label": "slicer extension survives", + }, + ), ), ExternalFixtureSpec( fixture_id="excelize_chart_points_formula_cf", @@ -137,6 +153,21 @@ def excelize_fixture_specs() -> list[ExternalFixtureSpec]: "xl/worksheets/sheet1.xml", ), notes="Per-point chart styling, formulas, and conditional-formatting rules.", + readback_probes=( + {"kind": "cell_value", "sheet": "Metrics", "cell": "A1", "expected": "Month"}, + { + "kind": "cell_formula", + "sheet": "Metrics", + "cell": "B5", + "expected": "=SUM(B2:B4)", + }, + { + "kind": "zip_contains", + "part": "xl/worksheets/sheet1.xml", + "contains": "conditionalFormatting", + "label": "conditional formatting survives", + }, + ), ), ] diff --git a/src/excelbench/harness/external_fixture_specs/exceljs.py b/src/excelbench/harness/external_fixture_specs/exceljs.py index 496dfff..2535fd6 100644 --- a/src/excelbench/harness/external_fixture_specs/exceljs.py +++ b/src/excelbench/harness/external_fixture_specs/exceljs.py @@ -106,5 +106,33 @@ def exceljs_fixture_specs() -> list[ExternalFixtureSpec]: "ExcelJS table, formula, data validation, rich text, comment, hyperlink, " "image, merge, freeze panes, and sheet protection." ), + readback_probes=( + {"kind": "cell_value", "sheet": "ExcelJS", "cell": "A1", "expected": "Metric"}, + { + "kind": "cell_formula", + "sheet": "ExcelJS", + "cell": "B4", + "expected": "=SUM(B2:B3)", + }, + { + "kind": "comment_text", + "sheet": "ExcelJS", + "cell": "B4", + "contains": "Formula result", + }, + {"kind": "merged_range", "sheet": "ExcelJS", "range": "D1:F1"}, + { + "kind": "zip_contains", + "part": "xl/worksheets/sheet1.xml", + "contains": "dataValidations", + "label": "data validation survives", + }, + { + "kind": "zip_contains", + "part": "xl/tables/table1.xml", + "contains": "ExcelJsReviewTable", + "label": "table name survives", + }, + ), ) ] diff --git a/src/excelbench/harness/external_fixture_specs/npoi.py b/src/excelbench/harness/external_fixture_specs/npoi.py index 86241af..0c6046d 100644 --- a/src/excelbench/harness/external_fixture_specs/npoi.py +++ b/src/excelbench/harness/external_fixture_specs/npoi.py @@ -53,5 +53,27 @@ def npoi_fixture_specs() -> list[ExternalFixtureSpec]: "xl/worksheets/sheet1.xml", ), notes="NPOI formula, rich text, legacy comments, merged range, and protection.", + readback_probes=( + {"kind": "cell_value", "sheet": "NPOI", "cell": "A1", "expected": "Account"}, + { + "kind": "cell_formula", + "sheet": "NPOI", + "cell": "B4", + "expected": "=SUM(B2:B3)", + }, + { + "kind": "comment_text", + "sheet": "NPOI", + "cell": "B4", + "contains": "Formula result", + }, + {"kind": "merged_range", "sheet": "NPOI", "range": "D1:F1"}, + { + "kind": "zip_contains", + "part": "xl/worksheets/sheet1.xml", + "contains": "sheetProtection", + "label": "sheet protection survives", + }, + ), ) ] diff --git a/src/excelbench/harness/external_wolfxl_validation.py b/src/excelbench/harness/external_wolfxl_validation.py index 9a607fe..70ecc2e 100644 --- a/src/excelbench/harness/external_wolfxl_validation.py +++ b/src/excelbench/harness/external_wolfxl_validation.py @@ -20,7 +20,9 @@ class WolfXLFixtureValidation: source_workbook: Path modified_workbook: Path expected_parts: tuple[str, ...] + readback_probes: tuple[JSONDict, ...] missing_parts_after_save: tuple[str, ...] + readback_failures: tuple[str, ...] read_passed: bool modify_save_passed: bool marker_passed: bool @@ -34,6 +36,7 @@ def passed(self) -> bool: and self.modify_save_passed and self.marker_passed and not self.missing_parts_after_save + and not self.readback_failures and self.error is None ) @@ -44,7 +47,9 @@ def to_json_dict(self, fixture_root: Path) -> JSONDict: "source_workbook": _display_path(self.source_workbook, fixture_root), "modified_workbook": _display_path(self.modified_workbook, fixture_root), "expected_parts": list(self.expected_parts), + "readback_probes": list(self.readback_probes), "missing_parts_after_save": list(self.missing_parts_after_save), + "readback_failures": list(self.readback_failures), "read_passed": self.read_passed, "modify_save_passed": self.modify_save_passed, "marker_passed": self.marker_passed, @@ -84,6 +89,9 @@ def validate_wolfxl_external_fixture_pack( fixture_id = str(entry.get("fixture_id", "unknown")) source_workbook = fixture_root / str(entry["workbook"]) expected_parts = tuple(str(part) for part in entry.get("expected_parts", [])) + readback_probes = tuple( + cast(JSONDict, probe) for probe in entry.get("readback_probes", []) + ) modified_workbook = output_dir / source_workbook.name results.append( _validate_one_fixture( @@ -91,6 +99,7 @@ def validate_wolfxl_external_fixture_pack( source_workbook=source_workbook, modified_workbook=modified_workbook, expected_parts=expected_parts, + readback_probes=readback_probes, marker_cell=marker_cell, marker_value=marker_value, ) @@ -113,6 +122,7 @@ def _validate_one_fixture( source_workbook: Path, modified_workbook: Path, expected_parts: tuple[str, ...], + readback_probes: tuple[JSONDict, ...], marker_cell: str, marker_value: str, ) -> WolfXLFixtureValidation: @@ -136,14 +146,23 @@ def _validate_one_fixture( after_parts = _zip_part_names(modified_workbook) missing_parts = tuple(part for part in tracked_parts if part not in after_parts) roundtrip = openpyxl.load_workbook(modified_workbook) - marker_passed = roundtrip[sheet_name][marker_cell].value == marker_value - roundtrip.close() + try: + marker_passed = roundtrip[sheet_name][marker_cell].value == marker_value + readback_failures = _run_readback_probes( + roundtrip, + modified_workbook, + readback_probes, + ) + finally: + roundtrip.close() return WolfXLFixtureValidation( fixture_id=fixture_id, source_workbook=source_workbook, modified_workbook=modified_workbook, expected_parts=expected_parts, + readback_probes=readback_probes, missing_parts_after_save=missing_parts, + readback_failures=readback_failures, read_passed=read_passed, modify_save_passed=True, marker_passed=marker_passed, @@ -154,7 +173,9 @@ def _validate_one_fixture( source_workbook=source_workbook, modified_workbook=modified_workbook, expected_parts=expected_parts, + readback_probes=readback_probes, missing_parts_after_save=(), + readback_failures=(), read_passed=False, modify_save_passed=False, marker_passed=False, @@ -174,6 +195,70 @@ def _zip_part_names(path: Path) -> set[str]: return set(workbook_zip.namelist()) +def _run_readback_probes( + workbook: Any, + workbook_path: Path, + probes: tuple[JSONDict, ...], +) -> tuple[str, ...]: + failures: list[str] = [] + for index, probe in enumerate(probes, start=1): + kind = str(probe.get("kind", "")) + label = str(probe.get("label") or f"probe {index}") + try: + if kind == "cell_value": + _check_cell_value(workbook, probe) + elif kind == "cell_formula": + _check_cell_formula(workbook, probe) + elif kind == "comment_text": + _check_comment_text(workbook, probe) + elif kind == "merged_range": + _check_merged_range(workbook, probe) + elif kind == "zip_contains": + _check_zip_contains(workbook_path, probe) + else: + raise AssertionError(f"unknown probe kind {kind!r}") + except Exception as exc: + failures.append(f"{label}: {type(exc).__name__}: {exc}") + return tuple(failures) + + +def _check_cell_value(workbook: Any, probe: JSONDict) -> None: + value = workbook[str(probe["sheet"])][str(probe["cell"])].value + expected = probe.get("expected") + if value != expected: + raise AssertionError(f"expected {expected!r}, got {value!r}") + + +def _check_cell_formula(workbook: Any, probe: JSONDict) -> None: + value = workbook[str(probe["sheet"])][str(probe["cell"])].value + expected = str(probe["expected"]) + if value != expected: + raise AssertionError(f"expected formula {expected!r}, got {value!r}") + + +def _check_comment_text(workbook: Any, probe: JSONDict) -> None: + comment = workbook[str(probe["sheet"])][str(probe["cell"])].comment + expected = str(probe["contains"]) + if comment is None or expected not in comment.text: + raise AssertionError(f"expected comment containing {expected!r}") + + +def _check_merged_range(workbook: Any, probe: JSONDict) -> None: + ranges = {str(cell_range) for cell_range in workbook[str(probe["sheet"])].merged_cells.ranges} + expected = str(probe["range"]) + if expected not in ranges: + raise AssertionError(f"expected merged range {expected!r}, got {sorted(ranges)}") + + +def _check_zip_contains(workbook_path: Path, probe: JSONDict) -> None: + part = str(probe["part"]) + expected = str(probe["contains"]) + with ZipFile(workbook_path) as workbook_zip: + text = workbook_zip.read(part).decode("utf-8", errors="ignore") + if expected not in text: + raise AssertionError(f"expected {part} to contain {expected!r}") + + def _display_path(path: Path, root: Path) -> str: try: return str(path.relative_to(root)) diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index e9b39f5..63080a9 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -33,6 +33,8 @@ def test_excelize_fixture_specs_are_stable() -> None: assert all(spec.tool == "excelize" for spec in specs) assert "xl/pivotTables/pivotTable1.xml" in specs[0].expected_parts assert "xl/charts/chart1.xml" in specs[1].expected_parts + assert any(probe["kind"] == "zip_contains" for probe in specs[0].readback_probes) + assert any(probe["kind"] == "cell_formula" for probe in specs[1].readback_probes) def test_closedxml_fixture_specs_are_stable() -> None: @@ -47,6 +49,7 @@ def test_closedxml_fixture_specs_are_stable() -> None: assert "pivotCache/pivotCacheDefinition1.xml" in specs[0].expected_parts assert "xl/comments1.xml" in specs[1].expected_parts assert "xl/drawings/vmldrawing.vml" in specs[1].expected_parts + assert any(probe["kind"] == "comment_text" for probe in specs[1].readback_probes) assert [spec.fixture_id for spec in external_fixture_specs()] == [ "excelize_sales_pivot_slicer_chart", "excelize_chart_points_formula_cf", @@ -65,6 +68,7 @@ def test_apache_poi_fixture_specs_are_stable() -> None: assert all(spec.tool == "apache-poi" for spec in specs) assert "xl/tables/table1.xml" in specs[0].expected_parts assert "xl/drawings/vmlDrawing0.vml" in specs[0].expected_parts + assert any(probe["kind"] == "merged_range" for probe in specs[0].readback_probes) def test_exceljs_fixture_specs_are_stable() -> None: @@ -74,6 +78,7 @@ def test_exceljs_fixture_specs_are_stable() -> None: assert all(spec.tool == "exceljs" for spec in specs) assert "xl/tables/table1.xml" in specs[0].expected_parts assert "xl/media/image1.png" in specs[0].expected_parts + assert any(probe["kind"] == "cell_formula" for probe in specs[0].readback_probes) def test_npoi_fixture_specs_are_stable() -> None: @@ -83,6 +88,7 @@ def test_npoi_fixture_specs_are_stable() -> None: assert all(spec.tool == "npoi" for spec in specs) assert "xl/comments1.xml" in specs[0].expected_parts assert "xl/drawings/vmlDrawing1.vml" in specs[0].expected_parts + assert any(probe["kind"] == "comment_text" for probe in specs[0].readback_probes) @pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") @@ -204,3 +210,5 @@ def test_validate_wolfxl_external_fixture_pack(tmp_path: Path) -> None: assert all(result.passed for result in results) assert (tmp_path / "wolfxl-validation.json").exists() assert all(not result.missing_parts_after_save for result in results) + assert all(not result.readback_failures for result in results) + assert any(result.readback_probes for result in results) From 298cea86f4436b83a07cc1a88f4d6e005d11a20e Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:32:46 -0700 Subject: [PATCH 16/25] test: expand external fixture readback probes --- .../external_fixture_specs/apache_poi.py | 26 +++++++ .../harness/external_fixture_specs/base.py | 4 +- .../external_fixture_specs/closedxml.py | 7 ++ .../external_fixture_specs/excelize.py | 7 ++ .../harness/external_fixture_specs/exceljs.py | 26 +++++++ .../harness/external_wolfxl_validation.py | 68 +++++++++++++++++++ tests/test_external_fixture_pack.py | 4 ++ 7 files changed, 141 insertions(+), 1 deletion(-) diff --git a/src/excelbench/harness/external_fixture_specs/apache_poi.py b/src/excelbench/harness/external_fixture_specs/apache_poi.py index 3d88d4e..81bb6d7 100644 --- a/src/excelbench/harness/external_fixture_specs/apache_poi.py +++ b/src/excelbench/harness/external_fixture_specs/apache_poi.py @@ -34,13 +34,39 @@ def apache_poi_fixture_specs() -> list[ExternalFixtureSpec]: "cell": "B4", "expected": "=SUM(B2:B3)", }, + { + "kind": "cell_style", + "sheet": "POI", + "cell": "B2", + "expected": {"number_format": "$#,##0"}, + }, { "kind": "comment_text", "sheet": "POI", "cell": "B4", "contains": "POI formula", }, + { + "kind": "hyperlink_target", + "sheet": "POI", + "cell": "D4", + "target": "https://poi.apache.org/", + }, + { + "kind": "data_validation", + "sheet": "POI", + "cell": "C2", + "type": "list", + "formula1": '"Open,Closed,Review"', + }, {"kind": "merged_range", "sheet": "POI", "range": "D1:F1"}, + { + "kind": "table_metadata", + "sheet": "POI", + "name": "PoiReviewTable", + "ref": "F1:G4", + "style": "TableStyleMedium2", + }, { "kind": "zip_contains", "part": "xl/worksheets/sheet1.xml", diff --git a/src/excelbench/harness/external_fixture_specs/base.py b/src/excelbench/harness/external_fixture_specs/base.py index 81b89d6..1fd689c 100644 --- a/src/excelbench/harness/external_fixture_specs/base.py +++ b/src/excelbench/harness/external_fixture_specs/base.py @@ -20,7 +20,9 @@ class ExternalFixtureSpec: expected_parts: OOXML package parts expected in the generated workbook. readback_probes: Declarative checks run after WolfXL modify-save. Probe kinds currently include ``cell_value``, ``cell_formula``, - ``comment_text``, ``merged_range``, and ``zip_contains``. + ``cell_style``, ``comment_text``, ``hyperlink_target``, + ``data_validation``, ``merged_range``, ``table_metadata``, and + ``zip_contains``. notes: Human-readable reason this fixture exists. """ diff --git a/src/excelbench/harness/external_fixture_specs/closedxml.py b/src/excelbench/harness/external_fixture_specs/closedxml.py index dda4426..5fd7716 100644 --- a/src/excelbench/harness/external_fixture_specs/closedxml.py +++ b/src/excelbench/harness/external_fixture_specs/closedxml.py @@ -66,6 +66,13 @@ def closedxml_fixture_specs() -> list[ExternalFixtureSpec]: "contains": "ClosedXmlSales", "label": "table name survives", }, + { + "kind": "table_metadata", + "sheet": "Data", + "name": "ClosedXmlSales", + "ref": "A1:C4", + "style": "TableStyleMedium2", + }, ), ), ExternalFixtureSpec( diff --git a/src/excelbench/harness/external_fixture_specs/excelize.py b/src/excelbench/harness/external_fixture_specs/excelize.py index b2f48f1..3088a6f 100644 --- a/src/excelbench/harness/external_fixture_specs/excelize.py +++ b/src/excelbench/harness/external_fixture_specs/excelize.py @@ -87,6 +87,13 @@ def excelize_fixture_specs() -> list[ExternalFixtureSpec]: "contains": "slicerList", "label": "slicer extension survives", }, + { + "kind": "table_metadata", + "sheet": "Data", + "name": "SalesTable", + "ref": "A1:C6", + "style": "TableStyleMedium9", + }, ), ), ExternalFixtureSpec( diff --git a/src/excelbench/harness/external_fixture_specs/exceljs.py b/src/excelbench/harness/external_fixture_specs/exceljs.py index 2535fd6..d2b574a 100644 --- a/src/excelbench/harness/external_fixture_specs/exceljs.py +++ b/src/excelbench/harness/external_fixture_specs/exceljs.py @@ -114,13 +114,39 @@ def exceljs_fixture_specs() -> list[ExternalFixtureSpec]: "cell": "B4", "expected": "=SUM(B2:B3)", }, + { + "kind": "cell_style", + "sheet": "ExcelJS", + "cell": "B2", + "expected": {"number_format": "$#,##0"}, + }, { "kind": "comment_text", "sheet": "ExcelJS", "cell": "B4", "contains": "Formula result", }, + { + "kind": "hyperlink_target", + "sheet": "ExcelJS", + "cell": "D4", + "target": "https://github.com/exceljs/exceljs", + }, + { + "kind": "data_validation", + "sheet": "ExcelJS", + "cell": "C2", + "type": "list", + "formula1": '"Open,Closed,Review"', + }, {"kind": "merged_range", "sheet": "ExcelJS", "range": "D1:F1"}, + { + "kind": "table_metadata", + "sheet": "ExcelJS", + "name": "ExcelJsReviewTable", + "ref": "F1:G4", + "style": "TableStyleMedium2", + }, { "kind": "zip_contains", "part": "xl/worksheets/sheet1.xml", diff --git a/src/excelbench/harness/external_wolfxl_validation.py b/src/excelbench/harness/external_wolfxl_validation.py index 70ecc2e..ed97022 100644 --- a/src/excelbench/harness/external_wolfxl_validation.py +++ b/src/excelbench/harness/external_wolfxl_validation.py @@ -209,10 +209,18 @@ def _run_readback_probes( _check_cell_value(workbook, probe) elif kind == "cell_formula": _check_cell_formula(workbook, probe) + elif kind == "cell_style": + _check_cell_style(workbook, probe) elif kind == "comment_text": _check_comment_text(workbook, probe) + elif kind == "hyperlink_target": + _check_hyperlink_target(workbook, probe) + elif kind == "data_validation": + _check_data_validation(workbook, probe) elif kind == "merged_range": _check_merged_range(workbook, probe) + elif kind == "table_metadata": + _check_table_metadata(workbook, probe) elif kind == "zip_contains": _check_zip_contains(workbook_path, probe) else: @@ -236,6 +244,20 @@ def _check_cell_formula(workbook: Any, probe: JSONDict) -> None: raise AssertionError(f"expected formula {expected!r}, got {value!r}") +def _check_cell_style(workbook: Any, probe: JSONDict) -> None: + cell = workbook[str(probe["sheet"])][str(probe["cell"])] + expected = cast(JSONDict, probe.get("expected", {})) + checks = { + "number_format": cell.number_format, + "font_bold": cell.font.bold, + "font_italic": cell.font.italic, + } + for key, expected_value in expected.items(): + actual = checks.get(str(key)) + if actual != expected_value: + raise AssertionError(f"expected {key}={expected_value!r}, got {actual!r}") + + def _check_comment_text(workbook: Any, probe: JSONDict) -> None: comment = workbook[str(probe["sheet"])][str(probe["cell"])].comment expected = str(probe["contains"]) @@ -243,6 +265,36 @@ def _check_comment_text(workbook: Any, probe: JSONDict) -> None: raise AssertionError(f"expected comment containing {expected!r}") +def _check_hyperlink_target(workbook: Any, probe: JSONDict) -> None: + hyperlink = workbook[str(probe["sheet"])][str(probe["cell"])].hyperlink + expected = str(probe["target"]) + actual = hyperlink.target if hyperlink is not None else None + if actual != expected: + raise AssertionError(f"expected hyperlink target {expected!r}, got {actual!r}") + + +def _check_data_validation(workbook: Any, probe: JSONDict) -> None: + sheet = workbook[str(probe["sheet"])] + expected_cell = str(probe["cell"]) + expected_type = probe.get("type") + expected_formula1 = probe.get("formula1") + for validation in sheet.data_validations.dataValidation: + sqref = str(validation.sqref) + cells = {part.strip() for part in sqref.split()} + if expected_cell not in cells: + continue + if expected_type is not None and validation.type != expected_type: + raise AssertionError( + f"expected validation type {expected_type!r}, got {validation.type!r}" + ) + if expected_formula1 is not None and validation.formula1 != expected_formula1: + raise AssertionError( + f"expected validation formula1 {expected_formula1!r}, got {validation.formula1!r}" + ) + return + raise AssertionError(f"expected data validation covering {expected_cell!r}") + + def _check_merged_range(workbook: Any, probe: JSONDict) -> None: ranges = {str(cell_range) for cell_range in workbook[str(probe["sheet"])].merged_cells.ranges} expected = str(probe["range"]) @@ -250,6 +302,22 @@ def _check_merged_range(workbook: Any, probe: JSONDict) -> None: raise AssertionError(f"expected merged range {expected!r}, got {sorted(ranges)}") +def _check_table_metadata(workbook: Any, probe: JSONDict) -> None: + sheet = workbook[str(probe["sheet"])] + table_name = str(probe["name"]) + if table_name not in sheet.tables: + raise AssertionError(f"expected table {table_name!r}, got {sorted(sheet.tables)}") + table = sheet.tables[table_name] + expected_ref = probe.get("ref") + if expected_ref is not None and table.ref != expected_ref: + raise AssertionError(f"expected table ref {expected_ref!r}, got {table.ref!r}") + expected_style = probe.get("style") + style = getattr(table, "tableStyleInfo", None) + actual_style = style.name if style is not None else None + if expected_style is not None and actual_style != expected_style: + raise AssertionError(f"expected table style {expected_style!r}, got {actual_style!r}") + + def _check_zip_contains(workbook_path: Path, probe: JSONDict) -> None: part = str(probe["part"]) expected = str(probe["contains"]) diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index 63080a9..5b2046a 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -47,6 +47,7 @@ def test_closedxml_fixture_specs_are_stable() -> None: assert all(spec.tool == "closedxml" for spec in specs) assert "xl/pivotTables/pivotTable.xml" in specs[0].expected_parts assert "pivotCache/pivotCacheDefinition1.xml" in specs[0].expected_parts + assert any(probe["kind"] == "table_metadata" for probe in specs[0].readback_probes) assert "xl/comments1.xml" in specs[1].expected_parts assert "xl/drawings/vmldrawing.vml" in specs[1].expected_parts assert any(probe["kind"] == "comment_text" for probe in specs[1].readback_probes) @@ -68,6 +69,8 @@ def test_apache_poi_fixture_specs_are_stable() -> None: assert all(spec.tool == "apache-poi" for spec in specs) assert "xl/tables/table1.xml" in specs[0].expected_parts assert "xl/drawings/vmlDrawing0.vml" in specs[0].expected_parts + assert any(probe["kind"] == "data_validation" for probe in specs[0].readback_probes) + assert any(probe["kind"] == "hyperlink_target" for probe in specs[0].readback_probes) assert any(probe["kind"] == "merged_range" for probe in specs[0].readback_probes) @@ -78,6 +81,7 @@ def test_exceljs_fixture_specs_are_stable() -> None: assert all(spec.tool == "exceljs" for spec in specs) assert "xl/tables/table1.xml" in specs[0].expected_parts assert "xl/media/image1.png" in specs[0].expected_parts + assert any(probe["kind"] == "cell_style" for probe in specs[0].readback_probes) assert any(probe["kind"] == "cell_formula" for probe in specs[0].readback_probes) From 68619510259c2ca38cc7a31cae598271a274a13b Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:39:36 -0700 Subject: [PATCH 17/25] test: probe formatting and drawing relationships --- .../external_fixture_specs/apache_poi.py | 7 +++ .../harness/external_fixture_specs/base.py | 6 +-- .../external_fixture_specs/closedxml.py | 7 +++ .../external_fixture_specs/excelize.py | 23 ++++++++ .../harness/external_fixture_specs/exceljs.py | 7 +++ .../harness/external_wolfxl_validation.py | 54 +++++++++++++++++++ tests/test_external_fixture_pack.py | 3 ++ 7 files changed, 104 insertions(+), 3 deletions(-) diff --git a/src/excelbench/harness/external_fixture_specs/apache_poi.py b/src/excelbench/harness/external_fixture_specs/apache_poi.py index 81bb6d7..988fa75 100644 --- a/src/excelbench/harness/external_fixture_specs/apache_poi.py +++ b/src/excelbench/harness/external_fixture_specs/apache_poi.py @@ -67,6 +67,13 @@ def apache_poi_fixture_specs() -> list[ExternalFixtureSpec]: "ref": "F1:G4", "style": "TableStyleMedium2", }, + { + "kind": "relationship_target", + "part": "xl/drawings/_rels/drawing1.xml.rels", + "target": "../media/image1.png", + "type_contains": "/image", + "label": "image drawing relationship survives", + }, { "kind": "zip_contains", "part": "xl/worksheets/sheet1.xml", diff --git a/src/excelbench/harness/external_fixture_specs/base.py b/src/excelbench/harness/external_fixture_specs/base.py index 1fd689c..dcafa5e 100644 --- a/src/excelbench/harness/external_fixture_specs/base.py +++ b/src/excelbench/harness/external_fixture_specs/base.py @@ -20,9 +20,9 @@ class ExternalFixtureSpec: expected_parts: OOXML package parts expected in the generated workbook. readback_probes: Declarative checks run after WolfXL modify-save. Probe kinds currently include ``cell_value``, ``cell_formula``, - ``cell_style``, ``comment_text``, ``hyperlink_target``, - ``data_validation``, ``merged_range``, ``table_metadata``, and - ``zip_contains``. + ``cell_style``, ``conditional_formatting``, ``comment_text``, + ``hyperlink_target``, ``data_validation``, ``merged_range``, + ``table_metadata``, ``relationship_target``, and ``zip_contains``. notes: Human-readable reason this fixture exists. """ diff --git a/src/excelbench/harness/external_fixture_specs/closedxml.py b/src/excelbench/harness/external_fixture_specs/closedxml.py index 5fd7716..0f5f57f 100644 --- a/src/excelbench/harness/external_fixture_specs/closedxml.py +++ b/src/excelbench/harness/external_fixture_specs/closedxml.py @@ -60,6 +60,13 @@ def closedxml_fixture_specs() -> list[ExternalFixtureSpec]: "contains": "conditionalFormatting", "label": "conditional formatting survives", }, + { + "kind": "conditional_formatting", + "sheet": "Data", + "sqref": "C2:C4", + "type": "dataBar", + "priority": 2, + }, { "kind": "zip_contains", "part": "xl/tables/table1.xml", diff --git a/src/excelbench/harness/external_fixture_specs/excelize.py b/src/excelbench/harness/external_fixture_specs/excelize.py index 3088a6f..606fd70 100644 --- a/src/excelbench/harness/external_fixture_specs/excelize.py +++ b/src/excelbench/harness/external_fixture_specs/excelize.py @@ -81,6 +81,13 @@ def excelize_fixture_specs() -> list[ExternalFixtureSpec]: "contains": "conditionalFormatting", "label": "conditional formatting survives", }, + { + "kind": "conditional_formatting", + "sheet": "Data", + "sqref": "C2:C6", + "type": "dataBar", + "priority": 2, + }, { "kind": "zip_contains", "part": "xl/worksheets/sheet1.xml", @@ -94,6 +101,13 @@ def excelize_fixture_specs() -> list[ExternalFixtureSpec]: "ref": "A1:C6", "style": "TableStyleMedium9", }, + { + "kind": "relationship_target", + "part": "xl/drawings/_rels/drawing1.xml.rels", + "target": "../media/image1.png", + "type_contains": "/image", + "label": "image drawing relationship survives", + }, ), ), ExternalFixtureSpec( @@ -174,6 +188,15 @@ def excelize_fixture_specs() -> list[ExternalFixtureSpec]: "contains": "conditionalFormatting", "label": "conditional formatting survives", }, + { + "kind": "conditional_formatting", + "sheet": "Metrics", + "sqref": "C2:C4", + "type": "cellIs", + "priority": 2, + "operator": "greaterThan", + "formula": "0.3", + }, ), ), ] diff --git a/src/excelbench/harness/external_fixture_specs/exceljs.py b/src/excelbench/harness/external_fixture_specs/exceljs.py index d2b574a..4f95108 100644 --- a/src/excelbench/harness/external_fixture_specs/exceljs.py +++ b/src/excelbench/harness/external_fixture_specs/exceljs.py @@ -147,6 +147,13 @@ def exceljs_fixture_specs() -> list[ExternalFixtureSpec]: "ref": "F1:G4", "style": "TableStyleMedium2", }, + { + "kind": "relationship_target", + "part": "xl/drawings/_rels/drawing1.xml.rels", + "target": "../media/image1.png", + "type_contains": "/image", + "label": "image drawing relationship survives", + }, { "kind": "zip_contains", "part": "xl/worksheets/sheet1.xml", diff --git a/src/excelbench/harness/external_wolfxl_validation.py b/src/excelbench/harness/external_wolfxl_validation.py index ed97022..1d398e3 100644 --- a/src/excelbench/harness/external_wolfxl_validation.py +++ b/src/excelbench/harness/external_wolfxl_validation.py @@ -4,6 +4,7 @@ import json import shutil +import xml.etree.ElementTree as ET from dataclasses import dataclass from pathlib import Path from typing import Any, cast @@ -211,6 +212,8 @@ def _run_readback_probes( _check_cell_formula(workbook, probe) elif kind == "cell_style": _check_cell_style(workbook, probe) + elif kind == "conditional_formatting": + _check_conditional_formatting(workbook, probe) elif kind == "comment_text": _check_comment_text(workbook, probe) elif kind == "hyperlink_target": @@ -221,6 +224,8 @@ def _run_readback_probes( _check_merged_range(workbook, probe) elif kind == "table_metadata": _check_table_metadata(workbook, probe) + elif kind == "relationship_target": + _check_relationship_target(workbook_path, probe) elif kind == "zip_contains": _check_zip_contains(workbook_path, probe) else: @@ -265,6 +270,35 @@ def _check_comment_text(workbook: Any, probe: JSONDict) -> None: raise AssertionError(f"expected comment containing {expected!r}") +def _check_conditional_formatting(workbook: Any, probe: JSONDict) -> None: + sheet = workbook[str(probe["sheet"])] + expected_sqref = str(probe["sqref"]) + expected_type = str(probe["type"]) + expected_priority = probe.get("priority") + expected_operator = probe.get("operator") + expected_formula = probe.get("formula") + for formatting in sheet.conditional_formatting: + if str(formatting.sqref) != expected_sqref: + continue + for rule in formatting.rules: + if rule.type != expected_type: + continue + if expected_priority is not None and rule.priority != expected_priority: + raise AssertionError( + f"expected priority {expected_priority!r}, got {rule.priority!r}" + ) + if expected_operator is not None and rule.operator != expected_operator: + raise AssertionError( + f"expected operator {expected_operator!r}, got {rule.operator!r}" + ) + if expected_formula is not None and list(rule.formula or []) != [expected_formula]: + raise AssertionError( + f"expected formula {[expected_formula]!r}, got {list(rule.formula or [])!r}" + ) + return + raise AssertionError(f"expected {expected_type!r} conditional format on {expected_sqref!r}") + + def _check_hyperlink_target(workbook: Any, probe: JSONDict) -> None: hyperlink = workbook[str(probe["sheet"])][str(probe["cell"])].hyperlink expected = str(probe["target"]) @@ -318,6 +352,26 @@ def _check_table_metadata(workbook: Any, probe: JSONDict) -> None: raise AssertionError(f"expected table style {expected_style!r}, got {actual_style!r}") +def _check_relationship_target(workbook_path: Path, probe: JSONDict) -> None: + part = str(probe["part"]) + expected_target = str(probe["target"]) + expected_type_contains = probe.get("type_contains") + with ZipFile(workbook_path) as workbook_zip: + root = ET.fromstring(workbook_zip.read(part)) + for relationship in root: + target = relationship.attrib.get("Target") + rel_type = relationship.attrib.get("Type", "") + if target != expected_target: + continue + if expected_type_contains is not None and str(expected_type_contains) not in rel_type: + raise AssertionError( + "expected relationship type containing " + f"{expected_type_contains!r}, got {rel_type!r}" + ) + return + raise AssertionError(f"expected relationship target {expected_target!r} in {part}") + + def _check_zip_contains(workbook_path: Path, probe: JSONDict) -> None: part = str(probe["part"]) expected = str(probe["contains"]) diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index 5b2046a..2cf215b 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -33,6 +33,7 @@ def test_excelize_fixture_specs_are_stable() -> None: assert all(spec.tool == "excelize" for spec in specs) assert "xl/pivotTables/pivotTable1.xml" in specs[0].expected_parts assert "xl/charts/chart1.xml" in specs[1].expected_parts + assert any(probe["kind"] == "conditional_formatting" for probe in specs[0].readback_probes) assert any(probe["kind"] == "zip_contains" for probe in specs[0].readback_probes) assert any(probe["kind"] == "cell_formula" for probe in specs[1].readback_probes) @@ -47,6 +48,7 @@ def test_closedxml_fixture_specs_are_stable() -> None: assert all(spec.tool == "closedxml" for spec in specs) assert "xl/pivotTables/pivotTable.xml" in specs[0].expected_parts assert "pivotCache/pivotCacheDefinition1.xml" in specs[0].expected_parts + assert any(probe["kind"] == "conditional_formatting" for probe in specs[0].readback_probes) assert any(probe["kind"] == "table_metadata" for probe in specs[0].readback_probes) assert "xl/comments1.xml" in specs[1].expected_parts assert "xl/drawings/vmldrawing.vml" in specs[1].expected_parts @@ -72,6 +74,7 @@ def test_apache_poi_fixture_specs_are_stable() -> None: assert any(probe["kind"] == "data_validation" for probe in specs[0].readback_probes) assert any(probe["kind"] == "hyperlink_target" for probe in specs[0].readback_probes) assert any(probe["kind"] == "merged_range" for probe in specs[0].readback_probes) + assert any(probe["kind"] == "relationship_target" for probe in specs[0].readback_probes) def test_exceljs_fixture_specs_are_stable() -> None: From 7f0a99bd3e58f83907723e22125a18d829057628 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:56:19 -0700 Subject: [PATCH 18/25] test: probe protection and rich text fixtures --- .../external_fixture_specs/apache_poi.py | 12 ++++ .../harness/external_fixture_specs/base.py | 3 +- .../external_fixture_specs/closedxml.py | 10 ++- .../harness/external_fixture_specs/exceljs.py | 12 ++++ .../harness/external_fixture_specs/npoi.py | 12 ++++ .../harness/external_wolfxl_validation.py | 72 +++++++++++++++++++ tests/test_external_fixture_pack.py | 36 ++++++++++ 7 files changed, 154 insertions(+), 3 deletions(-) diff --git a/src/excelbench/harness/external_fixture_specs/apache_poi.py b/src/excelbench/harness/external_fixture_specs/apache_poi.py index 988fa75..376349f 100644 --- a/src/excelbench/harness/external_fixture_specs/apache_poi.py +++ b/src/excelbench/harness/external_fixture_specs/apache_poi.py @@ -86,6 +86,18 @@ def apache_poi_fixture_specs() -> list[ExternalFixtureSpec]: "contains": "PoiReviewTable", "label": "table name survives", }, + { + "kind": "sheet_protection", + "sheet": "POI", + "expected": {"sheet": True, "objects": True, "scenarios": True}, + }, + { + "kind": "rich_text_runs", + "part": "xl/sharedStrings.xml", + "min_runs": 2, + "contains": ["Apache POI", "rich text"], + "label": "rich text runs survive", + }, ), ) ] diff --git a/src/excelbench/harness/external_fixture_specs/base.py b/src/excelbench/harness/external_fixture_specs/base.py index dcafa5e..db104df 100644 --- a/src/excelbench/harness/external_fixture_specs/base.py +++ b/src/excelbench/harness/external_fixture_specs/base.py @@ -22,7 +22,8 @@ class ExternalFixtureSpec: kinds currently include ``cell_value``, ``cell_formula``, ``cell_style``, ``conditional_formatting``, ``comment_text``, ``hyperlink_target``, ``data_validation``, ``merged_range``, - ``table_metadata``, ``relationship_target``, and ``zip_contains``. + ``table_metadata``, ``sheet_protection``, ``workbook_protection``, + ``rich_text_runs``, ``relationship_target``, and ``zip_contains``. notes: Human-readable reason this fixture exists. """ diff --git a/src/excelbench/harness/external_fixture_specs/closedxml.py b/src/excelbench/harness/external_fixture_specs/closedxml.py index 0f5f57f..65a7898 100644 --- a/src/excelbench/harness/external_fixture_specs/closedxml.py +++ b/src/excelbench/harness/external_fixture_specs/closedxml.py @@ -136,9 +136,15 @@ def closedxml_fixture_specs() -> list[ExternalFixtureSpec]: "label": "sheet protection survives", }, { - "kind": "zip_contains", + "kind": "sheet_protection", + "sheet": "Review", + "expected": {"sheet": True, "objects": True}, + }, + { + "kind": "rich_text_runs", "part": "xl/sharedStrings.xml", - "contains": ":r>", + "min_runs": 2, + "contains": ["Priority: ", "management response needed"], "label": "rich text runs survive", }, ), diff --git a/src/excelbench/harness/external_fixture_specs/exceljs.py b/src/excelbench/harness/external_fixture_specs/exceljs.py index 4f95108..d438458 100644 --- a/src/excelbench/harness/external_fixture_specs/exceljs.py +++ b/src/excelbench/harness/external_fixture_specs/exceljs.py @@ -166,6 +166,18 @@ def exceljs_fixture_specs() -> list[ExternalFixtureSpec]: "contains": "ExcelJsReviewTable", "label": "table name survives", }, + { + "kind": "sheet_protection", + "sheet": "ExcelJS", + "expected": {"sheet": True}, + }, + { + "kind": "rich_text_runs", + "part": "xl/sharedStrings.xml", + "min_runs": 2, + "contains": ["ExcelJS ", "rich text"], + "label": "rich text runs survive", + }, ), ) ] diff --git a/src/excelbench/harness/external_fixture_specs/npoi.py b/src/excelbench/harness/external_fixture_specs/npoi.py index 0c6046d..1e30c47 100644 --- a/src/excelbench/harness/external_fixture_specs/npoi.py +++ b/src/excelbench/harness/external_fixture_specs/npoi.py @@ -74,6 +74,18 @@ def npoi_fixture_specs() -> list[ExternalFixtureSpec]: "contains": "sheetProtection", "label": "sheet protection survives", }, + { + "kind": "sheet_protection", + "sheet": "NPOI", + "expected": {"sheet": True, "objects": True, "scenarios": True}, + }, + { + "kind": "rich_text_runs", + "part": "xl/sharedStrings.xml", + "min_runs": 2, + "contains": ["NPOI ", "rich text"], + "label": "rich text runs survive", + }, ), ) ] diff --git a/src/excelbench/harness/external_wolfxl_validation.py b/src/excelbench/harness/external_wolfxl_validation.py index 1d398e3..dbd4021 100644 --- a/src/excelbench/harness/external_wolfxl_validation.py +++ b/src/excelbench/harness/external_wolfxl_validation.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import re import shutil import xml.etree.ElementTree as ET from dataclasses import dataclass @@ -224,6 +225,12 @@ def _run_readback_probes( _check_merged_range(workbook, probe) elif kind == "table_metadata": _check_table_metadata(workbook, probe) + elif kind == "sheet_protection": + _check_sheet_protection(workbook, probe) + elif kind == "workbook_protection": + _check_workbook_protection(workbook_path, probe) + elif kind == "rich_text_runs": + _check_rich_text_runs(workbook_path, probe) elif kind == "relationship_target": _check_relationship_target(workbook_path, probe) elif kind == "zip_contains": @@ -352,6 +359,56 @@ def _check_table_metadata(workbook: Any, probe: JSONDict) -> None: raise AssertionError(f"expected table style {expected_style!r}, got {actual_style!r}") +def _check_sheet_protection(workbook: Any, probe: JSONDict) -> None: + sheet = workbook[str(probe["sheet"])] + expected = cast(JSONDict, probe.get("expected", {"sheet": True})) + for key, expected_value in expected.items(): + actual = getattr(sheet.protection, str(key)) + if actual != expected_value: + raise AssertionError( + f"expected sheet protection {key}={expected_value!r}, got {actual!r}" + ) + + +def _check_workbook_protection(workbook_path: Path, probe: JSONDict) -> None: + expected = cast(JSONDict, probe.get("expected", {})) + with ZipFile(workbook_path) as workbook_zip: + root = ET.fromstring(workbook_zip.read("xl/workbook.xml")) + protection = _find_first_child(root, "workbookProtection") + if protection is None: + raise AssertionError("expected xl/workbook.xml to contain workbookProtection") + for key, expected_value in expected.items(): + actual = protection.attrib.get(str(key)) + if isinstance(expected_value, bool): + actual_value: Any = _xml_bool(actual) + else: + actual_value = actual + if actual_value != expected_value: + raise AssertionError( + f"expected workbook protection {key}={expected_value!r}, got {actual_value!r}" + ) + + +def _check_rich_text_runs(workbook_path: Path, probe: JSONDict) -> None: + part = str(probe.get("part", "xl/sharedStrings.xml")) + min_runs = int(probe.get("min_runs", 1)) + expected_fragments = [str(item) for item in probe.get("contains", [])] + with ZipFile(workbook_path) as workbook_zip: + text = workbook_zip.read(part).decode("utf-8", errors="ignore") + try: + root = ET.fromstring(text) + run_count = sum(1 for element in root.iter() if _local_name(element.tag) == "r") + visible_text = "".join(root.itertext()) + except ET.ParseError: + run_count = len(re.findall(r"<(?:[A-Za-z0-9_]+:)?r[\s>]", text)) + visible_text = text + if run_count < min_runs: + raise AssertionError(f"expected at least {min_runs} rich-text runs, got {run_count}") + for fragment in expected_fragments: + if fragment not in visible_text: + raise AssertionError(f"expected rich-text content containing {fragment!r}") + + def _check_relationship_target(workbook_path: Path, probe: JSONDict) -> None: part = str(probe["part"]) expected_target = str(probe["target"]) @@ -381,6 +438,21 @@ def _check_zip_contains(workbook_path: Path, probe: JSONDict) -> None: raise AssertionError(f"expected {part} to contain {expected!r}") +def _find_first_child(root: ET.Element, local_name: str) -> ET.Element | None: + for child in root: + if _local_name(child.tag) == local_name: + return child + return None + + +def _local_name(tag: str) -> str: + return tag.rsplit("}", 1)[-1] + + +def _xml_bool(value: str | None) -> bool: + return str(value).lower() in {"1", "true"} + + def _display_path(path: Path, root: Path) -> str: try: return str(path.relative_to(root)) diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index 2cf215b..a8042e2 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -19,6 +19,7 @@ npoi_fixture_specs, ) from excelbench.harness.external_wolfxl_validation import ( + _run_readback_probes, validate_wolfxl_external_fixture_pack, ) @@ -53,6 +54,8 @@ def test_closedxml_fixture_specs_are_stable() -> None: assert "xl/comments1.xml" in specs[1].expected_parts assert "xl/drawings/vmldrawing.vml" in specs[1].expected_parts assert any(probe["kind"] == "comment_text" for probe in specs[1].readback_probes) + assert any(probe["kind"] == "sheet_protection" for probe in specs[1].readback_probes) + assert any(probe["kind"] == "rich_text_runs" for probe in specs[1].readback_probes) assert [spec.fixture_id for spec in external_fixture_specs()] == [ "excelize_sales_pivot_slicer_chart", "excelize_chart_points_formula_cf", @@ -75,6 +78,8 @@ def test_apache_poi_fixture_specs_are_stable() -> None: assert any(probe["kind"] == "hyperlink_target" for probe in specs[0].readback_probes) assert any(probe["kind"] == "merged_range" for probe in specs[0].readback_probes) assert any(probe["kind"] == "relationship_target" for probe in specs[0].readback_probes) + assert any(probe["kind"] == "sheet_protection" for probe in specs[0].readback_probes) + assert any(probe["kind"] == "rich_text_runs" for probe in specs[0].readback_probes) def test_exceljs_fixture_specs_are_stable() -> None: @@ -86,6 +91,8 @@ def test_exceljs_fixture_specs_are_stable() -> None: assert "xl/media/image1.png" in specs[0].expected_parts assert any(probe["kind"] == "cell_style" for probe in specs[0].readback_probes) assert any(probe["kind"] == "cell_formula" for probe in specs[0].readback_probes) + assert any(probe["kind"] == "sheet_protection" for probe in specs[0].readback_probes) + assert any(probe["kind"] == "rich_text_runs" for probe in specs[0].readback_probes) def test_npoi_fixture_specs_are_stable() -> None: @@ -96,6 +103,35 @@ def test_npoi_fixture_specs_are_stable() -> None: assert "xl/comments1.xml" in specs[0].expected_parts assert "xl/drawings/vmlDrawing1.vml" in specs[0].expected_parts assert any(probe["kind"] == "comment_text" for probe in specs[0].readback_probes) + assert any(probe["kind"] == "sheet_protection" for probe in specs[0].readback_probes) + assert any(probe["kind"] == "rich_text_runs" for probe in specs[0].readback_probes) + + +def test_workbook_protection_probe_reads_workbook_xml(tmp_path: Path) -> None: + openpyxl = pytest.importorskip("openpyxl") + + workbook_path = tmp_path / "protected-workbook.xlsx" + workbook = openpyxl.Workbook() + workbook.security.lockStructure = True + workbook.save(workbook_path) + workbook.close() + + roundtrip = openpyxl.load_workbook(workbook_path) + try: + failures = _run_readback_probes( + roundtrip, + workbook_path, + ( + { + "kind": "workbook_protection", + "expected": {"lockStructure": True}, + }, + ), + ) + finally: + roundtrip.close() + + assert failures == () @pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") From 5f98de90dfe0304e7854eb4537a0a36072a02807 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:00:33 -0700 Subject: [PATCH 19/25] test: add external workbook protection probe --- src/excelbench/harness/external_fixture_specs/apache_poi.py | 6 ++++++ tests/test_external_fixture_pack.py | 5 +++++ tools/external-oracles/apache-poi/src/PoiOracle.java | 4 +++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/excelbench/harness/external_fixture_specs/apache_poi.py b/src/excelbench/harness/external_fixture_specs/apache_poi.py index 376349f..4d7f721 100644 --- a/src/excelbench/harness/external_fixture_specs/apache_poi.py +++ b/src/excelbench/harness/external_fixture_specs/apache_poi.py @@ -14,6 +14,7 @@ def apache_poi_fixture_specs() -> list[ExternalFixtureSpec]: filename="apache-poi-table-validation-image-comment.xlsx", payload={}, expected_parts=( + "xl/workbook.xml", "xl/sharedStrings.xml", "xl/comments1.xml", "xl/drawings/vmlDrawing0.vml", @@ -98,6 +99,11 @@ def apache_poi_fixture_specs() -> list[ExternalFixtureSpec]: "contains": ["Apache POI", "rich text"], "label": "rich text runs survive", }, + { + "kind": "workbook_protection", + "expected": {"lockStructure": True}, + "label": "workbook structure protection survives", + }, ), ) ] diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index a8042e2..67c2e53 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -72,6 +72,7 @@ def test_apache_poi_fixture_specs_are_stable() -> None: assert [spec.fixture_id for spec in specs] == ["apache_poi_table_validation_image_comment"] assert all(spec.tool == "apache-poi" for spec in specs) + assert "xl/workbook.xml" in specs[0].expected_parts assert "xl/tables/table1.xml" in specs[0].expected_parts assert "xl/drawings/vmlDrawing0.vml" in specs[0].expected_parts assert any(probe["kind"] == "data_validation" for probe in specs[0].readback_probes) @@ -80,6 +81,7 @@ def test_apache_poi_fixture_specs_are_stable() -> None: assert any(probe["kind"] == "relationship_target" for probe in specs[0].readback_probes) assert any(probe["kind"] == "sheet_protection" for probe in specs[0].readback_probes) assert any(probe["kind"] == "rich_text_runs" for probe in specs[0].readback_probes) + assert any(probe["kind"] == "workbook_protection" for probe in specs[0].readback_probes) def test_exceljs_fixture_specs_are_stable() -> None: @@ -222,8 +224,11 @@ def test_generate_external_fixture_pack_without_validators(tmp_path: Path) -> No assert apache_poi_fixture.exists() with ZipFile(apache_poi_fixture) as workbook: apache_poi_names = set(workbook.namelist()) + apache_poi_workbook_xml = workbook.read("xl/workbook.xml").decode() apache_poi_sheet_xml = workbook.read("xl/worksheets/sheet1.xml").decode() apache_poi_shared_strings_xml = workbook.read("xl/sharedStrings.xml").decode() + assert "workbookProtection" in apache_poi_workbook_xml + assert "lockStructure" in apache_poi_workbook_xml assert "xl/tables/table1.xml" in apache_poi_names assert "xl/comments1.xml" in apache_poi_names assert "xl/drawings/vmlDrawing0.vml" in apache_poi_names diff --git a/tools/external-oracles/apache-poi/src/PoiOracle.java b/tools/external-oracles/apache-poi/src/PoiOracle.java index 84dea3f..680af35 100644 --- a/tools/external-oracles/apache-poi/src/PoiOracle.java +++ b/tools/external-oracles/apache-poi/src/PoiOracle.java @@ -60,6 +60,7 @@ private static void writeFixture(String fixtureId, String outputPath) throws Exc try (XSSFWorkbook workbook = new XSSFWorkbook()) { workbook.getProperties().getCoreProperties().setCreator("ExcelBench Apache POI Oracle"); + workbook.lockStructure(); XSSFSheet sheet = workbook.createSheet("POI"); sheet.createFreezePane(1, 1); sheet.setColumnWidth(0, 18 * 256); @@ -111,7 +112,8 @@ private static void writeFixture(String fixtureId, String outputPath) throws Exc + "\"data_validations\":1," + "\"merged_ranges\":1," + "\"images\":1," - + "\"protected_sheets\":1" + + "\"protected_sheets\":1," + + "\"protected_workbook\":1" + "}}"); } From b331f4dc7afa680aba21955e2b09ada8602f2885 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:43:15 -0700 Subject: [PATCH 20/25] feat: add workbook semantic context lanes --- architecture.md | 28 +- decisions.md | 43 ++ src/excelbench/cli.py | 155 +++++++ src/excelbench/harness/artifact_context.py | 255 ++++++++++++ src/excelbench/harness/compat_cases.py | 437 ++++++++++++++++++++ src/excelbench/harness/roundtrip_runner.py | 232 +++++++++++ src/excelbench/harness/semantic_diff.py | 185 +++++++++ src/excelbench/harness/workbook_snapshot.py | 319 ++++++++++++++ src/excelbench/models.py | 2 + src/excelbench/results/failure_explainer.py | 226 ++++++++++ src/excelbench/results/renderer.py | 33 +- tests/test_context_lanes.py | 101 +++++ tests/test_failure_explainer.py | 47 +++ tests/test_semantic_diff.py | 67 +++ 14 files changed, 2126 insertions(+), 4 deletions(-) create mode 100644 src/excelbench/harness/artifact_context.py create mode 100644 src/excelbench/harness/compat_cases.py create mode 100644 src/excelbench/harness/roundtrip_runner.py create mode 100644 src/excelbench/harness/semantic_diff.py create mode 100644 src/excelbench/harness/workbook_snapshot.py create mode 100644 src/excelbench/results/failure_explainer.py create mode 100644 tests/test_context_lanes.py create mode 100644 tests/test_failure_explainer.py create mode 100644 tests/test_semantic_diff.py diff --git a/architecture.md b/architecture.md index 1934d87..82b9d95 100644 --- a/architecture.md +++ b/architecture.md @@ -26,7 +26,7 @@ A key design principle is reproducibility: At a high level, ExcelBench is split into six layers: 1. Fixtures + generator (Excel as ground truth) -2. Fidelity harness (adapters + scoring + diagnostics) +2. Fidelity harness (adapters + scoring + diagnostics + semantic workbook diff) 3. External oracle helpers (optional subprocess validators/generators) 4. Performance harness (throughput workloads + best-effort memory) 5. Rendering + publishing (markdown/csv + HTML dashboard + plots) @@ -71,6 +71,11 @@ Most-touched top-level directories: - `models.py`: dataclasses/contracts (CellValue, CellFormat, BorderInfo, ...) - `generator/`: fixture generation (xlwings + Excel) - `harness/`: fidelity benchmark runner, adapters, and optional external oracle helpers + - `harness/workbook_snapshot.py`: normalized workbook/package snapshots used by semantic diffs + - `harness/semantic_diff.py`: structured workbook diff artifacts for diagnostics/context lanes + - `harness/roundtrip_runner.py`: open/save idempotence context lane + - `harness/compat_cases.py`: openpyxl-style compatibility snippet lane + - `harness/artifact_context.py`: chart and macro package evidence lanes - `harness/external_fixture_specs/`: tool-specific external oracle fixture definitions imported by the fixture-pack generator - `perf/`: performance runner + renderer @@ -191,6 +196,27 @@ suite. The first local pack is generated by `scripts/generate_external_oracle_fixtures.py` into `results_dev_external/` and validated against WolfXL with `scripts/validate_external_oracle_fixtures_with_wolfxl.py`. +### 3a) Semantic diff and context lanes + +``` +workbook(s) + -> workbook_snapshot.py normalizes workbook semantics and selected OOXML parts + -> semantic_diff.py compares category-level snapshots + -> diff-workbooks / roundtrip-context / compatibility-context / chart and macro context commands + -> JSON + markdown artifacts with explicit skips for unsupported adapters +``` + +Commands: + +- `uv run excelbench diff-workbooks --left a.xlsx --right b.xlsx --output results-workbook-diff` +- `uv run excelbench roundtrip-context --tests fixtures/excel --output results-roundtrip` +- `uv run excelbench compatibility-context --output results-compatibility` +- `uv run excelbench cross-language-chart-context --output results-cross-language-charts` +- `uv run excelbench macro-context --tests fixtures/excel_xlsm --output results-macros` + +These are context/evidence lanes. They do not change the core fidelity scoring model unless a +future decision explicitly promotes their outputs into scored benchmark features. + ### 4) Performance benchmark (speed/memory) ``` diff --git a/decisions.md b/decisions.md index 5496039..c74dc0d 100644 --- a/decisions.md +++ b/decisions.md @@ -40,6 +40,49 @@ Skip logging for routine bug fixes, refactors, or incremental test additions. ## Decisions +### DEC-022 — Semantic diff and context lanes stay additive (2026-04-29) + +**Context**: ExcelBench needed better evidence for workbook drift, roundtrip +idempotence, openpyxl-style compatibility, charts, macros, and future +cross-language promotion without overloading the existing scored fidelity +matrix. The existing runner diagnostics could say a test failed, but not +produce a reusable workbook-level explanation. + +**Decision**: + +- Add workbook semantic snapshot/diff tooling as public infrastructure via + `excelbench diff-workbooks`. +- Reuse that diff infrastructure from additive context lanes: + `roundtrip-context`, `compatibility-context`, + `cross-language-chart-context`, and `macro-context`. +- Keep these lanes separate from normal fidelity scores. Unsupported adapters + produce explicit skip rows rather than silent passes or broad score changes. +- Enrich rendered diagnostics with structured failure explanations and write + `WHY_FAILED.md` when a rendered benchmark directory contains failures. + +**Alternatives considered**: + +1. **Fold roundtrip and compatibility into the main scored matrix immediately** + - rejected because the adapter API does not expose a uniform + read-modify-save or openpyxl-compatible snippet surface. +2. **Keep semantic diff internal-only** - rejected because a public CLI gives + direct reproducibility and a simple debugging tool for future lanes. +3. **Use only package-part checks for charts/macros** - rejected for charts; + chart lanes should also inspect drawing relationships and chart references. + Macro v1 remains preserve-only because macro execution/semantic validation + is a separate trust boundary. + +**Consequences**: + +- New context outputs are comparable as evidence artifacts but not as headline + fidelity scores. +- Some broad-adapter requests legitimately become skip rows until adapters + expose compatible APIs. +- Future promotion of ClosedXML, ExcelJS, and NPOI into scored/context adapters + can reuse the same diff and explanation surfaces. + +**Commit(s)**: this commit. + ### DEC-021 — Optional external oracle subprocess contract (2026-04-28) **Context**: WolfXL's pre-release parity pass is now green against the existing diff --git a/src/excelbench/cli.py b/src/excelbench/cli.py index 8872b81..92172c9 100644 --- a/src/excelbench/cli.py +++ b/src/excelbench/cli.py @@ -1055,6 +1055,159 @@ def benchmark_profiles( console.print(f" - {output_dir}/README.md") +@app.command("diff-workbooks") +def diff_workbooks_cli( + left: Path = typer.Option(..., "--left", help="Left workbook path."), + right: Path = typer.Option(..., "--right", help="Right workbook path."), + output_dir: Path = typer.Option( + Path("results-workbook-diff"), + "--output", + "-o", + help="Directory to save semantic diff artifacts.", + ), +) -> None: + """Diff two workbooks by normalized workbook semantics and package parts.""" + from excelbench.harness.semantic_diff import write_diff_artifacts + + try: + diff = write_diff_artifacts(left, right, output_dir) + except Exception as e: + console.print(f"[red]Error: {e}[/red]") + raise typer.Exit(1) + + status = "no deltas" if diff.passed else f"{len(diff.deltas)} deltas" + console.print(f"[green]✓ Workbook diff written to {output_dir} ({status})[/green]") + console.print(f" - {output_dir}/summary.json") + console.print(f" - {output_dir}/summary.md") + console.print(f" - {output_dir}/categories/") + + +@app.command("roundtrip-context") +def roundtrip_context( + test_dir: Path = typer.Option( + PERF_XLSX_PROFILE_DEFAULT_TEST_DIR, + "--tests", + "-t", + help="Directory containing .xlsx tests and manifest.json.", + ), + output_dir: Path = typer.Option( + Path("results-roundtrip"), + "--output", + "-o", + help="Directory to save roundtrip context artifacts.", + ), + adapters: list[str] | None = typer.Option( + None, + "--adapter", + "-a", + help="Run only specified adapter(s) by name. Repeatable.", + ), + cycles: int = typer.Option(2, "--cycles", min=1, help="Number of open/save cycles."), +) -> None: + """Measure semantic drift after repeated workbook open/save cycles.""" + from excelbench.harness.adapters import get_all_adapters + from excelbench.harness.roundtrip_runner import run_roundtrip_context + + available = {adapter.name: adapter for adapter in get_all_adapters()} + selected_names = adapters or sorted(available) + selected = [available[name] for name in selected_names if name in available] + missing = [name for name in selected_names if name not in available] + if missing: + console.print(f"[yellow]Skipping unavailable adapters: {', '.join(missing)}[/yellow]") + if not selected: + console.print("[red]Error: No selected adapters are available.[/red]") + raise typer.Exit(1) + + results = run_roundtrip_context(test_dir, output_dir, adapters=selected, cycles=cycles) + failures = [result for result in results if not result.passed and not result.skipped] + console.print(f"[green]✓ Roundtrip context written to {output_dir}[/green]") + console.print(f" - {output_dir}/roundtrip.json") + console.print(f" - {output_dir}/ROUNDTRIP.md") + console.print(f" - failures: {len(failures)}") + + +@app.command("compatibility-context") +def compatibility_context( + output_dir: Path = typer.Option( + Path("results-compatibility"), + "--output", + "-o", + help="Directory to save compatibility context artifacts.", + ), + adapters: list[str] | None = typer.Option( + None, + "--adapter", + "-a", + help="Run only specified adapter(s) by name. Repeatable.", + ), +) -> None: + """Run openpyxl-style snippets against compatible adapters.""" + from excelbench.harness.compat_cases import run_compatibility_context + + payload = run_compatibility_context(output_dir, adapter_names=adapters) + failures = [row for row in payload["results"] if not row["passed"] and not row["skipped"]] + console.print(f"[green]✓ Compatibility context written to {output_dir}[/green]") + console.print(f" - {output_dir}/compatibility.json") + console.print(f" - {output_dir}/COMPATIBILITY.md") + console.print(f" - failures: {len(failures)}") + + +@app.command("cross-language-chart-context") +def cross_language_chart_context( + fixture_dir: Path = typer.Option( + Path("fixtures/excel"), + "--fixtures", + help="Directory containing chart-bearing .xlsx fixtures.", + ), + output_dir: Path = typer.Option( + Path("results-cross-language-charts"), + "--output", + "-o", + help="Directory to save chart artifact context.", + ), +) -> None: + """Validate chart-specific artifact evidence structurally.""" + from excelbench.harness.artifact_context import run_chart_context + + payload = run_chart_context(fixture_dir, output_dir) + failures = [row for row in payload["results"] if not row["passed"] and not row["skipped"]] + console.print(f"[green]✓ Chart context written to {output_dir}[/green]") + console.print(f" - {output_dir}/results.json") + console.print(f" - {output_dir}/README.md") + console.print(f" - failures: {len(failures)}") + + +@app.command("macro-context") +def macro_context( + test_dir: Path = typer.Option( + Path("fixtures/excel_xlsm"), + "--tests", + "-t", + help="Directory containing .xlsm fixtures.", + ), + output_dir: Path = typer.Option( + Path("results-macros"), + "--output", + "-o", + help="Directory to save macro artifact context.", + ), + preserve_with_wolfxl: bool = typer.Option( + True, + "--wolfxl-preserve/--no-wolfxl-preserve", + help="Attempt WolfXL modify-save preservation when wolfxl is installed.", + ), +) -> None: + """Validate macro package preservation evidence.""" + from excelbench.harness.artifact_context import run_macro_context + + payload = run_macro_context(test_dir, output_dir, preserve_with_wolfxl=preserve_with_wolfxl) + failures = [row for row in payload["results"] if not row["passed"] and not row["skipped"]] + console.print(f"[green]✓ Macro context written to {output_dir}[/green]") + console.print(f" - {output_dir}/results.json") + console.print(f" - {output_dir}/README.md") + console.print(f" - failures: {len(failures)}") + + def _results_from_json(data: dict[str, Any]) -> "BenchmarkResults": from datetime import datetime @@ -1121,6 +1274,8 @@ def _results_from_json(data: dict[str, Any]) -> "BenchmarkResults": ), adapter_message=d["adapter_message"], probable_cause=d.get("probable_cause"), + root_cause_code=d.get("root_cause_code"), + suggested_next_step=d.get("suggested_next_step"), ) for d in op_data.get("diagnostics", []) ], diff --git a/src/excelbench/harness/artifact_context.py b/src/excelbench/harness/artifact_context.py new file mode 100644 index 0000000..a677aa0 --- /dev/null +++ b/src/excelbench/harness/artifact_context.py @@ -0,0 +1,255 @@ +"""Chart and macro artifact context lanes.""" + +from __future__ import annotations + +import json +import platform +import shutil +from dataclasses import dataclass +from datetime import UTC, datetime +from pathlib import Path +from typing import Any +from zipfile import ZipFile + +JSONDict = dict[str, Any] + + +@dataclass(frozen=True) +class ArtifactValidation: + fixture: str + passed: bool + skipped: bool + checks: dict[str, bool] + details: dict[str, Any] + error: str | None = None + + def to_json_dict(self) -> JSONDict: + return { + "fixture": self.fixture, + "passed": self.passed, + "skipped": self.skipped, + "checks": self.checks, + "details": self.details, + "error": self.error, + } + + +def run_chart_context(fixture_dir: Path, output_dir: Path) -> JSONDict: + """Validate chart-bearing OOXML artifacts structurally.""" + fixtures = sorted(Path(fixture_dir).rglob("*.xlsx")) + validations = [_validate_chart_fixture(path) for path in fixtures] + payload = _artifact_payload("charts", fixture_dir, validations) + _write_artifact_outputs(output_dir, "charts", payload) + return payload + + +def run_macro_context( + fixture_dir: Path, + output_dir: Path, + *, + preserve_with_wolfxl: bool = True, +) -> JSONDict: + """Validate macro-bearing OOXML artifacts structurally.""" + fixtures = sorted(Path(fixture_dir).rglob("*.xlsm")) + validations = [ + _validate_macro_fixture(path, output_dir, preserve_with_wolfxl) for path in fixtures + ] + if not fixtures: + validations = [ + ArtifactValidation( + fixture=str(Path(fixture_dir)), + passed=False, + skipped=True, + checks={}, + details={}, + error="No .xlsm fixtures found.", + ) + ] + payload = _artifact_payload("macros", fixture_dir, validations) + _write_artifact_outputs(output_dir, "macros", payload) + return payload + + +def _validate_chart_fixture(path: Path) -> ArtifactValidation: + try: + with ZipFile(path) as workbook_zip: + names = set(workbook_zip.namelist()) + chart_parts = sorted(name for name in names if name.startswith("xl/charts/")) + drawing_parts = sorted(name for name in names if name.startswith("xl/drawings/drawing")) + drawing_rels = sorted(name for name in names if name.startswith("xl/drawings/_rels/")) + worksheet_rels = sorted( + name for name in names if name.startswith("xl/worksheets/_rels/") + ) + chart_xml_mentions = 0 + range_mentions = 0 + for part in chart_parts: + xml = workbook_zip.read(part).decode("utf-8", errors="ignore") + chart_xml_mentions += int("" in xml or "" in xml)) + checks = { + "chart_parts_present": bool(chart_parts), + "drawing_parts_present": bool(drawing_parts), + "drawing_relationships_present": bool(drawing_rels), + "worksheet_relationships_present": bool(worksheet_rels), + "chart_xml_present": chart_xml_mentions > 0, + "chart_references_present": range_mentions > 0, + } + return ArtifactValidation( + fixture=str(path), + passed=all(checks.values()), + skipped=False, + checks=checks, + details={ + "chart_parts": chart_parts, + "drawing_parts": drawing_parts, + "drawing_rels": drawing_rels, + "worksheet_rels": worksheet_rels, + }, + ) + except Exception as exc: + return ArtifactValidation( + fixture=str(path), + passed=False, + skipped=False, + checks={}, + details={}, + error=f"{type(exc).__name__}: {exc}", + ) + + +def _validate_macro_fixture( + path: Path, + output_dir: Path, + preserve_with_wolfxl: bool, +) -> ArtifactValidation: + try: + with ZipFile(path) as workbook_zip: + before = set(workbook_zip.namelist()) + content_types = workbook_zip.read("[Content_Types].xml").decode( + "utf-8", errors="ignore" + ) + rels_text = _read_optional(workbook_zip, "xl/_rels/workbook.xml.rels") + checks = { + "vba_project_present": "xl/vbaProject.bin" in before, + "content_type_present": "vbaProject" in content_types, + "relationship_present": "vbaProject" in rels_text, + } + details: JSONDict = { + "macro_parts": sorted(name for name in before if "vba" in name.lower()) + } + if preserve_with_wolfxl: + preserve_checks, preserve_details = _wolfxl_macro_preservation(path, output_dir) + checks.update(preserve_checks) + details.update(preserve_details) + return ArtifactValidation( + fixture=str(path), + passed=all(checks.values()), + skipped=False, + checks=checks, + details=details, + ) + except Exception as exc: + return ArtifactValidation( + fixture=str(path), + passed=False, + skipped=False, + checks={}, + details={}, + error=f"{type(exc).__name__}: {exc}", + ) + + +def _wolfxl_macro_preservation(path: Path, output_dir: Path) -> tuple[dict[str, bool], JSONDict]: + try: + import wolfxl + + out_dir = Path(output_dir) / "wolfxl-modified" + out_dir.mkdir(parents=True, exist_ok=True) + out_path = out_dir / path.name + shutil.copy2(path, out_path) + workbook = wolfxl.load_workbook(out_path, modify=True) + try: + workbook.save(out_path) + finally: + close = getattr(workbook, "close", None) + if close is not None: + close() + with ZipFile(out_path) as workbook_zip: + after = set(workbook_zip.namelist()) + return ( + { + "wolfxl_modify_save_completed": True, + "wolfxl_preserved_vba_project": "xl/vbaProject.bin" in after, + }, + {"wolfxl_modified_workbook": str(out_path)}, + ) + except Exception as exc: + return ( + { + "wolfxl_modify_save_completed": False, + "wolfxl_preserved_vba_project": False, + }, + {"wolfxl_error": f"{type(exc).__name__}: {exc}"}, + ) + + +def _artifact_payload( + kind: str, + fixture_dir: Path, + validations: list[ArtifactValidation], +) -> JSONDict: + return { + "kind": kind, + "generated_at": datetime.now(UTC).isoformat(), + "platform": f"{platform.system()}-{platform.machine()}", + "fixture_dir": str(fixture_dir), + "results": [validation.to_json_dict() for validation in validations], + "passed": all(item.passed or item.skipped for item in validations), + } + + +def _write_artifact_outputs(output_dir: Path, kind: str, payload: JSONDict) -> None: + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + (output_dir / "results.json").write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") + (output_dir / "README.md").write_text(_render_artifact_readme(payload)) + (output_dir / "CONTEXT.md").write_text(_render_artifact_context(kind)) + + +def _render_artifact_readme(payload: JSONDict) -> str: + title = "Chart" if payload["kind"] == "charts" else "Macro" + lines = [ + f"# ExcelBench {title} Artifact Context", + "", + f"- Generated: `{payload['generated_at']}`", + f"- Fixture dir: `{payload['fixture_dir']}`", + f"- Passed: `{payload['passed']}`", + "", + "| Fixture | Status | Checks |", + "|---------|--------|--------|", + ] + for row in payload["results"]: + status = "skipped" if row["skipped"] else ("passed" if row["passed"] else "failed") + checks = ", ".join(f"{k}:{v}" for k, v in row["checks"].items()) or row.get("error") or "-" + lines.append(f"| {Path(row['fixture']).name} | {status} | {checks} |") + lines.append("") + return "\n".join(lines) + + +def _render_artifact_context(kind: str) -> str: + return "\n".join( + [ + f"# {kind.title()} Context", + "", + "This is an artifact lane, not a normal scored benchmark lane.", + "It records package-level evidence for advanced workbook capabilities.", + "", + ] + ) + + +def _read_optional(workbook_zip: ZipFile, part: str) -> str: + try: + return workbook_zip.read(part).decode("utf-8", errors="ignore") + except KeyError: + return "" diff --git a/src/excelbench/harness/compat_cases.py b/src/excelbench/harness/compat_cases.py new file mode 100644 index 0000000..f645ca5 --- /dev/null +++ b/src/excelbench/harness/compat_cases.py @@ -0,0 +1,437 @@ +"""Openpyxl-style compatibility context cases.""" + +from __future__ import annotations + +import json +import platform +from collections.abc import Callable +from dataclasses import dataclass +from datetime import UTC, datetime +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Any + +from excelbench.harness.semantic_diff import write_diff_artifacts + +JSONDict = dict[str, Any] +CaseFunc = Callable[[Any, Path], Any] + + +@dataclass(frozen=True) +class CompatibilityCase: + case_id: str + api_surface: str + description: str + run: CaseFunc + + +@dataclass(frozen=True) +class CompatibilityResult: + adapter: str + case_id: str + api_surface: str + passed: bool + skipped: bool + return_match: bool + snapshot_match: bool + delta_count: int + diff_dir: str | None = None + error: str | None = None + + def to_json_dict(self) -> JSONDict: + return { + "adapter": self.adapter, + "case_id": self.case_id, + "api_surface": self.api_surface, + "passed": self.passed, + "skipped": self.skipped, + "return_match": self.return_match, + "snapshot_match": self.snapshot_match, + "delta_count": self.delta_count, + "diff_dir": self.diff_dir, + "error": self.error, + } + + +def compatibility_cases() -> list[CompatibilityCase]: + """Return the initial broad openpyxl-style compatibility case set.""" + return [ + CompatibilityCase("create_string", "cells", "write a string", _case_create_string), + CompatibilityCase("create_number", "cells", "write a number", _case_create_number), + CompatibilityCase("create_formula", "formulas", "write a formula", _case_create_formula), + CompatibilityCase("font_bold", "styles", "set bold font", _case_font_bold), + CompatibilityCase("font_color", "styles", "set font color", _case_font_color), + CompatibilityCase("fill_color", "styles", "set fill color", _case_fill_color), + CompatibilityCase("number_format", "styles", "set number format", _case_number_format), + CompatibilityCase("alignment_wrap", "styles", "set wrap alignment", _case_alignment_wrap), + CompatibilityCase("comment", "comments", "add a comment", _case_comment), + CompatibilityCase("hyperlink", "hyperlinks", "add a hyperlink", _case_hyperlink), + CompatibilityCase( + "validation", + "data_validations", + "add list validation", + _case_validation, + ), + CompatibilityCase("merge", "merges", "merge cells", _case_merge), + CompatibilityCase("unmerge", "merges", "merge then unmerge cells", _case_unmerge), + CompatibilityCase("freeze_panes", "freeze_panes", "freeze panes", _case_freeze), + CompatibilityCase("named_range", "named_ranges", "add defined name", _case_named_range), + CompatibilityCase("table", "tables", "add table", _case_table), + CompatibilityCase("sheet_create", "sheets", "create a second sheet", _case_sheet_create), + CompatibilityCase("sheet_rename", "sheets", "rename a sheet", _case_sheet_rename), + CompatibilityCase("row_height", "dimensions", "set row height", _case_row_height), + CompatibilityCase("column_width", "dimensions", "set column width", _case_column_width), + ] + + +def run_compatibility_context( + output_dir: Path, + *, + adapter_names: list[str] | None = None, +) -> JSONDict: + """Run compatibility snippets against openpyxl and selected compatible adapters.""" + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + adapters = _resolve_compatible_modules(adapter_names) + cases = compatibility_cases() + results: list[CompatibilityResult] = [] + + with TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + for case in cases: + reference_path = tmp_path / f"{case.case_id}.openpyxl.xlsx" + reference_return = case.run(_openpyxl_module(), reference_path) + for adapter_name, module in adapters.items(): + if adapter_name == "openpyxl": + continue + target_path = tmp_path / f"{case.case_id}.{adapter_name}.xlsx" + try: + target_return = case.run(module, target_path) + diff_dir = output_dir / "diffs" / adapter_name / case.case_id + diff = write_diff_artifacts(reference_path, target_path, diff_dir) + return_match = _stable_json(reference_return) == _stable_json(target_return) + results.append( + CompatibilityResult( + adapter=adapter_name, + case_id=case.case_id, + api_surface=case.api_surface, + passed=return_match and diff.passed, + skipped=False, + return_match=return_match, + snapshot_match=diff.passed, + delta_count=len(diff.deltas), + diff_dir=str(diff_dir), + ) + ) + except Exception as exc: + results.append( + CompatibilityResult( + adapter=adapter_name, + case_id=case.case_id, + api_surface=case.api_surface, + passed=False, + skipped=False, + return_match=False, + snapshot_match=False, + delta_count=0, + error=f"{type(exc).__name__}: {exc}", + ) + ) + + requested = adapter_names or sorted(adapters) + skipped = [ + CompatibilityResult( + adapter=name, + case_id="all", + api_surface="adapter", + passed=False, + skipped=True, + return_match=False, + snapshot_match=False, + delta_count=0, + error="Adapter is not openpyxl-compatible for snippet execution.", + ) + for name in requested + if name not in adapters and name != "openpyxl" + ] + all_results = [*results, *skipped] + payload: JSONDict = { + "generated_at": datetime.now(UTC).isoformat(), + "platform": f"{platform.system()}-{platform.machine()}", + "cases": [ + { + "case_id": case.case_id, + "api_surface": case.api_surface, + "description": case.description, + } + for case in cases + ], + "results": [result.to_json_dict() for result in all_results], + "passed": all(result.passed or result.skipped for result in all_results), + } + (output_dir / "compatibility.json").write_text( + json.dumps(payload, indent=2, sort_keys=True) + "\n" + ) + (output_dir / "COMPATIBILITY.md").write_text(_render_compatibility_markdown(payload)) + (output_dir / "CONTEXT.md").write_text(_render_compatibility_context(requested)) + return payload + + +def _resolve_compatible_modules(adapter_names: list[str] | None) -> dict[str, Any]: + names = adapter_names or ["openpyxl", "wolfxl"] + modules: dict[str, Any] = {"openpyxl": _openpyxl_module()} + for name in names: + if name == "openpyxl": + continue + if name == "wolfxl": + try: + import wolfxl + except Exception: + continue + modules["wolfxl"] = wolfxl + return modules + + +def _openpyxl_module() -> Any: + import openpyxl + + return openpyxl + + +def _new_workbook(module: Any) -> Any: + workbook = module.Workbook() + sheet = workbook.active + sheet.title = "Sheet1" + return workbook + + +def _save(workbook: Any, path: Path) -> None: + workbook.save(path) + close = getattr(workbook, "close", None) + if close is not None: + close() + + +def _case_create_string(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + wb["Sheet1"]["A1"] = "Revenue" + _save(wb, path) + return {"A1": "Revenue"} + + +def _case_create_number(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + wb["Sheet1"]["A1"] = 123 + _save(wb, path) + return {"A1": 123} + + +def _case_create_formula(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + ws = wb["Sheet1"] + ws["A1"] = 1 + ws["A2"] = 2 + ws["A3"] = "=SUM(A1:A2)" + _save(wb, path) + return {"A3": "=SUM(A1:A2)"} + + +def _case_font_bold(module: Any, path: Path) -> JSONDict: + from openpyxl.styles import Font + + wb = _new_workbook(module) + wb["Sheet1"]["A1"] = "Bold" + wb["Sheet1"]["A1"].font = Font(bold=True) + _save(wb, path) + return {"bold": True} + + +def _case_font_color(module: Any, path: Path) -> JSONDict: + from openpyxl.styles import Font + + wb = _new_workbook(module) + wb["Sheet1"]["A1"] = "Red" + wb["Sheet1"]["A1"].font = Font(color="FF0000") + _save(wb, path) + return {"font_color": "FF0000"} + + +def _case_fill_color(module: Any, path: Path) -> JSONDict: + from openpyxl.styles import PatternFill + + wb = _new_workbook(module) + wb["Sheet1"]["A1"] = "Fill" + wb["Sheet1"]["A1"].fill = PatternFill(fill_type="solid", fgColor="FFFF00") + _save(wb, path) + return {"fill": "FFFF00"} + + +def _case_number_format(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + wb["Sheet1"]["A1"] = 12.34 + wb["Sheet1"]["A1"].number_format = "$#,##0.00" + _save(wb, path) + return {"number_format": "$#,##0.00"} + + +def _case_alignment_wrap(module: Any, path: Path) -> JSONDict: + from openpyxl.styles import Alignment + + wb = _new_workbook(module) + wb["Sheet1"]["A1"] = "Wrapped text" + wb["Sheet1"]["A1"].alignment = Alignment(wrap_text=True) + _save(wb, path) + return {"wrap_text": True} + + +def _case_comment(module: Any, path: Path) -> JSONDict: + from openpyxl.comments import Comment + + wb = _new_workbook(module) + wb["Sheet1"]["A1"] = "Reviewed" + wb["Sheet1"]["A1"].comment = Comment("Tie to PBC", "ExcelBench") + _save(wb, path) + return {"comment": "Tie to PBC"} + + +def _case_hyperlink(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + wb["Sheet1"]["A1"] = "Project" + wb["Sheet1"]["A1"].hyperlink = "https://github.com/SynthGL/wolfxl" + _save(wb, path) + return {"hyperlink": "https://github.com/SynthGL/wolfxl"} + + +def _case_validation(module: Any, path: Path) -> JSONDict: + from openpyxl.worksheet.datavalidation import DataValidation + + wb = _new_workbook(module) + ws = wb["Sheet1"] + ws["A1"] = "Open" + dv = DataValidation(type="list", formula1='"Open,Closed"', allow_blank=True) + ws.add_data_validation(dv) + dv.add(ws["A1"]) + _save(wb, path) + return {"validation": "list"} + + +def _case_merge(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + ws = wb["Sheet1"] + ws["A1"] = "Header" + ws.merge_cells("A1:C1") + _save(wb, path) + return {"merge": "A1:C1"} + + +def _case_unmerge(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + ws = wb["Sheet1"] + ws.merge_cells("A1:C1") + ws.unmerge_cells("A1:C1") + _save(wb, path) + return {"merge": None} + + +def _case_freeze(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + wb["Sheet1"].freeze_panes = "B2" + _save(wb, path) + return {"freeze_panes": "B2"} + + +def _case_named_range(module: Any, path: Path) -> JSONDict: + from openpyxl.workbook.defined_name import DefinedName + + wb = _new_workbook(module) + wb["Sheet1"]["A1"] = "Named" + wb.defined_names.add(DefinedName("MetricName", attr_text="'Sheet1'!$A$1")) + _save(wb, path) + return {"defined_name": "MetricName"} + + +def _case_table(module: Any, path: Path) -> JSONDict: + from openpyxl.worksheet.table import Table, TableStyleInfo + + wb = _new_workbook(module) + ws = wb["Sheet1"] + ws.append(["Metric", "Value"]) + ws.append(["Revenue", 100]) + table = Table(displayName="CompatTable", ref="A1:B2") + table.tableStyleInfo = TableStyleInfo(name="TableStyleMedium2", showRowStripes=True) + ws.add_table(table) + _save(wb, path) + return {"table": "CompatTable"} + + +def _case_sheet_create(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + wb.create_sheet("Data") + _save(wb, path) + return {"sheets": ["Sheet1", "Data"]} + + +def _case_sheet_rename(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + wb["Sheet1"].title = "Renamed" + _save(wb, path) + return {"sheet": "Renamed"} + + +def _case_row_height(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + wb["Sheet1"].row_dimensions[1].height = 30 + _save(wb, path) + return {"row_height": 30} + + +def _case_column_width(module: Any, path: Path) -> JSONDict: + wb = _new_workbook(module) + wb["Sheet1"].column_dimensions["A"].width = 24 + _save(wb, path) + return {"column_width": 24} + + +def _stable_json(value: Any) -> str: + return json.dumps(value, sort_keys=True, default=str) + + +def _render_compatibility_markdown(payload: JSONDict) -> str: + lines = [ + "# ExcelBench Compatibility Context", + "", + f"- Generated: `{payload['generated_at']}`", + f"- Passed: `{payload['passed']}`", + f"- Cases: `{len(payload['cases'])}`", + "", + "| Adapter | Case | Surface | Status | Return | Snapshot | Deltas |", + "|---------|------|---------|--------|--------|----------|--------|", + ] + for row in payload["results"]: + if row["skipped"]: + status = "skipped" + elif row["passed"]: + status = "passed" + else: + status = "failed" + lines.append( + f"| {row['adapter']} | {row['case_id']} | {row['api_surface']} | {status} | " + f"{row['return_match']} | {row['snapshot_match']} | {row['delta_count']} |" + ) + lines.append("") + return "\n".join(lines) + + +def _render_compatibility_context(requested: list[str]) -> str: + return "\n".join( + [ + "# Compatibility Context", + "", + "This lane runs openpyxl-style snippets against compatible adapters.", + "", + f"- Requested adapters: {', '.join(requested)}", + "- openpyxl is the reference implementation.", + "- Non openpyxl-compatible adapters are explicit skips.", + "", + ] + ) diff --git a/src/excelbench/harness/roundtrip_runner.py b/src/excelbench/harness/roundtrip_runner.py new file mode 100644 index 0000000..59ff1bd --- /dev/null +++ b/src/excelbench/harness/roundtrip_runner.py @@ -0,0 +1,232 @@ +"""Roundtrip/idempotence context lane.""" + +from __future__ import annotations + +import json +import platform +import shutil +from dataclasses import dataclass +from datetime import UTC, datetime +from pathlib import Path +from typing import Any + +from excelbench.generator.generate import load_manifest +from excelbench.harness.adapters import ExcelAdapter +from excelbench.harness.semantic_diff import write_diff_artifacts + +JSONDict = dict[str, Any] + + +@dataclass(frozen=True) +class RoundtripResult: + adapter: str + workbook: str + cycle: int + passed: bool + skipped: bool + delta_count: int + category_counts: dict[str, int] + output_workbook: str | None = None + diff_dir: str | None = None + error: str | None = None + + def to_json_dict(self) -> JSONDict: + return { + "adapter": self.adapter, + "workbook": self.workbook, + "cycle": self.cycle, + "passed": self.passed, + "skipped": self.skipped, + "delta_count": self.delta_count, + "category_counts": self.category_counts, + "output_workbook": self.output_workbook, + "diff_dir": self.diff_dir, + "error": self.error, + } + + +def run_roundtrip_context( + test_dir: Path, + output_dir: Path, + *, + adapters: list[ExcelAdapter], + cycles: int = 2, +) -> list[RoundtripResult]: + """Run roundtrip drift checks for selected adapters and fixture workbooks.""" + test_dir = Path(test_dir) + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + workbooks = _discover_workbooks(test_dir) + results: list[RoundtripResult] = [] + + for adapter in adapters: + for workbook_path in workbooks: + results.extend( + _roundtrip_one_adapter(adapter, workbook_path, output_dir, cycles=cycles) + ) + + payload = { + "generated_at": datetime.now(UTC).isoformat(), + "platform": f"{platform.system()}-{platform.machine()}", + "test_dir": str(test_dir), + "cycles": cycles, + "results": [result.to_json_dict() for result in results], + "passed": all(result.passed or result.skipped for result in results), + } + (output_dir / "roundtrip.json").write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") + (output_dir / "ROUNDTRIP.md").write_text(_render_roundtrip_markdown(payload)) + (output_dir / "CONTEXT.md").write_text(_render_roundtrip_context(adapters, cycles)) + return results + + +def _roundtrip_one_adapter( + adapter: ExcelAdapter, + workbook_path: Path, + output_dir: Path, + *, + cycles: int, +) -> list[RoundtripResult]: + if not _can_roundtrip(adapter): + return [ + RoundtripResult( + adapter=adapter.name, + workbook=str(workbook_path), + cycle=0, + passed=False, + skipped=True, + delta_count=0, + category_counts={}, + error=( + "Adapter does not expose an openpyxl-compatible read-modify-save path " + "for semantic roundtrip checks." + ), + ) + ] + + current = workbook_path + results: list[RoundtripResult] = [] + for cycle in range(1, cycles + 1): + cycle_dir = output_dir / "workbooks" / adapter.name + cycle_dir.mkdir(parents=True, exist_ok=True) + out_path = cycle_dir / f"{workbook_path.stem}.cycle{cycle}{workbook_path.suffix}" + try: + _save_roundtrip(adapter, current, out_path) + diff_dir = output_dir / "diffs" / adapter.name / workbook_path.stem / f"cycle{cycle}" + diff = write_diff_artifacts(workbook_path, out_path, diff_dir) + results.append( + RoundtripResult( + adapter=adapter.name, + workbook=str(workbook_path), + cycle=cycle, + passed=_roundtrip_passed(diff.category_counts()), + skipped=False, + delta_count=len(diff.deltas), + category_counts=diff.category_counts(), + output_workbook=str(out_path), + diff_dir=str(diff_dir), + ) + ) + current = out_path + except Exception as exc: + results.append( + RoundtripResult( + adapter=adapter.name, + workbook=str(workbook_path), + cycle=cycle, + passed=False, + skipped=False, + delta_count=0, + category_counts={}, + output_workbook=str(out_path), + error=f"{type(exc).__name__}: {exc}", + ) + ) + break + return results + + +def _discover_workbooks(test_dir: Path) -> list[Path]: + manifest_path = test_dir / "manifest.json" + if manifest_path.exists(): + manifest = load_manifest(manifest_path) + return [test_dir / f.path for f in manifest.files if (test_dir / f.path).suffix == ".xlsx"] + return sorted(test_dir.rglob("*.xlsx")) + + +def _roundtrip_passed(category_counts: dict[str, int]) -> bool: + """Package hash churn is informational; workbook-semantic drift fails.""" + return not {category for category in category_counts if category != "package_parts"} + + +def _can_roundtrip(adapter: ExcelAdapter) -> bool: + return adapter.name in {"openpyxl", "wolfxl"} + + +def _save_roundtrip(adapter: ExcelAdapter, input_path: Path, output_path: Path) -> None: + if adapter.name == "openpyxl": + import openpyxl + + workbook = openpyxl.load_workbook(input_path, data_only=False) + try: + workbook.save(output_path) + finally: + workbook.close() + return + if adapter.name == "wolfxl": + import wolfxl + + shutil.copy2(input_path, output_path) + workbook = wolfxl.load_workbook(output_path, modify=True) + try: + workbook.save(output_path) + finally: + close = getattr(workbook, "close", None) + if close is not None: + close() + return + raise NotImplementedError(f"{adapter.name} has no roundtrip implementation") + + +def _render_roundtrip_markdown(payload: JSONDict) -> str: + rows = payload["results"] + lines = [ + "# ExcelBench Roundtrip Context", + "", + f"- Generated: `{payload['generated_at']}`", + f"- Cycles: `{payload['cycles']}`", + f"- Passed: `{payload['passed']}`", + "", + "| Adapter | Workbook | Cycle | Status | Deltas | Categories |", + "|---------|----------|-------|--------|--------|------------|", + ] + for row in rows: + if row["skipped"]: + status = "skipped" + elif row["passed"]: + status = "passed" + else: + status = "failed" + categories = ", ".join(f"{k}:{v}" for k, v in row["category_counts"].items()) + lines.append( + f"| {row['adapter']} | {Path(row['workbook']).name} | {row['cycle']} | " + f"{status} | {row['delta_count']} | {categories or '-'} |" + ) + lines.append("") + return "\n".join(lines) + + +def _render_roundtrip_context(adapters: list[ExcelAdapter], cycles: int) -> str: + adapter_names = ", ".join(adapter.name for adapter in adapters) + return "\n".join( + [ + "# Roundtrip Context", + "", + "This lane measures semantic drift after repeated open/save cycles.", + "", + f"- Requested adapters: {adapter_names}", + f"- Cycles: {cycles}", + "- Unsupported adapters are explicit skips, not silent passes.", + "- Diffs are generated with the semantic workbook diff tool.", + "", + ] + ) diff --git a/src/excelbench/harness/semantic_diff.py b/src/excelbench/harness/semantic_diff.py new file mode 100644 index 0000000..193a071 --- /dev/null +++ b/src/excelbench/harness/semantic_diff.py @@ -0,0 +1,185 @@ +"""Structured semantic diffs for Excel workbooks.""" + +from __future__ import annotations + +import json +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +from excelbench.harness.workbook_snapshot import WorkbookSnapshot, snapshot_workbook, write_snapshot + +JSONDict = dict[str, Any] + + +@dataclass(frozen=True) +class SemanticDelta: + """One semantic difference between two workbook snapshots.""" + + category: str + path: str + left: Any + right: Any + + def to_json_dict(self) -> JSONDict: + return { + "category": self.category, + "path": self.path, + "left": self.left, + "right": self.right, + } + + +@dataclass(frozen=True) +class WorkbookDiff: + """Structured diff result grouped by semantic category.""" + + left: str + right: str + deltas: tuple[SemanticDelta, ...] = field(default_factory=tuple) + + @property + def passed(self) -> bool: + return not self.deltas + + def category_counts(self) -> dict[str, int]: + counts: dict[str, int] = {} + for delta in self.deltas: + counts[delta.category] = counts.get(delta.category, 0) + 1 + return dict(sorted(counts.items())) + + def grouped(self) -> dict[str, list[JSONDict]]: + grouped: dict[str, list[JSONDict]] = {} + for delta in self.deltas: + grouped.setdefault(delta.category, []).append(delta.to_json_dict()) + return dict(sorted(grouped.items())) + + def to_json_dict(self) -> JSONDict: + return { + "left": self.left, + "right": self.right, + "passed": self.passed, + "delta_count": len(self.deltas), + "category_counts": self.category_counts(), + "deltas": [delta.to_json_dict() for delta in self.deltas], + } + + +def diff_workbooks(left: Path, right: Path) -> WorkbookDiff: + """Snapshot and compare two workbooks.""" + return compare_snapshots(snapshot_workbook(left), snapshot_workbook(right)) + + +def compare_snapshots(left: WorkbookSnapshot, right: WorkbookSnapshot) -> WorkbookDiff: + """Compare two already-built snapshots.""" + deltas: list[SemanticDelta] = [] + categories = sorted(set(left.categories) | set(right.categories)) + for category in categories: + _collect_deltas( + category=category, + path=category, + left=left.categories.get(category), + right=right.categories.get(category), + deltas=deltas, + ) + return WorkbookDiff(left=left.workbook, right=right.workbook, deltas=tuple(deltas)) + + +def write_diff_artifacts(left: Path, right: Path, output_dir: Path) -> WorkbookDiff: + """Write snapshot and diff artifacts for two workbooks.""" + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + left_snapshot = snapshot_workbook(left) + right_snapshot = snapshot_workbook(right) + diff = compare_snapshots(left_snapshot, right_snapshot) + + write_snapshot(left_snapshot, output_dir / "left.snapshot.json") + write_snapshot(right_snapshot, output_dir / "right.snapshot.json") + (output_dir / "summary.json").write_text( + json.dumps(diff.to_json_dict(), indent=2, sort_keys=True) + "\n" + ) + (output_dir / "summary.md").write_text(render_diff_markdown(diff)) + + categories_dir = output_dir / "categories" + categories_dir.mkdir(exist_ok=True) + for category, items in diff.grouped().items(): + (categories_dir / f"{category}.json").write_text( + json.dumps(items, indent=2, sort_keys=True) + "\n" + ) + return diff + + +def render_diff_markdown(diff: WorkbookDiff) -> str: + lines = [ + "# Workbook Semantic Diff", + "", + f"- Left: `{diff.left}`", + f"- Right: `{diff.right}`", + f"- Passed: `{diff.passed}`", + f"- Delta count: `{len(diff.deltas)}`", + "", + ] + if not diff.deltas: + lines.extend(["No semantic deltas detected.", ""]) + return "\n".join(lines) + + lines.extend(["## Category Summary", "", "| Category | Deltas |", "|----------|--------|"]) + for category, count in diff.category_counts().items(): + lines.append(f"| {category} | {count} |") + lines.append("") + lines.extend(["## Deltas", ""]) + for delta in diff.deltas[:200]: + lines.append( + f"- `{delta.category}` `{delta.path}`: " + f"`{_short(delta.left)}` -> `{_short(delta.right)}`" + ) + if len(diff.deltas) > 200: + lines.append(f"- ... {len(diff.deltas) - 200} additional deltas omitted from markdown") + lines.append("") + return "\n".join(lines) + + +def _collect_deltas( + *, + category: str, + path: str, + left: Any, + right: Any, + deltas: list[SemanticDelta], +) -> None: + if isinstance(left, dict) and isinstance(right, dict): + for key in sorted(set(left) | set(right), key=str): + _collect_deltas( + category=category, + path=f"{path}.{key}", + left=left.get(key), + right=right.get(key), + deltas=deltas, + ) + return + if isinstance(left, list) and isinstance(right, list): + left_sorted = sorted(left, key=_sort_key) + right_sorted = sorted(right, key=_sort_key) + if left_sorted == right_sorted: + return + max_len = max(len(left_sorted), len(right_sorted)) + for index in range(max_len): + _collect_deltas( + category=category, + path=f"{path}[{index}]", + left=left_sorted[index] if index < len(left_sorted) else None, + right=right_sorted[index] if index < len(right_sorted) else None, + deltas=deltas, + ) + return + if left != right: + deltas.append(SemanticDelta(category=category, path=path, left=left, right=right)) + + +def _sort_key(value: Any) -> str: + return json.dumps(value, sort_keys=True, default=str) + + +def _short(value: Any) -> str: + text = json.dumps(value, sort_keys=True, default=str) + return text if len(text) <= 180 else f"{text[:177]}..." diff --git a/src/excelbench/harness/workbook_snapshot.py b/src/excelbench/harness/workbook_snapshot.py new file mode 100644 index 0000000..39ad672 --- /dev/null +++ b/src/excelbench/harness/workbook_snapshot.py @@ -0,0 +1,319 @@ +"""Semantic workbook snapshot extraction for workbook/package diffs.""" + +from __future__ import annotations + +import hashlib +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Any +from zipfile import ZipFile + +import openpyxl + +JSONDict = dict[str, Any] + +_VOLATILE_PARTS = { + "docProps/core.xml", +} + + +@dataclass(frozen=True) +class WorkbookSnapshot: + """Normalized workbook semantics plus selected OOXML package metadata.""" + + workbook: str + categories: dict[str, Any] + + def to_json_dict(self) -> JSONDict: + return {"workbook": self.workbook, "categories": self.categories} + + +def snapshot_workbook(path: Path) -> WorkbookSnapshot: + """Build a deterministic semantic snapshot for an OOXML workbook.""" + path = Path(path) + workbook = openpyxl.load_workbook(path, data_only=False) + try: + categories: dict[str, Any] = { + "sheets": _snapshot_sheets(workbook), + "cells": _snapshot_cells(workbook), + "formats": _snapshot_formats(workbook), + "merges": _snapshot_merges(workbook), + "validations": _snapshot_validations(workbook), + "hyperlinks": _snapshot_hyperlinks(workbook), + "comments": _snapshot_comments(workbook), + "freeze_panes": _snapshot_freeze_panes(workbook), + "named_ranges": _snapshot_named_ranges(workbook), + "tables": _snapshot_tables(workbook), + "images": _snapshot_images(workbook), + "pivot_metadata": _snapshot_pivot_metadata(path), + "package_parts": _snapshot_package_parts(path), + } + finally: + workbook.close() + return WorkbookSnapshot(workbook=str(path), categories=_json_stable(categories)) + + +def write_snapshot(snapshot: WorkbookSnapshot, path: Path) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(snapshot.to_json_dict(), indent=2, sort_keys=True) + "\n") + + +def _snapshot_sheets(workbook: Any) -> JSONDict: + return { + "names": list(workbook.sheetnames), + "states": {name: workbook[name].sheet_state for name in workbook.sheetnames}, + } + + +def _snapshot_cells(workbook: Any) -> JSONDict: + cells: JSONDict = {} + for sheet_name in workbook.sheetnames: + sheet = workbook[sheet_name] + sheet_cells: JSONDict = {} + for row in sheet.iter_rows(): + for cell in row: + value = cell.value + if ( + value is None + and not cell.has_style + and cell.comment is None + and cell.hyperlink is None + ): + continue + sheet_cells[cell.coordinate] = { + "value": _normalize_value(value), + "data_type": cell.data_type, + } + cells[sheet_name] = sheet_cells + return cells + + +def _snapshot_formats(workbook: Any) -> JSONDict: + formats: JSONDict = {} + for sheet_name in workbook.sheetnames: + sheet = workbook[sheet_name] + sheet_formats: JSONDict = {} + for row in sheet.iter_rows(): + for cell in row: + if not cell.has_style: + continue + fmt = _cell_format(cell) + if fmt: + sheet_formats[cell.coordinate] = fmt + formats[sheet_name] = sheet_formats + return formats + + +def _snapshot_merges(workbook: Any) -> JSONDict: + return { + name: sorted(str(rng) for rng in workbook[name].merged_cells.ranges) + for name in workbook.sheetnames + } + + +def _snapshot_validations(workbook: Any) -> JSONDict: + out: JSONDict = {} + for sheet_name in workbook.sheetnames: + sheet = workbook[sheet_name] + validations = [] + for dv in getattr(sheet.data_validations, "dataValidation", []): + validations.append( + { + "sqref": str(dv.sqref), + "type": dv.type, + "operator": dv.operator, + "formula1": dv.formula1, + "formula2": dv.formula2, + "allow_blank": bool(dv.allow_blank), + } + ) + out[sheet_name] = sorted(validations, key=lambda item: (item["sqref"], item["type"] or "")) + return out + + +def _snapshot_hyperlinks(workbook: Any) -> JSONDict: + out: JSONDict = {} + for sheet_name in workbook.sheetnames: + sheet = workbook[sheet_name] + links: JSONDict = {} + for row in sheet.iter_rows(): + for cell in row: + link = cell.hyperlink + if link is None: + continue + links[cell.coordinate] = { + "target": link.target, + "location": link.location, + "display": link.display, + "tooltip": link.tooltip, + } + out[sheet_name] = links + return out + + +def _snapshot_comments(workbook: Any) -> JSONDict: + out: JSONDict = {} + for sheet_name in workbook.sheetnames: + sheet = workbook[sheet_name] + comments: JSONDict = {} + for row in sheet.iter_rows(): + for cell in row: + if cell.comment is not None: + comments[cell.coordinate] = { + "text": cell.comment.text, + "author": cell.comment.author, + } + out[sheet_name] = comments + return out + + +def _snapshot_freeze_panes(workbook: Any) -> JSONDict: + return {name: str(workbook[name].freeze_panes or "") for name in workbook.sheetnames} + + +def _snapshot_named_ranges(workbook: Any) -> JSONDict: + ranges: JSONDict = {} + for name, defined_name in workbook.defined_names.items(): + ranges[name] = { + "attr_text": defined_name.attr_text, + "local_sheet_id": defined_name.localSheetId, + "hidden": bool(defined_name.hidden), + } + return ranges + + +def _snapshot_tables(workbook: Any) -> JSONDict: + out: JSONDict = {} + for sheet_name in workbook.sheetnames: + sheet = workbook[sheet_name] + tables: JSONDict = {} + for name, table in sheet.tables.items(): + table_obj = sheet.tables[name] + tables[name] = { + "ref": table_obj.ref, + "display_name": table_obj.displayName, + "totals_row_shown": bool(table_obj.totalsRowShown), + "table_style": getattr(table_obj.tableStyleInfo, "name", None), + } + out[sheet_name] = tables + return out + + +def _snapshot_images(workbook: Any) -> JSONDict: + out: JSONDict = {} + for sheet_name in workbook.sheetnames: + sheet = workbook[sheet_name] + images = [] + for image in getattr(sheet, "_images", []): + anchor = getattr(image, "anchor", None) + images.append( + { + "format": getattr(image, "format", None), + "width": getattr(image, "width", None), + "height": getattr(image, "height", None), + "anchor": _anchor_to_text(anchor), + } + ) + out[sheet_name] = images + return out + + +def _snapshot_pivot_metadata(path: Path) -> JSONDict: + with ZipFile(path) as workbook_zip: + parts = sorted( + name + for name in workbook_zip.namelist() + if "pivot" in name.lower() or "pivotcache" in name.lower() + ) + return {"parts": parts, "count": len(parts)} + + +def _snapshot_package_parts(path: Path) -> JSONDict: + parts: JSONDict = {} + with ZipFile(path) as workbook_zip: + for info in workbook_zip.infolist(): + if info.filename in _VOLATILE_PARTS or info.is_dir(): + continue + payload = workbook_zip.read(info.filename) + parts[info.filename] = { + "size": len(payload), + "sha256": hashlib.sha256(payload).hexdigest(), + } + return parts + + +def _cell_format(cell: Any) -> JSONDict: + font = cell.font + fill = cell.fill + alignment = cell.alignment + return _drop_empty( + { + "number_format": cell.number_format, + "font": _drop_empty( + { + "name": font.name, + "size": font.sz, + "bold": bool(font.b), + "italic": bool(font.i), + "underline": font.u, + "strike": bool(font.strike), + "color": _color_to_text(font.color), + } + ), + "fill": _drop_empty( + { + "type": fill.fill_type, + "fg_color": _color_to_text(fill.fgColor), + "bg_color": _color_to_text(fill.bgColor), + } + ), + "alignment": _drop_empty( + { + "horizontal": alignment.horizontal, + "vertical": alignment.vertical, + "wrap_text": alignment.wrap_text, + "text_rotation": alignment.textRotation, + "indent": alignment.indent, + } + ), + } + ) + + +def _normalize_value(value: Any) -> Any: + if hasattr(value, "isoformat"): + return value.isoformat() + return value + + +def _color_to_text(color: Any) -> str | None: + if color is None: + return None + color_type = getattr(color, "type", None) + if color_type == "rgb": + return str(getattr(color, "rgb", "") or "").upper() or None + if color_type == "indexed": + return f"indexed:{getattr(color, 'indexed', '')}" + if color_type == "theme": + return f"theme:{getattr(color, 'theme', '')}:{getattr(color, 'tint', 0)}" + return str(color) + + +def _anchor_to_text(anchor: Any) -> str: + if isinstance(anchor, str): + return anchor + marker = getattr(anchor, "_from", None) + if marker is None: + return str(anchor) + row = int(getattr(marker, "row", 0)) + 1 + col = int(getattr(marker, "col", 0)) + 1 + return f"R{row}C{col}" + + +def _drop_empty(payload: JSONDict) -> JSONDict: + return {key: value for key, value in payload.items() if value not in (None, "", {}, [])} + + +def _json_stable(value: Any) -> Any: + return json.loads(json.dumps(value, sort_keys=True, default=str)) diff --git a/src/excelbench/models.py b/src/excelbench/models.py index a865106..17a3f11 100644 --- a/src/excelbench/models.py +++ b/src/excelbench/models.py @@ -188,6 +188,8 @@ class Diagnostic: location: DiagnosticLocation adapter_message: str probable_cause: str | None = None + root_cause_code: str | None = None + suggested_next_step: str | None = None # ============================================================================= diff --git a/src/excelbench/results/failure_explainer.py b/src/excelbench/results/failure_explainer.py new file mode 100644 index 0000000..2c78db9 --- /dev/null +++ b/src/excelbench/results/failure_explainer.py @@ -0,0 +1,226 @@ +"""Human-readable explanations for benchmark diagnostics.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from excelbench.models import Diagnostic, TestResult + +JSONDict = dict[str, Any] + + +@dataclass(frozen=True) +class FailureExplanation: + """Readable failure explanation attached to diagnostics and reports.""" + + code: str + summary: str + probable_cause: str + next_step: str + + def to_json_dict(self) -> JSONDict: + return { + "code": self.code, + "summary": self.summary, + "probable_cause": self.probable_cause, + "next_step": self.next_step, + } + + +def explain_test_failure(result: TestResult) -> FailureExplanation | None: + """Classify a failed test result into a concise explanation.""" + if result.passed: + return None + for diagnostic in result.diagnostics: + explanation = explain_diagnostic(diagnostic, result.expected, result.actual) + if explanation is not None: + return explanation + return _classify_payload(result.expected, result.actual) + + +def explain_diagnostic( + diagnostic: Diagnostic, + expected: JSONDict | None = None, + actual: JSONDict | None = None, +) -> FailureExplanation | None: + """Classify a diagnostic plus optional expected/actual payloads.""" + if diagnostic.root_cause_code and diagnostic.suggested_next_step: + return FailureExplanation( + code=diagnostic.root_cause_code, + summary=diagnostic.probable_cause or diagnostic.adapter_message, + probable_cause=diagnostic.probable_cause or diagnostic.adapter_message, + next_step=diagnostic.suggested_next_step, + ) + message = f"{diagnostic.adapter_message} {diagnostic.probable_cause or ''}".lower() + payload_text = f"{expected or {}} {actual or {}}".lower() + return _classify_text(message, payload_text) or _classify_payload(expected or {}, actual or {}) + + +def enrich_diagnostic( + diagnostic: Diagnostic, + *, + expected: JSONDict | None = None, + actual: JSONDict | None = None, +) -> Diagnostic: + """Populate root cause fields when a known failure archetype matches.""" + explanation = explain_diagnostic(diagnostic, expected, actual) + if explanation is None: + return diagnostic + if diagnostic.probable_cause is None: + diagnostic.probable_cause = explanation.probable_cause + diagnostic.root_cause_code = diagnostic.root_cause_code or explanation.code + diagnostic.suggested_next_step = diagnostic.suggested_next_step or explanation.next_step + return diagnostic + + +def render_why_failed(results: list[tuple[str, str, TestResult]]) -> str: + """Render a compact WHY_FAILED.md body.""" + lines = ["# Why Failed", ""] + failures = [(feature, library, tr) for feature, library, tr in results if not tr.passed] + if not failures: + lines.extend(["No failed test cases recorded.", ""]) + return "\n".join(lines) + for feature, library, tr in failures: + explanation = explain_test_failure(tr) + lines.append(f"## {feature} / {library} / {tr.operation.value} / {tr.test_case_id}") + lines.append("") + if explanation is None: + lines.append("- What failed: benchmark expectation did not match actual output.") + lines.append("- Likely cause: no known failure archetype matched this payload.") + lines.append("- Next debug surface: inspect expected/actual payloads in results.json.") + else: + lines.append(f"- What failed: {explanation.summary}") + lines.append(f"- Likely cause: {explanation.probable_cause}") + lines.append(f"- Next debug surface: {explanation.next_step}") + lines.append("") + return "\n".join(lines) + + +def _classify_payload(expected: JSONDict, actual: JSONDict) -> FailureExplanation | None: + expected_keys = set(expected) + actual_keys = set(actual) + text = f"{expected} {actual}".lower() + if "error" in actual: + return FailureExplanation( + "exception", + "adapter raised instead of returning comparable workbook data", + "adapter/runtime error interrupted the assertion path", + "open the diagnostic adapter message and reproduce the adapter call directly", + ) + if "formula" in expected_keys or "formula" in actual_keys or "cached" in text: + return FailureExplanation( + "formula_cache_or_formula_drift", + "formula text or cached formula result drifted", + "formula preservation and cached-value handling differ between libraries", + "inspect formula XML and cached value handling for the target cell", + ) + if {"bg_color", "font_color", "number_format", "format"} & expected_keys or "style" in text: + return FailureExplanation( + "style_drift", + "cell style metadata changed", + "style normalization, default style handling, or writer formatting support " + "is incomplete", + "diff styles.xml and the adapter's cell-format read/write path", + ) + return _classify_text("", text) + + +def _classify_text(message: str, payload_text: str) -> FailureExplanation | None: + text = f"{message} {payload_text}" + rules = [ + ( + ("not implemented", "unsupported", "not supported", "read-only", "write-only"), + FailureExplanation( + "unsupported_feature", + "adapter does not support this feature surface", + "the library or adapter has no implementation for this operation", + "check adapter capability gating before treating this as semantic drift", + ), + ), + ( + ("fill", "color", "black", "font", "number_format", "style"), + FailureExplanation( + "style_drift", + "cell style metadata changed", + "formatting was dropped, defaulted, or normalized differently", + "inspect styles.xml and the adapter's style mapping", + ), + ), + ( + ("table", "totals", "autofilter"), + FailureExplanation( + "table_metadata_drift", + "table metadata changed", + "table XML, totals-row state, or auto-filter metadata was not preserved", + "inspect xl/tables/table*.xml and worksheet table relationships", + ), + ), + ( + ("image", "drawing", "anchor", "media"), + FailureExplanation( + "drawing_or_image_drift", + "image or drawing relationship changed", + "media part may exist but worksheet drawing rels or anchors do not match", + "inspect drawing XML, drawing rels, and xl/media package parts", + ), + ), + ( + ("named", "definedname", "scope"), + FailureExplanation( + "named_range_scope_drift", + "named range metadata changed", + "workbook-level vs sheet-level defined-name scope was lost or rewritten", + "inspect workbook.xml definedNames and localSheetId values", + ), + ), + ( + ("merge", "merged"), + FailureExplanation( + "merge_drift", + "merged-cell metadata changed", + "merged range XML or non-anchor cell handling differs", + "inspect mergeCells in the worksheet XML and subordinate cell behavior", + ), + ), + ( + ("validation", "data_validation", "sqref"), + FailureExplanation( + "data_validation_drift", + "data validation metadata changed", + "validation type, formula, or target range was dropped or rewritten", + "inspect dataValidations in the worksheet XML", + ), + ), + ( + ("hyperlink", "relationship_target"), + FailureExplanation( + "hyperlink_drift", + "hyperlink target or display metadata changed", + "hyperlink rel target, tooltip, or internal location was not preserved", + "inspect worksheet hyperlinks and worksheet rels", + ), + ), + ( + ("comment", "vml"), + FailureExplanation( + "comment_drift", + "comment metadata changed", + "legacy comment text, author, or VML relationship was not preserved", + "inspect comments XML plus VML drawing relationships", + ), + ), + ( + ("freeze", "pane"), + FailureExplanation( + "freeze_pane_drift", + "freeze pane settings changed", + "pane split/top-left metadata was dropped or normalized incorrectly", + "inspect sheetViews/pane in the worksheet XML", + ), + ), + ] + for needles, explanation in rules: + if any(needle in text for needle in needles): + return explanation + return None diff --git a/src/excelbench/results/renderer.py b/src/excelbench/results/renderer.py index 21a444f..3d53f12 100644 --- a/src/excelbench/results/renderer.py +++ b/src/excelbench/results/renderer.py @@ -8,6 +8,7 @@ from typing import Any from excelbench.models import BenchmarkResults, Diagnostic, FeatureScore, OperationType, TestResult +from excelbench.results.failure_explainer import explain_diagnostic, render_why_failed from excelbench.results.report_policy import is_visible_library, modify_mode_label # Feature tier assignments @@ -89,6 +90,7 @@ def render_results(results: BenchmarkResults, output_dir: Path) -> None: render_json(results, output_dir / "results.json") render_markdown(results, output_dir / "README.md") render_csv(results, output_dir / "matrix.csv") + _render_why_failed(results, output_dir / "WHY_FAILED.md") _append_history(results, output_dir) _render_fidelity_deltas(output_dir) @@ -688,7 +690,10 @@ def _group_test_cases(test_results: list[TestResult]) -> dict[str, Any]: "expected": tr.expected, "actual": tr.actual, "notes": tr.notes, - "diagnostics": [_diagnostic_to_json(d) for d in tr.diagnostics], + "diagnostics": [ + _diagnostic_to_json(d, expected=tr.expected, actual=tr.actual) + for d in tr.diagnostics + ], "importance": tr.importance.value if tr.importance else None, "label": tr.label, } @@ -841,7 +846,13 @@ def _compute_fidelity_deltas( return deltas -def _diagnostic_to_json(diagnostic: Diagnostic) -> dict[str, Any]: +def _diagnostic_to_json( + diagnostic: Diagnostic, + *, + expected: dict[str, Any] | None = None, + actual: dict[str, Any] | None = None, +) -> dict[str, Any]: + explanation = explain_diagnostic(diagnostic, expected, actual) return { "category": diagnostic.category.value, "severity": diagnostic.severity.value, @@ -853,10 +864,26 @@ def _diagnostic_to_json(diagnostic: Diagnostic) -> dict[str, Any]: "cell": diagnostic.location.cell, }, "adapter_message": diagnostic.adapter_message, - "probable_cause": diagnostic.probable_cause, + "probable_cause": diagnostic.probable_cause + or (explanation.probable_cause if explanation else None), + "root_cause_code": diagnostic.root_cause_code + or (explanation.code if explanation else None), + "suggested_next_step": diagnostic.suggested_next_step + or (explanation.next_step if explanation else None), + "explanation": explanation.to_json_dict() if explanation else None, } +def _render_why_failed(results: BenchmarkResults, path: Path) -> None: + rows: list[tuple[str, str, TestResult]] = [] + for score in results.scores: + for tr in score.test_results: + rows.append((score.feature, score.library, tr)) + if not any(not tr.passed for _, _, tr in rows): + return + path.write_text(render_why_failed(rows)) + + def _render_diagnostics_summary(results: BenchmarkResults) -> list[str]: diagnostics: list[Diagnostic] = [] for score in results.scores: diff --git a/tests/test_context_lanes.py b/tests/test_context_lanes.py new file mode 100644 index 0000000..c80c535 --- /dev/null +++ b/tests/test_context_lanes.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from openpyxl import Workbook +from typer.testing import CliRunner + +from excelbench.cli import app + + +def _write_xlsx(path: Path) -> None: + workbook = Workbook() + sheet = workbook.active + sheet.title = "Data" + sheet["A1"] = "Revenue" + workbook.save(path) + workbook.close() + + +def test_diff_workbooks_cli(tmp_path: Path) -> None: + left = tmp_path / "left.xlsx" + right = tmp_path / "right.xlsx" + output = tmp_path / "out" + _write_xlsx(left) + _write_xlsx(right) + + result = CliRunner().invoke( + app, + [ + "diff-workbooks", + "--left", + str(left), + "--right", + str(right), + "--output", + str(output), + ], + ) + + assert result.exit_code == 0 + assert (output / "summary.json").exists() + + +def test_roundtrip_context_cli_with_openpyxl(tmp_path: Path) -> None: + fixtures = tmp_path / "fixtures" + fixtures.mkdir() + _write_xlsx(fixtures / "sample.xlsx") + output = tmp_path / "roundtrip" + + result = CliRunner().invoke( + app, + [ + "roundtrip-context", + "--tests", + str(fixtures), + "--output", + str(output), + "--adapter", + "openpyxl", + "--cycles", + "1", + ], + ) + + assert result.exit_code == 0 + payload = json.loads((output / "roundtrip.json").read_text()) + assert payload["results"] + assert all(row["passed"] for row in payload["results"]) + + +def test_compatibility_context_cli_skips_unknown_adapter(tmp_path: Path) -> None: + output = tmp_path / "compat" + + result = CliRunner().invoke( + app, + [ + "compatibility-context", + "--output", + str(output), + "--adapter", + "unknown-adapter", + ], + ) + + assert result.exit_code == 0 + payload = json.loads((output / "compatibility.json").read_text()) + assert payload["results"][0]["skipped"] is True + + +def test_macro_context_cli_skips_without_fixtures(tmp_path: Path) -> None: + output = tmp_path / "macros" + + result = CliRunner().invoke( + app, + ["macro-context", "--tests", str(tmp_path / "missing"), "--output", str(output)], + ) + + assert result.exit_code == 0 + payload = json.loads((output / "results.json").read_text()) + assert payload["results"][0]["skipped"] is True diff --git a/tests/test_failure_explainer.py b/tests/test_failure_explainer.py new file mode 100644 index 0000000..a7d5f92 --- /dev/null +++ b/tests/test_failure_explainer.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from excelbench.models import ( + Diagnostic, + DiagnosticCategory, + DiagnosticLocation, + DiagnosticSeverity, + OperationType, + TestResult, +) +from excelbench.results.failure_explainer import explain_diagnostic, explain_test_failure + + +def _diagnostic(message: str) -> Diagnostic: + return Diagnostic( + category=DiagnosticCategory.DATA_MISMATCH, + severity=DiagnosticSeverity.ERROR, + location=DiagnosticLocation(feature="styles", operation=OperationType.READ), + adapter_message=message, + ) + + +def test_explain_diagnostic_classifies_style_drift() -> None: + explanation = explain_diagnostic( + _diagnostic("Expected fill color #FFFF00, actual default black"), + {"bg_color": "#FFFF00"}, + {"bg_color": "#000000"}, + ) + + assert explanation is not None + assert explanation.code == "style_drift" + + +def test_explain_test_failure_classifies_formula_payload() -> None: + result = TestResult( + test_case_id="formula", + operation=OperationType.READ, + passed=False, + expected={"formula": "=SUM(A1:A2)"}, + actual={"value": None}, + diagnostics=[], + ) + + explanation = explain_test_failure(result) + + assert explanation is not None + assert explanation.code == "formula_cache_or_formula_drift" diff --git a/tests/test_semantic_diff.py b/tests/test_semantic_diff.py new file mode 100644 index 0000000..7cff4f8 --- /dev/null +++ b/tests/test_semantic_diff.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from openpyxl import Workbook +from openpyxl.comments import Comment +from openpyxl.styles import PatternFill + +from excelbench.harness.semantic_diff import diff_workbooks, write_diff_artifacts +from excelbench.harness.workbook_snapshot import snapshot_workbook + + +def _write_workbook(path: Path, *, value: str = "Revenue", fill: str = "FFFF00") -> None: + workbook = Workbook() + sheet = workbook.active + sheet.title = "Data" + sheet["A1"] = value + sheet["A1"].fill = PatternFill(fill_type="solid", fgColor=fill) + sheet["B1"] = "Link" + sheet["B1"].hyperlink = "https://example.com" + sheet["C1"].comment = Comment("reviewed", "ExcelBench") + sheet.merge_cells("D1:E1") + sheet.freeze_panes = "B2" + workbook.save(path) + workbook.close() + + +def test_snapshot_workbook_captures_semantic_categories(tmp_path: Path) -> None: + path = tmp_path / "book.xlsx" + _write_workbook(path) + + snapshot = snapshot_workbook(path) + + assert "cells" in snapshot.categories + assert snapshot.categories["cells"]["Data"]["A1"]["value"] == "Revenue" + assert snapshot.categories["merges"]["Data"] == ["D1:E1"] + assert snapshot.categories["hyperlinks"]["Data"]["B1"]["target"] == "https://example.com" + assert snapshot.categories["comments"]["Data"]["C1"]["text"] == "reviewed" + assert snapshot.categories["freeze_panes"]["Data"] == "B2" + + +def test_diff_workbooks_reports_category_counts(tmp_path: Path) -> None: + left = tmp_path / "left.xlsx" + right = tmp_path / "right.xlsx" + _write_workbook(left, value="Revenue") + _write_workbook(right, value="COGS") + + diff = diff_workbooks(left, right) + + assert not diff.passed + assert diff.category_counts()["cells"] >= 1 + + +def test_write_diff_artifacts(tmp_path: Path) -> None: + left = tmp_path / "left.xlsx" + right = tmp_path / "right.xlsx" + output = tmp_path / "diff" + _write_workbook(left, value="Revenue") + _write_workbook(right, value="COGS") + + write_diff_artifacts(left, right, output) + + summary = json.loads((output / "summary.json").read_text()) + assert summary["passed"] is False + assert (output / "summary.md").exists() + assert (output / "categories" / "cells.json").exists() From 00e4946cdcc2d0d54cc70668311afeace9c74232 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:52:21 -0700 Subject: [PATCH 21/25] feat: add cross-language context adapters --- docs/trackers/apache-poi-adapter-design.md | 138 +++ .../apache-poi-excelize-rollout-plan.md | 119 +++ .../cross-language-comparison-strategy.md | 89 ++ docs/trackers/excelize-adapter-design.md | 139 +++ src/excelbench/cli.py | 439 +++++++++- src/excelbench/harness/adapters/__init__.py | 31 + .../harness/adapters/apache_poi_adapter.py | 252 ++++++ .../harness/adapters/excelize_adapter.py | 309 +++++++ tests/test_apache_poi_adapter.py | 397 +++++++++ tests/test_cross_language_context_cli.py | 70 ++ tests/test_excelize_adapter.py | 318 +++++++ .../external-oracles/apache-poi/poi_oracle.py | 254 ++++++ .../apache-poi/src/PoiOracle.java | 691 +++++++++++++++ tools/external-oracles/excelize/main.go | 792 +++++++++++++++++- 14 files changed, 4010 insertions(+), 28 deletions(-) create mode 100644 docs/trackers/apache-poi-adapter-design.md create mode 100644 docs/trackers/apache-poi-excelize-rollout-plan.md create mode 100644 docs/trackers/cross-language-comparison-strategy.md create mode 100644 docs/trackers/excelize-adapter-design.md create mode 100644 src/excelbench/harness/adapters/apache_poi_adapter.py create mode 100644 src/excelbench/harness/adapters/excelize_adapter.py create mode 100644 tests/test_apache_poi_adapter.py create mode 100644 tests/test_cross_language_context_cli.py create mode 100644 tests/test_excelize_adapter.py diff --git a/docs/trackers/apache-poi-adapter-design.md b/docs/trackers/apache-poi-adapter-design.md new file mode 100644 index 0000000..e3600ce --- /dev/null +++ b/docs/trackers/apache-poi-adapter-design.md @@ -0,0 +1,138 @@ +# Apache POI Adapter Design + +## Goal + +Add `Apache POI` as a cross-language context adapter in ExcelBench. + +This adapter is not meant to answer the Python migration question. It is meant to show where WolfXL sits relative to a mature Java spreadsheet library. + +## Existing scaffold + +Current helper assets: + +- `tools/external-oracles/apache-poi/src/PoiOracle.java` +- `tools/external-oracles/apache-poi/poi_oracle.py` +- `tools/external-oracles/apache-poi/build.sh` +- `tools/external-oracles/apache-poi/fetch_deps.py` +- `tools/external-oracles/apache-poi/README.md` + +Bootstrap command: + +```bash +cd tools/external-oracles/apache-poi +./build.sh +``` + +The helper currently supports: + +- `write_fixture` +- `read_metadata` + +That means the shortest path is **not** a full cell-by-cell adapter on day one. The shortest path is a write-focused adapter with selective read support where metadata inspection is already stable. + +## Recommended adapter shape + +### Public classification + +- Language: `java` +- Caps: `R+W` only if stable read semantics are implemented for benchmark-required features +- Fallback classification: `W` if read support is too partial for a fair comparison +- Modify: `No` + +### Initial scope + +P0 should target: + +- Tier 0 write: `cell_values`, `formulas`, `multiple_sheets` +- Tier 1 write: `alignment`, `background_colors`, `borders`, `dimensions`, `number_formats`, `text_formatting` +- Tier 2 write where the current helper already has evidence: `comments`, `hyperlinks`, `data_validation`, `merged_cells`, `freeze_panes`, `images` +- Tier 3 write: defer until workbook metadata APIs are mapped cleanly + +Read scope for P0 should be conservative: + +- `cell_values` +- `formulas` +- `multiple_sheets` +- selected metadata-backed checks only if they can be surfaced in a benchmark-compatible way + +## Why this scope + +- The existing helper already generates realistic POI workbooks. +- The helper already inspects package-level metadata. +- That makes POI a good benchmark citizen for write fidelity first. +- Forcing deep read parity immediately would slow rollout and increase ambiguity. + +## Implementation approach + +### Phase 1 - bridge helper into adapter runtime + +Create a thin Python adapter that shells out to the existing helper instead of embedding Java logic directly. + +Suggested new files: + +- `src/excelbench/harness/adapters/apache_poi_adapter.py` +- optionally `src/excelbench/harness/adapters/external_process_adapter_utils.py` if shared subprocess glue is needed + +Suggested existing files to update: + +- `src/excelbench/harness/adapters/__init__.py` +- any adapter registry/test discovery file that enumerates public adapters + +### Phase 2 - benchmark contract mapping + +Map ExcelBench adapter methods onto helper-backed operations. + +Write path options: + +1. translate a benchmark workbook spec directly into the helper JSON contract +2. let the helper produce `.xlsx` +3. verify with the existing benchmark oracle path + +Read path options: + +1. open workbook via helper `read_metadata` for structural features +2. use a lightweight Java read extension only for values/formulas if needed +3. mark unsupported features honestly instead of faking read support + +## Capability boundaries to document + +Before public inclusion, write down feature-by-feature status: + +- score normally +- score via metadata inspection only +- unsupported in current adapter +- intentionally deferred + +Important examples to decide explicitly: + +- charts +- pivot tables +- named ranges +- tables +- comments/VML handling +- image preservation vs image semantic readback + +## Testing plan + +1. Add one adapter smoke test that confirms bootstrap failure returns structured skip, not an opaque crash. +2. Add one write-path integration test for a small Tier 0 workbook. +3. Add one advanced-feature integration test matching a currently supported POI fixture. +4. Run a dated snapshot separate from the Python-first hero table first. + +## Public reporting plan + +- Add POI only in a `Cross-Language Context` section initially. +- Do not place POI in the primary hero table. +- Label it clearly as ecosystem context, not a Python substitute. + +## Decision log + +- Chose helper-backed adapter over direct Java embedding because the helper already exists, has pinned dependencies, and reduces integration risk. +- Chose write-first rollout because the scaffold already proves fixture generation and metadata inspection, while full read parity is less certain. + +## Exact next implementation targets + +1. `src/excelbench/harness/adapters/apache_poi_adapter.py` +2. `src/excelbench/harness/adapters/__init__.py` +3. adapter smoke/integration tests under the existing adapter test area +4. a dated cross-language snapshot output path, separate from the Python hero snapshot diff --git a/docs/trackers/apache-poi-excelize-rollout-plan.md b/docs/trackers/apache-poi-excelize-rollout-plan.md new file mode 100644 index 0000000..d5dd7bd --- /dev/null +++ b/docs/trackers/apache-poi-excelize-rollout-plan.md @@ -0,0 +1,119 @@ +# Apache POI + Excelize Rollout Plan + +## Objective + +Promote `Apache POI` and `Excelize` from external-oracle helpers into the first public cross-language context tier for ExcelBench. + +This is a secondary comparison track. It should strengthen ecosystem credibility without replacing the Python-first benchmark story. + +## Why these two first + +- `Apache POI` is the strongest mature Java spreadsheet reference point. +- `Excelize` is a strong Go spreadsheet library with good modern developer relevance. +- Both already exist in the external-oracle scaffold, which reduces integration risk. + +## Current starting point + +Both helpers already exist under the external-oracle system: + +- `tools/external-oracles/apache-poi` +- `tools/external-oracles/excelize` + +Both already support fixture generation and metadata inspection, and both have smoke coverage documented in `docs/trackers/external-oracle-expansion.md`. + +## Phase 1 - Capability audit + +Goal: define what each library can be scored on fairly. + +### Apache POI + +1. Confirm read vs write scope for the adapter surface. +2. Enumerate unsupported or weak features explicitly. +3. Decide whether modify/preserve semantics should be marked `No` or `Preserve-only`. +4. Capture runtime/bootstrap steps for local and CI use. + +### Excelize + +1. Confirm read vs write scope for the adapter surface. +2. Confirm which advanced features are realistic to score vs generate-only. +3. Decide whether streaming APIs belong in a separate variant adapter later. +4. Capture Go toolchain and invocation assumptions. + +## Phase 2 - Adapter contract design + +Goal: map both libraries onto the same comparison model used by Python adapters. + +Deliverables: + +1. Adapter design note for `Apache POI` +2. Adapter design note for `Excelize` +3. Feature-scope matrix with statuses: + - score normally + - score with caveat + - unsupported + - preserve-only + +## Phase 3 - Minimal benchmark adapter + +Goal: get each library into a dated snapshot with honest capability boundaries. + +### Apache POI + +P0 milestone: + +1. Implement write-path adapter for Tier 0 and Tier 1. +2. Implement read-path adapter for Tier 0 and whichever metadata paths are stable. +3. Run against canonical fixtures. +4. Render results into a dedicated dated snapshot. + +### Excelize + +P0 milestone: + +1. Implement write-path adapter for Tier 0 and supported Tier 1/Tier 2 features. +2. Implement read-path adapter for cell values, sheets, and any stable metadata. +3. Run against canonical fixtures. +4. Render results into a dedicated dated snapshot. + +## Phase 4 - Public reporting + +Goal: publish results without muddying the Python-first README story. + +Rules: + +1. Keep the current hero table Python-first. +2. Add a separate "Cross-Language Context" section. +3. Label POI/Excelize results as ecosystem context, not Python alternatives. +4. Use a separate dated snapshot or subsection if needed. + +## Acceptance criteria + +Before either library appears in the main public repo README as an active comparison: + +1. Adapter runs reproducibly from documented commands. +2. Capability boundaries are documented feature-by-feature. +3. Results are dated and traceable to raw artifacts. +4. At least one smoke test or integration test covers the adapter path. +5. README wording makes clear that the comparison is cross-language context. + +## Recommended command path + +Start from the existing helper-backed scaffold, not a greenfield adapter. + +1. Reuse fixture-generation/inspection logic from external oracles. +2. Wrap the stable operations in ExcelBench adapter interfaces. +3. Keep bootstrap scripts per runtime (`go`, `java`) under `tools/external-oracles/`. + +## Suggested order + +1. `Apache POI` +2. `Excelize` +3. `ClosedXML` +4. `ExcelJS` + +## Success condition + +ExcelBench can show: + +- a clear Python-first benchmark story for migration decisions +- a separate cross-language context view showing WolfXL relative to mature spreadsheet libraries in Java, Go, .NET, and Node diff --git a/docs/trackers/cross-language-comparison-strategy.md b/docs/trackers/cross-language-comparison-strategy.md new file mode 100644 index 0000000..3f8b6f5 --- /dev/null +++ b/docs/trackers/cross-language-comparison-strategy.md @@ -0,0 +1,89 @@ +# Cross-Language Comparison Strategy + +## Goal + +Extend ExcelBench beyond Python-only comparisons without weakening the core public story. + +The repo should answer two different questions clearly: + +1. **Python replacement question**: what should a Python team use instead of `openpyxl`? +2. **Ecosystem position question**: how does WolfXL compare to mature spreadsheet libraries in other languages? + +## Comparison Tiers + +### Tier 1 - Primary public comparison + +This is the default README and launch-post comparison set. + +- `openpyxl` +- `xlsxwriter` +- `xlsxwriter-constmem` +- `python-calamine` +- `pandas` +- `pyexcel` / `tablib` / `pylightxl` as lower-fidelity reference points + +Use this tier when the audience is choosing a Python library. + +### Tier 2 - Cross-language credibility comparison + +This tier is for ecosystem context, research posts, and deeper benchmark reports. + +High-priority additions: + +- `Apache POI` +- `ClosedXML` +- `Excelize` +- `ExcelJS` + +Lower-priority additions: + +- `NPOI` +- `EPPlus` if licensing constraints are addressed explicitly + +Use this tier to show where WolfXL sits relative to mature spreadsheet tooling outside Python. + +## Public Messaging Rules + +- Lead with Python alternatives in the main README and launch copy. +- Put cross-language results in a separate section or separate report. +- Do not mix Python drop-in comparisons and cross-language ecosystem rankings in one hero table. +- When a library is not a realistic substitute for Python users, say so directly. + +## Why This Split Exists + +- Python users care first about migration cost and workflow fit. +- Cross-language libraries are valuable for credibility, not for drop-in replacement decisions. +- A single giant scoreboard makes the main value proposition harder to understand. + +## Candidate Priority + +### P0 + +- `Apache POI` +- `Excelize` + +### P1 + +- `ClosedXML` +- `ExcelJS` + +### P2 + +- `NPOI` + +## Acceptance Criteria For New Cross-Language Adapters + +Before adding a new library to public comparison tables: + +1. Adapter is reproducible from source or documented package install. +2. Fixture generation and verification path are documented. +3. Capability boundaries are explicit: read, write, modify, or preserve-only. +4. Platform/runtime caveats are documented. +5. Results are generated in the same dated snapshot format as Python adapters. + +## Suggested Rollout + +1. Add `Apache POI` and `Excelize` first. +2. Publish them in a secondary "cross-language context" section. +3. Keep the main benchmark hero table Python-first. +4. Promote any cross-language table to primary only if the user story demands it. diff --git a/docs/trackers/excelize-adapter-design.md b/docs/trackers/excelize-adapter-design.md new file mode 100644 index 0000000..f8f2cb3 --- /dev/null +++ b/docs/trackers/excelize-adapter-design.md @@ -0,0 +1,139 @@ +# Excelize Adapter Design + +## Goal + +Add `Excelize` as a cross-language context adapter in ExcelBench. + +Excelize is valuable because it represents a modern Go spreadsheet stack with strong relevance for backend and systems developers. + +## Existing scaffold + +Current helper assets: + +- `tools/external-oracles/excelize/main.go` +- `tools/external-oracles/excelize/main_test.go` +- `tools/external-oracles/excelize/go.mod` +- `tools/external-oracles/excelize/README.md` + +Run commands: + +```bash +cd tools/external-oracles/excelize +go test ./... +go run . < request.json +``` + +The helper currently supports: + +- `write_fixture` +- `read_metadata` + +Supported write payload keys already include: + +- `cells` +- `columns` +- `tables` +- `conditional_formats` +- `charts` +- `pivots` +- `slicers` +- `pictures` + +This is a strong base for a write-heavy adapter and a metadata-aware structural adapter. + +## Recommended adapter shape + +### Public classification + +- Language: `go` +- Caps: `R+W` only if stable value/formula reads are implemented in benchmark form +- Fallback classification: `W` if read support stays metadata-only for too long +- Modify: `No` + +### Initial scope + +P0 should target: + +- Tier 0 write: `cell_values`, `formulas`, `multiple_sheets` +- Tier 1 write: `alignment`, `background_colors`, `dimensions`, `number_formats`, `text_formatting` +- Tier 2 write where helper support already exists: `conditional_formatting`, `images`, `freeze_panes`, `merged_cells` +- Pivot-related generation should be treated carefully: good for context, but not scored unless the verification path is fair and stable + +Read scope for P0 should be conservative: + +- `cell_values` +- `multiple_sheets` +- metadata-backed structural features only where the helper already reports them consistently + +## Why this scope + +- The Excelize helper already expresses a broad write payload. +- It also reports metadata counts for pivots, slicers, and conditional formatting. +- That makes it a good candidate for an honest write-capability benchmark plus limited read coverage. + +## Implementation approach + +### Phase 1 - helper-backed adapter + +Create a Python adapter that uses subprocess calls to the existing Go helper. + +Suggested new files: + +- `src/excelbench/harness/adapters/excelize_adapter.py` +- optionally a shared helper module if subprocess patterns overlap with POI + +Suggested existing files to update: + +- `src/excelbench/harness/adapters/__init__.py` + +### Phase 2 - benchmark contract mapping + +Write path: + +1. translate benchmark workbook spec to the Excelize helper JSON format +2. generate `.xlsx` through `go run .` +3. reuse current verification/oracle path + +Read path: + +1. use helper `read_metadata` for structural features +2. add direct value/formula read support only if it can be surfaced cleanly and reproducibly +3. mark unsupported features explicitly where necessary + +## Capability boundaries to document + +Before public inclusion, decide explicitly how to treat: + +- pivots +- slicers +- charts +- pictures/images +- table metadata +- conditional formatting richness + +These are good Excelize strengths, but they need fair scoring criteria. If the helper only exposes counts and package presence, that belongs in a context/capability section, not in a full-fidelity read claim. + +## Testing plan + +1. Adapter smoke test for missing Go toolchain or helper path. +2. Small Tier 0 write-path integration test. +3. One advanced-feature integration test using current helper-supported payloads. +4. Dated snapshot run in a separate cross-language context output path. + +## Public reporting plan + +- Add Excelize to a `Cross-Language Context` section first. +- Do not mix it into the Python hero table. +- Use it to answer the ecosystem-position question, not the Python migration question. + +## Decision log + +- Chose helper-backed adapter over direct Go embedding because the helper already has tests and a well-defined JSON contract. +- Chose write-first rollout because the helper is already strong at fixture generation and structural emit. + +## Exact next implementation targets + +1. `src/excelbench/harness/adapters/excelize_adapter.py` +2. `src/excelbench/harness/adapters/__init__.py` +3. adapter smoke/integration tests under the existing adapter test area +4. a dated cross-language snapshot output path, separate from the Python hero snapshot diff --git a/src/excelbench/cli.py b/src/excelbench/cli.py index 92172c9..4c8292b 100644 --- a/src/excelbench/cli.py +++ b/src/excelbench/cli.py @@ -20,6 +20,7 @@ XLS_PROFILE_DEFAULT_TEST_DIR = Path("fixtures/excel_xls") XLSX_PROFILE_DEFAULT_TEST_DIR = Path("test_files") PERF_XLSX_PROFILE_DEFAULT_TEST_DIR = Path("fixtures/excel") +CROSS_LANGUAGE_ADAPTER_NAMES = ("apache-poi", "excelize") @app.command() @@ -389,9 +390,7 @@ def perf( ] -def _resolve_shape_features( - *, types_arg: str, rows: int -) -> tuple[list[str], list[str], list[str]]: +def _resolve_shape_features(*, types_arg: str, rows: int) -> tuple[list[str], list[str], list[str]]: """Translate --types and --rows into (read_features, write_features, tier_labels). Tiers are chosen as every label whose canonical cell-count <= ``rows``, so @@ -414,9 +413,7 @@ def _resolve_shape_features( tier_labels = [label for label, cap in _DATA_SHAPE_TIER_CAPS if cap <= rows] if not tier_labels: - raise ValueError( - f"--rows {rows} is below the smallest tier (1000); pick at least 1000." - ) + raise ValueError(f"--rows {rows} is below the smallest tier (1000); pick at least 1000.") read_features: list[str] = [] write_features: list[str] = [] @@ -581,8 +578,9 @@ def _file_shape_fixtures_stale( isinstance(f, dict) and isinstance(f.get("feature"), str) and f["feature"].startswith("file_shape_") - and ("_1m" in f["feature"] or "_100x10k_" in f["feature"] - or "_1000x1k_" in f["feature"]) + and ( + "_1m" in f["feature"] or "_100x10k_" in f["feature"] or "_1000x1k_" in f["feature"] + ) for f in files ): return True @@ -691,14 +689,10 @@ def perf_shape( needs_1m = "1m" in tier_labels manifest_path = fixtures / "manifest.json" generator_script = ( - Path(__file__).resolve().parents[2] - / "scripts" - / "generate_throughput_fixtures.py" + Path(__file__).resolve().parents[2] / "scripts" / "generate_throughput_fixtures.py" ) - if regenerate or _shape_fixtures_stale( - manifest_path, generator_script, needs_1m=needs_1m - ): + if regenerate or _shape_fixtures_stale(manifest_path, generator_script, needs_1m=needs_1m): gen_cmd = [ sys.executable, str(generator_script), @@ -881,14 +875,10 @@ def perf_file_shape( ) manifest_path = fixtures / "manifest.json" generator_script = ( - Path(__file__).resolve().parents[2] - / "scripts" - / "generate_throughput_fixtures.py" + Path(__file__).resolve().parents[2] / "scripts" / "generate_throughput_fixtures.py" ) - if regenerate or _file_shape_fixtures_stale( - manifest_path, generator_script, needs_1m=needs_1m - ): + if regenerate or _file_shape_fixtures_stale(manifest_path, generator_script, needs_1m=needs_1m): gen_cmd = [ sys.executable, str(generator_script), @@ -1208,6 +1198,97 @@ def macro_context( console.print(f" - failures: {len(failures)}") +@app.command("cross-language-context") +def cross_language_context( + test_dir: Path = typer.Option( + PERF_XLSX_PROFILE_DEFAULT_TEST_DIR, + "--tests", + "-t", + help="Directory containing .xlsx tests and manifest.json.", + ), + output_dir: Path = typer.Option( + Path("results-cross-language"), + "--output", + "-o", + help="Directory to save cross-language context results.", + ), +) -> None: + """Run a dedicated cross-language context snapshot. + + This keeps POI/Excelize-style ecosystem comparisons separate from the + Python-first benchmark story. + """ + from excelbench.harness.adapters import get_all_adapters + from excelbench.harness.runner import run_benchmark + from excelbench.results import render_results + + available = {adapter.name: adapter for adapter in get_all_adapters()} + selected = [available[name] for name in CROSS_LANGUAGE_ADAPTER_NAMES if name in available] + + if not selected: + console.print( + "[red]Error: No cross-language adapters available. " + "Build the Apache POI and/or Excelize helpers first.[/red]" + ) + raise typer.Exit(1) + + console.print("[bold]Running cross-language context snapshot...[/bold]") + console.print(f" Test files: {test_dir}") + console.print(f" Output: {output_dir}") + console.print(f" Adapters: {', '.join(a.name for a in selected)}") + console.print() + + try: + results = run_benchmark(test_dir, adapters=selected, profile="xlsx") + render_results(results, output_dir) + _write_cross_language_index(output_dir, [a.name for a in selected]) + console.print() + console.print(f"[green]✓ Cross-language context results written to {output_dir}[/green]") + console.print(f" - {output_dir}/results.json") + console.print(f" - {output_dir}/README.md") + console.print(f" - {output_dir}/matrix.csv") + console.print(f" - {output_dir}/CONTEXT.md") + except FileNotFoundError as e: + console.print(f"[red]Error: {e}[/red]") + raise typer.Exit(1) + except Exception as e: + console.print(f"[red]Error: {e}[/red]") + raise typer.Exit(1) + + +@app.command("cross-language-pivot-context") +def cross_language_pivot_context( + fixture_path: Path = typer.Option( + Path("fixtures/excel/tier2/15_pivot_tables.xlsx"), + "--fixture", + help="Pivot fixture path to inspect.", + ), + output_dir: Path = typer.Option( + Path("results-cross-language-pivots"), + "--output", + "-o", + help="Directory to save cross-language pivot context artifacts.", + ), +) -> None: + """Generate a separate cross-language pivot context artifact. + + This lane exists because the main cross-language snapshot does not score + pivot tables on macOS. It reports what the helpers can detect from the + shipped fixture and whether Excelize can emit a pivot-bearing workbook. + """ + import json + + output_dir.mkdir(parents=True, exist_ok=True) + results = _collect_cross_language_pivot_context(fixture_path, output_dir) + (output_dir / "results.json").write_text(json.dumps(results, indent=2), encoding="utf-8") + _write_cross_language_pivot_readme(output_dir, results) + _write_cross_language_pivot_context_note(output_dir) + console.print(f"[green]✓ Cross-language pivot context written to {output_dir}[/green]") + console.print(f" - {output_dir}/results.json") + console.print(f" - {output_dir}/README.md") + console.print(f" - {output_dir}/CONTEXT.md") + + def _results_from_json(data: dict[str, Any]) -> "BenchmarkResults": from datetime import datetime @@ -1353,6 +1434,321 @@ def _write_profile_index(output_dir: Path) -> None: (output_dir / "README.md").write_text(content) +def _write_cross_language_index(output_dir: Path, adapters: list[str]) -> None: + output_dir.mkdir(parents=True, exist_ok=True) + adapter_lines = "\n".join(f"- `{adapter}`" for adapter in adapters) + content = "\n".join( + [ + "# ExcelBench Cross-Language Context", + "", + "This snapshot is for ecosystem context, not Python migration decisions.", + "", + "Use this snapshot to answer: how does WolfXL compare to mature " + "spreadsheet tooling outside Python?", + "", + "Included adapters:", + adapter_lines, + "", + "Artifacts:", + "- [README](./README.md)", + "- [results.json](./results.json)", + "- [matrix.csv](./matrix.csv)", + "", + "Interpretation guidance:", + "- Keep this separate from the Python hero table.", + "- Cite the artifact path and generation date.", + "- Treat write-only adapters as ecosystem context, not drop-in migration advice.", + "", + "> Do not compare this table directly against the Python-first hero table " + "without naming the scope. " + "This run exists to position WolfXL against non-Python spreadsheet tooling.", + "", + ] + ) + (output_dir / "CONTEXT.md").write_text(content) + + +def _collect_cross_language_pivot_context(fixture_path: Path, output_dir: Path) -> dict[str, Any]: + import platform as _platform + from datetime import UTC, datetime + from zipfile import ZipFile + + from excelbench.harness.external_oracles import ( + ExternalOracleRequest, + external_oracle_catalog, + run_external_oracle, + ) + + repo_root = Path(__file__).resolve().parents[2] + fixture_path = fixture_path.resolve() + catalog = external_oracle_catalog(repo_root=repo_root) + + fixture_parts: list[str] = [] + fixture_contains_pivot_parts = False + if fixture_path.exists(): + with ZipFile(fixture_path) as zf: + fixture_parts = sorted( + name + for name in zf.namelist() + if "pivot" in name.lower() or "cache" in name.lower() + ) + fixture_contains_pivot_parts = bool(fixture_parts) + + helpers: dict[str, Any] = {} + for name in CROSS_LANGUAGE_ADAPTER_NAMES: + tool = catalog[name] + helper_info: dict[str, Any] = { + "available": tool.is_available(), + "fixture_read_detects_pivot": False, + "fixture_metadata": None, + "fixture_error": None, + } + if tool.is_available() and fixture_path.exists(): + res = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="pivot-read", + operation="read_metadata", + input_path=fixture_path, + payload={}, + ), + ) + helper_info["fixture_metadata"] = res.payload + if res.passed: + helper_info["fixture_read_detects_pivot"] = _helper_detects_pivot(name, res.payload) + else: + helper_info["fixture_error"] = ( + res.payload.get("message") or res.stderr or res.stdout + ) + helpers[name] = helper_info + + excelize_probe = _run_excelize_pivot_probe(output_dir / "excelize-pivot-probe.xlsx") + apache_poi_probe = { + "supported": False, + "reason": "ApachePoiAdapter does not implement pivot table creation yet.", + } + + return { + "metadata": { + "run_date": datetime.now(UTC).isoformat(), + "platform": f"{_platform.system()}-{_platform.machine()}", + "fixture_path": str(fixture_path), + }, + "fixture": { + "exists": fixture_path.exists(), + "contains_pivot_parts": fixture_contains_pivot_parts, + "pivot_related_parts": fixture_parts, + }, + "helpers": helpers, + "probes": { + "apache_poi": apache_poi_probe, + "excelize": excelize_probe, + }, + } + + +def _helper_detects_pivot(helper_name: str, payload: dict[str, Any]) -> bool: + if helper_name == "excelize": + sheets = payload.get("sheets") or [] + return any(int(sheet.get("pivots", 0)) > 0 for sheet in sheets) + counts = payload.get("counts") or {} + return any("pivot" in key.lower() and int(value) > 0 for key, value in counts.items()) + + +def _run_excelize_pivot_probe(output_path: Path) -> dict[str, Any]: + from zipfile import ZipFile + + from excelbench.harness.adapters.excelize_adapter import ExcelizeAdapter + from excelbench.harness.adapters.openpyxl_adapter import OpenpyxlAdapter + from excelbench.models import CellType, CellValue + + adapter = ExcelizeAdapter() + if not adapter.is_available(): + return {"supported": False, "reason": "Excelize helper is not available."} + output_path = output_path.resolve() + + workbook = adapter.create_workbook() + adapter.add_sheet(workbook, "Data") + adapter.add_sheet(workbook, "Pivot") + headers = ["Region", "Quarter", "Revenue", "Units"] + rows = [ + ["North", "Q1", 1000, 10], + ["North", "Q2", 1200, 12], + ["South", "Q1", 900, 9], + ["South", "Q2", 1100, 11], + ] + for col, header in enumerate(headers, start=1): + adapter.write_cell_value( + workbook, + "Data", + _pivot_coord_to_cell(1, col), + CellValue(type=CellType.STRING, value=header), + ) + for row_index, row in enumerate(rows, start=2): + for col, value in enumerate(row, start=1): + adapter.write_cell_value( + workbook, + "Data", + _pivot_coord_to_cell(row_index, col), + CellValue( + type=CellType.STRING if isinstance(value, str) else CellType.NUMBER, + value=value, + ), + ) + adapter.add_pivot_table( + workbook, + "Pivot", + { + "pivot": { + "name": "SalesPivot", + "data_range": "Data!A1:D5", + "range": "Pivot!B3:E10", + "rows": [{"name": "Region"}], + "columns": [{"name": "Quarter"}], + "data": [{"name": "Revenue", "subtotal": "Sum"}], + } + }, + ) + try: + adapter.save_workbook(workbook, output_path) + except Exception as exc: + return {"supported": False, "reason": str(exc)} + + pivot_parts: list[str] = [] + with ZipFile(output_path) as zf: + pivot_parts = sorted( + name for name in zf.namelist() if "pivot" in name.lower() or "cache" in name.lower() + ) + verifier = OpenpyxlAdapter() + wb = verifier.open_workbook(output_path) + try: + pivots = verifier.read_pivot_tables(wb, "Pivot") + finally: + verifier.close_workbook(wb) + return { + "supported": True, + "output_path": str(output_path), + "pivot_related_parts": pivot_parts, + "openpyxl_readback": pivots, + } + + +def _pivot_coord_to_cell(row: int, col: int) -> str: + letters = "" + while col > 0: + col, rem = divmod(col - 1, 26) + letters = chr(65 + rem) + letters + return f"{letters}{row}" + + +def _write_cross_language_pivot_readme(output_dir: Path, results: dict[str, Any]) -> None: + import json + + fixture = results["fixture"] + helpers = results["helpers"] + probes = results["probes"] + fixture_parts = fixture.get("pivot_related_parts") or [] + fixture_summary = "Yes" if fixture.get("contains_pivot_parts") else "No" + excelize_probe = probes["excelize"] + apache_probe = probes["apache_poi"] + lines = [ + "# Cross-Language Pivot Context", + "", + "This artifact exists because the main cross-language benchmark lane does " + "not score pivot tables on macOS.", + "", + "## Fixture Check", + "", + f"- Shipped fixture contains pivot OOXML parts: **{fixture_summary}**", + f"- Fixture path: `{results['metadata']['fixture_path']}`", + ] + if fixture_parts: + lines.extend( + ["- Fixture pivot-related parts:"] + [f" - `{part}`" for part in fixture_parts] + ) + else: + lines.append("- Fixture pivot-related parts: none detected") + lines.extend( + [ + "", + "## Helper Detection", + "", + "| Helper | Available | Detects pivots in shipped fixture | Notes |", + "|---|---:|---:|---|", + _pivot_helper_row("apache-poi", helpers, "package metadata helper"), + _pivot_helper_row("excelize", helpers, "sheet metadata helper"), + "", + "## Write Probes", + "", + "| Tool | Pivot write support | Evidence |", + "|---|---:|---|", + _pivot_probe_row("apache-poi", apache_probe, ""), + _pivot_probe_row("excelize", excelize_probe, "OOXML parts + openpyxl readback"), + ] + ) + if excelize_probe.get("supported"): + lines.extend( + [ + "", + "### Excelize Probe Details", + "", + f"- Output workbook: `{excelize_probe['output_path']}`", + "- Pivot-related OOXML parts:", + ] + + [f" - `{part}`" for part in excelize_probe.get("pivot_related_parts", [])] + + [ + "- Openpyxl readback:", + "```json\n" + f"{json.dumps(excelize_probe.get('openpyxl_readback', []), indent=2)}\n" + "```", + ] + ) + lines.extend( + [ + "", + "## Interpretation", + "", + "- The main cross-language scorecard remains correct to mark " + "`pivot_tables` as not scored on macOS.", + "- The shipped fixture currently does not provide scoreable pivot evidence " + "for this lane.", + "- `excelize` can still emit pivot-bearing workbooks, and this artifact " + "captures that separately from the main scorecard.", + ] + ) + (output_dir / "README.md").write_text("\n".join(lines), encoding="utf-8") + + +def _pivot_helper_row(helper_name: str, helpers: dict[str, Any], notes: str) -> str: + helper = helpers[helper_name] + available = "Yes" if helper["available"] else "No" + detects = "Yes" if helper["fixture_read_detects_pivot"] else "No" + return f"| {helper_name} | {available} | {detects} | {notes} |" + + +def _pivot_probe_row(tool_name: str, probe: dict[str, Any], success_evidence: str) -> str: + supported = "Yes" if probe.get("supported") else "No" + evidence = success_evidence if probe.get("supported") else str(probe.get("reason", "")) + return f"| {tool_name} | {supported} | {evidence} |" + + +def _write_cross_language_pivot_context_note(output_dir: Path) -> None: + content = "\n".join( + [ + "# Pivot Context Note", + "", + "This directory is a capability artifact, not a scored benchmark lane.", + "", + "Use it when you want to answer: can the cross-language helpers detect " + "or emit pivot-bearing workbooks?", + "", + "Keep it separate from the main `results-cross-language/` scoreboard.", + "", + ] + ) + (output_dir / "CONTEXT.md").write_text(content, encoding="utf-8") + + @app.command() def report( results_path: Path = typer.Option( @@ -1503,8 +1899,7 @@ def html_dashboard( try: perf = perf_path if perf_path.exists() else None sdir = scatter_dir if scatter_dir.exists() else None - mem = (memory_path if isinstance(memory_path, Path) and memory_path.exists() - else None) + mem = memory_path if isinstance(memory_path, Path) and memory_path.exists() else None render_html_dashboard(fidelity_path, perf, output_path, sdir, memory_json=mem) console.print(f" [green]✓[/green] {output_path}") except Exception as e: diff --git a/src/excelbench/harness/adapters/__init__.py b/src/excelbench/harness/adapters/__init__.py index df68d14..196364c 100644 --- a/src/excelbench/harness/adapters/__init__.py +++ b/src/excelbench/harness/adapters/__init__.py @@ -7,6 +7,15 @@ AdapterClass: TypeAlias = type[ExcelAdapter] + +def _is_available_adapter(adapter: AdapterClass | None) -> bool: + if adapter is None: + return False + is_available = getattr(adapter, "is_available", None) + if is_available is None: + return True + return bool(is_available()) + try: from excelbench.harness.adapters.xlsxwriter_adapter import ( XlsxwriterAdapter as _XlsxwriterAdapter, @@ -141,6 +150,20 @@ else: ExcelOracleAdapter = _ExcelOracleAdapter +try: + from excelbench.harness.adapters.apache_poi_adapter import ApachePoiAdapter as _ApachePoiAdapter +except ImportError: + ApachePoiAdapter: AdapterClass | None = None +else: + ApachePoiAdapter = _ApachePoiAdapter + +try: + from excelbench.harness.adapters.excelize_adapter import ExcelizeAdapter as _ExcelizeAdapter +except ImportError: + ExcelizeAdapter: AdapterClass | None = None +else: + ExcelizeAdapter = _ExcelizeAdapter + __all__ = [ "ExcelAdapter", "ReadOnlyAdapter", @@ -183,6 +206,10 @@ __all__.append("PolarsAdapter") if TablibAdapter is not None: __all__.append("TablibAdapter") +if ApachePoiAdapter is not None: + __all__.append("ApachePoiAdapter") +if ExcelizeAdapter is not None: + __all__.append("ExcelizeAdapter") def get_all_adapters() -> list[ExcelAdapter]: @@ -222,4 +249,8 @@ def get_all_adapters() -> list[ExcelAdapter]: adapters.append(PolarsAdapter()) if TablibAdapter is not None: adapters.append(TablibAdapter()) + if ApachePoiAdapter is not None and _is_available_adapter(ApachePoiAdapter): + adapters.append(ApachePoiAdapter()) + if ExcelizeAdapter is not None and _is_available_adapter(ExcelizeAdapter): + adapters.append(ExcelizeAdapter()) return adapters diff --git a/src/excelbench/harness/adapters/apache_poi_adapter.py b/src/excelbench/harness/adapters/apache_poi_adapter.py new file mode 100644 index 0000000..c947454 --- /dev/null +++ b/src/excelbench/harness/adapters/apache_poi_adapter.py @@ -0,0 +1,252 @@ +"""Write-only adapter backed by the Apache POI external helper.""" + +from __future__ import annotations + +from dataclasses import asdict +from datetime import date, datetime +from pathlib import Path +from typing import Any + +from excelbench.harness.adapters.base import WriteOnlyAdapter +from excelbench.harness.external_oracles import ( + ExternalOracleRequest, + ExternalOracleTool, + external_oracle_catalog, + run_external_oracle, +) +from excelbench.models import BorderInfo, CellFormat, CellType, CellValue, LibraryInfo + +WorkbookData = dict[str, Any] + + +class ApachePoiAdapter(WriteOnlyAdapter): + """Minimal write-only adapter using the Apache POI helper. + + This is a cross-language context adapter, not part of the Python-first hero + benchmark path yet. It currently targets generic workbook generation for a + focused subset of write operations. + """ + + VERSION = "5.5.1" + + @classmethod + def repo_root(cls) -> Path: + return Path(__file__).resolve().parents[4] + + @classmethod + def tool(cls) -> ExternalOracleTool: + return external_oracle_catalog(repo_root=cls.repo_root())["apache-poi"] + + @classmethod + def is_available(cls) -> bool: + return cls.tool().is_available() + + @property + def info(self) -> LibraryInfo: + return LibraryInfo( + name="apache-poi", + version=self.VERSION, + language="java", + capabilities={"write"}, + ) + + def create_workbook(self) -> WorkbookData: + return { + "sheets": [], + "values": [], + "formats": [], + "borders": [], + "row_heights": [], + "column_widths": [], + "merges": [], + "validations": [], + "hyperlinks": [], + "comments": [], + "freeze_panes": [], + "tables": [], + "images": [], + "named_ranges": [], + } + + def add_sheet(self, workbook: WorkbookData, name: str) -> None: + if name not in workbook["sheets"]: + workbook["sheets"].append(name) + + def _ensure_sheet(self, workbook: WorkbookData, sheet: str) -> None: + if sheet not in workbook["sheets"]: + workbook["sheets"].append(sheet) + + def write_cell_value( + self, + workbook: WorkbookData, + sheet: str, + cell: str, + value: CellValue, + ) -> None: + self._ensure_sheet(workbook, sheet) + workbook["values"].append( + { + "sheet": sheet, + "cell": cell, + "type": str(value.type), + "value": self._serialize_cell_value(value), + "formula": value.formula or "", + } + ) + + def write_cell_format( + self, + workbook: WorkbookData, + sheet: str, + cell: str, + format: CellFormat, + ) -> None: + self._ensure_sheet(workbook, sheet) + workbook["formats"].append({"sheet": sheet, "cell": cell, **asdict(format)}) + + def write_cell_border( + self, + workbook: WorkbookData, + sheet: str, + cell: str, + border: BorderInfo, + ) -> None: + self._ensure_sheet(workbook, sheet) + workbook["borders"].append({"sheet": sheet, "cell": cell, "border": asdict(border)}) + + def set_row_height( + self, + workbook: WorkbookData, + sheet: str, + row: int, + height: float, + ) -> None: + self._ensure_sheet(workbook, sheet) + workbook["row_heights"].append({"sheet": sheet, "row": row, "height": height}) + + def set_column_width( + self, + workbook: WorkbookData, + sheet: str, + column: str, + width: float, + ) -> None: + self._ensure_sheet(workbook, sheet) + workbook["column_widths"].append({"sheet": sheet, "column": column, "width": width}) + + def merge_cells(self, workbook: WorkbookData, sheet: str, cell_range: str) -> None: + self._ensure_sheet(workbook, sheet) + workbook["merges"].append({"sheet": sheet, "range": cell_range}) + + def add_conditional_format( + self, workbook: WorkbookData, sheet: str, rule: dict[str, Any] + ) -> None: + self._ensure_sheet(workbook, sheet) + payload = rule.get("cf_rule", rule) + workbook.setdefault("conditional_formats", []).append({"sheet": sheet, **payload}) + + def add_data_validation( + self, workbook: WorkbookData, sheet: str, validation: dict[str, Any] + ) -> None: + self._ensure_sheet(workbook, sheet) + payload = validation.get("validation", validation) + if payload.get("values") and not payload.get("formula1"): + payload = { + **payload, + "validation_type": payload.get("validation_type", "list"), + "formula1": '"' + ",".join(str(v) for v in payload["values"]) + '"', + } + workbook["validations"].append({"sheet": sheet, **payload}) + + def add_hyperlink(self, workbook: WorkbookData, sheet: str, link: dict[str, Any]) -> None: + self._ensure_sheet(workbook, sheet) + payload = link.get("hyperlink", link) + workbook["hyperlinks"].append({"sheet": sheet, **payload}) + + def add_image(self, workbook: WorkbookData, sheet: str, image: dict[str, Any]) -> None: + self._ensure_sheet(workbook, sheet) + payload = image.get("image", image) + path = payload.get("path") + resolved = str(Path(path).resolve()) if path else path + workbook["images"].append({"sheet": sheet, **payload, "path": resolved}) + + def add_pivot_table(self, workbook: WorkbookData, sheet: str, pivot: dict[str, Any]) -> None: + raise NotImplementedError("apache-poi adapter does not yet support pivot writes") + + def add_comment(self, workbook: WorkbookData, sheet: str, comment: dict[str, Any]) -> None: + self._ensure_sheet(workbook, sheet) + payload = comment.get("comment", comment) + workbook["comments"].append({"sheet": sheet, **payload}) + + def set_freeze_panes( + self, workbook: WorkbookData, sheet: str, settings: dict[str, Any] + ) -> None: + self._ensure_sheet(workbook, sheet) + payload = settings.get("freeze", settings) + mode = str(payload.get("mode") or "freeze") + x_split = int(payload.get("x_split", 0) or 0) + y_split = int(payload.get("y_split", 0) or 0) + top_left = payload.get("top_left_cell") + if mode == "freeze" and top_left and x_split == 0 and y_split == 0: + from openpyxl.utils.cell import coordinate_to_tuple + + row, col = coordinate_to_tuple(str(top_left)) + x_split = max(col - 1, 0) + y_split = max(row - 1, 0) + workbook["freeze_panes"].append( + { + "sheet": sheet, + "mode": mode, + "x_split": x_split, + "y_split": y_split, + "top_left_cell": top_left, + } + ) + + def add_table(self, workbook: WorkbookData, sheet: str, table: dict[str, Any]) -> None: + self._ensure_sheet(workbook, sheet) + payload = table.get("table", table) + workbook["tables"].append({"sheet": sheet, **payload}) + + def add_named_range( + self, workbook: WorkbookData, sheet: str, named_range: dict[str, Any] + ) -> None: + self._ensure_sheet(workbook, sheet) + payload = named_range.get("named_range", named_range) + workbook["named_ranges"].append({"sheet": sheet, **payload}) + + def save_workbook(self, workbook: WorkbookData, path: Path) -> None: + tool = self.tool() + result = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="apache-poi-adapter", + operation="write_adapter_workbook", + output_path=path, + payload=workbook, + ), + ) + if result.skipped: + raise FileNotFoundError(result.notes or "Apache POI helper unavailable") + if not result.passed: + message = result.payload.get("message") or result.stderr or result.stdout + raise RuntimeError(str(message)) + + def _serialize_cell_value(self, value: CellValue) -> str: + if value.type == CellType.BLANK: + return "" + if value.type == CellType.STRING: + return "" if value.value is None else str(value.value) + if value.type == CellType.NUMBER: + return str(value.value) + if value.type == CellType.BOOLEAN: + return "true" if bool(value.value) else "false" + if value.type == CellType.ERROR: + return str(value.value) + if value.type == CellType.FORMULA: + return str(value.value or value.formula or "") + if value.type == CellType.DATE and isinstance(value.value, date): + return value.value.isoformat() + if value.type == CellType.DATETIME and isinstance(value.value, datetime): + return value.value.isoformat(timespec="seconds") + return "" if value.value is None else str(value.value) diff --git a/src/excelbench/harness/adapters/excelize_adapter.py b/src/excelbench/harness/adapters/excelize_adapter.py new file mode 100644 index 0000000..fbfbc61 --- /dev/null +++ b/src/excelbench/harness/adapters/excelize_adapter.py @@ -0,0 +1,309 @@ +"""Write-oriented adapter backed by the Excelize external helper.""" + +from __future__ import annotations + +from dataclasses import asdict +from pathlib import Path +from typing import Any + +from excelbench.harness.adapters.base import WriteOnlyAdapter +from excelbench.harness.external_oracles import ( + ExternalOracleRequest, + ExternalOracleTool, + external_oracle_catalog, + run_external_oracle, +) +from excelbench.models import BorderInfo, CellFormat, CellType, CellValue, LibraryInfo + +WorkbookData = dict[str, Any] + + +class ExcelizeAdapter(WriteOnlyAdapter): + """Cross-language write-first adapter backed by the Excelize Go helper.""" + + @classmethod + def repo_root(cls) -> Path: + return Path(__file__).resolve().parents[4] + + @classmethod + def tool(cls) -> ExternalOracleTool: + return external_oracle_catalog(repo_root=cls.repo_root())["excelize"] + + @classmethod + def is_available(cls) -> bool: + return cls.tool().is_available() + + @property + def info(self) -> LibraryInfo: + return LibraryInfo( + name="excelize", + version="go-helper", + language="go", + capabilities={"write"}, + ) + + def create_workbook(self) -> WorkbookData: + return { + "sheets": [], + "cells": [], + "formats": [], + "borders": [], + "columns": [], + "row_heights": [], + "merges": [], + "validations": [], + "hyperlinks": [], + "comments": [], + "panes": [], + "named_ranges": [], + "tables": [], + "conditional_formats": [], + "charts": [], + "pivots": [], + "slicers": [], + "pictures": [], + } + + def add_sheet(self, workbook: WorkbookData, name: str) -> None: + if not any(sheet["name"] == name for sheet in workbook["sheets"]): + workbook["sheets"].append({"name": name}) + + def _ensure_sheet(self, workbook: WorkbookData, sheet: str) -> None: + self.add_sheet(workbook, sheet) + + def write_cell_value( + self, + workbook: WorkbookData, + sheet: str, + cell: str, + value: CellValue, + ) -> None: + self._ensure_sheet(workbook, sheet) + entry: dict[str, Any] = {"sheet": sheet, "cell": cell} + if value.type == CellType.FORMULA: + entry["type"] = "formula" + entry["formula"] = value.formula or str(value.value or "") + entry["value"] = value.value + elif value.type == CellType.BLANK or (value.type == CellType.STRING and value.value == ""): + entry["type"] = "blank" + entry["value"] = None + else: + entry["type"] = str(value.type) + entry["value"] = self._serialize_value(value) + workbook["cells"].append(entry) + + def write_cell_format( + self, + workbook: WorkbookData, + sheet: str, + cell: str, + format: CellFormat, + ) -> None: + self._ensure_sheet(workbook, sheet) + workbook["formats"].append( + { + "sheet": sheet, + "cell": cell, + "bold": format.bold, + "italic": format.italic, + "underline": format.underline, + "strikethrough": format.strikethrough, + "font_name": format.font_name, + "font_size": format.font_size, + "font_color": format.font_color, + "bg_color": format.bg_color, + "number_format": format.number_format, + "h_align": format.h_align, + "v_align": format.v_align, + "wrap": format.wrap, + "rotation": format.rotation, + "indent": format.indent, + } + ) + + def write_cell_border( + self, + workbook: WorkbookData, + sheet: str, + cell: str, + border: BorderInfo, + ) -> None: + self._ensure_sheet(workbook, sheet) + workbook["borders"].append({"sheet": sheet, "cell": cell, "border": asdict(border)}) + + def set_row_height( + self, + workbook: WorkbookData, + sheet: str, + row: int, + height: float, + ) -> None: + self._ensure_sheet(workbook, sheet) + workbook["row_heights"].append({"sheet": sheet, "row": row, "height": height}) + + def set_column_width( + self, + workbook: WorkbookData, + sheet: str, + column: str, + width: float, + ) -> None: + self._ensure_sheet(workbook, sheet) + workbook["columns"].append({"sheet": sheet, "start": column, "end": column, "width": width}) + + def merge_cells(self, workbook: WorkbookData, sheet: str, cell_range: str) -> None: + self._ensure_sheet(workbook, sheet) + workbook["merges"].append({"sheet": sheet, "range": cell_range}) + + def add_conditional_format( + self, workbook: WorkbookData, sheet: str, rule: dict[str, Any] + ) -> None: + self._ensure_sheet(workbook, sheet) + payload = rule.get("cf_rule", rule) + workbook["conditional_formats"].append( + { + "sheet": sheet, + "range": payload.get("range"), + "type": self._map_conditional_format_type(payload.get("rule_type")), + "criteria": self._map_conditional_format_criteria( + payload.get("rule_type"), payload.get("operator"), payload.get("formula") + ), + "value": self._map_conditional_format_value(payload), + "stop_if_true": payload.get("stop_if_true", False), + "bg_color": (payload.get("format") or {}).get("bg_color", ""), + } + ) + + def add_data_validation( + self, workbook: WorkbookData, sheet: str, validation: dict[str, Any] + ) -> None: + self._ensure_sheet(workbook, sheet) + payload = validation.get("validation", validation) + workbook["validations"].append({"sheet": sheet, **payload}) + + def add_hyperlink(self, workbook: WorkbookData, sheet: str, link: dict[str, Any]) -> None: + self._ensure_sheet(workbook, sheet) + payload = link.get("hyperlink", link) + workbook["hyperlinks"].append({"sheet": sheet, **payload}) + + def add_image(self, workbook: WorkbookData, sheet: str, image: dict[str, Any]) -> None: + self._ensure_sheet(workbook, sheet) + payload = image.get("image", image) + workbook["pictures"].append({"sheet": sheet, **payload}) + + def add_pivot_table(self, workbook: WorkbookData, sheet: str, pivot: dict[str, Any]) -> None: + payload = pivot.get("pivot", pivot) + target_cell = payload.get("target_cell") + target_range = payload.get("range") + if target_range is None and target_cell is not None: + target_range = f"{sheet}!{target_cell}:{target_cell}" + workbook["pivots"].append( + { + "data_range": payload.get("source_range") or payload.get("data_range"), + "range": target_range, + "name": payload.get("name"), + "rows": payload.get("rows") + or [{"name": name} for name in payload.get("row_fields", [])], + "columns": payload.get("columns") + or [{"name": name} for name in payload.get("column_fields", [])], + "data": payload.get("data") + or [{"name": name, "subtotal": "Sum"} for name in payload.get("data_fields", [])], + "filters": payload.get("filters") + or [{"name": name} for name in payload.get("filter_fields", [])], + } + ) + + def add_comment(self, workbook: WorkbookData, sheet: str, comment: dict[str, Any]) -> None: + self._ensure_sheet(workbook, sheet) + payload = comment.get("comment", comment) + workbook["comments"].append({"sheet": sheet, **payload}) + + def set_freeze_panes( + self, workbook: WorkbookData, sheet: str, settings: dict[str, Any] + ) -> None: + self._ensure_sheet(workbook, sheet) + payload = settings.get("freeze", settings) + workbook["panes"].append({"sheet": sheet, **payload}) + + def add_table(self, workbook: WorkbookData, sheet: str, table: dict[str, Any]) -> None: + self._ensure_sheet(workbook, sheet) + payload = table.get("table", table) + workbook["tables"].append( + { + "sheet": sheet, + "range": payload.get("ref") or payload.get("range"), + "name": payload.get("name"), + "style": payload.get("style"), + "show_header_row": payload.get("header_row", True), + "show_row_stripes": True, + "totals_row": payload.get("totals_row", False), + "columns": payload.get("columns", []), + "autofilter": payload.get("autofilter", False), + } + ) + + def add_named_range( + self, workbook: WorkbookData, sheet: str, named_range: dict[str, Any] + ) -> None: + self._ensure_sheet(workbook, sheet) + payload = named_range.get("named_range", named_range) + workbook["named_ranges"].append({"sheet": sheet, **payload}) + + def save_workbook(self, workbook: WorkbookData, path: Path) -> None: + tool = self.tool() + result = run_external_oracle( + tool, + ExternalOracleRequest( + fixture_id="excelize-adapter", + operation="write_fixture", + output_path=path, + payload=workbook, + ), + timeout_seconds=120, + ) + if result.skipped: + raise FileNotFoundError(result.notes or "Excelize helper unavailable") + if not result.passed: + message = result.payload.get("message") or result.stderr or result.stdout + raise RuntimeError(str(message)) + + def _serialize_value(self, value: CellValue) -> Any: + if value.type == CellType.BLANK: + return None + if value.type == CellType.DATE and value.value is not None: + return value.value.isoformat() + if value.type == CellType.DATETIME and value.value is not None: + return value.value.isoformat(timespec="seconds") + return value.value + + def _map_conditional_format_type(self, value: Any) -> str: + rule_type = str(value or "") + if rule_type == "colorScale": + return "3_color_scale" + if rule_type == "dataBar": + return "data_bar" + if rule_type == "iconSet": + return "icon_set" + if rule_type == "expression": + return "formula" + return "cell" + + def _map_conditional_format_criteria(self, rule_type: Any, operator: Any, formula: Any) -> str: + if str(rule_type or "") == "expression": + return str(formula or "").lstrip("=") + mapping = { + "greaterThan": ">", + "greaterThanOrEqual": ">=", + "lessThan": "<", + "lessThanOrEqual": "<=", + "equal": "==", + "notEqual": "!=", + "between": "between", + "notBetween": "not between", + } + return mapping.get(str(operator or ""), ">") + + def _map_conditional_format_value(self, payload: dict[str, Any]) -> str: + if str(payload.get("rule_type") or "") == "expression": + return "" + return (payload.get("formula") or payload.get("value") or "0").lstrip("=") diff --git a/tests/test_apache_poi_adapter.py b/tests/test_apache_poi_adapter.py new file mode 100644 index 0000000..15ed92a --- /dev/null +++ b/tests/test_apache_poi_adapter.py @@ -0,0 +1,397 @@ +"""Tests for the ApachePoiAdapter cross-language write path.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from excelbench.harness.adapters.apache_poi_adapter import ApachePoiAdapter +from excelbench.harness.adapters.openpyxl_adapter import OpenpyxlAdapter +from excelbench.models import CellFormat, CellType, CellValue + + +@pytest.fixture +def opxl() -> OpenpyxlAdapter: + return OpenpyxlAdapter() + + +@pytest.fixture +def poi() -> ApachePoiAdapter: + return ApachePoiAdapter() + + +class TestApachePoiAdapterInfo: + def test_name(self, poi: ApachePoiAdapter) -> None: + assert poi.info.name == "apache-poi" + + def test_capabilities(self, poi: ApachePoiAdapter) -> None: + assert poi.info.capabilities == {"write"} + assert poi.can_write() + assert not poi.can_read() + + +class TestApachePoiAvailability: + def test_save_raises_structured_error_when_helper_missing( + self, poi: ApachePoiAdapter, monkeypatch: pytest.MonkeyPatch, tmp_path: Path + ) -> None: + class MissingTool: + name = "apache-poi" + command = ("missing-poi-helper",) + + def is_available(self) -> bool: + return False + + def resolve_executable(self) -> None: + return None + + def resolved_command(self) -> None: + return None + + monkeypatch.setattr(ApachePoiAdapter, "tool", classmethod(lambda cls: MissingTool())) + wb = poi.create_workbook() + poi.add_sheet(wb, "S") + poi.write_cell_value(wb, "S", "A1", CellValue(type=CellType.STRING, value="x")) + with pytest.raises(FileNotFoundError): + poi.save_workbook(wb, tmp_path / "missing.xlsx") + + +@pytest.mark.skipif( + not ApachePoiAdapter.is_available(), reason="Apache POI helper is not available" +) +class TestApachePoiWriteRoundtrip: + def test_string_formula_and_multiple_sheets( + self, poi: ApachePoiAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "poi_multi.xlsx" + wb = poi.create_workbook() + poi.add_sheet(wb, "S1") + poi.add_sheet(wb, "S2") + poi.write_cell_value(wb, "S1", "A1", CellValue(type=CellType.STRING, value="hello")) + poi.write_cell_value(wb, "S1", "B1", CellValue(type=CellType.NUMBER, value=42.5)) + poi.write_cell_value( + wb, + "S1", + "C1", + CellValue(type=CellType.FORMULA, value="=B1*2", formula="=B1*2"), + ) + poi.write_cell_value(wb, "S2", "A1", CellValue(type=CellType.BOOLEAN, value=True)) + poi.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + assert opxl.read_cell_value(rb, "S1", "A1").value == "hello" + assert opxl.read_cell_value(rb, "S1", "B1").value == 42.5 + assert opxl.read_cell_value(rb, "S1", "C1").type == CellType.FORMULA + assert opxl.read_cell_value(rb, "S2", "A1").value is True + opxl.close_workbook(rb) + + def test_format_merge_comment_hyperlink_freeze_and_validation( + self, poi: ApachePoiAdapter, tmp_path: Path + ) -> None: + import openpyxl + + path = tmp_path / "poi_features.xlsx" + wb = poi.create_workbook() + poi.add_sheet(wb, "S") + poi.write_cell_value(wb, "S", "A1", CellValue(type=CellType.STRING, value="bold")) + poi.write_cell_format(wb, "S", "A1", CellFormat(bold=True)) + poi.write_cell_value(wb, "S", "B2", CellValue(type=CellType.STRING, value="link")) + poi.add_hyperlink( + wb, "S", {"cell": "B2", "target": "https://poi.apache.org/", "label": "POI"} + ) + poi.add_comment(wb, "S", {"cell": "C3", "text": "note", "author": "ExcelBench"}) + poi.merge_cells(wb, "S", "D1:E1") + poi.set_freeze_panes(wb, "S", {"x_split": 1, "y_split": 1}) + poi.add_data_validation(wb, "S", {"cell": "F2", "values": ["Open", "Closed"]}) + poi.save_workbook(wb, path) + + rb = openpyxl.load_workbook(path) + ws = rb["S"] + assert ws["A1"].font.bold is True + assert ws["B2"].hyperlink is not None + assert ws["C3"].comment is not None + assert "D1:E1" in {str(rng) for rng in ws.merged_cells.ranges} + assert ws.freeze_panes == "B2" + assert len(list(ws.data_validations.dataValidation)) == 1 + rb.close() + + def test_image_roundtrip(self, poi: ApachePoiAdapter, tmp_path: Path) -> None: + import openpyxl + + path = tmp_path / "poi_image.xlsx" + wb = poi.create_workbook() + poi.add_sheet(wb, "S") + poi.add_image( + wb, "S", {"cell": "B2", "path": "fixtures/images/sample.png", "anchor": "oneCell"} + ) + poi.save_workbook(wb, path) + + rb = openpyxl.load_workbook(path) + ws = rb["S"] + assert len(getattr(ws, "_images", [])) == 1 + rb.close() + + def test_split_pane_roundtrip( + self, poi: ApachePoiAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "poi_split.xlsx" + wb = poi.create_workbook() + poi.add_sheet(wb, "Split") + poi.set_freeze_panes(wb, "Split", {"freeze": {"mode": "split", "x_split": 1, "y_split": 2}}) + poi.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + actual = opxl.read_freeze_panes(rb, "Split") + assert actual["mode"] == "split" + assert actual["x_split"] == 1 + assert actual["y_split"] == 2 + opxl.close_workbook(rb) + + def test_conditional_format_roundtrip( + self, poi: ApachePoiAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "poi_cf.xlsx" + wb = poi.create_workbook() + poi.add_sheet(wb, "S") + for row in range(2, 7): + poi.write_cell_value(wb, "S", f"B{row}", CellValue(type=CellType.NUMBER, value=row - 1)) + poi.add_conditional_format( + wb, + "S", + { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "format": {"bg_color": "#FFFF00"}, + } + }, + ) + poi.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + rules = opxl.read_conditional_formats(rb, "S") + assert any( + rule.get("rule_type") == "cellIs" and rule.get("formula") == "5" for rule in rules + ) + opxl.close_workbook(rb) + + def test_named_range_roundtrip( + self, poi: ApachePoiAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "poi_names.xlsx" + wb = poi.create_workbook() + poi.add_sheet(wb, "named_ranges") + poi.add_sheet(wb, "Targets") + poi.write_cell_value(wb, "named_ranges", "B2", CellValue(type=CellType.NUMBER, value=42)) + poi.write_cell_value(wb, "Targets", "A1", CellValue(type=CellType.STRING, value="Target")) + poi.add_named_range( + wb, + "named_ranges", + { + "named_range": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + } + }, + ) + poi.add_named_range( + wb, + "named_ranges", + { + "named_range": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$2", + } + }, + ) + poi.add_named_range( + wb, + "named_ranges", + { + "named_range": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1", + } + }, + ) + poi.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + names = opxl.read_named_ranges(rb, "named_ranges") + assert any( + item.get("name") == "SingleCell" and item.get("scope") == "workbook" for item in names + ) + assert any( + item.get("name") == "LocalName" and item.get("scope") == "sheet" for item in names + ) + assert any( + item.get("name") == "OtherSheet" and item.get("refers_to") == "Targets!$A$1" + for item in names + ) + opxl.close_workbook(rb) + + def test_border_roundtrip( + self, poi: ApachePoiAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + from excelbench.models import BorderEdge, BorderInfo, BorderStyle + + path = tmp_path / "poi_border.xlsx" + wb = poi.create_workbook() + poi.add_sheet(wb, "S") + poi.write_cell_value(wb, "S", "B2", CellValue(type=CellType.STRING, value="Border")) + poi.write_cell_border( + wb, + "S", + "B2", + BorderInfo( + top=BorderEdge(style=BorderStyle.THIN, color="#FF0000"), + bottom=BorderEdge(style=BorderStyle.DOUBLE, color="#00FF00"), + left=BorderEdge(style=BorderStyle.MEDIUM, color="#0000FF"), + right=BorderEdge(style=BorderStyle.DASHED, color="#FFFF00"), + ), + ) + poi.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + border = opxl.read_cell_border(rb, "S", "B2") + assert ( + border.top + and border.top.style.value == "thin" + and border.top.color.upper() == "#FF0000" + ) + assert ( + border.bottom + and border.bottom.style.value == "double" + and border.bottom.color.upper() == "#00FF00" + ) + assert ( + border.left + and border.left.style.value == "medium" + and border.left.color.upper() == "#0000FF" + ) + assert ( + border.right + and border.right.style.value == "dashed" + and border.right.color.upper() == "#FFFF00" + ) + opxl.close_workbook(rb) + + def test_diagonal_border_roundtrip( + self, poi: ApachePoiAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + from excelbench.models import BorderEdge, BorderInfo, BorderStyle + + path = tmp_path / "poi_diag_border.xlsx" + wb = poi.create_workbook() + poi.add_sheet(wb, "S") + poi.write_cell_value(wb, "S", "B2", CellValue(type=CellType.STRING, value="Diag")) + poi.write_cell_border( + wb, + "S", + "B2", + BorderInfo( + diagonal_up=BorderEdge(style=BorderStyle.THIN, color="#FF0000"), + diagonal_down=BorderEdge(style=BorderStyle.THIN, color="#0000FF"), + ), + ) + poi.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + border = opxl.read_cell_border(rb, "S", "B2") + assert border.diagonal_up and border.diagonal_up.style.value == "thin" + assert border.diagonal_down and border.diagonal_down.style.value == "thin" + opxl.close_workbook(rb) + + def test_stop_if_true_roundtrip( + self, poi: ApachePoiAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "poi_cf_stop.xlsx" + wb = poi.create_workbook() + poi.add_sheet(wb, "S") + for row in range(7, 10): + poi.write_cell_value(wb, "S", f"B{row}", CellValue(type=CellType.NUMBER, value=row - 6)) + poi.add_conditional_format( + wb, + "S", + { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "stop_if_true": True, + "format": {"bg_color": "#FF0000"}, + } + }, + ) + poi.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + rules = opxl.read_conditional_formats(rb, "S") + assert any( + rule.get("rule_type") == "cellIs" and rule.get("stop_if_true") is True for rule in rules + ) + opxl.close_workbook(rb) + + def test_text_format_roundtrip( + self, poi: ApachePoiAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "poi_text_format.xlsx" + wb = poi.create_workbook() + poi.add_sheet(wb, "S") + poi.write_cell_value(wb, "S", "B2", CellValue(type=CellType.STRING, value="Format")) + poi.write_cell_format( + wb, + "S", + "B2", + CellFormat( + bold=True, + italic=True, + underline="double", + strikethrough=True, + font_name="Arial", + font_size=16, + font_color="#FF0000", + ), + ) + poi.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + fmt = opxl.read_cell_format(rb, "S", "B2") + assert fmt.bold is True + assert fmt.italic is True + assert fmt.underline == "double" + assert fmt.strikethrough is True + assert fmt.font_name == "Arial" + assert float(fmt.font_size or 0) == 16.0 + assert fmt.font_color and fmt.font_color.upper() == "#FF0000" + opxl.close_workbook(rb) + + def test_alignment_roundtrip( + self, poi: ApachePoiAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "poi_alignment.xlsx" + wb = poi.create_workbook() + poi.add_sheet(wb, "S") + poi.write_cell_value(wb, "S", "B2", CellValue(type=CellType.STRING, value="Line 1\nLine 2")) + poi.write_cell_format( + wb, + "S", + "B2", + CellFormat(h_align="center", v_align="bottom", wrap=True, rotation=45, indent=2), + ) + poi.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + fmt = opxl.read_cell_format(rb, "S", "B2") + assert fmt.h_align == "center" + assert fmt.v_align == "bottom" + assert fmt.wrap is True + assert fmt.rotation == 45 + assert fmt.indent == 2 + opxl.close_workbook(rb) diff --git a/tests/test_cross_language_context_cli.py b/tests/test_cross_language_context_cli.py new file mode 100644 index 0000000..477572c --- /dev/null +++ b/tests/test_cross_language_context_cli.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import pytest +from typer.testing import CliRunner + +from excelbench.cli import _write_cross_language_index, app + + +def test_write_cross_language_index(tmp_path: Path) -> None: + _write_cross_language_index(tmp_path, ["apache-poi", "excelize"]) + index = tmp_path / "CONTEXT.md" + assert index.exists() + content = index.read_text() + assert "cross-language context" in content.lower() + assert "apache-poi" in content + assert "excelize" in content + + +def test_cross_language_context_exits_when_no_adapters( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + monkeypatch.setattr( + "excelbench.harness.adapters.get_all_adapters", + lambda: [], + ) + runner = CliRunner() + result = runner.invoke(app, ["cross-language-context", "--output", str(tmp_path)]) + assert result.exit_code == 1 + assert "No cross-language adapters available" in result.output + + +def test_cross_language_context_writes_results_and_context( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + class FakeAdapter: + def __init__(self, name: str) -> None: + self.name = name + + def fake_get_all_adapters() -> list[FakeAdapter]: + return [FakeAdapter("apache-poi"), FakeAdapter("excelize")] + + def fake_run_benchmark( + test_dir: Path, + adapters: list[FakeAdapter], + profile: str = "xlsx", + ) -> str: + return "fake-results" + + def fake_render_results(results: Any, output_dir: Path) -> None: + output_dir.mkdir(parents=True, exist_ok=True) + (output_dir / "README.md").write_text("rendered") + (output_dir / "results.json").write_text("{}") + (output_dir / "matrix.csv").write_text("feature,library") + + monkeypatch.setattr("excelbench.harness.adapters.get_all_adapters", fake_get_all_adapters) + monkeypatch.setattr("excelbench.harness.runner.run_benchmark", fake_run_benchmark) + monkeypatch.setattr("excelbench.results.render_results", fake_render_results) + + runner = CliRunner() + result = runner.invoke(app, ["cross-language-context", "--output", str(tmp_path)]) + assert result.exit_code == 0 + assert (tmp_path / "README.md").exists() + assert (tmp_path / "results.json").exists() + assert (tmp_path / "matrix.csv").exists() + assert (tmp_path / "CONTEXT.md").exists() diff --git a/tests/test_excelize_adapter.py b/tests/test_excelize_adapter.py new file mode 100644 index 0000000..60683b6 --- /dev/null +++ b/tests/test_excelize_adapter.py @@ -0,0 +1,318 @@ +"""Tests for the ExcelizeAdapter cross-language write path.""" + +from __future__ import annotations + +from pathlib import Path +from zipfile import ZipFile + +import pytest + +from excelbench.harness.adapters.excelize_adapter import ExcelizeAdapter +from excelbench.harness.adapters.openpyxl_adapter import OpenpyxlAdapter +from excelbench.models import BorderEdge, BorderInfo, BorderStyle, CellFormat, CellType, CellValue + + +@pytest.fixture +def opxl() -> OpenpyxlAdapter: + return OpenpyxlAdapter() + + +@pytest.fixture +def excelize() -> ExcelizeAdapter: + return ExcelizeAdapter() + + +class TestExcelizeAdapterInfo: + def test_name(self, excelize: ExcelizeAdapter) -> None: + assert excelize.info.name == "excelize" + + def test_capabilities(self, excelize: ExcelizeAdapter) -> None: + assert excelize.info.capabilities == {"write"} + assert excelize.can_write() + assert not excelize.can_read() + + +class TestExcelizeAvailability: + def test_save_raises_structured_error_when_helper_missing( + self, excelize: ExcelizeAdapter, monkeypatch: pytest.MonkeyPatch, tmp_path: Path + ) -> None: + class MissingTool: + name = "excelize" + command = ("missing-excelize-helper",) + + def is_available(self) -> bool: + return False + + def resolve_executable(self) -> None: + return None + + def resolved_command(self) -> None: + return None + + monkeypatch.setattr(ExcelizeAdapter, "tool", classmethod(lambda cls: MissingTool())) + wb = excelize.create_workbook() + excelize.add_sheet(wb, "S") + excelize.write_cell_value(wb, "S", "A1", CellValue(type=CellType.STRING, value="x")) + with pytest.raises(FileNotFoundError): + excelize.save_workbook(wb, tmp_path / "missing.xlsx") + + +@pytest.mark.skipif(not ExcelizeAdapter.is_available(), reason="Excelize helper is not available") +class TestExcelizeWriteRoundtrip: + def test_values_and_multiple_sheets( + self, excelize: ExcelizeAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "excelize_multi.xlsx" + wb = excelize.create_workbook() + excelize.add_sheet(wb, "Data") + excelize.add_sheet(wb, "More") + excelize.write_cell_value(wb, "Data", "A1", CellValue(type=CellType.STRING, value="hello")) + excelize.write_cell_value(wb, "Data", "B1", CellValue(type=CellType.NUMBER, value=42.5)) + excelize.write_cell_value( + wb, + "Data", + "C1", + CellValue(type=CellType.FORMULA, value="=B1*2", formula="=B1*2"), + ) + excelize.write_cell_value(wb, "More", "A1", CellValue(type=CellType.BOOLEAN, value=True)) + excelize.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + assert opxl.read_cell_value(rb, "Data", "A1").value == "hello" + assert opxl.read_cell_value(rb, "Data", "B1").value == 42.5 + assert opxl.read_cell_value(rb, "Data", "C1").type == CellType.FORMULA + assert opxl.read_cell_value(rb, "Data", "C1").formula == "=B1*2" + assert opxl.read_cell_value(rb, "More", "A1").value is True + opxl.close_workbook(rb) + + def test_blank_cell_roundtrip( + self, excelize: ExcelizeAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "excelize_blank.xlsx" + wb = excelize.create_workbook() + excelize.add_sheet(wb, "Data") + excelize.write_cell_value(wb, "Data", "A1", CellValue(type=CellType.BLANK, value=None)) + excelize.write_cell_value(wb, "Data", "A2", CellValue(type=CellType.STRING, value="")) + excelize.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + assert opxl.read_cell_value(rb, "Data", "A1").type == CellType.BLANK + assert opxl.read_cell_value(rb, "Data", "A2").type == CellType.BLANK + opxl.close_workbook(rb) + + def test_text_format_alignment_validation_hyperlink_comment_and_panes( + self, excelize: ExcelizeAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "excelize_features.xlsx" + wb = excelize.create_workbook() + excelize.add_sheet(wb, "S") + excelize.write_cell_value(wb, "S", "A1", CellValue(type=CellType.STRING, value="Styled")) + excelize.write_cell_format( + wb, + "S", + "A1", + CellFormat( + bold=True, + italic=True, + underline="double", + strikethrough=True, + font_name="Arial", + font_size=16, + font_color="#FF0000", + bg_color="#FFFF00", + h_align="center", + v_align="bottom", + wrap=True, + rotation=45, + indent=2, + ), + ) + excelize.write_cell_value(wb, "S", "B2", CellValue(type=CellType.STRING, value="Link")) + excelize.add_hyperlink( + wb, + "S", + { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": False, + } + }, + ) + excelize.add_comment( + wb, "S", {"comment": {"cell": "C3", "text": "note", "author": "ExcelBench"}} + ) + excelize.add_data_validation( + wb, + "S", + { + "validation": { + "range": "D4", + "validation_type": "list", + "formula1": '"Open,Closed"', + "allow_blank": True, + } + }, + ) + excelize.set_freeze_panes( + wb, + "S", + {"freeze": {"mode": "freeze", "x_split": 1, "y_split": 1, "top_left_cell": "B2"}}, + ) + excelize.merge_cells(wb, "S", "E1:F1") + excelize.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + fmt = opxl.read_cell_format(rb, "S", "A1") + assert fmt.bold is True + assert fmt.italic is True + assert fmt.underline == "double" + assert fmt.strikethrough is True + assert fmt.font_name == "Arial" + assert float(fmt.font_size or 0) == 16.0 + assert fmt.font_color and fmt.font_color.upper() == "#FF0000" + assert fmt.bg_color and fmt.bg_color.upper() == "#FFFF00" + assert fmt.h_align == "center" + assert fmt.v_align == "bottom" + assert fmt.wrap is True + assert fmt.rotation == 45 + assert fmt.indent == 2 + ws = rb["S"] + assert ws["B2"].hyperlink is not None + assert ws["C3"].comment is not None + assert len(list(ws.data_validations.dataValidation)) == 1 + assert ws.freeze_panes == "B2" + assert "E1:F1" in {str(rng) for rng in ws.merged_cells.ranges} + opxl.close_workbook(rb) + + def test_border_row_height_and_named_range_roundtrip( + self, excelize: ExcelizeAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "excelize_border_named.xlsx" + wb = excelize.create_workbook() + excelize.add_sheet(wb, "named_ranges") + excelize.write_cell_value( + wb, "named_ranges", "B2", CellValue(type=CellType.STRING, value="Bordered") + ) + excelize.write_cell_border( + wb, + "named_ranges", + "B2", + BorderInfo( + top=BorderEdge(style=BorderStyle.THIN, color="#FF0000"), + bottom=BorderEdge(style=BorderStyle.DOUBLE, color="#00FF00"), + left=BorderEdge(style=BorderStyle.MEDIUM, color="#0000FF"), + right=BorderEdge(style=BorderStyle.DASHED, color="#FFFF00"), + ), + ) + excelize.set_row_height(wb, "named_ranges", 2, 30) + excelize.set_column_width(wb, "named_ranges", "B", 20) + excelize.add_named_range( + wb, + "named_ranges", + { + "named_range": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + } + }, + ) + excelize.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + border = opxl.read_cell_border(rb, "named_ranges", "B2") + assert border.top and border.top.style.value == "thin" + assert border.bottom and border.bottom.style.value == "double" + ws = rb["named_ranges"] + assert round(ws.row_dimensions[2].height or 0) == 30 + assert round(ws.column_dimensions["B"].width or 0) == 20 + names = opxl.read_named_ranges(rb, "named_ranges") + assert any(item.get("name") == "SingleCell" for item in names) + opxl.close_workbook(rb) + + def test_conditional_format_roundtrip( + self, excelize: ExcelizeAdapter, opxl: OpenpyxlAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "excelize_cf.xlsx" + wb = excelize.create_workbook() + excelize.add_sheet(wb, "conditional_formatting") + for row in range(2, 7): + excelize.write_cell_value( + wb, + "conditional_formatting", + f"B{row}", + CellValue(type=CellType.NUMBER, value=row - 1), + ) + excelize.add_conditional_format( + wb, + "conditional_formatting", + { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "format": {"bg_color": "#FFFF00"}, + } + }, + ) + excelize.add_conditional_format( + wb, + "conditional_formatting", + { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": '=ISNUMBER(SEARCH("foo",B2))', + "format": {"bg_color": "#FF00FF"}, + } + }, + ) + excelize.save_workbook(wb, path) + + rb = opxl.open_workbook(path) + rules = opxl.read_conditional_formats(rb, "conditional_formatting") + assert any( + rule.get("rule_type") == "cellIs" and rule.get("formula") == "5" for rule in rules + ) + assert any(rule.get("rule_type") == "expression" for rule in rules) + opxl.close_workbook(rb) + + def test_tables_conditional_format_picture_and_pivot_parts( + self, excelize: ExcelizeAdapter, tmp_path: Path + ) -> None: + path = tmp_path / "excelize_structural.xlsx" + wb = excelize.create_workbook() + excelize.add_sheet(wb, "Data") + excelize.add_sheet(wb, "Pivot") + excelize.write_cell_value(wb, "Data", "A1", CellValue(type=CellType.STRING, value="Region")) + excelize.write_cell_value(wb, "Data", "B1", CellValue(type=CellType.STRING, value="Sales")) + excelize.write_cell_value(wb, "Data", "A2", CellValue(type=CellType.STRING, value="West")) + excelize.write_cell_value(wb, "Data", "B2", CellValue(type=CellType.NUMBER, value=120)) + excelize.write_cell_value(wb, "Data", "A3", CellValue(type=CellType.STRING, value="East")) + excelize.write_cell_value(wb, "Data", "B3", CellValue(type=CellType.NUMBER, value=95)) + excelize.add_table(wb, "Data", {"range": "A1:B3", "name": "SalesTable"}) + excelize.add_conditional_format(wb, "Data", {"range": "B2:B3", "type": "3_color_scale"}) + excelize.add_image(wb, "Data", {"cell": "D2", "name": "Pixel"}) + excelize.add_pivot_table( + wb, + "Pivot", + { + "data_range": "Data!A1:B3", + "range": "Pivot!A3:D8", + "name": "SalesPivot", + "rows": [{"name": "Region"}], + "data": [{"name": "Sales", "subtotal": "Sum"}], + }, + ) + excelize.save_workbook(wb, path) + + with ZipFile(path) as workbook: + names = set(workbook.namelist()) + assert "xl/tables/table1.xml" in names + assert "xl/pivotTables/pivotTable1.xml" in names + assert any(name.startswith("xl/media/") for name in names) + assert any(name.startswith("xl/drawings/") for name in names) diff --git a/tools/external-oracles/apache-poi/poi_oracle.py b/tools/external-oracles/apache-poi/poi_oracle.py index c477982..cb07ed0 100755 --- a/tools/external-oracles/apache-poi/poi_oracle.py +++ b/tools/external-oracles/apache-poi/poi_oracle.py @@ -4,10 +4,12 @@ from __future__ import annotations import json +import tempfile import shutil import subprocess import sys import zipfile +from base64 import b64encode from pathlib import Path from typing import Any @@ -25,6 +27,8 @@ def main() -> int: operation = request["operation"] if operation == "write_fixture": return _write_fixture(request) + if operation == "write_adapter_workbook": + return _write_adapter_workbook(request) if operation == "read_metadata": _write_json(_read_metadata(request)) return 0 @@ -77,6 +81,62 @@ def _write_fixture(request: JSONDict) -> int: return 0 +def _write_adapter_workbook(request: JSONDict) -> int: + output_path = request.get("output_path") + payload = request.get("payload") or {} + if not output_path: + _write_json( + { + "error": "apache_poi_oracle_failed", + "message": "write_adapter_workbook requires output_path", + } + ) + return 1 + java = _java_executable() + classpath = _classpath() + if java is None or classpath is None: + _write_json({"skipped": True, "notes": "Apache POI helper is not built"}) + return 0 + with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".spec", delete=False) as spec: + spec_path = Path(spec.name) + try: + spec.write(_serialize_adapter_payload(payload)) + finally: + spec.flush() + try: + completed = subprocess.run( + [ + java, + "-cp", + classpath, + "PoiOracle", + "write_adapter_workbook", + request.get("fixture_id", "apache-poi-adapter"), + output_path, + str(spec_path), + ], + text=True, + capture_output=True, + check=False, + timeout=180, + ) + finally: + spec_path.unlink(missing_ok=True) + if completed.returncode != 0: + _write_json( + { + "error": "apache_poi_oracle_failed", + "message": completed.stderr.strip() or completed.stdout.strip(), + } + ) + return completed.returncode + json_line = next( + line for line in reversed(completed.stdout.splitlines()) if line.strip().startswith("{") + ) + _write_json(json.loads(json_line)) + return 0 + + def _read_metadata(request: JSONDict) -> JSONDict: input_path = request.get("input_path") or request.get("output_path") if not input_path: @@ -118,6 +178,200 @@ def _count(names: list[str], fragment: str) -> int: return sum(1 for name in names if fragment in name) +def _encode(value: str) -> str: + return b64encode(value.encode("utf-8")).decode("ascii") + + +def _serialize_adapter_payload(payload: JSONDict) -> str: + lines: list[str] = [] + for sheet in payload.get("sheets", []): + lines.append(f"SHEET\t{_encode(str(sheet))}") + for entry in payload.get("values", []): + lines.append( + "\t".join( + [ + "VALUE", + _encode(str(entry["sheet"])), + _encode(str(entry["cell"])), + _encode(str(entry["type"])), + _encode(str(entry.get("value", ""))), + _encode(str(entry.get("formula", ""))), + ] + ) + ) + for entry in payload.get("formats", []): + lines.append( + "\t".join( + [ + "FORMAT", + _encode(str(entry["sheet"])), + _encode(str(entry["cell"])), + "1" if entry.get("bold") else "0", + "1" if entry.get("italic") else "0", + _encode(str(entry.get("underline") or "")), + "1" if entry.get("strikethrough") else "0", + _encode(str(entry.get("font_name") or "")), + _encode(str(entry.get("font_size") or "")), + _encode(str(entry.get("font_color") or "")), + _encode(str(entry.get("bg_color") or "")), + _encode(str(entry.get("number_format") or "")), + _encode(str(entry.get("h_align") or "")), + _encode(str(entry.get("v_align") or "")), + "1" if entry.get("wrap") else "0", + _encode(str(entry.get("rotation") or "")), + _encode(str(entry.get("indent") or "")), + ] + ) + ) + for entry in payload.get("borders", []): + lines.append( + "\t".join( + [ + "BORDER", + _encode(str(entry["sheet"])), + _encode(str(entry["cell"])), + _encode(json.dumps(entry["border"], sort_keys=True, separators=(",", ":"))), + ] + ) + ) + for entry in payload.get("conditional_formats", []): + lines.append( + "\t".join( + [ + "CF", + _encode(str(entry["sheet"])), + _encode(str(entry.get("range") or "")), + _encode(str(entry.get("rule_type") or "")), + _encode(str(entry.get("operator") or "")), + _encode(str(entry.get("formula") or "")), + "1" if entry.get("stop_if_true") else "0", + _encode(str((entry.get("format") or {}).get("bg_color") or "")), + ] + ) + ) + for entry in payload.get("row_heights", []): + lines.append( + "\t".join( + [ + "ROW_HEIGHT", + _encode(str(entry["sheet"])), + str(entry["row"]), + str(entry["height"]), + ] + ) + ) + for entry in payload.get("column_widths", []): + lines.append( + "\t".join( + [ + "COL_WIDTH", + _encode(str(entry["sheet"])), + _encode(str(entry["column"])), + str(entry["width"]), + ] + ) + ) + for entry in payload.get("merges", []): + lines.append( + "\t".join(["MERGE", _encode(str(entry["sheet"])), _encode(str(entry["range"]))]) + ) + for entry in payload.get("validations", []): + lines.append( + "\t".join( + [ + "VALIDATION", + _encode(str(entry["sheet"])), + _encode(str(entry.get("range") or entry.get("cell") or "")), + _encode(str(entry.get("validation_type") or "")), + _encode(str(entry.get("operator") or "")), + _encode(str(entry.get("formula1") or "")), + _encode(str(entry.get("formula2") or "")), + "1" if entry.get("allow_blank", True) else "0", + _encode(str(entry.get("error_title") or "")), + _encode(str(entry.get("error") or "")), + ] + ) + ) + for entry in payload.get("hyperlinks", []): + lines.append( + "\t".join( + [ + "HYPERLINK", + _encode(str(entry["sheet"])), + _encode(str(entry["cell"])), + _encode(str(entry.get("target") or entry.get("url") or "")), + _encode(str(entry.get("display") or entry.get("label") or "")), + _encode(str(entry.get("tooltip") or "")), + "1" if entry.get("internal") else "0", + ] + ) + ) + for entry in payload.get("comments", []): + lines.append( + "\t".join( + [ + "COMMENT", + _encode(str(entry["sheet"])), + _encode(str(entry["cell"])), + _encode(str(entry.get("text") or "")), + _encode(str(entry.get("author") or "ExcelBench")), + ] + ) + ) + for entry in payload.get("images", []): + lines.append( + "\t".join( + [ + "IMAGE", + _encode(str(entry["sheet"])), + _encode(str(entry.get("cell") or "")), + _encode(str(entry.get("path") or "")), + _encode(str(entry.get("anchor") or "")), + ] + ) + ) + for entry in payload.get("named_ranges", []): + lines.append( + "\t".join( + [ + "NAME", + _encode(str(entry["sheet"])), + _encode(str(entry.get("name") or "")), + _encode(str(entry.get("scope") or "workbook")), + _encode(str(entry.get("refers_to") or "")), + ] + ) + ) + for entry in payload.get("freeze_panes", []): + lines.append( + "\t".join( + [ + "FREEZE", + _encode(str(entry["sheet"])), + _encode(str(entry.get("mode") or "freeze")), + str(entry.get("x_split", 0)), + str(entry.get("y_split", 0)), + _encode(str(entry.get("top_left_cell") or "")), + ] + ) + ) + for entry in payload.get("tables", []): + lines.append( + "\t".join( + [ + "TABLE", + _encode(str(entry["sheet"])), + _encode(str(entry.get("ref") or entry.get("range") or "")), + _encode(str(entry.get("name") or "")), + _encode(str(entry.get("style") or "")), + "1" if entry.get("totals_row") else "0", + "1" if entry.get("autofilter") else "0", + ] + ) + ) + return "\n".join(lines) + ("\n" if lines else "") + + def _write_json(payload: JSONDict) -> None: print(json.dumps(payload, sort_keys=True)) diff --git a/tools/external-oracles/apache-poi/src/PoiOracle.java b/tools/external-oracles/apache-poi/src/PoiOracle.java index 680af35..6be6d03 100644 --- a/tools/external-oracles/apache-poi/src/PoiOracle.java +++ b/tools/external-oracles/apache-poi/src/PoiOracle.java @@ -1,9 +1,26 @@ import java.io.File; import java.io.FileOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.charset.StandardCharsets; +import java.sql.Timestamp; +import java.sql.Date; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.ComparisonOperator; +import org.apache.poi.ss.usermodel.ConditionalFormattingRule; import org.apache.poi.common.usermodel.HyperlinkType; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.FormulaError; +import org.apache.poi.ss.usermodel.PatternFormatting; +import org.apache.poi.ss.usermodel.SheetConditionalFormatting; import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; @@ -15,17 +32,28 @@ import org.apache.poi.ss.usermodel.DataValidationHelper; import org.apache.poi.ss.usermodel.Drawing; import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.Hyperlink; import org.apache.poi.ss.usermodel.RichTextString; import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.ss.util.CellReference; +import org.apache.poi.xssf.model.ThemesTable; +import org.apache.poi.xssf.model.StylesTable; +import org.apache.poi.xssf.usermodel.DefaultIndexedColorMap; +import org.apache.poi.xssf.usermodel.XSSFColor; import org.apache.poi.xssf.usermodel.XSSFCellStyle; +import org.apache.poi.xssf.usermodel.extensions.XSSFCellBorder; +import org.apache.poi.xssf.usermodel.extensions.XSSFCellBorder.BorderSide; +import org.apache.poi.xssf.usermodel.XSSFPatternFormatting; import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFHyperlink; import org.apache.poi.xssf.usermodel.XSSFRichTextString; +import org.apache.poi.xssf.usermodel.XSSFSheetConditionalFormatting; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFTable; import org.apache.poi.xssf.usermodel.XSSFWorkbook; @@ -33,6 +61,10 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumns; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableStyleInfo; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTConditionalFormatting; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCfRule; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTPane; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.STPaneState; public final class PoiOracle { private static final String PIXEL_PNG_BASE64 = @@ -45,12 +77,76 @@ public static void main(String[] args) throws Exception { System.out.println("OK"); return; } + if (args.length == 4 && "write_adapter_workbook".equals(args[0])) { + writeAdapterWorkbook(args[1], args[2], args[3]); + return; + } if (args.length != 3 || !"write_fixture".equals(args[0])) { throw new IllegalArgumentException("Usage: PoiOracle write_fixture "); } writeFixture(args[1], args[2]); } + private static void writeAdapterWorkbook(String fixtureId, String outputPath, String specPath) throws Exception { + File output = new File(outputPath); + File parent = output.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + + try (XSSFWorkbook workbook = new XSSFWorkbook()) { + workbook.getProperties().getCoreProperties().setCreator("ExcelBench Apache POI Adapter"); + List lines = Files.readAllLines(Path.of(specPath), StandardCharsets.UTF_8); + Map sheets = new HashMap<>(); + + if (lines.isEmpty()) { + XSSFSheet defaultSheet = workbook.createSheet("Sheet1"); + sheets.put("Sheet1", defaultSheet); + } + + for (String line : lines) { + if (line.isBlank()) { + continue; + } + String[] parts = line.split("\t", -1); + String op = parts[0]; + if ("SHEET".equals(op)) { + String sheetName = decode(parts[1]); + if (!sheets.containsKey(sheetName)) { + sheets.put(sheetName, workbook.createSheet(sheetName)); + } + } + } + if (sheets.isEmpty()) { + XSSFSheet defaultSheet = workbook.createSheet("Sheet1"); + sheets.put("Sheet1", defaultSheet); + } + + for (String line : lines) { + if (line.isBlank()) { + continue; + } + String[] parts = line.split("\t", -1); + String op = parts[0]; + if ("SHEET".equals(op)) { + continue; + } + applySpecLine(workbook, sheets, op, parts); + } + + try (FileOutputStream stream = new FileOutputStream(output)) { + workbook.write(stream); + } + } + + System.out.println("{" + + "\"fixture_id\":\"" + json(fixtureId) + "\"," + + "\"operation\":\"write_adapter_workbook\"," + + "\"output_path\":\"" + json(outputPath) + "\"," + + "\"tool\":\"apache-poi\"" + + "}"); + } + private static void writeFixture(String fixtureId, String outputPath) throws Exception { File output = new File(outputPath); File parent = output.getParentFile(); @@ -237,6 +333,601 @@ private static void addImage(XSSFWorkbook workbook, XSSFSheet sheet) { drawing.createPicture(anchor, picture); } + private static void applySpecLine( + XSSFWorkbook workbook, + Map sheets, + String op, + String[] parts + ) { + switch (op) { + case "VALUE": + applyValue(workbook, sheets.get(decode(parts[1])), decode(parts[2]), decode(parts[3]), decode(parts[4]), decode(parts[5])); + return; + case "FORMAT": + applyFormat(workbook, sheets.get(decode(parts[1])), decode(parts[2]), parts); + return; + case "BORDER": + applyBorder(workbook, sheets.get(decode(parts[1])), decode(parts[2]), decode(parts[3])); + return; + case "CF": + applyConditionalFormat(sheets.get(decode(parts[1])), decode(parts[2]), decode(parts[3]), decode(parts[4]), decode(parts[5]), "1".equals(parts[6]), decode(parts[7])); + return; + case "ROW_HEIGHT": + applyRowHeight(sheets.get(decode(parts[1])), Integer.parseInt(parts[2]), Float.parseFloat(parts[3])); + return; + case "COL_WIDTH": + applyColumnWidth(sheets.get(decode(parts[1])), decode(parts[2]), Float.parseFloat(parts[3])); + return; + case "MERGE": + sheets.get(decode(parts[1])).addMergedRegion(CellRangeAddress.valueOf(decode(parts[2]))); + return; + case "VALIDATION": + applyValidation(sheets.get(decode(parts[1])), decode(parts[2]), decode(parts[3]), decode(parts[4]), decode(parts[5]), decode(parts[6]), "1".equals(parts[7]), decode(parts[8]), decode(parts[9])); + return; + case "HYPERLINK": + applyHyperlink(workbook, sheets.get(decode(parts[1])), decode(parts[2]), decode(parts[3]), decode(parts[4]), decode(parts[5]), "1".equals(parts[6])); + return; + case "COMMENT": + applyComment(workbook, sheets.get(decode(parts[1])), decode(parts[2]), decode(parts[3]), decode(parts[4])); + return; + case "IMAGE": + applyImage(workbook, sheets.get(decode(parts[1])), decode(parts[2]), decode(parts[3])); + return; + case "NAME": + applyNamedRange(workbook, decode(parts[1]), decode(parts[2]), decode(parts[3]), decode(parts[4])); + return; + case "FREEZE": + applyPane(sheets.get(decode(parts[1])), decode(parts[2]), Integer.parseInt(parts[3]), Integer.parseInt(parts[4]), decode(parts[5])); + return; + case "TABLE": + applyTable(sheets.get(decode(parts[1])), decode(parts[2]), decode(parts[3]), decode(parts[4]), "1".equals(parts[5]), "1".equals(parts[6])); + return; + default: + throw new IllegalArgumentException("Unsupported spec op: " + op); + } + } + + private static void applyValue(XSSFWorkbook workbook, XSSFSheet sheet, String address, String type, String rawValue, String rawFormula) { + Cell cell = getCell(sheet, address); + if ("blank".equals(type)) { + cell.setBlank(); + return; + } + if ("string".equals(type)) { + cell.setCellValue(rawValue); + return; + } + if ("number".equals(type)) { + cell.setCellValue(Double.parseDouble(rawValue)); + return; + } + if ("boolean".equals(type)) { + cell.setCellValue(Boolean.parseBoolean(rawValue)); + return; + } + if ("formula".equals(type)) { + String formula = rawFormula.isEmpty() ? rawValue : rawFormula; + if (formula.startsWith("=")) { + formula = formula.substring(1); + } + cell.setCellFormula(formula); + return; + } + if ("error".equals(type)) { + FormulaError error = FormulaError.forString(rawValue); + cell.setCellErrorValue(error.getCode()); + return; + } + if ("date".equals(type)) { + cell.setCellValue(Date.valueOf(LocalDate.parse(rawValue))); + XSSFCellStyle style = (XSSFCellStyle) workbook.createCellStyle(); + style.setDataFormat(workbook.getCreationHelper().createDataFormat().getFormat("yyyy-mm-dd")); + cell.setCellStyle(style); + return; + } + if ("datetime".equals(type)) { + String normalized = rawValue.contains("T") ? rawValue : rawValue.replace(" ", "T"); + normalized = normalized.replace("Z", ""); + cell.setCellValue(Timestamp.valueOf(LocalDateTime.parse(normalized))); + XSSFCellStyle style = (XSSFCellStyle) workbook.createCellStyle(); + style.setDataFormat(workbook.getCreationHelper().createDataFormat().getFormat("yyyy-mm-dd hh:mm:ss")); + cell.setCellStyle(style); + return; + } + throw new IllegalArgumentException("Unsupported cell type: " + type); + } + + private static void applyFormat(XSSFWorkbook workbook, XSSFSheet sheet, String address, String[] parts) { + Cell cell = getCell(sheet, address); + XSSFCellStyle style = cloneStyle(workbook, cell); + XSSFFont font = cloneFont(workbook, style); + if ("1".equals(parts[3])) { + font.setBold(true); + } + if ("1".equals(parts[4])) { + font.setItalic(true); + } + String underline = decode(parts[5]); + if ("single".equals(underline)) { + font.setUnderline(Font.U_SINGLE); + } else if ("double".equals(underline)) { + font.setUnderline(Font.U_DOUBLE); + } + if ("1".equals(parts[6])) { + font.setStrikeout(true); + } + String fontName = decode(parts[7]); + if (!fontName.isEmpty()) { + font.setFontName(fontName); + } + String fontSize = decode(parts[8]); + if (!fontSize.isEmpty()) { + font.setFontHeight(Double.parseDouble(fontSize)); + } + applyFontColor(font, decode(parts[9])); + applyFillColor(style, decode(parts[10])); + String numFmt = decode(parts[11]); + if (!numFmt.isEmpty()) { + style.setDataFormat(workbook.getCreationHelper().createDataFormat().getFormat(numFmt)); + } + applyHorizontalAlignment(style, decode(parts[12])); + applyVerticalAlignment(style, decode(parts[13])); + if ("1".equals(parts[14])) { + style.setWrapText(true); + } + String rotation = decode(parts[15]); + if (!rotation.isEmpty()) { + style.setRotation(Short.parseShort(rotation)); + } + String indent = decode(parts[16]); + if (!indent.isEmpty()) { + style.setIndention(Short.parseShort(indent)); + } + style.setFont(font); + cell.setCellStyle(style); + } + + private static void applyBorder(XSSFWorkbook workbook, XSSFSheet sheet, String address, String borderJson) { + Cell cell = getCell(sheet, address); + XSSFCellStyle style = cloneStyle(workbook, cell); + applyBorderEdge(style, borderJson, "top"); + applyBorderEdge(style, borderJson, "bottom"); + applyBorderEdge(style, borderJson, "left"); + applyBorderEdge(style, borderJson, "right"); + applyDiagonalBorders(workbook, style, borderJson); + cell.setCellStyle(style); + } + + private static void applyConditionalFormat(XSSFSheet sheet, String rangeRef, String ruleType, String operator, String formula, boolean stopIfTrue, String bgColor) { + SheetConditionalFormatting scf = sheet.getSheetConditionalFormatting(); + CellRangeAddress[] regions = new CellRangeAddress[] {CellRangeAddress.valueOf(rangeRef)}; + ConditionalFormattingRule rule; + if ("cellIs".equals(ruleType)) { + rule = scf.createConditionalFormattingRule(mapComparisonOperator(operator), formula); + } else if ("expression".equals(ruleType)) { + rule = scf.createConditionalFormattingRule(formula); + } else if ("dataBar".equals(ruleType) && scf instanceof XSSFSheetConditionalFormatting) { + XSSFColor color = colorFromHex(bgColor == null || bgColor.isEmpty() ? "#638EC6" : bgColor); + rule = ((XSSFSheetConditionalFormatting) scf).createConditionalFormattingRule(color); + } else if ("colorScale".equals(ruleType) && scf instanceof XSSFSheetConditionalFormatting) { + rule = ((XSSFSheetConditionalFormatting) scf).createConditionalFormattingColorScaleRule(); + } else { + throw new IllegalArgumentException("Unsupported conditional formatting rule type: " + ruleType); + } + if (bgColor != null && !bgColor.isEmpty()) { + PatternFormatting pattern = rule.createPatternFormatting(); + XSSFColor fillColor = colorFromHex(bgColor); + if (pattern instanceof XSSFPatternFormatting) { + ((XSSFPatternFormatting) pattern).setFillBackgroundColor(fillColor); + ((XSSFPatternFormatting) pattern).setFillForegroundColor(fillColor); + } else { + short colorIndex = rgbToClosestIndexedColor(bgColor); + pattern.setFillBackgroundColor(colorIndex); + pattern.setFillForegroundColor(colorIndex); + } + pattern.setFillPattern(PatternFormatting.SOLID_FOREGROUND); + } + scf.addConditionalFormatting(regions, rule); + if (stopIfTrue) { + CTConditionalFormatting[] blocks = sheet.getCTWorksheet().getConditionalFormattingArray(); + if (blocks.length > 0) { + CTConditionalFormatting block = blocks[blocks.length - 1]; + CTCfRule[] cfRules = block.getCfRuleArray(); + if (cfRules.length > 0) { + cfRules[cfRules.length - 1].setStopIfTrue(true); + } + } + } + } + + private static void applyRowHeight(XSSFSheet sheet, int rowIndex, float height) { + Row row = sheet.getRow(rowIndex - 1); + if (row == null) { + row = sheet.createRow(rowIndex - 1); + } + row.setHeightInPoints(height); + } + + private static void applyColumnWidth(XSSFSheet sheet, String column, float width) { + sheet.setColumnWidth(columnToIndex(column), Math.round(width * 256)); + } + + private static void applyValidation(XSSFSheet sheet, String address, String validationType, String operator, String formula1, String formula2, boolean allowBlank, String errorTitle, String errorMessage) { + CellReference ref = new CellReference(address); + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint; + if ("list".equals(validationType)) { + if (formula1.startsWith("\"") && formula1.endsWith("\"")) { + String body = formula1.substring(1, formula1.length() - 1); + constraint = helper.createExplicitListConstraint(body.split(",")); + } else { + constraint = helper.createFormulaListConstraint(formula1); + } + } else if ("custom".equals(validationType)) { + constraint = helper.createCustomConstraint(formula1); + } else if ("whole".equals(validationType)) { + int poiOperator = "between".equals(operator) ? DataValidationConstraint.OperatorType.BETWEEN : DataValidationConstraint.OperatorType.IGNORED; + constraint = helper.createIntegerConstraint(poiOperator, formula1, formula2); + } else { + return; + } + CellRangeAddressList range = new CellRangeAddressList(ref.getRow(), ref.getRow(), ref.getCol(), ref.getCol()); + DataValidation validation = helper.createValidation(constraint, range); + validation.setEmptyCellAllowed(allowBlank); + validation.setShowErrorBox(errorTitle != null && !errorTitle.isEmpty()); + if (errorTitle != null && !errorTitle.isEmpty()) { + validation.createErrorBox(errorTitle, errorMessage == null ? "" : errorMessage); + } + sheet.addValidationData(validation); + } + + private static void applyHyperlink(XSSFWorkbook workbook, XSSFSheet sheet, String address, String target, String label, String tooltip, boolean internal) { + CreationHelper helper = workbook.getCreationHelper(); + Hyperlink link = helper.createHyperlink(internal ? HyperlinkType.DOCUMENT : HyperlinkType.URL); + link.setAddress(target); + if (tooltip != null && !tooltip.isEmpty() && link instanceof XSSFHyperlink) { + ((XSSFHyperlink) link).setTooltip(tooltip); + } + Cell cell = getCell(sheet, address); + cell.setCellValue(label.isEmpty() ? target : label); + cell.setHyperlink(link); + } + + private static void applyComment(XSSFWorkbook workbook, XSSFSheet sheet, String address, String text, String author) { + CreationHelper helper = workbook.getCreationHelper(); + Drawing drawing = sheet.createDrawingPatriarch(); + ClientAnchor anchor = helper.createClientAnchor(); + Cell cell = getCell(sheet, address); + anchor.setCol1(cell.getColumnIndex() + 1); + anchor.setCol2(cell.getColumnIndex() + 4); + anchor.setRow1(cell.getRowIndex()); + anchor.setRow2(cell.getRowIndex() + 3); + Comment comment = drawing.createCellComment(anchor); + comment.setString(helper.createRichTextString(text)); + comment.setAuthor(author); + cell.setCellComment(comment); + } + + private static void applyImage(XSSFWorkbook workbook, XSSFSheet sheet, String address, String path) { + if (path == null || path.isEmpty()) { + return; + } + byte[] imageBytes; + try { + imageBytes = Files.readAllBytes(Path.of(path)); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to read image path: " + path, e); + } + int pictureType = pictureTypeFromPath(path); + int pictureIdx = workbook.addPicture(imageBytes, pictureType); + CreationHelper helper = workbook.getCreationHelper(); + Drawing drawing = sheet.createDrawingPatriarch(); + ClientAnchor anchor = helper.createClientAnchor(); + CellReference ref = new CellReference(address); + anchor.setCol1(ref.getCol()); + anchor.setRow1(ref.getRow()); + anchor.setCol2(ref.getCol() + 2); + anchor.setRow2(ref.getRow() + 3); + drawing.createPicture(anchor, pictureIdx); + } + + private static void applyPane(XSSFSheet sheet, String mode, int xSplit, int ySplit, String topLeftCell) { + if ("split".equals(mode)) { + if (sheet.getCTWorksheet().getSheetViews() == null || sheet.getCTWorksheet().getSheetViews().sizeOfSheetViewArray() == 0) { + sheet.getCTWorksheet().addNewSheetViews().addNewSheetView(); + } + CTPane pane = sheet.getCTWorksheet().getSheetViews().getSheetViewArray(0).isSetPane() + ? sheet.getCTWorksheet().getSheetViews().getSheetViewArray(0).getPane() + : sheet.getCTWorksheet().getSheetViews().getSheetViewArray(0).addNewPane(); + pane.setState(STPaneState.SPLIT); + pane.setXSplit(xSplit); + pane.setYSplit(ySplit); + if (topLeftCell != null && !topLeftCell.isEmpty()) { + pane.setTopLeftCell(topLeftCell); + } + return; + } + sheet.createFreezePane(xSplit, ySplit); + } + + private static void applyNamedRange(XSSFWorkbook workbook, String sheetName, String name, String scope, String refersTo) { + if (name == null || name.isEmpty() || refersTo == null || refersTo.isEmpty()) { + return; + } + org.apache.poi.ss.usermodel.Name definedName = workbook.createName(); + definedName.setNameName(name); + definedName.setRefersToFormula(refersTo); + if ("sheet".equals(scope)) { + int sheetIndex = workbook.getSheetIndex(sheetName); + if (sheetIndex >= 0) { + definedName.setSheetIndex(sheetIndex); + } + } + } + + private static void applyTable(XSSFSheet sheet, String ref, String name, String styleName, boolean totalsRow, boolean autoFilter) { + AreaReference area = new AreaReference(ref, SpreadsheetVersion.EXCEL2007); + XSSFTable table = sheet.createTable(area); + CTTable ctTable = table.getCTTable(); + ctTable.setId(1); + ctTable.setName(name); + ctTable.setDisplayName(name); + ctTable.setRef(ref); + ctTable.setTotalsRowShown(totalsRow); + if (totalsRow) { + ctTable.setTotalsRowCount(1); + } + if (autoFilter) { + ctTable.addNewAutoFilter().setRef(ref); + } + if (styleName != null && !styleName.isEmpty()) { + CTTableStyleInfo style = ctTable.isSetTableStyleInfo() ? ctTable.getTableStyleInfo() : ctTable.addNewTableStyleInfo(); + style.setName(styleName); + style.setShowRowStripes(true); + } + } + + private static XSSFCellStyle cloneStyle(XSSFWorkbook workbook, Cell cell) { + XSSFCellStyle style = (XSSFCellStyle) workbook.createCellStyle(); + if (cell.getCellStyle() != null) { + style.cloneStyleFrom(cell.getCellStyle()); + } + return style; + } + + private static XSSFFont cloneFont(XSSFWorkbook workbook, XSSFCellStyle style) { + XSSFFont font = workbook.createFont(); + if (style.getFontIndexAsInt() > 0) { + XSSFFont existing = workbook.getFontAt(style.getFontIndexAsInt()); + font.setBold(existing.getBold()); + font.setItalic(existing.getItalic()); + font.setFontHeight(existing.getFontHeight()); + font.setFontName(existing.getFontName()); + font.setUnderline(existing.getUnderline()); + if (existing.getXSSFColor() != null) { + font.setColor(existing.getXSSFColor()); + } + } + return font; + } + + private static void applyFontColor(XSSFFont font, String hex) { + if (hex == null || hex.isEmpty()) { + return; + } + font.setColor(colorFromHex(hex)); + } + + private static void applyFillColor(XSSFCellStyle style, String hex) { + if (hex == null || hex.isEmpty()) { + return; + } + style.setFillForegroundColor(colorFromHex(hex)); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + } + + private static void applyHorizontalAlignment(XSSFCellStyle style, String value) { + if (value == null || value.isEmpty()) { + return; + } + if ("left".equals(value)) { + style.setAlignment(HorizontalAlignment.LEFT); + } else if ("center".equals(value)) { + style.setAlignment(HorizontalAlignment.CENTER); + } else if ("right".equals(value)) { + style.setAlignment(HorizontalAlignment.RIGHT); + } else if ("justify".equals(value)) { + style.setAlignment(HorizontalAlignment.JUSTIFY); + } + } + + private static void applyVerticalAlignment(XSSFCellStyle style, String value) { + if (value == null || value.isEmpty()) { + return; + } + if ("top".equals(value)) { + style.setVerticalAlignment(VerticalAlignment.TOP); + } else if ("center".equals(value)) { + style.setVerticalAlignment(VerticalAlignment.CENTER); + } else if ("bottom".equals(value)) { + style.setVerticalAlignment(VerticalAlignment.BOTTOM); + } + } + + private static void applyBorderEdge(XSSFCellStyle style, String borderJson, String edgeName) { + String marker = "\"" + edgeName + "\":{"; + if (!borderJson.contains(marker)) { + return; + } + String styleValue = extractBorderStyle(borderJson, edgeName); + BorderStyle poiStyle = mapBorderStyle(styleValue); + String colorValue = extractBorderColor(borderJson, edgeName); + if ("top".equals(edgeName)) { + style.setBorderTop(poiStyle); + if (colorValue != null) { + style.setTopBorderColor(colorFromHex(colorValue)); + } + } else if ("bottom".equals(edgeName)) { + style.setBorderBottom(poiStyle); + if (colorValue != null) { + style.setBottomBorderColor(colorFromHex(colorValue)); + } + } else if ("left".equals(edgeName)) { + style.setBorderLeft(poiStyle); + if (colorValue != null) { + style.setLeftBorderColor(colorFromHex(colorValue)); + } + } else if ("right".equals(edgeName)) { + style.setBorderRight(poiStyle); + if (colorValue != null) { + style.setRightBorderColor(colorFromHex(colorValue)); + } + } + } + + private static void applyDiagonalBorders(XSSFWorkbook workbook, XSSFCellStyle style, String borderJson) { + String upStyle = extractBorderStyle(borderJson, "diagonal_up"); + String downStyle = extractBorderStyle(borderJson, "diagonal_down"); + String upColor = extractBorderColor(borderJson, "diagonal_up"); + String downColor = extractBorderColor(borderJson, "diagonal_down"); + boolean hasUp = upStyle != null && !"none".equals(upStyle); + boolean hasDown = downStyle != null && !"none".equals(downStyle); + if (!hasUp && !hasDown) { + return; + } + + StylesTable styles = workbook.getStylesSource(); + XSSFCellBorder border = new XSSFCellBorder(); + border.setBorderStyle(BorderSide.TOP, style.getBorderTop()); + border.setBorderStyle(BorderSide.BOTTOM, style.getBorderBottom()); + border.setBorderStyle(BorderSide.LEFT, style.getBorderLeft()); + border.setBorderStyle(BorderSide.RIGHT, style.getBorderRight()); + XSSFColor topColor = style.getTopBorderXSSFColor(); + XSSFColor bottomColor = style.getBottomBorderXSSFColor(); + XSSFColor leftColor = style.getLeftBorderXSSFColor(); + XSSFColor rightColor = style.getRightBorderXSSFColor(); + if (topColor != null) border.setBorderColor(BorderSide.TOP, topColor); + if (bottomColor != null) border.setBorderColor(BorderSide.BOTTOM, bottomColor); + if (leftColor != null) border.setBorderColor(BorderSide.LEFT, leftColor); + if (rightColor != null) border.setBorderColor(BorderSide.RIGHT, rightColor); + + String diagonalStyle = hasUp ? upStyle : downStyle; + border.setBorderStyle(BorderSide.DIAGONAL, mapBorderStyle(diagonalStyle)); + String diagonalColor = upColor != null ? upColor : downColor; + if (diagonalColor != null) { + border.setBorderColor(BorderSide.DIAGONAL, colorFromHex(diagonalColor)); + } + border.getCTBorder().setDiagonalUp(hasUp); + border.getCTBorder().setDiagonalDown(hasDown); + + long borderId = styles.putBorder(border); + style.getCoreXf().setBorderId(borderId); + style.getCoreXf().setApplyBorder(true); + } + + private static String extractBorderStyle(String borderJson, String edgeName) { + String marker = "\"" + edgeName + "\":{"; + int start = borderJson.indexOf(marker); + if (start < 0) { + return "none"; + } + int styleIndex = borderJson.indexOf("\"style\":\"", start); + if (styleIndex < 0) { + return "none"; + } + int valueStart = styleIndex + 9; + int valueEnd = borderJson.indexOf('"', valueStart); + if (valueEnd < 0) { + return "none"; + } + return borderJson.substring(valueStart, valueEnd); + } + + private static String extractBorderColor(String borderJson, String edgeName) { + String marker = "\"" + edgeName + "\":{"; + int start = borderJson.indexOf(marker); + if (start < 0) { + return null; + } + int colorIndex = borderJson.indexOf("\"color\":\"", start); + if (colorIndex < 0) { + return null; + } + int valueStart = colorIndex + 9; + int valueEnd = borderJson.indexOf('"', valueStart); + if (valueEnd < 0) { + return null; + } + return borderJson.substring(valueStart, valueEnd); + } + + private static BorderStyle mapBorderStyle(String style) { + if ("thin".equals(style)) return BorderStyle.THIN; + if ("medium".equals(style)) return BorderStyle.MEDIUM; + if ("thick".equals(style)) return BorderStyle.THICK; + if ("double".equals(style)) return BorderStyle.DOUBLE; + if ("dashed".equals(style)) return BorderStyle.DASHED; + if ("dotted".equals(style)) return BorderStyle.DOTTED; + if ("hair".equals(style)) return BorderStyle.HAIR; + if ("mediumDashed".equals(style)) return BorderStyle.MEDIUM_DASHED; + if ("dashDot".equals(style)) return BorderStyle.DASH_DOT; + if ("mediumDashDot".equals(style)) return BorderStyle.MEDIUM_DASH_DOT; + if ("dashDotDot".equals(style)) return BorderStyle.DASH_DOT_DOT; + if ("mediumDashDotDot".equals(style)) return BorderStyle.MEDIUM_DASH_DOT_DOT; + if ("slantDashDot".equals(style)) return BorderStyle.SLANTED_DASH_DOT; + return BorderStyle.NONE; + } + + private static int columnToIndex(String column) { + int col = 0; + for (int i = 0; i < column.length(); i++) { + col = col * 26 + (column.charAt(i) - 'A' + 1); + } + return col - 1; + } + + private static XSSFColor colorFromHex(String hex) { + String normalized = hex.startsWith("#") ? hex.substring(1) : hex; + int r = Integer.parseInt(normalized.substring(0, 2), 16); + int g = Integer.parseInt(normalized.substring(2, 4), 16); + int b = Integer.parseInt(normalized.substring(4, 6), 16); + return new XSSFColor(new byte[] {(byte) r, (byte) g, (byte) b}, new DefaultIndexedColorMap()); + } + + private static String decode(String value) { + if (value == null || value.isEmpty()) { + return ""; + } + return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8); + } + + private static int pictureTypeFromPath(String path) { + String lower = path.toLowerCase(); + if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) { + return Workbook.PICTURE_TYPE_JPEG; + } + return Workbook.PICTURE_TYPE_PNG; + } + + private static byte mapComparisonOperator(String operator) { + if ("greaterThan".equals(operator)) return ComparisonOperator.GT; + if ("greaterThanOrEqual".equals(operator)) return ComparisonOperator.GE; + if ("lessThan".equals(operator)) return ComparisonOperator.LT; + if ("lessThanOrEqual".equals(operator)) return ComparisonOperator.LE; + if ("between".equals(operator)) return ComparisonOperator.BETWEEN; + if ("equal".equals(operator)) return ComparisonOperator.EQUAL; + return ComparisonOperator.NO_COMPARISON; + } + + private static short rgbToClosestIndexedColor(String hex) { + String normalized = hex.startsWith("#") ? hex.substring(1) : hex; + if (normalized.equalsIgnoreCase("FFFF00")) return org.apache.poi.ss.usermodel.IndexedColors.YELLOW.getIndex(); + if (normalized.equalsIgnoreCase("FF00FF")) return org.apache.poi.ss.usermodel.IndexedColors.VIOLET.getIndex(); + if (normalized.equalsIgnoreCase("FF0000")) return org.apache.poi.ss.usermodel.IndexedColors.RED.getIndex(); + if (normalized.equalsIgnoreCase("00FF00")) return org.apache.poi.ss.usermodel.IndexedColors.BRIGHT_GREEN.getIndex(); + return org.apache.poi.ss.usermodel.IndexedColors.YELLOW.getIndex(); + } + private static Cell getCell(XSSFSheet sheet, String address) { CellReference ref = new CellReference(address); Row row = sheet.getRow(ref.getRow()); diff --git a/tools/external-oracles/excelize/main.go b/tools/external-oracles/excelize/main.go index d7e2bc9..bd5b85c 100644 --- a/tools/external-oracles/excelize/main.go +++ b/tools/external-oracles/excelize/main.go @@ -1,6 +1,8 @@ package main import ( + "archive/zip" + "bytes" "encoding/base64" "encoding/json" "errors" @@ -9,7 +11,10 @@ import ( "io" "os" "path/filepath" + "regexp" "strconv" + "strings" + "time" "github.com/xuri/excelize/v2" ) @@ -25,7 +30,16 @@ type oracleRequest struct { type writePayload struct { Sheets []sheetSpec `json:"sheets"` Cells []cellSpec `json:"cells"` + Formats []formatSpec `json:"formats"` + Borders []borderSpec `json:"borders"` Columns []columnSpec `json:"columns"` + RowHeights []rowHeightSpec `json:"row_heights"` + Merges []mergeSpec `json:"merges"` + Validations []validationSpec `json:"validations"` + Hyperlinks []hyperlinkSpec `json:"hyperlinks"` + Comments []commentSpec `json:"comments"` + Panes []paneSpec `json:"panes"` + NamedRanges []namedRangeSpec `json:"named_ranges"` Tables []tableSpec `json:"tables"` ConditionalFormats []conditionalFormatSpec `json:"conditional_formats"` Charts []chartSpec `json:"charts"` @@ -46,6 +60,25 @@ type cellSpec struct { Formula string `json:"formula"` } +type formatSpec struct { + Sheet string `json:"sheet"` + Cell string `json:"cell"` + Bold *bool `json:"bold"` + Italic *bool `json:"italic"` + Underline string `json:"underline"` + Strikethrough *bool `json:"strikethrough"` + FontName string `json:"font_name"` + FontSize *float64 `json:"font_size"` + FontColor string `json:"font_color"` + BGColor string `json:"bg_color"` + NumberFormat string `json:"number_format"` + HAlign string `json:"h_align"` + VAlign string `json:"v_align"` + Wrap *bool `json:"wrap"` + Rotation *int `json:"rotation"` + Indent *int `json:"indent"` +} + type columnSpec struct { Sheet string `json:"sheet"` Start string `json:"start"` @@ -53,13 +86,76 @@ type columnSpec struct { Width float64 `json:"width"` } -type tableSpec struct { +type borderSpec struct { + Sheet string `json:"sheet"` + Cell string `json:"cell"` + Border map[string]interface{} `json:"border"` +} + +type rowHeightSpec struct { + Sheet string `json:"sheet"` + Row int `json:"row"` + Height float64 `json:"height"` +} + +type mergeSpec struct { + Sheet string `json:"sheet"` + Range string `json:"range"` +} + +type validationSpec struct { Sheet string `json:"sheet"` Range string `json:"range"` - Name string `json:"name"` - Style string `json:"style"` - ShowHeaderRow *bool `json:"show_header_row"` - ShowRowStripes *bool `json:"show_row_stripes"` + ValidationType string `json:"validation_type"` + Operator string `json:"operator"` + Formula1 string `json:"formula1"` + Formula2 string `json:"formula2"` + AllowBlank *bool `json:"allow_blank"` + ErrorTitle string `json:"error_title"` + Error string `json:"error"` +} + +type hyperlinkSpec struct { + Sheet string `json:"sheet"` + Cell string `json:"cell"` + Target string `json:"target"` + Display string `json:"display"` + Tooltip string `json:"tooltip"` + Internal bool `json:"internal"` +} + +type commentSpec struct { + Sheet string `json:"sheet"` + Cell string `json:"cell"` + Text string `json:"text"` + Author string `json:"author"` +} + +type paneSpec struct { + Sheet string `json:"sheet"` + Mode string `json:"mode"` + XSplit int `json:"x_split"` + YSplit int `json:"y_split"` + TopLeftCell string `json:"top_left_cell"` +} + +type namedRangeSpec struct { + Sheet string `json:"sheet"` + Name string `json:"name"` + Scope string `json:"scope"` + RefersTo string `json:"refers_to"` +} + +type tableSpec struct { + Sheet string `json:"sheet"` + Range string `json:"range"` + Name string `json:"name"` + Style string `json:"style"` + ShowHeaderRow *bool `json:"show_header_row"` + ShowRowStripes *bool `json:"show_row_stripes"` + TotalsRow bool `json:"totals_row"` + Columns []string `json:"columns"` + AutoFilter bool `json:"autofilter"` } type conditionalFormatSpec struct { @@ -68,6 +164,7 @@ type conditionalFormatSpec struct { Type string `json:"type"` Criteria string `json:"criteria"` Value string `json:"value"` + BGColor string `json:"bg_color"` MinType string `json:"min_type"` MidType string `json:"mid_type"` MaxType string `json:"max_type"` @@ -201,9 +298,36 @@ func writeFixture(output io.Writer, request oracleRequest) error { if err := applyCells(workbook, payload.Cells); err != nil { return err } + if err := applyFormats(workbook, payload.Formats); err != nil { + return err + } + if err := applyBorders(workbook, payload.Borders); err != nil { + return err + } if err := applyColumns(workbook, payload.Columns); err != nil { return err } + if err := applyRowHeights(workbook, payload.RowHeights); err != nil { + return err + } + if err := applyMerges(workbook, payload.Merges); err != nil { + return err + } + if err := applyDataValidations(workbook, payload.Validations); err != nil { + return err + } + if err := applyHyperlinks(workbook, payload.Hyperlinks); err != nil { + return err + } + if err := applyComments(workbook, payload.Comments); err != nil { + return err + } + if err := applyPanes(workbook, payload.Panes); err != nil { + return err + } + if err := applyNamedRanges(workbook, payload.NamedRanges); err != nil { + return err + } if err := applyTables(workbook, payload.Tables); err != nil { return err } @@ -229,6 +353,12 @@ func writeFixture(output io.Writer, request oracleRequest) error { if err := workbook.SaveAs(request.OutputPath); err != nil { return fmt.Errorf("save workbook: %w", err) } + if err := normalizeExcelizeConditionalFormats(request.OutputPath, payload.ConditionalFormats); err != nil { + return err + } + if err := normalizeExcelizeTables(request.OutputPath, payload.Tables); err != nil { + return err + } return json.NewEncoder(output).Encode(map[string]interface{}{ "fixture_id": request.FixtureID, @@ -238,6 +368,15 @@ func writeFixture(output io.Writer, request oracleRequest) error { "counts": map[string]int{ "sheets": len(workbook.GetSheetList()), "cells": len(payload.Cells), + "formats": len(payload.Formats), + "borders": len(payload.Borders), + "merges": len(payload.Merges), + "row_heights": len(payload.RowHeights), + "validations": len(payload.Validations), + "hyperlinks": len(payload.Hyperlinks), + "comments": len(payload.Comments), + "panes": len(payload.Panes), + "named_ranges": len(payload.NamedRanges), "tables": len(payload.Tables), "conditional_formats": len(payload.ConditionalFormats), "charts": len(payload.Charts), @@ -310,16 +449,36 @@ func applyCells(workbook *excelize.File, cells []cellSpec) error { if cell.Sheet == "" || cell.Cell == "" { return errors.New("cell entries require sheet and cell") } + if cell.Type == "blank" { + continue + } if cell.Type == "formula" || cell.Formula != "" { formula := cell.Formula if formula == "" { formula = stringify(cell.Value) } + formula = trimFormulaEquals(formula) if err := workbook.SetCellFormula(cell.Sheet, cell.Cell, formula); err != nil { return fmt.Errorf("set formula %s!%s: %w", cell.Sheet, cell.Cell, err) } continue } + if cell.Type == "date" || cell.Type == "datetime" { + parsed, err := parseTemporalCell(cell.Type, cell.Value) + if err != nil { + return fmt.Errorf("parse %s %s!%s: %w", cell.Type, cell.Sheet, cell.Cell, err) + } + if err := workbook.SetCellValue(cell.Sheet, cell.Cell, parsed); err != nil { + return fmt.Errorf("set %s %s!%s: %w", cell.Type, cell.Sheet, cell.Cell, err) + } + continue + } + if cell.Type == "error" { + if err := workbook.SetCellValue(cell.Sheet, cell.Cell, stringify(cell.Value)); err != nil { + return fmt.Errorf("set error %s!%s: %w", cell.Sheet, cell.Cell, err) + } + continue + } value := normalizeValue(cell.Value) if err := workbook.SetCellValue(cell.Sheet, cell.Cell, value); err != nil { return fmt.Errorf("set value %s!%s: %w", cell.Sheet, cell.Cell, err) @@ -328,6 +487,103 @@ func applyCells(workbook *excelize.File, cells []cellSpec) error { return nil } +func applyBorders(workbook *excelize.File, borders []borderSpec) error { + for _, spec := range borders { + if spec.Sheet == "" || spec.Cell == "" { + return errors.New("border entries require sheet and cell") + } + style := &excelize.Style{Border: buildBorders(spec.Border)} + styleID, err := workbook.NewStyle(style) + if err != nil { + return fmt.Errorf("new border style %s!%s: %w", spec.Sheet, spec.Cell, err) + } + if err := workbook.SetCellStyle(spec.Sheet, spec.Cell, spec.Cell, styleID); err != nil { + return fmt.Errorf("set border style %s!%s: %w", spec.Sheet, spec.Cell, err) + } + } + return nil +} + +func applyFormats(workbook *excelize.File, formats []formatSpec) error { + for _, spec := range formats { + if spec.Sheet == "" || spec.Cell == "" { + return errors.New("format entries require sheet and cell") + } + style := &excelize.Style{} + var hasStyle bool + if spec.Bold != nil || spec.Italic != nil || spec.Underline != "" || spec.Strikethrough != nil || spec.FontName != "" || spec.FontSize != nil || spec.FontColor != "" { + font := &excelize.Font{} + if spec.Bold != nil { + font.Bold = *spec.Bold + } + if spec.Italic != nil { + font.Italic = *spec.Italic + } + if spec.Underline != "" { + font.Underline = spec.Underline + } + if spec.Strikethrough != nil { + font.Strike = *spec.Strikethrough + } + if spec.FontName != "" { + font.Family = spec.FontName + } + if spec.FontSize != nil { + font.Size = *spec.FontSize + } + if spec.FontColor != "" { + font.Color = trimHash(spec.FontColor) + } + style.Font = font + hasStyle = true + } + if spec.BGColor != "" { + style.Fill = excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{trimHash(spec.BGColor)}} + hasStyle = true + } + if spec.HAlign != "" || spec.VAlign != "" || spec.Wrap != nil || spec.Rotation != nil || spec.Indent != nil { + align := &excelize.Alignment{} + if spec.HAlign != "" { + align.Horizontal = spec.HAlign + } + if spec.VAlign != "" { + align.Vertical = spec.VAlign + } + if spec.Wrap != nil { + align.WrapText = *spec.Wrap + } + if spec.Rotation != nil { + align.TextRotation = *spec.Rotation + } + if spec.Indent != nil { + align.Indent = *spec.Indent + } + style.Alignment = align + hasStyle = true + } + if spec.NumberFormat != "" { + if numFmt, ok := builtinNumFmt(spec.NumberFormat); ok { + style.NumFmt = numFmt + } else { + custom := spec.NumberFormat + style.CustomNumFmt = &custom + } + hasStyle = true + } + if !hasStyle { + continue + } + styleID, err := workbook.NewStyle(style) + if err != nil { + return fmt.Errorf("new style %s!%s: %w", spec.Sheet, spec.Cell, err) + } + if err := workbook.SetCellStyle(spec.Sheet, spec.Cell, spec.Cell, styleID); err != nil { + return fmt.Errorf("set style %s!%s: %w", spec.Sheet, spec.Cell, err) + } + } + return nil +} + func applyColumns(workbook *excelize.File, columns []columnSpec) error { for _, column := range columns { if column.Sheet == "" || column.Start == "" || column.End == "" { @@ -340,6 +596,141 @@ func applyColumns(workbook *excelize.File, columns []columnSpec) error { return nil } +func applyRowHeights(workbook *excelize.File, rows []rowHeightSpec) error { + for _, spec := range rows { + if spec.Sheet == "" || spec.Row <= 0 { + return errors.New("row height entries require sheet and row") + } + if err := workbook.SetRowHeight(spec.Sheet, spec.Row, spec.Height); err != nil { + return fmt.Errorf("set row height %s!%d: %w", spec.Sheet, spec.Row, err) + } + } + return nil +} + +func applyMerges(workbook *excelize.File, merges []mergeSpec) error { + for _, merge := range merges { + if merge.Sheet == "" || merge.Range == "" { + return errors.New("merge entries require sheet and range") + } + start, end, ok := splitRange(merge.Range) + if !ok { + return fmt.Errorf("invalid merge range %q", merge.Range) + } + if err := workbook.MergeCell(merge.Sheet, start, end); err != nil { + return fmt.Errorf("merge cells %s!%s: %w", merge.Sheet, merge.Range, err) + } + } + return nil +} + +func applyDataValidations(workbook *excelize.File, validations []validationSpec) error { + for _, spec := range validations { + if spec.Sheet == "" || spec.Range == "" || spec.ValidationType == "" { + return errors.New("validation entries require sheet, range, and validation_type") + } + dv := excelize.NewDataValidation(boolPtrDefault(spec.AllowBlank, true)) + dv.Sqref = spec.Range + switch spec.ValidationType { + case "list": + if strings.HasPrefix(spec.Formula1, "\"") && strings.HasSuffix(spec.Formula1, "\"") { + body := strings.Trim(spec.Formula1, "\"") + if err := dv.SetDropList(strings.Split(body, ",")); err != nil { + return fmt.Errorf("set validation drop list %s!%s: %w", spec.Sheet, spec.Range, err) + } + } else { + dv.Type = "list" + dv.Formula1 = spec.Formula1 + } + case "custom": + dv.Type = "custom" + dv.Formula1 = spec.Formula1 + case "whole": + if err := dv.SetRange(spec.Formula1, spec.Formula2, excelize.DataValidationTypeWhole, validationOperator(spec.Operator)); err != nil { + return fmt.Errorf("set validation range %s!%s: %w", spec.Sheet, spec.Range, err) + } + default: + return fmt.Errorf("unsupported validation type %q", spec.ValidationType) + } + if spec.ErrorTitle != "" || spec.Error != "" { + dv.SetError(excelize.DataValidationErrorStyleStop, spec.ErrorTitle, spec.Error) + } + if err := workbook.AddDataValidation(spec.Sheet, dv); err != nil { + return fmt.Errorf("add data validation %s!%s: %w", spec.Sheet, spec.Range, err) + } + } + return nil +} + +func applyHyperlinks(workbook *excelize.File, links []hyperlinkSpec) error { + for _, spec := range links { + if spec.Sheet == "" || spec.Cell == "" || spec.Target == "" { + return errors.New("hyperlink entries require sheet, cell, and target") + } + display := spec.Display + if display != "" { + if err := workbook.SetCellValue(spec.Sheet, spec.Cell, display); err != nil { + return fmt.Errorf("set hyperlink display %s!%s: %w", spec.Sheet, spec.Cell, err) + } + } + tooltip := spec.Tooltip + linkType := "External" + if spec.Internal { + linkType = "Location" + } + if err := workbook.SetCellHyperLink(spec.Sheet, spec.Cell, spec.Target, linkType, excelize.HyperlinkOpts{Display: stringPtrIfSet(display), Tooltip: stringPtrIfSet(tooltip)}); err != nil { + return fmt.Errorf("set hyperlink %s!%s: %w", spec.Sheet, spec.Cell, err) + } + } + return nil +} + +func applyComments(workbook *excelize.File, comments []commentSpec) error { + for _, spec := range comments { + if spec.Sheet == "" || spec.Cell == "" { + return errors.New("comment entries require sheet and cell") + } + comment := excelize.Comment{Cell: spec.Cell, Author: spec.Author, Paragraph: []excelize.RichTextRun{{Text: spec.Text}}} + if err := workbook.AddComment(spec.Sheet, comment); err != nil { + return fmt.Errorf("add comment %s!%s: %w", spec.Sheet, spec.Cell, err) + } + } + return nil +} + +func applyPanes(workbook *excelize.File, panes []paneSpec) error { + for _, spec := range panes { + if spec.Sheet == "" { + return errors.New("pane entries require sheet") + } + mode := spec.Mode + if mode == "" { + mode = "freeze" + } + panesSpec := &excelize.Panes{Freeze: mode == "freeze", Split: mode == "split", XSplit: spec.XSplit, YSplit: spec.YSplit, TopLeftCell: spec.TopLeftCell, ActivePane: activePane(spec.XSplit, spec.YSplit, mode)} + if err := workbook.SetPanes(spec.Sheet, panesSpec); err != nil { + return fmt.Errorf("set panes %s: %w", spec.Sheet, err) + } + } + return nil +} + +func applyNamedRanges(workbook *excelize.File, names []namedRangeSpec) error { + for _, spec := range names { + if spec.Name == "" || spec.RefersTo == "" { + return errors.New("named range entries require name and refers_to") + } + defined := &excelize.DefinedName{Name: spec.Name, RefersTo: spec.RefersTo} + if spec.Scope == "sheet" { + defined.Scope = spec.Sheet + } + if err := workbook.SetDefinedName(defined); err != nil { + return fmt.Errorf("set defined name %s: %w", spec.Name, err) + } + } + return nil +} + func applyTables(workbook *excelize.File, tables []tableSpec) error { for _, table := range tables { if table.Sheet == "" || table.Range == "" || table.Name == "" { @@ -382,6 +773,9 @@ func applyConditionalFormats(workbook *excelize.File, formats []conditionalForma IconStyle: format.IconStyle, StopIfTrue: format.StopIfTrue, } + if option.Type == "formula" { + option.Criteria = format.Criteria + } if option.Type == "3_color_scale" && option.MidType == "" { option.MidType = "percentile" option.MidColor = defaultString(option.MidColor, "#FFEB84") @@ -395,7 +789,7 @@ func applyConditionalFormats(workbook *excelize.File, formats []conditionalForma if option.Type == "cell" && option.Value == "" { option.Value = "0" } - if option.Type == "cell" { + if option.Type == "cell" || option.Type == "formula" { style, err := workbook.NewConditionalStyle(&excelize.Style{ Font: &excelize.Font{Color: "9A0511"}, Fill: excelize.Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}, @@ -403,6 +797,14 @@ func applyConditionalFormats(workbook *excelize.File, formats []conditionalForma if err != nil { return fmt.Errorf("new conditional style: %w", err) } + if format.BGColor != "" { + style, err = workbook.NewConditionalStyle(&excelize.Style{ + Fill: excelize.Fill{Type: "pattern", Color: []string{format.BGColor}, Pattern: 1}, + }) + if err != nil { + return fmt.Errorf("new conditional fill style: %w", err) + } + } option.Format = &style } if err := workbook.SetConditionalFormat(format.Sheet, format.Range, []excelize.ConditionalFormatOptions{option}); err != nil { @@ -626,6 +1028,373 @@ func stringify(value interface{}) string { } } +func trimFormulaEquals(value string) string { + return strings.TrimPrefix(value, "=") +} + +func trimHash(value string) string { + return strings.TrimPrefix(value, "#") +} + +func splitRange(ref string) (string, string, bool) { + parts := strings.Split(ref, ":") + if len(parts) != 2 { + return "", "", false + } + return parts[0], parts[1], true +} + +func boolPtrDefault(value *bool, fallback bool) bool { + if value == nil { + return fallback + } + return *value +} + +func stringPtrIfSet(value string) *string { + if value == "" { + return nil + } + return &value +} + +func activePane(xSplit int, ySplit int, mode string) string { + if mode == "split" { + if xSplit > 0 && ySplit > 0 { + return "bottomRight" + } + if xSplit > 0 { + return "topRight" + } + if ySplit > 0 { + return "bottomLeft" + } + return "topLeft" + } + if xSplit > 0 && ySplit == 0 { + return "topRight" + } + if ySplit > 0 { + return "bottomLeft" + } + return "topLeft" +} + +func builtinNumFmt(value string) (int, bool) { + switch value { + case "0.00%": + return 10, true + case "0.00E+00": + return 11, true + default: + return 0, false + } +} + +func buildBorders(border map[string]interface{}) []excelize.Border { + if border == nil { + return nil + } + order := []struct { + key string + typeName string + }{ + {"top", "top"}, + {"bottom", "bottom"}, + {"left", "left"}, + {"right", "right"}, + {"diagonal_up", "diagonalUp"}, + {"diagonal_down", "diagonalDown"}, + } + result := make([]excelize.Border, 0) + for _, item := range order { + raw, ok := border[item.key].(map[string]interface{}) + if !ok || raw == nil { + continue + } + styleName, _ := raw["style"].(string) + if styleName == "" || styleName == "none" { + continue + } + color, _ := raw["color"].(string) + result = append(result, excelize.Border{Type: item.typeName, Color: trimHash(color), Style: borderStyle(styleName)}) + } + return result +} + +func borderStyle(value string) int { + switch value { + case "thin": + return 1 + case "medium": + return 2 + case "dashed": + return 3 + case "dotted": + return 4 + case "thick": + return 5 + case "double": + return 6 + case "hair": + return 7 + case "mediumDashed": + return 8 + case "dashDot": + return 9 + case "mediumDashDot": + return 10 + case "dashDotDot": + return 11 + case "mediumDashDotDot": + return 12 + case "slantDashDot": + return 13 + default: + return 1 + } +} + +func normalizeExcelizeTables(path string, tables []tableSpec) error { + if len(tables) == 0 { + return nil + } + reader, err := zip.OpenReader(path) + if err != nil { + return fmt.Errorf("open generated workbook for table normalization: %w", err) + } + defer reader.Close() + + var buf bytes.Buffer + writer := zip.NewWriter(&buf) + for _, file := range reader.File { + rc, err := file.Open() + if err != nil { + return fmt.Errorf("open zip member %s: %w", file.Name, err) + } + data, err := io.ReadAll(rc) + _ = rc.Close() + if err != nil { + return fmt.Errorf("read zip member %s: %w", file.Name, err) + } + if strings.HasPrefix(file.Name, "xl/tables/table") && strings.HasSuffix(file.Name, ".xml") { + idxText := strings.TrimSuffix(strings.TrimPrefix(file.Name, "xl/tables/table"), ".xml") + if idx, convErr := strconv.Atoi(idxText); convErr == nil && idx >= 1 && idx <= len(tables) { + data = []byte(patchTableXML(string(data), tables[idx-1])) + } + } + hdr := file.FileHeader + w, err := writer.CreateHeader(&hdr) + if err != nil { + return fmt.Errorf("create zip member %s: %w", file.Name, err) + } + if _, err := w.Write(data); err != nil { + return fmt.Errorf("write zip member %s: %w", file.Name, err) + } + } + if err := writer.Close(); err != nil { + return fmt.Errorf("close normalized workbook zip: %w", err) + } + if err := os.WriteFile(path, buf.Bytes(), 0o644); err != nil { + return fmt.Errorf("rewrite normalized workbook: %w", err) + } + return nil +} + +func normalizeExcelizeConditionalFormats(path string, formats []conditionalFormatSpec) error { + if len(formats) == 0 { + return nil + } + reader, err := zip.OpenReader(path) + if err != nil { + return fmt.Errorf("open generated workbook for CF normalization: %w", err) + } + defer reader.Close() + + var buf bytes.Buffer + writer := zip.NewWriter(&buf) + styleColors := make([]string, 0) + for _, spec := range formats { + if spec.BGColor != "" { + styleColors = append(styleColors, trimHash(spec.BGColor)) + } + } + for _, file := range reader.File { + rc, err := file.Open() + if err != nil { + return fmt.Errorf("open zip member %s: %w", file.Name, err) + } + data, err := io.ReadAll(rc) + _ = rc.Close() + if err != nil { + return fmt.Errorf("read zip member %s: %w", file.Name, err) + } + if file.Name == "xl/styles.xml" && len(styleColors) > 0 { + data = []byte(patchStylesDxfColors(string(data), styleColors)) + } + hdr := file.FileHeader + w, err := writer.CreateHeader(&hdr) + if err != nil { + return fmt.Errorf("create zip member %s: %w", file.Name, err) + } + if _, err := w.Write(data); err != nil { + return fmt.Errorf("write zip member %s: %w", file.Name, err) + } + } + if err := writer.Close(); err != nil { + return fmt.Errorf("close normalized workbook zip: %w", err) + } + if err := os.WriteFile(path, buf.Bytes(), 0o644); err != nil { + return fmt.Errorf("rewrite normalized workbook: %w", err) + } + return nil +} + +func patchTableXML(xml string, spec tableSpec) string { + if spec.Range != "" { + xml = replaceXMLAttr(xml, "ref", spec.Range) + xml = replaceXMLAttr(xml, "xr:uid", "") + } + if spec.ShowHeaderRow != nil { + xml = replaceXMLBoolAttr(xml, "headerRowCount", boolToIntString(*spec.ShowHeaderRow, 1, 0)) + } + if spec.Style == "" { + xml = removeTableStyleInfo(xml) + } + if spec.TotalsRow { + xml = ensureXMLAttr(xml, "totalsRowCount", "1") + xml = ensureXMLAttr(xml, "totalsRowShown", "1") + } else { + xml = ensureXMLAttr(xml, "totalsRowCount", "0") + xml = ensureXMLAttr(xml, "totalsRowShown", "0") + } + xml = ensureAutoFilterRef(xml, spec.Range) + return xml +} + +func patchStylesDxfColors(xml string, colors []string) string { + re := regexp.MustCompile(``) + xml = re.ReplaceAllStringFunc(xml, func(match string) string { + inner := re.FindStringSubmatch(match) + if len(inner) < 2 { + return match + } + argb := inner[1] + if len(argb) == 6 { + argb = "FF" + argb + } + return `` + }) + for _, color := range colors { + argb := color + if len(argb) == 6 { + argb = "FF" + argb + } + if strings.Contains(xml, "= 0 { + start += len(prefix) + end := strings.Index(xml[start:], "\"") + if end >= 0 { + return xml[:start] + value + xml[start+end:] + } + } + return xml +} + +func ensureXMLAttr(xml string, attr string, value string) string { + prefix := attr + "=\"" + if start := strings.Index(xml, prefix); start >= 0 { + start += len(prefix) + end := strings.Index(xml[start:], "\"") + if end >= 0 { + return xml[:start] + value + xml[start+end:] + } + } + tableStart := strings.Index(xml, "= 0 { + if idx := strings.Index(xml[tableStart:], ">"); idx >= 0 { + idx += tableStart + return xml[:idx] + " " + attr + "=\"" + value + "\"" + xml[idx:] + } + } + return xml +} + +func replaceXMLBoolAttr(xml string, attr string, value string) string { + return ensureXMLAttr(xml, attr, value) +} + +func removeTableStyleInfo(xml string) string { + start := strings.Index(xml, ""); end >= 0 { + end += start + 2 + return xml[:start] + xml[end:] + } + if end := strings.Index(xml[start:], ""); end >= 0 { + end += start + len("") + return xml[:start] + xml[end:] + } + return xml +} + +func ensureAutoFilterRef(xml string, ref string) string { + if ref == "" { + return xml + } + prefix := "= 0 { + start += len(prefix) + if end := strings.Index(xml[start:], "\""); end >= 0 { + return xml[:start] + ref + xml[start+end:] + } + } + return xml +} + +func boolToIntString(value bool, trueValue int, falseValue int) string { + if value { + return strconv.Itoa(trueValue) + } + return strconv.Itoa(falseValue) +} + +func validationOperator(value string) excelize.DataValidationOperator { + switch value { + case "between": + return excelize.DataValidationOperatorBetween + case "greaterThan": + return excelize.DataValidationOperatorGreaterThan + case "greaterThanOrEqual": + return excelize.DataValidationOperatorGreaterThanOrEqual + case "lessThan": + return excelize.DataValidationOperatorLessThan + case "lessThanOrEqual": + return excelize.DataValidationOperatorLessThanOrEqual + case "equal": + return excelize.DataValidationOperatorEqual + case "notBetween": + return excelize.DataValidationOperatorNotBetween + case "notEqual": + return excelize.DataValidationOperatorNotEqual + default: + return excelize.DataValidationOperatorBetween + } +} + func defaultString(value string, fallback string) string { if value == "" { return fallback @@ -646,3 +1415,14 @@ func nonzeroFloat(value float64, fallback float64) float64 { } return value } + +func parseTemporalCell(cellType string, value interface{}) (time.Time, error) { + text := stringify(value) + if cellType == "date" { + return time.Parse("2006-01-02", text) + } + if parsed, err := time.Parse(time.RFC3339, text); err == nil { + return parsed, nil + } + return time.Parse("2006-01-02T15:04:05", text) +} From 97903cb5054cedc51e6605b514d56c900a271341 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:12:10 -0700 Subject: [PATCH 22/25] docs: publish release and cross-language artifacts --- .gitignore | 2 + LAUNCH.md | 128 + METHODOLOGY.md | 17 +- README.md | 161 +- docs/cross-language-context.md | 53 + docs/public-reporting.md | 68 + results-cross-language-pivots/CONTEXT.md | 7 + results-cross-language-pivots/README.md | 47 + .../excelize-pivot-probe.xlsx | Bin 0 -> 8362 bytes results-cross-language-pivots/results.json | 76 + results-cross-language/CONTEXT.md | 21 + results-cross-language/FIDELITY_DELTAS.md | 3 + results-cross-language/README.md | 268 + results-cross-language/history.jsonl | 1 + results-cross-language/matrix.csv | 39 + results-cross-language/results.json | 5288 ++ results-release-2026-04-28/DASHBOARD.md | 37 + results-release-2026-04-28/FIDELITY_DELTAS.md | 3 + results-release-2026-04-28/README.md | 3337 + results-release-2026-04-28/history.jsonl | 1 + results-release-2026-04-28/matrix.csv | 248 + results-release-2026-04-28/perf/README.md | 193 + results-release-2026-04-28/perf/history.jsonl | 1 + results-release-2026-04-28/perf/matrix.csv | 248 + results-release-2026-04-28/perf/results.json | 9928 +++ results-release-2026-04-28/results.json | 57467 ++++++++++++++++ 26 files changed, 77604 insertions(+), 38 deletions(-) create mode 100644 LAUNCH.md create mode 100644 docs/cross-language-context.md create mode 100644 docs/public-reporting.md create mode 100644 results-cross-language-pivots/CONTEXT.md create mode 100644 results-cross-language-pivots/README.md create mode 100644 results-cross-language-pivots/excelize-pivot-probe.xlsx create mode 100644 results-cross-language-pivots/results.json create mode 100644 results-cross-language/CONTEXT.md create mode 100644 results-cross-language/FIDELITY_DELTAS.md create mode 100644 results-cross-language/README.md create mode 100644 results-cross-language/history.jsonl create mode 100644 results-cross-language/matrix.csv create mode 100644 results-cross-language/results.json create mode 100644 results-release-2026-04-28/DASHBOARD.md create mode 100644 results-release-2026-04-28/FIDELITY_DELTAS.md create mode 100644 results-release-2026-04-28/README.md create mode 100644 results-release-2026-04-28/history.jsonl create mode 100644 results-release-2026-04-28/matrix.csv create mode 100644 results-release-2026-04-28/perf/README.md create mode 100644 results-release-2026-04-28/perf/history.jsonl create mode 100644 results-release-2026-04-28/perf/matrix.csv create mode 100644 results-release-2026-04-28/perf/results.json create mode 100644 results-release-2026-04-28/results.json diff --git a/.gitignore b/.gitignore index 9d48f7f..1ca3a1b 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,8 @@ test_files/ # Results - regenerated on demand (but we may want to commit some) # results/ results_dev*/ +results-cross-language-smoke*/ +tools/external-oracles/**/results-*/ # IDE .idea/ diff --git a/LAUNCH.md b/LAUNCH.md new file mode 100644 index 0000000..29d12a6 --- /dev/null +++ b/LAUNCH.md @@ -0,0 +1,128 @@ +# Launch + +## One-liner + +WolfXL is a Rust-backed, openpyxl-compatible Excel engine, and ExcelBench is the benchmark suite that measures whether spreadsheet libraries preserve the workbook features people actually care about. + +## What is launching + +This launch has three lanes: + +1. Python replacement lane + +WolfXL reaches `18/18` green features in the current scored Python release snapshot. + +2. Cross-language context lane + +The checked-in cross-language snapshot shows both `Apache POI` and `Excelize` at `18/18` in the scored write lane. + +3. Pivot capability lane + +The scored macOS fixture is not valid for pivots, so pivot evidence lives in a separate artifact. That artifact shows `Excelize` can emit pivot-bearing workbooks and `openpyxl` can read back the emitted pivot metadata. + +## Core message + +Most spreadsheet comparisons focus on speed. The real question is whether a library can handle complex workbooks without quietly dropping the parts that matter. ExcelBench measures that directly, and WolfXL now has the evidence to claim both high fidelity and strong performance in Python. + +## Launch headline options + +- WolfXL reaches 18/18 in our Excel fidelity benchmark +- We built an Excel benchmark that tests what spreadsheet libraries actually preserve +- WolfXL now matches openpyxl on scored fidelity and adds a patch-based modify path + +## Main announcement draft + +We spent the last stretch turning WolfXL and ExcelBench from a promising project into something we can defend technically. + +WolfXL is a Rust-backed, openpyxl-compatible Excel engine for Python. ExcelBench is the benchmark suite we built to answer a simple question that most spreadsheet comparisons skip: can this library handle a real workbook without breaking the parts you care about? + +The current release snapshot is the first one that feels clean enough to publish: + +- WolfXL: `18/18` green features in the scored Python release lane +- Apache POI: `18/18` in the cross-language scored write lane +- Excelize: `18/18` in the cross-language scored write lane +- Pivot tables: tracked in a separate capability artifact on macOS because the shipped fixture is not scoreable there, while `Excelize` can still emit pivot-bearing workbooks + +The important part is not just the score. The benchmark now has distinct lanes: + +- a Python replacement lane for migration decisions +- a cross-language lane for ecosystem context +- a separate pivot capability lane when the scored fixture is not valid on this platform + +That separation matters because it keeps the claims honest. We are no longer mixing historical snapshots, capability demos, and scored benchmark results into one muddy story. + +If you process spreadsheets in Python, the practical takeaway is straightforward: WolfXL now has a strong case as a serious openpyxl alternative, and ExcelBench now has enough rigor to be useful as a benchmark in its own right. + +## HN draft + +Title: + +WolfXL reached 18/18 in our Excel fidelity benchmark + +Body: + +I built two related projects: + +- WolfXL: a Rust-backed, openpyxl-compatible Excel engine for Python +- ExcelBench: a benchmark suite for spreadsheet fidelity and performance + +The benchmark question is simple: not just “how fast is this library?”, but “can it actually preserve the workbook features people care about?” + +Current state: + +- WolfXL hits `18/18` in the scored Python release lane +- Apache POI and Excelize both hit `18/18` in the scored cross-language write lane +- pivot tables are tracked separately on macOS because the shipped fixture is not scoreable there, but Excelize can still emit pivot-bearing workbooks and openpyxl can read the resulting pivot metadata + +I think the most interesting part is the benchmark design, not just the project score: + +- Python replacement lane for migration decisions +- cross-language lane for ecosystem context +- separate capability lane for pivots when the platform fixture is not valid + +Repo links: + +- WolfXL: +- ExcelBench: + +If you work on spreadsheet tooling, I’d especially like feedback on the benchmark methodology and fixture design. + +## Investor / technical summary + +WolfXL is a Python spreadsheet engine with a Rust core and an openpyxl-style API. ExcelBench is the benchmark harness that measures spreadsheet fidelity rather than just throughput. + +The project is now in a much stronger position because the proof is cleaner: + +- WolfXL reaches `18/18` in the scored Python release lane +- cross-language reference points are strong: Apache POI `18/18`, Excelize `18/18` +- pivot capability is broken out into a separate artifact instead of being overstated in the main scorecard + +That gives us three things: + +1. a credible Python replacement story +2. a credible ecosystem-positioning story +3. a benchmark asset that is useful beyond WolfXL itself + +## Assets to link + +- Python release snapshot: `results-release-2026-04-28/README.md` +- Python release dashboard: `results-release-2026-04-28/DASHBOARD.md` +- Cross-language snapshot: `results-cross-language/README.md` +- Pivot capability artifact: `results-cross-language-pivots/README.md` +- Reporting policy: `docs/public-reporting.md` +- Cross-language context explainer: `docs/cross-language-context.md` + +## Claim guardrails + +- Say `scored Python release lane` for WolfXL results. +- Say `cross-language context snapshot` for Apache POI and Excelize. +- Say `pivot capability artifact` for the separate pivot evidence. +- Do not merge historical baseline, perf snapshot, and release snapshot into one undated claim. +- Do not imply that the macOS pivot fixture is currently scoreable. + +## Recommended order for public rollout + +1. Publish the README/docs update and the checked-in artifacts. +2. Post the main launch note using the announcement draft above. +3. Post the HN version with the benchmark-methodology angle. +4. Use the investor / technical summary in direct outreach. diff --git a/METHODOLOGY.md b/METHODOLOGY.md index 66243cb..32ba4bc 100644 --- a/METHODOLOGY.md +++ b/METHODOLOGY.md @@ -2,7 +2,11 @@ ## Purpose -ExcelBench measures **feature fidelity** for Python Excel libraries -- how accurately they can read and write Excel features compared to native Excel. It also measures **performance** (throughput, memory) as a secondary axis. +ExcelBench measures **feature fidelity** for spreadsheet libraries -- how accurately they can read and write Excel features compared to native Excel. It also measures **performance** (throughput, memory) as a secondary axis. + +The default public presentation is still **Python-first** because most users are choosing between Python libraries. Cross-language comparisons are supported as a secondary ecosystem-context tier. + +See `docs/trackers/cross-language-comparison-strategy.md` for the comparison policy. ## Fidelity vs Performance @@ -44,8 +48,8 @@ Features are organized into tiers reflecting complexity: | **Tier 3** (Workbook Metadata) | named_ranges, tables | 2 | **Framework coverage:** 19 modeled features total. -**Current public XLSX profile:** 17 tested features, where 16 are scoreable per-library in -current results (pivot_tables is tested but N/A across adapters on macOS fixtures). +**Fresh release snapshot:** 19 tested features, where 18 are scoreable per-library in +the 2026-04-29 wheel-backed WolfXL 2.0 rerun (pivot_tables is tested but N/A across adapters on macOS fixtures). ## Scoring @@ -70,3 +74,10 @@ Renderers produce human-readable output (README.md, matrix.csv, heatmap, HTML da ```bash uv run excelbench report --input results/xlsx/results.json --output results/xlsx ``` + +## Comparison policy + +- **Primary tables** answer the Python replacement question. +- **Secondary tables** can include cross-language libraries such as Apache POI, ClosedXML, Excelize, and ExcelJS. +- Cross-language results should be labeled as ecosystem context unless the report is explicitly cross-language-focused. +- The current rollout plan for the first two cross-language candidates lives in `docs/trackers/apache-poi-excelize-rollout-plan.md`. diff --git a/README.md b/README.md index bb6655c..880baca 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,83 @@ # ExcelBench -**Objective, reproducible fidelity scores for Python Excel libraries.** +**Objective, reproducible spreadsheet-library benchmarks with a Python-first decision surface.** -Most Excel library comparisons focus on speed. ExcelBench answers the question developers actually have: **"Can this library handle my complex spreadsheet?"** +Most Excel library comparisons focus on speed. ExcelBench focuses on the question developers actually have: -We test 17 XLSX features across 12+ Python adapters. In current public runs, 16 are scoreable -across libraries (pivot tables are tested but N/A on macOS fixtures). +> Can this library handle my real spreadsheet without breaking the parts I care about? + +ExcelBench models 19 XLSX features across 12+ Python adapters. In the fresh +wheel-backed WolfXL 2.0 release snapshot, 18 are scoreable across libraries +(pivot tables remain N/A on macOS fixtures). ## Results at a Glance -> Last run: 2026-02-17 | Excel 16.105.3 | macOS (Apple Silicon) | [Full results](results/xlsx/README.md) +> Python release snapshot: 2026-04-29 UTC | wheel-backed WolfXL 2.0 rerun | [Fidelity](results-release-2026-04-28/README.md) | [Perf](results-release-2026-04-28/perf/README.md) | [Dashboard](results-release-2026-04-28/DASHBOARD.md) +> +> In that snapshot, WolfXL reaches `18/18` green features with `100%` pass rate. +> +> Cross-language context snapshot: [Apache POI `18/18`](results-cross-language/README.md) | [Excelize `18/18`](results-cross-language/README.md) +> +> Pivot capability lane: [separate artifact](results-cross-language-pivots/README.md) because the shipped macOS pivot fixture is not scoreable, while `excelize` can still emit pivot-bearing workbooks. +> +> Historical public baseline: 2026-02-17 | Excel 16.105.3 | macOS (Apple Silicon) | [Full results](results/xlsx/README.md) + +> Newer performance snapshot: 2026-04-20 | [Perf results](results/perf/README.md) +> +> Read [Public Reporting Status](docs/public-reporting.md) before quoting numbers across snapshots. ![ExcelBench Heatmap](results/xlsx/heatmap.png) -**The story:** Every Python Excel library faces a speed-fidelity tradeoff. openpyxl achieves full fidelity (16/16) but reads at 284K cells/s. python-calamine reads at 1.6M cells/s but scores 1/16 green. **WolfXL breaks this tradeoff** -- its hybrid Rust+Python architecture delivers near-full fidelity (14/16) at 3-9x the throughput of openpyxl, with a unique patch-based modify mode that no other library offers. Once you move past basic cell values, nearly every other library drops to zero on formatting, comments, hyperlinks, images, merged cells, and conditional formatting. +**The current story:** ExcelBench now has three useful lanes. The Python release lane answers the migration question. The cross-language lane answers the ecosystem-positioning question. The pivot capability lane captures pivot evidence separately when the scored fixture is not valid on this platform. Keep every claim tied to the exact dated artifact you are citing. + +## Comparison Scope + +ExcelBench intentionally keeps the main public comparison **Python-first**. That is the decision surface most users care about: `openpyxl`, `xlsxwriter`, `python-calamine`, `pandas`, and adjacent Python options. + +Cross-language libraries matter too, but for a different reason: they show how strong WolfXL looks next to mature spreadsheet tooling outside Python. The current checked-in cross-language lane includes: + +- `Apache POI` +- `Excelize` + +Pivot tables sit in a separate capability lane. On macOS, the shipped pivot fixture does not currently contain scoreable pivot OOXML, so the pivot story is tracked as a dedicated artifact instead of being mixed into the scored lane. + +See [cross-language comparison strategy](docs/trackers/cross-language-comparison-strategy.md). + +## Three Lanes + +1. **Python replacement lane** +Use this when the question is: what should a Python team use instead of `openpyxl`? + +2. **Cross-language context lane** +Use this when the question is: how does WolfXL compare to serious spreadsheet tooling in Java and Go? + +3. **Pivot capability lane** +Use this when the question is: can the cross-language helpers detect or emit pivot-bearing workbooks even when the main scored fixture is not valid on macOS? ### Library Comparison | Library | Caps | Fidelity | Read Speed | Write Speed | Modify | |---------|:----:|:--------:|:----------:|:-----------:|:------:| -| **wolfxl** | R+W | 14/16 | **9x** faster | **4.5x** faster | Patch | -| openpyxl | R+W | 16/16 | 1x (baseline) | 1x (baseline) | Rewrite | -| xlsxwriter | W | 15/16 | -- | ~1x | No | -| xlsxwriter-constmem | W | 12/16 | -- | ~2x | No | -| python-calamine | R | 1/16 | ~1.3x | -- | No | -| pandas | R+W | 3/16 | <1x | <1x | Rebuild | -| polars | R | 0/16 | ~1x | -- | No | +| **wolfxl** | R+W | 18/18 in 2026-04-29 release snapshot | workload-specific | workload-specific | Patch | +| openpyxl | R+W | 18/18 in 2026-04-29 release snapshot | 1x (baseline per workload) | 1x (baseline per workload) | Rewrite | +| xlsxwriter | W | 15/18 in 2026-04-29 release snapshot | -- | ~1x | No | +| xlsxwriter-constmem | W | 12/18 in 2026-04-29 release snapshot | -- | ~2x | No | +| python-calamine | R | 1/18 in 2026-04-29 release snapshot | ~1.3x | -- | No | +| pandas | R+W | 3/18 in 2026-04-29 release snapshot | <1x | <1x | Rebuild | +| polars | R | 0/18 in 2026-04-29 release snapshot | ~1x | -- | No | -> Speed relative to openpyxl (higher = faster). WolfXL benchmarked with bulk read (1.26M cells/s) and bulk write (1.73M cells/s) vs openpyxl baseline. See [performance results](results/perf/README.md) for full numbers. +> Speed numbers are snapshot-specific. Always cite the artifact date, workload, and profile. See [performance results](results/perf/README.md), [METHODOLOGY.md](METHODOLOGY.md), and [Public Reporting Status](docs/public-reporting.md). ### Key Findings -- **WolfXL breaks the speed-fidelity tradeoff**: hybrid Rust+Python achieves 14/16 green features at 3-9x throughput -- the only library with both high fidelity and high speed -- **Patch modify is unique**: WolfXL's `load_workbook(path, modify=True)` does surgical ZIP patching (10-14x vs openpyxl rewrite) -- no other Python library offers this +- **High-fidelity libraries are rare**: in the fresh wheel-backed release snapshot, only openpyxl and WolfXL reach 18/18 green features +- **Patch modify is structurally different**: WolfXL's `load_workbook(path, modify=True)` uses surgical ZIP patching rather than a full workbook rewrite - **The abstraction tax is real**: pandas wraps openpyxl but drops from 16 to 3 green features due to DataFrame coercion (errors become NaN) -- **Speed vs fidelity tradeoff**: xlsxwriter-constmem writes at 4.7M cells/s but loses 3 features; python-calamine reads at 1.6M cells/s but scores 1/16 green +- **Speed vs fidelity tradeoff is measurable**: use the perf snapshot together with the fidelity matrix rather than quoting one without the other - **Optimization modes have clear costs**: openpyxl-readonly loses 13 green features for streaming speed +- **Cross-language context is now strong too**: both `Apache POI` and `Excelize` land at `18/18` in the current scored write lane -See the [full dashboard](results/DASHBOARD.md) for the combined fidelity + performance comparison. +See the [release snapshot dashboard](results-release-2026-04-28/DASHBOARD.md) for the fresh wheel-backed combined view, or the [historical dashboard](results/DASHBOARD.md) for the older public baseline. ### Score Legend @@ -54,17 +94,17 @@ See the [full dashboard](results/DASHBOARD.md) for the combined fidelity + perfo | Library | Version | Lang | Caps | Green Features | |:--------|:--------|:-----|:-----|:--------------:| -| [openpyxl](https://openpyxl.readthedocs.io/) | 3.1.5 | Python | R+W | 16/16 | -| [XlsxWriter](https://xlsxwriter.readthedocs.io/) | 3.2.9 | Python | W | 15/16 | -| [xlsxwriter-constmem](https://xlsxwriter.readthedocs.io/) | 3.2.9 | Python | W | 12/16 | -| [openpyxl-readonly](https://openpyxl.readthedocs.io/) | 3.1.5 | Python | R | 3/16 | -| [pandas](https://pandas.pydata.org/) | 3.0.0 | Python | R+W | 3/16 | -| [pyexcel](https://github.com/pyexcel/pyexcel) | 0.7.4 | Python | R+W | 3/16 | -| [tablib](https://tablib.readthedocs.io/) | 3.9.0 | Python | R+W | 3/16 | -| [pylightxl](https://github.com/PydPiper/pylightxl) | 1.61 | Python | R+W | 3/16 | -| [python-calamine](https://github.com/dimastbk/python-calamine) | 0.6.1 | Rust | R | 1/16 | -| [polars](https://pola.rs/) | 1.38.1 | Rust | R | 0/16 | -| [xlwt](https://github.com/python-excel/xlwt) | 1.3.0 | Python | W | 4/16 | +| [openpyxl](https://openpyxl.readthedocs.io/) | 3.1.5 | Python | R+W | 18/18 | +| [XlsxWriter](https://xlsxwriter.readthedocs.io/) | 3.2.9 | Python | W | 15/18 | +| [xlsxwriter-constmem](https://xlsxwriter.readthedocs.io/) | 3.2.9 | Python | W | 12/18 | +| [openpyxl-readonly](https://openpyxl.readthedocs.io/) | 3.1.5 | Python | R | 3/18 | +| [pandas](https://pandas.pydata.org/) | 3.0.0 | Python | R+W | 3/18 | +| [pyexcel](https://github.com/pyexcel/pyexcel) | 0.7.4 | Python | R+W | 3/18 | +| [tablib](https://tablib.readthedocs.io/) | 3.9.0 | Python | R+W | 3/18 | +| [pylightxl](https://github.com/PydPiper/pylightxl) | 1.61 | Python | R+W | 2/18 | +| [python-calamine](https://github.com/dimastbk/python-calamine) | 0.6.1 | Rust | R | 1/18 | +| [polars](https://pola.rs/) | 1.38.1 | Rust | R | 0/18 | +| [xlwt](https://github.com/python-excel/xlwt) | 1.3.0 | Python | W | 4/18 | | [xlrd](https://github.com/python-excel/xlrd) | 2.0.2 | Python | R | .xls only | ### XLS Profile (2 adapters) @@ -97,6 +137,46 @@ uv run maturin develop --manifest-path rust/excelbench_rust/Cargo.toml \ > `uv sync` may uninstall locally-built extensions; rerun `maturin develop` after. +### Cross-Language Context + +ExcelBench now ships a separate cross-language context snapshot for mature non-Python spreadsheet libraries: + +- `Apache POI` (Java) +- `Excelize` (Go) + +These are not framed as Python drop-in replacements. They answer a different question: how strong is WolfXL relative to serious spreadsheet tooling outside Python? + +Current checked-in cross-language snapshot: + +- [`results-cross-language/README.md`](results-cross-language/README.md) +- [`results-cross-language/CONTEXT.md`](results-cross-language/CONTEXT.md) +- [`results-cross-language-pivots/README.md`](results-cross-language-pivots/README.md) +- [`docs/cross-language-context.md`](docs/cross-language-context.md) + +Current takeaways from that snapshot: + +- `apache-poi`: `18/18` green features in the scored write surfaces of this lane +- `excelize`: `18/18` green features in the scored write surfaces of this lane +- `pivot_tables`: tracked in a separate capability artifact because the shipped macOS fixture does not contain scoreable pivot OOXML, while `excelize` can still emit pivot-bearing workbooks + +The concrete rollout plan for the first two candidates is here: + +- [Apache POI + Excelize rollout plan](docs/trackers/apache-poi-excelize-rollout-plan.md) +- [Apache POI adapter design](docs/trackers/apache-poi-adapter-design.md) +- [Excelize adapter design](docs/trackers/excelize-adapter-design.md) + +Run the dedicated cross-language context snapshot with: + +```bash +uv run excelbench cross-language-context --tests fixtures/excel --output results-cross-language +``` + +Run the dedicated pivot capability artifact with: + +```bash +uv run excelbench cross-language-pivot-context --fixture fixtures/excel/tier2/15_pivot_tables.xlsx --output results-cross-language-pivots +``` + ## How It Works 1. **Generate reference files** -- [xlwings](https://www.xlwings.org/) drives real Excel to produce canonical `.xlsx`/`.xls` test files with known features. @@ -106,6 +186,15 @@ uv run maturin develop --manifest-path rust/excelbench_rust/Cargo.toml \ Full methodology: [METHODOLOGY.md](METHODOLOGY.md) +## Public Reporting Rules + +- Treat each `results/` directory as a dated snapshot. +- Do not merge February fidelity claims and April perf claims into one undated headline. +- Cite the artifact date and workload whenever quoting a speedup number. +- Keep WolfXL-specific release claims aligned with the WolfXL repo's evidence page. + +See [docs/public-reporting.md](docs/public-reporting.md). + ## WolfXL Docs WolfXL documentation lives in the [wolfxl repository](https://github.com/SynthGL/wolfxl/tree/main/docs). @@ -143,34 +232,36 @@ uv run excelbench generate --output fixtures/excel ## Feature Coverage -### Tested (17 features; 16 currently scoreable) +### Tested (19 features; 18 currently scoreable in the release snapshot) | Tier | Features | Count | |:-----|:---------|:-----:| | **Tier 0** -- Core | Cell values, formulas, multiple sheets | 3 | | **Tier 1** -- Formatting | Text formatting, background colors, number formats, alignment, borders, dimensions | 6 | | **Tier 2** -- Advanced | Merged cells, conditional formatting, data validation, hyperlinks, images, comments, freeze panes, pivot tables | 8 | +| **Tier 3** -- Workbook metadata | Named ranges, tables | 2 | > Pivot tables are tested but score N/A across all adapters in the current macOS run. -> Library green-feature scores therefore use a /16 denominator (excluding pivot tables). +> Library green-feature scores therefore use an /18 denominator in the fresh release snapshot. ### Planned -Named ranges, tables, charts, print settings, protection. +Charts, print settings, protection. ## Detailed Results - **[XLSX results](results/xlsx/README.md)** -- per-library, per-test-case breakdowns with tier list +- **[Release snapshot results](results-release-2026-04-28/README.md)** -- fresh wheel-backed WolfXL 2.0 rerun - **[XLS results](results/xls/README.md)** -- legacy format results - **[Performance results](results/perf/README.md)** -- throughput benchmarks (cells/s) +- **[Release snapshot perf](results-release-2026-04-28/perf/README.md)** -- matching wheel-backed perf snapshot - **[Dashboard](results/DASHBOARD.md)** -- combined fidelity + performance comparison +- **[Release snapshot dashboard](results-release-2026-04-28/DASHBOARD.md)** -- combined view for the fresh rerun - **[Heatmap (PNG)](results/xlsx/heatmap.png)** | **[SVG](results/xlsx/heatmap.svg)** -- visual score matrix ## Project Status -**v0.1.0** -- 17 features tested, 12 Python xlsx adapters + 2 xls + 5 Rust/PyO3. - -1126 tests passing. Actively maintained. +**v0.1.0** -- actively maintained benchmarking harness with dated fidelity and performance snapshots, reproducible methodology, and multi-adapter coverage across Python and Rust-backed spreadsheet libraries. ## Contributing diff --git a/docs/cross-language-context.md b/docs/cross-language-context.md new file mode 100644 index 0000000..685dfb4 --- /dev/null +++ b/docs/cross-language-context.md @@ -0,0 +1,53 @@ +# Cross-Language Context + +ExcelBench is still a Python-first benchmark project. The main public question is: + +> What should a Python team use instead of `openpyxl`? + +The cross-language context snapshot answers a different question: + +> How does WolfXL compare to mature spreadsheet tooling outside Python? + +## Current Snapshot + +- Artifact: [`results-cross-language/README.md`](../results-cross-language/README.md) +- Generated: 2026-04-29 UTC +- Scope: write-path ecosystem context only + +## Current Takeaways + +- `apache-poi` is now a strong cross-language reference point in this harness with `18/18` green features across the scored write surfaces in the current snapshot. +- `excelize` is now also at `18/18` green features across the scored write surfaces in the current snapshot. +- `pivot_tables` remain out of scope in the scored macOS cross-language snapshot because the shipped fixture does not currently contain pivot OOXML, but a separate pivot capability artifact exists. + +## Three-Lane Framing + +Use the ExcelBench outputs as three separate lanes, not one mixed claim set. + +1. **Python replacement lane** +The main `results-release-*` artifacts answer the migration question for Python teams. + +2. **Cross-language context lane** +The `results-cross-language/` artifacts answer the ecosystem-positioning question for technical readers who know POI or Excelize. + +3. **Pivot capability lane** +The `results-cross-language-pivots/` artifact answers whether the helpers can detect or emit pivot-bearing workbooks even though the main scored pivot fixture is not valid on macOS. + +## Why this matters + +- It shows WolfXL is not only competitive inside Python. It also sits well against mature spreadsheet tooling in Java and Go. +- It gives a credibility layer for technical readers who know Apache POI or Excelize and want broader ecosystem context. +- It keeps the Python replacement story clean because the cross-language results live in a separate lane. + +## How to cite it + +- Say `cross-language context snapshot`, not `primary benchmark result`. +- Cite the artifact path and generation date. +- Keep the Python hero table and the cross-language table separate in launch copy. + +## Links + +- [Cross-language README](../results-cross-language/README.md) +- [Cross-language context note](../results-cross-language/CONTEXT.md) +- [Pivot capability artifact](../results-cross-language-pivots/README.md) +- [Cross-language comparison strategy](trackers/cross-language-comparison-strategy.md) diff --git a/docs/public-reporting.md b/docs/public-reporting.md new file mode 100644 index 0000000..d44a6ff --- /dev/null +++ b/docs/public-reporting.md @@ -0,0 +1,68 @@ +# Public Reporting Status + +This page explains what the current ExcelBench repo artifacts mean and how to cite them accurately. + +## Status + +- Package version: `0.1.0` +- Repository: `SynthGL/ExcelBench` +- Last local verification pass: 2026-04-29 +- Current public snapshot in `results/xlsx/` and `results/DASHBOARD.md`: 2026-02-17 +- Newer perf snapshot in `results/perf/`: 2026-04-20 +- Fresh WolfXL 2.0 wheel-backed release snapshot: `results-release-2026-04-28/` generated 2026-04-29 UTC +- Current checked-in cross-language context snapshot: `results-cross-language/` generated 2026-04-29 UTC +- Current checked-in cross-language pivot capability artifact: `results-cross-language-pivots/` generated 2026-04-29 UTC + +## How To Cite ExcelBench Safely + +1. Treat every results directory as a timestamped snapshot. +2. Cite the date, platform, and workload profile whenever quoting a number. +3. Separate historical public snapshots from release-blocking reruns. +4. If fidelity and perf were generated on different dates, say that explicitly. + +## Current Artifact State + +| Artifact | Date | Meaning | +|---|---|---| +| `results/xlsx/README.md` | 2026-02-17 | Current checked-in public XLSX fidelity snapshot | +| `results/DASHBOARD.md` | 2026-02-17 | Current checked-in combined dashboard snapshot | +| `results/perf/README.md` | 2026-04-20 | Newer performance rerun with fresher timing data | +| `results-release-2026-04-28/README.md` | 2026-04-29 | Fresh wheel-backed WolfXL 2.0 fidelity rerun | +| `results-release-2026-04-28/perf/README.md` | 2026-04-29 | Matching wheel-backed performance rerun | +| `results-cross-language/README.md` | 2026-04-29 | Checked-in cross-language context snapshot for Apache POI and Excelize | +| `results-cross-language-pivots/README.md` | 2026-04-29 | Separate pivot capability artifact for cross-language helpers | + +## Safe Claims Right Now + +- ExcelBench provides reproducible fidelity scoring across multiple Python spreadsheet libraries. +- The methodology and raw JSON artifacts are available in-repo. +- The checked-in results are dated snapshots, not timeless truths. +- The fresh WolfXL 2.0 wheel-backed release snapshot is available separately from the older historical baseline. +- A separate cross-language context snapshot is available for ecosystem positioning and should be cited as a separate lane from the Python hero table. +- A separate pivot capability artifact is available for cross-language helpers and should be cited as a capability note, not as a scored lane. + +## Claims That Need Fresh Reruns + +- Any statement that merges February fidelity and April perf into one "current" result without caveat. +- Any ecosystem ranking that implies a same-day apples-to-apples rerun if the artifacts are from different dates. +- Any statement that mixes the historical baseline and the WolfXL 2.0 release rerun without naming which snapshot is being cited. +- Any statement that treats the cross-language context snapshot as the same decision surface as the Python replacement snapshot. +- Any statement that treats the pivot capability artifact as if it were part of the scored cross-language matrix. + +## Verification Commands + +```bash +uv run pytest tests/ --cov-fail-under=65 +uv run excelbench benchmark --tests fixtures/excel --output results +uv run excelbench perf --tests fixtures/excel --output results +uv run excelbench report --input results/xlsx/results.json --output results/xlsx +``` + +## Recommended README Policy + +- Lead with what ExcelBench measures. +- Link directly to `METHODOLOGY.md`. +- Mark top-level result summaries as dated snapshots. +- Keep WolfXL-specific launch claims in sync with the WolfXL repo's release evidence page. +- Keep the cross-language context snapshot clearly separated from the Python-first comparison in README and launch copy. +- Keep the pivot capability artifact clearly separated from both the Python-first and cross-language scorecards. diff --git a/results-cross-language-pivots/CONTEXT.md b/results-cross-language-pivots/CONTEXT.md new file mode 100644 index 0000000..ef551bd --- /dev/null +++ b/results-cross-language-pivots/CONTEXT.md @@ -0,0 +1,7 @@ +# Pivot Context Note + +This directory is a capability artifact, not a scored benchmark lane. + +Use it when you want to answer: can the cross-language helpers detect or emit pivot-bearing workbooks? + +Keep it separate from the main `results-cross-language/` scoreboard. diff --git a/results-cross-language-pivots/README.md b/results-cross-language-pivots/README.md new file mode 100644 index 0000000..d59a256 --- /dev/null +++ b/results-cross-language-pivots/README.md @@ -0,0 +1,47 @@ +# Cross-Language Pivot Context + +This artifact exists because the main cross-language benchmark lane does not score pivot tables on macOS. + +## Fixture Check + +- Shipped fixture contains pivot OOXML parts: **No** +- Fixture path: `fixtures/excel/tier2/15_pivot_tables.xlsx` +- Fixture pivot-related parts: none detected + +## Helper Detection + +| Helper | Available | Detects pivots in shipped fixture | Notes | +|---|---:|---:|---| +| apache-poi | Yes | No | package metadata helper | +| excelize | Yes | No | sheet metadata helper | + +## Write Probes + +| Tool | Pivot write support | Evidence | +|---|---:|---| +| apache-poi | No | ApachePoiAdapter does not implement pivot table creation yet. | +| excelize | Yes | OOXML parts + openpyxl readback | + +### Excelize Probe Details + +- Output workbook: `results-cross-language-pivots/excelize-pivot-probe.xlsx` +- Pivot-related OOXML parts: + - `xl/pivotCache/pivotCacheDefinition1.xml` + - `xl/pivotTables/_rels/pivotTable1.xml.rels` + - `xl/pivotTables/pivotTable1.xml` +- Openpyxl readback: +```json +[ + { + "name": "SalesPivot", + "source_range": "Data!A1:D5", + "target_cell": "Pivot!B3:E10" + } +] +``` + +## Interpretation + +- The main cross-language scorecard remains correct to mark `pivot_tables` as not scored on macOS. +- The shipped fixture currently does not provide scoreable pivot evidence for this lane. +- `excelize` can still emit pivot-bearing workbooks, and this artifact captures that separately from the main scorecard. diff --git a/results-cross-language-pivots/excelize-pivot-probe.xlsx b/results-cross-language-pivots/excelize-pivot-probe.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e8632de4f435ca0fb2fd023bd4b3b84a061d92bc GIT binary patch literal 8362 zcmb7p1yq!4*EZcC-Q9?Eht$v^jdTwo-7$oKgmgEGgoGd<-Q7rcx6%#L-=OdL^zgms z{OjMd=9!siJ@>U{$G!Kp_pKxc4TBAV0De6deDDo-YZf;v=Gv%8J8 zd{nC}3>!+&fsjP+g5^{|NQE8@M#HCzy0823HmM&N_63`qrwKOCH=dC7+C^~y&w|x4 z@!*47U$;4~Ciiu=UQBhbArs#QG&(f)g-nmj*M3Xs~HTCX@|5R&3Mw zO&#=vspvAJn)FT>0^Hk)7vE0`(x8!=4w zkxHp*fG-mK`*fM;lIF5*e#3T(n&(giZeEq=HtWKKXfycObfYKfr;deJV=-_#wFm7i z0$x&;dr-j*p<^eXPKG7n7?R7O9f-TWoVmLX`oW;BiNVLPd7_6;nxAt4FKMn5zpTdE zs8mIU^9yu9Njy`cj_Q~2i&qbW>QrOw$1UdJsA1BVI=YJF1wKJO^6X>z+yTDBoLwW| zCd|~akrB~9wwJ_saCui%6*{{eSyfkV$5otQ;9*{hKDO?ZeFTqt5B-%Y z@oF;;$(9MQY9*?%USPWw_HrkxH9np6){fdXN+ne%Au7Fl6}t2BgMKx^o3aU$G4kA} zD>-JnQsSiN&p@H%18%K4C=~~-E6BAaZ)$G=1f!+QuS{=i8}4BKGJ#qo|?&RXB=G@#0#XfxxpFKn1O>Pc*sE&qG?8r5f{d%cHnDpIp?Ujmtx-dz)Kv}B= z5ljV}>)bQsLJ6w&J{Jr4Y@%-w;YJ%bY{(f>TEVz%gc&#pe;kEFpviF_x3F^lHent;O=0-U?OKDc{NlyAk@NKL|eI~lA+Ok*Pb%Dh)HVp0~Fl5OpR1oaxOUN-cc>DtW8VStisH25tq|k20e@y*40y1 ziB@Rl^BI8c0yDFEFzYR!!%l#LO{yew;|r>Hyi-1uBy;r34BswCX;uo^b?_s0QmM+< zwDnEnTe|v~zoHJ<72qzrolT#(JF(1+&2$ES)U=pHR^Yy7D419*$CixBqW>5ukEcFE zv&vJTjne<7;PiIMI4-{W{eEm|a`P3|U#`ZO$c*?5cC+ojMmzZNhs^A30ki@By8g^j zyD39|Xo1$QegKZA)?!K{IzfELqp;c;+H%Fv|QlVsqC!RWqO=V27zAz|7F_pn&yu4n1dR69LzXZ45m+NX+boi zg^NN@4GwtM^dpD)8KCHq^)`v>9T(6Xjk0{a=$yo`x@mRu+}vI< zWiAs8(Sa%c_vHB0(5c&|-% z588016zZS*b&Ey9PF>zXD)5b-#LH8V3z0%)W66w=Mi@sZBk9twnpE#aE=0}^`qp2n zYcY&qMu3o;Y5FuxuSmh^UK@at+8^t=0yL# z3gkVUJKV#b^`)EBufoD!Z}0B*x_O40DxLb)_3w`-c7*R5WO?;st`|@=SL9)hL9wd==Lc~BQsJv|@C-T^f694BfL_u&4s2pKUO ziXgUv9ET{#DRhh%oPC=e7!;v;BER)|lw#HZS;Euc^~xmSYO z)LzxzqwgV@A@fjnt3$cN4B+~g%3j*F$pehPB4C8@X5B!%0Ll!1Md|b1r{^W+``TfRJlR)WVL*MIl~w)xOKj%*h>SGD{($ zz015N=ZI=Pp4~O`Tlj8);1|sBAd@~#pEv*>8R?jN54Gzt1WvuQm`_B`uz?Z@Sw}P` zS@khO%u_unq+GGf>CT9zazt*qa|zAxD(y=vOgeZGmM*;mV_ntHVI%s@#Lg%emwGw! zkouSV8-B}%N^jI4!r#pBpDPRw0}x3Z|E`Qr}4AJ*P7pF(Bm!0@RnNJl^o!Vfp>8Cr?sX% z0DLzoS?WW=SIK25Bv7%m-j~2hiu`n}T-2bVCz#bTir*J$OuE^%Gm6Y7Nm@I)QIuYX z`6BCxzixkHSmSKi+JgMHOLg~_%LAaEr=Rc=G>OX1yoGtNh>l`ObT-O=Hk#_$O?Xei z+IB{`Jy~_0cj@QFHWIEla}hp)PMfE2Ns1 z$Lwt@f09k`%uAS!yGk@n&)kTD<(2ZRJMe+(p4Kw2E-F_+NnsTZGD>Dk^Q}ntqf$Z-a3Rm z{yXVYbAZFAi1nAz^g)^or#v{+sSP%)RVL#@3p=6Hf?;FHr7Rdo_@ybry zS`+VvMAWhOa8{qc`Rafc_ajB^eKc=YsSIic;1xZ0G3E1)LB~`20fQQWP*)~g9HSm3 zhEqRoqqpkgA3)D5d>tpVvt0KH-%>dtEQb2s(862b!X62GXA1<5TOzF}>kP$s+NRZq zX`UCAl8Vw2zbK2>`yw&`wSckd@+j>f-$d^?B_36;qzC zoU{58PbcxU3ib);lth2;z+yP%t*T z3lQ^ElNo_@a1#>EXhfZhD_&v+JMxnqV(fn!TA&6TAXchr9qy%bL=|2ZrwE_9FXL77eI@&cZzC7 z;NgcqJP&}7qNCtuXbL3C2%On+cti98IvqrqIA&VkL_q=!;|O2dn`(?k+QVnSL`iCC zPKJO1U$G%}g!DJ9=<}VSy)L1epdgW7wwU`YmvfgQX;wmLctdC~`KdD+P7rFIK@`nG zd?NKjw!e72zY-+IGg%fNTsuVg0|$y3vidcdA56E>bE*Hp&$`YtZjL%do z=-rf-3DfV+4*Z+yp=|it=GN>f;_V{#Hu-rPyV6c0i>jE4L)YK$-nbDkybg5qPR~-*T8I(o zNS>a>vQIVFjSM57vE;3pA4M>qq_X3N+so6@pTH3S;(v${K!0nhPs4s2?aw=I@jBpg zlcx|A_`<%-Q(=y$Zoo}NU0jd%BUS<*RV7EG74`zze6^F6sp@8d?|u$jY?8;U+q_O9 zc8>!4_W8JzR1zO;xHk`-q|YSY$sA!wqVLM4l{&Q+RQkacGjMl)k98r5UD0N*%1C_o z2T!(Nah?-Q!~0$1AaYpdkkEBSHya={+^%*X3JxS8c9*x_W&(-)1$0K{Kt#Vr(wG@y zzI24P*}Lz`g!d*fD7SXD{mcB}R~YCL?5FqSI5xFWCnLnn;qAWW{r6CRg;G~YyLCsnGUqdwu_*59n~wA4J^pd24K%vKk=7peMM#{ z(``y2faPW;M`|Gc=F4**;U9N*H5;9J1s0g3hEr$DAyT!}@8EcSn}kT0@ktvBYnfPz zpa;x$IIsjUk^WNL-qO|1S=_P=;e)%w>&WioyY(Vom5?BB*svL57>~}` z9{||qi?pa}dlupNVm`;&H8MG?!U&$;Qan0y@1jhZf=0&Bd_qTj^Z8g|J{>3f<7E`GgSb2=m*3hx9FYt17cL_*bsAj4pd+aRbo5$L5h4&@PJa}g<(Q4644JmS z$A=0=dQ}l4;#}M%e&xaGfZRsVoXxLf{bilui(pboKv|VPcAYNC)(5!;g7uHB=X}Gf zeme%Xc~=B^T+#U2J+~-U0kb@Uygzs+CC2xvnX4dSOqC*;bNi<@%9= z8Q=>Uf7CUAbO%JoX**6clvJl33|d!VH)ii!LT%u_JnJ$$_KG0_Xh?miIVtd0w0|3n z_=4MqCSS|3sG#ZzeBhLncw;F8o;-n?z#hUgb9rTcHt(m10{!8XGN( zeyi%0mF13=kJgG22>wSv#nyb%$;Z7KVS~vE^pwxj`eP$=E<21@SG()YU|0Tf{RgH1 ztP-%>EB>`ZWB9+W|22?!6ac@b5|L#uI#|)gK}~J5qyisA5ISr9u|_NDyTwb@_Aaj0SRM0xcn%H4F+Rop>`#ym0&CKA&Go6VjcOWMUxYI~pIeb=UT-O~mj3fz-PKejoV13$LspbXPd-WD{oEB=ZzlCbbhnffEQ+pDketAJYh8W z#s*^R#esS6cdD!H4;Cu`^LV$RO?fyjG1*;ho6%S=ksG(|r#L4r;>(!(M7(+m7-N-p zvU;it0?>nIu`FMFt=~9Sjbmd^4h3m{$O=SLrC}AzP}*xgsrGO6?~^usH*!#=B8e77 z)!W~>8>qx^!(Z*|oAUl`wV~tsR|AswSYLNPpN+-%#48IDcoX!r9Z=SrsM_ys+^?f= zKl5s;)$xXL8fTK8U-9}d{rzj4^fOqCM!w3PvSPz6-@4&Y zT$h-C+$JA&#YJnguGANiR!n2Pq@s(=sK;ZRM$^b2R`9{~7y>LBFX^-o;>&z8eY2X{5@9E?Xp&qIt;>2oDj9RGc{g@7%~$9Hl|s=e0|Y z;DJdZ)-tWshx?Eqn*7Ii;0~h@F(++QILaX15cIjLTN4*daIN;bGAgc zWJF7|+>v`LP;G^zRe0_|TU84hy9zy)i{_+@)LzQgFTIWAyXyHYiDaKT_gSrRI~~_) zPga{!*w!G)Uxo~gz=zKm7KZNW8b2NA<^I+J5j&feP;dmPSGF)K#pv|PYhEOJTh$hw zHh!?hI8oaS5oAOnNyYcHk}$ytuU<40$1oUI@5L}TQ~FH!$j48}07p^nxMpa53B?uZ zBvZ-KgX@p)(Z0VK#a(<)Y7q2kvMcvlh8SgyhP*`ljFhtLh53k-hzcHPeTLEMi|MmcOX4x#&7P6j#PQLyMxsJ@ix9UDiX7xhITSw?= zA+isK&WRXo`}zvzjdyX%>MLsEcR=@D3=Z$C;O4;K{+UBCZTb~LtGwihRbY+C!FvC* z&dA^=e(lPiJ28H^RrZDz9q#b@oa@7KxlbHmhFwAo$Nc#Sok zq367;UQ1C2?uBe2c$uqgmbG@;4CGqQLJ`_R=Wg{@9@}>$WaRkg$$jfi7+gVW73s+* z5h|T8&=o9M0flTR(j)ek(UJ)f8D9~$hpS$OGIN+#_h6gn(PS+6GI#>0N)yrco*Oau ztnuv>};KZ zw$27>9`-;dy@#-!}k{}-6RzaTmCHCJ?pF*tFp}EkRzEc4lm!5rLk;CIh1==&kIs#W+4ly z&=78($}q5_!YE*3buj-RbQ+e&Jb6K5bM6Vh?OFCC6GbwphvtI7xUwNYdkMrWegpJ7{NTz*1RLY(mEM1{fkEm?1VZeVFDuFZJ?N!d@a zsni4Mj;V07?8h9W(wE7^1wq_E44@OZX=_-b-2YbfU6G(7Y)t-Hs!Nx+Y@Tx&(y5b4 zYa)Y*|G@r$5S)ifyThftAj>9$Z|aCD3miv!iKFK9-I7y zs=xh_B5*YR9p%3sIz0ybbH@L0X!Bc2z{dFx;9oWO@!~&6Ob_w?TT;P;C-CCOhXRiQ z{_NQvCQ-jd6P#bb0Kex_kC*=0^*l6nzeO6HjvtmjZt)%?{7If4x})DB3a*b}gx~b| z|MW_avHs+p4|Myt2!ajs2HL z81x^=)?=JMYvTi<_$@E+{tLZ$y!_8H^H4y4O9ps<{4YECPdROy_48h+D l)8mDI?)1OMY#G=X|0QE9$-%-spg(zdfmu}`c%BLY@jr Do not compare this table directly against the Python-first hero table without naming the scope. This run exists to position WolfXL against non-Python spreadsheet tooling. diff --git a/results-cross-language/FIDELITY_DELTAS.md b/results-cross-language/FIDELITY_DELTAS.md new file mode 100644 index 0000000..a91d7e9 --- /dev/null +++ b/results-cross-language/FIDELITY_DELTAS.md @@ -0,0 +1,3 @@ +# Fidelity Deltas + +Need at least two runs in history.jsonl. diff --git a/results-cross-language/README.md b/results-cross-language/README.md new file mode 100644 index 0000000..d39f3a5 --- /dev/null +++ b/results-cross-language/README.md @@ -0,0 +1,268 @@ +# ExcelBench Results + +*Generated: 2026-04-29 14:26 UTC* +*Profile: xlsx* +*Excel Version: 16.105.3* +*Platform: Darwin-arm64* + +## Overview + +> Condensed view — shows the **best score** across read/write for each library. See [Full Results Matrix](#full-results-matrix) for the complete R/W breakdown. + +**Tier 0 — Basic Values** + +| Feature | apache-poi | excelize | +|---------|:-:|:-:| +| Cell Values | 🟢 | 🟢 | +| Formulas | 🟢 | 🟢 | +| Sheets | 🟢 | 🟢 | + +**Tier 1 — Formatting** + +| Feature | apache-poi | excelize | +|---------|:-:|:-:| +| Alignment | 🟢 | 🟢 | +| Bg Colors | 🟢 | 🟢 | +| Borders | 🟢 | 🟢 | +| Dimensions | 🟢 | 🟢 | +| Num Fmt | 🟢 | 🟢 | +| Text Fmt | 🟢 | 🟢 | + +**Tier 2 — Advanced** + +| Feature | apache-poi | excelize | +|---------|:-:|:-:| +| Comments | 🟢 | 🟢 | +| Cond Fmt | 🟢 | 🟢 | +| Validation | 🟢 | 🟢 | +| Freeze | 🟢 | 🟢 | +| Hyperlinks | 🟢 | 🟢 | +| Images | 🟢 | 🟢 | +| Merged | 🟢 | 🟢 | + +**Tier 3 — Workbook Metadata** + +| Feature | apache-poi | excelize | +|---------|:-:|:-:| +| Named Ranges | 🟢 | 🟢 | +| Tables | 🟢 | 🟢 | + +## Library Tiers + +> Libraries ranked by their best capability (max of read/write green features). + +| Tier | Library | Caps | Modify | Green Features | Summary | +|:----:|---------|:----:|:------:|:--------------:|---------| +| **S** | apache-poi | W | No | 18/18 | 18/18 features with full fidelity | +| **S** | excelize | W | No | 18/18 | 18/18 features with full fidelity | + +## Score Legend + +| Score | Meaning | +|-------|---------| +| 🟢 3 | Complete — all basic and edge cases pass | +| 🟡 2 | Functional — all basic pass, one or more edge cases fail | +| 🟠 1 | Minimal — at least one basic case passes, but not all basic cases | +| 🔴 0 | Unsupported — errors or data loss | +| ➖ | Not applicable | + +## Full Results Matrix + +**Tier 0 — Basic Values** + +| Feature | apache-poi (W) | excelize (W) | +|---------|------------|------------| +| [cell_values](#cell_values-details) | 🟢 3 | 🟢 3 | +| [formulas](#formulas-details) | 🟢 3 | 🟢 3 | +| [multiple_sheets](#multiple_sheets-details) | 🟢 3 | 🟢 3 | + +**Tier 1 — Formatting** + +| Feature | apache-poi (W) | excelize (W) | +|---------|------------|------------| +| [alignment](#alignment-details) | 🟢 3 | 🟢 3 | +| [background_colors](#background_colors-details) | 🟢 3 | 🟢 3 | +| [borders](#borders-details) | 🟢 3 | 🟢 3 | +| [dimensions](#dimensions-details) | 🟢 3 | 🟢 3 | +| [number_formats](#number_formats-details) | 🟢 3 | 🟢 3 | +| [text_formatting](#text_formatting-details) | 🟢 3 | 🟢 3 | + +**Tier 2 — Advanced** + +| Feature | apache-poi (W) | excelize (W) | +|---------|------------|------------| +| [comments](#comments-details) | 🟢 3 | 🟢 3 | +| [conditional_formatting](#conditional_formatting-details) | 🟢 3 | 🟢 3 | +| [data_validation](#data_validation-details) | 🟢 3 | 🟢 3 | +| [freeze_panes](#freeze_panes-details) | 🟢 3 | 🟢 3 | +| [hyperlinks](#hyperlinks-details) | 🟢 3 | 🟢 3 | +| [images](#images-details) | 🟢 3 | 🟢 3 | +| [merged_cells](#merged_cells-details) | 🟢 3 | 🟢 3 | +| [pivot_tables](#pivot_tables-details) | ➖ | ➖ | + +**Tier 3 — Workbook Metadata** + +| Feature | apache-poi (W) | excelize (W) | +|---------|------------|------------| +| [named_ranges](#named_ranges-details) | 🟢 3 | 🟢 3 | +| [tables](#tables-details) | 🟢 3 | 🟢 3 | + +## Notes + +- **pivot_tables**: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +## Statistics + +| Library | Mode | Tests | Passed | Failed | Pass Rate | Green Features | +|---------|------|-------|--------|--------|-----------|----------------| +| apache-poi | W | 125 | 125 | 0 | 100% | 18/18 | +| excelize | W | 125 | 125 | 0 | 100% | 18/18 | + +## Libraries Tested + +- **apache-poi** v5.5.1 (java) - write; modify: No +- **excelize** vgo-helper (go) - write; modify: No + +## Diagnostics Summary + +No diagnostics recorded. + +## Detailed Results + + +### alignment + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### background_colors + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### borders + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### cell_values + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### comments + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### conditional_formatting + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### data_validation + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### dimensions + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### formulas + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### freeze_panes + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### hyperlinks + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### images + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### merged_cells + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### multiple_sheets + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### named_ranges + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### number_formats + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### pivot_tables + +**apache-poi** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**excelize** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + + +### tables + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + + +### text_formatting + +**apache-poi** — Write: 🟢 3 + +**excelize** — Write: 🟢 3 + +--- +*Benchmark version: 0.1.0* \ No newline at end of file diff --git a/results-cross-language/history.jsonl b/results-cross-language/history.jsonl new file mode 100644 index 0000000..eac32d3 --- /dev/null +++ b/results-cross-language/history.jsonl @@ -0,0 +1 @@ +{"run_date": "2026-04-29T14:26:38.301955+00:00", "commit": "5f98de9", "profile": "xlsx", "scores": {"apache-poi": {"cell_values": {"read": null, "write": 3}, "formulas": {"read": null, "write": 3}, "text_formatting": {"read": null, "write": 3}, "background_colors": {"read": null, "write": 3}, "number_formats": {"read": null, "write": 3}, "alignment": {"read": null, "write": 3}, "borders": {"read": null, "write": 3}, "dimensions": {"read": null, "write": 3}, "multiple_sheets": {"read": null, "write": 3}, "merged_cells": {"read": null, "write": 3}, "conditional_formatting": {"read": null, "write": 3}, "data_validation": {"read": null, "write": 3}, "hyperlinks": {"read": null, "write": 3}, "images": {"read": null, "write": 3}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": null, "write": 3}, "freeze_panes": {"read": null, "write": 3}, "named_ranges": {"read": null, "write": 3}, "tables": {"read": null, "write": 3}}, "excelize": {"cell_values": {"read": null, "write": 3}, "formulas": {"read": null, "write": 3}, "text_formatting": {"read": null, "write": 3}, "background_colors": {"read": null, "write": 3}, "number_formats": {"read": null, "write": 3}, "alignment": {"read": null, "write": 3}, "borders": {"read": null, "write": 3}, "dimensions": {"read": null, "write": 3}, "multiple_sheets": {"read": null, "write": 3}, "merged_cells": {"read": null, "write": 3}, "conditional_formatting": {"read": null, "write": 3}, "data_validation": {"read": null, "write": 3}, "hyperlinks": {"read": null, "write": 3}, "images": {"read": null, "write": 3}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": null, "write": 3}, "freeze_panes": {"read": null, "write": 3}, "named_ranges": {"read": null, "write": 3}, "tables": {"read": null, "write": 3}}}} diff --git a/results-cross-language/matrix.csv b/results-cross-language/matrix.csv new file mode 100644 index 0000000..65bb1e9 --- /dev/null +++ b/results-cross-language/matrix.csv @@ -0,0 +1,39 @@ +library,feature,read_score,write_score +apache-poi,cell_values,,3 +excelize,cell_values,,3 +apache-poi,formulas,,3 +excelize,formulas,,3 +apache-poi,text_formatting,,3 +excelize,text_formatting,,3 +apache-poi,background_colors,,3 +excelize,background_colors,,3 +apache-poi,number_formats,,3 +excelize,number_formats,,3 +apache-poi,alignment,,3 +excelize,alignment,,3 +apache-poi,borders,,3 +excelize,borders,,3 +apache-poi,dimensions,,3 +excelize,dimensions,,3 +apache-poi,multiple_sheets,,3 +excelize,multiple_sheets,,3 +apache-poi,merged_cells,,3 +excelize,merged_cells,,3 +apache-poi,conditional_formatting,,3 +excelize,conditional_formatting,,3 +apache-poi,data_validation,,3 +excelize,data_validation,,3 +apache-poi,hyperlinks,,3 +excelize,hyperlinks,,3 +apache-poi,images,,3 +excelize,images,,3 +apache-poi,pivot_tables,, +excelize,pivot_tables,, +apache-poi,comments,,3 +excelize,comments,,3 +apache-poi,freeze_panes,,3 +excelize,freeze_panes,,3 +apache-poi,named_ranges,,3 +excelize,named_ranges,,3 +apache-poi,tables,,3 +excelize,tables,,3 \ No newline at end of file diff --git a/results-cross-language/results.json b/results-cross-language/results.json new file mode 100644 index 0000000..f0be09a --- /dev/null +++ b/results-cross-language/results.json @@ -0,0 +1,5288 @@ +{ + "metadata": { + "benchmark_version": "0.1.0", + "run_date": "2026-04-29T14:26:38.301955+00:00", + "excel_version": "16.105.3", + "platform": "Darwin-arm64", + "profile": "xlsx" + }, + "libraries": { + "apache-poi": { + "name": "apache-poi", + "version": "5.5.1", + "language": "java", + "capabilities": [ + "write" + ] + }, + "excelize": { + "name": "excelize", + "version": "go-helper", + "language": "go", + "capabilities": [ + "write" + ] + } + }, + "results": [ + { + "feature": "cell_values", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "string_simple": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "write": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "write": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "string_simple": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "write": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "write": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "formula_sum": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "formula_sum": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "bold": { + "write": { + "passed": true, + "expected": { + "bold": true + }, + "actual": { + "bold": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "write": { + "passed": true, + "expected": { + "italic": true + }, + "actual": { + "italic": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "write": { + "passed": true, + "expected": { + "underline": "single" + }, + "actual": { + "underline": "single", + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "write": { + "passed": true, + "expected": { + "underline": "double" + }, + "actual": { + "underline": "double", + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "write": { + "passed": true, + "expected": { + "strikethrough": true + }, + "actual": { + "strikethrough": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "write": { + "passed": true, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "bold": true, + "italic": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "write": { + "passed": true, + "expected": { + "font_size": 8 + }, + "actual": { + "font_name": "Calibri", + "font_size": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "write": { + "passed": true, + "expected": { + "font_size": 14 + }, + "actual": { + "font_name": "Calibri", + "font_size": 14.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "write": { + "passed": true, + "expected": { + "font_size": 24 + }, + "actual": { + "font_name": "Calibri", + "font_size": 24.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "write": { + "passed": true, + "expected": { + "font_size": 36 + }, + "actual": { + "font_name": "Calibri", + "font_size": 36.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "write": { + "passed": true, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Arial", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "write": { + "passed": true, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Times New Roman", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "write": { + "passed": true, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Courier New", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "write": { + "passed": true, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "write": { + "passed": true, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "write": { + "passed": true, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "write": { + "passed": true, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "write": { + "passed": true, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "bold": true, + "font_name": "Calibri", + "font_size": 16.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "bold": { + "write": { + "passed": true, + "expected": { + "bold": true + }, + "actual": { + "bold": true, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "write": { + "passed": true, + "expected": { + "italic": true + }, + "actual": { + "italic": true, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "write": { + "passed": true, + "expected": { + "underline": "single" + }, + "actual": { + "underline": "single", + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "write": { + "passed": true, + "expected": { + "underline": "double" + }, + "actual": { + "underline": "double", + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "write": { + "passed": true, + "expected": { + "strikethrough": true + }, + "actual": { + "strikethrough": true, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "write": { + "passed": true, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "bold": true, + "italic": true, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "write": { + "passed": true, + "expected": { + "font_size": 8 + }, + "actual": { + "font_size": 8.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "write": { + "passed": true, + "expected": { + "font_size": 14 + }, + "actual": { + "font_size": 14.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "write": { + "passed": true, + "expected": { + "font_size": 24 + }, + "actual": { + "font_size": 24.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "write": { + "passed": true, + "expected": { + "font_size": 36 + }, + "actual": { + "font_size": 36.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "write": { + "passed": true, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Arial", + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "write": { + "passed": true, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Times New Roman", + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "write": { + "passed": true, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Courier New", + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "write": { + "passed": true, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "write": { + "passed": true, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "write": { + "passed": true, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "write": { + "passed": true, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "write": { + "passed": true, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "bold": true, + "font_size": 16.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "bg_red": { + "write": { + "passed": true, + "expected": { + "bg_color": "#FF0000" + }, + "actual": { + "bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "write": { + "passed": true, + "expected": { + "bg_color": "#0000FF" + }, + "actual": { + "bg_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "write": { + "passed": true, + "expected": { + "bg_color": "#00FF00" + }, + "actual": { + "bg_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "write": { + "passed": true, + "expected": { + "bg_color": "#8B4513" + }, + "actual": { + "bg_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "bg_red": { + "write": { + "passed": true, + "expected": { + "bg_color": "#FF0000" + }, + "actual": { + "bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "write": { + "passed": true, + "expected": { + "bg_color": "#0000FF" + }, + "actual": { + "bg_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "write": { + "passed": true, + "expected": { + "bg_color": "#00FF00" + }, + "actual": { + "bg_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "write": { + "passed": true, + "expected": { + "bg_color": "#8B4513" + }, + "actual": { + "bg_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "numfmt_currency": { + "write": { + "passed": true, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "$#,##0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "write": { + "passed": true, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "0.00%" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "write": { + "passed": true, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "yyyy-mm-dd" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "write": { + "passed": true, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "0.00E+00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "write": { + "passed": true, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "\"USD\" 0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "numfmt_currency": { + "write": { + "passed": true, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "$#,##0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "write": { + "passed": true, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "0.00%" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "write": { + "passed": true, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "yyyy-mm-dd" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "write": { + "passed": true, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "0.00E+00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "write": { + "passed": true, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "\"USD\" 0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "h_left": { + "write": { + "passed": true, + "expected": { + "h_align": "left" + }, + "actual": { + "h_align": "left" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "write": { + "passed": true, + "expected": { + "h_align": "center" + }, + "actual": { + "h_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "write": { + "passed": true, + "expected": { + "h_align": "right" + }, + "actual": { + "h_align": "right" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "write": { + "passed": true, + "expected": { + "v_align": "top" + }, + "actual": { + "v_align": "top" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "write": { + "passed": true, + "expected": { + "v_align": "center" + }, + "actual": { + "v_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "write": { + "passed": true, + "expected": { + "v_align": "bottom" + }, + "actual": { + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "write": { + "passed": true, + "expected": { + "wrap": true + }, + "actual": { + "wrap": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "write": { + "passed": true, + "expected": { + "rotation": 45 + }, + "actual": { + "rotation": 45 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "write": { + "passed": true, + "expected": { + "indent": 2 + }, + "actual": { + "indent": 2.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "h_left": { + "write": { + "passed": true, + "expected": { + "h_align": "left" + }, + "actual": { + "h_align": "left" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "write": { + "passed": true, + "expected": { + "h_align": "center" + }, + "actual": { + "h_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "write": { + "passed": true, + "expected": { + "h_align": "right" + }, + "actual": { + "h_align": "right" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "write": { + "passed": true, + "expected": { + "v_align": "top" + }, + "actual": { + "v_align": "top" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "write": { + "passed": true, + "expected": { + "v_align": "center" + }, + "actual": { + "v_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "write": { + "passed": true, + "expected": { + "v_align": "bottom" + }, + "actual": { + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "write": { + "passed": true, + "expected": { + "wrap": true + }, + "actual": { + "wrap": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "write": { + "passed": true, + "expected": { + "rotation": 45 + }, + "actual": { + "rotation": 45 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "write": { + "passed": true, + "expected": { + "indent": 2 + }, + "actual": { + "indent": 2.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "thin_all": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": { + "border_style": "thin", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "write": { + "passed": true, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": { + "border_style": "medium", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "write": { + "passed": true, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": { + "border_style": "thick", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "write": { + "passed": true, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": { + "border_style": "double", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "write": { + "passed": true, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "write": { + "passed": true, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": { + "border_style": "dotted", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "write": { + "passed": true, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "write": { + "passed": true, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "write": { + "passed": true, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": { + "border_top": "thin", + "border_top_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": { + "border_bottom": "thin", + "border_bottom_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": { + "border_left": "thin", + "border_left_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": { + "border_right": "thin", + "border_right_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": { + "border_diagonal_up": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "write": { + "passed": true, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "write": { + "passed": true, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "write": { + "passed": true, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00", + "border_style": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "thin_all": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": { + "border_style": "thin", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "write": { + "passed": true, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": { + "border_style": "medium", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "write": { + "passed": true, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": { + "border_style": "thick", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "write": { + "passed": true, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": { + "border_style": "double", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "write": { + "passed": true, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "write": { + "passed": true, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": { + "border_style": "dotted", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "write": { + "passed": true, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "write": { + "passed": true, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "write": { + "passed": true, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": { + "border_top": "thin", + "border_top_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": { + "border_bottom": "thin", + "border_bottom_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": { + "border_left": "thin", + "border_left_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": { + "border_right": "thin", + "border_right_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": { + "border_diagonal_up": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "write": { + "passed": true, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "write": { + "passed": true, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "write": { + "passed": true, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00", + "border_style": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "row_height_30": { + "write": { + "passed": true, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": 30.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "write": { + "passed": true, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": 45.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "write": { + "passed": true, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 20.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "write": { + "passed": true, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "row_height_30": { + "write": { + "passed": true, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": 30.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "write": { + "passed": true, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": 45.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "write": { + "passed": true, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 20.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "write": { + "passed": true, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "sheet_names": { + "write": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "sheet_names": { + "write": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "merge_horizontal": { + "write": { + "passed": true, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "write": { + "passed": true, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "write": { + "passed": true, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "write": { + "passed": true, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "merge_horizontal": { + "write": { + "passed": true, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "write": { + "passed": true, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "write": { + "passed": true, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "write": { + "passed": true, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "cf_cell_gt": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "format": { + "bg_color": "#FF00FF" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "cf_cell_gt": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "format": { + "bg_color": "#FF00FF" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "dv_list_csv": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "dv_list_csv": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "link_external": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "link_external": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "image_one_cell": { + "write": { + "passed": true, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "write": { + "passed": true, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "image_one_cell": { + "write": { + "passed": true, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "write": { + "passed": true, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "pivot_tables", + "library": "apache-poi", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "excelize", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "comments", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "comment_legacy": { + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "comment_legacy": { + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "freeze_b2": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "freeze_b2": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "nr_simple_cell": { + "write": { + "passed": true, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "write": { + "passed": true, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "write": { + "passed": true, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "write": { + "passed": true, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "write": { + "passed": true, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "write": { + "passed": true, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "nr_simple_cell": { + "write": { + "passed": true, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "write": { + "passed": true, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "write": { + "passed": true, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "write": { + "passed": true, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "write": { + "passed": true, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "write": { + "passed": true, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "apache-poi", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "tbl_basic": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "excelize", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "tbl_basic": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "write": { + "passed": true, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + } + ] +} \ No newline at end of file diff --git a/results-release-2026-04-28/DASHBOARD.md b/results-release-2026-04-28/DASHBOARD.md new file mode 100644 index 0000000..dae70c4 --- /dev/null +++ b/results-release-2026-04-28/DASHBOARD.md @@ -0,0 +1,37 @@ +# ExcelBench Dashboard + +*Profile: xlsx | Generated: 2026-04-29T03:12:20.382948+00:00* + +> Combined fidelity and performance view. Fidelity shows correctness; +> throughput shows speed. Use this to find the right library for your needs. + +## Library Comparison + +| Library | Caps | Modify | Green Features | Pass Rate | Best For | +|---------|:----:|:------:|:--------------:|:---------:|----------| +| openpyxl | R+W | Rewrite | 18/18 | 100% | Full-fidelity read + write | +| wolfxl | R+W | Patch | 18/18 | 100% | General use | +| xlsxwriter | W | No | 15/18 | 90% | High-fidelity write-only workflows | +| xlsxwriter-constmem | W | No | 12/18 | 84% | Large file writes with memory limits | +| xlwt | W | No | 4/18 | 58% | Legacy .xls file writes | +| openpyxl-readonly | R | No | 3/18 | 21% | Streaming reads when formatting isn't needed | +| pandas | R+W | Rebuild | 3/18 | 18% | Data analysis pipelines (accept NaN coercion) | +| pyexcel | R+W | Rebuild | 3/18 | 19% | Multi-format compatibility layer | +| tablib | R+W | Rebuild | 3/18 | 19% | Dataset export/import workflows | +| pylightxl | R+W | Rebuild | 2/18 | 18% | Lightweight value extraction | +| python-calamine | R | No | 1/18 | 15% | Fast bulk value reads | +| polars | R | No | 0/18 | 14% | High-performance DataFrames (values only) | + +## Key Insights + +- **Fidelity leaders**: openpyxl, wolfxl (18/18 green features) +- **Abstraction cost**: pandas wraps openpyxl but drops from 18 to 3 green features due to DataFrame coercion +- **Optimization cost**: xlsxwriter constant_memory mode loses 3 green features for lower memory usage + +## Best Adapter by Workload Profile + +| Workload Size | Best Read Adapter | Best Write Adapter | +|---------------|-------------------|--------------------| +| small | — | — | +| medium | — | — | +| large | — | — | diff --git a/results-release-2026-04-28/FIDELITY_DELTAS.md b/results-release-2026-04-28/FIDELITY_DELTAS.md new file mode 100644 index 0000000..a91d7e9 --- /dev/null +++ b/results-release-2026-04-28/FIDELITY_DELTAS.md @@ -0,0 +1,3 @@ +# Fidelity Deltas + +Need at least two runs in history.jsonl. diff --git a/results-release-2026-04-28/README.md b/results-release-2026-04-28/README.md new file mode 100644 index 0000000..f4dd499 --- /dev/null +++ b/results-release-2026-04-28/README.md @@ -0,0 +1,3337 @@ +# ExcelBench Results + +*Generated: 2026-04-29 03:12 UTC* +*Profile: xlsx* +*Excel Version: 16.105.3* +*Platform: Darwin-arm64* + +## Overview + +> Condensed view — shows the **best score** across read/write for each library. See [Full Results Matrix](#full-results-matrix) for the complete R/W breakdown. + +**Tier 0 — Basic Values** + +| Feature | openpyxl | opxl-readonly | pandas | polars | pyexcel | pylightxl | calamine | tablib | wolfxl | xlsxwriter | xlsx-constmem | xlwt | +|---------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| +| Cell Values | 🟢 | 🟢 | 🟢 | 🟠 | 🟢 | 🟢 | 🟠 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | +| Formulas | 🟢 | 🟢 | 🟢 | 🔴 | 🟢 | 🟢 | 🔴 | 🟢 | 🟢 | 🟢 | 🟢 | 🔴 | +| Sheets | 🟢 | 🟢 | 🟢 | 🟠 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | + +**Tier 1 — Formatting** + +| Feature | openpyxl | opxl-readonly | pandas | polars | pyexcel | pylightxl | calamine | tablib | wolfxl | xlsxwriter | xlsx-constmem | xlwt | +|---------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| +| Alignment | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟠 | 🟠 | 🟢 | +| Bg Colors | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🟢 | 🟠 | +| Borders | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🟢 | 🟠 | +| Dimensions | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🟠 | 🟠 | +| Num Fmt | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🟢 | 🟢 | +| Text Fmt | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🟢 | 🟠 | + +**Tier 2 — Advanced** + +| Feature | openpyxl | opxl-readonly | pandas | polars | pyexcel | pylightxl | calamine | tablib | wolfxl | xlsxwriter | xlsx-constmem | xlwt | +|---------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| +| Comments | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🔴 | 🔴 | +| Cond Fmt | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🟢 | 🔴 | +| Validation | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🟢 | 🔴 | +| Freeze | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🟢 | 🔴 | +| Hyperlinks | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🟢 | 🔴 | +| Images | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🔴 | 🔴 | +| Merged | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🟢 | 🟢 | 🔴 | + +**Tier 3 — Workbook Metadata** + +| Feature | openpyxl | opxl-readonly | pandas | polars | pyexcel | pylightxl | calamine | tablib | wolfxl | xlsxwriter | xlsx-constmem | xlwt | +|---------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| +| Named Ranges | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🔴 | 🔴 | 🔴 | +| Tables | 🟢 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🟢 | 🔴 | 🔴 | 🔴 | + +## Library Tiers + +> Libraries ranked by their best capability (max of read/write green features). + +| Tier | Library | Caps | Modify | Green Features | Summary | +|:----:|---------|:----:|:------:|:--------------:|---------| +| **S** | openpyxl | R+W | Rewrite | 18/18 | Reference adapter — full read + write fidelity | +| **S** | wolfxl | R+W | Patch | 18/18 | Hybrid Rust+Python — near-full fidelity at 3-9x throughput | +| **A** | xlsxwriter | W | No | 15/18 | Best write-only option — full formatting support | +| **B** | xlsxwriter-constmem | W | No | 12/18 | Memory-optimized write — loses images, comments, row height | +| **B** | xlwt | W | No | 4/18 | Legacy .xls writer — basic formatting subset | +| **C** | openpyxl-readonly | R | No | 3/18 | Streaming read — loses all formatting metadata | +| **C** | pandas | R+W | Rebuild | 3/18 | DataFrame abstraction — errors coerced to NaN on read | +| **C** | pyexcel | R+W | Rebuild | 3/18 | Meta-library wrapping openpyxl — preserves error values | +| **C** | tablib | R+W | Rebuild | 3/18 | Dataset wrapper — matches pyexcel on fidelity | +| **C** | pylightxl | R+W | Rebuild | 2/18 | Lightweight — basic values, no formatting API | +| **C** | python-calamine | R | No | 1/18 | Fast Rust-backed reader — cell values + sheet names only | +| **D** | polars | R | No | 0/18 | Rust DataFrame reader — columnar type coercion drops fidelity | + +## Score Legend + +| Score | Meaning | +|-------|---------| +| 🟢 3 | Complete — all basic and edge cases pass | +| 🟡 2 | Functional — all basic pass, one or more edge cases fail | +| 🟠 1 | Minimal — at least one basic case passes, but not all basic cases | +| 🔴 0 | Unsupported — errors or data loss | +| ➖ | Not applicable | + +## Full Results Matrix + +**Tier 0 — Basic Values** + +| Feature | openpyxl (R) | openpyxl (W) | openpyxl-readonly (R) | pandas (R) | pandas (W) | polars (R) | pyexcel (R) | pyexcel (W) | pylightxl (R) | pylightxl (W) | python-calamine (R) | tablib (R) | tablib (W) | wolfxl (R) | wolfxl (W) | xlrd (R) | xlsxwriter (W) | xlsxwriter-constmem (W) | xlwt (W) | +|---------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------| +| [cell_values](#cell_values-details) | 🟢 3 | 🟢 3 | 🟢 3 | 🟠 1 | 🟢 3 | 🟠 1 | 🟢 3 | 🟢 3 | 🟢 3 | 🟠 1 | 🟠 1 | 🟢 3 | 🟢 3 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🟢 3 | +| [formulas](#formulas-details) | 🟢 3 | 🟢 3 | 🟢 3 | 🔴 0 | 🟢 3 | 🔴 0 | 🔴 0 | 🟢 3 | 🔴 0 | 🟢 3 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🔴 0 | +| [multiple_sheets](#multiple_sheets-details) | 🟢 3 | 🟢 3 | 🟢 3 | 🟢 3 | 🟢 3 | 🟠 1 | 🟢 3 | 🟢 3 | 🟢 3 | 🟢 3 | 🟢 3 | 🟢 3 | 🟢 3 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🟢 3 | + +**Tier 1 — Formatting** + +| Feature | openpyxl (R) | openpyxl (W) | openpyxl-readonly (R) | pandas (R) | pandas (W) | polars (R) | pyexcel (R) | pyexcel (W) | pylightxl (R) | pylightxl (W) | python-calamine (R) | tablib (R) | tablib (W) | wolfxl (R) | wolfxl (W) | xlrd (R) | xlsxwriter (W) | xlsxwriter-constmem (W) | xlwt (W) | +|---------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------| +| [alignment](#alignment-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟠 1 | 🟠 1 | 🟢 3 | +| [background_colors](#background_colors-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🟠 1 | +| [borders](#borders-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🟠 1 | +| [dimensions](#dimensions-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟠 1 | 🟠 1 | +| [number_formats](#number_formats-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🟢 3 | +| [text_formatting](#text_formatting-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🟠 1 | + +**Tier 2 — Advanced** + +| Feature | openpyxl (R) | openpyxl (W) | openpyxl-readonly (R) | pandas (R) | pandas (W) | polars (R) | pyexcel (R) | pyexcel (W) | pylightxl (R) | pylightxl (W) | python-calamine (R) | tablib (R) | tablib (W) | wolfxl (R) | wolfxl (W) | xlrd (R) | xlsxwriter (W) | xlsxwriter-constmem (W) | xlwt (W) | +|---------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------| +| [comments](#comments-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🔴 0 | 🔴 0 | +| [conditional_formatting](#conditional_formatting-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🔴 0 | +| [data_validation](#data_validation-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🔴 0 | +| [freeze_panes](#freeze_panes-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🔴 0 | +| [hyperlinks](#hyperlinks-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🔴 0 | +| [images](#images-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🔴 0 | 🔴 0 | +| [merged_cells](#merged_cells-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🟢 3 | 🟢 3 | 🔴 0 | +| [pivot_tables](#pivot_tables-details) | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | ➖ | + +**Tier 3 — Workbook Metadata** + +| Feature | openpyxl (R) | openpyxl (W) | openpyxl-readonly (R) | pandas (R) | pandas (W) | polars (R) | pyexcel (R) | pyexcel (W) | pylightxl (R) | pylightxl (W) | python-calamine (R) | tablib (R) | tablib (W) | wolfxl (R) | wolfxl (W) | xlrd (R) | xlsxwriter (W) | xlsxwriter-constmem (W) | xlwt (W) | +|---------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------|------------| +| [named_ranges](#named_ranges-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🔴 0 | 🔴 0 | 🔴 0 | +| [tables](#tables-details) | 🟢 3 | 🟢 3 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🔴 0 | 🟢 3 | 🟢 3 | ➖ | 🔴 0 | 🔴 0 | 🔴 0 | + +## Notes + +- **alignment**: Known limitation: pylightxl alignment write is a no-op because the library does not support formatting writes. +- **cell_values**: Known limitation: pylightxl cell-values write has date/boolean/error fidelity limits due to writer encoding behavior. +- **alignment**: Known limitation: python-calamine alignment read is limited because its API does not expose style/alignment metadata. +- **cell_values**: Known limitation: python-calamine can surface formula error cells as blank values in current API responses. +- **cell_values, formulas, ... (19 features)**: Not applicable: xlrd does not support .xlsx input +- **pivot_tables**: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +## Statistics + +| Library | Mode | Tests | Passed | Failed | Pass Rate | Green Features | +|---------|------|-------|--------|--------|-----------|----------------| +| openpyxl | R | 125 | 125 | 0 | 100% | 18/18 | +| openpyxl | W | 125 | 125 | 0 | 100% | 18/18 | +| openpyxl-readonly | R | 125 | 26 | 99 | 21% | 3/18 | +| pandas | R | 125 | 19 | 106 | 15% | 1/18 | +| pandas | W | 125 | 26 | 99 | 21% | 3/18 | +| polars | R | 125 | 17 | 108 | 14% | 0/18 | +| pyexcel | R | 125 | 22 | 103 | 18% | 2/18 | +| pyexcel | W | 125 | 26 | 99 | 21% | 3/18 | +| pylightxl | R | 125 | 22 | 103 | 18% | 2/18 | +| pylightxl | W | 125 | 22 | 103 | 18% | 2/18 | +| python-calamine | R | 125 | 19 | 106 | 15% | 1/18 | +| tablib | R | 125 | 22 | 103 | 18% | 2/18 | +| tablib | W | 125 | 26 | 99 | 21% | 3/18 | +| wolfxl | R | 125 | 125 | 0 | 100% | 18/18 | +| wolfxl | W | 125 | 125 | 0 | 100% | 18/18 | +| xlsxwriter | W | 125 | 112 | 13 | 90% | 15/18 | +| xlsxwriter-constmem | W | 125 | 105 | 20 | 84% | 12/18 | +| xlwt | W | 125 | 72 | 53 | 58% | 4/18 | + +## Libraries Tested + +- **openpyxl** v3.1.5 (python) - read, write; modify: Rewrite +- **openpyxl-readonly** v3.1.5 (python) - read; modify: No +- **pandas** v3.0.0 (python) - read, write; modify: Rebuild +- **polars** v1.38.1 (python) - read; modify: No +- **pyexcel** v0.7.4 (python) - read, write; modify: Rebuild +- **pylightxl** v1.61 (python) - read, write; modify: Rebuild +- **python-calamine** v0.6.1 (python) - read; modify: No +- **tablib** v3.9.0 (python) - read, write; modify: Rebuild +- **wolfxl** vcal=2.0.0+rxw=2.0.0 (rust) - modify, read, write; modify: Patch +- **xlrd** v2.0.2 (python) - read; modify: No +- **xlsxwriter** v3.2.9 (python) - write; modify: No +- **xlsxwriter-constmem** v3.2.9 (python) - write; modify: No +- **xlwt** v1.3.0 (python) - write; modify: No + +## Diagnostics Summary + +| Group | Value | Count | +|-------|-------|-------| +| category | data_mismatch | 966 | +| category | internal | 31 | +| category | invalid_input | 61 | +| category | unsupported_feature | 156 | +| severity | error | 1058 | +| severity | warning | 156 | + +### Diagnostic Details + +| Feature | Library | Test Case | Operation | Category | Severity | Message | +|---------|---------|-----------|-----------|----------|----------|---------| +| cell_values | python-calamine | error_div0 | read | data_mismatch | error | Expected values did not match actual values: expected={'type': 'error', 'value': '#DIV/0!'}, actual={'type': 'blank'} | +| cell_values | python-calamine | error_na | read | data_mismatch | error | Expected values did not match actual values: expected={'type': 'error', 'value': '#N/A'}, actual={'type': 'blank'} | +| cell_values | python-calamine | error_value | read | data_mismatch | error | Expected values did not match actual values: expected={'type': 'error', 'value': '#VALUE!'}, actual={'type': 'blank'} | +| cell_values | pylightxl | date_standard | write | data_mismatch | error | Expected values did not match actual values: expected={'type': 'date', 'value': '2026-02-04'}, actual={'type': 'string', 'value': '2026-02-04'} | +| cell_values | pylightxl | datetime | write | data_mismatch | error | Expected values did not match actual values: expected={'type': 'datetime', 'value': '2026-02-04T10:30:45'}, actual={'type': 'string', 'value': '2026-02-04T10:30:45'} | +| cell_values | pylightxl | boolean_true | write | data_mismatch | error | Expected values did not match actual values: expected={'type': 'boolean', 'value': True}, actual={'type': 'number', 'value': 1} | +| cell_values | pylightxl | boolean_false | write | data_mismatch | error | Expected values did not match actual values: expected={'type': 'boolean', 'value': False}, actual={'type': 'number', 'value': 0} | +| cell_values | pandas | error_div0 | read | data_mismatch | error | Expected values did not match actual values: expected={'type': 'error', 'value': '#DIV/0!'}, actual={'type': 'blank'} | +| cell_values | pandas | error_na | read | data_mismatch | error | Expected values did not match actual values: expected={'type': 'error', 'value': '#N/A'}, actual={'type': 'blank'} | +| cell_values | pandas | error_value | read | data_mismatch | error | Expected values did not match actual values: expected={'type': 'error', 'value': '#VALUE!'}, actual={'type': 'blank'} | +| cell_values | polars | error_div0 | read | data_mismatch | error | Expected values did not match actual values: expected={'type': 'error', 'value': '#DIV/0!'}, actual={'type': 'blank'} | +| cell_values | polars | error_na | read | data_mismatch | error | Expected values did not match actual values: expected={'type': 'error', 'value': '#N/A'}, actual={'type': 'blank'} | +| cell_values | polars | error_value | read | data_mismatch | error | Expected values did not match actual values: expected={'type': 'error', 'value': '#VALUE!'}, actual={'type': 'blank'} | +| formulas | python-calamine | formula_sum | read | internal | error | RuntimeError: Expected formula, got number | +| formulas | python-calamine | formula_cell_ref | read | internal | error | RuntimeError: Expected formula, got blank | +| formulas | python-calamine | formula_concat | read | internal | error | RuntimeError: Expected formula, got string | +| formulas | python-calamine | formula_cross_sheet | read | internal | error | RuntimeError: Expected formula, got number | +| formulas | pylightxl | formula_sum | read | internal | error | RuntimeError: Expected formula, got number | +| formulas | pylightxl | formula_cell_ref | read | internal | error | RuntimeError: Expected formula, got error | +| formulas | pylightxl | formula_concat | read | internal | error | RuntimeError: Expected formula, got string | +| formulas | pylightxl | formula_cross_sheet | read | internal | error | RuntimeError: Expected formula, got number | +| formulas | pyexcel | formula_sum | read | internal | error | RuntimeError: Expected formula, got number | +| formulas | pyexcel | formula_cell_ref | read | internal | error | RuntimeError: Expected formula, got error | +| formulas | pyexcel | formula_concat | read | internal | error | RuntimeError: Expected formula, got string | +| formulas | pyexcel | formula_cross_sheet | read | internal | error | RuntimeError: Expected formula, got number | +| formulas | xlwt | formula_sum | write | internal | error | RuntimeError: Expected formula, got string | +| formulas | xlwt | formula_cell_ref | write | internal | error | RuntimeError: Expected formula, got string | +| formulas | xlwt | formula_concat | write | internal | error | RuntimeError: Expected formula, got string | +| formulas | xlwt | formula_cross_sheet | write | internal | error | RuntimeError: Expected formula, got string | +| formulas | pandas | formula_sum | read | internal | error | RuntimeError: Expected formula, got number | +| formulas | pandas | formula_cell_ref | read | internal | error | RuntimeError: Expected formula, got blank | +| formulas | pandas | formula_concat | read | internal | error | RuntimeError: Expected formula, got string | +| formulas | pandas | formula_cross_sheet | read | internal | error | RuntimeError: Expected formula, got number | +| formulas | polars | formula_sum | read | internal | error | RuntimeError: Expected formula, got number | +| formulas | polars | formula_cell_ref | read | internal | error | RuntimeError: Expected formula, got blank | +| formulas | polars | formula_concat | read | internal | error | RuntimeError: Expected formula, got string | +| formulas | polars | formula_cross_sheet | read | internal | error | RuntimeError: Expected formula, got number | +| formulas | tablib | formula_sum | read | internal | error | RuntimeError: Expected formula, got number | +| formulas | tablib | formula_cell_ref | read | internal | error | RuntimeError: Expected formula, got error | +| formulas | tablib | formula_concat | read | internal | error | RuntimeError: Expected formula, got string | +| formulas | tablib | formula_cross_sheet | read | internal | error | RuntimeError: Expected formula, got number | +| text_formatting | python-calamine | bold | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True}, actual={} | +| text_formatting | python-calamine | italic | read | data_mismatch | error | Expected values did not match actual values: expected={'italic': True}, actual={} | +| text_formatting | python-calamine | underline_single | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'single'}, actual={} | +| text_formatting | python-calamine | underline_double | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'double'}, actual={} | +| text_formatting | python-calamine | strikethrough | read | data_mismatch | error | Expected values did not match actual values: expected={'strikethrough': True}, actual={} | +| text_formatting | python-calamine | bold_italic | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={} | +| text_formatting | python-calamine | font_size_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 8}, actual={} | +| text_formatting | python-calamine | font_size_14 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 14}, actual={} | +| text_formatting | python-calamine | font_size_24 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 24}, actual={} | +| text_formatting | python-calamine | font_size_36 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 36}, actual={} | +| text_formatting | python-calamine | font_arial | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={} | +| text_formatting | python-calamine | font_times | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={} | +| text_formatting | python-calamine | font_courier | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={} | +| text_formatting | python-calamine | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={} | +| text_formatting | python-calamine | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={} | +| text_formatting | python-calamine | color_green | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={} | +| text_formatting | python-calamine | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={} | +| text_formatting | python-calamine | combined | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={} | +| text_formatting | pylightxl | bold | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True}, actual={} | +| text_formatting | pylightxl | italic | read | data_mismatch | error | Expected values did not match actual values: expected={'italic': True}, actual={} | +| text_formatting | pylightxl | underline_single | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'single'}, actual={} | +| text_formatting | pylightxl | underline_double | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'double'}, actual={} | +| text_formatting | pylightxl | strikethrough | read | data_mismatch | error | Expected values did not match actual values: expected={'strikethrough': True}, actual={} | +| text_formatting | pylightxl | bold_italic | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={} | +| text_formatting | pylightxl | font_size_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 8}, actual={} | +| text_formatting | pylightxl | font_size_14 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 14}, actual={} | +| text_formatting | pylightxl | font_size_24 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 24}, actual={} | +| text_formatting | pylightxl | font_size_36 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 36}, actual={} | +| text_formatting | pylightxl | font_arial | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={} | +| text_formatting | pylightxl | font_times | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={} | +| text_formatting | pylightxl | font_courier | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={} | +| text_formatting | pylightxl | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={} | +| text_formatting | pylightxl | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={} | +| text_formatting | pylightxl | color_green | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={} | +| text_formatting | pylightxl | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={} | +| text_formatting | pylightxl | combined | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={} | +| text_formatting | pylightxl | bold | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | italic | write | data_mismatch | error | Expected values did not match actual values: expected={'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | underline_single | write | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'single'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | underline_double | write | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'double'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | strikethrough | write | data_mismatch | error | Expected values did not match actual values: expected={'strikethrough': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | bold_italic | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | font_size_8 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 8}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | font_size_14 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 14}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | font_size_24 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 24}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | font_size_36 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 36}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | font_arial | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | font_times | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | font_courier | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | color_red | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | color_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | color_green | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | color_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pylightxl | combined | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | bold | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True}, actual={} | +| text_formatting | pyexcel | italic | read | data_mismatch | error | Expected values did not match actual values: expected={'italic': True}, actual={} | +| text_formatting | pyexcel | underline_single | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'single'}, actual={} | +| text_formatting | pyexcel | underline_double | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'double'}, actual={} | +| text_formatting | pyexcel | strikethrough | read | data_mismatch | error | Expected values did not match actual values: expected={'strikethrough': True}, actual={} | +| text_formatting | pyexcel | bold_italic | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={} | +| text_formatting | pyexcel | font_size_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 8}, actual={} | +| text_formatting | pyexcel | font_size_14 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 14}, actual={} | +| text_formatting | pyexcel | font_size_24 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 24}, actual={} | +| text_formatting | pyexcel | font_size_36 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 36}, actual={} | +| text_formatting | pyexcel | font_arial | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={} | +| text_formatting | pyexcel | font_times | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={} | +| text_formatting | pyexcel | font_courier | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={} | +| text_formatting | pyexcel | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={} | +| text_formatting | pyexcel | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={} | +| text_formatting | pyexcel | color_green | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={} | +| text_formatting | pyexcel | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={} | +| text_formatting | pyexcel | combined | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={} | +| text_formatting | pyexcel | bold | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | italic | write | data_mismatch | error | Expected values did not match actual values: expected={'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | underline_single | write | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'single'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | underline_double | write | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'double'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | strikethrough | write | data_mismatch | error | Expected values did not match actual values: expected={'strikethrough': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | bold_italic | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | font_size_8 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 8}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | font_size_14 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 14}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | font_size_24 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 24}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | font_size_36 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 36}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | font_arial | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | font_times | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | font_courier | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | color_red | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | color_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | color_green | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | color_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pyexcel | combined | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | xlwt | color_green | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={'font_name': 'Arial', 'font_size': 10.0, 'font_color': '#008000'} | +| text_formatting | xlwt | color_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={'font_name': 'Arial', 'font_size': 10.0, 'font_color': '#993300'} | +| text_formatting | pandas | bold | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True}, actual={} | +| text_formatting | pandas | italic | read | data_mismatch | error | Expected values did not match actual values: expected={'italic': True}, actual={} | +| text_formatting | pandas | underline_single | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'single'}, actual={} | +| text_formatting | pandas | underline_double | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'double'}, actual={} | +| text_formatting | pandas | strikethrough | read | data_mismatch | error | Expected values did not match actual values: expected={'strikethrough': True}, actual={} | +| text_formatting | pandas | bold_italic | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={} | +| text_formatting | pandas | font_size_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 8}, actual={} | +| text_formatting | pandas | font_size_14 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 14}, actual={} | +| text_formatting | pandas | font_size_24 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 24}, actual={} | +| text_formatting | pandas | font_size_36 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 36}, actual={} | +| text_formatting | pandas | font_arial | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={} | +| text_formatting | pandas | font_times | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={} | +| text_formatting | pandas | font_courier | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={} | +| text_formatting | pandas | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={} | +| text_formatting | pandas | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={} | +| text_formatting | pandas | color_green | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={} | +| text_formatting | pandas | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={} | +| text_formatting | pandas | combined | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={} | +| text_formatting | pandas | bold | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | italic | write | data_mismatch | error | Expected values did not match actual values: expected={'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | underline_single | write | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'single'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | underline_double | write | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'double'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | strikethrough | write | data_mismatch | error | Expected values did not match actual values: expected={'strikethrough': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | bold_italic | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | font_size_8 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 8}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | font_size_14 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 14}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | font_size_24 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 24}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | font_size_36 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 36}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | font_arial | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | font_times | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | font_courier | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | color_red | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | color_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | color_green | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | color_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | pandas | combined | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | openpyxl-readonly | bold | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True}, actual={} | +| text_formatting | openpyxl-readonly | italic | read | data_mismatch | error | Expected values did not match actual values: expected={'italic': True}, actual={} | +| text_formatting | openpyxl-readonly | underline_single | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'single'}, actual={} | +| text_formatting | openpyxl-readonly | underline_double | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'double'}, actual={} | +| text_formatting | openpyxl-readonly | strikethrough | read | data_mismatch | error | Expected values did not match actual values: expected={'strikethrough': True}, actual={} | +| text_formatting | openpyxl-readonly | bold_italic | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={} | +| text_formatting | openpyxl-readonly | font_size_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 8}, actual={} | +| text_formatting | openpyxl-readonly | font_size_14 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 14}, actual={} | +| text_formatting | openpyxl-readonly | font_size_24 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 24}, actual={} | +| text_formatting | openpyxl-readonly | font_size_36 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 36}, actual={} | +| text_formatting | openpyxl-readonly | font_arial | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={} | +| text_formatting | openpyxl-readonly | font_times | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={} | +| text_formatting | openpyxl-readonly | font_courier | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={} | +| text_formatting | openpyxl-readonly | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={} | +| text_formatting | openpyxl-readonly | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={} | +| text_formatting | openpyxl-readonly | color_green | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={} | +| text_formatting | openpyxl-readonly | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={} | +| text_formatting | openpyxl-readonly | combined | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={} | +| text_formatting | polars | bold | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True}, actual={} | +| text_formatting | polars | italic | read | data_mismatch | error | Expected values did not match actual values: expected={'italic': True}, actual={} | +| text_formatting | polars | underline_single | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'single'}, actual={} | +| text_formatting | polars | underline_double | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'double'}, actual={} | +| text_formatting | polars | strikethrough | read | data_mismatch | error | Expected values did not match actual values: expected={'strikethrough': True}, actual={} | +| text_formatting | polars | bold_italic | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={} | +| text_formatting | polars | font_size_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 8}, actual={} | +| text_formatting | polars | font_size_14 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 14}, actual={} | +| text_formatting | polars | font_size_24 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 24}, actual={} | +| text_formatting | polars | font_size_36 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 36}, actual={} | +| text_formatting | polars | font_arial | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={} | +| text_formatting | polars | font_times | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={} | +| text_formatting | polars | font_courier | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={} | +| text_formatting | polars | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={} | +| text_formatting | polars | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={} | +| text_formatting | polars | color_green | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={} | +| text_formatting | polars | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={} | +| text_formatting | polars | combined | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={} | +| text_formatting | tablib | bold | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True}, actual={} | +| text_formatting | tablib | italic | read | data_mismatch | error | Expected values did not match actual values: expected={'italic': True}, actual={} | +| text_formatting | tablib | underline_single | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'single'}, actual={} | +| text_formatting | tablib | underline_double | read | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'double'}, actual={} | +| text_formatting | tablib | strikethrough | read | data_mismatch | error | Expected values did not match actual values: expected={'strikethrough': True}, actual={} | +| text_formatting | tablib | bold_italic | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={} | +| text_formatting | tablib | font_size_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 8}, actual={} | +| text_formatting | tablib | font_size_14 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 14}, actual={} | +| text_formatting | tablib | font_size_24 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 24}, actual={} | +| text_formatting | tablib | font_size_36 | read | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 36}, actual={} | +| text_formatting | tablib | font_arial | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={} | +| text_formatting | tablib | font_times | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={} | +| text_formatting | tablib | font_courier | read | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={} | +| text_formatting | tablib | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={} | +| text_formatting | tablib | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={} | +| text_formatting | tablib | color_green | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={} | +| text_formatting | tablib | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={} | +| text_formatting | tablib | combined | read | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={} | +| text_formatting | tablib | bold | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | italic | write | data_mismatch | error | Expected values did not match actual values: expected={'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | underline_single | write | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'single'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | underline_double | write | data_mismatch | error | Expected values did not match actual values: expected={'underline': 'double'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | strikethrough | write | data_mismatch | error | Expected values did not match actual values: expected={'strikethrough': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | bold_italic | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | font_size_8 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 8}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | font_size_14 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 14}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | font_size_24 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 24}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | font_size_36 | write | data_mismatch | error | Expected values did not match actual values: expected={'font_size': 36}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | font_arial | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | font_times | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | font_courier | write | data_mismatch | error | Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | color_red | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | color_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | color_green | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | color_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| text_formatting | tablib | combined | write | data_mismatch | error | Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0} | +| background_colors | python-calamine | bg_red | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={} | +| background_colors | python-calamine | bg_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={} | +| background_colors | python-calamine | bg_green | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={} | +| background_colors | python-calamine | bg_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={} | +| background_colors | pylightxl | bg_red | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={} | +| background_colors | pylightxl | bg_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={} | +| background_colors | pylightxl | bg_green | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={} | +| background_colors | pylightxl | bg_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={} | +| background_colors | pylightxl | bg_red | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={} | +| background_colors | pylightxl | bg_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={} | +| background_colors | pylightxl | bg_green | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={} | +| background_colors | pylightxl | bg_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={} | +| background_colors | pyexcel | bg_red | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={} | +| background_colors | pyexcel | bg_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={} | +| background_colors | pyexcel | bg_green | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={} | +| background_colors | pyexcel | bg_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={} | +| background_colors | pyexcel | bg_red | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={} | +| background_colors | pyexcel | bg_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={} | +| background_colors | pyexcel | bg_green | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={} | +| background_colors | pyexcel | bg_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={} | +| background_colors | xlwt | bg_green | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={'bg_color': '#008000'} | +| background_colors | xlwt | bg_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={'bg_color': '#993300'} | +| background_colors | pandas | bg_red | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={} | +| background_colors | pandas | bg_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={} | +| background_colors | pandas | bg_green | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={} | +| background_colors | pandas | bg_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={} | +| background_colors | pandas | bg_red | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={} | +| background_colors | pandas | bg_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={} | +| background_colors | pandas | bg_green | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={} | +| background_colors | pandas | bg_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={} | +| background_colors | openpyxl-readonly | bg_red | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={} | +| background_colors | openpyxl-readonly | bg_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={} | +| background_colors | openpyxl-readonly | bg_green | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={} | +| background_colors | openpyxl-readonly | bg_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={} | +| background_colors | polars | bg_red | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={} | +| background_colors | polars | bg_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={} | +| background_colors | polars | bg_green | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={} | +| background_colors | polars | bg_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={} | +| background_colors | tablib | bg_red | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={} | +| background_colors | tablib | bg_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={} | +| background_colors | tablib | bg_green | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={} | +| background_colors | tablib | bg_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={} | +| background_colors | tablib | bg_red | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={} | +| background_colors | tablib | bg_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={} | +| background_colors | tablib | bg_green | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={} | +| background_colors | tablib | bg_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={} | +| number_formats | python-calamine | numfmt_currency | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={} | +| number_formats | python-calamine | numfmt_percent | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={} | +| number_formats | python-calamine | numfmt_date | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={} | +| number_formats | python-calamine | numfmt_scientific | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={} | +| number_formats | python-calamine | numfmt_custom_text | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '"USD" 0.00'}, actual={} | +| number_formats | pylightxl | numfmt_currency | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={} | +| number_formats | pylightxl | numfmt_percent | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={} | +| number_formats | pylightxl | numfmt_date | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={} | +| number_formats | pylightxl | numfmt_scientific | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={} | +| number_formats | pylightxl | numfmt_custom_text | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '"USD" 0.00'}, actual={} | +| number_formats | pylightxl | numfmt_currency | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={'number_format': 'General'} | +| number_formats | pylightxl | numfmt_percent | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={'number_format': 'General'} | +| number_formats | pylightxl | numfmt_date | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={'number_format': 'General'} | +| number_formats | pylightxl | numfmt_scientific | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={'number_format': 'General'} | +| number_formats | pylightxl | numfmt_custom_text | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '"USD" 0.00'}, actual={'number_format': 'General'} | +| number_formats | pyexcel | numfmt_currency | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={} | +| number_formats | pyexcel | numfmt_percent | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={} | +| number_formats | pyexcel | numfmt_date | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={} | +| number_formats | pyexcel | numfmt_scientific | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={} | +| number_formats | pyexcel | numfmt_custom_text | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '"USD" 0.00'}, actual={} | +| number_formats | pyexcel | numfmt_currency | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={'number_format': 'General'} | +| number_formats | pyexcel | numfmt_percent | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={'number_format': 'General'} | +| number_formats | pyexcel | numfmt_date | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={'number_format': 'General'} | +| number_formats | pyexcel | numfmt_scientific | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={'number_format': 'General'} | +| number_formats | pyexcel | numfmt_custom_text | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '"USD" 0.00'}, actual={'number_format': 'General'} | +| number_formats | pandas | numfmt_currency | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={} | +| number_formats | pandas | numfmt_percent | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={} | +| number_formats | pandas | numfmt_date | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={} | +| number_formats | pandas | numfmt_scientific | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={} | +| number_formats | pandas | numfmt_custom_text | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '"USD" 0.00'}, actual={} | +| number_formats | pandas | numfmt_currency | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={'number_format': 'General'} | +| number_formats | pandas | numfmt_percent | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={'number_format': 'General'} | +| number_formats | pandas | numfmt_date | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={'number_format': 'General'} | +| number_formats | pandas | numfmt_scientific | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={'number_format': 'General'} | +| number_formats | pandas | numfmt_custom_text | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '"USD" 0.00'}, actual={'number_format': 'General'} | +| number_formats | openpyxl-readonly | numfmt_currency | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={} | +| number_formats | openpyxl-readonly | numfmt_percent | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={} | +| number_formats | openpyxl-readonly | numfmt_date | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={} | +| number_formats | openpyxl-readonly | numfmt_scientific | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={} | +| number_formats | openpyxl-readonly | numfmt_custom_text | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '"USD" 0.00'}, actual={} | +| number_formats | polars | numfmt_currency | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={} | +| number_formats | polars | numfmt_percent | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={} | +| number_formats | polars | numfmt_date | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={} | +| number_formats | polars | numfmt_scientific | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={} | +| number_formats | polars | numfmt_custom_text | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '"USD" 0.00'}, actual={} | +| number_formats | tablib | numfmt_currency | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={} | +| number_formats | tablib | numfmt_percent | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={} | +| number_formats | tablib | numfmt_date | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={} | +| number_formats | tablib | numfmt_scientific | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={} | +| number_formats | tablib | numfmt_custom_text | read | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '"USD" 0.00'}, actual={} | +| number_formats | tablib | numfmt_currency | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={'number_format': 'General'} | +| number_formats | tablib | numfmt_percent | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={'number_format': 'General'} | +| number_formats | tablib | numfmt_date | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={'number_format': 'General'} | +| number_formats | tablib | numfmt_scientific | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={'number_format': 'General'} | +| number_formats | tablib | numfmt_custom_text | write | data_mismatch | error | Expected values did not match actual values: expected={'number_format': '"USD" 0.00'}, actual={'number_format': 'General'} | +| alignment | xlsxwriter | v_bottom | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | python-calamine | h_left | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'left'}, actual={} | +| alignment | python-calamine | h_center | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'center'}, actual={} | +| alignment | python-calamine | h_right | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'right'}, actual={} | +| alignment | python-calamine | v_top | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'top'}, actual={} | +| alignment | python-calamine | v_center | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'center'}, actual={} | +| alignment | python-calamine | v_bottom | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | python-calamine | wrap_text | read | data_mismatch | error | Expected values did not match actual values: expected={'wrap': True}, actual={} | +| alignment | python-calamine | rotation_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'rotation': 45}, actual={} | +| alignment | python-calamine | indent_2 | read | data_mismatch | error | Expected values did not match actual values: expected={'indent': 2}, actual={} | +| alignment | pylightxl | h_left | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| alignment | pylightxl | h_center | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| alignment | pylightxl | h_right | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| alignment | pylightxl | v_top | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| alignment | pylightxl | v_center | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| alignment | pylightxl | v_bottom | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| alignment | pylightxl | wrap_text | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| alignment | pylightxl | rotation_45 | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| alignment | pylightxl | indent_2 | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| alignment | pylightxl | h_left | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'left'}, actual={} | +| alignment | pylightxl | h_center | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'center'}, actual={} | +| alignment | pylightxl | h_right | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'right'}, actual={} | +| alignment | pylightxl | v_top | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'top'}, actual={} | +| alignment | pylightxl | v_center | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'center'}, actual={} | +| alignment | pylightxl | v_bottom | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | pylightxl | wrap_text | write | data_mismatch | error | Expected values did not match actual values: expected={'wrap': True}, actual={} | +| alignment | pylightxl | rotation_45 | write | data_mismatch | error | Expected values did not match actual values: expected={'rotation': 45}, actual={} | +| alignment | pylightxl | indent_2 | write | data_mismatch | error | Expected values did not match actual values: expected={'indent': 2}, actual={} | +| alignment | pyexcel | h_left | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'left'}, actual={} | +| alignment | pyexcel | h_center | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'center'}, actual={} | +| alignment | pyexcel | h_right | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'right'}, actual={} | +| alignment | pyexcel | v_top | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'top'}, actual={} | +| alignment | pyexcel | v_center | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'center'}, actual={} | +| alignment | pyexcel | v_bottom | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | pyexcel | wrap_text | read | data_mismatch | error | Expected values did not match actual values: expected={'wrap': True}, actual={} | +| alignment | pyexcel | rotation_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'rotation': 45}, actual={} | +| alignment | pyexcel | indent_2 | read | data_mismatch | error | Expected values did not match actual values: expected={'indent': 2}, actual={} | +| alignment | pyexcel | h_left | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'left'}, actual={} | +| alignment | pyexcel | h_center | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'center'}, actual={} | +| alignment | pyexcel | h_right | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'right'}, actual={} | +| alignment | pyexcel | v_top | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'top'}, actual={} | +| alignment | pyexcel | v_center | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'center'}, actual={} | +| alignment | pyexcel | v_bottom | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | pyexcel | wrap_text | write | data_mismatch | error | Expected values did not match actual values: expected={'wrap': True}, actual={} | +| alignment | pyexcel | rotation_45 | write | data_mismatch | error | Expected values did not match actual values: expected={'rotation': 45}, actual={} | +| alignment | pyexcel | indent_2 | write | data_mismatch | error | Expected values did not match actual values: expected={'indent': 2}, actual={} | +| alignment | pandas | h_left | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'left'}, actual={} | +| alignment | pandas | h_center | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'center'}, actual={} | +| alignment | pandas | h_right | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'right'}, actual={} | +| alignment | pandas | v_top | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'top'}, actual={} | +| alignment | pandas | v_center | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'center'}, actual={} | +| alignment | pandas | v_bottom | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | pandas | wrap_text | read | data_mismatch | error | Expected values did not match actual values: expected={'wrap': True}, actual={} | +| alignment | pandas | rotation_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'rotation': 45}, actual={} | +| alignment | pandas | indent_2 | read | data_mismatch | error | Expected values did not match actual values: expected={'indent': 2}, actual={} | +| alignment | pandas | h_left | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'left'}, actual={} | +| alignment | pandas | h_center | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'center'}, actual={} | +| alignment | pandas | h_right | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'right'}, actual={} | +| alignment | pandas | v_top | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'top'}, actual={} | +| alignment | pandas | v_center | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'center'}, actual={} | +| alignment | pandas | v_bottom | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | pandas | wrap_text | write | data_mismatch | error | Expected values did not match actual values: expected={'wrap': True}, actual={} | +| alignment | pandas | rotation_45 | write | data_mismatch | error | Expected values did not match actual values: expected={'rotation': 45}, actual={} | +| alignment | pandas | indent_2 | write | data_mismatch | error | Expected values did not match actual values: expected={'indent': 2}, actual={} | +| alignment | xlsxwriter-constmem | v_bottom | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | openpyxl-readonly | h_left | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'left'}, actual={} | +| alignment | openpyxl-readonly | h_center | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'center'}, actual={} | +| alignment | openpyxl-readonly | h_right | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'right'}, actual={} | +| alignment | openpyxl-readonly | v_top | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'top'}, actual={} | +| alignment | openpyxl-readonly | v_center | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'center'}, actual={} | +| alignment | openpyxl-readonly | v_bottom | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | openpyxl-readonly | wrap_text | read | data_mismatch | error | Expected values did not match actual values: expected={'wrap': True}, actual={} | +| alignment | openpyxl-readonly | rotation_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'rotation': 45}, actual={} | +| alignment | openpyxl-readonly | indent_2 | read | data_mismatch | error | Expected values did not match actual values: expected={'indent': 2}, actual={} | +| alignment | polars | h_left | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'left'}, actual={} | +| alignment | polars | h_center | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'center'}, actual={} | +| alignment | polars | h_right | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'right'}, actual={} | +| alignment | polars | v_top | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'top'}, actual={} | +| alignment | polars | v_center | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'center'}, actual={} | +| alignment | polars | v_bottom | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | polars | wrap_text | read | data_mismatch | error | Expected values did not match actual values: expected={'wrap': True}, actual={} | +| alignment | polars | rotation_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'rotation': 45}, actual={} | +| alignment | polars | indent_2 | read | data_mismatch | error | Expected values did not match actual values: expected={'indent': 2}, actual={} | +| alignment | tablib | h_left | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'left'}, actual={} | +| alignment | tablib | h_center | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'center'}, actual={} | +| alignment | tablib | h_right | read | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'right'}, actual={} | +| alignment | tablib | v_top | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'top'}, actual={} | +| alignment | tablib | v_center | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'center'}, actual={} | +| alignment | tablib | v_bottom | read | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | tablib | wrap_text | read | data_mismatch | error | Expected values did not match actual values: expected={'wrap': True}, actual={} | +| alignment | tablib | rotation_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'rotation': 45}, actual={} | +| alignment | tablib | indent_2 | read | data_mismatch | error | Expected values did not match actual values: expected={'indent': 2}, actual={} | +| alignment | tablib | h_left | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'left'}, actual={} | +| alignment | tablib | h_center | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'center'}, actual={} | +| alignment | tablib | h_right | write | data_mismatch | error | Expected values did not match actual values: expected={'h_align': 'right'}, actual={} | +| alignment | tablib | v_top | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'top'}, actual={} | +| alignment | tablib | v_center | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'center'}, actual={} | +| alignment | tablib | v_bottom | write | data_mismatch | error | Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={} | +| alignment | tablib | wrap_text | write | data_mismatch | error | Expected values did not match actual values: expected={'wrap': True}, actual={} | +| alignment | tablib | rotation_45 | write | data_mismatch | error | Expected values did not match actual values: expected={'rotation': 45}, actual={} | +| alignment | tablib | indent_2 | write | data_mismatch | error | Expected values did not match actual values: expected={'indent': 2}, actual={} | +| borders | python-calamine | thin_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={} | +| borders | python-calamine | medium_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={} | +| borders | python-calamine | thick_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={} | +| borders | python-calamine | double | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={} | +| borders | python-calamine | dashed | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={} | +| borders | python-calamine | dotted | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={} | +| borders | python-calamine | dash_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={} | +| borders | python-calamine | dash_dot_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={} | +| borders | python-calamine | top_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={} | +| borders | python-calamine | bottom_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={} | +| borders | python-calamine | left_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={} | +| borders | python-calamine | right_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={} | +| borders | python-calamine | diagonal_up | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={} | +| borders | python-calamine | diagonal_down | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={} | +| borders | python-calamine | diagonal_both | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={} | +| borders | python-calamine | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={} | +| borders | python-calamine | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={} | +| borders | python-calamine | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={} | +| borders | python-calamine | mixed_styles | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={} | +| borders | python-calamine | mixed_colors | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={} | +| borders | pylightxl | thin_all | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | medium_all | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | thick_all | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | double | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | dashed | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | dotted | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | dash_dot | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | dash_dot_dot | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | top_only | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | bottom_only | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | left_only | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | right_only | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | diagonal_up | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | diagonal_down | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | diagonal_both | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | color_red | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | color_blue | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | color_custom | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | mixed_styles | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | mixed_colors | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| borders | pylightxl | thin_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={} | +| borders | pylightxl | medium_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={} | +| borders | pylightxl | thick_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={} | +| borders | pylightxl | double | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={} | +| borders | pylightxl | dashed | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={} | +| borders | pylightxl | dotted | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={} | +| borders | pylightxl | dash_dot | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={} | +| borders | pylightxl | dash_dot_dot | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={} | +| borders | pylightxl | top_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={} | +| borders | pylightxl | bottom_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={} | +| borders | pylightxl | left_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={} | +| borders | pylightxl | right_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={} | +| borders | pylightxl | diagonal_up | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={} | +| borders | pylightxl | diagonal_down | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={} | +| borders | pylightxl | diagonal_both | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={} | +| borders | pylightxl | color_red | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={} | +| borders | pylightxl | color_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={} | +| borders | pylightxl | color_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={} | +| borders | pylightxl | mixed_styles | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={} | +| borders | pylightxl | mixed_colors | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={} | +| borders | pyexcel | thin_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | medium_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | thick_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | double | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | dashed | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | dotted | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | dash_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | dash_dot_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | top_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={} | +| borders | pyexcel | bottom_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={} | +| borders | pyexcel | left_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={} | +| borders | pyexcel | right_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={} | +| borders | pyexcel | diagonal_up | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={} | +| borders | pyexcel | diagonal_down | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={} | +| borders | pyexcel | diagonal_both | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={} | +| borders | pyexcel | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={} | +| borders | pyexcel | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={} | +| borders | pyexcel | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={} | +| borders | pyexcel | mixed_styles | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={} | +| borders | pyexcel | mixed_colors | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={} | +| borders | pyexcel | thin_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | medium_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | thick_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | double | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | dashed | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | dotted | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | dash_dot | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | dash_dot_dot | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={} | +| borders | pyexcel | top_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={} | +| borders | pyexcel | bottom_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={} | +| borders | pyexcel | left_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={} | +| borders | pyexcel | right_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={} | +| borders | pyexcel | diagonal_up | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={} | +| borders | pyexcel | diagonal_down | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={} | +| borders | pyexcel | diagonal_both | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={} | +| borders | pyexcel | color_red | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={} | +| borders | pyexcel | color_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={} | +| borders | pyexcel | color_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={} | +| borders | pyexcel | mixed_styles | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={} | +| borders | pyexcel | mixed_colors | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={} | +| borders | xlwt | diagonal_up | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={'border_diagonal_down': 'thin'} | +| borders | xlwt | diagonal_down | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={'border_diagonal_up': 'thin'} | +| borders | xlwt | color_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={'border_style': 'thin', 'border_color': '#993300'} | +| borders | xlwt | mixed_colors | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={'border_top_color': '#FF0000', 'border_bottom_color': '#008000', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00', 'border_style': 'thin'} | +| borders | pandas | thin_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={} | +| borders | pandas | medium_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={} | +| borders | pandas | thick_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={} | +| borders | pandas | double | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={} | +| borders | pandas | dashed | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={} | +| borders | pandas | dotted | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={} | +| borders | pandas | dash_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={} | +| borders | pandas | dash_dot_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={} | +| borders | pandas | top_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={} | +| borders | pandas | bottom_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={} | +| borders | pandas | left_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={} | +| borders | pandas | right_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={} | +| borders | pandas | diagonal_up | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={} | +| borders | pandas | diagonal_down | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={} | +| borders | pandas | diagonal_both | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={} | +| borders | pandas | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={} | +| borders | pandas | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={} | +| borders | pandas | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={} | +| borders | pandas | mixed_styles | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={} | +| borders | pandas | mixed_colors | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={} | +| borders | pandas | thin_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={} | +| borders | pandas | medium_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={} | +| borders | pandas | thick_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={} | +| borders | pandas | double | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={} | +| borders | pandas | dashed | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={} | +| borders | pandas | dotted | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={} | +| borders | pandas | dash_dot | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={} | +| borders | pandas | dash_dot_dot | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={} | +| borders | pandas | top_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={} | +| borders | pandas | bottom_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={} | +| borders | pandas | left_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={} | +| borders | pandas | right_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={} | +| borders | pandas | diagonal_up | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={} | +| borders | pandas | diagonal_down | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={} | +| borders | pandas | diagonal_both | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={} | +| borders | pandas | color_red | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={} | +| borders | pandas | color_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={} | +| borders | pandas | color_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={} | +| borders | pandas | mixed_styles | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={} | +| borders | pandas | mixed_colors | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={} | +| borders | openpyxl-readonly | thin_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={} | +| borders | openpyxl-readonly | medium_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={} | +| borders | openpyxl-readonly | thick_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={} | +| borders | openpyxl-readonly | double | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={} | +| borders | openpyxl-readonly | dashed | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={} | +| borders | openpyxl-readonly | dotted | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={} | +| borders | openpyxl-readonly | dash_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={} | +| borders | openpyxl-readonly | dash_dot_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={} | +| borders | openpyxl-readonly | top_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={} | +| borders | openpyxl-readonly | bottom_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={} | +| borders | openpyxl-readonly | left_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={} | +| borders | openpyxl-readonly | right_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={} | +| borders | openpyxl-readonly | diagonal_up | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={} | +| borders | openpyxl-readonly | diagonal_down | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={} | +| borders | openpyxl-readonly | diagonal_both | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={} | +| borders | openpyxl-readonly | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={} | +| borders | openpyxl-readonly | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={} | +| borders | openpyxl-readonly | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={} | +| borders | openpyxl-readonly | mixed_styles | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={} | +| borders | openpyxl-readonly | mixed_colors | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={} | +| borders | polars | thin_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={} | +| borders | polars | medium_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={} | +| borders | polars | thick_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={} | +| borders | polars | double | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={} | +| borders | polars | dashed | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={} | +| borders | polars | dotted | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={} | +| borders | polars | dash_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={} | +| borders | polars | dash_dot_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={} | +| borders | polars | top_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={} | +| borders | polars | bottom_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={} | +| borders | polars | left_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={} | +| borders | polars | right_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={} | +| borders | polars | diagonal_up | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={} | +| borders | polars | diagonal_down | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={} | +| borders | polars | diagonal_both | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={} | +| borders | polars | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={} | +| borders | polars | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={} | +| borders | polars | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={} | +| borders | polars | mixed_styles | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={} | +| borders | polars | mixed_colors | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={} | +| borders | tablib | thin_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={} | +| borders | tablib | medium_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={} | +| borders | tablib | thick_all | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={} | +| borders | tablib | double | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={} | +| borders | tablib | dashed | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={} | +| borders | tablib | dotted | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={} | +| borders | tablib | dash_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={} | +| borders | tablib | dash_dot_dot | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={} | +| borders | tablib | top_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={} | +| borders | tablib | bottom_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={} | +| borders | tablib | left_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={} | +| borders | tablib | right_only | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={} | +| borders | tablib | diagonal_up | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={} | +| borders | tablib | diagonal_down | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={} | +| borders | tablib | diagonal_both | read | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={} | +| borders | tablib | color_red | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={} | +| borders | tablib | color_blue | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={} | +| borders | tablib | color_custom | read | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={} | +| borders | tablib | mixed_styles | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={} | +| borders | tablib | mixed_colors | read | data_mismatch | error | Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={} | +| borders | tablib | thin_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={} | +| borders | tablib | medium_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={} | +| borders | tablib | thick_all | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={} | +| borders | tablib | double | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={} | +| borders | tablib | dashed | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={} | +| borders | tablib | dotted | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={} | +| borders | tablib | dash_dot | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={} | +| borders | tablib | dash_dot_dot | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={} | +| borders | tablib | top_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={} | +| borders | tablib | bottom_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={} | +| borders | tablib | left_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={} | +| borders | tablib | right_only | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={} | +| borders | tablib | diagonal_up | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={} | +| borders | tablib | diagonal_down | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={} | +| borders | tablib | diagonal_both | write | data_mismatch | error | Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={} | +| borders | tablib | color_red | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={} | +| borders | tablib | color_blue | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={} | +| borders | tablib | color_custom | write | data_mismatch | error | Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={} | +| borders | tablib | mixed_styles | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={} | +| borders | tablib | mixed_colors | write | data_mismatch | error | Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={} | +| dimensions | python-calamine | row_height_30 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | python-calamine | row_height_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | python-calamine | col_width_20 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None} | +| dimensions | python-calamine | col_width_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None} | +| dimensions | pylightxl | row_height_30 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | pylightxl | row_height_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | pylightxl | col_width_20 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None} | +| dimensions | pylightxl | col_width_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None} | +| dimensions | pylightxl | row_height_30 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | pylightxl | row_height_45 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | pylightxl | col_width_20 | write | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': 13.0} | +| dimensions | pylightxl | col_width_8 | write | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': 13.0} | +| dimensions | pyexcel | row_height_30 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | pyexcel | row_height_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | pyexcel | col_width_20 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None} | +| dimensions | pyexcel | col_width_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None} | +| dimensions | pyexcel | row_height_30 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | pyexcel | row_height_45 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | pyexcel | col_width_20 | write | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': 13.0} | +| dimensions | pyexcel | col_width_8 | write | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': 13.0} | +| dimensions | xlwt | row_height_30 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | xlwt | row_height_45 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | pandas | row_height_30 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | pandas | row_height_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | pandas | col_width_20 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None} | +| dimensions | pandas | col_width_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None} | +| dimensions | pandas | row_height_30 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | pandas | row_height_45 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | pandas | col_width_20 | write | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': 13.0} | +| dimensions | pandas | col_width_8 | write | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': 13.0} | +| dimensions | xlsxwriter-constmem | row_height_30 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | xlsxwriter-constmem | row_height_45 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | openpyxl-readonly | row_height_30 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | openpyxl-readonly | row_height_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | openpyxl-readonly | col_width_20 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None} | +| dimensions | openpyxl-readonly | col_width_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None} | +| dimensions | polars | row_height_30 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | polars | row_height_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | polars | col_width_20 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None} | +| dimensions | polars | col_width_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None} | +| dimensions | tablib | row_height_30 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | tablib | row_height_45 | read | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | tablib | col_width_20 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None} | +| dimensions | tablib | col_width_8 | read | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None} | +| dimensions | tablib | row_height_30 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None} | +| dimensions | tablib | row_height_45 | write | data_mismatch | error | Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None} | +| dimensions | tablib | col_width_20 | write | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': 13.0} | +| dimensions | tablib | col_width_8 | write | data_mismatch | error | Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': 13.0} | +| multiple_sheets | polars | value_beta | read | data_mismatch | error | Expected values did not match actual values: expected={'type': 'string', 'value': 'Beta'}, actual={'type': 'blank'} | +| multiple_sheets | polars | value_gamma | read | data_mismatch | error | Expected values did not match actual values: expected={'type': 'string', 'value': 'Gamma'}, actual={'type': 'blank'} | +| merged_cells | python-calamine | merge_horizontal | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None} | +| merged_cells | python-calamine | merge_vertical | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None} | +| merged_cells | python-calamine | merge_value_off_top_left | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None} | +| merged_cells | python-calamine | merge_top_left_fill | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None} | +| merged_cells | pylightxl | merge_horizontal | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None} | +| merged_cells | pylightxl | merge_vertical | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None} | +| merged_cells | pylightxl | merge_value_off_top_left | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None} | +| merged_cells | pylightxl | merge_top_left_fill | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None} | +| merged_cells | pylightxl | merge_horizontal | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None} | +| merged_cells | pylightxl | merge_vertical | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None} | +| merged_cells | pylightxl | merge_value_off_top_left | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None} | +| merged_cells | pylightxl | merge_top_left_fill | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None} | +| merged_cells | pyexcel | merge_horizontal | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None} | +| merged_cells | pyexcel | merge_vertical | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None} | +| merged_cells | pyexcel | merge_value_off_top_left | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None} | +| merged_cells | pyexcel | merge_top_left_fill | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None} | +| merged_cells | pyexcel | merge_horizontal | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None} | +| merged_cells | pyexcel | merge_vertical | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None} | +| merged_cells | pyexcel | merge_value_off_top_left | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None} | +| merged_cells | pyexcel | merge_top_left_fill | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None} | +| merged_cells | xlwt | merge_horizontal | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': 'B2:D2', 'top_left_value': None} | +| merged_cells | xlwt | merge_vertical | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': 'B3:B5', 'top_left_value': None} | +| merged_cells | xlwt | merge_value_off_top_left | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': 'B6:D6', 'top_left_value': None, 'non_top_left_nonempty': 0} | +| merged_cells | xlwt | merge_top_left_fill | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': 'B7:D7', 'top_left_value': None, 'top_left_bg_color': None} | +| merged_cells | pandas | merge_horizontal | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None} | +| merged_cells | pandas | merge_vertical | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None} | +| merged_cells | pandas | merge_value_off_top_left | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None} | +| merged_cells | pandas | merge_top_left_fill | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None} | +| merged_cells | pandas | merge_horizontal | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None} | +| merged_cells | pandas | merge_vertical | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None} | +| merged_cells | pandas | merge_value_off_top_left | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None} | +| merged_cells | pandas | merge_top_left_fill | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None} | +| merged_cells | openpyxl-readonly | merge_horizontal | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None} | +| merged_cells | openpyxl-readonly | merge_vertical | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None} | +| merged_cells | openpyxl-readonly | merge_value_off_top_left | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None} | +| merged_cells | openpyxl-readonly | merge_top_left_fill | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None} | +| merged_cells | polars | merge_horizontal | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None} | +| merged_cells | polars | merge_vertical | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None} | +| merged_cells | polars | merge_value_off_top_left | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None} | +| merged_cells | polars | merge_top_left_fill | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None} | +| merged_cells | tablib | merge_horizontal | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None} | +| merged_cells | tablib | merge_vertical | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None} | +| merged_cells | tablib | merge_value_off_top_left | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None} | +| merged_cells | tablib | merge_top_left_fill | read | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None} | +| merged_cells | tablib | merge_horizontal | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None} | +| merged_cells | tablib | merge_vertical | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None} | +| merged_cells | tablib | merge_value_off_top_left | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None} | +| merged_cells | tablib | merge_top_left_fill | write | data_mismatch | error | Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None} | +| conditional_formatting | python-calamine | cf_cell_gt | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | python-calamine | cf_formula_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={} | +| conditional_formatting | python-calamine | cf_text_contains | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH("foo",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | python-calamine | cf_data_bar | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={} | +| conditional_formatting | python-calamine | cf_color_scale | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={} | +| conditional_formatting | python-calamine | cf_stop_if_true | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={} | +| conditional_formatting | pylightxl | cf_cell_gt | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| conditional_formatting | pylightxl | cf_formula_cross_sheet | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| conditional_formatting | pylightxl | cf_text_contains | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| conditional_formatting | pylightxl | cf_data_bar | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| conditional_formatting | pylightxl | cf_color_scale | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| conditional_formatting | pylightxl | cf_stop_if_true | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| conditional_formatting | pylightxl | cf_cell_gt | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | pylightxl | cf_formula_cross_sheet | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={} | +| conditional_formatting | pylightxl | cf_text_contains | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH("foo",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | pylightxl | cf_data_bar | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={} | +| conditional_formatting | pylightxl | cf_color_scale | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={} | +| conditional_formatting | pylightxl | cf_stop_if_true | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={} | +| conditional_formatting | pyexcel | cf_cell_gt | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | pyexcel | cf_formula_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={} | +| conditional_formatting | pyexcel | cf_text_contains | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH("foo",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | pyexcel | cf_data_bar | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={} | +| conditional_formatting | pyexcel | cf_color_scale | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={} | +| conditional_formatting | pyexcel | cf_stop_if_true | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={} | +| conditional_formatting | pyexcel | cf_cell_gt | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | pyexcel | cf_formula_cross_sheet | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={} | +| conditional_formatting | pyexcel | cf_text_contains | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH("foo",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | pyexcel | cf_data_bar | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={} | +| conditional_formatting | pyexcel | cf_color_scale | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={} | +| conditional_formatting | pyexcel | cf_stop_if_true | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={} | +| conditional_formatting | xlwt | cf_cell_gt | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | xlwt | cf_formula_cross_sheet | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={} | +| conditional_formatting | xlwt | cf_text_contains | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH("foo",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | xlwt | cf_data_bar | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={} | +| conditional_formatting | xlwt | cf_color_scale | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={} | +| conditional_formatting | xlwt | cf_stop_if_true | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={} | +| conditional_formatting | pandas | cf_cell_gt | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | pandas | cf_formula_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={} | +| conditional_formatting | pandas | cf_text_contains | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH("foo",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | pandas | cf_data_bar | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={} | +| conditional_formatting | pandas | cf_color_scale | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={} | +| conditional_formatting | pandas | cf_stop_if_true | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={} | +| conditional_formatting | pandas | cf_cell_gt | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | pandas | cf_formula_cross_sheet | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={} | +| conditional_formatting | pandas | cf_text_contains | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH("foo",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | pandas | cf_data_bar | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={} | +| conditional_formatting | pandas | cf_color_scale | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={} | +| conditional_formatting | pandas | cf_stop_if_true | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={} | +| conditional_formatting | openpyxl-readonly | cf_cell_gt | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | openpyxl-readonly | cf_formula_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={} | +| conditional_formatting | openpyxl-readonly | cf_text_contains | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH("foo",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | openpyxl-readonly | cf_data_bar | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={} | +| conditional_formatting | openpyxl-readonly | cf_color_scale | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={} | +| conditional_formatting | openpyxl-readonly | cf_stop_if_true | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={} | +| conditional_formatting | polars | cf_cell_gt | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | polars | cf_formula_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={} | +| conditional_formatting | polars | cf_text_contains | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH("foo",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | polars | cf_data_bar | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={} | +| conditional_formatting | polars | cf_color_scale | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={} | +| conditional_formatting | polars | cf_stop_if_true | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={} | +| conditional_formatting | tablib | cf_cell_gt | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | tablib | cf_formula_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={} | +| conditional_formatting | tablib | cf_text_contains | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH("foo",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | tablib | cf_data_bar | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={} | +| conditional_formatting | tablib | cf_color_scale | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={} | +| conditional_formatting | tablib | cf_stop_if_true | read | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={} | +| conditional_formatting | tablib | cf_cell_gt | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | tablib | cf_formula_cross_sheet | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={} | +| conditional_formatting | tablib | cf_text_contains | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH("foo",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={} | +| conditional_formatting | tablib | cf_data_bar | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={} | +| conditional_formatting | tablib | cf_color_scale | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={} | +| conditional_formatting | tablib | cf_stop_if_true | write | data_mismatch | error | Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={} | +| data_validation | python-calamine | dv_list_csv | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '"Red,Green,Blue"', 'allow_blank': True}}, actual={} | +| data_validation | python-calamine | dv_list_range | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={} | +| data_validation | python-calamine | dv_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={} | +| data_validation | python-calamine | dv_custom_formula | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={} | +| data_validation | python-calamine | dv_whole_between | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={} | +| data_validation | pylightxl | dv_list_csv | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| data_validation | pylightxl | dv_list_range | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| data_validation | pylightxl | dv_cross_sheet | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| data_validation | pylightxl | dv_custom_formula | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| data_validation | pylightxl | dv_whole_between | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| data_validation | pylightxl | dv_list_csv | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '"Red,Green,Blue"', 'allow_blank': True}}, actual={} | +| data_validation | pylightxl | dv_list_range | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={} | +| data_validation | pylightxl | dv_cross_sheet | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={} | +| data_validation | pylightxl | dv_custom_formula | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={} | +| data_validation | pylightxl | dv_whole_between | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={} | +| data_validation | pyexcel | dv_list_csv | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '"Red,Green,Blue"', 'allow_blank': True}}, actual={} | +| data_validation | pyexcel | dv_list_range | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={} | +| data_validation | pyexcel | dv_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={} | +| data_validation | pyexcel | dv_custom_formula | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={} | +| data_validation | pyexcel | dv_whole_between | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={} | +| data_validation | pyexcel | dv_list_csv | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '"Red,Green,Blue"', 'allow_blank': True}}, actual={} | +| data_validation | pyexcel | dv_list_range | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={} | +| data_validation | pyexcel | dv_cross_sheet | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={} | +| data_validation | pyexcel | dv_custom_formula | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={} | +| data_validation | pyexcel | dv_whole_between | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={} | +| data_validation | xlwt | dv_list_csv | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '"Red,Green,Blue"', 'allow_blank': True}}, actual={} | +| data_validation | xlwt | dv_list_range | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={} | +| data_validation | xlwt | dv_cross_sheet | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={} | +| data_validation | xlwt | dv_custom_formula | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={} | +| data_validation | xlwt | dv_whole_between | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={} | +| data_validation | pandas | dv_list_csv | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '"Red,Green,Blue"', 'allow_blank': True}}, actual={} | +| data_validation | pandas | dv_list_range | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={} | +| data_validation | pandas | dv_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={} | +| data_validation | pandas | dv_custom_formula | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={} | +| data_validation | pandas | dv_whole_between | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={} | +| data_validation | pandas | dv_list_csv | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '"Red,Green,Blue"', 'allow_blank': True}}, actual={} | +| data_validation | pandas | dv_list_range | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={} | +| data_validation | pandas | dv_cross_sheet | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={} | +| data_validation | pandas | dv_custom_formula | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={} | +| data_validation | pandas | dv_whole_between | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={} | +| data_validation | openpyxl-readonly | dv_list_csv | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '"Red,Green,Blue"', 'allow_blank': True}}, actual={} | +| data_validation | openpyxl-readonly | dv_list_range | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={} | +| data_validation | openpyxl-readonly | dv_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={} | +| data_validation | openpyxl-readonly | dv_custom_formula | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={} | +| data_validation | openpyxl-readonly | dv_whole_between | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={} | +| data_validation | polars | dv_list_csv | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '"Red,Green,Blue"', 'allow_blank': True}}, actual={} | +| data_validation | polars | dv_list_range | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={} | +| data_validation | polars | dv_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={} | +| data_validation | polars | dv_custom_formula | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={} | +| data_validation | polars | dv_whole_between | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={} | +| data_validation | tablib | dv_list_csv | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '"Red,Green,Blue"', 'allow_blank': True}}, actual={} | +| data_validation | tablib | dv_list_range | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={} | +| data_validation | tablib | dv_cross_sheet | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={} | +| data_validation | tablib | dv_custom_formula | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={} | +| data_validation | tablib | dv_whole_between | read | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={} | +| data_validation | tablib | dv_list_csv | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '"Red,Green,Blue"', 'allow_blank': True}}, actual={} | +| data_validation | tablib | dv_list_range | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={} | +| data_validation | tablib | dv_cross_sheet | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={} | +| data_validation | tablib | dv_custom_formula | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={} | +| data_validation | tablib | dv_whole_between | write | data_mismatch | error | Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={} | +| hyperlinks | python-calamine | link_external | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={} | +| hyperlinks | python-calamine | link_internal | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={} | +| hyperlinks | python-calamine | link_mailto | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={} | +| hyperlinks | python-calamine | link_long | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={} | +| hyperlinks | pylightxl | link_external | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| hyperlinks | pylightxl | link_internal | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| hyperlinks | pylightxl | link_mailto | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| hyperlinks | pylightxl | link_long | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| hyperlinks | pylightxl | link_external | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={} | +| hyperlinks | pylightxl | link_internal | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={} | +| hyperlinks | pylightxl | link_mailto | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={} | +| hyperlinks | pylightxl | link_long | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={} | +| hyperlinks | pyexcel | link_external | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={} | +| hyperlinks | pyexcel | link_internal | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={} | +| hyperlinks | pyexcel | link_mailto | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={} | +| hyperlinks | pyexcel | link_long | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={} | +| hyperlinks | pyexcel | link_external | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={} | +| hyperlinks | pyexcel | link_internal | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={} | +| hyperlinks | pyexcel | link_mailto | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={} | +| hyperlinks | pyexcel | link_long | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={} | +| hyperlinks | xlwt | link_external | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={} | +| hyperlinks | xlwt | link_internal | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={} | +| hyperlinks | xlwt | link_mailto | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={} | +| hyperlinks | xlwt | link_long | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={} | +| hyperlinks | pandas | link_external | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={} | +| hyperlinks | pandas | link_internal | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={} | +| hyperlinks | pandas | link_mailto | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={} | +| hyperlinks | pandas | link_long | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={} | +| hyperlinks | pandas | link_external | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={} | +| hyperlinks | pandas | link_internal | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={} | +| hyperlinks | pandas | link_mailto | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={} | +| hyperlinks | pandas | link_long | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={} | +| hyperlinks | openpyxl-readonly | link_external | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={} | +| hyperlinks | openpyxl-readonly | link_internal | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={} | +| hyperlinks | openpyxl-readonly | link_mailto | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={} | +| hyperlinks | openpyxl-readonly | link_long | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={} | +| hyperlinks | polars | link_external | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={} | +| hyperlinks | polars | link_internal | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={} | +| hyperlinks | polars | link_mailto | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={} | +| hyperlinks | polars | link_long | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={} | +| hyperlinks | tablib | link_external | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={} | +| hyperlinks | tablib | link_internal | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={} | +| hyperlinks | tablib | link_mailto | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={} | +| hyperlinks | tablib | link_long | read | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={} | +| hyperlinks | tablib | link_external | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={} | +| hyperlinks | tablib | link_internal | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={} | +| hyperlinks | tablib | link_mailto | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={} | +| hyperlinks | tablib | link_long | write | data_mismatch | error | Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={} | +| images | python-calamine | image_one_cell | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | python-calamine | image_two_cell_offset | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| images | pylightxl | image_one_cell | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| images | pylightxl | image_two_cell_offset | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| images | pylightxl | image_one_cell | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | pylightxl | image_two_cell_offset | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| images | pyexcel | image_one_cell | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | pyexcel | image_two_cell_offset | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| images | pyexcel | image_one_cell | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | pyexcel | image_two_cell_offset | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| images | xlwt | image_one_cell | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | xlwt | image_two_cell_offset | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| images | pandas | image_one_cell | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | pandas | image_two_cell_offset | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| images | pandas | image_one_cell | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | pandas | image_two_cell_offset | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| images | xlsxwriter-constmem | image_one_cell | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | xlsxwriter-constmem | image_two_cell_offset | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| images | openpyxl-readonly | image_one_cell | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | openpyxl-readonly | image_two_cell_offset | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| images | polars | image_one_cell | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | polars | image_two_cell_offset | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| images | tablib | image_one_cell | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | tablib | image_two_cell_offset | read | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| images | tablib | image_one_cell | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={} | +| images | tablib | image_two_cell_offset | write | data_mismatch | error | Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={} | +| comments | python-calamine | comment_legacy | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | python-calamine | comment_threaded | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | python-calamine | comment_author | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | pylightxl | comment_legacy | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | pylightxl | comment_threaded | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | pylightxl | comment_author | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | pylightxl | comment_legacy | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | pylightxl | comment_threaded | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | pylightxl | comment_author | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | pyexcel | comment_legacy | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | pyexcel | comment_threaded | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | pyexcel | comment_author | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | pyexcel | comment_legacy | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | pyexcel | comment_threaded | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | pyexcel | comment_author | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | xlwt | comment_legacy | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | xlwt | comment_threaded | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | xlwt | comment_author | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | pandas | comment_legacy | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | pandas | comment_threaded | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | pandas | comment_author | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | pandas | comment_legacy | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | pandas | comment_threaded | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | pandas | comment_author | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | xlsxwriter-constmem | comment_legacy | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | xlsxwriter-constmem | comment_threaded | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | xlsxwriter-constmem | comment_author | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | openpyxl-readonly | comment_legacy | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | openpyxl-readonly | comment_threaded | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | openpyxl-readonly | comment_author | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | polars | comment_legacy | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | polars | comment_threaded | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | polars | comment_author | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | tablib | comment_legacy | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | tablib | comment_threaded | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | tablib | comment_author | read | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| comments | tablib | comment_legacy | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={} | +| comments | tablib | comment_threaded | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={} | +| comments | tablib | comment_author | write | data_mismatch | error | Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={} | +| freeze_panes | python-calamine | freeze_b2 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | python-calamine | freeze_d5 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | python-calamine | split_2x1 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}} | +| freeze_panes | pylightxl | freeze_b2 | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| freeze_panes | pylightxl | freeze_d5 | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| freeze_panes | pylightxl | split_2x1 | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| freeze_panes | pylightxl | freeze_b2 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | pylightxl | freeze_d5 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | pylightxl | split_2x1 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}} | +| freeze_panes | pyexcel | freeze_b2 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | pyexcel | freeze_d5 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | pyexcel | split_2x1 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}} | +| freeze_panes | pyexcel | freeze_b2 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | pyexcel | freeze_d5 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | pyexcel | split_2x1 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}} | +| freeze_panes | xlwt | freeze_b2 | write | internal | error | AttributeError: 'Sheet' object has no attribute 'frozen_row_count' | +| freeze_panes | xlwt | freeze_d5 | write | internal | error | AttributeError: 'Sheet' object has no attribute 'frozen_row_count' | +| freeze_panes | xlwt | split_2x1 | write | internal | error | AttributeError: 'Sheet' object has no attribute 'frozen_row_count' | +| freeze_panes | pandas | freeze_b2 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | pandas | freeze_d5 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | pandas | split_2x1 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}} | +| freeze_panes | pandas | freeze_b2 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | pandas | freeze_d5 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | pandas | split_2x1 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}} | +| freeze_panes | openpyxl-readonly | freeze_b2 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | openpyxl-readonly | freeze_d5 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | openpyxl-readonly | split_2x1 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}} | +| freeze_panes | polars | freeze_b2 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | polars | freeze_d5 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | polars | split_2x1 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}} | +| freeze_panes | tablib | freeze_b2 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | tablib | freeze_d5 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | tablib | split_2x1 | read | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}} | +| freeze_panes | tablib | freeze_b2 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | tablib | freeze_d5 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}} | +| freeze_panes | tablib | split_2x1 | write | data_mismatch | error | Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}} | +| named_ranges | xlsxwriter | nr_simple_cell | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement named range writes | +| named_ranges | xlsxwriter | nr_cell_range | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement named range writes | +| named_ranges | xlsxwriter | nr_formula_ref | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement named range writes | +| named_ranges | xlsxwriter | nr_sheet_scope | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement named range writes | +| named_ranges | xlsxwriter | nr_cross_sheet | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement named range writes | +| named_ranges | xlsxwriter | nr_special_chars | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement named range writes | +| named_ranges | python-calamine | nr_simple_cell | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement named range reads | +| named_ranges | python-calamine | nr_cell_range | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement named range reads | +| named_ranges | python-calamine | nr_formula_ref | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement named range reads | +| named_ranges | python-calamine | nr_sheet_scope | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement named range reads | +| named_ranges | python-calamine | nr_cross_sheet | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement named range reads | +| named_ranges | python-calamine | nr_special_chars | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement named range reads | +| named_ranges | pylightxl | nr_simple_cell | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| named_ranges | pylightxl | nr_cell_range | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| named_ranges | pylightxl | nr_formula_ref | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| named_ranges | pylightxl | nr_sheet_scope | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| named_ranges | pylightxl | nr_cross_sheet | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| named_ranges | pylightxl | nr_special_chars | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| named_ranges | pylightxl | nr_simple_cell | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement named range writes | +| named_ranges | pylightxl | nr_cell_range | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement named range writes | +| named_ranges | pylightxl | nr_formula_ref | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement named range writes | +| named_ranges | pylightxl | nr_sheet_scope | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement named range writes | +| named_ranges | pylightxl | nr_cross_sheet | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement named range writes | +| named_ranges | pylightxl | nr_special_chars | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement named range writes | +| named_ranges | pyexcel | nr_simple_cell | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range reads | +| named_ranges | pyexcel | nr_cell_range | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range reads | +| named_ranges | pyexcel | nr_formula_ref | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range reads | +| named_ranges | pyexcel | nr_sheet_scope | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range reads | +| named_ranges | pyexcel | nr_cross_sheet | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range reads | +| named_ranges | pyexcel | nr_special_chars | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range reads | +| named_ranges | pyexcel | nr_simple_cell | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range writes | +| named_ranges | pyexcel | nr_cell_range | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range writes | +| named_ranges | pyexcel | nr_formula_ref | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range writes | +| named_ranges | pyexcel | nr_sheet_scope | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range writes | +| named_ranges | pyexcel | nr_cross_sheet | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range writes | +| named_ranges | pyexcel | nr_special_chars | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement named range writes | +| named_ranges | xlwt | nr_simple_cell | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement named range writes | +| named_ranges | xlwt | nr_cell_range | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement named range writes | +| named_ranges | xlwt | nr_formula_ref | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement named range writes | +| named_ranges | xlwt | nr_sheet_scope | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement named range writes | +| named_ranges | xlwt | nr_cross_sheet | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement named range writes | +| named_ranges | xlwt | nr_special_chars | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement named range writes | +| named_ranges | pandas | nr_simple_cell | read | unsupported_feature | warning | NotImplementedError: pandas does not implement named range reads | +| named_ranges | pandas | nr_cell_range | read | unsupported_feature | warning | NotImplementedError: pandas does not implement named range reads | +| named_ranges | pandas | nr_formula_ref | read | unsupported_feature | warning | NotImplementedError: pandas does not implement named range reads | +| named_ranges | pandas | nr_sheet_scope | read | unsupported_feature | warning | NotImplementedError: pandas does not implement named range reads | +| named_ranges | pandas | nr_cross_sheet | read | unsupported_feature | warning | NotImplementedError: pandas does not implement named range reads | +| named_ranges | pandas | nr_special_chars | read | unsupported_feature | warning | NotImplementedError: pandas does not implement named range reads | +| named_ranges | pandas | nr_simple_cell | write | unsupported_feature | warning | NotImplementedError: pandas does not implement named range writes | +| named_ranges | pandas | nr_cell_range | write | unsupported_feature | warning | NotImplementedError: pandas does not implement named range writes | +| named_ranges | pandas | nr_formula_ref | write | unsupported_feature | warning | NotImplementedError: pandas does not implement named range writes | +| named_ranges | pandas | nr_sheet_scope | write | unsupported_feature | warning | NotImplementedError: pandas does not implement named range writes | +| named_ranges | pandas | nr_cross_sheet | write | unsupported_feature | warning | NotImplementedError: pandas does not implement named range writes | +| named_ranges | pandas | nr_special_chars | write | unsupported_feature | warning | NotImplementedError: pandas does not implement named range writes | +| named_ranges | xlsxwriter-constmem | nr_simple_cell | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement named range writes | +| named_ranges | xlsxwriter-constmem | nr_cell_range | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement named range writes | +| named_ranges | xlsxwriter-constmem | nr_formula_ref | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement named range writes | +| named_ranges | xlsxwriter-constmem | nr_sheet_scope | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement named range writes | +| named_ranges | xlsxwriter-constmem | nr_cross_sheet | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement named range writes | +| named_ranges | xlsxwriter-constmem | nr_special_chars | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement named range writes | +| named_ranges | openpyxl-readonly | nr_simple_cell | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement named range reads | +| named_ranges | openpyxl-readonly | nr_cell_range | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement named range reads | +| named_ranges | openpyxl-readonly | nr_formula_ref | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement named range reads | +| named_ranges | openpyxl-readonly | nr_sheet_scope | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement named range reads | +| named_ranges | openpyxl-readonly | nr_cross_sheet | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement named range reads | +| named_ranges | openpyxl-readonly | nr_special_chars | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement named range reads | +| named_ranges | polars | nr_simple_cell | read | unsupported_feature | warning | NotImplementedError: polars does not implement named range reads | +| named_ranges | polars | nr_cell_range | read | unsupported_feature | warning | NotImplementedError: polars does not implement named range reads | +| named_ranges | polars | nr_formula_ref | read | unsupported_feature | warning | NotImplementedError: polars does not implement named range reads | +| named_ranges | polars | nr_sheet_scope | read | unsupported_feature | warning | NotImplementedError: polars does not implement named range reads | +| named_ranges | polars | nr_cross_sheet | read | unsupported_feature | warning | NotImplementedError: polars does not implement named range reads | +| named_ranges | polars | nr_special_chars | read | unsupported_feature | warning | NotImplementedError: polars does not implement named range reads | +| named_ranges | tablib | nr_simple_cell | read | unsupported_feature | warning | NotImplementedError: tablib does not implement named range reads | +| named_ranges | tablib | nr_cell_range | read | unsupported_feature | warning | NotImplementedError: tablib does not implement named range reads | +| named_ranges | tablib | nr_formula_ref | read | unsupported_feature | warning | NotImplementedError: tablib does not implement named range reads | +| named_ranges | tablib | nr_sheet_scope | read | unsupported_feature | warning | NotImplementedError: tablib does not implement named range reads | +| named_ranges | tablib | nr_cross_sheet | read | unsupported_feature | warning | NotImplementedError: tablib does not implement named range reads | +| named_ranges | tablib | nr_special_chars | read | unsupported_feature | warning | NotImplementedError: tablib does not implement named range reads | +| named_ranges | tablib | nr_simple_cell | write | unsupported_feature | warning | NotImplementedError: tablib does not implement named range writes | +| named_ranges | tablib | nr_cell_range | write | unsupported_feature | warning | NotImplementedError: tablib does not implement named range writes | +| named_ranges | tablib | nr_formula_ref | write | unsupported_feature | warning | NotImplementedError: tablib does not implement named range writes | +| named_ranges | tablib | nr_sheet_scope | write | unsupported_feature | warning | NotImplementedError: tablib does not implement named range writes | +| named_ranges | tablib | nr_cross_sheet | write | unsupported_feature | warning | NotImplementedError: tablib does not implement named range writes | +| named_ranges | tablib | nr_special_chars | write | unsupported_feature | warning | NotImplementedError: tablib does not implement named range writes | +| tables | xlsxwriter | tbl_basic | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement table writes | +| tables | xlsxwriter | tbl_with_totals | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement table writes | +| tables | xlsxwriter | tbl_no_style | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement table writes | +| tables | xlsxwriter | tbl_single_col | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement table writes | +| tables | xlsxwriter | tbl_single_row | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement table writes | +| tables | xlsxwriter | tbl_autofilter | write | unsupported_feature | warning | NotImplementedError: xlsxwriter does not implement table writes | +| tables | python-calamine | tbl_basic | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement table reads | +| tables | python-calamine | tbl_with_totals | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement table reads | +| tables | python-calamine | tbl_no_style | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement table reads | +| tables | python-calamine | tbl_single_col | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement table reads | +| tables | python-calamine | tbl_single_row | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement table reads | +| tables | python-calamine | tbl_autofilter | read | unsupported_feature | warning | NotImplementedError: python-calamine does not implement table reads | +| tables | pylightxl | tbl_basic | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| tables | pylightxl | tbl_with_totals | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| tables | pylightxl | tbl_no_style | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| tables | pylightxl | tbl_single_col | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| tables | pylightxl | tbl_single_row | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| tables | pylightxl | tbl_autofilter | read | invalid_input | error | TypeError: expected string or bytes-like object, got 'NoneType' | +| tables | pylightxl | tbl_basic | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement table writes | +| tables | pylightxl | tbl_with_totals | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement table writes | +| tables | pylightxl | tbl_no_style | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement table writes | +| tables | pylightxl | tbl_single_col | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement table writes | +| tables | pylightxl | tbl_single_row | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement table writes | +| tables | pylightxl | tbl_autofilter | write | unsupported_feature | warning | NotImplementedError: pylightxl does not implement table writes | +| tables | pyexcel | tbl_basic | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table reads | +| tables | pyexcel | tbl_with_totals | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table reads | +| tables | pyexcel | tbl_no_style | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table reads | +| tables | pyexcel | tbl_single_col | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table reads | +| tables | pyexcel | tbl_single_row | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table reads | +| tables | pyexcel | tbl_autofilter | read | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table reads | +| tables | pyexcel | tbl_basic | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table writes | +| tables | pyexcel | tbl_with_totals | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table writes | +| tables | pyexcel | tbl_no_style | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table writes | +| tables | pyexcel | tbl_single_col | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table writes | +| tables | pyexcel | tbl_single_row | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table writes | +| tables | pyexcel | tbl_autofilter | write | unsupported_feature | warning | NotImplementedError: pyexcel does not implement table writes | +| tables | xlwt | tbl_basic | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement table writes | +| tables | xlwt | tbl_with_totals | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement table writes | +| tables | xlwt | tbl_no_style | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement table writes | +| tables | xlwt | tbl_single_col | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement table writes | +| tables | xlwt | tbl_single_row | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement table writes | +| tables | xlwt | tbl_autofilter | write | unsupported_feature | warning | NotImplementedError: xlwt does not implement table writes | +| tables | pandas | tbl_basic | read | unsupported_feature | warning | NotImplementedError: pandas does not implement table reads | +| tables | pandas | tbl_with_totals | read | unsupported_feature | warning | NotImplementedError: pandas does not implement table reads | +| tables | pandas | tbl_no_style | read | unsupported_feature | warning | NotImplementedError: pandas does not implement table reads | +| tables | pandas | tbl_single_col | read | unsupported_feature | warning | NotImplementedError: pandas does not implement table reads | +| tables | pandas | tbl_single_row | read | unsupported_feature | warning | NotImplementedError: pandas does not implement table reads | +| tables | pandas | tbl_autofilter | read | unsupported_feature | warning | NotImplementedError: pandas does not implement table reads | +| tables | pandas | tbl_basic | write | unsupported_feature | warning | NotImplementedError: pandas does not implement table writes | +| tables | pandas | tbl_with_totals | write | unsupported_feature | warning | NotImplementedError: pandas does not implement table writes | +| tables | pandas | tbl_no_style | write | unsupported_feature | warning | NotImplementedError: pandas does not implement table writes | +| tables | pandas | tbl_single_col | write | unsupported_feature | warning | NotImplementedError: pandas does not implement table writes | +| tables | pandas | tbl_single_row | write | unsupported_feature | warning | NotImplementedError: pandas does not implement table writes | +| tables | pandas | tbl_autofilter | write | unsupported_feature | warning | NotImplementedError: pandas does not implement table writes | +| tables | xlsxwriter-constmem | tbl_basic | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement table writes | +| tables | xlsxwriter-constmem | tbl_with_totals | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement table writes | +| tables | xlsxwriter-constmem | tbl_no_style | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement table writes | +| tables | xlsxwriter-constmem | tbl_single_col | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement table writes | +| tables | xlsxwriter-constmem | tbl_single_row | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement table writes | +| tables | xlsxwriter-constmem | tbl_autofilter | write | unsupported_feature | warning | NotImplementedError: xlsxwriter-constmem does not implement table writes | +| tables | openpyxl-readonly | tbl_basic | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement table reads | +| tables | openpyxl-readonly | tbl_with_totals | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement table reads | +| tables | openpyxl-readonly | tbl_no_style | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement table reads | +| tables | openpyxl-readonly | tbl_single_col | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement table reads | +| tables | openpyxl-readonly | tbl_single_row | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement table reads | +| tables | openpyxl-readonly | tbl_autofilter | read | unsupported_feature | warning | NotImplementedError: openpyxl-readonly does not implement table reads | +| tables | polars | tbl_basic | read | unsupported_feature | warning | NotImplementedError: polars does not implement table reads | +| tables | polars | tbl_with_totals | read | unsupported_feature | warning | NotImplementedError: polars does not implement table reads | +| tables | polars | tbl_no_style | read | unsupported_feature | warning | NotImplementedError: polars does not implement table reads | +| tables | polars | tbl_single_col | read | unsupported_feature | warning | NotImplementedError: polars does not implement table reads | +| tables | polars | tbl_single_row | read | unsupported_feature | warning | NotImplementedError: polars does not implement table reads | +| tables | polars | tbl_autofilter | read | unsupported_feature | warning | NotImplementedError: polars does not implement table reads | +| tables | tablib | tbl_basic | read | unsupported_feature | warning | NotImplementedError: tablib does not implement table reads | +| tables | tablib | tbl_with_totals | read | unsupported_feature | warning | NotImplementedError: tablib does not implement table reads | +| tables | tablib | tbl_no_style | read | unsupported_feature | warning | NotImplementedError: tablib does not implement table reads | +| tables | tablib | tbl_single_col | read | unsupported_feature | warning | NotImplementedError: tablib does not implement table reads | +| tables | tablib | tbl_single_row | read | unsupported_feature | warning | NotImplementedError: tablib does not implement table reads | +| tables | tablib | tbl_autofilter | read | unsupported_feature | warning | NotImplementedError: tablib does not implement table reads | +| tables | tablib | tbl_basic | write | unsupported_feature | warning | NotImplementedError: tablib does not implement table writes | +| tables | tablib | tbl_with_totals | write | unsupported_feature | warning | NotImplementedError: tablib does not implement table writes | +| tables | tablib | tbl_no_style | write | unsupported_feature | warning | NotImplementedError: tablib does not implement table writes | +| tables | tablib | tbl_single_col | write | unsupported_feature | warning | NotImplementedError: tablib does not implement table writes | +| tables | tablib | tbl_single_row | write | unsupported_feature | warning | NotImplementedError: tablib does not implement table writes | +| tables | tablib | tbl_autofilter | write | unsupported_feature | warning | NotImplementedError: tablib does not implement table writes | + +## Detailed Results + + +### alignment + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Align - left | basic | ❌ | +| Align - center | basic | ❌ | +| Align - right | basic | ❌ | +| Align - top | basic | ❌ | +| Align - center | basic | ❌ | +| Align - bottom | basic | ❌ | +| Align - wrap text | basic | ❌ | +| Align - rotation 45 | basic | ❌ | +| Align - indent 2 | basic | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Align - left | basic | ❌ | ❌ | +| Align - center | basic | ❌ | ❌ | +| Align - right | basic | ❌ | ❌ | +| Align - top | basic | ❌ | ❌ | +| Align - center | basic | ❌ | ❌ | +| Align - bottom | basic | ❌ | ❌ | +| Align - wrap text | basic | ❌ | ❌ | +| Align - rotation 45 | basic | ❌ | ❌ | +| Align - indent 2 | basic | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Align - left | basic | ❌ | +| Align - center | basic | ❌ | +| Align - right | basic | ❌ | +| Align - top | basic | ❌ | +| Align - center | basic | ❌ | +| Align - bottom | basic | ❌ | +| Align - wrap text | basic | ❌ | +| Align - rotation 45 | basic | ❌ | +| Align - indent 2 | basic | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Align - left | basic | ❌ | ❌ | +| Align - center | basic | ❌ | ❌ | +| Align - right | basic | ❌ | ❌ | +| Align - top | basic | ❌ | ❌ | +| Align - center | basic | ❌ | ❌ | +| Align - bottom | basic | ❌ | ❌ | +| Align - wrap text | basic | ❌ | ❌ | +| Align - rotation 45 | basic | ❌ | ❌ | +| Align - indent 2 | basic | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 +- Notes: Known limitation: pylightxl alignment write is a no-op because the library does not support formatting writes. + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Align - left | basic | ❌ | ❌ | +| Align - center | basic | ❌ | ❌ | +| Align - right | basic | ❌ | ❌ | +| Align - top | basic | ❌ | ❌ | +| Align - center | basic | ❌ | ❌ | +| Align - bottom | basic | ❌ | ❌ | +| Align - wrap text | basic | ❌ | ❌ | +| Align - rotation 45 | basic | ❌ | ❌ | +| Align - indent 2 | basic | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 +- Notes: Known limitation: python-calamine alignment read is limited because its API does not expose style/alignment metadata. + +| Test | Importance | Read | +|------|-----------|------| +| Align - left | basic | ❌ | +| Align - center | basic | ❌ | +| Align - right | basic | ❌ | +| Align - top | basic | ❌ | +| Align - center | basic | ❌ | +| Align - bottom | basic | ❌ | +| Align - wrap text | basic | ❌ | +| Align - rotation 45 | basic | ❌ | +| Align - indent 2 | basic | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Align - left | basic | ❌ | ❌ | +| Align - center | basic | ❌ | ❌ | +| Align - right | basic | ❌ | ❌ | +| Align - top | basic | ❌ | ❌ | +| Align - center | basic | ❌ | ❌ | +| Align - bottom | basic | ❌ | ❌ | +| Align - wrap text | basic | ❌ | ❌ | +| Align - rotation 45 | basic | ❌ | ❌ | +| Align - indent 2 | basic | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟠 1 + +| Test | Importance | Write | +|------|-----------|-------| +| Align - left | basic | ✅ | +| Align - center | basic | ✅ | +| Align - right | basic | ✅ | +| Align - top | basic | ✅ | +| Align - center | basic | ✅ | +| Align - bottom | basic | ❌ | +| Align - wrap text | basic | ✅ | +| Align - rotation 45 | basic | ✅ | +| Align - indent 2 | basic | ✅ | + +**xlsxwriter-constmem** — Write: 🟠 1 + +| Test | Importance | Write | +|------|-----------|-------| +| Align - left | basic | ✅ | +| Align - center | basic | ✅ | +| Align - right | basic | ✅ | +| Align - top | basic | ✅ | +| Align - center | basic | ✅ | +| Align - bottom | basic | ❌ | +| Align - wrap text | basic | ✅ | +| Align - rotation 45 | basic | ✅ | +| Align - indent 2 | basic | ✅ | + +**xlwt** — Write: 🟢 3 + + +### background_colors + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Background - red | basic | ❌ | +| Background - blue | basic | ❌ | +| Background - green | basic | ❌ | +| Background - custom (#8B4513) | basic | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Background - red | basic | ❌ | ❌ | +| Background - blue | basic | ❌ | ❌ | +| Background - green | basic | ❌ | ❌ | +| Background - custom (#8B4513) | basic | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Background - red | basic | ❌ | +| Background - blue | basic | ❌ | +| Background - green | basic | ❌ | +| Background - custom (#8B4513) | basic | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Background - red | basic | ❌ | ❌ | +| Background - blue | basic | ❌ | ❌ | +| Background - green | basic | ❌ | ❌ | +| Background - custom (#8B4513) | basic | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Background - red | basic | ❌ | ❌ | +| Background - blue | basic | ❌ | ❌ | +| Background - green | basic | ❌ | ❌ | +| Background - custom (#8B4513) | basic | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Background - red | basic | ❌ | +| Background - blue | basic | ❌ | +| Background - green | basic | ❌ | +| Background - custom (#8B4513) | basic | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Background - red | basic | ❌ | ❌ | +| Background - blue | basic | ❌ | ❌ | +| Background - green | basic | ❌ | ❌ | +| Background - custom (#8B4513) | basic | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🟠 1 + +| Test | Importance | Write | +|------|-----------|-------| +| Background - red | basic | ✅ | +| Background - blue | basic | ✅ | +| Background - green | basic | ❌ | +| Background - custom (#8B4513) | basic | ❌ | + + +### borders + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Border - thin all edges | basic | ❌ | +| Border - medium all edges | basic | ❌ | +| Border - thick all edges | basic | ❌ | +| Border - double line | basic | ❌ | +| Border - dashed | basic | ❌ | +| Border - dotted | basic | ❌ | +| Border - dash-dot | basic | ❌ | +| Border - dash-dot-dot | basic | ❌ | +| Border - top only | basic | ❌ | +| Border - bottom only | basic | ❌ | +| Border - left only | basic | ❌ | +| Border - right only | basic | ❌ | +| Border - diagonal up | basic | ❌ | +| Border - diagonal down | basic | ❌ | +| Border - diagonal both | basic | ❌ | +| Border - red color | basic | ❌ | +| Border - blue color | basic | ❌ | +| Border - custom color (#8B4513) | basic | ❌ | +| Border - mixed styles per edge | basic | ❌ | +| Border - mixed colors per edge | basic | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Border - thin all edges | basic | ❌ | ❌ | +| Border - medium all edges | basic | ❌ | ❌ | +| Border - thick all edges | basic | ❌ | ❌ | +| Border - double line | basic | ❌ | ❌ | +| Border - dashed | basic | ❌ | ❌ | +| Border - dotted | basic | ❌ | ❌ | +| Border - dash-dot | basic | ❌ | ❌ | +| Border - dash-dot-dot | basic | ❌ | ❌ | +| Border - top only | basic | ❌ | ❌ | +| Border - bottom only | basic | ❌ | ❌ | +| Border - left only | basic | ❌ | ❌ | +| Border - right only | basic | ❌ | ❌ | +| Border - diagonal up | basic | ❌ | ❌ | +| Border - diagonal down | basic | ❌ | ❌ | +| Border - diagonal both | basic | ❌ | ❌ | +| Border - red color | basic | ❌ | ❌ | +| Border - blue color | basic | ❌ | ❌ | +| Border - custom color (#8B4513) | basic | ❌ | ❌ | +| Border - mixed styles per edge | basic | ❌ | ❌ | +| Border - mixed colors per edge | basic | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Border - thin all edges | basic | ❌ | +| Border - medium all edges | basic | ❌ | +| Border - thick all edges | basic | ❌ | +| Border - double line | basic | ❌ | +| Border - dashed | basic | ❌ | +| Border - dotted | basic | ❌ | +| Border - dash-dot | basic | ❌ | +| Border - dash-dot-dot | basic | ❌ | +| Border - top only | basic | ❌ | +| Border - bottom only | basic | ❌ | +| Border - left only | basic | ❌ | +| Border - right only | basic | ❌ | +| Border - diagonal up | basic | ❌ | +| Border - diagonal down | basic | ❌ | +| Border - diagonal both | basic | ❌ | +| Border - red color | basic | ❌ | +| Border - blue color | basic | ❌ | +| Border - custom color (#8B4513) | basic | ❌ | +| Border - mixed styles per edge | basic | ❌ | +| Border - mixed colors per edge | basic | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Border - thin all edges | basic | ❌ | ❌ | +| Border - medium all edges | basic | ❌ | ❌ | +| Border - thick all edges | basic | ❌ | ❌ | +| Border - double line | basic | ❌ | ❌ | +| Border - dashed | basic | ❌ | ❌ | +| Border - dotted | basic | ❌ | ❌ | +| Border - dash-dot | basic | ❌ | ❌ | +| Border - dash-dot-dot | basic | ❌ | ❌ | +| Border - top only | basic | ❌ | ❌ | +| Border - bottom only | basic | ❌ | ❌ | +| Border - left only | basic | ❌ | ❌ | +| Border - right only | basic | ❌ | ❌ | +| Border - diagonal up | basic | ❌ | ❌ | +| Border - diagonal down | basic | ❌ | ❌ | +| Border - diagonal both | basic | ❌ | ❌ | +| Border - red color | basic | ❌ | ❌ | +| Border - blue color | basic | ❌ | ❌ | +| Border - custom color (#8B4513) | basic | ❌ | ❌ | +| Border - mixed styles per edge | basic | ❌ | ❌ | +| Border - mixed colors per edge | basic | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Border - thin all edges | basic | ❌ | ❌ | +| Border - medium all edges | basic | ❌ | ❌ | +| Border - thick all edges | basic | ❌ | ❌ | +| Border - double line | basic | ❌ | ❌ | +| Border - dashed | basic | ❌ | ❌ | +| Border - dotted | basic | ❌ | ❌ | +| Border - dash-dot | basic | ❌ | ❌ | +| Border - dash-dot-dot | basic | ❌ | ❌ | +| Border - top only | basic | ❌ | ❌ | +| Border - bottom only | basic | ❌ | ❌ | +| Border - left only | basic | ❌ | ❌ | +| Border - right only | basic | ❌ | ❌ | +| Border - diagonal up | basic | ❌ | ❌ | +| Border - diagonal down | basic | ❌ | ❌ | +| Border - diagonal both | basic | ❌ | ❌ | +| Border - red color | basic | ❌ | ❌ | +| Border - blue color | basic | ❌ | ❌ | +| Border - custom color (#8B4513) | basic | ❌ | ❌ | +| Border - mixed styles per edge | basic | ❌ | ❌ | +| Border - mixed colors per edge | basic | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Border - thin all edges | basic | ❌ | +| Border - medium all edges | basic | ❌ | +| Border - thick all edges | basic | ❌ | +| Border - double line | basic | ❌ | +| Border - dashed | basic | ❌ | +| Border - dotted | basic | ❌ | +| Border - dash-dot | basic | ❌ | +| Border - dash-dot-dot | basic | ❌ | +| Border - top only | basic | ❌ | +| Border - bottom only | basic | ❌ | +| Border - left only | basic | ❌ | +| Border - right only | basic | ❌ | +| Border - diagonal up | basic | ❌ | +| Border - diagonal down | basic | ❌ | +| Border - diagonal both | basic | ❌ | +| Border - red color | basic | ❌ | +| Border - blue color | basic | ❌ | +| Border - custom color (#8B4513) | basic | ❌ | +| Border - mixed styles per edge | basic | ❌ | +| Border - mixed colors per edge | basic | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Border - thin all edges | basic | ❌ | ❌ | +| Border - medium all edges | basic | ❌ | ❌ | +| Border - thick all edges | basic | ❌ | ❌ | +| Border - double line | basic | ❌ | ❌ | +| Border - dashed | basic | ❌ | ❌ | +| Border - dotted | basic | ❌ | ❌ | +| Border - dash-dot | basic | ❌ | ❌ | +| Border - dash-dot-dot | basic | ❌ | ❌ | +| Border - top only | basic | ❌ | ❌ | +| Border - bottom only | basic | ❌ | ❌ | +| Border - left only | basic | ❌ | ❌ | +| Border - right only | basic | ❌ | ❌ | +| Border - diagonal up | basic | ❌ | ❌ | +| Border - diagonal down | basic | ❌ | ❌ | +| Border - diagonal both | basic | ❌ | ❌ | +| Border - red color | basic | ❌ | ❌ | +| Border - blue color | basic | ❌ | ❌ | +| Border - custom color (#8B4513) | basic | ❌ | ❌ | +| Border - mixed styles per edge | basic | ❌ | ❌ | +| Border - mixed colors per edge | basic | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🟠 1 + +| Test | Importance | Write | +|------|-----------|-------| +| Border - thin all edges | basic | ✅ | +| Border - medium all edges | basic | ✅ | +| Border - thick all edges | basic | ✅ | +| Border - double line | basic | ✅ | +| Border - dashed | basic | ✅ | +| Border - dotted | basic | ✅ | +| Border - dash-dot | basic | ✅ | +| Border - dash-dot-dot | basic | ✅ | +| Border - top only | basic | ✅ | +| Border - bottom only | basic | ✅ | +| Border - left only | basic | ✅ | +| Border - right only | basic | ✅ | +| Border - diagonal up | basic | ❌ | +| Border - diagonal down | basic | ❌ | +| Border - diagonal both | basic | ✅ | +| Border - red color | basic | ✅ | +| Border - blue color | basic | ✅ | +| Border - custom color (#8B4513) | basic | ❌ | +| Border - mixed styles per edge | basic | ✅ | +| Border - mixed colors per edge | basic | ❌ | + + +### cell_values + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🟢 3 + +**pandas** — Read: 🟠 1 | Write: 🟢 3 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| String - simple | basic | ✅ | ✅ | +| String - unicode | basic | ✅ | ✅ | +| String - empty | basic | ✅ | ✅ | +| String - long (1000 chars) | basic | ✅ | ✅ | +| String - with newlines | basic | ✅ | ✅ | +| Number - integer | basic | ✅ | ✅ | +| Number - float | basic | ✅ | ✅ | +| Number - negative | basic | ✅ | ✅ | +| Number - large | basic | ✅ | ✅ | +| Number - scientific notation | basic | ✅ | ✅ | +| Date - standard | basic | ✅ | ✅ | +| DateTime - with time | basic | ✅ | ✅ | +| Boolean - TRUE | basic | ✅ | ✅ | +| Boolean - FALSE | basic | ✅ | ✅ | +| Error - #DIV/0! | basic | ❌ | ✅ | +| Error - #N/A | basic | ❌ | ✅ | +| Error - #VALUE! | basic | ❌ | ✅ | +| Blank cell | basic | ✅ | ✅ | + +**polars** — Read: 🟠 1 + +| Test | Importance | Read | +|------|-----------|------| +| String - simple | basic | ✅ | +| String - unicode | basic | ✅ | +| String - empty | basic | ✅ | +| String - long (1000 chars) | basic | ✅ | +| String - with newlines | basic | ✅ | +| Number - integer | basic | ✅ | +| Number - float | basic | ✅ | +| Number - negative | basic | ✅ | +| Number - large | basic | ✅ | +| Number - scientific notation | basic | ✅ | +| Date - standard | basic | ✅ | +| DateTime - with time | basic | ✅ | +| Boolean - TRUE | basic | ✅ | +| Boolean - FALSE | basic | ✅ | +| Error - #DIV/0! | basic | ❌ | +| Error - #N/A | basic | ❌ | +| Error - #VALUE! | basic | ❌ | +| Blank cell | basic | ✅ | + +**pyexcel** — Read: 🟢 3 | Write: 🟢 3 + +**pylightxl** — Read: 🟢 3 | Write: 🟠 1 +- Notes: Known limitation: pylightxl cell-values write has date/boolean/error fidelity limits due to writer encoding behavior. + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| String - simple | basic | ✅ | ✅ | +| String - unicode | basic | ✅ | ✅ | +| String - empty | basic | ✅ | ✅ | +| String - long (1000 chars) | basic | ✅ | ✅ | +| String - with newlines | basic | ✅ | ✅ | +| Number - integer | basic | ✅ | ✅ | +| Number - float | basic | ✅ | ✅ | +| Number - negative | basic | ✅ | ✅ | +| Number - large | basic | ✅ | ✅ | +| Number - scientific notation | basic | ✅ | ✅ | +| Date - standard | basic | ✅ | ❌ | +| DateTime - with time | basic | ✅ | ❌ | +| Boolean - TRUE | basic | ✅ | ❌ | +| Boolean - FALSE | basic | ✅ | ❌ | +| Error - #DIV/0! | basic | ✅ | ✅ | +| Error - #N/A | basic | ✅ | ✅ | +| Error - #VALUE! | basic | ✅ | ✅ | +| Blank cell | basic | ✅ | ✅ | + +**python-calamine** — Read: 🟠 1 +- Notes: Known limitation: python-calamine can surface formula error cells as blank values in current API responses. + +| Test | Importance | Read | +|------|-----------|------| +| String - simple | basic | ✅ | +| String - unicode | basic | ✅ | +| String - empty | basic | ✅ | +| String - long (1000 chars) | basic | ✅ | +| String - with newlines | basic | ✅ | +| Number - integer | basic | ✅ | +| Number - float | basic | ✅ | +| Number - negative | basic | ✅ | +| Number - large | basic | ✅ | +| Number - scientific notation | basic | ✅ | +| Date - standard | basic | ✅ | +| DateTime - with time | basic | ✅ | +| Boolean - TRUE | basic | ✅ | +| Boolean - FALSE | basic | ✅ | +| Error - #DIV/0! | basic | ❌ | +| Error - #N/A | basic | ❌ | +| Error - #VALUE! | basic | ❌ | +| Blank cell | basic | ✅ | + +**tablib** — Read: 🟢 3 | Write: 🟢 3 + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🟢 3 + + +### comments + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Comment: legacy note | basic | ❌ | +| Comment: threaded | edge | ❌ | +| Comment: second author | edge | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Comment: legacy note | basic | ❌ | ❌ | +| Comment: threaded | edge | ❌ | ❌ | +| Comment: second author | edge | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Comment: legacy note | basic | ❌ | +| Comment: threaded | edge | ❌ | +| Comment: second author | edge | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Comment: legacy note | basic | ❌ | ❌ | +| Comment: threaded | edge | ❌ | ❌ | +| Comment: second author | edge | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Comment: legacy note | basic | ❌ | ❌ | +| Comment: threaded | edge | ❌ | ❌ | +| Comment: second author | edge | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Comment: legacy note | basic | ❌ | +| Comment: threaded | edge | ❌ | +| Comment: second author | edge | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Comment: legacy note | basic | ❌ | ❌ | +| Comment: threaded | edge | ❌ | ❌ | +| Comment: second author | edge | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Comment: legacy note | basic | ❌ | +| Comment: threaded | edge | ❌ | +| Comment: second author | edge | ❌ | + +**xlwt** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Comment: legacy note | basic | ❌ | +| Comment: threaded | edge | ❌ | +| Comment: second author | edge | ❌ | + + +### conditional_formatting + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| CF: cell > 5 (yellow fill) | basic | ❌ | +| CF: formula rule with cross-sheet ref | edge | ❌ | +| CF: text contains | edge | ❌ | +| CF: data bar | edge | ❌ | +| CF: 3-color scale | edge | ❌ | +| CF: stop-if-true priority | edge | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| CF: cell > 5 (yellow fill) | basic | ❌ | ❌ | +| CF: formula rule with cross-sheet ref | edge | ❌ | ❌ | +| CF: text contains | edge | ❌ | ❌ | +| CF: data bar | edge | ❌ | ❌ | +| CF: 3-color scale | edge | ❌ | ❌ | +| CF: stop-if-true priority | edge | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| CF: cell > 5 (yellow fill) | basic | ❌ | +| CF: formula rule with cross-sheet ref | edge | ❌ | +| CF: text contains | edge | ❌ | +| CF: data bar | edge | ❌ | +| CF: 3-color scale | edge | ❌ | +| CF: stop-if-true priority | edge | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| CF: cell > 5 (yellow fill) | basic | ❌ | ❌ | +| CF: formula rule with cross-sheet ref | edge | ❌ | ❌ | +| CF: text contains | edge | ❌ | ❌ | +| CF: data bar | edge | ❌ | ❌ | +| CF: 3-color scale | edge | ❌ | ❌ | +| CF: stop-if-true priority | edge | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| CF: cell > 5 (yellow fill) | basic | ❌ | ❌ | +| CF: formula rule with cross-sheet ref | edge | ❌ | ❌ | +| CF: text contains | edge | ❌ | ❌ | +| CF: data bar | edge | ❌ | ❌ | +| CF: 3-color scale | edge | ❌ | ❌ | +| CF: stop-if-true priority | edge | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| CF: cell > 5 (yellow fill) | basic | ❌ | +| CF: formula rule with cross-sheet ref | edge | ❌ | +| CF: text contains | edge | ❌ | +| CF: data bar | edge | ❌ | +| CF: 3-color scale | edge | ❌ | +| CF: stop-if-true priority | edge | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| CF: cell > 5 (yellow fill) | basic | ❌ | ❌ | +| CF: formula rule with cross-sheet ref | edge | ❌ | ❌ | +| CF: text contains | edge | ❌ | ❌ | +| CF: data bar | edge | ❌ | ❌ | +| CF: 3-color scale | edge | ❌ | ❌ | +| CF: stop-if-true priority | edge | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| CF: cell > 5 (yellow fill) | basic | ❌ | +| CF: formula rule with cross-sheet ref | edge | ❌ | +| CF: text contains | edge | ❌ | +| CF: data bar | edge | ❌ | +| CF: 3-color scale | edge | ❌ | +| CF: stop-if-true priority | edge | ❌ | + + +### data_validation + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| DV: list from CSV | basic | ❌ | +| DV: list from range | edge | ❌ | +| DV: cross-sheet named range | edge | ❌ | +| DV: custom formula | edge | ❌ | +| DV: whole number with error | basic | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| DV: list from CSV | basic | ❌ | ❌ | +| DV: list from range | edge | ❌ | ❌ | +| DV: cross-sheet named range | edge | ❌ | ❌ | +| DV: custom formula | edge | ❌ | ❌ | +| DV: whole number with error | basic | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| DV: list from CSV | basic | ❌ | +| DV: list from range | edge | ❌ | +| DV: cross-sheet named range | edge | ❌ | +| DV: custom formula | edge | ❌ | +| DV: whole number with error | basic | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| DV: list from CSV | basic | ❌ | ❌ | +| DV: list from range | edge | ❌ | ❌ | +| DV: cross-sheet named range | edge | ❌ | ❌ | +| DV: custom formula | edge | ❌ | ❌ | +| DV: whole number with error | basic | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| DV: list from CSV | basic | ❌ | ❌ | +| DV: list from range | edge | ❌ | ❌ | +| DV: cross-sheet named range | edge | ❌ | ❌ | +| DV: custom formula | edge | ❌ | ❌ | +| DV: whole number with error | basic | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| DV: list from CSV | basic | ❌ | +| DV: list from range | edge | ❌ | +| DV: cross-sheet named range | edge | ❌ | +| DV: custom formula | edge | ❌ | +| DV: whole number with error | basic | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| DV: list from CSV | basic | ❌ | ❌ | +| DV: list from range | edge | ❌ | ❌ | +| DV: cross-sheet named range | edge | ❌ | ❌ | +| DV: custom formula | edge | ❌ | ❌ | +| DV: whole number with error | basic | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| DV: list from CSV | basic | ❌ | +| DV: list from range | edge | ❌ | +| DV: cross-sheet named range | edge | ❌ | +| DV: custom formula | edge | ❌ | +| DV: whole number with error | basic | ❌ | + + +### dimensions + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Row height - 30 | basic | ❌ | +| Row height - 45 | basic | ❌ | +| Column width - D = 20 | basic | ❌ | +| Column width - E = 8 | basic | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Row height - 30 | basic | ❌ | ❌ | +| Row height - 45 | basic | ❌ | ❌ | +| Column width - D = 20 | basic | ❌ | ❌ | +| Column width - E = 8 | basic | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Row height - 30 | basic | ❌ | +| Row height - 45 | basic | ❌ | +| Column width - D = 20 | basic | ❌ | +| Column width - E = 8 | basic | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Row height - 30 | basic | ❌ | ❌ | +| Row height - 45 | basic | ❌ | ❌ | +| Column width - D = 20 | basic | ❌ | ❌ | +| Column width - E = 8 | basic | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Row height - 30 | basic | ❌ | ❌ | +| Row height - 45 | basic | ❌ | ❌ | +| Column width - D = 20 | basic | ❌ | ❌ | +| Column width - E = 8 | basic | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Row height - 30 | basic | ❌ | +| Row height - 45 | basic | ❌ | +| Column width - D = 20 | basic | ❌ | +| Column width - E = 8 | basic | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Row height - 30 | basic | ❌ | ❌ | +| Row height - 45 | basic | ❌ | ❌ | +| Column width - D = 20 | basic | ❌ | ❌ | +| Column width - E = 8 | basic | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟠 1 + +| Test | Importance | Write | +|------|-----------|-------| +| Row height - 30 | basic | ❌ | +| Row height - 45 | basic | ❌ | +| Column width - D = 20 | basic | ✅ | +| Column width - E = 8 | basic | ✅ | + +**xlwt** — Write: 🟠 1 + +| Test | Importance | Write | +|------|-----------|-------| +| Row height - 30 | basic | ❌ | +| Row height - 45 | basic | ❌ | +| Column width - D = 20 | basic | ✅ | +| Column width - E = 8 | basic | ✅ | + + +### formulas + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🟢 3 + +**pandas** — Read: 🔴 0 | Write: 🟢 3 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Formula - SUM | basic | ❌ | ✅ | +| Formula - cell reference | basic | ❌ | ✅ | +| Formula - concat | basic | ❌ | ✅ | +| Formula - cross sheet | basic | ❌ | ✅ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Formula - SUM | basic | ❌ | +| Formula - cell reference | basic | ❌ | +| Formula - concat | basic | ❌ | +| Formula - cross sheet | basic | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🟢 3 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Formula - SUM | basic | ❌ | ✅ | +| Formula - cell reference | basic | ❌ | ✅ | +| Formula - concat | basic | ❌ | ✅ | +| Formula - cross sheet | basic | ❌ | ✅ | + +**pylightxl** — Read: 🔴 0 | Write: 🟢 3 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Formula - SUM | basic | ❌ | ✅ | +| Formula - cell reference | basic | ❌ | ✅ | +| Formula - concat | basic | ❌ | ✅ | +| Formula - cross sheet | basic | ❌ | ✅ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Formula - SUM | basic | ❌ | +| Formula - cell reference | basic | ❌ | +| Formula - concat | basic | ❌ | +| Formula - cross sheet | basic | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🟢 3 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Formula - SUM | basic | ❌ | ✅ | +| Formula - cell reference | basic | ❌ | ✅ | +| Formula - concat | basic | ❌ | ✅ | +| Formula - cross sheet | basic | ❌ | ✅ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Formula - SUM | basic | ❌ | +| Formula - cell reference | basic | ❌ | +| Formula - concat | basic | ❌ | +| Formula - cross sheet | basic | ❌ | + + +### freeze_panes + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Freeze panes at B2 | basic | ❌ | +| Freeze panes at D5 | edge | ❌ | +| Split panes row=2 col=1 | edge | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Freeze panes at B2 | basic | ❌ | ❌ | +| Freeze panes at D5 | edge | ❌ | ❌ | +| Split panes row=2 col=1 | edge | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Freeze panes at B2 | basic | ❌ | +| Freeze panes at D5 | edge | ❌ | +| Split panes row=2 col=1 | edge | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Freeze panes at B2 | basic | ❌ | ❌ | +| Freeze panes at D5 | edge | ❌ | ❌ | +| Split panes row=2 col=1 | edge | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Freeze panes at B2 | basic | ❌ | ❌ | +| Freeze panes at D5 | edge | ❌ | ❌ | +| Split panes row=2 col=1 | edge | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Freeze panes at B2 | basic | ❌ | +| Freeze panes at D5 | edge | ❌ | +| Split panes row=2 col=1 | edge | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Freeze panes at B2 | basic | ❌ | ❌ | +| Freeze panes at D5 | edge | ❌ | ❌ | +| Split panes row=2 col=1 | edge | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Freeze panes at B2 | basic | ❌ | +| Freeze panes at D5 | edge | ❌ | +| Split panes row=2 col=1 | edge | ❌ | + + +### hyperlinks + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Hyperlink: external URL | basic | ❌ | +| Hyperlink: internal sheet | edge | ❌ | +| Hyperlink: mailto | basic | ❌ | +| Hyperlink: long encoded URL | edge | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Hyperlink: external URL | basic | ❌ | ❌ | +| Hyperlink: internal sheet | edge | ❌ | ❌ | +| Hyperlink: mailto | basic | ❌ | ❌ | +| Hyperlink: long encoded URL | edge | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Hyperlink: external URL | basic | ❌ | +| Hyperlink: internal sheet | edge | ❌ | +| Hyperlink: mailto | basic | ❌ | +| Hyperlink: long encoded URL | edge | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Hyperlink: external URL | basic | ❌ | ❌ | +| Hyperlink: internal sheet | edge | ❌ | ❌ | +| Hyperlink: mailto | basic | ❌ | ❌ | +| Hyperlink: long encoded URL | edge | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Hyperlink: external URL | basic | ❌ | ❌ | +| Hyperlink: internal sheet | edge | ❌ | ❌ | +| Hyperlink: mailto | basic | ❌ | ❌ | +| Hyperlink: long encoded URL | edge | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Hyperlink: external URL | basic | ❌ | +| Hyperlink: internal sheet | edge | ❌ | +| Hyperlink: mailto | basic | ❌ | +| Hyperlink: long encoded URL | edge | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Hyperlink: external URL | basic | ❌ | ❌ | +| Hyperlink: internal sheet | edge | ❌ | ❌ | +| Hyperlink: mailto | basic | ❌ | ❌ | +| Hyperlink: long encoded URL | edge | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Hyperlink: external URL | basic | ❌ | +| Hyperlink: internal sheet | edge | ❌ | +| Hyperlink: mailto | basic | ❌ | +| Hyperlink: long encoded URL | edge | ❌ | + + +### images + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Image: one-cell anchor | basic | ❌ | +| Image: two-cell anchor with offset | edge | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Image: one-cell anchor | basic | ❌ | ❌ | +| Image: two-cell anchor with offset | edge | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Image: one-cell anchor | basic | ❌ | +| Image: two-cell anchor with offset | edge | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Image: one-cell anchor | basic | ❌ | ❌ | +| Image: two-cell anchor with offset | edge | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Image: one-cell anchor | basic | ❌ | ❌ | +| Image: two-cell anchor with offset | edge | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Image: one-cell anchor | basic | ❌ | +| Image: two-cell anchor with offset | edge | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Image: one-cell anchor | basic | ❌ | ❌ | +| Image: two-cell anchor with offset | edge | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Image: one-cell anchor | basic | ❌ | +| Image: two-cell anchor with offset | edge | ❌ | + +**xlwt** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Image: one-cell anchor | basic | ❌ | +| Image: two-cell anchor with offset | edge | ❌ | + + +### merged_cells + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Merge horizontal B2:D2 | basic | ❌ | +| Merge vertical B3:B5 | basic | ❌ | +| Merge with non-top-left value | edge | ❌ | +| Merge with top-left fill | edge | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Merge horizontal B2:D2 | basic | ❌ | ❌ | +| Merge vertical B3:B5 | basic | ❌ | ❌ | +| Merge with non-top-left value | edge | ❌ | ❌ | +| Merge with top-left fill | edge | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Merge horizontal B2:D2 | basic | ❌ | +| Merge vertical B3:B5 | basic | ❌ | +| Merge with non-top-left value | edge | ❌ | +| Merge with top-left fill | edge | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Merge horizontal B2:D2 | basic | ❌ | ❌ | +| Merge vertical B3:B5 | basic | ❌ | ❌ | +| Merge with non-top-left value | edge | ❌ | ❌ | +| Merge with top-left fill | edge | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Merge horizontal B2:D2 | basic | ❌ | ❌ | +| Merge vertical B3:B5 | basic | ❌ | ❌ | +| Merge with non-top-left value | edge | ❌ | ❌ | +| Merge with top-left fill | edge | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Merge horizontal B2:D2 | basic | ❌ | +| Merge vertical B3:B5 | basic | ❌ | +| Merge with non-top-left value | edge | ❌ | +| Merge with top-left fill | edge | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Merge horizontal B2:D2 | basic | ❌ | ❌ | +| Merge vertical B3:B5 | basic | ❌ | ❌ | +| Merge with non-top-left value | edge | ❌ | ❌ | +| Merge with top-left fill | edge | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Merge horizontal B2:D2 | basic | ❌ | +| Merge vertical B3:B5 | basic | ❌ | +| Merge with non-top-left value | edge | ❌ | +| Merge with top-left fill | edge | ❌ | + + +### multiple_sheets + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🟢 3 + +**pandas** — Read: 🟢 3 | Write: 🟢 3 + +**polars** — Read: 🟠 1 + +| Test | Importance | Read | +|------|-----------|------| +| Sheet names | basic | ✅ | +| Alpha value | basic | ✅ | +| Beta value | basic | ❌ | +| Gamma value | basic | ❌ | + +**pyexcel** — Read: 🟢 3 | Write: 🟢 3 + +**pylightxl** — Read: 🟢 3 | Write: 🟢 3 + +**python-calamine** — Read: 🟢 3 + +**tablib** — Read: 🟢 3 | Write: 🟢 3 + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🟢 3 + + +### named_ranges + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Named range: single cell | basic | ❌ | +| Named range: cell range | basic | ❌ | +| Named range: used in formula | basic | ❌ | +| Named range: sheet-scoped | edge | ❌ | +| Named range: cross-sheet reference | edge | ❌ | +| Named range: underscore name | edge | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Named range: single cell | basic | ❌ | ❌ | +| Named range: cell range | basic | ❌ | ❌ | +| Named range: used in formula | basic | ❌ | ❌ | +| Named range: sheet-scoped | edge | ❌ | ❌ | +| Named range: cross-sheet reference | edge | ❌ | ❌ | +| Named range: underscore name | edge | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Named range: single cell | basic | ❌ | +| Named range: cell range | basic | ❌ | +| Named range: used in formula | basic | ❌ | +| Named range: sheet-scoped | edge | ❌ | +| Named range: cross-sheet reference | edge | ❌ | +| Named range: underscore name | edge | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Named range: single cell | basic | ❌ | ❌ | +| Named range: cell range | basic | ❌ | ❌ | +| Named range: used in formula | basic | ❌ | ❌ | +| Named range: sheet-scoped | edge | ❌ | ❌ | +| Named range: cross-sheet reference | edge | ❌ | ❌ | +| Named range: underscore name | edge | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Named range: single cell | basic | ❌ | ❌ | +| Named range: cell range | basic | ❌ | ❌ | +| Named range: used in formula | basic | ❌ | ❌ | +| Named range: sheet-scoped | edge | ❌ | ❌ | +| Named range: cross-sheet reference | edge | ❌ | ❌ | +| Named range: underscore name | edge | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Named range: single cell | basic | ❌ | +| Named range: cell range | basic | ❌ | +| Named range: used in formula | basic | ❌ | +| Named range: sheet-scoped | edge | ❌ | +| Named range: cross-sheet reference | edge | ❌ | +| Named range: underscore name | edge | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Named range: single cell | basic | ❌ | ❌ | +| Named range: cell range | basic | ❌ | ❌ | +| Named range: used in formula | basic | ❌ | ❌ | +| Named range: sheet-scoped | edge | ❌ | ❌ | +| Named range: cross-sheet reference | edge | ❌ | ❌ | +| Named range: underscore name | edge | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Named range: single cell | basic | ❌ | +| Named range: cell range | basic | ❌ | +| Named range: used in formula | basic | ❌ | +| Named range: sheet-scoped | edge | ❌ | +| Named range: cross-sheet reference | edge | ❌ | +| Named range: underscore name | edge | ❌ | + +**xlsxwriter-constmem** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Named range: single cell | basic | ❌ | +| Named range: cell range | basic | ❌ | +| Named range: used in formula | basic | ❌ | +| Named range: sheet-scoped | edge | ❌ | +| Named range: cross-sheet reference | edge | ❌ | +| Named range: underscore name | edge | ❌ | + +**xlwt** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Named range: single cell | basic | ❌ | +| Named range: cell range | basic | ❌ | +| Named range: used in formula | basic | ❌ | +| Named range: sheet-scoped | edge | ❌ | +| Named range: cross-sheet reference | edge | ❌ | +| Named range: underscore name | edge | ❌ | + + +### number_formats + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Format - currency | basic | ❌ | +| Format - percent | basic | ❌ | +| Format - date | basic | ❌ | +| Format - scientific | basic | ❌ | +| Format - custom text | basic | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Format - currency | basic | ❌ | ❌ | +| Format - percent | basic | ❌ | ❌ | +| Format - date | basic | ❌ | ❌ | +| Format - scientific | basic | ❌ | ❌ | +| Format - custom text | basic | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Format - currency | basic | ❌ | +| Format - percent | basic | ❌ | +| Format - date | basic | ❌ | +| Format - scientific | basic | ❌ | +| Format - custom text | basic | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Format - currency | basic | ❌ | ❌ | +| Format - percent | basic | ❌ | ❌ | +| Format - date | basic | ❌ | ❌ | +| Format - scientific | basic | ❌ | ❌ | +| Format - custom text | basic | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Format - currency | basic | ❌ | ❌ | +| Format - percent | basic | ❌ | ❌ | +| Format - date | basic | ❌ | ❌ | +| Format - scientific | basic | ❌ | ❌ | +| Format - custom text | basic | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Format - currency | basic | ❌ | +| Format - percent | basic | ❌ | +| Format - date | basic | ❌ | +| Format - scientific | basic | ❌ | +| Format - custom text | basic | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Format - currency | basic | ❌ | ❌ | +| Format - percent | basic | ❌ | ❌ | +| Format - date | basic | ❌ | ❌ | +| Format - scientific | basic | ❌ | ❌ | +| Format - custom text | basic | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🟢 3 + + +### pivot_tables + +**openpyxl** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**openpyxl-readonly** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**pandas** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**polars** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**pyexcel** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**pylightxl** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**python-calamine** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**tablib** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**wolfxl** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**xlsxwriter-constmem** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + +**xlwt** +- Notes: Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx). + + +### tables + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Table: basic 3-col | basic | ❌ | +| Table: with totals row | basic | ❌ | +| Table: no style | basic | ❌ | +| Table: single column | edge | ❌ | +| Table: header only (no data rows) | edge | ❌ | +| Table: with autoFilter | edge | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Table: basic 3-col | basic | ❌ | ❌ | +| Table: with totals row | basic | ❌ | ❌ | +| Table: no style | basic | ❌ | ❌ | +| Table: single column | edge | ❌ | ❌ | +| Table: header only (no data rows) | edge | ❌ | ❌ | +| Table: with autoFilter | edge | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Table: basic 3-col | basic | ❌ | +| Table: with totals row | basic | ❌ | +| Table: no style | basic | ❌ | +| Table: single column | edge | ❌ | +| Table: header only (no data rows) | edge | ❌ | +| Table: with autoFilter | edge | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Table: basic 3-col | basic | ❌ | ❌ | +| Table: with totals row | basic | ❌ | ❌ | +| Table: no style | basic | ❌ | ❌ | +| Table: single column | edge | ❌ | ❌ | +| Table: header only (no data rows) | edge | ❌ | ❌ | +| Table: with autoFilter | edge | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Table: basic 3-col | basic | ❌ | ❌ | +| Table: with totals row | basic | ❌ | ❌ | +| Table: no style | basic | ❌ | ❌ | +| Table: single column | edge | ❌ | ❌ | +| Table: header only (no data rows) | edge | ❌ | ❌ | +| Table: with autoFilter | edge | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Table: basic 3-col | basic | ❌ | +| Table: with totals row | basic | ❌ | +| Table: no style | basic | ❌ | +| Table: single column | edge | ❌ | +| Table: header only (no data rows) | edge | ❌ | +| Table: with autoFilter | edge | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Table: basic 3-col | basic | ❌ | ❌ | +| Table: with totals row | basic | ❌ | ❌ | +| Table: no style | basic | ❌ | ❌ | +| Table: single column | edge | ❌ | ❌ | +| Table: header only (no data rows) | edge | ❌ | ❌ | +| Table: with autoFilter | edge | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Table: basic 3-col | basic | ❌ | +| Table: with totals row | basic | ❌ | +| Table: no style | basic | ❌ | +| Table: single column | edge | ❌ | +| Table: header only (no data rows) | edge | ❌ | +| Table: with autoFilter | edge | ❌ | + +**xlsxwriter-constmem** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Table: basic 3-col | basic | ❌ | +| Table: with totals row | basic | ❌ | +| Table: no style | basic | ❌ | +| Table: single column | edge | ❌ | +| Table: header only (no data rows) | edge | ❌ | +| Table: with autoFilter | edge | ❌ | + +**xlwt** — Write: 🔴 0 + +| Test | Importance | Write | +|------|-----------|-------| +| Table: basic 3-col | basic | ❌ | +| Table: with totals row | basic | ❌ | +| Table: no style | basic | ❌ | +| Table: single column | edge | ❌ | +| Table: header only (no data rows) | edge | ❌ | +| Table: with autoFilter | edge | ❌ | + + +### text_formatting + +**openpyxl** — Read: 🟢 3 | Write: 🟢 3 + +**openpyxl-readonly** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Bold | basic | ❌ | +| Italic | basic | ❌ | +| Underline - single | basic | ❌ | +| Underline - double | basic | ❌ | +| Strikethrough | basic | ❌ | +| Bold + Italic | basic | ❌ | +| Font size 8 | basic | ❌ | +| Font size 14 | basic | ❌ | +| Font size 24 | basic | ❌ | +| Font size 36 | basic | ❌ | +| Font - Arial | basic | ❌ | +| Font - Times New Roman | basic | ❌ | +| Font - Courier New | basic | ❌ | +| Font color - red | basic | ❌ | +| Font color - blue | basic | ❌ | +| Font color - green | basic | ❌ | +| Font color - custom (#8B4513) | basic | ❌ | +| Combined - bold, 16pt, red | basic | ❌ | + +**pandas** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Bold | basic | ❌ | ❌ | +| Italic | basic | ❌ | ❌ | +| Underline - single | basic | ❌ | ❌ | +| Underline - double | basic | ❌ | ❌ | +| Strikethrough | basic | ❌ | ❌ | +| Bold + Italic | basic | ❌ | ❌ | +| Font size 8 | basic | ❌ | ❌ | +| Font size 14 | basic | ❌ | ❌ | +| Font size 24 | basic | ❌ | ❌ | +| Font size 36 | basic | ❌ | ❌ | +| Font - Arial | basic | ❌ | ❌ | +| Font - Times New Roman | basic | ❌ | ❌ | +| Font - Courier New | basic | ❌ | ❌ | +| Font color - red | basic | ❌ | ❌ | +| Font color - blue | basic | ❌ | ❌ | +| Font color - green | basic | ❌ | ❌ | +| Font color - custom (#8B4513) | basic | ❌ | ❌ | +| Combined - bold, 16pt, red | basic | ❌ | ❌ | + +**polars** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Bold | basic | ❌ | +| Italic | basic | ❌ | +| Underline - single | basic | ❌ | +| Underline - double | basic | ❌ | +| Strikethrough | basic | ❌ | +| Bold + Italic | basic | ❌ | +| Font size 8 | basic | ❌ | +| Font size 14 | basic | ❌ | +| Font size 24 | basic | ❌ | +| Font size 36 | basic | ❌ | +| Font - Arial | basic | ❌ | +| Font - Times New Roman | basic | ❌ | +| Font - Courier New | basic | ❌ | +| Font color - red | basic | ❌ | +| Font color - blue | basic | ❌ | +| Font color - green | basic | ❌ | +| Font color - custom (#8B4513) | basic | ❌ | +| Combined - bold, 16pt, red | basic | ❌ | + +**pyexcel** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Bold | basic | ❌ | ❌ | +| Italic | basic | ❌ | ❌ | +| Underline - single | basic | ❌ | ❌ | +| Underline - double | basic | ❌ | ❌ | +| Strikethrough | basic | ❌ | ❌ | +| Bold + Italic | basic | ❌ | ❌ | +| Font size 8 | basic | ❌ | ❌ | +| Font size 14 | basic | ❌ | ❌ | +| Font size 24 | basic | ❌ | ❌ | +| Font size 36 | basic | ❌ | ❌ | +| Font - Arial | basic | ❌ | ❌ | +| Font - Times New Roman | basic | ❌ | ❌ | +| Font - Courier New | basic | ❌ | ❌ | +| Font color - red | basic | ❌ | ❌ | +| Font color - blue | basic | ❌ | ❌ | +| Font color - green | basic | ❌ | ❌ | +| Font color - custom (#8B4513) | basic | ❌ | ❌ | +| Combined - bold, 16pt, red | basic | ❌ | ❌ | + +**pylightxl** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Bold | basic | ❌ | ❌ | +| Italic | basic | ❌ | ❌ | +| Underline - single | basic | ❌ | ❌ | +| Underline - double | basic | ❌ | ❌ | +| Strikethrough | basic | ❌ | ❌ | +| Bold + Italic | basic | ❌ | ❌ | +| Font size 8 | basic | ❌ | ❌ | +| Font size 14 | basic | ❌ | ❌ | +| Font size 24 | basic | ❌ | ❌ | +| Font size 36 | basic | ❌ | ❌ | +| Font - Arial | basic | ❌ | ❌ | +| Font - Times New Roman | basic | ❌ | ❌ | +| Font - Courier New | basic | ❌ | ❌ | +| Font color - red | basic | ❌ | ❌ | +| Font color - blue | basic | ❌ | ❌ | +| Font color - green | basic | ❌ | ❌ | +| Font color - custom (#8B4513) | basic | ❌ | ❌ | +| Combined - bold, 16pt, red | basic | ❌ | ❌ | + +**python-calamine** — Read: 🔴 0 + +| Test | Importance | Read | +|------|-----------|------| +| Bold | basic | ❌ | +| Italic | basic | ❌ | +| Underline - single | basic | ❌ | +| Underline - double | basic | ❌ | +| Strikethrough | basic | ❌ | +| Bold + Italic | basic | ❌ | +| Font size 8 | basic | ❌ | +| Font size 14 | basic | ❌ | +| Font size 24 | basic | ❌ | +| Font size 36 | basic | ❌ | +| Font - Arial | basic | ❌ | +| Font - Times New Roman | basic | ❌ | +| Font - Courier New | basic | ❌ | +| Font color - red | basic | ❌ | +| Font color - blue | basic | ❌ | +| Font color - green | basic | ❌ | +| Font color - custom (#8B4513) | basic | ❌ | +| Combined - bold, 16pt, red | basic | ❌ | + +**tablib** — Read: 🔴 0 | Write: 🔴 0 + +| Test | Importance | Read | Write | +|------|-----------|------|-------| +| Bold | basic | ❌ | ❌ | +| Italic | basic | ❌ | ❌ | +| Underline - single | basic | ❌ | ❌ | +| Underline - double | basic | ❌ | ❌ | +| Strikethrough | basic | ❌ | ❌ | +| Bold + Italic | basic | ❌ | ❌ | +| Font size 8 | basic | ❌ | ❌ | +| Font size 14 | basic | ❌ | ❌ | +| Font size 24 | basic | ❌ | ❌ | +| Font size 36 | basic | ❌ | ❌ | +| Font - Arial | basic | ❌ | ❌ | +| Font - Times New Roman | basic | ❌ | ❌ | +| Font - Courier New | basic | ❌ | ❌ | +| Font color - red | basic | ❌ | ❌ | +| Font color - blue | basic | ❌ | ❌ | +| Font color - green | basic | ❌ | ❌ | +| Font color - custom (#8B4513) | basic | ❌ | ❌ | +| Combined - bold, 16pt, red | basic | ❌ | ❌ | + +**wolfxl** — Read: 🟢 3 | Write: 🟢 3 + +**xlrd** +- Notes: Not applicable: xlrd does not support .xlsx input + +**xlsxwriter** — Write: 🟢 3 + +**xlsxwriter-constmem** — Write: 🟢 3 + +**xlwt** — Write: 🟠 1 + +| Test | Importance | Write | +|------|-----------|-------| +| Bold | basic | ✅ | +| Italic | basic | ✅ | +| Underline - single | basic | ✅ | +| Underline - double | basic | ✅ | +| Strikethrough | basic | ✅ | +| Bold + Italic | basic | ✅ | +| Font size 8 | basic | ✅ | +| Font size 14 | basic | ✅ | +| Font size 24 | basic | ✅ | +| Font size 36 | basic | ✅ | +| Font - Arial | basic | ✅ | +| Font - Times New Roman | basic | ✅ | +| Font - Courier New | basic | ✅ | +| Font color - red | basic | ✅ | +| Font color - blue | basic | ✅ | +| Font color - green | basic | ❌ | +| Font color - custom (#8B4513) | basic | ❌ | +| Combined - bold, 16pt, red | basic | ✅ | + +--- +*Benchmark version: 0.1.0* \ No newline at end of file diff --git a/results-release-2026-04-28/history.jsonl b/results-release-2026-04-28/history.jsonl new file mode 100644 index 0000000..933c931 --- /dev/null +++ b/results-release-2026-04-28/history.jsonl @@ -0,0 +1 @@ +{"run_date": "2026-04-29T03:12:20.382948+00:00", "commit": "5f98de9", "profile": "xlsx", "scores": {"openpyxl": {"cell_values": {"read": 3, "write": 3}, "formulas": {"read": 3, "write": 3}, "text_formatting": {"read": 3, "write": 3}, "background_colors": {"read": 3, "write": 3}, "number_formats": {"read": 3, "write": 3}, "alignment": {"read": 3, "write": 3}, "borders": {"read": 3, "write": 3}, "dimensions": {"read": 3, "write": 3}, "multiple_sheets": {"read": 3, "write": 3}, "merged_cells": {"read": 3, "write": 3}, "conditional_formatting": {"read": 3, "write": 3}, "data_validation": {"read": 3, "write": 3}, "hyperlinks": {"read": 3, "write": 3}, "images": {"read": 3, "write": 3}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": 3, "write": 3}, "freeze_panes": {"read": 3, "write": 3}, "named_ranges": {"read": 3, "write": 3}, "tables": {"read": 3, "write": 3}}, "xlsxwriter": {"cell_values": {"read": null, "write": 3}, "formulas": {"read": null, "write": 3}, "text_formatting": {"read": null, "write": 3}, "background_colors": {"read": null, "write": 3}, "number_formats": {"read": null, "write": 3}, "alignment": {"read": null, "write": 1}, "borders": {"read": null, "write": 3}, "dimensions": {"read": null, "write": 3}, "multiple_sheets": {"read": null, "write": 3}, "merged_cells": {"read": null, "write": 3}, "conditional_formatting": {"read": null, "write": 3}, "data_validation": {"read": null, "write": 3}, "hyperlinks": {"read": null, "write": 3}, "images": {"read": null, "write": 3}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": null, "write": 3}, "freeze_panes": {"read": null, "write": 3}, "named_ranges": {"read": null, "write": 0}, "tables": {"read": null, "write": 0}}, "python-calamine": {"cell_values": {"read": 1, "write": null}, "formulas": {"read": 0, "write": null}, "text_formatting": {"read": 0, "write": null}, "background_colors": {"read": 0, "write": null}, "number_formats": {"read": 0, "write": null}, "alignment": {"read": 0, "write": null}, "borders": {"read": 0, "write": null}, "dimensions": {"read": 0, "write": null}, "multiple_sheets": {"read": 3, "write": null}, "merged_cells": {"read": 0, "write": null}, "conditional_formatting": {"read": 0, "write": null}, "data_validation": {"read": 0, "write": null}, "hyperlinks": {"read": 0, "write": null}, "images": {"read": 0, "write": null}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": 0, "write": null}, "freeze_panes": {"read": 0, "write": null}, "named_ranges": {"read": 0, "write": null}, "tables": {"read": 0, "write": null}}, "wolfxl": {"cell_values": {"read": 3, "write": 3}, "formulas": {"read": 3, "write": 3}, "text_formatting": {"read": 3, "write": 3}, "background_colors": {"read": 3, "write": 3}, "number_formats": {"read": 3, "write": 3}, "alignment": {"read": 3, "write": 3}, "borders": {"read": 3, "write": 3}, "dimensions": {"read": 3, "write": 3}, "multiple_sheets": {"read": 3, "write": 3}, "merged_cells": {"read": 3, "write": 3}, "conditional_formatting": {"read": 3, "write": 3}, "data_validation": {"read": 3, "write": 3}, "hyperlinks": {"read": 3, "write": 3}, "images": {"read": 3, "write": 3}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": 3, "write": 3}, "freeze_panes": {"read": 3, "write": 3}, "named_ranges": {"read": 3, "write": 3}, "tables": {"read": 3, "write": 3}}, "pylightxl": {"cell_values": {"read": 3, "write": 1}, "formulas": {"read": 0, "write": 3}, "text_formatting": {"read": 0, "write": 0}, "background_colors": {"read": 0, "write": 0}, "number_formats": {"read": 0, "write": 0}, "alignment": {"read": 0, "write": 0}, "borders": {"read": 0, "write": 0}, "dimensions": {"read": 0, "write": 0}, "multiple_sheets": {"read": 3, "write": 3}, "merged_cells": {"read": 0, "write": 0}, "conditional_formatting": {"read": 0, "write": 0}, "data_validation": {"read": 0, "write": 0}, "hyperlinks": {"read": 0, "write": 0}, "images": {"read": 0, "write": 0}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": 0, "write": 0}, "freeze_panes": {"read": 0, "write": 0}, "named_ranges": {"read": 0, "write": 0}, "tables": {"read": 0, "write": 0}}, "xlrd": {"cell_values": {"read": null, "write": null}, "formulas": {"read": null, "write": null}, "text_formatting": {"read": null, "write": null}, "background_colors": {"read": null, "write": null}, "number_formats": {"read": null, "write": null}, "alignment": {"read": null, "write": null}, "borders": {"read": null, "write": null}, "dimensions": {"read": null, "write": null}, "multiple_sheets": {"read": null, "write": null}, "merged_cells": {"read": null, "write": null}, "conditional_formatting": {"read": null, "write": null}, "data_validation": {"read": null, "write": null}, "hyperlinks": {"read": null, "write": null}, "images": {"read": null, "write": null}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": null, "write": null}, "freeze_panes": {"read": null, "write": null}, "named_ranges": {"read": null, "write": null}, "tables": {"read": null, "write": null}}, "pyexcel": {"cell_values": {"read": 3, "write": 3}, "formulas": {"read": 0, "write": 3}, "text_formatting": {"read": 0, "write": 0}, "background_colors": {"read": 0, "write": 0}, "number_formats": {"read": 0, "write": 0}, "alignment": {"read": 0, "write": 0}, "borders": {"read": 0, "write": 0}, "dimensions": {"read": 0, "write": 0}, "multiple_sheets": {"read": 3, "write": 3}, "merged_cells": {"read": 0, "write": 0}, "conditional_formatting": {"read": 0, "write": 0}, "data_validation": {"read": 0, "write": 0}, "hyperlinks": {"read": 0, "write": 0}, "images": {"read": 0, "write": 0}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": 0, "write": 0}, "freeze_panes": {"read": 0, "write": 0}, "named_ranges": {"read": 0, "write": 0}, "tables": {"read": 0, "write": 0}}, "xlwt": {"cell_values": {"read": null, "write": 3}, "formulas": {"read": null, "write": 0}, "text_formatting": {"read": null, "write": 1}, "background_colors": {"read": null, "write": 1}, "number_formats": {"read": null, "write": 3}, "alignment": {"read": null, "write": 3}, "borders": {"read": null, "write": 1}, "dimensions": {"read": null, "write": 1}, "multiple_sheets": {"read": null, "write": 3}, "merged_cells": {"read": null, "write": 0}, "conditional_formatting": {"read": null, "write": 0}, "data_validation": {"read": null, "write": 0}, "hyperlinks": {"read": null, "write": 0}, "images": {"read": null, "write": 0}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": null, "write": 0}, "freeze_panes": {"read": null, "write": 0}, "named_ranges": {"read": null, "write": 0}, "tables": {"read": null, "write": 0}}, "pandas": {"cell_values": {"read": 1, "write": 3}, "formulas": {"read": 0, "write": 3}, "text_formatting": {"read": 0, "write": 0}, "background_colors": {"read": 0, "write": 0}, "number_formats": {"read": 0, "write": 0}, "alignment": {"read": 0, "write": 0}, "borders": {"read": 0, "write": 0}, "dimensions": {"read": 0, "write": 0}, "multiple_sheets": {"read": 3, "write": 3}, "merged_cells": {"read": 0, "write": 0}, "conditional_formatting": {"read": 0, "write": 0}, "data_validation": {"read": 0, "write": 0}, "hyperlinks": {"read": 0, "write": 0}, "images": {"read": 0, "write": 0}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": 0, "write": 0}, "freeze_panes": {"read": 0, "write": 0}, "named_ranges": {"read": 0, "write": 0}, "tables": {"read": 0, "write": 0}}, "xlsxwriter-constmem": {"cell_values": {"read": null, "write": 3}, "formulas": {"read": null, "write": 3}, "text_formatting": {"read": null, "write": 3}, "background_colors": {"read": null, "write": 3}, "number_formats": {"read": null, "write": 3}, "alignment": {"read": null, "write": 1}, "borders": {"read": null, "write": 3}, "dimensions": {"read": null, "write": 1}, "multiple_sheets": {"read": null, "write": 3}, "merged_cells": {"read": null, "write": 3}, "conditional_formatting": {"read": null, "write": 3}, "data_validation": {"read": null, "write": 3}, "hyperlinks": {"read": null, "write": 3}, "images": {"read": null, "write": 0}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": null, "write": 0}, "freeze_panes": {"read": null, "write": 3}, "named_ranges": {"read": null, "write": 0}, "tables": {"read": null, "write": 0}}, "openpyxl-readonly": {"cell_values": {"read": 3, "write": null}, "formulas": {"read": 3, "write": null}, "text_formatting": {"read": 0, "write": null}, "background_colors": {"read": 0, "write": null}, "number_formats": {"read": 0, "write": null}, "alignment": {"read": 0, "write": null}, "borders": {"read": 0, "write": null}, "dimensions": {"read": 0, "write": null}, "multiple_sheets": {"read": 3, "write": null}, "merged_cells": {"read": 0, "write": null}, "conditional_formatting": {"read": 0, "write": null}, "data_validation": {"read": 0, "write": null}, "hyperlinks": {"read": 0, "write": null}, "images": {"read": 0, "write": null}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": 0, "write": null}, "freeze_panes": {"read": 0, "write": null}, "named_ranges": {"read": 0, "write": null}, "tables": {"read": 0, "write": null}}, "polars": {"cell_values": {"read": 1, "write": null}, "formulas": {"read": 0, "write": null}, "text_formatting": {"read": 0, "write": null}, "background_colors": {"read": 0, "write": null}, "number_formats": {"read": 0, "write": null}, "alignment": {"read": 0, "write": null}, "borders": {"read": 0, "write": null}, "dimensions": {"read": 0, "write": null}, "multiple_sheets": {"read": 1, "write": null}, "merged_cells": {"read": 0, "write": null}, "conditional_formatting": {"read": 0, "write": null}, "data_validation": {"read": 0, "write": null}, "hyperlinks": {"read": 0, "write": null}, "images": {"read": 0, "write": null}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": 0, "write": null}, "freeze_panes": {"read": 0, "write": null}, "named_ranges": {"read": 0, "write": null}, "tables": {"read": 0, "write": null}}, "tablib": {"cell_values": {"read": 3, "write": 3}, "formulas": {"read": 0, "write": 3}, "text_formatting": {"read": 0, "write": 0}, "background_colors": {"read": 0, "write": 0}, "number_formats": {"read": 0, "write": 0}, "alignment": {"read": 0, "write": 0}, "borders": {"read": 0, "write": 0}, "dimensions": {"read": 0, "write": 0}, "multiple_sheets": {"read": 3, "write": 3}, "merged_cells": {"read": 0, "write": 0}, "conditional_formatting": {"read": 0, "write": 0}, "data_validation": {"read": 0, "write": 0}, "hyperlinks": {"read": 0, "write": 0}, "images": {"read": 0, "write": 0}, "pivot_tables": {"read": null, "write": null}, "comments": {"read": 0, "write": 0}, "freeze_panes": {"read": 0, "write": 0}, "named_ranges": {"read": 0, "write": 0}, "tables": {"read": 0, "write": 0}}}} diff --git a/results-release-2026-04-28/matrix.csv b/results-release-2026-04-28/matrix.csv new file mode 100644 index 0000000..288e03e --- /dev/null +++ b/results-release-2026-04-28/matrix.csv @@ -0,0 +1,248 @@ +library,feature,read_score,write_score +openpyxl,cell_values,3,3 +xlsxwriter,cell_values,,3 +python-calamine,cell_values,1, +wolfxl,cell_values,3,3 +pylightxl,cell_values,3,1 +xlrd,cell_values,, +pyexcel,cell_values,3,3 +xlwt,cell_values,,3 +pandas,cell_values,1,3 +xlsxwriter-constmem,cell_values,,3 +openpyxl-readonly,cell_values,3, +polars,cell_values,1, +tablib,cell_values,3,3 +openpyxl,formulas,3,3 +xlsxwriter,formulas,,3 +python-calamine,formulas,0, +wolfxl,formulas,3,3 +pylightxl,formulas,0,3 +xlrd,formulas,, +pyexcel,formulas,0,3 +xlwt,formulas,,0 +pandas,formulas,0,3 +xlsxwriter-constmem,formulas,,3 +openpyxl-readonly,formulas,3, +polars,formulas,0, +tablib,formulas,0,3 +openpyxl,text_formatting,3,3 +xlsxwriter,text_formatting,,3 +python-calamine,text_formatting,0, +wolfxl,text_formatting,3,3 +pylightxl,text_formatting,0,0 +xlrd,text_formatting,, +pyexcel,text_formatting,0,0 +xlwt,text_formatting,,1 +pandas,text_formatting,0,0 +xlsxwriter-constmem,text_formatting,,3 +openpyxl-readonly,text_formatting,0, +polars,text_formatting,0, +tablib,text_formatting,0,0 +openpyxl,background_colors,3,3 +xlsxwriter,background_colors,,3 +python-calamine,background_colors,0, +wolfxl,background_colors,3,3 +pylightxl,background_colors,0,0 +xlrd,background_colors,, +pyexcel,background_colors,0,0 +xlwt,background_colors,,1 +pandas,background_colors,0,0 +xlsxwriter-constmem,background_colors,,3 +openpyxl-readonly,background_colors,0, +polars,background_colors,0, +tablib,background_colors,0,0 +openpyxl,number_formats,3,3 +xlsxwriter,number_formats,,3 +python-calamine,number_formats,0, +wolfxl,number_formats,3,3 +pylightxl,number_formats,0,0 +xlrd,number_formats,, +pyexcel,number_formats,0,0 +xlwt,number_formats,,3 +pandas,number_formats,0,0 +xlsxwriter-constmem,number_formats,,3 +openpyxl-readonly,number_formats,0, +polars,number_formats,0, +tablib,number_formats,0,0 +openpyxl,alignment,3,3 +xlsxwriter,alignment,,1 +python-calamine,alignment,0, +wolfxl,alignment,3,3 +pylightxl,alignment,0,0 +xlrd,alignment,, +pyexcel,alignment,0,0 +xlwt,alignment,,3 +pandas,alignment,0,0 +xlsxwriter-constmem,alignment,,1 +openpyxl-readonly,alignment,0, +polars,alignment,0, +tablib,alignment,0,0 +openpyxl,borders,3,3 +xlsxwriter,borders,,3 +python-calamine,borders,0, +wolfxl,borders,3,3 +pylightxl,borders,0,0 +xlrd,borders,, +pyexcel,borders,0,0 +xlwt,borders,,1 +pandas,borders,0,0 +xlsxwriter-constmem,borders,,3 +openpyxl-readonly,borders,0, +polars,borders,0, +tablib,borders,0,0 +openpyxl,dimensions,3,3 +xlsxwriter,dimensions,,3 +python-calamine,dimensions,0, +wolfxl,dimensions,3,3 +pylightxl,dimensions,0,0 +xlrd,dimensions,, +pyexcel,dimensions,0,0 +xlwt,dimensions,,1 +pandas,dimensions,0,0 +xlsxwriter-constmem,dimensions,,1 +openpyxl-readonly,dimensions,0, +polars,dimensions,0, +tablib,dimensions,0,0 +openpyxl,multiple_sheets,3,3 +xlsxwriter,multiple_sheets,,3 +python-calamine,multiple_sheets,3, +wolfxl,multiple_sheets,3,3 +pylightxl,multiple_sheets,3,3 +xlrd,multiple_sheets,, +pyexcel,multiple_sheets,3,3 +xlwt,multiple_sheets,,3 +pandas,multiple_sheets,3,3 +xlsxwriter-constmem,multiple_sheets,,3 +openpyxl-readonly,multiple_sheets,3, +polars,multiple_sheets,1, +tablib,multiple_sheets,3,3 +openpyxl,merged_cells,3,3 +xlsxwriter,merged_cells,,3 +python-calamine,merged_cells,0, +wolfxl,merged_cells,3,3 +pylightxl,merged_cells,0,0 +xlrd,merged_cells,, +pyexcel,merged_cells,0,0 +xlwt,merged_cells,,0 +pandas,merged_cells,0,0 +xlsxwriter-constmem,merged_cells,,3 +openpyxl-readonly,merged_cells,0, +polars,merged_cells,0, +tablib,merged_cells,0,0 +openpyxl,conditional_formatting,3,3 +xlsxwriter,conditional_formatting,,3 +python-calamine,conditional_formatting,0, +wolfxl,conditional_formatting,3,3 +pylightxl,conditional_formatting,0,0 +xlrd,conditional_formatting,, +pyexcel,conditional_formatting,0,0 +xlwt,conditional_formatting,,0 +pandas,conditional_formatting,0,0 +xlsxwriter-constmem,conditional_formatting,,3 +openpyxl-readonly,conditional_formatting,0, +polars,conditional_formatting,0, +tablib,conditional_formatting,0,0 +openpyxl,data_validation,3,3 +xlsxwriter,data_validation,,3 +python-calamine,data_validation,0, +wolfxl,data_validation,3,3 +pylightxl,data_validation,0,0 +xlrd,data_validation,, +pyexcel,data_validation,0,0 +xlwt,data_validation,,0 +pandas,data_validation,0,0 +xlsxwriter-constmem,data_validation,,3 +openpyxl-readonly,data_validation,0, +polars,data_validation,0, +tablib,data_validation,0,0 +openpyxl,hyperlinks,3,3 +xlsxwriter,hyperlinks,,3 +python-calamine,hyperlinks,0, +wolfxl,hyperlinks,3,3 +pylightxl,hyperlinks,0,0 +xlrd,hyperlinks,, +pyexcel,hyperlinks,0,0 +xlwt,hyperlinks,,0 +pandas,hyperlinks,0,0 +xlsxwriter-constmem,hyperlinks,,3 +openpyxl-readonly,hyperlinks,0, +polars,hyperlinks,0, +tablib,hyperlinks,0,0 +openpyxl,images,3,3 +xlsxwriter,images,,3 +python-calamine,images,0, +wolfxl,images,3,3 +pylightxl,images,0,0 +xlrd,images,, +pyexcel,images,0,0 +xlwt,images,,0 +pandas,images,0,0 +xlsxwriter-constmem,images,,0 +openpyxl-readonly,images,0, +polars,images,0, +tablib,images,0,0 +openpyxl,pivot_tables,, +xlsxwriter,pivot_tables,, +python-calamine,pivot_tables,, +wolfxl,pivot_tables,, +pylightxl,pivot_tables,, +xlrd,pivot_tables,, +pyexcel,pivot_tables,, +xlwt,pivot_tables,, +pandas,pivot_tables,, +xlsxwriter-constmem,pivot_tables,, +openpyxl-readonly,pivot_tables,, +polars,pivot_tables,, +tablib,pivot_tables,, +openpyxl,comments,3,3 +xlsxwriter,comments,,3 +python-calamine,comments,0, +wolfxl,comments,3,3 +pylightxl,comments,0,0 +xlrd,comments,, +pyexcel,comments,0,0 +xlwt,comments,,0 +pandas,comments,0,0 +xlsxwriter-constmem,comments,,0 +openpyxl-readonly,comments,0, +polars,comments,0, +tablib,comments,0,0 +openpyxl,freeze_panes,3,3 +xlsxwriter,freeze_panes,,3 +python-calamine,freeze_panes,0, +wolfxl,freeze_panes,3,3 +pylightxl,freeze_panes,0,0 +xlrd,freeze_panes,, +pyexcel,freeze_panes,0,0 +xlwt,freeze_panes,,0 +pandas,freeze_panes,0,0 +xlsxwriter-constmem,freeze_panes,,3 +openpyxl-readonly,freeze_panes,0, +polars,freeze_panes,0, +tablib,freeze_panes,0,0 +openpyxl,named_ranges,3,3 +xlsxwriter,named_ranges,,0 +python-calamine,named_ranges,0, +wolfxl,named_ranges,3,3 +pylightxl,named_ranges,0,0 +xlrd,named_ranges,, +pyexcel,named_ranges,0,0 +xlwt,named_ranges,,0 +pandas,named_ranges,0,0 +xlsxwriter-constmem,named_ranges,,0 +openpyxl-readonly,named_ranges,0, +polars,named_ranges,0, +tablib,named_ranges,0,0 +openpyxl,tables,3,3 +xlsxwriter,tables,,0 +python-calamine,tables,0, +wolfxl,tables,3,3 +pylightxl,tables,0,0 +xlrd,tables,, +pyexcel,tables,0,0 +xlwt,tables,,0 +pandas,tables,0,0 +xlsxwriter-constmem,tables,,0 +openpyxl-readonly,tables,0, +polars,tables,0, +tablib,tables,0,0 \ No newline at end of file diff --git a/results-release-2026-04-28/perf/README.md b/results-release-2026-04-28/perf/README.md new file mode 100644 index 0000000..cff77ac --- /dev/null +++ b/results-release-2026-04-28/perf/README.md @@ -0,0 +1,193 @@ +# ExcelBench Performance Results + +*Generated: 2026-04-29T03:12:22.197235+00:00* +*Profile: xlsx* +*Platform: Darwin-arm64* +*Python: 3.12.3* +*Commit: 5f98de9* +*Config: warmup=3 iters=25 iteration_policy=fixed breakdown=False* + +## Notes + +These numbers measure only the library under test. Write timings do NOT include oracle verification. + +## Summary (p50 wall time) + +**Tier 0 — Basic Values** + +| Feature | openpyxl (R p50 ms) | openpyxl (W p50 ms) | openpyxl-readonly (R p50 ms) | pandas (R p50 ms) | pandas (W p50 ms) | polars (R p50 ms) | pyexcel (R p50 ms) | pyexcel (W p50 ms) | pylightxl (R p50 ms) | pylightxl (W p50 ms) | python-calamine (R p50 ms) | tablib (R p50 ms) | tablib (W p50 ms) | wolfxl (R p50 ms) | wolfxl (W p50 ms) | xlrd (R p50 ms) | xlsxwriter (W p50 ms) | xlsxwriter-constmem (W p50 ms) | xlwt (W p50 ms) | +|---------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------| +| cell_values | 1.36 | 1.42 | 5.00 | 1.79 | 1.79 | 0.33 | 1.51 | 1.46 | 1.41 | 0.39 | 0.93 | 1.37 | 1.52 | 0.24 | 0.19 | — | 1.90 | 1.85 | 0.24 | +| formulas | 1.17 | 1.53 | 1.44 | 1.64 | 1.93 | 0.41 | 1.30 | 1.65 | 1.20 | 0.31 | 0.16 | 1.30 | 1.62 | 0.19 | 0.19 | — | 1.78 | 2.03 | 0.28 | +| multiple_sheets | 1.33 | 1.82 | 1.02 | 1.84 | 2.56 | 0.62 | 1.45 | 1.96 | 1.39 | 0.35 | 0.07 | 1.36 | 2.04 | 0.07 | 0.19 | — | 2.29 | 2.61 | 0.19 | + +**Tier 1 — Formatting** + +| Feature | openpyxl (R p50 ms) | openpyxl (W p50 ms) | openpyxl-readonly (R p50 ms) | pandas (R p50 ms) | pandas (W p50 ms) | polars (R p50 ms) | pyexcel (R p50 ms) | pyexcel (W p50 ms) | pylightxl (R p50 ms) | pylightxl (W p50 ms) | python-calamine (R p50 ms) | tablib (R p50 ms) | tablib (W p50 ms) | wolfxl (R p50 ms) | wolfxl (W p50 ms) | xlrd (R p50 ms) | xlsxwriter (W p50 ms) | xlsxwriter-constmem (W p50 ms) | xlwt (W p50 ms) | +|---------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------| +| alignment | 1.28 | 1.50 | 0.96 | 1.49 | 1.70 | 0.27 | 1.43 | 1.50 | — | 0.34 | 0.06 | 1.30 | 1.37 | 0.29 | 0.19 | — | 2.18 | 1.87 | 0.24 | +| background_colors | 1.08 | 1.51 | 0.91 | 1.41 | 1.82 | 0.33 | 1.17 | 1.40 | 1.03 | 0.33 | 0.06 | 1.09 | 1.50 | 0.27 | 0.17 | — | 2.28 | 2.03 | 0.21 | +| borders | 1.85 | 2.45 | 1.30 | 2.08 | 1.83 | 0.29 | 1.83 | 1.54 | — | 0.42 | 0.07 | 1.78 | 1.56 | 0.35 | 0.27 | — | 2.36 | 2.55 | 0.43 | +| dimensions | 0.99 | 1.29 | 0.87 | 1.28 | 1.41 | 0.32 | 1.09 | 1.30 | 0.96 | 0.27 | 0.06 | 1.00 | 1.33 | 0.08 | 0.16 | — | 1.71 | 1.78 | 0.15 | +| number_formats | 1.18 | 1.70 | 0.91 | 1.51 | 1.98 | 0.26 | 1.25 | 1.46 | 1.07 | 0.46 | 0.07 | 1.08 | 1.35 | 0.32 | 0.18 | — | 2.09 | 2.08 | 0.22 | +| text_formatting | 1.74 | 2.03 | 1.50 | 2.11 | 1.77 | 0.34 | 1.93 | 1.44 | 1.55 | 0.41 | 0.09 | 1.91 | 1.72 | 0.36 | 0.23 | — | 2.39 | 2.28 | 0.35 | + +**Tier 2 — Advanced** + +| Feature | openpyxl (R p50 ms) | openpyxl (W p50 ms) | openpyxl-readonly (R p50 ms) | pandas (R p50 ms) | pandas (W p50 ms) | polars (R p50 ms) | pyexcel (R p50 ms) | pyexcel (W p50 ms) | pylightxl (R p50 ms) | pylightxl (W p50 ms) | python-calamine (R p50 ms) | tablib (R p50 ms) | tablib (W p50 ms) | wolfxl (R p50 ms) | wolfxl (W p50 ms) | xlrd (R p50 ms) | xlsxwriter (W p50 ms) | xlsxwriter-constmem (W p50 ms) | xlwt (W p50 ms) | +|---------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------|--------------| +| comments | 1.15 | 1.59 | 0.92 | 1.36 | 1.61 | 0.25 | 1.28 | 1.39 | 1.20 | 0.26 | 0.07 | 1.06 | 1.34 | 0.21 | 0.20 | — | 2.26 | 1.81 | 0.13 | +| conditional_formatting | 1.42 | 1.84 | 0.96 | 1.80 | 2.16 | 0.40 | 1.53 | 1.56 | — | 0.31 | 0.06 | 1.42 | 1.51 | 0.22 | 0.19 | — | 1.87 | 2.03 | 0.16 | +| data_validation | 1.33 | 1.38 | 0.97 | 2.29 | 1.87 | 0.49 | 1.51 | 1.61 | — | 0.27 | 0.05 | 1.37 | 1.42 | 0.16 | 0.16 | — | 1.65 | 1.99 | 0.15 | +| freeze_panes | 1.45 | 2.32 | 1.02 | 2.00 | 2.46 | 0.51 | 1.74 | 2.49 | — | 0.32 | 0.06 | 1.45 | 2.58 | 0.24 | 0.20 | — | 2.29 | 2.56 | 0.19 | +| hyperlinks | 1.39 | 1.50 | 0.97 | 1.69 | 1.54 | 0.47 | 1.55 | 1.45 | — | 0.28 | 0.06 | 1.24 | 1.49 | 0.25 | 0.18 | — | 2.20 | 2.54 | 0.13 | +| images | 1.28 | 2.03 | 0.83 | 1.27 | 1.43 | 0.29 | 1.31 | 1.38 | — | 0.27 | 0.06 | 0.98 | 1.34 | 0.46 | 0.28 | — | 3.07 | 1.67 | 0.14 | +| merged_cells | 1.28 | 1.47 | 0.91 | 1.40 | 1.72 | 0.26 | 1.40 | 1.36 | 1.03 | 0.34 | 0.06 | 1.10 | 1.35 | 0.30 | 0.16 | — | 1.85 | 1.91 | 0.20 | +| named_ranges | 1.24 | 1.59 | 0.89 | 1.81 | 1.97 | 0.43 | 1.34 | 1.77 | — | 0.28 | 0.05 | 1.27 | 1.78 | 0.06 | 0.17 | — | 2.01 | 2.16 | 0.16 | +| pivot_tables | 0.90 | 1.29 | 0.82 | 1.24 | 1.47 | 0.25 | 0.97 | 1.37 | 0.86 | 0.28 | 0.06 | 0.95 | 1.26 | 0.06 | 0.15 | — | 1.72 | 1.73 | 0.14 | +| tables | 1.66 | 1.43 | 0.92 | 1.96 | 1.69 | 0.38 | 2.06 | 1.67 | — | 0.27 | 0.06 | 1.41 | 1.35 | 0.06 | 0.15 | — | 1.75 | 2.03 | 0.14 | + +## Run Issues + +- alignment / openpyxl-readonly: Write unsupported +- alignment / polars: Write unsupported +- alignment / pylightxl: Read failed: TypeError: expected string or bytes-like object, got 'NoneType' +- alignment / python-calamine: Write unsupported +- alignment / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- alignment / xlsxwriter-constmem: Read unsupported +- alignment / xlsxwriter: Read unsupported +- alignment / xlwt: Read unsupported +- background_colors / openpyxl-readonly: Write unsupported +- background_colors / polars: Write unsupported +- background_colors / python-calamine: Write unsupported +- background_colors / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- background_colors / xlsxwriter-constmem: Read unsupported +- background_colors / xlsxwriter: Read unsupported +- background_colors / xlwt: Read unsupported +- borders / openpyxl-readonly: Write unsupported +- borders / polars: Write unsupported +- borders / pylightxl: Read failed: TypeError: expected string or bytes-like object, got 'NoneType' +- borders / python-calamine: Write unsupported +- borders / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- borders / xlsxwriter-constmem: Read unsupported +- borders / xlsxwriter: Read unsupported +- borders / xlwt: Read unsupported +- cell_values / openpyxl-readonly: Write unsupported +- cell_values / polars: Write unsupported +- cell_values / python-calamine: Write unsupported +- cell_values / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- cell_values / xlsxwriter-constmem: Read unsupported +- cell_values / xlsxwriter: Read unsupported +- cell_values / xlwt: Read unsupported +- comments / openpyxl-readonly: Write unsupported +- comments / polars: Write unsupported +- comments / python-calamine: Write unsupported +- comments / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- comments / xlsxwriter-constmem: Read unsupported +- comments / xlsxwriter: Read unsupported +- comments / xlwt: Read unsupported +- conditional_formatting / openpyxl-readonly: Write unsupported +- conditional_formatting / polars: Write unsupported +- conditional_formatting / pylightxl: Read failed: TypeError: expected string or bytes-like object, got 'NoneType' +- conditional_formatting / python-calamine: Write unsupported +- conditional_formatting / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- conditional_formatting / xlsxwriter-constmem: Read unsupported +- conditional_formatting / xlsxwriter: Read unsupported +- conditional_formatting / xlwt: Read unsupported +- data_validation / openpyxl-readonly: Write unsupported +- data_validation / polars: Write unsupported +- data_validation / pylightxl: Read failed: TypeError: expected string or bytes-like object, got 'NoneType' +- data_validation / python-calamine: Write unsupported +- data_validation / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- data_validation / xlsxwriter-constmem: Read unsupported +- data_validation / xlsxwriter: Read unsupported +- data_validation / xlwt: Read unsupported +- dimensions / openpyxl-readonly: Write unsupported +- dimensions / polars: Write unsupported +- dimensions / python-calamine: Write unsupported +- dimensions / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- dimensions / xlsxwriter-constmem: Read unsupported +- dimensions / xlsxwriter: Read unsupported +- dimensions / xlwt: Read unsupported +- formulas / openpyxl-readonly: Write unsupported +- formulas / polars: Write unsupported +- formulas / python-calamine: Write unsupported +- formulas / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- formulas / xlsxwriter-constmem: Read unsupported +- formulas / xlsxwriter: Read unsupported +- formulas / xlwt: Read unsupported +- freeze_panes / openpyxl-readonly: Write unsupported +- freeze_panes / polars: Write unsupported +- freeze_panes / pylightxl: Read failed: TypeError: expected string or bytes-like object, got 'NoneType' +- freeze_panes / python-calamine: Write unsupported +- freeze_panes / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- freeze_panes / xlsxwriter-constmem: Read unsupported +- freeze_panes / xlsxwriter: Read unsupported +- freeze_panes / xlwt: Read unsupported +- hyperlinks / openpyxl-readonly: Write unsupported +- hyperlinks / polars: Write unsupported +- hyperlinks / pylightxl: Read failed: TypeError: expected string or bytes-like object, got 'NoneType' +- hyperlinks / python-calamine: Write unsupported +- hyperlinks / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- hyperlinks / xlsxwriter-constmem: Read unsupported +- hyperlinks / xlsxwriter: Read unsupported +- hyperlinks / xlwt: Read unsupported +- images / openpyxl-readonly: Write unsupported +- images / polars: Write unsupported +- images / pylightxl: Read failed: TypeError: expected string or bytes-like object, got 'NoneType' +- images / python-calamine: Write unsupported +- images / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- images / xlsxwriter-constmem: Read unsupported +- images / xlsxwriter: Read unsupported +- images / xlwt: Read unsupported +- merged_cells / openpyxl-readonly: Write unsupported +- merged_cells / polars: Write unsupported +- merged_cells / python-calamine: Write unsupported +- merged_cells / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- merged_cells / xlsxwriter-constmem: Read unsupported +- merged_cells / xlsxwriter: Read unsupported +- merged_cells / xlwt: Read unsupported +- multiple_sheets / openpyxl-readonly: Write unsupported +- multiple_sheets / polars: Write unsupported +- multiple_sheets / python-calamine: Write unsupported +- multiple_sheets / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- multiple_sheets / xlsxwriter-constmem: Read unsupported +- multiple_sheets / xlsxwriter: Read unsupported +- multiple_sheets / xlwt: Read unsupported +- named_ranges / openpyxl-readonly: Write unsupported +- named_ranges / polars: Write unsupported +- named_ranges / pylightxl: Read failed: TypeError: expected string or bytes-like object, got 'NoneType' +- named_ranges / python-calamine: Write unsupported +- named_ranges / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- named_ranges / xlsxwriter-constmem: Read unsupported +- named_ranges / xlsxwriter: Read unsupported +- named_ranges / xlwt: Read unsupported +- number_formats / openpyxl-readonly: Write unsupported +- number_formats / polars: Write unsupported +- number_formats / python-calamine: Write unsupported +- number_formats / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- number_formats / xlsxwriter-constmem: Read unsupported +- number_formats / xlsxwriter: Read unsupported +- number_formats / xlwt: Read unsupported +- pivot_tables / openpyxl-readonly: Write unsupported +- pivot_tables / polars: Write unsupported +- pivot_tables / python-calamine: Write unsupported +- pivot_tables / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- pivot_tables / xlsxwriter-constmem: Read unsupported +- pivot_tables / xlsxwriter: Read unsupported +- pivot_tables / xlwt: Read unsupported +- tables / openpyxl-readonly: Write unsupported +- tables / polars: Write unsupported +- tables / pylightxl: Read failed: TypeError: expected string or bytes-like object, got 'NoneType' +- tables / python-calamine: Write unsupported +- tables / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- tables / xlsxwriter-constmem: Read unsupported +- tables / xlsxwriter: Read unsupported +- tables / xlwt: Read unsupported +- text_formatting / openpyxl-readonly: Write unsupported +- text_formatting / polars: Write unsupported +- text_formatting / python-calamine: Write unsupported +- text_formatting / xlrd: Write unsupported; Read not applicable: xlrd does not support .xlsx input +- text_formatting / xlsxwriter-constmem: Read unsupported +- text_formatting / xlsxwriter: Read unsupported +- text_formatting / xlwt: Read unsupported diff --git a/results-release-2026-04-28/perf/history.jsonl b/results-release-2026-04-28/perf/history.jsonl new file mode 100644 index 0000000..b31cea5 --- /dev/null +++ b/results-release-2026-04-28/perf/history.jsonl @@ -0,0 +1 @@ +{"run_date": "2026-04-29T03:12:22.197235+00:00", "commit": "5f98de9", "profile": "xlsx", "config": {"warmup": 3, "iters": 25, "iteration_policy": "fixed", "breakdown": false}, "p50_wall_ms": {"openpyxl": {"cell_values": {"read_p50": 1.361917, "write_p50": 1.422}, "formulas": {"read_p50": 1.165792, "write_p50": 1.531209}, "text_formatting": {"read_p50": 1.736334, "write_p50": 2.03}, "background_colors": {"read_p50": 1.079833, "write_p50": 1.506458}, "number_formats": {"read_p50": 1.178, "write_p50": 1.701792}, "alignment": {"read_p50": 1.277958, "write_p50": 1.499542}, "borders": {"read_p50": 1.853584, "write_p50": 2.452875}, "dimensions": {"read_p50": 0.987, "write_p50": 1.287125}, "multiple_sheets": {"read_p50": 1.334666, "write_p50": 1.817333}, "merged_cells": {"read_p50": 1.284375, "write_p50": 1.468542}, "conditional_formatting": {"read_p50": 1.424834, "write_p50": 1.842792}, "data_validation": {"read_p50": 1.325875, "write_p50": 1.378917}, "hyperlinks": {"read_p50": 1.385542, "write_p50": 1.502334}, "images": {"read_p50": 1.276125, "write_p50": 2.02675}, "pivot_tables": {"read_p50": 0.896875, "write_p50": 1.285291}, "comments": {"read_p50": 1.152666, "write_p50": 1.587709}, "freeze_panes": {"read_p50": 1.4545, "write_p50": 2.318334}, "named_ranges": {"read_p50": 1.240333, "write_p50": 1.591}, "tables": {"read_p50": 1.657125, "write_p50": 1.426625}}, "xlsxwriter": {"cell_values": {"read_p50": null, "write_p50": 1.896542}, "formulas": {"read_p50": null, "write_p50": 1.778625}, "text_formatting": {"read_p50": null, "write_p50": 2.392375}, "background_colors": {"read_p50": null, "write_p50": 2.281083}, "number_formats": {"read_p50": null, "write_p50": 2.093}, "alignment": {"read_p50": null, "write_p50": 2.18}, "borders": {"read_p50": null, "write_p50": 2.356459}, "dimensions": {"read_p50": null, "write_p50": 1.708167}, "multiple_sheets": {"read_p50": null, "write_p50": 2.293208}, "merged_cells": {"read_p50": null, "write_p50": 1.846166}, "conditional_formatting": {"read_p50": null, "write_p50": 1.871333}, "data_validation": {"read_p50": null, "write_p50": 1.647709}, "hyperlinks": {"read_p50": null, "write_p50": 2.195375}, "images": {"read_p50": null, "write_p50": 3.072083}, "pivot_tables": {"read_p50": null, "write_p50": 1.71925}, "comments": {"read_p50": null, "write_p50": 2.255833}, "freeze_panes": {"read_p50": null, "write_p50": 2.287917}, "named_ranges": {"read_p50": null, "write_p50": 2.010292}, "tables": {"read_p50": null, "write_p50": 1.751333}}, "python-calamine": {"cell_values": {"read_p50": 0.9335, "write_p50": null}, "formulas": {"read_p50": 0.164917, "write_p50": null}, "text_formatting": {"read_p50": 0.093584, "write_p50": null}, "background_colors": {"read_p50": 0.064041, "write_p50": null}, "number_formats": {"read_p50": 0.066416, "write_p50": null}, "alignment": {"read_p50": 0.060875, "write_p50": null}, "borders": {"read_p50": 0.069459, "write_p50": null}, "dimensions": {"read_p50": 0.062333, "write_p50": null}, "multiple_sheets": {"read_p50": 0.06725, "write_p50": null}, "merged_cells": {"read_p50": 0.063542, "write_p50": null}, "conditional_formatting": {"read_p50": 0.056542, "write_p50": null}, "data_validation": {"read_p50": 0.053708, "write_p50": null}, "hyperlinks": {"read_p50": 0.055417, "write_p50": null}, "images": {"read_p50": 0.056125, "write_p50": null}, "pivot_tables": {"read_p50": 0.05875, "write_p50": null}, "comments": {"read_p50": 0.065708, "write_p50": null}, "freeze_panes": {"read_p50": 0.058125, "write_p50": null}, "named_ranges": {"read_p50": 0.0545, "write_p50": null}, "tables": {"read_p50": 0.058875, "write_p50": null}}, "wolfxl": {"cell_values": {"read_p50": 0.241209, "write_p50": 0.189541}, "formulas": {"read_p50": 0.188083, "write_p50": 0.190208}, "text_formatting": {"read_p50": 0.364708, "write_p50": 0.234375}, "background_colors": {"read_p50": 0.27125, "write_p50": 0.169583}, "number_formats": {"read_p50": 0.322875, "write_p50": 0.178208}, "alignment": {"read_p50": 0.288875, "write_p50": 0.193625}, "borders": {"read_p50": 0.348417, "write_p50": 0.272708}, "dimensions": {"read_p50": 0.083958, "write_p50": 0.163209}, "multiple_sheets": {"read_p50": 0.065166, "write_p50": 0.187083}, "merged_cells": {"read_p50": 0.299834, "write_p50": 0.162375}, "conditional_formatting": {"read_p50": 0.216666, "write_p50": 0.187042}, "data_validation": {"read_p50": 0.161083, "write_p50": 0.164833}, "hyperlinks": {"read_p50": 0.251417, "write_p50": 0.184916}, "images": {"read_p50": 0.46275, "write_p50": 0.279584}, "pivot_tables": {"read_p50": 0.060083, "write_p50": 0.153125}, "comments": {"read_p50": 0.211875, "write_p50": 0.200584}, "freeze_panes": {"read_p50": 0.2395, "write_p50": 0.202}, "named_ranges": {"read_p50": 0.055, "write_p50": 0.1725}, "tables": {"read_p50": 0.057833, "write_p50": 0.153416}}, "pylightxl": {"cell_values": {"read_p50": 1.405542, "write_p50": 0.389292}, "formulas": {"read_p50": 1.196708, "write_p50": 0.3065}, "text_formatting": {"read_p50": 1.553667, "write_p50": 0.412}, "background_colors": {"read_p50": 1.027833, "write_p50": 0.33175}, "number_formats": {"read_p50": 1.068208, "write_p50": 0.456875}, "alignment": {"read_p50": null, "write_p50": 0.343709}, "borders": {"read_p50": null, "write_p50": 0.422166}, "dimensions": {"read_p50": 0.956292, "write_p50": 0.266042}, "multiple_sheets": {"read_p50": 1.389208, "write_p50": 0.353625}, "merged_cells": {"read_p50": 1.033208, "write_p50": 0.33775}, "conditional_formatting": {"read_p50": null, "write_p50": 0.307292}, "data_validation": {"read_p50": null, "write_p50": 0.265625}, "hyperlinks": {"read_p50": null, "write_p50": 0.27975}, "images": {"read_p50": null, "write_p50": 0.265083}, "pivot_tables": {"read_p50": 0.856667, "write_p50": 0.275}, "comments": {"read_p50": 1.1985, "write_p50": 0.25925}, "freeze_panes": {"read_p50": null, "write_p50": 0.317542}, "named_ranges": {"read_p50": null, "write_p50": 0.279709}, "tables": {"read_p50": null, "write_p50": 0.273167}}, "xlrd": {"cell_values": {"read_p50": null, "write_p50": null}, "formulas": {"read_p50": null, "write_p50": null}, "text_formatting": {"read_p50": null, "write_p50": null}, "background_colors": {"read_p50": null, "write_p50": null}, "number_formats": {"read_p50": null, "write_p50": null}, "alignment": {"read_p50": null, "write_p50": null}, "borders": {"read_p50": null, "write_p50": null}, "dimensions": {"read_p50": null, "write_p50": null}, "multiple_sheets": {"read_p50": null, "write_p50": null}, "merged_cells": {"read_p50": null, "write_p50": null}, "conditional_formatting": {"read_p50": null, "write_p50": null}, "data_validation": {"read_p50": null, "write_p50": null}, "hyperlinks": {"read_p50": null, "write_p50": null}, "images": {"read_p50": null, "write_p50": null}, "pivot_tables": {"read_p50": null, "write_p50": null}, "comments": {"read_p50": null, "write_p50": null}, "freeze_panes": {"read_p50": null, "write_p50": null}, "named_ranges": {"read_p50": null, "write_p50": null}, "tables": {"read_p50": null, "write_p50": null}}, "pyexcel": {"cell_values": {"read_p50": 1.51475, "write_p50": 1.456125}, "formulas": {"read_p50": 1.301334, "write_p50": 1.647333}, "text_formatting": {"read_p50": 1.930209, "write_p50": 1.441125}, "background_colors": {"read_p50": 1.169, "write_p50": 1.398834}, "number_formats": {"read_p50": 1.2475, "write_p50": 1.46125}, "alignment": {"read_p50": 1.430583, "write_p50": 1.504375}, "borders": {"read_p50": 1.830292, "write_p50": 1.541875}, "dimensions": {"read_p50": 1.089333, "write_p50": 1.301542}, "multiple_sheets": {"read_p50": 1.453041, "write_p50": 1.964084}, "merged_cells": {"read_p50": 1.40275, "write_p50": 1.364833}, "conditional_formatting": {"read_p50": 1.528084, "write_p50": 1.563417}, "data_validation": {"read_p50": 1.508792, "write_p50": 1.605708}, "hyperlinks": {"read_p50": 1.552166, "write_p50": 1.449958}, "images": {"read_p50": 1.313125, "write_p50": 1.375208}, "pivot_tables": {"read_p50": 0.966625, "write_p50": 1.37425}, "comments": {"read_p50": 1.281875, "write_p50": 1.388917}, "freeze_panes": {"read_p50": 1.738916, "write_p50": 2.486584}, "named_ranges": {"read_p50": 1.336875, "write_p50": 1.77025}, "tables": {"read_p50": 2.059917, "write_p50": 1.67}}, "xlwt": {"cell_values": {"read_p50": null, "write_p50": 0.241916}, "formulas": {"read_p50": null, "write_p50": 0.275916}, "text_formatting": {"read_p50": null, "write_p50": 0.345875}, "background_colors": {"read_p50": null, "write_p50": 0.206125}, "number_formats": {"read_p50": null, "write_p50": 0.216959}, "alignment": {"read_p50": null, "write_p50": 0.239709}, "borders": {"read_p50": null, "write_p50": 0.428917}, "dimensions": {"read_p50": null, "write_p50": 0.15175}, "multiple_sheets": {"read_p50": null, "write_p50": 0.192834}, "merged_cells": {"read_p50": null, "write_p50": 0.1995}, "conditional_formatting": {"read_p50": null, "write_p50": 0.158167}, "data_validation": {"read_p50": null, "write_p50": 0.14725}, "hyperlinks": {"read_p50": null, "write_p50": 0.133458}, "images": {"read_p50": null, "write_p50": 0.136042}, "pivot_tables": {"read_p50": null, "write_p50": 0.138167}, "comments": {"read_p50": null, "write_p50": 0.133416}, "freeze_panes": {"read_p50": null, "write_p50": 0.191292}, "named_ranges": {"read_p50": null, "write_p50": 0.158708}, "tables": {"read_p50": null, "write_p50": 0.139667}}, "pandas": {"cell_values": {"read_p50": 1.785625, "write_p50": 1.794833}, "formulas": {"read_p50": 1.638125, "write_p50": 1.927167}, "text_formatting": {"read_p50": 2.105833, "write_p50": 1.768625}, "background_colors": {"read_p50": 1.410209, "write_p50": 1.820166}, "number_formats": {"read_p50": 1.509708, "write_p50": 1.976875}, "alignment": {"read_p50": 1.487042, "write_p50": 1.695875}, "borders": {"read_p50": 2.08375, "write_p50": 1.825667}, "dimensions": {"read_p50": 1.283584, "write_p50": 1.414333}, "multiple_sheets": {"read_p50": 1.837584, "write_p50": 2.56125}, "merged_cells": {"read_p50": 1.399, "write_p50": 1.717}, "conditional_formatting": {"read_p50": 1.795375, "write_p50": 2.160416}, "data_validation": {"read_p50": 2.287042, "write_p50": 1.867666}, "hyperlinks": {"read_p50": 1.686458, "write_p50": 1.53875}, "images": {"read_p50": 1.273834, "write_p50": 1.433042}, "pivot_tables": {"read_p50": 1.242125, "write_p50": 1.473416}, "comments": {"read_p50": 1.358709, "write_p50": 1.612042}, "freeze_panes": {"read_p50": 2.0015, "write_p50": 2.459625}, "named_ranges": {"read_p50": 1.80825, "write_p50": 1.968167}, "tables": {"read_p50": 1.962709, "write_p50": 1.692667}}, "xlsxwriter-constmem": {"cell_values": {"read_p50": null, "write_p50": 1.852958}, "formulas": {"read_p50": null, "write_p50": 2.031792}, "text_formatting": {"read_p50": null, "write_p50": 2.283042}, "background_colors": {"read_p50": null, "write_p50": 2.033792}, "number_formats": {"read_p50": null, "write_p50": 2.081416}, "alignment": {"read_p50": null, "write_p50": 1.8725}, "borders": {"read_p50": null, "write_p50": 2.545625}, "dimensions": {"read_p50": null, "write_p50": 1.776791}, "multiple_sheets": {"read_p50": null, "write_p50": 2.607292}, "merged_cells": {"read_p50": null, "write_p50": 1.912834}, "conditional_formatting": {"read_p50": null, "write_p50": 2.028792}, "data_validation": {"read_p50": null, "write_p50": 1.98925}, "hyperlinks": {"read_p50": null, "write_p50": 2.536292}, "images": {"read_p50": null, "write_p50": 1.665333}, "pivot_tables": {"read_p50": null, "write_p50": 1.73425}, "comments": {"read_p50": null, "write_p50": 1.806583}, "freeze_panes": {"read_p50": null, "write_p50": 2.563292}, "named_ranges": {"read_p50": null, "write_p50": 2.161}, "tables": {"read_p50": null, "write_p50": 2.030833}}, "openpyxl-readonly": {"cell_values": {"read_p50": 4.995875, "write_p50": null}, "formulas": {"read_p50": 1.439083, "write_p50": null}, "text_formatting": {"read_p50": 1.500667, "write_p50": null}, "background_colors": {"read_p50": 0.90925, "write_p50": null}, "number_formats": {"read_p50": 0.905958, "write_p50": null}, "alignment": {"read_p50": 0.964375, "write_p50": null}, "borders": {"read_p50": 1.298, "write_p50": null}, "dimensions": {"read_p50": 0.8705, "write_p50": null}, "multiple_sheets": {"read_p50": 1.016084, "write_p50": null}, "merged_cells": {"read_p50": 0.910542, "write_p50": null}, "conditional_formatting": {"read_p50": 0.961208, "write_p50": null}, "data_validation": {"read_p50": 0.973834, "write_p50": null}, "hyperlinks": {"read_p50": 0.968625, "write_p50": null}, "images": {"read_p50": 0.827167, "write_p50": null}, "pivot_tables": {"read_p50": 0.820083, "write_p50": null}, "comments": {"read_p50": 0.917583, "write_p50": null}, "freeze_panes": {"read_p50": 1.016209, "write_p50": null}, "named_ranges": {"read_p50": 0.886416, "write_p50": null}, "tables": {"read_p50": 0.924625, "write_p50": null}}, "polars": {"cell_values": {"read_p50": 0.33, "write_p50": null}, "formulas": {"read_p50": 0.411292, "write_p50": null}, "text_formatting": {"read_p50": 0.343208, "write_p50": null}, "background_colors": {"read_p50": 0.326292, "write_p50": null}, "number_formats": {"read_p50": 0.262166, "write_p50": null}, "alignment": {"read_p50": 0.268875, "write_p50": null}, "borders": {"read_p50": 0.292833, "write_p50": null}, "dimensions": {"read_p50": 0.317375, "write_p50": null}, "multiple_sheets": {"read_p50": 0.618458, "write_p50": null}, "merged_cells": {"read_p50": 0.26, "write_p50": null}, "conditional_formatting": {"read_p50": 0.397083, "write_p50": null}, "data_validation": {"read_p50": 0.486333, "write_p50": null}, "hyperlinks": {"read_p50": 0.465083, "write_p50": null}, "images": {"read_p50": 0.286916, "write_p50": null}, "pivot_tables": {"read_p50": 0.245167, "write_p50": null}, "comments": {"read_p50": 0.252333, "write_p50": null}, "freeze_panes": {"read_p50": 0.508166, "write_p50": null}, "named_ranges": {"read_p50": 0.433959, "write_p50": null}, "tables": {"read_p50": 0.377166, "write_p50": null}}, "tablib": {"cell_values": {"read_p50": 1.367167, "write_p50": 1.520875}, "formulas": {"read_p50": 1.304958, "write_p50": 1.618542}, "text_formatting": {"read_p50": 1.908125, "write_p50": 1.718417}, "background_colors": {"read_p50": 1.086584, "write_p50": 1.502583}, "number_formats": {"read_p50": 1.076209, "write_p50": 1.345666}, "alignment": {"read_p50": 1.301042, "write_p50": 1.374458}, "borders": {"read_p50": 1.78175, "write_p50": 1.564375}, "dimensions": {"read_p50": 1.002542, "write_p50": 1.329667}, "multiple_sheets": {"read_p50": 1.364417, "write_p50": 2.040958}, "merged_cells": {"read_p50": 1.103583, "write_p50": 1.354375}, "conditional_formatting": {"read_p50": 1.422, "write_p50": 1.511791}, "data_validation": {"read_p50": 1.3705, "write_p50": 1.417708}, "hyperlinks": {"read_p50": 1.236209, "write_p50": 1.489667}, "images": {"read_p50": 0.975208, "write_p50": 1.343459}, "pivot_tables": {"read_p50": 0.950291, "write_p50": 1.257292}, "comments": {"read_p50": 1.063041, "write_p50": 1.343167}, "freeze_panes": {"read_p50": 1.448583, "write_p50": 2.58225}, "named_ranges": {"read_p50": 1.270708, "write_p50": 1.779417}, "tables": {"read_p50": 1.412458, "write_p50": 1.354667}}}} diff --git a/results-release-2026-04-28/perf/matrix.csv b/results-release-2026-04-28/perf/matrix.csv new file mode 100644 index 0000000..81dfe83 --- /dev/null +++ b/results-release-2026-04-28/perf/matrix.csv @@ -0,0 +1,248 @@ +library,feature,read_p50_wall_ms,read_p95_wall_ms,read_op_count,read_op_unit,read_p50_units_per_sec,write_p50_wall_ms,write_p95_wall_ms,write_op_count,write_op_unit,write_p50_units_per_sec +openpyxl,cell_values,1.361917,1.693667,,,,1.422,1.607166,,, +xlsxwriter,cell_values,,,,,,1.896542,2.429167,,, +python-calamine,cell_values,0.9335,0.955959,,,,,,,, +wolfxl,cell_values,0.241209,0.26525,,,,0.189541,0.203458,,, +pylightxl,cell_values,1.405542,1.560333,,,,0.389292,0.427834,,, +xlrd,cell_values,,,,,,,,,, +pyexcel,cell_values,1.51475,1.830125,,,,1.456125,1.550875,,, +xlwt,cell_values,,,,,,0.241916,0.296459,,, +pandas,cell_values,1.785625,2.021625,,,,1.794833,2.093708,,, +xlsxwriter-constmem,cell_values,,,,,,1.852958,2.368875,,, +openpyxl-readonly,cell_values,4.995875,5.261125,,,,,,,, +polars,cell_values,0.33,0.394792,,,,,,,, +tablib,cell_values,1.367167,1.537625,,,,1.520875,1.706666,,, +openpyxl,formulas,1.165792,1.283417,,,,1.531209,1.807042,,, +xlsxwriter,formulas,,,,,,1.778625,2.162458,,, +python-calamine,formulas,0.164917,0.168209,,,,,,,, +wolfxl,formulas,0.188083,0.194292,,,,0.190208,0.202959,,, +pylightxl,formulas,1.196708,1.26125,,,,0.3065,0.337292,,, +xlrd,formulas,,,,,,,,,, +pyexcel,formulas,1.301334,1.521333,,,,1.647333,1.94575,,, +xlwt,formulas,,,,,,0.275916,0.338333,,, +pandas,formulas,1.638125,1.862208,,,,1.927167,2.31925,,, +xlsxwriter-constmem,formulas,,,,,,2.031792,2.625833,,, +openpyxl-readonly,formulas,1.439083,1.957875,,,,,,,, +polars,formulas,0.411292,0.578,,,,,,,, +tablib,formulas,1.304958,1.519916,,,,1.618542,2.006958,,, +openpyxl,text_formatting,1.736334,1.821,,,,2.03,2.710166,,, +xlsxwriter,text_formatting,,,,,,2.392375,3.286083,,, +python-calamine,text_formatting,0.093584,0.142417,,,,,,,, +wolfxl,text_formatting,0.364708,0.37975,,,,0.234375,0.316041,,, +pylightxl,text_formatting,1.553667,1.711875,,,,0.412,0.548292,,, +xlrd,text_formatting,,,,,,,,,, +pyexcel,text_formatting,1.930209,2.339125,,,,1.441125,1.562958,,, +xlwt,text_formatting,,,,,,0.345875,0.406959,,, +pandas,text_formatting,2.105833,2.319084,,,,1.768625,2.206167,,, +xlsxwriter-constmem,text_formatting,,,,,,2.283042,2.61875,,, +openpyxl-readonly,text_formatting,1.500667,1.7765,,,,,,,, +polars,text_formatting,0.343208,0.431125,,,,,,,, +tablib,text_formatting,1.908125,2.196125,,,,1.718417,1.95325,,, +openpyxl,background_colors,1.079833,1.359958,,,,1.506458,1.900583,,, +xlsxwriter,background_colors,,,,,,2.281083,2.644333,,, +python-calamine,background_colors,0.064041,0.065583,,,,,,,, +wolfxl,background_colors,0.27125,0.279292,,,,0.169583,0.27175,,, +pylightxl,background_colors,1.027833,1.170291,,,,0.33175,0.45125,,, +xlrd,background_colors,,,,,,,,,, +pyexcel,background_colors,1.169,1.34475,,,,1.398834,1.582667,,, +xlwt,background_colors,,,,,,0.206125,0.220167,,, +pandas,background_colors,1.410209,1.803292,,,,1.820166,2.265792,,, +xlsxwriter-constmem,background_colors,,,,,,2.033792,2.679958,,, +openpyxl-readonly,background_colors,0.90925,0.97875,,,,,,,, +polars,background_colors,0.326292,0.432917,,,,,,,, +tablib,background_colors,1.086584,1.469084,,,,1.502583,1.902458,,, +openpyxl,number_formats,1.178,1.395291,,,,1.701792,2.264542,,, +xlsxwriter,number_formats,,,,,,2.093,2.500084,,, +python-calamine,number_formats,0.066416,0.0695,,,,,,,, +wolfxl,number_formats,0.322875,0.332208,,,,0.178208,0.199041,,, +pylightxl,number_formats,1.068208,1.227709,,,,0.456875,0.685166,,, +xlrd,number_formats,,,,,,,,,, +pyexcel,number_formats,1.2475,1.536708,,,,1.46125,1.867208,,, +xlwt,number_formats,,,,,,0.216959,0.299667,,, +pandas,number_formats,1.509708,2.142167,,,,1.976875,2.4295,,, +xlsxwriter-constmem,number_formats,,,,,,2.081416,2.631792,,, +openpyxl-readonly,number_formats,0.905958,0.988292,,,,,,,, +polars,number_formats,0.262166,0.317834,,,,,,,, +tablib,number_formats,1.076209,1.123667,,,,1.345666,1.567375,,, +openpyxl,alignment,1.277958,1.552958,,,,1.499542,1.693,,, +xlsxwriter,alignment,,,,,,2.18,2.737417,,, +python-calamine,alignment,0.060875,0.064958,,,,,,,, +wolfxl,alignment,0.288875,0.295166,,,,0.193625,0.201125,,, +pylightxl,alignment,,,,,,0.343709,0.382959,,, +xlrd,alignment,,,,,,,,,, +pyexcel,alignment,1.430583,1.685291,,,,1.504375,1.662292,,, +xlwt,alignment,,,,,,0.239709,0.297167,,, +pandas,alignment,1.487042,1.695584,,,,1.695875,2.1205,,, +xlsxwriter-constmem,alignment,,,,,,1.8725,2.337291,,, +openpyxl-readonly,alignment,0.964375,1.040542,,,,,,,, +polars,alignment,0.268875,0.339792,,,,,,,, +tablib,alignment,1.301042,1.423875,,,,1.374458,1.554666,,, +openpyxl,borders,1.853584,2.056084,,,,2.452875,3.0435,,, +xlsxwriter,borders,,,,,,2.356459,2.735,,, +python-calamine,borders,0.069459,0.071833,,,,,,,, +wolfxl,borders,0.348417,0.358542,,,,0.272708,0.278292,,, +pylightxl,borders,,,,,,0.422166,0.46025,,, +xlrd,borders,,,,,,,,,, +pyexcel,borders,1.830292,1.910584,,,,1.541875,1.92025,,, +xlwt,borders,,,,,,0.428917,0.486208,,, +pandas,borders,2.08375,2.408083,,,,1.825667,2.0515,,, +xlsxwriter-constmem,borders,,,,,,2.545625,4.061208,,, +openpyxl-readonly,borders,1.298,1.369709,,,,,,,, +polars,borders,0.292833,0.356833,,,,,,,, +tablib,borders,1.78175,2.208125,,,,1.564375,2.347042,,, +openpyxl,dimensions,0.987,1.04125,,,,1.287125,1.483792,,, +xlsxwriter,dimensions,,,,,,1.708167,1.997416,,, +python-calamine,dimensions,0.062333,0.064292,,,,,,,, +wolfxl,dimensions,0.083958,0.086166,,,,0.163209,0.168958,,, +pylightxl,dimensions,0.956292,1.055875,,,,0.266042,0.311834,,, +xlrd,dimensions,,,,,,,,,, +pyexcel,dimensions,1.089333,1.35175,,,,1.301542,1.424041,,, +xlwt,dimensions,,,,,,0.15175,0.178041,,, +pandas,dimensions,1.283584,1.576458,,,,1.414333,1.706667,,, +xlsxwriter-constmem,dimensions,,,,,,1.776791,2.176375,,, +openpyxl-readonly,dimensions,0.8705,1.037459,,,,,,,, +polars,dimensions,0.317375,0.399041,,,,,,,, +tablib,dimensions,1.002542,1.286,,,,1.329667,1.716666,,, +openpyxl,multiple_sheets,1.334666,1.833292,,,,1.817333,2.007875,,, +xlsxwriter,multiple_sheets,,,,,,2.293208,2.538958,,, +python-calamine,multiple_sheets,0.06725,0.070708,,,,,,,, +wolfxl,multiple_sheets,0.065166,0.068459,,,,0.187083,0.192584,,, +pylightxl,multiple_sheets,1.389208,1.426541,,,,0.353625,0.420875,,, +xlrd,multiple_sheets,,,,,,,,,, +pyexcel,multiple_sheets,1.453041,1.517,,,,1.964084,2.614125,,, +xlwt,multiple_sheets,,,,,,0.192834,0.201666,,, +pandas,multiple_sheets,1.837584,2.333333,,,,2.56125,3.03575,,, +xlsxwriter-constmem,multiple_sheets,,,,,,2.607292,3.172042,,, +openpyxl-readonly,multiple_sheets,1.016084,1.070042,,,,,,,, +polars,multiple_sheets,0.618458,0.766875,,,,,,,, +tablib,multiple_sheets,1.364417,1.604458,,,,2.040958,2.555375,,, +openpyxl,merged_cells,1.284375,1.444583,,,,1.468542,1.734292,,, +xlsxwriter,merged_cells,,,,,,1.846166,2.479791,,, +python-calamine,merged_cells,0.063542,0.06875,,,,,,,, +wolfxl,merged_cells,0.299834,0.31075,,,,0.162375,0.168,,, +pylightxl,merged_cells,1.033208,1.166875,,,,0.33775,0.386167,,, +xlrd,merged_cells,,,,,,,,,, +pyexcel,merged_cells,1.40275,1.548917,,,,1.364833,1.582292,,, +xlwt,merged_cells,,,,,,0.1995,0.21375,,, +pandas,merged_cells,1.399,1.814709,,,,1.717,2.222416,,, +xlsxwriter-constmem,merged_cells,,,,,,1.912834,2.241667,,, +openpyxl-readonly,merged_cells,0.910542,1.144792,,,,,,,, +polars,merged_cells,0.26,0.376417,,,,,,,, +tablib,merged_cells,1.103583,1.318875,,,,1.354375,1.6305,,, +openpyxl,conditional_formatting,1.424834,1.751916,,,,1.842792,2.275542,,, +xlsxwriter,conditional_formatting,,,,,,1.871333,2.270209,,, +python-calamine,conditional_formatting,0.056542,0.059416,,,,,,,, +wolfxl,conditional_formatting,0.216666,0.221584,,,,0.187042,0.207875,,, +pylightxl,conditional_formatting,,,,,,0.307292,0.331041,,, +xlrd,conditional_formatting,,,,,,,,,, +pyexcel,conditional_formatting,1.528084,1.822,,,,1.563417,1.702333,,, +xlwt,conditional_formatting,,,,,,0.158167,0.166125,,, +pandas,conditional_formatting,1.795375,2.179666,,,,2.160416,2.994041,,, +xlsxwriter-constmem,conditional_formatting,,,,,,2.028792,2.710584,,, +openpyxl-readonly,conditional_formatting,0.961208,1.00375,,,,,,,, +polars,conditional_formatting,0.397083,0.512084,,,,,,,, +tablib,conditional_formatting,1.422,1.650542,,,,1.511791,1.58,,, +openpyxl,data_validation,1.325875,1.522834,,,,1.378917,1.61275,,, +xlsxwriter,data_validation,,,,,,1.647709,2.198917,,, +python-calamine,data_validation,0.053708,0.055875,,,,,,,, +wolfxl,data_validation,0.161083,0.168375,,,,0.164833,0.171417,,, +pylightxl,data_validation,,,,,,0.265625,0.27375,,, +xlrd,data_validation,,,,,,,,,, +pyexcel,data_validation,1.508792,2.048375,,,,1.605708,2.082875,,, +xlwt,data_validation,,,,,,0.14725,0.212125,,, +pandas,data_validation,2.287042,2.584291,,,,1.867666,3.110417,,, +xlsxwriter-constmem,data_validation,,,,,,1.98925,2.391625,,, +openpyxl-readonly,data_validation,0.973834,1.250834,,,,,,,, +polars,data_validation,0.486333,0.564083,,,,,,,, +tablib,data_validation,1.3705,1.68525,,,,1.417708,1.712875,,, +openpyxl,hyperlinks,1.385542,1.501542,,,,1.502334,1.705584,,, +xlsxwriter,hyperlinks,,,,,,2.195375,2.599792,,, +python-calamine,hyperlinks,0.055417,0.058,,,,,,,, +wolfxl,hyperlinks,0.251417,0.303667,,,,0.184916,0.190333,,, +pylightxl,hyperlinks,,,,,,0.27975,0.353125,,, +xlrd,hyperlinks,,,,,,,,,, +pyexcel,hyperlinks,1.552166,1.953584,,,,1.449958,1.7855,,, +xlwt,hyperlinks,,,,,,0.133458,0.145292,,, +pandas,hyperlinks,1.686458,2.112625,,,,1.53875,2.620708,,, +xlsxwriter-constmem,hyperlinks,,,,,,2.536292,7.951125,,, +openpyxl-readonly,hyperlinks,0.968625,1.1705,,,,,,,, +polars,hyperlinks,0.465083,0.539584,,,,,,,, +tablib,hyperlinks,1.236209,1.494583,,,,1.489667,1.754584,,, +openpyxl,images,1.276125,1.660917,,,,2.02675,2.325875,,, +xlsxwriter,images,,,,,,3.072083,3.596875,,, +python-calamine,images,0.056125,0.058708,,,,,,,, +wolfxl,images,0.46275,0.469625,,,,0.279584,0.297125,,, +pylightxl,images,,,,,,0.265083,0.301166,,, +xlrd,images,,,,,,,,,, +pyexcel,images,1.313125,1.5785,,,,1.375208,1.663709,,, +xlwt,images,,,,,,0.136042,0.152583,,, +pandas,images,1.273834,1.551584,,,,1.433042,1.702833,,, +xlsxwriter-constmem,images,,,,,,1.665333,2.053625,,, +openpyxl-readonly,images,0.827167,0.950458,,,,,,,, +polars,images,0.286916,0.399709,,,,,,,, +tablib,images,0.975208,1.170417,,,,1.343459,1.561542,,, +openpyxl,pivot_tables,0.896875,0.966167,,,,1.285291,1.433958,,, +xlsxwriter,pivot_tables,,,,,,1.71925,1.880958,,, +python-calamine,pivot_tables,0.05875,0.061167,,,,,,,, +wolfxl,pivot_tables,0.060083,0.097625,,,,0.153125,0.162166,,, +pylightxl,pivot_tables,0.856667,0.942958,,,,0.275,0.330042,,, +xlrd,pivot_tables,,,,,,,,,, +pyexcel,pivot_tables,0.966625,1.041625,,,,1.37425,1.688625,,, +xlwt,pivot_tables,,,,,,0.138167,0.145125,,, +pandas,pivot_tables,1.242125,1.598,,,,1.473416,1.6675,,, +xlsxwriter-constmem,pivot_tables,,,,,,1.73425,2.141209,,, +openpyxl-readonly,pivot_tables,0.820083,0.862541,,,,,,,, +polars,pivot_tables,0.245167,0.331375,,,,,,,, +tablib,pivot_tables,0.950291,1.099125,,,,1.257292,1.349459,,, +openpyxl,comments,1.152666,1.244292,,,,1.587709,1.826292,,, +xlsxwriter,comments,,,,,,2.255833,2.760708,,, +python-calamine,comments,0.065708,0.067042,,,,,,,, +wolfxl,comments,0.211875,0.219792,,,,0.200584,0.20875,,, +pylightxl,comments,1.1985,1.266917,,,,0.25925,0.27325,,, +xlrd,comments,,,,,,,,,, +pyexcel,comments,1.281875,1.490666,,,,1.388917,1.635584,,, +xlwt,comments,,,,,,0.133416,0.142375,,, +pandas,comments,1.358709,1.603917,,,,1.612042,2.248792,,, +xlsxwriter-constmem,comments,,,,,,1.806583,2.119375,,, +openpyxl-readonly,comments,0.917583,1.060042,,,,,,,, +polars,comments,0.252333,0.324541,,,,,,,, +tablib,comments,1.063041,1.168667,,,,1.343167,1.606542,,, +openpyxl,freeze_panes,1.4545,1.962166,,,,2.318334,2.810208,,, +xlsxwriter,freeze_panes,,,,,,2.287917,3.006875,,, +python-calamine,freeze_panes,0.058125,0.062667,,,,,,,, +wolfxl,freeze_panes,0.2395,0.258042,,,,0.202,0.228834,,, +pylightxl,freeze_panes,,,,,,0.317542,0.3605,,, +xlrd,freeze_panes,,,,,,,,,, +pyexcel,freeze_panes,1.738916,2.260666,,,,2.486584,3.110333,,, +xlwt,freeze_panes,,,,,,0.191292,0.204167,,, +pandas,freeze_panes,2.0015,2.214458,,,,2.459625,3.569709,,, +xlsxwriter-constmem,freeze_panes,,,,,,2.563292,3.133291,,, +openpyxl-readonly,freeze_panes,1.016209,1.310125,,,,,,,, +polars,freeze_panes,0.508166,0.66825,,,,,,,, +tablib,freeze_panes,1.448583,1.705416,,,,2.58225,2.971458,,, +openpyxl,named_ranges,1.240333,1.676958,,,,1.591,2.120834,,, +xlsxwriter,named_ranges,,,,,,2.010292,2.338459,,, +python-calamine,named_ranges,0.0545,0.058917,,,,,,,, +wolfxl,named_ranges,0.055,0.071458,,,,0.1725,0.182625,,, +pylightxl,named_ranges,,,,,,0.279709,0.305875,,, +xlrd,named_ranges,,,,,,,,,, +pyexcel,named_ranges,1.336875,1.716167,,,,1.77025,2.050584,,, +xlwt,named_ranges,,,,,,0.158708,0.208833,,, +pandas,named_ranges,1.80825,2.241542,,,,1.968167,2.359625,,, +xlsxwriter-constmem,named_ranges,,,,,,2.161,2.658958,,, +openpyxl-readonly,named_ranges,0.886416,0.979667,,,,,,,, +polars,named_ranges,0.433959,0.522,,,,,,,, +tablib,named_ranges,1.270708,1.511209,,,,1.779417,2.05625,,, +openpyxl,tables,1.657125,1.98775,,,,1.426625,1.85125,,, +xlsxwriter,tables,,,,,,1.751333,2.6565,,, +python-calamine,tables,0.058875,0.0615,,,,,,,, +wolfxl,tables,0.057833,0.060791,,,,0.153416,0.159292,,, +pylightxl,tables,,,,,,0.273167,0.527125,,, +xlrd,tables,,,,,,,,,, +pyexcel,tables,2.059917,2.885584,,,,1.67,1.862125,,, +xlwt,tables,,,,,,0.139667,0.150583,,, +pandas,tables,1.962709,2.286875,,,,1.692667,2.374,,, +xlsxwriter-constmem,tables,,,,,,2.030833,2.540125,,, +openpyxl-readonly,tables,0.924625,1.091417,,,,,,,, +polars,tables,0.377166,0.453625,,,,,,,, +tablib,tables,1.412458,1.6365,,,,1.354667,1.60525,,, \ No newline at end of file diff --git a/results-release-2026-04-28/perf/results.json b/results-release-2026-04-28/perf/results.json new file mode 100644 index 0000000..0bfc0a1 --- /dev/null +++ b/results-release-2026-04-28/perf/results.json @@ -0,0 +1,9928 @@ +{ + "metadata": { + "benchmark_version": "0.1.0", + "run_date": "2026-04-29T03:12:22.197235+00:00", + "excel_version": "16.105.3", + "platform": "Darwin-arm64", + "profile": "xlsx", + "python": "3.12.3", + "commit": "5f98de9", + "config": { + "warmup": 3, + "iters": 25, + "iteration_policy": "fixed", + "breakdown": false + } + }, + "libraries": { + "openpyxl": { + "name": "openpyxl", + "version": "3.1.5", + "language": "python", + "capabilities": [ + "read", + "write" + ] + }, + "xlsxwriter": { + "name": "xlsxwriter", + "version": "3.2.9", + "language": "python", + "capabilities": [ + "write" + ] + }, + "python-calamine": { + "name": "python-calamine", + "version": "0.6.1", + "language": "python", + "capabilities": [ + "read" + ] + }, + "wolfxl": { + "name": "wolfxl", + "version": "cal=2.0.0+rxw=2.0.0", + "language": "rust", + "capabilities": [ + "modify", + "read", + "write" + ] + }, + "pylightxl": { + "name": "pylightxl", + "version": "1.61", + "language": "python", + "capabilities": [ + "read", + "write" + ] + }, + "xlrd": { + "name": "xlrd", + "version": "2.0.2", + "language": "python", + "capabilities": [ + "read" + ] + }, + "pyexcel": { + "name": "pyexcel", + "version": "0.7.4", + "language": "python", + "capabilities": [ + "read", + "write" + ] + }, + "xlwt": { + "name": "xlwt", + "version": "1.3.0", + "language": "python", + "capabilities": [ + "write" + ] + }, + "pandas": { + "name": "pandas", + "version": "3.0.0", + "language": "python", + "capabilities": [ + "read", + "write" + ] + }, + "xlsxwriter-constmem": { + "name": "xlsxwriter-constmem", + "version": "3.2.9", + "language": "python", + "capabilities": [ + "write" + ] + }, + "openpyxl-readonly": { + "name": "openpyxl-readonly", + "version": "3.1.5", + "language": "python", + "capabilities": [ + "read" + ] + }, + "polars": { + "name": "polars", + "version": "1.38.1", + "language": "python", + "capabilities": [ + "read" + ] + }, + "tablib": { + "name": "tablib", + "version": "3.9.0", + "language": "python", + "capabilities": [ + "read", + "write" + ] + } + }, + "results": [ + { + "feature": "cell_values", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.236917, + "p50": 1.361917, + "p95": 1.693667 + }, + "cpu_ms": { + "min": 1.236, + "p50": 1.362, + "p95": 1.693 + }, + "rss_peak_mb": 144.078125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.361917, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.348084, + "p50": 1.422, + "p95": 1.607166 + }, + "cpu_ms": { + "min": 1.348, + "p50": 1.421, + "p95": 1.519 + }, + "rss_peak_mb": 144.34375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.422, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.816708, + "p50": 1.896542, + "p95": 2.429167 + }, + "cpu_ms": { + "min": 1.817, + "p50": 1.896, + "p95": 2.42 + }, + "rss_peak_mb": 144.46875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.896542, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "cell_values", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.913916, + "p50": 0.9335, + "p95": 0.955959 + }, + "cpu_ms": { + "min": 0.914, + "p50": 0.933, + "p95": 0.957 + }, + "rss_peak_mb": 144.953125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.9335, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "cell_values", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.229083, + "p50": 0.241209, + "p95": 0.26525 + }, + "cpu_ms": { + "min": 0.229, + "p50": 0.241, + "p95": 0.265 + }, + "rss_peak_mb": 146.25, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.241209, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.182125, + "p50": 0.189541, + "p95": 0.203458 + }, + "cpu_ms": { + "min": 0.182, + "p50": 0.19, + "p95": 0.203 + }, + "rss_peak_mb": 146.671875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.189541, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.323792, + "p50": 1.405542, + "p95": 1.560333 + }, + "cpu_ms": { + "min": 1.324, + "p50": 1.406, + "p95": 1.56 + }, + "rss_peak_mb": 146.90625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.405542, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.378375, + "p50": 0.389292, + "p95": 0.427834 + }, + "cpu_ms": { + "min": 0.378, + "p50": 0.39, + "p95": 0.428 + }, + "rss_peak_mb": 146.9375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.389292, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "cell_values", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.419875, + "p50": 1.51475, + "p95": 1.830125 + }, + "cpu_ms": { + "min": 1.42, + "p50": 1.516, + "p95": 1.83 + }, + "rss_peak_mb": 147.421875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.51475, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.396042, + "p50": 1.456125, + "p95": 1.550875 + }, + "cpu_ms": { + "min": 1.396, + "p50": 1.456, + "p95": 1.551 + }, + "rss_peak_mb": 147.46875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.456125, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.234166, + "p50": 0.241916, + "p95": 0.296459 + }, + "cpu_ms": { + "min": 0.234, + "p50": 0.242, + "p95": 0.297 + }, + "rss_peak_mb": 147.546875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.241916, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "cell_values", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.668084, + "p50": 1.785625, + "p95": 2.021625 + }, + "cpu_ms": { + "min": 1.668, + "p50": 1.785, + "p95": 2.022 + }, + "rss_peak_mb": 148.125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.785625, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.629041, + "p50": 1.794833, + "p95": 2.093708 + }, + "cpu_ms": { + "min": 1.609, + "p50": 1.795, + "p95": 2.077 + }, + "rss_peak_mb": 148.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.794833, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.733833, + "p50": 1.852958, + "p95": 2.368875 + }, + "cpu_ms": { + "min": 1.734, + "p50": 1.838, + "p95": 2.369 + }, + "rss_peak_mb": 148.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.852958, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "cell_values", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 4.806291, + "p50": 4.995875, + "p95": 5.261125 + }, + "cpu_ms": { + "min": 4.806, + "p50": 4.996, + "p95": 5.262 + }, + "rss_peak_mb": 149.390625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 4.995875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "cell_values", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.278583, + "p50": 0.33, + "p95": 0.394792 + }, + "cpu_ms": { + "min": 0.362, + "p50": 0.649, + "p95": 1.306 + }, + "rss_peak_mb": 160.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.33, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "cell_values", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.33475, + "p50": 1.367167, + "p95": 1.537625 + }, + "cpu_ms": { + "min": 1.334, + "p50": 1.367, + "p95": 1.538 + }, + "rss_peak_mb": 161.296875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.367167, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.434542, + "p50": 1.520875, + "p95": 1.706666 + }, + "cpu_ms": { + "min": 1.434, + "p50": 1.522, + "p95": 1.706 + }, + "rss_peak_mb": 161.609375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.520875, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.097792, + "p50": 1.165792, + "p95": 1.283417 + }, + "cpu_ms": { + "min": 1.097, + "p50": 1.166, + "p95": 1.283 + }, + "rss_peak_mb": 161.859375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.165792, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.451625, + "p50": 1.531209, + "p95": 1.807042 + }, + "cpu_ms": { + "min": 1.451, + "p50": 1.531, + "p95": 1.622 + }, + "rss_peak_mb": 161.90625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.531209, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.653292, + "p50": 1.778625, + "p95": 2.162458 + }, + "cpu_ms": { + "min": 1.653, + "p50": 1.762, + "p95": 2.163 + }, + "rss_peak_mb": 162.03125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.778625, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "formulas", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.159958, + "p50": 0.164917, + "p95": 0.168209 + }, + "cpu_ms": { + "min": 0.159, + "p50": 0.165, + "p95": 0.168 + }, + "rss_peak_mb": 162.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.164917, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "formulas", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.179583, + "p50": 0.188083, + "p95": 0.194292 + }, + "cpu_ms": { + "min": 0.179, + "p50": 0.188, + "p95": 0.195 + }, + "rss_peak_mb": 162.078125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.188083, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.182208, + "p50": 0.190208, + "p95": 0.202959 + }, + "cpu_ms": { + "min": 0.182, + "p50": 0.19, + "p95": 0.203 + }, + "rss_peak_mb": 162.21875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.190208, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.1205, + "p50": 1.196708, + "p95": 1.26125 + }, + "cpu_ms": { + "min": 1.121, + "p50": 1.196, + "p95": 1.262 + }, + "rss_peak_mb": 162.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.196708, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.291208, + "p50": 0.3065, + "p95": 0.337292 + }, + "cpu_ms": { + "min": 0.291, + "p50": 0.306, + "p95": 0.338 + }, + "rss_peak_mb": 162.3125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.3065, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "formulas", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.2135, + "p50": 1.301334, + "p95": 1.521333 + }, + "cpu_ms": { + "min": 1.214, + "p50": 1.302, + "p95": 1.517 + }, + "rss_peak_mb": 162.59375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.301334, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.49125, + "p50": 1.647333, + "p95": 1.94575 + }, + "cpu_ms": { + "min": 1.49, + "p50": 1.634, + "p95": 1.945 + }, + "rss_peak_mb": 162.703125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.647333, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.264708, + "p50": 0.275916, + "p95": 0.338333 + }, + "cpu_ms": { + "min": 0.265, + "p50": 0.276, + "p95": 0.338 + }, + "rss_peak_mb": 162.734375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.275916, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "formulas", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.490416, + "p50": 1.638125, + "p95": 1.862208 + }, + "cpu_ms": { + "min": 1.49, + "p50": 1.638, + "p95": 1.862 + }, + "rss_peak_mb": 163.078125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.638125, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.775625, + "p50": 1.927167, + "p95": 2.31925 + }, + "cpu_ms": { + "min": 1.775, + "p50": 1.926, + "p95": 2.319 + }, + "rss_peak_mb": 163.234375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.927167, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.910041, + "p50": 2.031792, + "p95": 2.625833 + }, + "cpu_ms": { + "min": 1.91, + "p50": 2.016, + "p95": 2.622 + }, + "rss_peak_mb": 163.234375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.031792, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "formulas", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.383583, + "p50": 1.439083, + "p95": 1.957875 + }, + "cpu_ms": { + "min": 1.383, + "p50": 1.44, + "p95": 1.939 + }, + "rss_peak_mb": 163.484375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.439083, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "formulas", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.320042, + "p50": 0.411292, + "p95": 0.578 + }, + "cpu_ms": { + "min": 0.566, + "p50": 1.367, + "p95": 2.564 + }, + "rss_peak_mb": 164.0, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.411292, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "formulas", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.143333, + "p50": 1.304958, + "p95": 1.519916 + }, + "cpu_ms": { + "min": 1.143, + "p50": 1.301, + "p95": 1.512 + }, + "rss_peak_mb": 164.0, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.304958, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.543792, + "p50": 1.618542, + "p95": 2.006958 + }, + "cpu_ms": { + "min": 1.543, + "p50": 1.618, + "p95": 1.861 + }, + "rss_peak_mb": 164.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.618542, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.655125, + "p50": 1.736334, + "p95": 1.821 + }, + "cpu_ms": { + "min": 1.655, + "p50": 1.737, + "p95": 1.821 + }, + "rss_peak_mb": 164.640625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.736334, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.834166, + "p50": 2.03, + "p95": 2.710166 + }, + "cpu_ms": { + "min": 1.835, + "p50": 2.022, + "p95": 2.655 + }, + "rss_peak_mb": 164.671875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.03, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 2.153709, + "p50": 2.392375, + "p95": 3.286083 + }, + "cpu_ms": { + "min": 2.147, + "p50": 2.355, + "p95": 2.995 + }, + "rss_peak_mb": 164.890625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.392375, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "text_formatting", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.085, + "p50": 0.093584, + "p95": 0.142417 + }, + "cpu_ms": { + "min": 0.085, + "p50": 0.093, + "p95": 0.138 + }, + "rss_peak_mb": 164.890625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.093584, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "text_formatting", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.359333, + "p50": 0.364708, + "p95": 0.37975 + }, + "cpu_ms": { + "min": 0.359, + "p50": 0.365, + "p95": 0.379 + }, + "rss_peak_mb": 164.953125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.364708, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.219458, + "p50": 0.234375, + "p95": 0.316041 + }, + "cpu_ms": { + "min": 0.22, + "p50": 0.235, + "p95": 0.26 + }, + "rss_peak_mb": 164.984375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.234375, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.491125, + "p50": 1.553667, + "p95": 1.711875 + }, + "cpu_ms": { + "min": 1.491, + "p50": 1.553, + "p95": 1.712 + }, + "rss_peak_mb": 164.984375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.553667, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.395542, + "p50": 0.412, + "p95": 0.548292 + }, + "cpu_ms": { + "min": 0.395, + "p50": 0.412, + "p95": 0.496 + }, + "rss_peak_mb": 164.984375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.412, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "text_formatting", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.800792, + "p50": 1.930209, + "p95": 2.339125 + }, + "cpu_ms": { + "min": 1.801, + "p50": 1.931, + "p95": 2.314 + }, + "rss_peak_mb": 165.328125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.930209, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.372083, + "p50": 1.441125, + "p95": 1.562958 + }, + "cpu_ms": { + "min": 1.372, + "p50": 1.442, + "p95": 1.564 + }, + "rss_peak_mb": 165.328125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.441125, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.326417, + "p50": 0.345875, + "p95": 0.406959 + }, + "cpu_ms": { + "min": 0.327, + "p50": 0.346, + "p95": 0.407 + }, + "rss_peak_mb": 165.328125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.345875, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "text_formatting", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.946209, + "p50": 2.105833, + "p95": 2.319084 + }, + "cpu_ms": { + "min": 1.946, + "p50": 2.106, + "p95": 2.319 + }, + "rss_peak_mb": 165.40625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 2.105833, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.575584, + "p50": 1.768625, + "p95": 2.206167 + }, + "cpu_ms": { + "min": 1.575, + "p50": 1.769, + "p95": 2.206 + }, + "rss_peak_mb": 165.40625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.768625, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 2.086583, + "p50": 2.283042, + "p95": 2.61875 + }, + "cpu_ms": { + "min": 2.086, + "p50": 2.279, + "p95": 2.602 + }, + "rss_peak_mb": 165.5625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.283042, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "text_formatting", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.439084, + "p50": 1.500667, + "p95": 1.7765 + }, + "cpu_ms": { + "min": 1.439, + "p50": 1.501, + "p95": 1.776 + }, + "rss_peak_mb": 165.59375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.500667, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "text_formatting", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.261417, + "p50": 0.343208, + "p95": 0.431125 + }, + "cpu_ms": { + "min": 0.382, + "p50": 0.558, + "p95": 1.24 + }, + "rss_peak_mb": 165.625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.343208, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "text_formatting", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.785125, + "p50": 1.908125, + "p95": 2.196125 + }, + "cpu_ms": { + "min": 1.785, + "p50": 1.902, + "p95": 2.185 + }, + "rss_peak_mb": 165.8125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.908125, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.489875, + "p50": 1.718417, + "p95": 1.95325 + }, + "cpu_ms": { + "min": 1.49, + "p50": 1.639, + "p95": 1.953 + }, + "rss_peak_mb": 165.859375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.718417, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.009083, + "p50": 1.079833, + "p95": 1.359958 + }, + "cpu_ms": { + "min": 1.008, + "p50": 1.081, + "p95": 1.344 + }, + "rss_peak_mb": 165.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.079833, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.374875, + "p50": 1.506458, + "p95": 1.900583 + }, + "cpu_ms": { + "min": 1.374, + "p50": 1.463, + "p95": 1.852 + }, + "rss_peak_mb": 165.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.506458, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.823833, + "p50": 2.281083, + "p95": 2.644333 + }, + "cpu_ms": { + "min": 1.824, + "p50": 2.182, + "p95": 2.357 + }, + "rss_peak_mb": 165.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.281083, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "background_colors", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.063292, + "p50": 0.064041, + "p95": 0.065583 + }, + "cpu_ms": { + "min": 0.062, + "p50": 0.064, + "p95": 0.066 + }, + "rss_peak_mb": 165.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.064041, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "background_colors", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.263, + "p50": 0.27125, + "p95": 0.279292 + }, + "cpu_ms": { + "min": 0.263, + "p50": 0.272, + "p95": 0.28 + }, + "rss_peak_mb": 165.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.27125, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.151959, + "p50": 0.169583, + "p95": 0.27175 + }, + "cpu_ms": { + "min": 0.152, + "p50": 0.17, + "p95": 0.222 + }, + "rss_peak_mb": 165.890625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.169583, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.912041, + "p50": 1.027833, + "p95": 1.170291 + }, + "cpu_ms": { + "min": 0.913, + "p50": 1.027, + "p95": 1.17 + }, + "rss_peak_mb": 165.9375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.027833, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.315959, + "p50": 0.33175, + "p95": 0.45125 + }, + "cpu_ms": { + "min": 0.316, + "p50": 0.332, + "p95": 0.39 + }, + "rss_peak_mb": 165.953125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.33175, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "background_colors", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.08325, + "p50": 1.169, + "p95": 1.34475 + }, + "cpu_ms": { + "min": 1.084, + "p50": 1.169, + "p95": 1.345 + }, + "rss_peak_mb": 166.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.169, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.306041, + "p50": 1.398834, + "p95": 1.582667 + }, + "cpu_ms": { + "min": 1.306, + "p50": 1.391, + "p95": 1.582 + }, + "rss_peak_mb": 166.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.398834, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.194083, + "p50": 0.206125, + "p95": 0.220167 + }, + "cpu_ms": { + "min": 0.194, + "p50": 0.206, + "p95": 0.22 + }, + "rss_peak_mb": 166.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.206125, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "background_colors", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.243291, + "p50": 1.410209, + "p95": 1.803292 + }, + "cpu_ms": { + "min": 1.244, + "p50": 1.409, + "p95": 1.799 + }, + "rss_peak_mb": 166.109375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.410209, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.520458, + "p50": 1.820166, + "p95": 2.265792 + }, + "cpu_ms": { + "min": 1.52, + "p50": 1.82, + "p95": 2.221 + }, + "rss_peak_mb": 166.109375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.820166, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.835125, + "p50": 2.033792, + "p95": 2.679958 + }, + "cpu_ms": { + "min": 1.797, + "p50": 1.933, + "p95": 2.495 + }, + "rss_peak_mb": 166.109375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.033792, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "background_colors", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.861625, + "p50": 0.90925, + "p95": 0.97875 + }, + "cpu_ms": { + "min": 0.861, + "p50": 0.91, + "p95": 0.979 + }, + "rss_peak_mb": 166.109375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.90925, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "background_colors", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.23725, + "p50": 0.326292, + "p95": 0.432917 + }, + "cpu_ms": { + "min": 0.349, + "p50": 0.96, + "p95": 1.351 + }, + "rss_peak_mb": 166.125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.326292, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "background_colors", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.007167, + "p50": 1.086584, + "p95": 1.469084 + }, + "cpu_ms": { + "min": 1.007, + "p50": 1.088, + "p95": 1.469 + }, + "rss_peak_mb": 166.15625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.086584, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.342292, + "p50": 1.502583, + "p95": 1.902458 + }, + "cpu_ms": { + "min": 1.339, + "p50": 1.501, + "p95": 1.903 + }, + "rss_peak_mb": 166.25, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.502583, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.065708, + "p50": 1.178, + "p95": 1.395291 + }, + "cpu_ms": { + "min": 1.065, + "p50": 1.178, + "p95": 1.386 + }, + "rss_peak_mb": 166.359375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.178, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.360417, + "p50": 1.701792, + "p95": 2.264542 + }, + "cpu_ms": { + "min": 1.36, + "p50": 1.687, + "p95": 2.027 + }, + "rss_peak_mb": 166.359375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.701792, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.764417, + "p50": 2.093, + "p95": 2.500084 + }, + "cpu_ms": { + "min": 1.71, + "p50": 2.007, + "p95": 2.207 + }, + "rss_peak_mb": 166.359375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.093, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "number_formats", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.064667, + "p50": 0.066416, + "p95": 0.0695 + }, + "cpu_ms": { + "min": 0.064, + "p50": 0.066, + "p95": 0.069 + }, + "rss_peak_mb": 166.359375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.066416, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "number_formats", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.317416, + "p50": 0.322875, + "p95": 0.332208 + }, + "cpu_ms": { + "min": 0.317, + "p50": 0.323, + "p95": 0.332 + }, + "rss_peak_mb": 166.359375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.322875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.172041, + "p50": 0.178208, + "p95": 0.199041 + }, + "cpu_ms": { + "min": 0.172, + "p50": 0.178, + "p95": 0.194 + }, + "rss_peak_mb": 166.359375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.178208, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.961583, + "p50": 1.068208, + "p95": 1.227709 + }, + "cpu_ms": { + "min": 0.962, + "p50": 1.068, + "p95": 1.229 + }, + "rss_peak_mb": 166.515625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.068208, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.322, + "p50": 0.456875, + "p95": 0.685166 + }, + "cpu_ms": { + "min": 0.321, + "p50": 0.43, + "p95": 0.577 + }, + "rss_peak_mb": 166.53125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.456875, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "number_formats", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.114625, + "p50": 1.2475, + "p95": 1.536708 + }, + "cpu_ms": { + "min": 1.115, + "p50": 1.248, + "p95": 1.537 + }, + "rss_peak_mb": 166.5625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.2475, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.320166, + "p50": 1.46125, + "p95": 1.867208 + }, + "cpu_ms": { + "min": 1.32, + "p50": 1.462, + "p95": 1.867 + }, + "rss_peak_mb": 166.5625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.46125, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.201542, + "p50": 0.216959, + "p95": 0.299667 + }, + "cpu_ms": { + "min": 0.201, + "p50": 0.216, + "p95": 0.297 + }, + "rss_peak_mb": 166.5625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.216959, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "number_formats", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.372375, + "p50": 1.509708, + "p95": 2.142167 + }, + "cpu_ms": { + "min": 1.372, + "p50": 1.509, + "p95": 2.138 + }, + "rss_peak_mb": 166.640625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.509708, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.687833, + "p50": 1.976875, + "p95": 2.4295 + }, + "cpu_ms": { + "min": 1.687, + "p50": 1.882, + "p95": 2.411 + }, + "rss_peak_mb": 166.671875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.976875, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.819416, + "p50": 2.081416, + "p95": 2.631792 + }, + "cpu_ms": { + "min": 1.819, + "p50": 2.068, + "p95": 2.533 + }, + "rss_peak_mb": 166.671875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.081416, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "number_formats", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.864583, + "p50": 0.905958, + "p95": 0.988292 + }, + "cpu_ms": { + "min": 0.865, + "p50": 0.906, + "p95": 0.988 + }, + "rss_peak_mb": 166.671875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.905958, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "number_formats", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.215125, + "p50": 0.262166, + "p95": 0.317834 + }, + "cpu_ms": { + "min": 0.328, + "p50": 0.564, + "p95": 0.71 + }, + "rss_peak_mb": 166.6875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.262166, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "number_formats", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.99475, + "p50": 1.076209, + "p95": 1.123667 + }, + "cpu_ms": { + "min": 0.994, + "p50": 1.076, + "p95": 1.124 + }, + "rss_peak_mb": 166.6875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.076209, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.261458, + "p50": 1.345666, + "p95": 1.567375 + }, + "cpu_ms": { + "min": 1.261, + "p50": 1.346, + "p95": 1.562 + }, + "rss_peak_mb": 166.78125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.345666, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.193125, + "p50": 1.277958, + "p95": 1.552958 + }, + "cpu_ms": { + "min": 1.194, + "p50": 1.278, + "p95": 1.553 + }, + "rss_peak_mb": 166.859375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.277958, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.446375, + "p50": 1.499542, + "p95": 1.693 + }, + "cpu_ms": { + "min": 1.447, + "p50": 1.5, + "p95": 1.693 + }, + "rss_peak_mb": 166.859375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.499542, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.87725, + "p50": 2.18, + "p95": 2.737417 + }, + "cpu_ms": { + "min": 1.877, + "p50": 2.129, + "p95": 2.481 + }, + "rss_peak_mb": 166.859375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.18, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "alignment", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.058083, + "p50": 0.060875, + "p95": 0.064958 + }, + "cpu_ms": { + "min": 0.058, + "p50": 0.061, + "p95": 0.064 + }, + "rss_peak_mb": 166.859375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.060875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "alignment", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.281708, + "p50": 0.288875, + "p95": 0.295166 + }, + "cpu_ms": { + "min": 0.282, + "p50": 0.289, + "p95": 0.295 + }, + "rss_peak_mb": 166.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.288875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.186875, + "p50": 0.193625, + "p95": 0.201125 + }, + "cpu_ms": { + "min": 0.186, + "p50": 0.194, + "p95": 0.201 + }, + "rss_peak_mb": 166.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.193625, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.327792, + "p50": 0.343709, + "p95": 0.382959 + }, + "cpu_ms": { + "min": 0.328, + "p50": 0.344, + "p95": 0.384 + }, + "rss_peak_mb": 166.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.343709, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read failed: TypeError: expected string or bytes-like object, got 'NoneType'" + }, + { + "feature": "alignment", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "alignment", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.206834, + "p50": 1.430583, + "p95": 1.685291 + }, + "cpu_ms": { + "min": 1.207, + "p50": 1.431, + "p95": 1.686 + }, + "rss_peak_mb": 166.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.430583, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.394333, + "p50": 1.504375, + "p95": 1.662292 + }, + "cpu_ms": { + "min": 1.394, + "p50": 1.504, + "p95": 1.662 + }, + "rss_peak_mb": 166.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.504375, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.230791, + "p50": 0.239709, + "p95": 0.297167 + }, + "cpu_ms": { + "min": 0.232, + "p50": 0.239, + "p95": 0.298 + }, + "rss_peak_mb": 166.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.239709, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "alignment", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.35925, + "p50": 1.487042, + "p95": 1.695584 + }, + "cpu_ms": { + "min": 1.36, + "p50": 1.487, + "p95": 1.684 + }, + "rss_peak_mb": 166.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.487042, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.57675, + "p50": 1.695875, + "p95": 2.1205 + }, + "cpu_ms": { + "min": 1.576, + "p50": 1.696, + "p95": 2.12 + }, + "rss_peak_mb": 166.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.695875, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.796459, + "p50": 1.8725, + "p95": 2.337291 + }, + "cpu_ms": { + "min": 1.797, + "p50": 1.872, + "p95": 2.329 + }, + "rss_peak_mb": 166.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.8725, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "alignment", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.90175, + "p50": 0.964375, + "p95": 1.040542 + }, + "cpu_ms": { + "min": 0.902, + "p50": 0.965, + "p95": 1.041 + }, + "rss_peak_mb": 166.875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.964375, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "alignment", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.207708, + "p50": 0.268875, + "p95": 0.339792 + }, + "cpu_ms": { + "min": 0.338, + "p50": 0.543, + "p95": 0.926 + }, + "rss_peak_mb": 166.890625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.268875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "alignment", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.195834, + "p50": 1.301042, + "p95": 1.423875 + }, + "cpu_ms": { + "min": 1.196, + "p50": 1.301, + "p95": 1.424 + }, + "rss_peak_mb": 166.984375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.301042, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.289042, + "p50": 1.374458, + "p95": 1.554666 + }, + "cpu_ms": { + "min": 1.289, + "p50": 1.374, + "p95": 1.549 + }, + "rss_peak_mb": 166.984375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.374458, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.753, + "p50": 1.853584, + "p95": 2.056084 + }, + "cpu_ms": { + "min": 1.753, + "p50": 1.854, + "p95": 2.052 + }, + "rss_peak_mb": 167.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.853584, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 2.341875, + "p50": 2.452875, + "p95": 3.0435 + }, + "cpu_ms": { + "min": 2.342, + "p50": 2.454, + "p95": 3.04 + }, + "rss_peak_mb": 167.03125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.452875, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 2.13325, + "p50": 2.356459, + "p95": 2.735 + }, + "cpu_ms": { + "min": 2.133, + "p50": 2.356, + "p95": 2.735 + }, + "rss_peak_mb": 167.03125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.356459, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "borders", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.067625, + "p50": 0.069459, + "p95": 0.071833 + }, + "cpu_ms": { + "min": 0.067, + "p50": 0.069, + "p95": 0.072 + }, + "rss_peak_mb": 167.03125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.069459, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "borders", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.341792, + "p50": 0.348417, + "p95": 0.358542 + }, + "cpu_ms": { + "min": 0.341, + "p50": 0.348, + "p95": 0.358 + }, + "rss_peak_mb": 167.03125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.348417, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.267292, + "p50": 0.272708, + "p95": 0.278292 + }, + "cpu_ms": { + "min": 0.267, + "p50": 0.273, + "p95": 0.278 + }, + "rss_peak_mb": 167.046875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.272708, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.407125, + "p50": 0.422166, + "p95": 0.46025 + }, + "cpu_ms": { + "min": 0.407, + "p50": 0.421, + "p95": 0.461 + }, + "rss_peak_mb": 167.046875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.422166, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read failed: TypeError: expected string or bytes-like object, got 'NoneType'" + }, + { + "feature": "borders", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "borders", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.7805, + "p50": 1.830292, + "p95": 1.910584 + }, + "cpu_ms": { + "min": 1.78, + "p50": 1.83, + "p95": 1.911 + }, + "rss_peak_mb": 167.046875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.830292, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.470084, + "p50": 1.541875, + "p95": 1.92025 + }, + "cpu_ms": { + "min": 1.469, + "p50": 1.542, + "p95": 1.917 + }, + "rss_peak_mb": 167.09375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.541875, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.40975, + "p50": 0.428917, + "p95": 0.486208 + }, + "cpu_ms": { + "min": 0.409, + "p50": 0.429, + "p95": 0.485 + }, + "rss_peak_mb": 167.09375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.428917, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "borders", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.926541, + "p50": 2.08375, + "p95": 2.408083 + }, + "cpu_ms": { + "min": 1.927, + "p50": 2.083, + "p95": 2.408 + }, + "rss_peak_mb": 167.140625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 2.08375, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.649667, + "p50": 1.825667, + "p95": 2.0515 + }, + "cpu_ms": { + "min": 1.65, + "p50": 1.826, + "p95": 2.048 + }, + "rss_peak_mb": 167.140625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.825667, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 2.277375, + "p50": 2.545625, + "p95": 4.061208 + }, + "cpu_ms": { + "min": 2.278, + "p50": 2.546, + "p95": 3.106 + }, + "rss_peak_mb": 167.140625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.545625, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "borders", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.265542, + "p50": 1.298, + "p95": 1.369709 + }, + "cpu_ms": { + "min": 1.266, + "p50": 1.298, + "p95": 1.37 + }, + "rss_peak_mb": 167.1875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.298, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "borders", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.237292, + "p50": 0.292833, + "p95": 0.356833 + }, + "cpu_ms": { + "min": 0.368, + "p50": 0.564, + "p95": 0.766 + }, + "rss_peak_mb": 167.21875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.292833, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "borders", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.694042, + "p50": 1.78175, + "p95": 2.208125 + }, + "cpu_ms": { + "min": 1.694, + "p50": 1.782, + "p95": 2.209 + }, + "rss_peak_mb": 167.234375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.78175, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.46575, + "p50": 1.564375, + "p95": 2.347042 + }, + "cpu_ms": { + "min": 1.466, + "p50": 1.564, + "p95": 1.92 + }, + "rss_peak_mb": 167.234375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.564375, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.92675, + "p50": 0.987, + "p95": 1.04125 + }, + "cpu_ms": { + "min": 0.927, + "p50": 0.987, + "p95": 1.042 + }, + "rss_peak_mb": 167.234375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.987, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.2235, + "p50": 1.287125, + "p95": 1.483792 + }, + "cpu_ms": { + "min": 1.224, + "p50": 1.288, + "p95": 1.484 + }, + "rss_peak_mb": 167.234375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.287125, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.523667, + "p50": 1.708167, + "p95": 1.997416 + }, + "cpu_ms": { + "min": 1.524, + "p50": 1.708, + "p95": 1.997 + }, + "rss_peak_mb": 167.234375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.708167, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "dimensions", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.060959, + "p50": 0.062333, + "p95": 0.064292 + }, + "cpu_ms": { + "min": 0.061, + "p50": 0.062, + "p95": 0.064 + }, + "rss_peak_mb": 167.234375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.062333, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "dimensions", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.082833, + "p50": 0.083958, + "p95": 0.086166 + }, + "cpu_ms": { + "min": 0.082, + "p50": 0.084, + "p95": 0.086 + }, + "rss_peak_mb": 167.25, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.083958, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.159541, + "p50": 0.163209, + "p95": 0.168958 + }, + "cpu_ms": { + "min": 0.159, + "p50": 0.163, + "p95": 0.169 + }, + "rss_peak_mb": 167.265625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.163209, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.879042, + "p50": 0.956292, + "p95": 1.055875 + }, + "cpu_ms": { + "min": 0.879, + "p50": 0.957, + "p95": 1.057 + }, + "rss_peak_mb": 167.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.956292, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.257583, + "p50": 0.266042, + "p95": 0.311834 + }, + "cpu_ms": { + "min": 0.257, + "p50": 0.266, + "p95": 0.312 + }, + "rss_peak_mb": 167.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.266042, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "dimensions", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.004833, + "p50": 1.089333, + "p95": 1.35175 + }, + "cpu_ms": { + "min": 1.005, + "p50": 1.089, + "p95": 1.351 + }, + "rss_peak_mb": 167.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.089333, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.189583, + "p50": 1.301542, + "p95": 1.424041 + }, + "cpu_ms": { + "min": 1.19, + "p50": 1.301, + "p95": 1.424 + }, + "rss_peak_mb": 167.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.301542, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.146042, + "p50": 0.15175, + "p95": 0.178041 + }, + "cpu_ms": { + "min": 0.147, + "p50": 0.152, + "p95": 0.177 + }, + "rss_peak_mb": 167.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.15175, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "dimensions", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.162417, + "p50": 1.283584, + "p95": 1.576458 + }, + "cpu_ms": { + "min": 1.162, + "p50": 1.285, + "p95": 1.576 + }, + "rss_peak_mb": 167.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.283584, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.289167, + "p50": 1.414333, + "p95": 1.706667 + }, + "cpu_ms": { + "min": 1.289, + "p50": 1.415, + "p95": 1.688 + }, + "rss_peak_mb": 167.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.414333, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.612292, + "p50": 1.776791, + "p95": 2.176375 + }, + "cpu_ms": { + "min": 1.612, + "p50": 1.777, + "p95": 2.153 + }, + "rss_peak_mb": 167.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.776791, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "dimensions", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.793125, + "p50": 0.8705, + "p95": 1.037459 + }, + "cpu_ms": { + "min": 0.793, + "p50": 0.869, + "p95": 1.037 + }, + "rss_peak_mb": 167.28125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.8705, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "dimensions", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.265, + "p50": 0.317375, + "p95": 0.399041 + }, + "cpu_ms": { + "min": 0.699, + "p50": 1.079, + "p95": 1.63 + }, + "rss_peak_mb": 167.609375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.317375, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "dimensions", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.931292, + "p50": 1.002542, + "p95": 1.286 + }, + "cpu_ms": { + "min": 0.931, + "p50": 1.003, + "p95": 1.286 + }, + "rss_peak_mb": 167.609375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.002542, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.203875, + "p50": 1.329667, + "p95": 1.716666 + }, + "cpu_ms": { + "min": 1.204, + "p50": 1.33, + "p95": 1.716 + }, + "rss_peak_mb": 167.609375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.329667, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.26975, + "p50": 1.334666, + "p95": 1.833292 + }, + "cpu_ms": { + "min": 1.27, + "p50": 1.334, + "p95": 1.822 + }, + "rss_peak_mb": 167.609375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.334666, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.750667, + "p50": 1.817333, + "p95": 2.007875 + }, + "cpu_ms": { + "min": 1.75, + "p50": 1.817, + "p95": 2.008 + }, + "rss_peak_mb": 167.609375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.817333, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 2.004542, + "p50": 2.293208, + "p95": 2.538958 + }, + "cpu_ms": { + "min": 2.004, + "p50": 2.291, + "p95": 2.54 + }, + "rss_peak_mb": 167.65625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.293208, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "multiple_sheets", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.063458, + "p50": 0.06725, + "p95": 0.070708 + }, + "cpu_ms": { + "min": 0.063, + "p50": 0.067, + "p95": 0.071 + }, + "rss_peak_mb": 167.65625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.06725, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "multiple_sheets", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.062667, + "p50": 0.065166, + "p95": 0.068459 + }, + "cpu_ms": { + "min": 0.063, + "p50": 0.065, + "p95": 0.069 + }, + "rss_peak_mb": 167.65625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.065166, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.182916, + "p50": 0.187083, + "p95": 0.192584 + }, + "cpu_ms": { + "min": 0.182, + "p50": 0.188, + "p95": 0.193 + }, + "rss_peak_mb": 167.65625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.187083, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.363625, + "p50": 1.389208, + "p95": 1.426541 + }, + "cpu_ms": { + "min": 1.363, + "p50": 1.389, + "p95": 1.427 + }, + "rss_peak_mb": 167.65625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.389208, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.341541, + "p50": 0.353625, + "p95": 0.420875 + }, + "cpu_ms": { + "min": 0.342, + "p50": 0.354, + "p95": 0.421 + }, + "rss_peak_mb": 167.65625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.353625, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "multiple_sheets", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.406458, + "p50": 1.453041, + "p95": 1.517 + }, + "cpu_ms": { + "min": 1.406, + "p50": 1.452, + "p95": 1.517 + }, + "rss_peak_mb": 167.65625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.453041, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.884375, + "p50": 1.964084, + "p95": 2.614125 + }, + "cpu_ms": { + "min": 1.884, + "p50": 1.964, + "p95": 2.605 + }, + "rss_peak_mb": 167.671875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.964084, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.184959, + "p50": 0.192834, + "p95": 0.201666 + }, + "cpu_ms": { + "min": 0.185, + "p50": 0.193, + "p95": 0.202 + }, + "rss_peak_mb": 167.734375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.192834, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "multiple_sheets", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.75925, + "p50": 1.837584, + "p95": 2.333333 + }, + "cpu_ms": { + "min": 1.759, + "p50": 1.837, + "p95": 2.292 + }, + "rss_peak_mb": 167.734375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.837584, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 2.303166, + "p50": 2.56125, + "p95": 3.03575 + }, + "cpu_ms": { + "min": 2.303, + "p50": 2.561, + "p95": 3.037 + }, + "rss_peak_mb": 167.734375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.56125, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 2.240167, + "p50": 2.607292, + "p95": 3.172042 + }, + "cpu_ms": { + "min": 2.24, + "p50": 2.603, + "p95": 3.166 + }, + "rss_peak_mb": 167.734375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.607292, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "multiple_sheets", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.931417, + "p50": 1.016084, + "p95": 1.070042 + }, + "cpu_ms": { + "min": 0.93, + "p50": 1.016, + "p95": 1.07 + }, + "rss_peak_mb": 167.734375, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.016084, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "multiple_sheets", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.484792, + "p50": 0.618458, + "p95": 0.766875 + }, + "cpu_ms": { + "min": 1.122, + "p50": 2.277, + "p95": 3.541 + }, + "rss_peak_mb": 168.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.618458, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "multiple_sheets", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.252708, + "p50": 1.364417, + "p95": 1.604458 + }, + "cpu_ms": { + "min": 1.252, + "p50": 1.365, + "p95": 1.605 + }, + "rss_peak_mb": 168.03125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.364417, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.923833, + "p50": 2.040958, + "p95": 2.555375 + }, + "cpu_ms": { + "min": 1.92, + "p50": 2.04, + "p95": 2.553 + }, + "rss_peak_mb": 168.203125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.040958, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.200917, + "p50": 1.284375, + "p95": 1.444583 + }, + "cpu_ms": { + "min": 1.201, + "p50": 1.284, + "p95": 1.444 + }, + "rss_peak_mb": 168.203125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.284375, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.397542, + "p50": 1.468542, + "p95": 1.734292 + }, + "cpu_ms": { + "min": 1.397, + "p50": 1.469, + "p95": 1.729 + }, + "rss_peak_mb": 168.203125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.468542, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.701917, + "p50": 1.846166, + "p95": 2.479791 + }, + "cpu_ms": { + "min": 1.702, + "p50": 1.846, + "p95": 2.478 + }, + "rss_peak_mb": 168.203125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.846166, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "merged_cells", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.061666, + "p50": 0.063542, + "p95": 0.06875 + }, + "cpu_ms": { + "min": 0.062, + "p50": 0.064, + "p95": 0.069 + }, + "rss_peak_mb": 168.203125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.063542, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "merged_cells", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.292125, + "p50": 0.299834, + "p95": 0.31075 + }, + "cpu_ms": { + "min": 0.291, + "p50": 0.299, + "p95": 0.311 + }, + "rss_peak_mb": 168.21875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.299834, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.16075, + "p50": 0.162375, + "p95": 0.168 + }, + "cpu_ms": { + "min": 0.16, + "p50": 0.162, + "p95": 0.169 + }, + "rss_peak_mb": 168.21875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.162375, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.924625, + "p50": 1.033208, + "p95": 1.166875 + }, + "cpu_ms": { + "min": 0.926, + "p50": 1.033, + "p95": 1.162 + }, + "rss_peak_mb": 168.21875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.033208, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.306167, + "p50": 0.33775, + "p95": 0.386167 + }, + "cpu_ms": { + "min": 0.307, + "p50": 0.338, + "p95": 0.386 + }, + "rss_peak_mb": 168.25, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.33775, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "merged_cells", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.32375, + "p50": 1.40275, + "p95": 1.548917 + }, + "cpu_ms": { + "min": 1.324, + "p50": 1.403, + "p95": 1.539 + }, + "rss_peak_mb": 168.265625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.40275, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.263708, + "p50": 1.364833, + "p95": 1.582292 + }, + "cpu_ms": { + "min": 1.264, + "p50": 1.366, + "p95": 1.583 + }, + "rss_peak_mb": 168.265625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.364833, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.192792, + "p50": 0.1995, + "p95": 0.21375 + }, + "cpu_ms": { + "min": 0.192, + "p50": 0.2, + "p95": 0.214 + }, + "rss_peak_mb": 168.265625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.1995, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "merged_cells", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.243708, + "p50": 1.399, + "p95": 1.814709 + }, + "cpu_ms": { + "min": 1.244, + "p50": 1.399, + "p95": 1.79 + }, + "rss_peak_mb": 168.296875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.399, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.500416, + "p50": 1.717, + "p95": 2.222416 + }, + "cpu_ms": { + "min": 1.499, + "p50": 1.717, + "p95": 2.223 + }, + "rss_peak_mb": 168.296875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.717, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.716208, + "p50": 1.912834, + "p95": 2.241667 + }, + "cpu_ms": { + "min": 1.716, + "p50": 1.913, + "p95": 2.239 + }, + "rss_peak_mb": 168.296875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.912834, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "merged_cells", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.832292, + "p50": 0.910542, + "p95": 1.144792 + }, + "cpu_ms": { + "min": 0.832, + "p50": 0.91, + "p95": 1.146 + }, + "rss_peak_mb": 168.296875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.910542, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "merged_cells", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.194459, + "p50": 0.26, + "p95": 0.376417 + }, + "cpu_ms": { + "min": 0.334, + "p50": 0.473, + "p95": 0.922 + }, + "rss_peak_mb": 168.3125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.26, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "merged_cells", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.01325, + "p50": 1.103583, + "p95": 1.318875 + }, + "cpu_ms": { + "min": 1.013, + "p50": 1.103, + "p95": 1.308 + }, + "rss_peak_mb": 168.3125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.103583, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.25925, + "p50": 1.354375, + "p95": 1.6305 + }, + "cpu_ms": { + "min": 1.26, + "p50": 1.354, + "p95": 1.617 + }, + "rss_peak_mb": 168.328125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.354375, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.375584, + "p50": 1.424834, + "p95": 1.751916 + }, + "cpu_ms": { + "min": 1.376, + "p50": 1.425, + "p95": 1.747 + }, + "rss_peak_mb": 168.640625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.424834, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.746375, + "p50": 1.842792, + "p95": 2.275542 + }, + "cpu_ms": { + "min": 1.747, + "p50": 1.843, + "p95": 2.276 + }, + "rss_peak_mb": 168.65625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.842792, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.789292, + "p50": 1.871333, + "p95": 2.270209 + }, + "cpu_ms": { + "min": 1.79, + "p50": 1.868, + "p95": 2.271 + }, + "rss_peak_mb": 168.65625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.871333, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "conditional_formatting", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.054208, + "p50": 0.056542, + "p95": 0.059416 + }, + "cpu_ms": { + "min": 0.054, + "p50": 0.056, + "p95": 0.059 + }, + "rss_peak_mb": 168.65625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.056542, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "conditional_formatting", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.209, + "p50": 0.216666, + "p95": 0.221584 + }, + "cpu_ms": { + "min": 0.209, + "p50": 0.216, + "p95": 0.221 + }, + "rss_peak_mb": 168.671875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.216666, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.181833, + "p50": 0.187042, + "p95": 0.207875 + }, + "cpu_ms": { + "min": 0.182, + "p50": 0.187, + "p95": 0.208 + }, + "rss_peak_mb": 168.6875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.187042, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.283875, + "p50": 0.307292, + "p95": 0.331041 + }, + "cpu_ms": { + "min": 0.284, + "p50": 0.307, + "p95": 0.331 + }, + "rss_peak_mb": 168.6875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.307292, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read failed: TypeError: expected string or bytes-like object, got 'NoneType'" + }, + { + "feature": "conditional_formatting", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "conditional_formatting", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.466542, + "p50": 1.528084, + "p95": 1.822 + }, + "cpu_ms": { + "min": 1.466, + "p50": 1.528, + "p95": 1.822 + }, + "rss_peak_mb": 168.6875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.528084, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.489875, + "p50": 1.563417, + "p95": 1.702333 + }, + "cpu_ms": { + "min": 1.491, + "p50": 1.564, + "p95": 1.702 + }, + "rss_peak_mb": 168.6875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.563417, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.1515, + "p50": 0.158167, + "p95": 0.166125 + }, + "cpu_ms": { + "min": 0.151, + "p50": 0.158, + "p95": 0.166 + }, + "rss_peak_mb": 168.6875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.158167, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "conditional_formatting", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.705125, + "p50": 1.795375, + "p95": 2.179666 + }, + "cpu_ms": { + "min": 1.705, + "p50": 1.796, + "p95": 2.18 + }, + "rss_peak_mb": 168.6875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.795375, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.748083, + "p50": 2.160416, + "p95": 2.994041 + }, + "cpu_ms": { + "min": 1.747, + "p50": 1.972, + "p95": 2.342 + }, + "rss_peak_mb": 168.6875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.160416, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.880041, + "p50": 2.028792, + "p95": 2.710584 + }, + "cpu_ms": { + "min": 1.881, + "p50": 2.029, + "p95": 2.711 + }, + "rss_peak_mb": 168.6875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.028792, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "conditional_formatting", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.8865, + "p50": 0.961208, + "p95": 1.00375 + }, + "cpu_ms": { + "min": 0.887, + "p50": 0.961, + "p95": 1.004 + }, + "rss_peak_mb": 168.6875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.961208, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "conditional_formatting", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.315708, + "p50": 0.397083, + "p95": 0.512084 + }, + "cpu_ms": { + "min": 0.55, + "p50": 0.968, + "p95": 1.789 + }, + "rss_peak_mb": 168.703125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.397083, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "conditional_formatting", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.357167, + "p50": 1.422, + "p95": 1.650542 + }, + "cpu_ms": { + "min": 1.356, + "p50": 1.422, + "p95": 1.65 + }, + "rss_peak_mb": 168.703125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.422, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.456208, + "p50": 1.511791, + "p95": 1.58 + }, + "cpu_ms": { + "min": 1.456, + "p50": 1.511, + "p95": 1.58 + }, + "rss_peak_mb": 168.703125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.511791, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.231792, + "p50": 1.325875, + "p95": 1.522834 + }, + "cpu_ms": { + "min": 1.233, + "p50": 1.327, + "p95": 1.522 + }, + "rss_peak_mb": 168.703125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.325875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.304083, + "p50": 1.378917, + "p95": 1.61275 + }, + "cpu_ms": { + "min": 1.305, + "p50": 1.381, + "p95": 1.584 + }, + "rss_peak_mb": 168.703125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.378917, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.559959, + "p50": 1.647709, + "p95": 2.198917 + }, + "cpu_ms": { + "min": 1.56, + "p50": 1.648, + "p95": 2.18 + }, + "rss_peak_mb": 168.703125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.647709, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "data_validation", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.052333, + "p50": 0.053708, + "p95": 0.055875 + }, + "cpu_ms": { + "min": 0.052, + "p50": 0.054, + "p95": 0.056 + }, + "rss_peak_mb": 168.703125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.053708, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "data_validation", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.157, + "p50": 0.161083, + "p95": 0.168375 + }, + "cpu_ms": { + "min": 0.156, + "p50": 0.161, + "p95": 0.169 + }, + "rss_peak_mb": 168.71875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.161083, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.159125, + "p50": 0.164833, + "p95": 0.171417 + }, + "cpu_ms": { + "min": 0.159, + "p50": 0.165, + "p95": 0.171 + }, + "rss_peak_mb": 168.71875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.164833, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.254917, + "p50": 0.265625, + "p95": 0.27375 + }, + "cpu_ms": { + "min": 0.254, + "p50": 0.265, + "p95": 0.274 + }, + "rss_peak_mb": 168.71875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.265625, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read failed: TypeError: expected string or bytes-like object, got 'NoneType'" + }, + { + "feature": "data_validation", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "data_validation", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.390791, + "p50": 1.508792, + "p95": 2.048375 + }, + "cpu_ms": { + "min": 1.39, + "p50": 1.509, + "p95": 2.026 + }, + "rss_peak_mb": 168.71875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.508792, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.320625, + "p50": 1.605708, + "p95": 2.082875 + }, + "cpu_ms": { + "min": 1.321, + "p50": 1.606, + "p95": 2.025 + }, + "rss_peak_mb": 168.71875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.605708, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.135667, + "p50": 0.14725, + "p95": 0.212125 + }, + "cpu_ms": { + "min": 0.135, + "p50": 0.147, + "p95": 0.211 + }, + "rss_peak_mb": 168.71875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.14725, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "data_validation", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.685833, + "p50": 2.287042, + "p95": 2.584291 + }, + "cpu_ms": { + "min": 1.687, + "p50": 2.281, + "p95": 2.567 + }, + "rss_peak_mb": 168.71875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 2.287042, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.420834, + "p50": 1.867666, + "p95": 3.110417 + }, + "cpu_ms": { + "min": 1.421, + "p50": 1.853, + "p95": 2.303 + }, + "rss_peak_mb": 168.71875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.867666, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.709916, + "p50": 1.98925, + "p95": 2.391625 + }, + "cpu_ms": { + "min": 1.709, + "p50": 1.989, + "p95": 2.368 + }, + "rss_peak_mb": 168.71875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.98925, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "data_validation", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.870708, + "p50": 0.973834, + "p95": 1.250834 + }, + "cpu_ms": { + "min": 0.871, + "p50": 0.974, + "p95": 1.246 + }, + "rss_peak_mb": 168.71875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.973834, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "data_validation", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.381625, + "p50": 0.486333, + "p95": 0.564083 + }, + "cpu_ms": { + "min": 1.215, + "p50": 1.751, + "p95": 2.602 + }, + "rss_peak_mb": 169.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.486333, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "data_validation", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.2745, + "p50": 1.3705, + "p95": 1.68525 + }, + "cpu_ms": { + "min": 1.274, + "p50": 1.371, + "p95": 1.682 + }, + "rss_peak_mb": 169.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.3705, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.263833, + "p50": 1.417708, + "p95": 1.712875 + }, + "cpu_ms": { + "min": 1.265, + "p50": 1.412, + "p95": 1.708 + }, + "rss_peak_mb": 169.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.417708, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.238291, + "p50": 1.385542, + "p95": 1.501542 + }, + "cpu_ms": { + "min": 1.237, + "p50": 1.386, + "p95": 1.499 + }, + "rss_peak_mb": 169.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.385542, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.346791, + "p50": 1.502334, + "p95": 1.705584 + }, + "cpu_ms": { + "min": 1.347, + "p50": 1.502, + "p95": 1.706 + }, + "rss_peak_mb": 169.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.502334, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.932625, + "p50": 2.195375, + "p95": 2.599792 + }, + "cpu_ms": { + "min": 1.933, + "p50": 2.196, + "p95": 2.601 + }, + "rss_peak_mb": 169.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.195375, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "hyperlinks", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.0535, + "p50": 0.055417, + "p95": 0.058 + }, + "cpu_ms": { + "min": 0.054, + "p50": 0.056, + "p95": 0.058 + }, + "rss_peak_mb": 169.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.055417, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "hyperlinks", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.226625, + "p50": 0.251417, + "p95": 0.303667 + }, + "cpu_ms": { + "min": 0.226, + "p50": 0.252, + "p95": 0.305 + }, + "rss_peak_mb": 169.015625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.251417, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.179625, + "p50": 0.184916, + "p95": 0.190333 + }, + "cpu_ms": { + "min": 0.18, + "p50": 0.185, + "p95": 0.19 + }, + "rss_peak_mb": 169.03125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.184916, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.265583, + "p50": 0.27975, + "p95": 0.353125 + }, + "cpu_ms": { + "min": 0.264, + "p50": 0.278, + "p95": 0.345 + }, + "rss_peak_mb": 169.03125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.27975, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read failed: TypeError: expected string or bytes-like object, got 'NoneType'" + }, + { + "feature": "hyperlinks", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "hyperlinks", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.4065, + "p50": 1.552166, + "p95": 1.953584 + }, + "cpu_ms": { + "min": 1.406, + "p50": 1.54, + "p95": 1.947 + }, + "rss_peak_mb": 169.03125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.552166, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.316625, + "p50": 1.449958, + "p95": 1.7855 + }, + "cpu_ms": { + "min": 1.317, + "p50": 1.451, + "p95": 1.781 + }, + "rss_peak_mb": 169.03125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.449958, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.12725, + "p50": 0.133458, + "p95": 0.145292 + }, + "cpu_ms": { + "min": 0.127, + "p50": 0.133, + "p95": 0.145 + }, + "rss_peak_mb": 169.03125, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.133458, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "hyperlinks", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.554125, + "p50": 1.686458, + "p95": 2.112625 + }, + "cpu_ms": { + "min": 1.554, + "p50": 1.686, + "p95": 2.113 + }, + "rss_peak_mb": 169.046875, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.686458, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.318667, + "p50": 1.53875, + "p95": 2.620708 + }, + "cpu_ms": { + "min": 1.318, + "p50": 1.535, + "p95": 2.554 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.53875, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.9805, + "p50": 2.536292, + "p95": 7.951125 + }, + "cpu_ms": { + "min": 1.981, + "p50": 2.532, + "p95": 4.553 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.536292, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "hyperlinks", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.884875, + "p50": 0.968625, + "p95": 1.1705 + }, + "cpu_ms": { + "min": 0.885, + "p50": 0.969, + "p95": 1.17 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.968625, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "hyperlinks", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.349416, + "p50": 0.465083, + "p95": 0.539584 + }, + "cpu_ms": { + "min": 0.635, + "p50": 1.28, + "p95": 2.173 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.465083, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "hyperlinks", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.142708, + "p50": 1.236209, + "p95": 1.494583 + }, + "cpu_ms": { + "min": 1.143, + "p50": 1.237, + "p95": 1.49 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.236209, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.227125, + "p50": 1.489667, + "p95": 1.754584 + }, + "cpu_ms": { + "min": 1.227, + "p50": 1.49, + "p95": 1.749 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.489667, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "images", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.185042, + "p50": 1.276125, + "p95": 1.660917 + }, + "cpu_ms": { + "min": 1.185, + "p50": 1.276, + "p95": 1.656 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.276125, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.817625, + "p50": 2.02675, + "p95": 2.325875 + }, + "cpu_ms": { + "min": 1.817, + "p50": 2.022, + "p95": 2.321 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.02675, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "images", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 2.654458, + "p50": 3.072083, + "p95": 3.596875 + }, + "cpu_ms": { + "min": 2.644, + "p50": 3.065, + "p95": 3.59 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 3.072083, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "images", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.050084, + "p50": 0.056125, + "p95": 0.058708 + }, + "cpu_ms": { + "min": 0.05, + "p50": 0.056, + "p95": 0.058 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.056125, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "images", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.448292, + "p50": 0.46275, + "p95": 0.469625 + }, + "cpu_ms": { + "min": 0.449, + "p50": 0.462, + "p95": 0.47 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.46275, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.271083, + "p50": 0.279584, + "p95": 0.297125 + }, + "cpu_ms": { + "min": 0.271, + "p50": 0.28, + "p95": 0.297 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.279584, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "images", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.25075, + "p50": 0.265083, + "p95": 0.301166 + }, + "cpu_ms": { + "min": 0.25, + "p50": 0.265, + "p95": 0.294 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.265083, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read failed: TypeError: expected string or bytes-like object, got 'NoneType'" + }, + { + "feature": "images", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "images", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.208292, + "p50": 1.313125, + "p95": 1.5785 + }, + "cpu_ms": { + "min": 1.208, + "p50": 1.314, + "p95": 1.579 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.313125, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.212208, + "p50": 1.375208, + "p95": 1.663709 + }, + "cpu_ms": { + "min": 1.211, + "p50": 1.375, + "p95": 1.663 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.375208, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "images", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.12675, + "p50": 0.136042, + "p95": 0.152583 + }, + "cpu_ms": { + "min": 0.126, + "p50": 0.136, + "p95": 0.153 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.136042, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "images", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.116875, + "p50": 1.273834, + "p95": 1.551584 + }, + "cpu_ms": { + "min": 1.116, + "p50": 1.274, + "p95": 1.552 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.273834, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.298875, + "p50": 1.433042, + "p95": 1.702833 + }, + "cpu_ms": { + "min": 1.298, + "p50": 1.432, + "p95": 1.703 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.433042, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "images", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.567084, + "p50": 1.665333, + "p95": 2.053625 + }, + "cpu_ms": { + "min": 1.567, + "p50": 1.666, + "p95": 2.044 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.665333, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "images", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.762583, + "p50": 0.827167, + "p95": 0.950458 + }, + "cpu_ms": { + "min": 0.763, + "p50": 0.826, + "p95": 0.945 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.827167, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "images", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.195208, + "p50": 0.286916, + "p95": 0.399709 + }, + "cpu_ms": { + "min": 0.318, + "p50": 0.517, + "p95": 1.162 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.286916, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "images", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.892459, + "p50": 0.975208, + "p95": 1.170417 + }, + "cpu_ms": { + "min": 0.893, + "p50": 0.976, + "p95": 1.17 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.975208, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.231917, + "p50": 1.343459, + "p95": 1.561542 + }, + "cpu_ms": { + "min": 1.232, + "p50": 1.344, + "p95": 1.553 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.343459, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "pivot_tables", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.826292, + "p50": 0.896875, + "p95": 0.966167 + }, + "cpu_ms": { + "min": 0.826, + "p50": 0.896, + "p95": 0.967 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.896875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.190959, + "p50": 1.285291, + "p95": 1.433958 + }, + "cpu_ms": { + "min": 1.192, + "p50": 1.285, + "p95": 1.43 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.285291, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "pivot_tables", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.526083, + "p50": 1.71925, + "p95": 1.880958 + }, + "cpu_ms": { + "min": 1.525, + "p50": 1.719, + "p95": 1.881 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.71925, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "pivot_tables", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.056375, + "p50": 0.05875, + "p95": 0.061167 + }, + "cpu_ms": { + "min": 0.055, + "p50": 0.058, + "p95": 0.061 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.05875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "pivot_tables", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.055666, + "p50": 0.060083, + "p95": 0.097625 + }, + "cpu_ms": { + "min": 0.056, + "p50": 0.06, + "p95": 0.097 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.060083, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.148542, + "p50": 0.153125, + "p95": 0.162166 + }, + "cpu_ms": { + "min": 0.148, + "p50": 0.153, + "p95": 0.163 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.153125, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "pivot_tables", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.769583, + "p50": 0.856667, + "p95": 0.942958 + }, + "cpu_ms": { + "min": 0.769, + "p50": 0.857, + "p95": 0.934 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.856667, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.259584, + "p50": 0.275, + "p95": 0.330042 + }, + "cpu_ms": { + "min": 0.26, + "p50": 0.275, + "p95": 0.331 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.275, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "pivot_tables", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "pivot_tables", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.888833, + "p50": 0.966625, + "p95": 1.041625 + }, + "cpu_ms": { + "min": 0.888, + "p50": 0.966, + "p95": 1.041 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.966625, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.207375, + "p50": 1.37425, + "p95": 1.688625 + }, + "cpu_ms": { + "min": 1.207, + "p50": 1.369, + "p95": 1.689 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.37425, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "pivot_tables", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.1315, + "p50": 0.138167, + "p95": 0.145125 + }, + "cpu_ms": { + "min": 0.132, + "p50": 0.138, + "p95": 0.145 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.138167, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "pivot_tables", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.085708, + "p50": 1.242125, + "p95": 1.598 + }, + "cpu_ms": { + "min": 1.085, + "p50": 1.241, + "p95": 1.59 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.242125, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.349417, + "p50": 1.473416, + "p95": 1.6675 + }, + "cpu_ms": { + "min": 1.349, + "p50": 1.473, + "p95": 1.668 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.473416, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "pivot_tables", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.573917, + "p50": 1.73425, + "p95": 2.141209 + }, + "cpu_ms": { + "min": 1.574, + "p50": 1.734, + "p95": 2.139 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.73425, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "pivot_tables", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.744375, + "p50": 0.820083, + "p95": 0.862541 + }, + "cpu_ms": { + "min": 0.744, + "p50": 0.82, + "p95": 0.863 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.820083, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "pivot_tables", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.219042, + "p50": 0.245167, + "p95": 0.331375 + }, + "cpu_ms": { + "min": 0.307, + "p50": 0.455, + "p95": 0.755 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.245167, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "pivot_tables", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.828917, + "p50": 0.950291, + "p95": 1.099125 + }, + "cpu_ms": { + "min": 0.829, + "p50": 0.95, + "p95": 1.093 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.950291, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.176042, + "p50": 1.257292, + "p95": 1.349459 + }, + "cpu_ms": { + "min": 1.176, + "p50": 1.257, + "p95": 1.35 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.257292, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.083709, + "p50": 1.152666, + "p95": 1.244292 + }, + "cpu_ms": { + "min": 1.084, + "p50": 1.153, + "p95": 1.245 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.152666, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.480666, + "p50": 1.587709, + "p95": 1.826292 + }, + "cpu_ms": { + "min": 1.481, + "p50": 1.589, + "p95": 1.822 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.587709, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.991291, + "p50": 2.255833, + "p95": 2.760708 + }, + "cpu_ms": { + "min": 1.991, + "p50": 2.256, + "p95": 2.755 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.255833, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "comments", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.063833, + "p50": 0.065708, + "p95": 0.067042 + }, + "cpu_ms": { + "min": 0.063, + "p50": 0.066, + "p95": 0.067 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.065708, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "comments", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.206, + "p50": 0.211875, + "p95": 0.219792 + }, + "cpu_ms": { + "min": 0.207, + "p50": 0.211, + "p95": 0.219 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.211875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.196208, + "p50": 0.200584, + "p95": 0.20875 + }, + "cpu_ms": { + "min": 0.196, + "p50": 0.201, + "p95": 0.209 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.200584, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.166375, + "p50": 1.1985, + "p95": 1.266917 + }, + "cpu_ms": { + "min": 1.166, + "p50": 1.199, + "p95": 1.267 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.1985, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.249083, + "p50": 0.25925, + "p95": 0.27325 + }, + "cpu_ms": { + "min": 0.249, + "p50": 0.259, + "p95": 0.273 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.25925, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "comments", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.18175, + "p50": 1.281875, + "p95": 1.490666 + }, + "cpu_ms": { + "min": 1.182, + "p50": 1.282, + "p95": 1.468 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.281875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.27875, + "p50": 1.388917, + "p95": 1.635584 + }, + "cpu_ms": { + "min": 1.279, + "p50": 1.388, + "p95": 1.635 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.388917, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.129792, + "p50": 0.133416, + "p95": 0.142375 + }, + "cpu_ms": { + "min": 0.129, + "p50": 0.133, + "p95": 0.143 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.133416, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "comments", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.216458, + "p50": 1.358709, + "p95": 1.603917 + }, + "cpu_ms": { + "min": 1.211, + "p50": 1.358, + "p95": 1.604 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.358709, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.369041, + "p50": 1.612042, + "p95": 2.248792 + }, + "cpu_ms": { + "min": 1.369, + "p50": 1.612, + "p95": 2.133 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.612042, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.600625, + "p50": 1.806583, + "p95": 2.119375 + }, + "cpu_ms": { + "min": 1.601, + "p50": 1.803, + "p95": 2.107 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.806583, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "comments", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.840208, + "p50": 0.917583, + "p95": 1.060042 + }, + "cpu_ms": { + "min": 0.84, + "p50": 0.919, + "p95": 1.06 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.917583, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "comments", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.211958, + "p50": 0.252333, + "p95": 0.324541 + }, + "cpu_ms": { + "min": 0.331, + "p50": 0.454, + "p95": 0.776 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.252333, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "comments", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.969708, + "p50": 1.063041, + "p95": 1.168667 + }, + "cpu_ms": { + "min": 0.97, + "p50": 1.064, + "p95": 1.168 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.063041, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.207958, + "p50": 1.343167, + "p95": 1.606542 + }, + "cpu_ms": { + "min": 1.208, + "p50": 1.344, + "p95": 1.607 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.343167, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.376416, + "p50": 1.4545, + "p95": 1.962166 + }, + "cpu_ms": { + "min": 1.377, + "p50": 1.454, + "p95": 1.962 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.4545, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 2.063458, + "p50": 2.318334, + "p95": 2.810208 + }, + "cpu_ms": { + "min": 2.063, + "p50": 2.308, + "p95": 2.803 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.318334, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 2.037083, + "p50": 2.287917, + "p95": 3.006875 + }, + "cpu_ms": { + "min": 2.037, + "p50": 2.288, + "p95": 2.994 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.287917, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "freeze_panes", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.056125, + "p50": 0.058125, + "p95": 0.062667 + }, + "cpu_ms": { + "min": 0.056, + "p50": 0.058, + "p95": 0.062 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.058125, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "freeze_panes", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.232084, + "p50": 0.2395, + "p95": 0.258042 + }, + "cpu_ms": { + "min": 0.232, + "p50": 0.239, + "p95": 0.259 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.2395, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.198166, + "p50": 0.202, + "p95": 0.228834 + }, + "cpu_ms": { + "min": 0.199, + "p50": 0.202, + "p95": 0.229 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.202, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.30575, + "p50": 0.317542, + "p95": 0.3605 + }, + "cpu_ms": { + "min": 0.306, + "p50": 0.319, + "p95": 0.36 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.317542, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read failed: TypeError: expected string or bytes-like object, got 'NoneType'" + }, + { + "feature": "freeze_panes", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "freeze_panes", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.532292, + "p50": 1.738916, + "p95": 2.260666 + }, + "cpu_ms": { + "min": 1.532, + "p50": 1.739, + "p95": 2.248 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.738916, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 2.216792, + "p50": 2.486584, + "p95": 3.110333 + }, + "cpu_ms": { + "min": 2.216, + "p50": 2.486, + "p95": 3.112 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.486584, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.181041, + "p50": 0.191292, + "p95": 0.204167 + }, + "cpu_ms": { + "min": 0.181, + "p50": 0.192, + "p95": 0.204 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.191292, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "freeze_panes", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.808416, + "p50": 2.0015, + "p95": 2.214458 + }, + "cpu_ms": { + "min": 1.808, + "p50": 1.992, + "p95": 2.203 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 2.0015, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 2.303833, + "p50": 2.459625, + "p95": 3.569709 + }, + "cpu_ms": { + "min": 2.304, + "p50": 2.459, + "p95": 3.57 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.459625, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 2.372125, + "p50": 2.563292, + "p95": 3.133291 + }, + "cpu_ms": { + "min": 2.372, + "p50": 2.557, + "p95": 3.134 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.563292, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "freeze_panes", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.921583, + "p50": 1.016209, + "p95": 1.310125 + }, + "cpu_ms": { + "min": 0.921, + "p50": 1.016, + "p95": 1.304 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.016209, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "freeze_panes", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.373458, + "p50": 0.508166, + "p95": 0.66825 + }, + "cpu_ms": { + "min": 0.604, + "p50": 1.675, + "p95": 2.409 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.508166, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "freeze_panes", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.353584, + "p50": 1.448583, + "p95": 1.705416 + }, + "cpu_ms": { + "min": 1.353, + "p50": 1.448, + "p95": 1.705 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.448583, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 2.198, + "p50": 2.58225, + "p95": 2.971458 + }, + "cpu_ms": { + "min": 2.198, + "p50": 2.572, + "p95": 2.939 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.58225, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.139125, + "p50": 1.240333, + "p95": 1.676958 + }, + "cpu_ms": { + "min": 1.139, + "p50": 1.241, + "p95": 1.66 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.240333, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.478333, + "p50": 1.591, + "p95": 2.120834 + }, + "cpu_ms": { + "min": 1.479, + "p50": 1.592, + "p95": 2.121 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.591, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.735833, + "p50": 2.010292, + "p95": 2.338459 + }, + "cpu_ms": { + "min": 1.736, + "p50": 2.01, + "p95": 2.338 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.010292, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "named_ranges", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.053042, + "p50": 0.0545, + "p95": 0.058917 + }, + "cpu_ms": { + "min": 0.053, + "p50": 0.054, + "p95": 0.06 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0545, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "named_ranges", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.052917, + "p50": 0.055, + "p95": 0.071458 + }, + "cpu_ms": { + "min": 0.053, + "p50": 0.055, + "p95": 0.07 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.055, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.166042, + "p50": 0.1725, + "p95": 0.182625 + }, + "cpu_ms": { + "min": 0.166, + "p50": 0.172, + "p95": 0.182 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.1725, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.267709, + "p50": 0.279709, + "p95": 0.305875 + }, + "cpu_ms": { + "min": 0.267, + "p50": 0.28, + "p95": 0.306 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.279709, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read failed: TypeError: expected string or bytes-like object, got 'NoneType'" + }, + { + "feature": "named_ranges", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "named_ranges", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.269542, + "p50": 1.336875, + "p95": 1.716167 + }, + "cpu_ms": { + "min": 1.269, + "p50": 1.337, + "p95": 1.716 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.336875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.569084, + "p50": 1.77025, + "p95": 2.050584 + }, + "cpu_ms": { + "min": 1.569, + "p50": 1.77, + "p95": 2.052 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.77025, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.150375, + "p50": 0.158708, + "p95": 0.208833 + }, + "cpu_ms": { + "min": 0.15, + "p50": 0.159, + "p95": 0.205 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.158708, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "named_ranges", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.490625, + "p50": 1.80825, + "p95": 2.241542 + }, + "cpu_ms": { + "min": 1.491, + "p50": 1.808, + "p95": 2.237 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.80825, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.699708, + "p50": 1.968167, + "p95": 2.359625 + }, + "cpu_ms": { + "min": 1.7, + "p50": 1.967, + "p95": 2.359 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.968167, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.818917, + "p50": 2.161, + "p95": 2.658958 + }, + "cpu_ms": { + "min": 1.818, + "p50": 2.162, + "p95": 2.639 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.161, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "named_ranges", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.813667, + "p50": 0.886416, + "p95": 0.979667 + }, + "cpu_ms": { + "min": 0.814, + "p50": 0.886, + "p95": 0.98 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.886416, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "named_ranges", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.349167, + "p50": 0.433959, + "p95": 0.522 + }, + "cpu_ms": { + "min": 0.802, + "p50": 1.749, + "p95": 2.139 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.433959, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "named_ranges", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.119666, + "p50": 1.270708, + "p95": 1.511209 + }, + "cpu_ms": { + "min": 1.12, + "p50": 1.271, + "p95": 1.5 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.270708, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.594375, + "p50": 1.779417, + "p95": 2.05625 + }, + "cpu_ms": { + "min": 1.594, + "p50": 1.775, + "p95": 2.051 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.779417, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "openpyxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.447125, + "p50": 1.657125, + "p95": 1.98775 + }, + "cpu_ms": { + "min": 1.448, + "p50": 1.652, + "p95": 1.966 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.657125, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.257083, + "p50": 1.426625, + "p95": 1.85125 + }, + "cpu_ms": { + "min": 1.257, + "p50": 1.426, + "p95": 1.784 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.426625, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "xlsxwriter", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.520875, + "p50": 1.751333, + "p95": 2.6565 + }, + "cpu_ms": { + "min": 1.521, + "p50": 1.747, + "p95": 2.186 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.751333, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "tables", + "library": "python-calamine", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.057875, + "p50": 0.058875, + "p95": 0.0615 + }, + "cpu_ms": { + "min": 0.057, + "p50": 0.059, + "p95": 0.061 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.058875, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "tables", + "library": "wolfxl", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.05725, + "p50": 0.057833, + "p95": 0.060791 + }, + "cpu_ms": { + "min": 0.057, + "p50": 0.058, + "p95": 0.06 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.057833, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 0.149041, + "p50": 0.153416, + "p95": 0.159292 + }, + "cpu_ms": { + "min": 0.149, + "p50": 0.153, + "p95": 0.16 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.153416, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "pylightxl", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.251167, + "p50": 0.273167, + "p95": 0.527125 + }, + "cpu_ms": { + "min": 0.251, + "p50": 0.274, + "p95": 0.375 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.273167, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read failed: TypeError: expected string or bytes-like object, got 'NoneType'" + }, + { + "feature": "tables", + "library": "xlrd", + "workload_size": "small", + "perf": { + "read": null, + "write": null + }, + "notes": "Write unsupported; Read not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "tables", + "library": "pyexcel", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.885, + "p50": 2.059917, + "p95": 2.885584 + }, + "cpu_ms": { + "min": 1.886, + "p50": 2.06, + "p95": 2.867 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 2.059917, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.377458, + "p50": 1.67, + "p95": 1.862125 + }, + "cpu_ms": { + "min": 1.378, + "p50": 1.647, + "p95": 1.821 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.67, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "xlwt", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 0.133792, + "p50": 0.139667, + "p95": 0.150583 + }, + "cpu_ms": { + "min": 0.134, + "p50": 0.14, + "p95": 0.15 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 0.139667, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "tables", + "library": "pandas", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.752375, + "p50": 1.962709, + "p95": 2.286875 + }, + "cpu_ms": { + "min": 1.753, + "p50": 1.963, + "p95": 2.282 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.962709, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.386292, + "p50": 1.692667, + "p95": 2.374 + }, + "cpu_ms": { + "min": 1.386, + "p50": 1.692, + "p95": 2.233 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.692667, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "xlsxwriter-constmem", + "workload_size": "small", + "perf": { + "read": null, + "write": { + "wall_ms": { + "min": 1.701542, + "p50": 2.030833, + "p95": 2.540125 + }, + "cpu_ms": { + "min": 1.703, + "p50": 2.031, + "p95": 2.535 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 2.030833, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": "Read unsupported" + }, + { + "feature": "tables", + "library": "openpyxl-readonly", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.89175, + "p50": 0.924625, + "p95": 1.091417 + }, + "cpu_ms": { + "min": 0.892, + "p50": 0.925, + "p95": 1.092 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.924625, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "tables", + "library": "polars", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 0.303375, + "p50": 0.377166, + "p95": 0.453625 + }, + "cpu_ms": { + "min": 0.965, + "p50": 1.378, + "p95": 1.965 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.377166, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": null + }, + "notes": "Write unsupported" + }, + { + "feature": "tables", + "library": "tablib", + "workload_size": "small", + "perf": { + "read": { + "wall_ms": { + "min": 1.316625, + "p50": 1.412458, + "p95": 1.6365 + }, + "cpu_ms": { + "min": 1.317, + "p50": 1.413, + "p95": 1.637 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 1.412458, + "write": 0.0, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + }, + "write": { + "wall_ms": { + "min": 1.225917, + "p50": 1.354667, + "p95": 1.60525 + }, + "cpu_ms": { + "min": 1.226, + "p50": 1.354, + "p95": 1.606 + }, + "rss_peak_mb": 169.0625, + "breakdown_ms": null, + "phase_attribution_ms": { + "parse": 0.0, + "write": 1.354667, + "verify": 0.0 + }, + "op_count": null, + "op_unit": null, + "rss_kb_via_time": null, + "python_heap_peak_kb": null + } + }, + "notes": null + } + ] +} \ No newline at end of file diff --git a/results-release-2026-04-28/results.json b/results-release-2026-04-28/results.json new file mode 100644 index 0000000..29a7ad1 --- /dev/null +++ b/results-release-2026-04-28/results.json @@ -0,0 +1,57467 @@ +{ + "metadata": { + "benchmark_version": "0.1.0", + "run_date": "2026-04-29T03:12:20.382948+00:00", + "excel_version": "16.105.3", + "platform": "Darwin-arm64", + "profile": "xlsx" + }, + "libraries": { + "openpyxl": { + "name": "openpyxl", + "version": "3.1.5", + "language": "python", + "capabilities": [ + "write", + "read" + ] + }, + "xlsxwriter": { + "name": "xlsxwriter", + "version": "3.2.9", + "language": "python", + "capabilities": [ + "write" + ] + }, + "python-calamine": { + "name": "python-calamine", + "version": "0.6.1", + "language": "python", + "capabilities": [ + "read" + ] + }, + "wolfxl": { + "name": "wolfxl", + "version": "cal=2.0.0+rxw=2.0.0", + "language": "rust", + "capabilities": [ + "modify", + "write", + "read" + ] + }, + "pylightxl": { + "name": "pylightxl", + "version": "1.61", + "language": "python", + "capabilities": [ + "write", + "read" + ] + }, + "xlrd": { + "name": "xlrd", + "version": "2.0.2", + "language": "python", + "capabilities": [ + "read" + ] + }, + "pyexcel": { + "name": "pyexcel", + "version": "0.7.4", + "language": "python", + "capabilities": [ + "write", + "read" + ] + }, + "xlwt": { + "name": "xlwt", + "version": "1.3.0", + "language": "python", + "capabilities": [ + "write" + ] + }, + "pandas": { + "name": "pandas", + "version": "3.0.0", + "language": "python", + "capabilities": [ + "write", + "read" + ] + }, + "xlsxwriter-constmem": { + "name": "xlsxwriter-constmem", + "version": "3.2.9", + "language": "python", + "capabilities": [ + "write" + ] + }, + "openpyxl-readonly": { + "name": "openpyxl-readonly", + "version": "3.1.5", + "language": "python", + "capabilities": [ + "read" + ] + }, + "polars": { + "name": "polars", + "version": "1.38.1", + "language": "python", + "capabilities": [ + "read" + ] + }, + "tablib": { + "name": "tablib", + "version": "3.9.0", + "language": "python", + "capabilities": [ + "write", + "read" + ] + } + }, + "results": [ + { + "feature": "cell_values", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "string_simple": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "read": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + }, + "write": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "read": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + }, + "write": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + }, + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + }, + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "string_simple": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "write": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "write": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "python-calamine", + "scores": { + "read": 1, + "write": null + }, + "test_cases": { + "string_simple": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "read": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "read": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "read": { + "passed": false, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "read", + "test_case_id": "error_div0", + "sheet": "cell_values", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'error', 'value': '#DIV/0!'}, actual={'type': 'blank'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "read": { + "passed": false, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "read", + "test_case_id": "error_na", + "sheet": "cell_values", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'error', 'value': '#N/A'}, actual={'type': 'blank'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "read": { + "passed": false, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "read", + "test_case_id": "error_value", + "sheet": "cell_values", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'error', 'value': '#VALUE!'}, actual={'type': 'blank'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": "Known limitation: python-calamine can surface formula error cells as blank values in current API responses." + }, + { + "feature": "cell_values", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "string_simple": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "read": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + }, + "write": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "read": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + }, + "write": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + }, + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + }, + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "pylightxl", + "scores": { + "read": 3, + "write": 1 + }, + "test_cases": { + "string_simple": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "read": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + }, + "write": { + "passed": false, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "string", + "value": "2026-02-04" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "write", + "test_case_id": "date_standard", + "sheet": "cell_values", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'date', 'value': '2026-02-04'}, actual={'type': 'string', 'value': '2026-02-04'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "read": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + }, + "write": { + "passed": false, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "string", + "value": "2026-02-04T10:30:45" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "write", + "test_case_id": "datetime", + "sheet": "cell_values", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'datetime', 'value': '2026-02-04T10:30:45'}, actual={'type': 'string', 'value': '2026-02-04T10:30:45'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + }, + "write": { + "passed": false, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "number", + "value": 1 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "write", + "test_case_id": "boolean_true", + "sheet": "cell_values", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'boolean', 'value': True}, actual={'type': 'number', 'value': 1}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + }, + "write": { + "passed": false, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "number", + "value": 0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "write", + "test_case_id": "boolean_false", + "sheet": "cell_values", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'boolean', 'value': False}, actual={'type': 'number', 'value': 0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": "Known limitation: pylightxl cell-values write has date/boolean/error fidelity limits due to writer encoding behavior." + }, + { + "feature": "cell_values", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "cell_values", + "library": "pyexcel", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "string_simple": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "read": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + }, + "write": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "read": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + }, + "write": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + }, + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + }, + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "xlwt", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "string_simple": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "write": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "write": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "pandas", + "scores": { + "read": 1, + "write": 3 + }, + "test_cases": { + "string_simple": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "read": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + }, + "write": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "read": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + }, + "write": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + }, + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + }, + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "read": { + "passed": false, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "read", + "test_case_id": "error_div0", + "sheet": "cell_values", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'error', 'value': '#DIV/0!'}, actual={'type': 'blank'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Error - #DIV/0!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "read": { + "passed": false, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "read", + "test_case_id": "error_na", + "sheet": "cell_values", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'error', 'value': '#N/A'}, actual={'type': 'blank'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Error - #N/A" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "read": { + "passed": false, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "read", + "test_case_id": "error_value", + "sheet": "cell_values", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'error', 'value': '#VALUE!'}, actual={'type': 'blank'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Error - #VALUE!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "string_simple": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "write": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "write": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "openpyxl-readonly", + "scores": { + "read": 3, + "write": null + }, + "test_cases": { + "string_simple": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "read": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "read": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "polars", + "scores": { + "read": 1, + "write": null + }, + "test_cases": { + "string_simple": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.141592654 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "read": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "read": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "read": { + "passed": false, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "read", + "test_case_id": "error_div0", + "sheet": "cell_values", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'error', 'value': '#DIV/0!'}, actual={'type': 'blank'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "read": { + "passed": false, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "read", + "test_case_id": "error_na", + "sheet": "cell_values", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'error', 'value': '#N/A'}, actual={'type': 'blank'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "read": { + "passed": false, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "cell_values", + "operation": "read", + "test_case_id": "error_value", + "sheet": "cell_values", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'error', 'value': '#VALUE!'}, actual={'type': 'blank'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "cell_values", + "library": "tablib", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "string_simple": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Hello World" + }, + "actual": { + "type": "string", + "value": "Hello World" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - simple" + } + }, + "string_unicode": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "actual": { + "type": "string", + "value": "\u65e5\u672c\u8a9e\ud83c\udf89\u00e9mojis" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - unicode" + } + }, + "string_empty": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - empty" + } + }, + "string_long": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "actual": { + "type": "string", + "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - long (1000 chars)" + } + }, + "string_newline": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "actual": { + "type": "string", + "value": "Line 1\nLine 2\nLine 3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "String - with newlines" + } + }, + "number_integer": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 42 + }, + "actual": { + "type": "number", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - integer" + } + }, + "number_float": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 3.14159265358979 + }, + "actual": { + "type": "number", + "value": 3.14159265358979 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - float" + } + }, + "number_negative": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": -100.5 + }, + "actual": { + "type": "number", + "value": -100.5 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - negative" + } + }, + "number_large": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1234567890123456 + }, + "actual": { + "type": "number", + "value": 1234567890123456 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - large" + } + }, + "number_scientific": { + "read": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + }, + "write": { + "passed": true, + "expected": { + "type": "number", + "value": 1.23e-10 + }, + "actual": { + "type": "number", + "value": 1.23e-10 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Number - scientific notation" + } + }, + "date_standard": { + "read": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + }, + "write": { + "passed": true, + "expected": { + "type": "date", + "value": "2026-02-04" + }, + "actual": { + "type": "date", + "value": "2026-02-04" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Date - standard" + } + }, + "datetime": { + "read": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + }, + "write": { + "passed": true, + "expected": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "actual": { + "type": "datetime", + "value": "2026-02-04T10:30:45" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DateTime - with time" + } + }, + "boolean_true": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + }, + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": true + }, + "actual": { + "type": "boolean", + "value": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - TRUE" + } + }, + "boolean_false": { + "read": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + }, + "write": { + "passed": true, + "expected": { + "type": "boolean", + "value": false + }, + "actual": { + "type": "boolean", + "value": false + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Boolean - FALSE" + } + }, + "error_div0": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#DIV/0!" + }, + "actual": { + "type": "error", + "value": "#DIV/0!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #DIV/0!" + } + }, + "error_na": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#N/A" + }, + "actual": { + "type": "error", + "value": "#N/A" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #N/A" + } + }, + "error_value": { + "read": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + }, + "write": { + "passed": true, + "expected": { + "type": "error", + "value": "#VALUE!" + }, + "actual": { + "type": "error", + "value": "#VALUE!" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Error - #VALUE!" + } + }, + "blank": { + "read": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + }, + "write": { + "passed": true, + "expected": { + "type": "blank" + }, + "actual": { + "type": "blank" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Blank cell" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "formula_sum": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "formula_sum": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "formula_sum": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_sum", + "sheet": "formulas", + "cell": "B2" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "error": "Expected formula, got blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cell_ref", + "sheet": "formulas", + "cell": "B3" + }, + "adapter_message": "RuntimeError: Expected formula, got blank", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "error": "Expected formula, got string" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_concat", + "sheet": "formulas", + "cell": "B4" + }, + "adapter_message": "RuntimeError: Expected formula, got string", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cross_sheet", + "sheet": "formulas", + "cell": "B5" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "formula_sum": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 3 + }, + "test_cases": { + "formula_sum": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_sum", + "sheet": "formulas", + "cell": "B2" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - SUM" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "error": "Expected formula, got error" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cell_ref", + "sheet": "formulas", + "cell": "B3" + }, + "adapter_message": "RuntimeError: Expected formula, got error", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cell reference" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "error": "Expected formula, got string" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_concat", + "sheet": "formulas", + "cell": "B4" + }, + "adapter_message": "RuntimeError: Expected formula, got string", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - concat" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cross_sheet", + "sheet": "formulas", + "cell": "B5" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cross sheet" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "formulas", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 3 + }, + "test_cases": { + "formula_sum": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_sum", + "sheet": "formulas", + "cell": "B2" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - SUM" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "error": "Expected formula, got error" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cell_ref", + "sheet": "formulas", + "cell": "B3" + }, + "adapter_message": "RuntimeError: Expected formula, got error", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cell reference" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "error": "Expected formula, got string" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_concat", + "sheet": "formulas", + "cell": "B4" + }, + "adapter_message": "RuntimeError: Expected formula, got string", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - concat" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cross_sheet", + "sheet": "formulas", + "cell": "B5" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cross sheet" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "xlwt", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "formula_sum": { + "write": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "error": "Expected formula, got string" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "write", + "test_case_id": "formula_sum", + "sheet": "formulas", + "cell": "B2" + }, + "adapter_message": "RuntimeError: Expected formula, got string", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "write": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "error": "Expected formula, got string" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "write", + "test_case_id": "formula_cell_ref", + "sheet": "formulas", + "cell": "B3" + }, + "adapter_message": "RuntimeError: Expected formula, got string", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "write": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "error": "Expected formula, got string" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "write", + "test_case_id": "formula_concat", + "sheet": "formulas", + "cell": "B4" + }, + "adapter_message": "RuntimeError: Expected formula, got string", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "write": { + "passed": false, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "error": "Expected formula, got string" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "write", + "test_case_id": "formula_cross_sheet", + "sheet": "formulas", + "cell": "B5" + }, + "adapter_message": "RuntimeError: Expected formula, got string", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "pandas", + "scores": { + "read": 0, + "write": 3 + }, + "test_cases": { + "formula_sum": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_sum", + "sheet": "formulas", + "cell": "B2" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - SUM" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "error": "Expected formula, got blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cell_ref", + "sheet": "formulas", + "cell": "B3" + }, + "adapter_message": "RuntimeError: Expected formula, got blank", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cell reference" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "error": "Expected formula, got string" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_concat", + "sheet": "formulas", + "cell": "B4" + }, + "adapter_message": "RuntimeError: Expected formula, got string", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - concat" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cross_sheet", + "sheet": "formulas", + "cell": "B5" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cross sheet" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "formula_sum": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "openpyxl-readonly", + "scores": { + "read": 3, + "write": null + }, + "test_cases": { + "formula_sum": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "read": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "formula_sum": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_sum", + "sheet": "formulas", + "cell": "B2" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "error": "Expected formula, got blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cell_ref", + "sheet": "formulas", + "cell": "B3" + }, + "adapter_message": "RuntimeError: Expected formula, got blank", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "error": "Expected formula, got string" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_concat", + "sheet": "formulas", + "cell": "B4" + }, + "adapter_message": "RuntimeError: Expected formula, got string", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cross_sheet", + "sheet": "formulas", + "cell": "B5" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "formulas", + "library": "tablib", + "scores": { + "read": 0, + "write": 3 + }, + "test_cases": { + "formula_sum": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_sum", + "sheet": "formulas", + "cell": "B2" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - SUM" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "actual": { + "type": "formula", + "formula": "=SUM(1,2,3)" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - SUM" + } + }, + "formula_cell_ref": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "error": "Expected formula, got error" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cell_ref", + "sheet": "formulas", + "cell": "B3" + }, + "adapter_message": "RuntimeError: Expected formula, got error", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cell reference" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A3*2" + }, + "actual": { + "type": "formula", + "formula": "=A3*2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cell reference" + } + }, + "formula_concat": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "error": "Expected formula, got string" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_concat", + "sheet": "formulas", + "cell": "B4" + }, + "adapter_message": "RuntimeError: Expected formula, got string", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - concat" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "actual": { + "type": "formula", + "formula": "=A4&\" \"&A5" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - concat" + } + }, + "formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "error": "Expected formula, got number" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "formulas", + "operation": "read", + "test_case_id": "formula_cross_sheet", + "sheet": "formulas", + "cell": "B5" + }, + "adapter_message": "RuntimeError: Expected formula, got number", + "probable_cause": "Adapter could not return a comparable value for this assertion." + } + ], + "importance": "basic", + "label": "Formula - cross sheet" + }, + "write": { + "passed": true, + "expected": { + "type": "formula", + "formula": "='References'!B2" + }, + "actual": { + "type": "formula", + "formula": "='References'!B2" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Formula - cross sheet" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "bold": { + "read": { + "passed": true, + "expected": { + "bold": true + }, + "actual": { + "bold": true, + "font_name": "Aptos Narrow", + "font_size": 12.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold" + }, + "write": { + "passed": true, + "expected": { + "bold": true + }, + "actual": { + "bold": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "read": { + "passed": true, + "expected": { + "italic": true + }, + "actual": { + "italic": true, + "font_name": "Aptos Narrow", + "font_size": 12.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Italic" + }, + "write": { + "passed": true, + "expected": { + "italic": true + }, + "actual": { + "italic": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "read": { + "passed": true, + "expected": { + "underline": "single" + }, + "actual": { + "underline": "single", + "font_name": "Aptos Narrow", + "font_size": 12.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - single" + }, + "write": { + "passed": true, + "expected": { + "underline": "single" + }, + "actual": { + "underline": "single" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "read": { + "passed": true, + "expected": { + "underline": "double" + }, + "actual": { + "underline": "double", + "font_name": "Aptos Narrow", + "font_size": 12.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - double" + }, + "write": { + "passed": true, + "expected": { + "underline": "double" + }, + "actual": { + "underline": "double" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "read": { + "passed": true, + "expected": { + "strikethrough": true + }, + "actual": { + "strikethrough": true, + "font_name": "Aptos Narrow", + "font_size": 12.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Strikethrough" + }, + "write": { + "passed": true, + "expected": { + "strikethrough": true + }, + "actual": { + "strikethrough": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "read": { + "passed": true, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "bold": true, + "italic": true, + "font_name": "Aptos Narrow", + "font_size": 12.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold + Italic" + }, + "write": { + "passed": true, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "bold": true, + "italic": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "read": { + "passed": true, + "expected": { + "font_size": 8 + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 8" + }, + "write": { + "passed": true, + "expected": { + "font_size": 8 + }, + "actual": { + "font_size": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "read": { + "passed": true, + "expected": { + "font_size": 14 + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 14.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 14" + }, + "write": { + "passed": true, + "expected": { + "font_size": 14 + }, + "actual": { + "font_size": 14.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "read": { + "passed": true, + "expected": { + "font_size": 24 + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 24.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 24" + }, + "write": { + "passed": true, + "expected": { + "font_size": 24 + }, + "actual": { + "font_size": 24.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "read": { + "passed": true, + "expected": { + "font_size": 36 + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 36.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 36" + }, + "write": { + "passed": true, + "expected": { + "font_size": 36 + }, + "actual": { + "font_size": 36.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "read": { + "passed": true, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Arial", + "font_size": 12.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Arial" + }, + "write": { + "passed": true, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Arial" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "read": { + "passed": true, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Times New Roman", + "font_size": 12.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Times New Roman" + }, + "write": { + "passed": true, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Times New Roman" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "read": { + "passed": true, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Courier New", + "font_size": 12.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Courier New" + }, + "write": { + "passed": true, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Courier New" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "read": { + "passed": true, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - red" + }, + "write": { + "passed": true, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "read": { + "passed": true, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - blue" + }, + "write": { + "passed": true, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "read": { + "passed": true, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - green" + }, + "write": { + "passed": true, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "read": { + "passed": true, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + }, + "write": { + "passed": true, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "read": { + "passed": true, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "bold": true, + "font_name": "Aptos Narrow", + "font_size": 16.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + }, + "write": { + "passed": true, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "bold": true, + "font_size": 16.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "bold": { + "write": { + "passed": true, + "expected": { + "bold": true + }, + "actual": { + "bold": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "write": { + "passed": true, + "expected": { + "italic": true + }, + "actual": { + "italic": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "write": { + "passed": true, + "expected": { + "underline": "single" + }, + "actual": { + "underline": "single", + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "write": { + "passed": true, + "expected": { + "underline": "double" + }, + "actual": { + "underline": "double", + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "write": { + "passed": true, + "expected": { + "strikethrough": true + }, + "actual": { + "strikethrough": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "write": { + "passed": true, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "bold": true, + "italic": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "write": { + "passed": true, + "expected": { + "font_size": 8 + }, + "actual": { + "font_name": "Calibri", + "font_size": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "write": { + "passed": true, + "expected": { + "font_size": 14 + }, + "actual": { + "font_name": "Calibri", + "font_size": 14.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "write": { + "passed": true, + "expected": { + "font_size": 24 + }, + "actual": { + "font_name": "Calibri", + "font_size": 24.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "write": { + "passed": true, + "expected": { + "font_size": 36 + }, + "actual": { + "font_name": "Calibri", + "font_size": 36.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "write": { + "passed": true, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Arial", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "write": { + "passed": true, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Times New Roman", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "write": { + "passed": true, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Courier New", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "write": { + "passed": true, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "write": { + "passed": true, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "write": { + "passed": true, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "write": { + "passed": true, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "write": { + "passed": true, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "bold": true, + "font_name": "Calibri", + "font_size": 16.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "bold": { + "read": { + "passed": false, + "expected": { + "bold": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold", + "sheet": "text_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "read": { + "passed": false, + "expected": { + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "italic", + "sheet": "text_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "read": { + "passed": false, + "expected": { + "underline": "single" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_single", + "sheet": "text_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'single'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "read": { + "passed": false, + "expected": { + "underline": "double" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_double", + "sheet": "text_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'double'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "read": { + "passed": false, + "expected": { + "strikethrough": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "strikethrough", + "sheet": "text_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'strikethrough': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "read": { + "passed": false, + "expected": { + "bold": true, + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold_italic", + "sheet": "text_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "read": { + "passed": false, + "expected": { + "font_size": 8 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_8", + "sheet": "text_formatting", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 8}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "read": { + "passed": false, + "expected": { + "font_size": 14 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_14", + "sheet": "text_formatting", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 14}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "read": { + "passed": false, + "expected": { + "font_size": 24 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_24", + "sheet": "text_formatting", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 24}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "read": { + "passed": false, + "expected": { + "font_size": 36 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_36", + "sheet": "text_formatting", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 36}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "read": { + "passed": false, + "expected": { + "font_name": "Arial" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_arial", + "sheet": "text_formatting", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "read": { + "passed": false, + "expected": { + "font_name": "Times New Roman" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_times", + "sheet": "text_formatting", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "read": { + "passed": false, + "expected": { + "font_name": "Courier New" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_courier", + "sheet": "text_formatting", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_red", + "sheet": "text_formatting", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "font_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "text_formatting", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "read": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "read": { + "passed": false, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "combined", + "sheet": "text_formatting", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "bold": { + "read": { + "passed": true, + "expected": { + "bold": true + }, + "actual": { + "bold": true, + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold" + }, + "write": { + "passed": true, + "expected": { + "bold": true + }, + "actual": { + "bold": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "read": { + "passed": true, + "expected": { + "italic": true + }, + "actual": { + "italic": true, + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Italic" + }, + "write": { + "passed": true, + "expected": { + "italic": true + }, + "actual": { + "italic": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "read": { + "passed": true, + "expected": { + "underline": "single" + }, + "actual": { + "underline": "single", + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - single" + }, + "write": { + "passed": true, + "expected": { + "underline": "single" + }, + "actual": { + "underline": "single" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "read": { + "passed": true, + "expected": { + "underline": "double" + }, + "actual": { + "underline": "double", + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - double" + }, + "write": { + "passed": true, + "expected": { + "underline": "double" + }, + "actual": { + "underline": "double" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "read": { + "passed": true, + "expected": { + "strikethrough": true + }, + "actual": { + "strikethrough": true, + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Strikethrough" + }, + "write": { + "passed": true, + "expected": { + "strikethrough": true + }, + "actual": { + "strikethrough": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "read": { + "passed": true, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "bold": true, + "italic": true, + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold + Italic" + }, + "write": { + "passed": true, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "bold": true, + "italic": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "read": { + "passed": true, + "expected": { + "font_size": 8 + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 8.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 8" + }, + "write": { + "passed": true, + "expected": { + "font_size": 8 + }, + "actual": { + "font_size": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "read": { + "passed": true, + "expected": { + "font_size": 14 + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 14.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 14" + }, + "write": { + "passed": true, + "expected": { + "font_size": 14 + }, + "actual": { + "font_size": 14.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "read": { + "passed": true, + "expected": { + "font_size": 24 + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 24.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 24" + }, + "write": { + "passed": true, + "expected": { + "font_size": 24 + }, + "actual": { + "font_size": 24.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "read": { + "passed": true, + "expected": { + "font_size": 36 + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 36.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 36" + }, + "write": { + "passed": true, + "expected": { + "font_size": 36 + }, + "actual": { + "font_size": 36.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "read": { + "passed": true, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Arial", + "font_size": 12.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Arial" + }, + "write": { + "passed": true, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Arial" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "read": { + "passed": true, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Times New Roman", + "font_size": 12.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Times New Roman" + }, + "write": { + "passed": true, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Times New Roman" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "read": { + "passed": true, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Courier New", + "font_size": 12.0, + "font_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Courier New" + }, + "write": { + "passed": true, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Courier New" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "read": { + "passed": true, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - red" + }, + "write": { + "passed": true, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "read": { + "passed": true, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - blue" + }, + "write": { + "passed": true, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "read": { + "passed": true, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - green" + }, + "write": { + "passed": true, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "read": { + "passed": true, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_name": "Aptos Narrow", + "font_size": 12.0, + "font_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + }, + "write": { + "passed": true, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "read": { + "passed": true, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "bold": true, + "font_name": "Aptos Narrow", + "font_size": 16.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + }, + "write": { + "passed": true, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "bold": true, + "font_size": 16.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "bold": { + "read": { + "passed": false, + "expected": { + "bold": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold", + "sheet": "text_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold" + }, + "write": { + "passed": false, + "expected": { + "bold": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "bold", + "sheet": "text_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "read": { + "passed": false, + "expected": { + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "italic", + "sheet": "text_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Italic" + }, + "write": { + "passed": false, + "expected": { + "italic": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "italic", + "sheet": "text_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "read": { + "passed": false, + "expected": { + "underline": "single" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_single", + "sheet": "text_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'single'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - single" + }, + "write": { + "passed": false, + "expected": { + "underline": "single" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "underline_single", + "sheet": "text_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'single'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "read": { + "passed": false, + "expected": { + "underline": "double" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_double", + "sheet": "text_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'double'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - double" + }, + "write": { + "passed": false, + "expected": { + "underline": "double" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "underline_double", + "sheet": "text_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'double'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "read": { + "passed": false, + "expected": { + "strikethrough": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "strikethrough", + "sheet": "text_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'strikethrough': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Strikethrough" + }, + "write": { + "passed": false, + "expected": { + "strikethrough": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "strikethrough", + "sheet": "text_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'strikethrough': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "read": { + "passed": false, + "expected": { + "bold": true, + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold_italic", + "sheet": "text_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold + Italic" + }, + "write": { + "passed": false, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "bold_italic", + "sheet": "text_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "read": { + "passed": false, + "expected": { + "font_size": 8 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_8", + "sheet": "text_formatting", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 8}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 8" + }, + "write": { + "passed": false, + "expected": { + "font_size": 8 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_8", + "sheet": "text_formatting", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 8}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "read": { + "passed": false, + "expected": { + "font_size": 14 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_14", + "sheet": "text_formatting", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 14}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 14" + }, + "write": { + "passed": false, + "expected": { + "font_size": 14 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_14", + "sheet": "text_formatting", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 14}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "read": { + "passed": false, + "expected": { + "font_size": 24 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_24", + "sheet": "text_formatting", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 24}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 24" + }, + "write": { + "passed": false, + "expected": { + "font_size": 24 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_24", + "sheet": "text_formatting", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 24}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "read": { + "passed": false, + "expected": { + "font_size": 36 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_36", + "sheet": "text_formatting", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 36}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 36" + }, + "write": { + "passed": false, + "expected": { + "font_size": 36 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_36", + "sheet": "text_formatting", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 36}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "read": { + "passed": false, + "expected": { + "font_name": "Arial" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_arial", + "sheet": "text_formatting", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Arial" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_arial", + "sheet": "text_formatting", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "read": { + "passed": false, + "expected": { + "font_name": "Times New Roman" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_times", + "sheet": "text_formatting", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Times New Roman" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_times", + "sheet": "text_formatting", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "read": { + "passed": false, + "expected": { + "font_name": "Courier New" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_courier", + "sheet": "text_formatting", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Courier New" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_courier", + "sheet": "text_formatting", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_red", + "sheet": "text_formatting", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - red" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_red", + "sheet": "text_formatting", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "font_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "text_formatting", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - blue" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_blue", + "sheet": "text_formatting", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "read": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "read": { + "passed": false, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "combined", + "sheet": "text_formatting", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + }, + "write": { + "passed": false, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "combined", + "sheet": "text_formatting", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "text_formatting", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "bold": { + "read": { + "passed": false, + "expected": { + "bold": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold", + "sheet": "text_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold" + }, + "write": { + "passed": false, + "expected": { + "bold": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "bold", + "sheet": "text_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "read": { + "passed": false, + "expected": { + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "italic", + "sheet": "text_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Italic" + }, + "write": { + "passed": false, + "expected": { + "italic": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "italic", + "sheet": "text_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "read": { + "passed": false, + "expected": { + "underline": "single" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_single", + "sheet": "text_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'single'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - single" + }, + "write": { + "passed": false, + "expected": { + "underline": "single" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "underline_single", + "sheet": "text_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'single'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "read": { + "passed": false, + "expected": { + "underline": "double" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_double", + "sheet": "text_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'double'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - double" + }, + "write": { + "passed": false, + "expected": { + "underline": "double" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "underline_double", + "sheet": "text_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'double'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "read": { + "passed": false, + "expected": { + "strikethrough": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "strikethrough", + "sheet": "text_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'strikethrough': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Strikethrough" + }, + "write": { + "passed": false, + "expected": { + "strikethrough": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "strikethrough", + "sheet": "text_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'strikethrough': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "read": { + "passed": false, + "expected": { + "bold": true, + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold_italic", + "sheet": "text_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold + Italic" + }, + "write": { + "passed": false, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "bold_italic", + "sheet": "text_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "read": { + "passed": false, + "expected": { + "font_size": 8 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_8", + "sheet": "text_formatting", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 8}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 8" + }, + "write": { + "passed": false, + "expected": { + "font_size": 8 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_8", + "sheet": "text_formatting", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 8}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "read": { + "passed": false, + "expected": { + "font_size": 14 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_14", + "sheet": "text_formatting", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 14}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 14" + }, + "write": { + "passed": false, + "expected": { + "font_size": 14 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_14", + "sheet": "text_formatting", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 14}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "read": { + "passed": false, + "expected": { + "font_size": 24 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_24", + "sheet": "text_formatting", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 24}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 24" + }, + "write": { + "passed": false, + "expected": { + "font_size": 24 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_24", + "sheet": "text_formatting", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 24}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "read": { + "passed": false, + "expected": { + "font_size": 36 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_36", + "sheet": "text_formatting", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 36}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 36" + }, + "write": { + "passed": false, + "expected": { + "font_size": 36 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_36", + "sheet": "text_formatting", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 36}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "read": { + "passed": false, + "expected": { + "font_name": "Arial" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_arial", + "sheet": "text_formatting", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Arial" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_arial", + "sheet": "text_formatting", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "read": { + "passed": false, + "expected": { + "font_name": "Times New Roman" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_times", + "sheet": "text_formatting", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Times New Roman" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_times", + "sheet": "text_formatting", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "read": { + "passed": false, + "expected": { + "font_name": "Courier New" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_courier", + "sheet": "text_formatting", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Courier New" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_courier", + "sheet": "text_formatting", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_red", + "sheet": "text_formatting", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - red" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_red", + "sheet": "text_formatting", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "font_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "text_formatting", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - blue" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_blue", + "sheet": "text_formatting", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "read": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "read": { + "passed": false, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "combined", + "sheet": "text_formatting", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + }, + "write": { + "passed": false, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "combined", + "sheet": "text_formatting", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "xlwt", + "scores": { + "read": null, + "write": 1 + }, + "test_cases": { + "bold": { + "write": { + "passed": true, + "expected": { + "bold": true + }, + "actual": { + "bold": true, + "font_name": "Arial", + "font_size": 10.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "write": { + "passed": true, + "expected": { + "italic": true + }, + "actual": { + "italic": true, + "font_name": "Arial", + "font_size": 10.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "write": { + "passed": true, + "expected": { + "underline": "single" + }, + "actual": { + "underline": "single", + "font_name": "Arial", + "font_size": 10.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "write": { + "passed": true, + "expected": { + "underline": "double" + }, + "actual": { + "underline": "double", + "font_name": "Arial", + "font_size": 10.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "write": { + "passed": true, + "expected": { + "strikethrough": true + }, + "actual": { + "strikethrough": true, + "font_name": "Arial", + "font_size": 10.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "write": { + "passed": true, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "bold": true, + "italic": true, + "font_name": "Arial", + "font_size": 10.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "write": { + "passed": true, + "expected": { + "font_size": 8 + }, + "actual": { + "font_name": "Arial", + "font_size": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "write": { + "passed": true, + "expected": { + "font_size": 14 + }, + "actual": { + "font_name": "Arial", + "font_size": 14.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "write": { + "passed": true, + "expected": { + "font_size": 24 + }, + "actual": { + "font_name": "Arial", + "font_size": 24.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "write": { + "passed": true, + "expected": { + "font_size": 36 + }, + "actual": { + "font_name": "Arial", + "font_size": 36.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "write": { + "passed": true, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Arial", + "font_size": 10.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "write": { + "passed": true, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Times New Roman", + "font_size": 10.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "write": { + "passed": true, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Courier New", + "font_size": 10.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "write": { + "passed": true, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Arial", + "font_size": 10.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "write": { + "passed": true, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_name": "Arial", + "font_size": 10.0, + "font_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "write": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_name": "Arial", + "font_size": 10.0, + "font_color": "#008000" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={'font_name': 'Arial', 'font_size': 10.0, 'font_color': '#008000'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "write": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_name": "Arial", + "font_size": 10.0, + "font_color": "#993300" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={'font_name': 'Arial', 'font_size': 10.0, 'font_color': '#993300'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "write": { + "passed": true, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "bold": true, + "font_name": "Arial", + "font_size": 16.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "bold": { + "read": { + "passed": false, + "expected": { + "bold": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold", + "sheet": "text_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold" + }, + "write": { + "passed": false, + "expected": { + "bold": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "bold", + "sheet": "text_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "read": { + "passed": false, + "expected": { + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "italic", + "sheet": "text_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Italic" + }, + "write": { + "passed": false, + "expected": { + "italic": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "italic", + "sheet": "text_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "read": { + "passed": false, + "expected": { + "underline": "single" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_single", + "sheet": "text_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'single'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - single" + }, + "write": { + "passed": false, + "expected": { + "underline": "single" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "underline_single", + "sheet": "text_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'single'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "read": { + "passed": false, + "expected": { + "underline": "double" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_double", + "sheet": "text_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'double'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - double" + }, + "write": { + "passed": false, + "expected": { + "underline": "double" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "underline_double", + "sheet": "text_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'double'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "read": { + "passed": false, + "expected": { + "strikethrough": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "strikethrough", + "sheet": "text_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'strikethrough': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Strikethrough" + }, + "write": { + "passed": false, + "expected": { + "strikethrough": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "strikethrough", + "sheet": "text_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'strikethrough': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "read": { + "passed": false, + "expected": { + "bold": true, + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold_italic", + "sheet": "text_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold + Italic" + }, + "write": { + "passed": false, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "bold_italic", + "sheet": "text_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "read": { + "passed": false, + "expected": { + "font_size": 8 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_8", + "sheet": "text_formatting", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 8}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 8" + }, + "write": { + "passed": false, + "expected": { + "font_size": 8 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_8", + "sheet": "text_formatting", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 8}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "read": { + "passed": false, + "expected": { + "font_size": 14 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_14", + "sheet": "text_formatting", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 14}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 14" + }, + "write": { + "passed": false, + "expected": { + "font_size": 14 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_14", + "sheet": "text_formatting", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 14}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "read": { + "passed": false, + "expected": { + "font_size": 24 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_24", + "sheet": "text_formatting", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 24}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 24" + }, + "write": { + "passed": false, + "expected": { + "font_size": 24 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_24", + "sheet": "text_formatting", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 24}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "read": { + "passed": false, + "expected": { + "font_size": 36 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_36", + "sheet": "text_formatting", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 36}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 36" + }, + "write": { + "passed": false, + "expected": { + "font_size": 36 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_36", + "sheet": "text_formatting", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 36}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "read": { + "passed": false, + "expected": { + "font_name": "Arial" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_arial", + "sheet": "text_formatting", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Arial" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_arial", + "sheet": "text_formatting", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "read": { + "passed": false, + "expected": { + "font_name": "Times New Roman" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_times", + "sheet": "text_formatting", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Times New Roman" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_times", + "sheet": "text_formatting", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "read": { + "passed": false, + "expected": { + "font_name": "Courier New" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_courier", + "sheet": "text_formatting", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Courier New" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_courier", + "sheet": "text_formatting", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_red", + "sheet": "text_formatting", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - red" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_red", + "sheet": "text_formatting", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "font_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "text_formatting", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - blue" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_blue", + "sheet": "text_formatting", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "read": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "read": { + "passed": false, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "combined", + "sheet": "text_formatting", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + }, + "write": { + "passed": false, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "combined", + "sheet": "text_formatting", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "bold": { + "write": { + "passed": true, + "expected": { + "bold": true + }, + "actual": { + "bold": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "write": { + "passed": true, + "expected": { + "italic": true + }, + "actual": { + "italic": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "write": { + "passed": true, + "expected": { + "underline": "single" + }, + "actual": { + "underline": "single", + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "write": { + "passed": true, + "expected": { + "underline": "double" + }, + "actual": { + "underline": "double", + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "write": { + "passed": true, + "expected": { + "strikethrough": true + }, + "actual": { + "strikethrough": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "write": { + "passed": true, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "bold": true, + "italic": true, + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "write": { + "passed": true, + "expected": { + "font_size": 8 + }, + "actual": { + "font_name": "Calibri", + "font_size": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "write": { + "passed": true, + "expected": { + "font_size": 14 + }, + "actual": { + "font_name": "Calibri", + "font_size": 14.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "write": { + "passed": true, + "expected": { + "font_size": 24 + }, + "actual": { + "font_name": "Calibri", + "font_size": 24.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "write": { + "passed": true, + "expected": { + "font_size": 36 + }, + "actual": { + "font_name": "Calibri", + "font_size": 36.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "write": { + "passed": true, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Arial", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "write": { + "passed": true, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Times New Roman", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "write": { + "passed": true, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Courier New", + "font_size": 11.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "write": { + "passed": true, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "write": { + "passed": true, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "write": { + "passed": true, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "write": { + "passed": true, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0, + "font_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "write": { + "passed": true, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "bold": true, + "font_name": "Calibri", + "font_size": 16.0, + "font_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "bold": { + "read": { + "passed": false, + "expected": { + "bold": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold", + "sheet": "text_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "read": { + "passed": false, + "expected": { + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "italic", + "sheet": "text_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "read": { + "passed": false, + "expected": { + "underline": "single" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_single", + "sheet": "text_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'single'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "read": { + "passed": false, + "expected": { + "underline": "double" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_double", + "sheet": "text_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'double'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "read": { + "passed": false, + "expected": { + "strikethrough": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "strikethrough", + "sheet": "text_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'strikethrough': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "read": { + "passed": false, + "expected": { + "bold": true, + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold_italic", + "sheet": "text_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "read": { + "passed": false, + "expected": { + "font_size": 8 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_8", + "sheet": "text_formatting", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 8}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "read": { + "passed": false, + "expected": { + "font_size": 14 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_14", + "sheet": "text_formatting", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 14}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "read": { + "passed": false, + "expected": { + "font_size": 24 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_24", + "sheet": "text_formatting", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 24}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "read": { + "passed": false, + "expected": { + "font_size": 36 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_36", + "sheet": "text_formatting", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 36}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "read": { + "passed": false, + "expected": { + "font_name": "Arial" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_arial", + "sheet": "text_formatting", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "read": { + "passed": false, + "expected": { + "font_name": "Times New Roman" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_times", + "sheet": "text_formatting", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "read": { + "passed": false, + "expected": { + "font_name": "Courier New" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_courier", + "sheet": "text_formatting", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_red", + "sheet": "text_formatting", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "font_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "text_formatting", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "read": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "read": { + "passed": false, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "combined", + "sheet": "text_formatting", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "bold": { + "read": { + "passed": false, + "expected": { + "bold": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold", + "sheet": "text_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "read": { + "passed": false, + "expected": { + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "italic", + "sheet": "text_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "read": { + "passed": false, + "expected": { + "underline": "single" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_single", + "sheet": "text_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'single'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "read": { + "passed": false, + "expected": { + "underline": "double" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_double", + "sheet": "text_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'double'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "read": { + "passed": false, + "expected": { + "strikethrough": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "strikethrough", + "sheet": "text_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'strikethrough': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "read": { + "passed": false, + "expected": { + "bold": true, + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold_italic", + "sheet": "text_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "read": { + "passed": false, + "expected": { + "font_size": 8 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_8", + "sheet": "text_formatting", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 8}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "read": { + "passed": false, + "expected": { + "font_size": 14 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_14", + "sheet": "text_formatting", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 14}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "read": { + "passed": false, + "expected": { + "font_size": 24 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_24", + "sheet": "text_formatting", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 24}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "read": { + "passed": false, + "expected": { + "font_size": 36 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_36", + "sheet": "text_formatting", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 36}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "read": { + "passed": false, + "expected": { + "font_name": "Arial" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_arial", + "sheet": "text_formatting", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "read": { + "passed": false, + "expected": { + "font_name": "Times New Roman" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_times", + "sheet": "text_formatting", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "read": { + "passed": false, + "expected": { + "font_name": "Courier New" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_courier", + "sheet": "text_formatting", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_red", + "sheet": "text_formatting", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "font_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "text_formatting", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "read": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "read": { + "passed": false, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "combined", + "sheet": "text_formatting", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "text_formatting", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "bold": { + "read": { + "passed": false, + "expected": { + "bold": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold", + "sheet": "text_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold" + }, + "write": { + "passed": false, + "expected": { + "bold": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "bold", + "sheet": "text_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold" + } + }, + "italic": { + "read": { + "passed": false, + "expected": { + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "italic", + "sheet": "text_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Italic" + }, + "write": { + "passed": false, + "expected": { + "italic": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "italic", + "sheet": "text_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Italic" + } + }, + "underline_single": { + "read": { + "passed": false, + "expected": { + "underline": "single" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_single", + "sheet": "text_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'single'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - single" + }, + "write": { + "passed": false, + "expected": { + "underline": "single" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "underline_single", + "sheet": "text_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'single'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - single" + } + }, + "underline_double": { + "read": { + "passed": false, + "expected": { + "underline": "double" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "underline_double", + "sheet": "text_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'double'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - double" + }, + "write": { + "passed": false, + "expected": { + "underline": "double" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "underline_double", + "sheet": "text_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'underline': 'double'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Underline - double" + } + }, + "strikethrough": { + "read": { + "passed": false, + "expected": { + "strikethrough": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "strikethrough", + "sheet": "text_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'strikethrough': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Strikethrough" + }, + "write": { + "passed": false, + "expected": { + "strikethrough": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "strikethrough", + "sheet": "text_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'strikethrough': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Strikethrough" + } + }, + "bold_italic": { + "read": { + "passed": false, + "expected": { + "bold": true, + "italic": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "bold_italic", + "sheet": "text_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold + Italic" + }, + "write": { + "passed": false, + "expected": { + "bold": true, + "italic": true + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "bold_italic", + "sheet": "text_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'italic': True}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Bold + Italic" + } + }, + "font_size_8": { + "read": { + "passed": false, + "expected": { + "font_size": 8 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_8", + "sheet": "text_formatting", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 8}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 8" + }, + "write": { + "passed": false, + "expected": { + "font_size": 8 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_8", + "sheet": "text_formatting", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 8}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 8" + } + }, + "font_size_14": { + "read": { + "passed": false, + "expected": { + "font_size": 14 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_14", + "sheet": "text_formatting", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 14}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 14" + }, + "write": { + "passed": false, + "expected": { + "font_size": 14 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_14", + "sheet": "text_formatting", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 14}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 14" + } + }, + "font_size_24": { + "read": { + "passed": false, + "expected": { + "font_size": 24 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_24", + "sheet": "text_formatting", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 24}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 24" + }, + "write": { + "passed": false, + "expected": { + "font_size": 24 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_24", + "sheet": "text_formatting", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 24}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 24" + } + }, + "font_size_36": { + "read": { + "passed": false, + "expected": { + "font_size": 36 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_size_36", + "sheet": "text_formatting", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 36}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 36" + }, + "write": { + "passed": false, + "expected": { + "font_size": 36 + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_size_36", + "sheet": "text_formatting", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_size': 36}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font size 36" + } + }, + "font_arial": { + "read": { + "passed": false, + "expected": { + "font_name": "Arial" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_arial", + "sheet": "text_formatting", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Arial" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Arial" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_arial", + "sheet": "text_formatting", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Arial'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Arial" + } + }, + "font_times": { + "read": { + "passed": false, + "expected": { + "font_name": "Times New Roman" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_times", + "sheet": "text_formatting", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Times New Roman" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Times New Roman" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_times", + "sheet": "text_formatting", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Times New Roman'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Times New Roman" + } + }, + "font_courier": { + "read": { + "passed": false, + "expected": { + "font_name": "Courier New" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "font_courier", + "sheet": "text_formatting", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Courier New" + }, + "write": { + "passed": false, + "expected": { + "font_name": "Courier New" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "font_courier", + "sheet": "text_formatting", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_name': 'Courier New'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font - Courier New" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_red", + "sheet": "text_formatting", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - red" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_red", + "sheet": "text_formatting", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - red" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "font_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "text_formatting", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - blue" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#0000FF" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_blue", + "sheet": "text_formatting", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#0000FF'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - blue" + } + }, + "color_green": { + "read": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#00FF00" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_green", + "sheet": "text_formatting", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#00FF00'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - green" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "font_color": "#8B4513" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "color_custom", + "sheet": "text_formatting", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'font_color': '#8B4513'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Font color - custom (#8B4513)" + } + }, + "combined": { + "read": { + "passed": false, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "read", + "test_case_id": "combined", + "sheet": "text_formatting", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + }, + "write": { + "passed": false, + "expected": { + "bold": true, + "font_size": 16, + "font_color": "#FF0000" + }, + "actual": { + "font_name": "Calibri", + "font_size": 11.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "text_formatting", + "operation": "write", + "test_case_id": "combined", + "sheet": "text_formatting", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'bold': True, 'font_size': 16, 'font_color': '#FF0000'}, actual={'font_name': 'Calibri', 'font_size': 11.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Combined - bold, 16pt, red" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "bg_red": { + "read": { + "passed": true, + "expected": { + "bg_color": "#FF0000" + }, + "actual": { + "bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - red" + }, + "write": { + "passed": true, + "expected": { + "bg_color": "#FF0000" + }, + "actual": { + "bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "read": { + "passed": true, + "expected": { + "bg_color": "#0000FF" + }, + "actual": { + "bg_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - blue" + }, + "write": { + "passed": true, + "expected": { + "bg_color": "#0000FF" + }, + "actual": { + "bg_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "read": { + "passed": true, + "expected": { + "bg_color": "#00FF00" + }, + "actual": { + "bg_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - green" + }, + "write": { + "passed": true, + "expected": { + "bg_color": "#00FF00" + }, + "actual": { + "bg_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "read": { + "passed": true, + "expected": { + "bg_color": "#8B4513" + }, + "actual": { + "bg_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - custom (#8B4513)" + }, + "write": { + "passed": true, + "expected": { + "bg_color": "#8B4513" + }, + "actual": { + "bg_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "bg_red": { + "write": { + "passed": true, + "expected": { + "bg_color": "#FF0000" + }, + "actual": { + "bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "write": { + "passed": true, + "expected": { + "bg_color": "#0000FF" + }, + "actual": { + "bg_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "write": { + "passed": true, + "expected": { + "bg_color": "#00FF00" + }, + "actual": { + "bg_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "write": { + "passed": true, + "expected": { + "bg_color": "#8B4513" + }, + "actual": { + "bg_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "bg_red": { + "read": { + "passed": false, + "expected": { + "bg_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_red", + "sheet": "background_colors", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "read": { + "passed": false, + "expected": { + "bg_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_blue", + "sheet": "background_colors", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "read": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "read": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "bg_red": { + "read": { + "passed": true, + "expected": { + "bg_color": "#FF0000" + }, + "actual": { + "bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - red" + }, + "write": { + "passed": true, + "expected": { + "bg_color": "#FF0000" + }, + "actual": { + "bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "read": { + "passed": true, + "expected": { + "bg_color": "#0000FF" + }, + "actual": { + "bg_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - blue" + }, + "write": { + "passed": true, + "expected": { + "bg_color": "#0000FF" + }, + "actual": { + "bg_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "read": { + "passed": true, + "expected": { + "bg_color": "#00FF00" + }, + "actual": { + "bg_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - green" + }, + "write": { + "passed": true, + "expected": { + "bg_color": "#00FF00" + }, + "actual": { + "bg_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "read": { + "passed": true, + "expected": { + "bg_color": "#8B4513" + }, + "actual": { + "bg_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - custom (#8B4513)" + }, + "write": { + "passed": true, + "expected": { + "bg_color": "#8B4513" + }, + "actual": { + "bg_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "bg_red": { + "read": { + "passed": false, + "expected": { + "bg_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_red", + "sheet": "background_colors", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - red" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_red", + "sheet": "background_colors", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "read": { + "passed": false, + "expected": { + "bg_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_blue", + "sheet": "background_colors", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - blue" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_blue", + "sheet": "background_colors", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "read": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "read": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "background_colors", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "bg_red": { + "read": { + "passed": false, + "expected": { + "bg_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_red", + "sheet": "background_colors", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - red" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_red", + "sheet": "background_colors", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "read": { + "passed": false, + "expected": { + "bg_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_blue", + "sheet": "background_colors", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - blue" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_blue", + "sheet": "background_colors", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "read": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "read": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "xlwt", + "scores": { + "read": null, + "write": 1 + }, + "test_cases": { + "bg_red": { + "write": { + "passed": true, + "expected": { + "bg_color": "#FF0000" + }, + "actual": { + "bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "write": { + "passed": true, + "expected": { + "bg_color": "#0000FF" + }, + "actual": { + "bg_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "write": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": { + "bg_color": "#008000" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={'bg_color': '#008000'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "write": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": { + "bg_color": "#993300" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={'bg_color': '#993300'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "bg_red": { + "read": { + "passed": false, + "expected": { + "bg_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_red", + "sheet": "background_colors", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - red" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_red", + "sheet": "background_colors", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "read": { + "passed": false, + "expected": { + "bg_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_blue", + "sheet": "background_colors", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - blue" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_blue", + "sheet": "background_colors", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "read": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "read": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "bg_red": { + "write": { + "passed": true, + "expected": { + "bg_color": "#FF0000" + }, + "actual": { + "bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "write": { + "passed": true, + "expected": { + "bg_color": "#0000FF" + }, + "actual": { + "bg_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "write": { + "passed": true, + "expected": { + "bg_color": "#00FF00" + }, + "actual": { + "bg_color": "#00FF00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "write": { + "passed": true, + "expected": { + "bg_color": "#8B4513" + }, + "actual": { + "bg_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "bg_red": { + "read": { + "passed": false, + "expected": { + "bg_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_red", + "sheet": "background_colors", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "read": { + "passed": false, + "expected": { + "bg_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_blue", + "sheet": "background_colors", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "read": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "read": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "bg_red": { + "read": { + "passed": false, + "expected": { + "bg_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_red", + "sheet": "background_colors", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "read": { + "passed": false, + "expected": { + "bg_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_blue", + "sheet": "background_colors", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "read": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "read": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "background_colors", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "bg_red": { + "read": { + "passed": false, + "expected": { + "bg_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_red", + "sheet": "background_colors", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - red" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_red", + "sheet": "background_colors", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - red" + } + }, + "bg_blue": { + "read": { + "passed": false, + "expected": { + "bg_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_blue", + "sheet": "background_colors", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - blue" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_blue", + "sheet": "background_colors", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - blue" + } + }, + "bg_green": { + "read": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#00FF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_green", + "sheet": "background_colors", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#00FF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - green" + } + }, + "bg_custom": { + "read": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "read", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "bg_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "background_colors", + "operation": "write", + "test_case_id": "bg_custom", + "sheet": "background_colors", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'bg_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Background - custom (#8B4513)" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "numfmt_currency": { + "read": { + "passed": true, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "$#,##0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - currency" + }, + "write": { + "passed": true, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "$#,##0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "read": { + "passed": true, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "0.00%" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - percent" + }, + "write": { + "passed": true, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "0.00%" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "read": { + "passed": true, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "yyyy-mm-dd" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - date" + }, + "write": { + "passed": true, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "yyyy-mm-dd" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "read": { + "passed": true, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "0.00E+00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - scientific" + }, + "write": { + "passed": true, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "0.00E+00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "read": { + "passed": true, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "\"USD\" 0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - custom text" + }, + "write": { + "passed": true, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "\"USD\" 0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "numfmt_currency": { + "write": { + "passed": true, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "$#,##0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "write": { + "passed": true, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "0.00%" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "write": { + "passed": true, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "yyyy-mm-dd" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "write": { + "passed": true, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "0.00E+00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "write": { + "passed": true, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "\"USD\" 0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "numfmt_currency": { + "read": { + "passed": false, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_currency", + "sheet": "number_formats", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00%" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_percent", + "sheet": "number_formats", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "read": { + "passed": false, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_date", + "sheet": "number_formats", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00E+00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_scientific", + "sheet": "number_formats", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "read": { + "passed": false, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_custom_text", + "sheet": "number_formats", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '\"USD\" 0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "numfmt_currency": { + "read": { + "passed": true, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "$#,##0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - currency" + }, + "write": { + "passed": true, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "$#,##0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "read": { + "passed": true, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "0.00%" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - percent" + }, + "write": { + "passed": true, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "0.00%" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "read": { + "passed": true, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "yyyy-mm-dd" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - date" + }, + "write": { + "passed": true, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "yyyy-mm-dd" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "read": { + "passed": true, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "0.00E+00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - scientific" + }, + "write": { + "passed": true, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "0.00E+00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "read": { + "passed": true, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "\"USD\" 0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - custom text" + }, + "write": { + "passed": true, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "\"USD\" 0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "numfmt_currency": { + "read": { + "passed": false, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_currency", + "sheet": "number_formats", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - currency" + }, + "write": { + "passed": false, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_currency", + "sheet": "number_formats", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00%" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_percent", + "sheet": "number_formats", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - percent" + }, + "write": { + "passed": false, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_percent", + "sheet": "number_formats", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "read": { + "passed": false, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_date", + "sheet": "number_formats", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - date" + }, + "write": { + "passed": false, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_date", + "sheet": "number_formats", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00E+00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_scientific", + "sheet": "number_formats", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - scientific" + }, + "write": { + "passed": false, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_scientific", + "sheet": "number_formats", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "read": { + "passed": false, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_custom_text", + "sheet": "number_formats", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '\"USD\" 0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - custom text" + }, + "write": { + "passed": false, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_custom_text", + "sheet": "number_formats", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '\"USD\" 0.00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "number_formats", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "numfmt_currency": { + "read": { + "passed": false, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_currency", + "sheet": "number_formats", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - currency" + }, + "write": { + "passed": false, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_currency", + "sheet": "number_formats", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00%" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_percent", + "sheet": "number_formats", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - percent" + }, + "write": { + "passed": false, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_percent", + "sheet": "number_formats", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "read": { + "passed": false, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_date", + "sheet": "number_formats", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - date" + }, + "write": { + "passed": false, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_date", + "sheet": "number_formats", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00E+00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_scientific", + "sheet": "number_formats", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - scientific" + }, + "write": { + "passed": false, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_scientific", + "sheet": "number_formats", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "read": { + "passed": false, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_custom_text", + "sheet": "number_formats", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '\"USD\" 0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - custom text" + }, + "write": { + "passed": false, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_custom_text", + "sheet": "number_formats", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '\"USD\" 0.00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "xlwt", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "numfmt_currency": { + "write": { + "passed": true, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "$#,##0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "write": { + "passed": true, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "0.00%" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "write": { + "passed": true, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "yyyy-mm-dd" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "write": { + "passed": true, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "0.00E+00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "write": { + "passed": true, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "\"USD\" 0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "numfmt_currency": { + "read": { + "passed": false, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_currency", + "sheet": "number_formats", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - currency" + }, + "write": { + "passed": false, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_currency", + "sheet": "number_formats", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00%" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_percent", + "sheet": "number_formats", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - percent" + }, + "write": { + "passed": false, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_percent", + "sheet": "number_formats", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "read": { + "passed": false, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_date", + "sheet": "number_formats", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - date" + }, + "write": { + "passed": false, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_date", + "sheet": "number_formats", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00E+00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_scientific", + "sheet": "number_formats", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - scientific" + }, + "write": { + "passed": false, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_scientific", + "sheet": "number_formats", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "read": { + "passed": false, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_custom_text", + "sheet": "number_formats", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '\"USD\" 0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - custom text" + }, + "write": { + "passed": false, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_custom_text", + "sheet": "number_formats", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '\"USD\" 0.00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "numfmt_currency": { + "write": { + "passed": true, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "$#,##0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "write": { + "passed": true, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "0.00%" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "write": { + "passed": true, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "yyyy-mm-dd" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "write": { + "passed": true, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "0.00E+00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "write": { + "passed": true, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "\"USD\" 0.00" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "numfmt_currency": { + "read": { + "passed": false, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_currency", + "sheet": "number_formats", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00%" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_percent", + "sheet": "number_formats", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "read": { + "passed": false, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_date", + "sheet": "number_formats", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00E+00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_scientific", + "sheet": "number_formats", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "read": { + "passed": false, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_custom_text", + "sheet": "number_formats", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '\"USD\" 0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "numfmt_currency": { + "read": { + "passed": false, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_currency", + "sheet": "number_formats", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00%" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_percent", + "sheet": "number_formats", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "read": { + "passed": false, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_date", + "sheet": "number_formats", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00E+00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_scientific", + "sheet": "number_formats", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "read": { + "passed": false, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_custom_text", + "sheet": "number_formats", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '\"USD\" 0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "number_formats", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "numfmt_currency": { + "read": { + "passed": false, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_currency", + "sheet": "number_formats", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - currency" + }, + "write": { + "passed": false, + "expected": { + "number_format": "$#,##0.00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_currency", + "sheet": "number_formats", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '$#,##0.00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - currency" + } + }, + "numfmt_percent": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00%" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_percent", + "sheet": "number_formats", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - percent" + }, + "write": { + "passed": false, + "expected": { + "number_format": "0.00%" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_percent", + "sheet": "number_formats", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00%'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - percent" + } + }, + "numfmt_date": { + "read": { + "passed": false, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_date", + "sheet": "number_formats", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - date" + }, + "write": { + "passed": false, + "expected": { + "number_format": "yyyy-mm-dd" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_date", + "sheet": "number_formats", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': 'yyyy-mm-dd'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - date" + } + }, + "numfmt_scientific": { + "read": { + "passed": false, + "expected": { + "number_format": "0.00E+00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_scientific", + "sheet": "number_formats", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - scientific" + }, + "write": { + "passed": false, + "expected": { + "number_format": "0.00E+00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_scientific", + "sheet": "number_formats", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '0.00E+00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - scientific" + } + }, + "numfmt_custom_text": { + "read": { + "passed": false, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "read", + "test_case_id": "numfmt_custom_text", + "sheet": "number_formats", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '\"USD\" 0.00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - custom text" + }, + "write": { + "passed": false, + "expected": { + "number_format": "\"USD\" 0.00" + }, + "actual": { + "number_format": "General" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "number_formats", + "operation": "write", + "test_case_id": "numfmt_custom_text", + "sheet": "number_formats", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'number_format': '\"USD\" 0.00'}, actual={'number_format': 'General'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Format - custom text" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "h_left": { + "read": { + "passed": true, + "expected": { + "h_align": "left" + }, + "actual": { + "h_align": "left" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - left" + }, + "write": { + "passed": true, + "expected": { + "h_align": "left" + }, + "actual": { + "h_align": "left" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "read": { + "passed": true, + "expected": { + "h_align": "center" + }, + "actual": { + "h_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": true, + "expected": { + "h_align": "center" + }, + "actual": { + "h_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "read": { + "passed": true, + "expected": { + "h_align": "right" + }, + "actual": { + "h_align": "right" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - right" + }, + "write": { + "passed": true, + "expected": { + "h_align": "right" + }, + "actual": { + "h_align": "right" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "read": { + "passed": true, + "expected": { + "v_align": "top" + }, + "actual": { + "v_align": "top" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - top" + }, + "write": { + "passed": true, + "expected": { + "v_align": "top" + }, + "actual": { + "v_align": "top" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "read": { + "passed": true, + "expected": { + "v_align": "center" + }, + "actual": { + "v_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": true, + "expected": { + "v_align": "center" + }, + "actual": { + "v_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "read": { + "passed": true, + "expected": { + "v_align": "bottom" + }, + "actual": { + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - bottom" + }, + "write": { + "passed": true, + "expected": { + "v_align": "bottom" + }, + "actual": { + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "read": { + "passed": true, + "expected": { + "wrap": true + }, + "actual": { + "wrap": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - wrap text" + }, + "write": { + "passed": true, + "expected": { + "wrap": true + }, + "actual": { + "wrap": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "read": { + "passed": true, + "expected": { + "rotation": 45 + }, + "actual": { + "rotation": 45 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - rotation 45" + }, + "write": { + "passed": true, + "expected": { + "rotation": 45 + }, + "actual": { + "rotation": 45 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "read": { + "passed": true, + "expected": { + "indent": 2 + }, + "actual": { + "indent": 2.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - indent 2" + }, + "write": { + "passed": true, + "expected": { + "indent": 2 + }, + "actual": { + "indent": 2.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 1 + }, + "test_cases": { + "h_left": { + "write": { + "passed": true, + "expected": { + "h_align": "left" + }, + "actual": { + "h_align": "left" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "write": { + "passed": true, + "expected": { + "h_align": "center" + }, + "actual": { + "h_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "write": { + "passed": true, + "expected": { + "h_align": "right" + }, + "actual": { + "h_align": "right" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "write": { + "passed": true, + "expected": { + "v_align": "top" + }, + "actual": { + "v_align": "top" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "write": { + "passed": true, + "expected": { + "v_align": "center" + }, + "actual": { + "v_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "write": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "write": { + "passed": true, + "expected": { + "wrap": true + }, + "actual": { + "wrap": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "write": { + "passed": true, + "expected": { + "rotation": 45 + }, + "actual": { + "rotation": 45 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "write": { + "passed": true, + "expected": { + "indent": 2 + }, + "actual": { + "h_align": "left", + "indent": 2.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "h_left": { + "read": { + "passed": false, + "expected": { + "h_align": "left" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_left", + "sheet": "alignment", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'left'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "read": { + "passed": false, + "expected": { + "h_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_center", + "sheet": "alignment", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "read": { + "passed": false, + "expected": { + "h_align": "right" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_right", + "sheet": "alignment", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'right'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "read": { + "passed": false, + "expected": { + "v_align": "top" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_top", + "sheet": "alignment", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'top'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "read": { + "passed": false, + "expected": { + "v_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_center", + "sheet": "alignment", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "read": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "read": { + "passed": false, + "expected": { + "wrap": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "wrap_text", + "sheet": "alignment", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'wrap': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "read": { + "passed": false, + "expected": { + "rotation": 45 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "rotation_45", + "sheet": "alignment", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'rotation': 45}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "read": { + "passed": false, + "expected": { + "indent": 2 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "indent_2", + "sheet": "alignment", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'indent': 2}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": "Known limitation: python-calamine alignment read is limited because its API does not expose style/alignment metadata." + }, + { + "feature": "alignment", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "h_left": { + "read": { + "passed": true, + "expected": { + "h_align": "left" + }, + "actual": { + "h_align": "left", + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - left" + }, + "write": { + "passed": true, + "expected": { + "h_align": "left" + }, + "actual": { + "h_align": "left" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "read": { + "passed": true, + "expected": { + "h_align": "center" + }, + "actual": { + "h_align": "center", + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": true, + "expected": { + "h_align": "center" + }, + "actual": { + "h_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "read": { + "passed": true, + "expected": { + "h_align": "right" + }, + "actual": { + "h_align": "right", + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - right" + }, + "write": { + "passed": true, + "expected": { + "h_align": "right" + }, + "actual": { + "h_align": "right" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "read": { + "passed": true, + "expected": { + "v_align": "top" + }, + "actual": { + "v_align": "top" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - top" + }, + "write": { + "passed": true, + "expected": { + "v_align": "top" + }, + "actual": { + "v_align": "top" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "read": { + "passed": true, + "expected": { + "v_align": "center" + }, + "actual": { + "v_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": true, + "expected": { + "v_align": "center" + }, + "actual": { + "v_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "read": { + "passed": true, + "expected": { + "v_align": "bottom" + }, + "actual": { + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - bottom" + }, + "write": { + "passed": true, + "expected": { + "v_align": "bottom" + }, + "actual": { + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "read": { + "passed": true, + "expected": { + "wrap": true + }, + "actual": { + "v_align": "bottom", + "wrap": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - wrap text" + }, + "write": { + "passed": true, + "expected": { + "wrap": true + }, + "actual": { + "wrap": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "read": { + "passed": true, + "expected": { + "rotation": 45 + }, + "actual": { + "v_align": "bottom", + "rotation": 45 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - rotation 45" + }, + "write": { + "passed": true, + "expected": { + "rotation": 45 + }, + "actual": { + "rotation": 45 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "read": { + "passed": true, + "expected": { + "indent": 2 + }, + "actual": { + "v_align": "bottom", + "indent": 2 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - indent 2" + }, + "write": { + "passed": true, + "expected": { + "indent": 2 + }, + "actual": { + "indent": 2.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "h_left": { + "read": { + "passed": false, + "expected": { + "h_align": "left" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_left", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Align - left" + }, + "write": { + "passed": false, + "expected": { + "h_align": "left" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_left", + "sheet": "alignment", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'left'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "read": { + "passed": false, + "expected": { + "h_align": "center" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_center", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": false, + "expected": { + "h_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_center", + "sheet": "alignment", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "read": { + "passed": false, + "expected": { + "h_align": "right" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_right", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Align - right" + }, + "write": { + "passed": false, + "expected": { + "h_align": "right" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_right", + "sheet": "alignment", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'right'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "read": { + "passed": false, + "expected": { + "v_align": "top" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_top", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Align - top" + }, + "write": { + "passed": false, + "expected": { + "v_align": "top" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_top", + "sheet": "alignment", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'top'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "read": { + "passed": false, + "expected": { + "v_align": "center" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_center", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": false, + "expected": { + "v_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_center", + "sheet": "alignment", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "read": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_bottom", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Align - bottom" + }, + "write": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "read": { + "passed": false, + "expected": { + "wrap": true + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "wrap_text", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Align - wrap text" + }, + "write": { + "passed": false, + "expected": { + "wrap": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "wrap_text", + "sheet": "alignment", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'wrap': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "read": { + "passed": false, + "expected": { + "rotation": 45 + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "rotation_45", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Align - rotation 45" + }, + "write": { + "passed": false, + "expected": { + "rotation": 45 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "rotation_45", + "sheet": "alignment", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'rotation': 45}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "read": { + "passed": false, + "expected": { + "indent": 2 + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "indent_2", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Align - indent 2" + }, + "write": { + "passed": false, + "expected": { + "indent": 2 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "indent_2", + "sheet": "alignment", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'indent': 2}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": "Known limitation: pylightxl alignment write is a no-op because the library does not support formatting writes." + }, + { + "feature": "alignment", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "alignment", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "h_left": { + "read": { + "passed": false, + "expected": { + "h_align": "left" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_left", + "sheet": "alignment", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'left'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - left" + }, + "write": { + "passed": false, + "expected": { + "h_align": "left" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_left", + "sheet": "alignment", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'left'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "read": { + "passed": false, + "expected": { + "h_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_center", + "sheet": "alignment", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": false, + "expected": { + "h_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_center", + "sheet": "alignment", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "read": { + "passed": false, + "expected": { + "h_align": "right" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_right", + "sheet": "alignment", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'right'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - right" + }, + "write": { + "passed": false, + "expected": { + "h_align": "right" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_right", + "sheet": "alignment", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'right'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "read": { + "passed": false, + "expected": { + "v_align": "top" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_top", + "sheet": "alignment", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'top'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - top" + }, + "write": { + "passed": false, + "expected": { + "v_align": "top" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_top", + "sheet": "alignment", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'top'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "read": { + "passed": false, + "expected": { + "v_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_center", + "sheet": "alignment", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": false, + "expected": { + "v_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_center", + "sheet": "alignment", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "read": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + }, + "write": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "read": { + "passed": false, + "expected": { + "wrap": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "wrap_text", + "sheet": "alignment", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'wrap': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - wrap text" + }, + "write": { + "passed": false, + "expected": { + "wrap": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "wrap_text", + "sheet": "alignment", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'wrap': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "read": { + "passed": false, + "expected": { + "rotation": 45 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "rotation_45", + "sheet": "alignment", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'rotation': 45}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - rotation 45" + }, + "write": { + "passed": false, + "expected": { + "rotation": 45 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "rotation_45", + "sheet": "alignment", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'rotation': 45}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "read": { + "passed": false, + "expected": { + "indent": 2 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "indent_2", + "sheet": "alignment", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'indent': 2}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - indent 2" + }, + "write": { + "passed": false, + "expected": { + "indent": 2 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "indent_2", + "sheet": "alignment", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'indent': 2}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "xlwt", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "h_left": { + "write": { + "passed": true, + "expected": { + "h_align": "left" + }, + "actual": { + "h_align": "left", + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "write": { + "passed": true, + "expected": { + "h_align": "center" + }, + "actual": { + "h_align": "center", + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "write": { + "passed": true, + "expected": { + "h_align": "right" + }, + "actual": { + "h_align": "right", + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "write": { + "passed": true, + "expected": { + "v_align": "top" + }, + "actual": { + "v_align": "top" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "write": { + "passed": true, + "expected": { + "v_align": "center" + }, + "actual": { + "v_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "write": { + "passed": true, + "expected": { + "v_align": "bottom" + }, + "actual": { + "v_align": "bottom" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "write": { + "passed": true, + "expected": { + "wrap": true + }, + "actual": { + "v_align": "bottom", + "wrap": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "write": { + "passed": true, + "expected": { + "rotation": 45 + }, + "actual": { + "v_align": "bottom", + "rotation": 45 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "write": { + "passed": true, + "expected": { + "indent": 2 + }, + "actual": { + "v_align": "bottom", + "indent": 2 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "h_left": { + "read": { + "passed": false, + "expected": { + "h_align": "left" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_left", + "sheet": "alignment", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'left'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - left" + }, + "write": { + "passed": false, + "expected": { + "h_align": "left" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_left", + "sheet": "alignment", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'left'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "read": { + "passed": false, + "expected": { + "h_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_center", + "sheet": "alignment", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": false, + "expected": { + "h_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_center", + "sheet": "alignment", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "read": { + "passed": false, + "expected": { + "h_align": "right" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_right", + "sheet": "alignment", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'right'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - right" + }, + "write": { + "passed": false, + "expected": { + "h_align": "right" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_right", + "sheet": "alignment", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'right'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "read": { + "passed": false, + "expected": { + "v_align": "top" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_top", + "sheet": "alignment", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'top'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - top" + }, + "write": { + "passed": false, + "expected": { + "v_align": "top" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_top", + "sheet": "alignment", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'top'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "read": { + "passed": false, + "expected": { + "v_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_center", + "sheet": "alignment", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": false, + "expected": { + "v_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_center", + "sheet": "alignment", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "read": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + }, + "write": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "read": { + "passed": false, + "expected": { + "wrap": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "wrap_text", + "sheet": "alignment", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'wrap': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - wrap text" + }, + "write": { + "passed": false, + "expected": { + "wrap": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "wrap_text", + "sheet": "alignment", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'wrap': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "read": { + "passed": false, + "expected": { + "rotation": 45 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "rotation_45", + "sheet": "alignment", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'rotation': 45}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - rotation 45" + }, + "write": { + "passed": false, + "expected": { + "rotation": 45 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "rotation_45", + "sheet": "alignment", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'rotation': 45}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "read": { + "passed": false, + "expected": { + "indent": 2 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "indent_2", + "sheet": "alignment", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'indent': 2}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - indent 2" + }, + "write": { + "passed": false, + "expected": { + "indent": 2 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "indent_2", + "sheet": "alignment", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'indent': 2}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 1 + }, + "test_cases": { + "h_left": { + "write": { + "passed": true, + "expected": { + "h_align": "left" + }, + "actual": { + "h_align": "left" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "write": { + "passed": true, + "expected": { + "h_align": "center" + }, + "actual": { + "h_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "write": { + "passed": true, + "expected": { + "h_align": "right" + }, + "actual": { + "h_align": "right" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "write": { + "passed": true, + "expected": { + "v_align": "top" + }, + "actual": { + "v_align": "top" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "write": { + "passed": true, + "expected": { + "v_align": "center" + }, + "actual": { + "v_align": "center" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "write": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "write": { + "passed": true, + "expected": { + "wrap": true + }, + "actual": { + "wrap": true + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "write": { + "passed": true, + "expected": { + "rotation": 45 + }, + "actual": { + "rotation": 45 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "write": { + "passed": true, + "expected": { + "indent": 2 + }, + "actual": { + "h_align": "left", + "indent": 2.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "h_left": { + "read": { + "passed": false, + "expected": { + "h_align": "left" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_left", + "sheet": "alignment", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'left'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "read": { + "passed": false, + "expected": { + "h_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_center", + "sheet": "alignment", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "read": { + "passed": false, + "expected": { + "h_align": "right" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_right", + "sheet": "alignment", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'right'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "read": { + "passed": false, + "expected": { + "v_align": "top" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_top", + "sheet": "alignment", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'top'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "read": { + "passed": false, + "expected": { + "v_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_center", + "sheet": "alignment", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "read": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "read": { + "passed": false, + "expected": { + "wrap": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "wrap_text", + "sheet": "alignment", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'wrap': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "read": { + "passed": false, + "expected": { + "rotation": 45 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "rotation_45", + "sheet": "alignment", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'rotation': 45}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "read": { + "passed": false, + "expected": { + "indent": 2 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "indent_2", + "sheet": "alignment", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'indent': 2}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "h_left": { + "read": { + "passed": false, + "expected": { + "h_align": "left" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_left", + "sheet": "alignment", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'left'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "read": { + "passed": false, + "expected": { + "h_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_center", + "sheet": "alignment", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "read": { + "passed": false, + "expected": { + "h_align": "right" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_right", + "sheet": "alignment", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'right'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "read": { + "passed": false, + "expected": { + "v_align": "top" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_top", + "sheet": "alignment", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'top'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "read": { + "passed": false, + "expected": { + "v_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_center", + "sheet": "alignment", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "read": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "read": { + "passed": false, + "expected": { + "wrap": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "wrap_text", + "sheet": "alignment", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'wrap': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "read": { + "passed": false, + "expected": { + "rotation": 45 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "rotation_45", + "sheet": "alignment", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'rotation': 45}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "read": { + "passed": false, + "expected": { + "indent": 2 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "indent_2", + "sheet": "alignment", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'indent': 2}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "alignment", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "h_left": { + "read": { + "passed": false, + "expected": { + "h_align": "left" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_left", + "sheet": "alignment", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'left'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - left" + }, + "write": { + "passed": false, + "expected": { + "h_align": "left" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_left", + "sheet": "alignment", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'left'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - left" + } + }, + "h_center": { + "read": { + "passed": false, + "expected": { + "h_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_center", + "sheet": "alignment", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": false, + "expected": { + "h_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_center", + "sheet": "alignment", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "h_right": { + "read": { + "passed": false, + "expected": { + "h_align": "right" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "h_right", + "sheet": "alignment", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'right'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - right" + }, + "write": { + "passed": false, + "expected": { + "h_align": "right" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "h_right", + "sheet": "alignment", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'h_align': 'right'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - right" + } + }, + "v_top": { + "read": { + "passed": false, + "expected": { + "v_align": "top" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_top", + "sheet": "alignment", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'top'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - top" + }, + "write": { + "passed": false, + "expected": { + "v_align": "top" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_top", + "sheet": "alignment", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'top'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - top" + } + }, + "v_center": { + "read": { + "passed": false, + "expected": { + "v_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_center", + "sheet": "alignment", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + }, + "write": { + "passed": false, + "expected": { + "v_align": "center" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_center", + "sheet": "alignment", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'center'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - center" + } + }, + "v_bottom": { + "read": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + }, + "write": { + "passed": false, + "expected": { + "v_align": "bottom" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "v_bottom", + "sheet": "alignment", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'v_align': 'bottom'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - bottom" + } + }, + "wrap_text": { + "read": { + "passed": false, + "expected": { + "wrap": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "wrap_text", + "sheet": "alignment", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'wrap': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - wrap text" + }, + "write": { + "passed": false, + "expected": { + "wrap": true + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "wrap_text", + "sheet": "alignment", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'wrap': True}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - wrap text" + } + }, + "rotation_45": { + "read": { + "passed": false, + "expected": { + "rotation": 45 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "rotation_45", + "sheet": "alignment", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'rotation': 45}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - rotation 45" + }, + "write": { + "passed": false, + "expected": { + "rotation": 45 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "rotation_45", + "sheet": "alignment", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'rotation': 45}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - rotation 45" + } + }, + "indent_2": { + "read": { + "passed": false, + "expected": { + "indent": 2 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "read", + "test_case_id": "indent_2", + "sheet": "alignment", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'indent': 2}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - indent 2" + }, + "write": { + "passed": false, + "expected": { + "indent": 2 + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "alignment", + "operation": "write", + "test_case_id": "indent_2", + "sheet": "alignment", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'indent': 2}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Align - indent 2" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "thin_all": { + "read": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": { + "border_style": "thin", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thin all edges" + }, + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": { + "border_style": "thin", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "read": { + "passed": true, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": { + "border_style": "medium", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - medium all edges" + }, + "write": { + "passed": true, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": { + "border_style": "medium", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "read": { + "passed": true, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": { + "border_style": "thick", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thick all edges" + }, + "write": { + "passed": true, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": { + "border_style": "thick", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "read": { + "passed": true, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": { + "border_style": "double", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - double line" + }, + "write": { + "passed": true, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": { + "border_style": "double", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "read": { + "passed": true, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dashed" + }, + "write": { + "passed": true, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "read": { + "passed": true, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": { + "border_style": "dotted", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dotted" + }, + "write": { + "passed": true, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": { + "border_style": "dotted", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "read": { + "passed": true, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot" + }, + "write": { + "passed": true, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "read": { + "passed": true, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot-dot" + }, + "write": { + "passed": true, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "read": { + "passed": true, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": { + "border_top": "thin", + "border_top_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - top only" + }, + "write": { + "passed": true, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": { + "border_top": "thin", + "border_top_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "read": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": { + "border_bottom": "thin", + "border_bottom_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - bottom only" + }, + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": { + "border_bottom": "thin", + "border_bottom_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "read": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": { + "border_left": "thin", + "border_left_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - left only" + }, + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": { + "border_left": "thin", + "border_left_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "read": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": { + "border_right": "thin", + "border_right_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - right only" + }, + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": { + "border_right": "thin", + "border_right_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "read": { + "passed": true, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": { + "border_diagonal_up": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal up" + }, + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": { + "border_diagonal_up": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "read": { + "passed": true, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal down" + }, + "write": { + "passed": true, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "read": { + "passed": true, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal both" + }, + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "read": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - red color" + }, + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "read": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - blue color" + }, + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "read": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + }, + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "read": { + "passed": true, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed styles per edge" + }, + "write": { + "passed": true, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "read": { + "passed": true, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00", + "border_style": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed colors per edge" + }, + "write": { + "passed": true, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00", + "border_style": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "thin_all": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": { + "border_style": "thin", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "write": { + "passed": true, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": { + "border_style": "medium", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "write": { + "passed": true, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": { + "border_style": "thick", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "write": { + "passed": true, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": { + "border_style": "double", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "write": { + "passed": true, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "write": { + "passed": true, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": { + "border_style": "dotted", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "write": { + "passed": true, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "write": { + "passed": true, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "write": { + "passed": true, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": { + "border_top": "thin", + "border_top_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": { + "border_bottom": "thin", + "border_bottom_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": { + "border_left": "thin", + "border_left_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": { + "border_right": "thin", + "border_right_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": { + "border_diagonal_up": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "write": { + "passed": true, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "write": { + "passed": true, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "write": { + "passed": true, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00", + "border_style": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "thin_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thin_all", + "sheet": "borders", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "read": { + "passed": false, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "medium_all", + "sheet": "borders", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thick_all", + "sheet": "borders", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "read": { + "passed": false, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "double", + "sheet": "borders", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "read": { + "passed": false, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dashed", + "sheet": "borders", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "read": { + "passed": false, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dotted", + "sheet": "borders", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot", + "sheet": "borders", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot_dot", + "sheet": "borders", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "read": { + "passed": false, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "top_only", + "sheet": "borders", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "bottom_only", + "sheet": "borders", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "left_only", + "sheet": "borders", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "right_only", + "sheet": "borders", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_up", + "sheet": "borders", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "read": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_down", + "sheet": "borders", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_both", + "sheet": "borders", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_red", + "sheet": "borders", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "borders", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "borders", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "read": { + "passed": false, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_styles", + "sheet": "borders", + "cell": "B20" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "read": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_colors", + "sheet": "borders", + "cell": "B21" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "thin_all": { + "read": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": { + "border_style": "thin", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thin all edges" + }, + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": { + "border_style": "thin", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "read": { + "passed": true, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": { + "border_style": "medium", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - medium all edges" + }, + "write": { + "passed": true, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": { + "border_style": "medium", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "read": { + "passed": true, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": { + "border_style": "thick", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thick all edges" + }, + "write": { + "passed": true, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": { + "border_style": "thick", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "read": { + "passed": true, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": { + "border_style": "double", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - double line" + }, + "write": { + "passed": true, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": { + "border_style": "double", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "read": { + "passed": true, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dashed" + }, + "write": { + "passed": true, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "read": { + "passed": true, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": { + "border_style": "dotted", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dotted" + }, + "write": { + "passed": true, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": { + "border_style": "dotted", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "read": { + "passed": true, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot" + }, + "write": { + "passed": true, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "read": { + "passed": true, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot-dot" + }, + "write": { + "passed": true, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "read": { + "passed": true, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": { + "border_top": "thin", + "border_top_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - top only" + }, + "write": { + "passed": true, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": { + "border_top": "thin", + "border_top_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "read": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": { + "border_bottom": "thin", + "border_bottom_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - bottom only" + }, + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": { + "border_bottom": "thin", + "border_bottom_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "read": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": { + "border_left": "thin", + "border_left_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - left only" + }, + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": { + "border_left": "thin", + "border_left_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "read": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": { + "border_right": "thin", + "border_right_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - right only" + }, + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": { + "border_right": "thin", + "border_right_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "read": { + "passed": true, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": { + "border_diagonal_up": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal up" + }, + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": { + "border_diagonal_up": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "read": { + "passed": true, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal down" + }, + "write": { + "passed": true, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "read": { + "passed": true, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal both" + }, + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "read": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - red color" + }, + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "read": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - blue color" + }, + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "read": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + }, + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "read": { + "passed": true, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed styles per edge" + }, + "write": { + "passed": true, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "read": { + "passed": true, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00", + "border_style": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed colors per edge" + }, + "write": { + "passed": true, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00", + "border_style": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "thin_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thin_all", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - thin all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "thin_all", + "sheet": "borders", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "read": { + "passed": false, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "medium_all", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - medium all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "medium_all", + "sheet": "borders", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thick_all", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - thick all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "thick_all", + "sheet": "borders", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "read": { + "passed": false, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "double", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - double line" + }, + "write": { + "passed": false, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "double", + "sheet": "borders", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "read": { + "passed": false, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dashed", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - dashed" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dashed", + "sheet": "borders", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "read": { + "passed": false, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dotted", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - dotted" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dotted", + "sheet": "borders", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - dash-dot" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dash_dot", + "sheet": "borders", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot_dot", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - dash-dot-dot" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dash_dot_dot", + "sheet": "borders", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "read": { + "passed": false, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "top_only", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - top only" + }, + "write": { + "passed": false, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "top_only", + "sheet": "borders", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "bottom_only", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - bottom only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "bottom_only", + "sheet": "borders", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "left_only", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - left only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "left_only", + "sheet": "borders", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "right_only", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - right only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "right_only", + "sheet": "borders", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_up", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_up", + "sheet": "borders", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "read": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_down", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_down", + "sheet": "borders", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_both", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - diagonal both" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_both", + "sheet": "borders", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_red", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - red color" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_red", + "sheet": "borders", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_blue", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - blue color" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_blue", + "sheet": "borders", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_custom", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_custom", + "sheet": "borders", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "read": { + "passed": false, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_styles", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - mixed styles per edge" + }, + "write": { + "passed": false, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "mixed_styles", + "sheet": "borders", + "cell": "B20" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "read": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_colors", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + }, + "write": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "mixed_colors", + "sheet": "borders", + "cell": "B21" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "borders", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "thin_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thin_all", + "sheet": "borders", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thin all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "thin_all", + "sheet": "borders", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "read": { + "passed": false, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "medium_all", + "sheet": "borders", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - medium all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "medium_all", + "sheet": "borders", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thick_all", + "sheet": "borders", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thick all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "thick_all", + "sheet": "borders", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "read": { + "passed": false, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "double", + "sheet": "borders", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - double line" + }, + "write": { + "passed": false, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "double", + "sheet": "borders", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "read": { + "passed": false, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dashed", + "sheet": "borders", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dashed" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dashed", + "sheet": "borders", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "read": { + "passed": false, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dotted", + "sheet": "borders", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dotted" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dotted", + "sheet": "borders", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot", + "sheet": "borders", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dash_dot", + "sheet": "borders", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot_dot", + "sheet": "borders", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot-dot" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dash_dot_dot", + "sheet": "borders", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "read": { + "passed": false, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "top_only", + "sheet": "borders", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - top only" + }, + "write": { + "passed": false, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "top_only", + "sheet": "borders", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "bottom_only", + "sheet": "borders", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - bottom only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "bottom_only", + "sheet": "borders", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "left_only", + "sheet": "borders", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - left only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "left_only", + "sheet": "borders", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "right_only", + "sheet": "borders", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - right only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "right_only", + "sheet": "borders", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_up", + "sheet": "borders", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_up", + "sheet": "borders", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "read": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_down", + "sheet": "borders", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_down", + "sheet": "borders", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_both", + "sheet": "borders", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal both" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_both", + "sheet": "borders", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_red", + "sheet": "borders", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - red color" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_red", + "sheet": "borders", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "borders", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - blue color" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_blue", + "sheet": "borders", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "borders", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_custom", + "sheet": "borders", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "read": { + "passed": false, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_styles", + "sheet": "borders", + "cell": "B20" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed styles per edge" + }, + "write": { + "passed": false, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "mixed_styles", + "sheet": "borders", + "cell": "B20" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "read": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_colors", + "sheet": "borders", + "cell": "B21" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + }, + "write": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "mixed_colors", + "sheet": "borders", + "cell": "B21" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "xlwt", + "scores": { + "read": null, + "write": 1 + }, + "test_cases": { + "thin_all": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": { + "border_style": "thin", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "write": { + "passed": true, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": { + "border_style": "medium", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "write": { + "passed": true, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": { + "border_style": "thick", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "write": { + "passed": true, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": { + "border_style": "double", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "write": { + "passed": true, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "write": { + "passed": true, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": { + "border_style": "dotted", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "write": { + "passed": true, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "write": { + "passed": true, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "write": { + "passed": true, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": { + "border_top": "thin", + "border_top_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": { + "border_bottom": "thin", + "border_bottom_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": { + "border_left": "thin", + "border_left_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": { + "border_right": "thin", + "border_right_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "write": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": { + "border_diagonal_down": "thin" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_up", + "sheet": "borders", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={'border_diagonal_down': 'thin'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "write": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_up": "thin" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_down", + "sheet": "borders", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={'border_diagonal_up': 'thin'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": { + "border_style": "thin", + "border_color": "#993300" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_custom", + "sheet": "borders", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={'border_style': 'thin', 'border_color': '#993300'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "write": { + "passed": true, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "write": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": { + "border_top_color": "#FF0000", + "border_bottom_color": "#008000", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00", + "border_style": "thin" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "mixed_colors", + "sheet": "borders", + "cell": "B21" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={'border_top_color': '#FF0000', 'border_bottom_color': '#008000', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00', 'border_style': 'thin'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "thin_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thin_all", + "sheet": "borders", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thin all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "thin_all", + "sheet": "borders", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "read": { + "passed": false, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "medium_all", + "sheet": "borders", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - medium all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "medium_all", + "sheet": "borders", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thick_all", + "sheet": "borders", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thick all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "thick_all", + "sheet": "borders", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "read": { + "passed": false, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "double", + "sheet": "borders", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - double line" + }, + "write": { + "passed": false, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "double", + "sheet": "borders", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "read": { + "passed": false, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dashed", + "sheet": "borders", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dashed" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dashed", + "sheet": "borders", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "read": { + "passed": false, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dotted", + "sheet": "borders", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dotted" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dotted", + "sheet": "borders", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot", + "sheet": "borders", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dash_dot", + "sheet": "borders", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot_dot", + "sheet": "borders", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot-dot" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dash_dot_dot", + "sheet": "borders", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "read": { + "passed": false, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "top_only", + "sheet": "borders", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - top only" + }, + "write": { + "passed": false, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "top_only", + "sheet": "borders", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "bottom_only", + "sheet": "borders", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - bottom only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "bottom_only", + "sheet": "borders", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "left_only", + "sheet": "borders", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - left only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "left_only", + "sheet": "borders", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "right_only", + "sheet": "borders", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - right only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "right_only", + "sheet": "borders", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_up", + "sheet": "borders", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_up", + "sheet": "borders", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "read": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_down", + "sheet": "borders", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_down", + "sheet": "borders", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_both", + "sheet": "borders", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal both" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_both", + "sheet": "borders", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_red", + "sheet": "borders", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - red color" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_red", + "sheet": "borders", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "borders", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - blue color" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_blue", + "sheet": "borders", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "borders", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_custom", + "sheet": "borders", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "read": { + "passed": false, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_styles", + "sheet": "borders", + "cell": "B20" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed styles per edge" + }, + "write": { + "passed": false, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "mixed_styles", + "sheet": "borders", + "cell": "B20" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "read": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_colors", + "sheet": "borders", + "cell": "B21" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + }, + "write": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "mixed_colors", + "sheet": "borders", + "cell": "B21" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "thin_all": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": { + "border_style": "thin", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "write": { + "passed": true, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": { + "border_style": "medium", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "write": { + "passed": true, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": { + "border_style": "thick", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "write": { + "passed": true, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": { + "border_style": "double", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "write": { + "passed": true, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "write": { + "passed": true, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": { + "border_style": "dotted", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "write": { + "passed": true, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "write": { + "passed": true, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "write": { + "passed": true, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": { + "border_top": "thin", + "border_top_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": { + "border_bottom": "thin", + "border_bottom_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": { + "border_left": "thin", + "border_left_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "write": { + "passed": true, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": { + "border_right": "thin", + "border_right_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": { + "border_diagonal_up": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "write": { + "passed": true, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "write": { + "passed": true, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "write": { + "passed": true, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "write": { + "passed": true, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed", + "border_color": "#000000" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "write": { + "passed": true, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00", + "border_style": "thin" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "thin_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thin_all", + "sheet": "borders", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "read": { + "passed": false, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "medium_all", + "sheet": "borders", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thick_all", + "sheet": "borders", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "read": { + "passed": false, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "double", + "sheet": "borders", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "read": { + "passed": false, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dashed", + "sheet": "borders", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "read": { + "passed": false, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dotted", + "sheet": "borders", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot", + "sheet": "borders", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot_dot", + "sheet": "borders", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "read": { + "passed": false, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "top_only", + "sheet": "borders", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "bottom_only", + "sheet": "borders", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "left_only", + "sheet": "borders", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "right_only", + "sheet": "borders", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_up", + "sheet": "borders", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "read": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_down", + "sheet": "borders", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_both", + "sheet": "borders", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_red", + "sheet": "borders", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "borders", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "borders", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "read": { + "passed": false, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_styles", + "sheet": "borders", + "cell": "B20" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "read": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_colors", + "sheet": "borders", + "cell": "B21" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "thin_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thin_all", + "sheet": "borders", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "read": { + "passed": false, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "medium_all", + "sheet": "borders", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thick_all", + "sheet": "borders", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "read": { + "passed": false, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "double", + "sheet": "borders", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "read": { + "passed": false, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dashed", + "sheet": "borders", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "read": { + "passed": false, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dotted", + "sheet": "borders", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot", + "sheet": "borders", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot_dot", + "sheet": "borders", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "read": { + "passed": false, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "top_only", + "sheet": "borders", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "bottom_only", + "sheet": "borders", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "left_only", + "sheet": "borders", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "right_only", + "sheet": "borders", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_up", + "sheet": "borders", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "read": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_down", + "sheet": "borders", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_both", + "sheet": "borders", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_red", + "sheet": "borders", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "borders", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "borders", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "read": { + "passed": false, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_styles", + "sheet": "borders", + "cell": "B20" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "read": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_colors", + "sheet": "borders", + "cell": "B21" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "borders", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "thin_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thin_all", + "sheet": "borders", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thin all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "thin_all", + "sheet": "borders", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thin all edges" + } + }, + "medium_all": { + "read": { + "passed": false, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "medium_all", + "sheet": "borders", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - medium all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "medium", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "medium_all", + "sheet": "borders", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'medium', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - medium all edges" + } + }, + "thick_all": { + "read": { + "passed": false, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "thick_all", + "sheet": "borders", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thick all edges" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thick", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "thick_all", + "sheet": "borders", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thick', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - thick all edges" + } + }, + "double": { + "read": { + "passed": false, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "double", + "sheet": "borders", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - double line" + }, + "write": { + "passed": false, + "expected": { + "border_style": "double", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "double", + "sheet": "borders", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'double', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - double line" + } + }, + "dashed": { + "read": { + "passed": false, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dashed", + "sheet": "borders", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dashed" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashed", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dashed", + "sheet": "borders", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashed', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dashed" + } + }, + "dotted": { + "read": { + "passed": false, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dotted", + "sheet": "borders", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dotted" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dotted", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dotted", + "sheet": "borders", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dotted', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dotted" + } + }, + "dash_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot", + "sheet": "borders", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dash_dot", + "sheet": "borders", + "cell": "B8" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot" + } + }, + "dash_dot_dot": { + "read": { + "passed": false, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "dash_dot_dot", + "sheet": "borders", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot-dot" + }, + "write": { + "passed": false, + "expected": { + "border_style": "dashDotDot", + "border_color": "#000000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "dash_dot_dot", + "sheet": "borders", + "cell": "B9" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'dashDotDot', 'border_color': '#000000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - dash-dot-dot" + } + }, + "top_only": { + "read": { + "passed": false, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "top_only", + "sheet": "borders", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - top only" + }, + "write": { + "passed": false, + "expected": { + "border_top": "thin", + "border_bottom": null, + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "top_only", + "sheet": "borders", + "cell": "B10" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thin', 'border_bottom': None, 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - top only" + } + }, + "bottom_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "bottom_only", + "sheet": "borders", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - bottom only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": "thin", + "border_left": null, + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "bottom_only", + "sheet": "borders", + "cell": "B11" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': 'thin', 'border_left': None, 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - bottom only" + } + }, + "left_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "left_only", + "sheet": "borders", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - left only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": "thin", + "border_right": null + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "left_only", + "sheet": "borders", + "cell": "B12" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': 'thin', 'border_right': None}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - left only" + } + }, + "right_only": { + "read": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "right_only", + "sheet": "borders", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - right only" + }, + "write": { + "passed": false, + "expected": { + "border_top": null, + "border_bottom": null, + "border_left": null, + "border_right": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "right_only", + "sheet": "borders", + "cell": "B13" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': None, 'border_bottom': None, 'border_left': None, 'border_right': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - right only" + } + }, + "diagonal_up": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_up", + "sheet": "borders", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_up": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_up", + "sheet": "borders", + "cell": "B14" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal up" + } + }, + "diagonal_down": { + "read": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_down", + "sheet": "borders", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_down", + "sheet": "borders", + "cell": "B15" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal down" + } + }, + "diagonal_both": { + "read": { + "passed": false, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "diagonal_both", + "sheet": "borders", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal both" + }, + "write": { + "passed": false, + "expected": { + "border_diagonal_up": "thin", + "border_diagonal_down": "thin" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "diagonal_both", + "sheet": "borders", + "cell": "B16" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_diagonal_up': 'thin', 'border_diagonal_down': 'thin'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - diagonal both" + } + }, + "color_red": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_red", + "sheet": "borders", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - red color" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#FF0000" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_red", + "sheet": "borders", + "cell": "B17" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#FF0000'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - red color" + } + }, + "color_blue": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_blue", + "sheet": "borders", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - blue color" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#0000FF" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_blue", + "sheet": "borders", + "cell": "B18" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#0000FF'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - blue color" + } + }, + "color_custom": { + "read": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "color_custom", + "sheet": "borders", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + }, + "write": { + "passed": false, + "expected": { + "border_style": "thin", + "border_color": "#8B4513" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "color_custom", + "sheet": "borders", + "cell": "B19" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_style': 'thin', 'border_color': '#8B4513'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - custom color (#8B4513)" + } + }, + "mixed_styles": { + "read": { + "passed": false, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_styles", + "sheet": "borders", + "cell": "B20" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed styles per edge" + }, + "write": { + "passed": false, + "expected": { + "border_top": "thick", + "border_bottom": "thin", + "border_left": "medium", + "border_right": "dashed" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "mixed_styles", + "sheet": "borders", + "cell": "B20" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top': 'thick', 'border_bottom': 'thin', 'border_left': 'medium', 'border_right': 'dashed'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed styles per edge" + } + }, + "mixed_colors": { + "read": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "read", + "test_case_id": "mixed_colors", + "sheet": "borders", + "cell": "B21" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + }, + "write": { + "passed": false, + "expected": { + "border_top_color": "#FF0000", + "border_bottom_color": "#00FF00", + "border_left_color": "#0000FF", + "border_right_color": "#FFFF00" + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "borders", + "operation": "write", + "test_case_id": "mixed_colors", + "sheet": "borders", + "cell": "B21" + }, + "adapter_message": "Expected values did not match actual values: expected={'border_top_color': '#FF0000', 'border_bottom_color': '#00FF00', 'border_left_color': '#0000FF', 'border_right_color': '#FFFF00'}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Border - mixed colors per edge" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "row_height_30": { + "read": { + "passed": true, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": 30.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 30" + }, + "write": { + "passed": true, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": 30.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "read": { + "passed": true, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": 45.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 45" + }, + "write": { + "passed": true, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": 45.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "read": { + "passed": true, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 20.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - D = 20" + }, + "write": { + "passed": true, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 20.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "read": { + "passed": true, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - E = 8" + }, + "write": { + "passed": true, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "row_height_30": { + "write": { + "passed": true, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": 30.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "write": { + "passed": true, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": 45.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "write": { + "passed": true, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 20.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "write": { + "passed": true, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "row_height_30": { + "read": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "read": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "read": { + "passed": false, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_20", + "sheet": "dimensions", + "cell": "D4" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "read": { + "passed": false, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_8", + "sheet": "dimensions", + "cell": "E5" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "row_height_30": { + "read": { + "passed": true, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": 30.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 30" + }, + "write": { + "passed": true, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": 30.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "read": { + "passed": true, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": 45.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 45" + }, + "write": { + "passed": true, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": 45.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "read": { + "passed": true, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 20.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - D = 20" + }, + "write": { + "passed": true, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 20.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "read": { + "passed": true, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - E = 8" + }, + "write": { + "passed": true, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "row_height_30": { + "read": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + }, + "write": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "read": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + }, + "write": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "read": { + "passed": false, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_20", + "sheet": "dimensions", + "cell": "D4" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - D = 20" + }, + "write": { + "passed": false, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 13.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "col_width_20", + "sheet": "dimensions", + "cell": "D4" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': 13.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "read": { + "passed": false, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_8", + "sheet": "dimensions", + "cell": "E5" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - E = 8" + }, + "write": { + "passed": false, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 13.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "col_width_8", + "sheet": "dimensions", + "cell": "E5" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': 13.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "dimensions", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "row_height_30": { + "read": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + }, + "write": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "read": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + }, + "write": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "read": { + "passed": false, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_20", + "sheet": "dimensions", + "cell": "D4" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - D = 20" + }, + "write": { + "passed": false, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 13.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "col_width_20", + "sheet": "dimensions", + "cell": "D4" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': 13.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "read": { + "passed": false, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_8", + "sheet": "dimensions", + "cell": "E5" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - E = 8" + }, + "write": { + "passed": false, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 13.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "col_width_8", + "sheet": "dimensions", + "cell": "E5" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': 13.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "xlwt", + "scores": { + "read": null, + "write": 1 + }, + "test_cases": { + "row_height_30": { + "write": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "write": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "write": { + "passed": true, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 20.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "write": { + "passed": true, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "row_height_30": { + "read": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + }, + "write": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "read": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + }, + "write": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "read": { + "passed": false, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_20", + "sheet": "dimensions", + "cell": "D4" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - D = 20" + }, + "write": { + "passed": false, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 13.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "col_width_20", + "sheet": "dimensions", + "cell": "D4" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': 13.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "read": { + "passed": false, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_8", + "sheet": "dimensions", + "cell": "E5" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - E = 8" + }, + "write": { + "passed": false, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 13.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "col_width_8", + "sheet": "dimensions", + "cell": "E5" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': 13.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 1 + }, + "test_cases": { + "row_height_30": { + "write": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "write": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "write": { + "passed": true, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 20.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "write": { + "passed": true, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 8.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "row_height_30": { + "read": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "read": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "read": { + "passed": false, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_20", + "sheet": "dimensions", + "cell": "D4" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "read": { + "passed": false, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_8", + "sheet": "dimensions", + "cell": "E5" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "row_height_30": { + "read": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "read": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "read": { + "passed": false, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_20", + "sheet": "dimensions", + "cell": "D4" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "read": { + "passed": false, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_8", + "sheet": "dimensions", + "cell": "E5" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "dimensions", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "row_height_30": { + "read": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + }, + "write": { + "passed": false, + "expected": { + "row_height": 30 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_30", + "sheet": "dimensions", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 30}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 30" + } + }, + "row_height_45": { + "read": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + }, + "write": { + "passed": false, + "expected": { + "row_height": 45 + }, + "actual": { + "row_height": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "row_height_45", + "sheet": "dimensions", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'row_height': 45}, actual={'row_height': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Row height - 45" + } + }, + "col_width_20": { + "read": { + "passed": false, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_20", + "sheet": "dimensions", + "cell": "D4" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - D = 20" + }, + "write": { + "passed": false, + "expected": { + "column_width": 20 + }, + "actual": { + "column_width": 13.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "col_width_20", + "sheet": "dimensions", + "cell": "D4" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 20}, actual={'column_width': 13.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - D = 20" + } + }, + "col_width_8": { + "read": { + "passed": false, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "read", + "test_case_id": "col_width_8", + "sheet": "dimensions", + "cell": "E5" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - E = 8" + }, + "write": { + "passed": false, + "expected": { + "column_width": 8 + }, + "actual": { + "column_width": 13.0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "dimensions", + "operation": "write", + "test_case_id": "col_width_8", + "sheet": "dimensions", + "cell": "E5" + }, + "adapter_message": "Expected values did not match actual values: expected={'column_width': 8}, actual={'column_width': 13.0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Column width - E = 8" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "sheet_names": { + "read": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + }, + "write": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "sheet_names": { + "write": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "python-calamine", + "scores": { + "read": 3, + "write": null + }, + "test_cases": { + "sheet_names": { + "read": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "sheet_names": { + "read": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + }, + "write": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "pylightxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "sheet_names": { + "read": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + }, + "write": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "multiple_sheets", + "library": "pyexcel", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "sheet_names": { + "read": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + }, + "write": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "xlwt", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "sheet_names": { + "write": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "pandas", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "sheet_names": { + "read": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + }, + "write": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "sheet_names": { + "write": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "openpyxl-readonly", + "scores": { + "read": 3, + "write": null + }, + "test_cases": { + "sheet_names": { + "read": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "polars", + "scores": { + "read": 1, + "write": null + }, + "test_cases": { + "sheet_names": { + "read": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "read": { + "passed": false, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "multiple_sheets", + "operation": "read", + "test_case_id": "value_beta", + "sheet": "Beta", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'string', 'value': 'Beta'}, actual={'type': 'blank'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "read": { + "passed": false, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "blank" + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "multiple_sheets", + "operation": "read", + "test_case_id": "value_gamma", + "sheet": "Gamma", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'type': 'string', 'value': 'Gamma'}, actual={'type': 'blank'}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "multiple_sheets", + "library": "tablib", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "sheet_names": { + "read": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + }, + "write": { + "passed": true, + "expected": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "actual": { + "sheet_names": [ + "Alpha", + "Beta", + "Gamma" + ] + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Sheet names" + } + }, + "value_alpha": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Alpha" + }, + "actual": { + "type": "string", + "value": "Alpha" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Alpha value" + } + }, + "value_beta": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Beta" + }, + "actual": { + "type": "string", + "value": "Beta" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Beta value" + } + }, + "value_gamma": { + "read": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + }, + "write": { + "passed": true, + "expected": { + "type": "string", + "value": "Gamma" + }, + "actual": { + "type": "string", + "value": "Gamma" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Gamma value" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "merge_horizontal": { + "read": { + "passed": true, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge horizontal B2:D2" + }, + "write": { + "passed": true, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "read": { + "passed": true, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge vertical B3:B5" + }, + "write": { + "passed": true, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "read": { + "passed": true, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with non-top-left value" + }, + "write": { + "passed": true, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "read": { + "passed": true, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with top-left fill" + }, + "write": { + "passed": true, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "merge_horizontal": { + "write": { + "passed": true, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "write": { + "passed": true, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "write": { + "passed": true, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "write": { + "passed": true, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "merge_horizontal": { + "read": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "read": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "read": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "read": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "merge_horizontal": { + "read": { + "passed": true, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge horizontal B2:D2" + }, + "write": { + "passed": true, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "read": { + "passed": true, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge vertical B3:B5" + }, + "write": { + "passed": true, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "read": { + "passed": true, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with non-top-left value" + }, + "write": { + "passed": true, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "read": { + "passed": true, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with top-left fill" + }, + "write": { + "passed": true, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "merge_horizontal": { + "read": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "read": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "read": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "read": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "merged_cells", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "merge_horizontal": { + "read": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "read": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "read": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "read": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "xlwt", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "merge_horizontal": { + "write": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": "B2:D2", + "top_left_value": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': 'B2:D2', 'top_left_value': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "write": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": "B3:B5", + "top_left_value": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': 'B3:B5', 'top_left_value': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "write": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": "B6:D6", + "top_left_value": null, + "non_top_left_nonempty": 0 + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': 'B6:D6', 'top_left_value': None, 'non_top_left_nonempty': 0}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "write": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": "B7:D7", + "top_left_value": null, + "top_left_bg_color": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': 'B7:D7', 'top_left_value': None, 'top_left_bg_color': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "merge_horizontal": { + "read": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "read": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "read": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "read": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "merge_horizontal": { + "write": { + "passed": true, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "write": { + "passed": true, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "write": { + "passed": true, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "write": { + "passed": true, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "merge_horizontal": { + "read": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "read": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "read": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "read": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "merge_horizontal": { + "read": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "read": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "read": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "read": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "merged_cells", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "merge_horizontal": { + "read": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B2:D2", + "top_left_value": "Merged" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_horizontal", + "sheet": "merged_cells", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B2:D2', 'top_left_value': 'Merged'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge horizontal B2:D2" + } + }, + "merge_vertical": { + "read": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B3:B5", + "top_left_value": "Vertical" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_vertical", + "sheet": "merged_cells", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B3:B5', 'top_left_value': 'Vertical'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Merge vertical B3:B5" + } + }, + "merge_value_off_top_left": { + "read": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B6:D6", + "top_left_value": "OffTop", + "non_top_left_nonempty": 0 + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_value_off_top_left", + "sheet": "merged_cells", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B6:D6', 'top_left_value': 'OffTop', 'non_top_left_nonempty': 0}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with non-top-left value" + } + }, + "merge_top_left_fill": { + "read": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "read", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + }, + "write": { + "passed": false, + "expected": { + "merged_range": "B7:D7", + "top_left_value": "Fill", + "top_left_bg_color": "#FF0000" + }, + "actual": { + "merged_range": null + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "merged_cells", + "operation": "write", + "test_case_id": "merge_top_left_fill", + "sheet": "merged_cells", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'merged_range': 'B7:D7', 'top_left_value': 'Fill', 'top_left_bg_color': '#FF0000'}, actual={'merged_range': None}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Merge with top-left fill" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "cf_cell_gt": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "format": { + "bg_color": "#FF00FF" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "format": { + "bg_color": "#FF00FF" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: text contains" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: data bar" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: 3-color scale" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: stop-if-true priority" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "cf_cell_gt": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "format": { + "bg_color": "#FF00FF" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "cf_cell_gt": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_cell_gt", + "sheet": "conditional_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_formula_cross_sheet", + "sheet": "conditional_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_text_contains", + "sheet": "conditional_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH(\"foo\",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_data_bar", + "sheet": "conditional_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_color_scale", + "sheet": "conditional_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_stop_if_true", + "sheet": "conditional_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "cf_cell_gt": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "format": { + "bg_color": "#FF00FF" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "format": { + "bg_color": "#FF00FF" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: text contains" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: data bar" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: 3-color scale" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "read": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: stop-if-true priority" + }, + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "cf_cell_gt": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_cell_gt", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_cell_gt", + "sheet": "conditional_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_formula_cross_sheet", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_formula_cross_sheet", + "sheet": "conditional_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_text_contains", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "CF: text contains" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_text_contains", + "sheet": "conditional_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH(\"foo\",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_data_bar", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "CF: data bar" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_data_bar", + "sheet": "conditional_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_color_scale", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_color_scale", + "sheet": "conditional_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_stop_if_true", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_stop_if_true", + "sheet": "conditional_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "conditional_formatting", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "cf_cell_gt": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_cell_gt", + "sheet": "conditional_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_cell_gt", + "sheet": "conditional_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_formula_cross_sheet", + "sheet": "conditional_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_formula_cross_sheet", + "sheet": "conditional_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_text_contains", + "sheet": "conditional_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH(\"foo\",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: text contains" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_text_contains", + "sheet": "conditional_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH(\"foo\",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_data_bar", + "sheet": "conditional_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: data bar" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_data_bar", + "sheet": "conditional_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_color_scale", + "sheet": "conditional_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_color_scale", + "sheet": "conditional_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_stop_if_true", + "sheet": "conditional_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_stop_if_true", + "sheet": "conditional_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "xlwt", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "cf_cell_gt": { + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_cell_gt", + "sheet": "conditional_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_formula_cross_sheet", + "sheet": "conditional_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_text_contains", + "sheet": "conditional_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH(\"foo\",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_data_bar", + "sheet": "conditional_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_color_scale", + "sheet": "conditional_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_stop_if_true", + "sheet": "conditional_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "cf_cell_gt": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_cell_gt", + "sheet": "conditional_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_cell_gt", + "sheet": "conditional_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_formula_cross_sheet", + "sheet": "conditional_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_formula_cross_sheet", + "sheet": "conditional_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_text_contains", + "sheet": "conditional_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH(\"foo\",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: text contains" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_text_contains", + "sheet": "conditional_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH(\"foo\",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_data_bar", + "sheet": "conditional_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: data bar" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_data_bar", + "sheet": "conditional_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_color_scale", + "sheet": "conditional_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_color_scale", + "sheet": "conditional_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_stop_if_true", + "sheet": "conditional_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_stop_if_true", + "sheet": "conditional_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "cf_cell_gt": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "format": { + "bg_color": "#FF00FF" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "format": { + "bg_color": "#FFFF00" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "write": { + "passed": true, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "cf_cell_gt": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_cell_gt", + "sheet": "conditional_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_formula_cross_sheet", + "sheet": "conditional_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_text_contains", + "sheet": "conditional_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH(\"foo\",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_data_bar", + "sheet": "conditional_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_color_scale", + "sheet": "conditional_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_stop_if_true", + "sheet": "conditional_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "cf_cell_gt": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_cell_gt", + "sheet": "conditional_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_formula_cross_sheet", + "sheet": "conditional_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_text_contains", + "sheet": "conditional_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH(\"foo\",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_data_bar", + "sheet": "conditional_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_color_scale", + "sheet": "conditional_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_stop_if_true", + "sheet": "conditional_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "conditional_formatting", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "cf_cell_gt": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_cell_gt", + "sheet": "conditional_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "cellIs", + "operator": "greaterThan", + "formula": "5", + "priority": 1, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_cell_gt", + "sheet": "conditional_formatting", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'cellIs', 'operator': 'greaterThan', 'formula': '5', 'priority': 1, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "CF: cell > 5 (yellow fill)" + } + }, + "cf_formula_cross_sheet": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_formula_cross_sheet", + "sheet": "conditional_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=Ref!$A$1>5", + "priority": 2, + "format": { + "bg_color": "#FF00FF" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_formula_cross_sheet", + "sheet": "conditional_formatting", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=Ref!$A$1>5', 'priority': 2, 'format': {'bg_color': '#FF00FF'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: formula rule with cross-sheet ref" + } + }, + "cf_text_contains": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_text_contains", + "sheet": "conditional_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH(\"foo\",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: text contains" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "expression", + "formula": "=ISNUMBER(SEARCH(\"foo\",B2))", + "priority": 3, + "format": { + "bg_color": "#FFFF00" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_text_contains", + "sheet": "conditional_formatting", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'expression', 'formula': '=ISNUMBER(SEARCH(\"foo\",B2))', 'priority': 3, 'format': {'bg_color': '#FFFF00'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: text contains" + } + }, + "cf_data_bar": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_data_bar", + "sheet": "conditional_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: data bar" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "dataBar", + "priority": 4 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_data_bar", + "sheet": "conditional_formatting", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'dataBar', 'priority': 4}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: data bar" + } + }, + "cf_color_scale": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_color_scale", + "sheet": "conditional_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B2:B6", + "rule_type": "colorScale", + "priority": 5 + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_color_scale", + "sheet": "conditional_formatting", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B2:B6', 'rule_type': 'colorScale', 'priority': 5}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: 3-color scale" + } + }, + "cf_stop_if_true": { + "read": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "read", + "test_case_id": "cf_stop_if_true", + "sheet": "conditional_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + }, + "write": { + "passed": false, + "expected": { + "cf_rule": { + "range": "B7:B9", + "rule_type": "cellIs", + "operator": "lessThan", + "formula": "3", + "priority": 1, + "stop_if_true": true, + "format": { + "bg_color": "#FF0000" + } + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "conditional_formatting", + "operation": "write", + "test_case_id": "cf_stop_if_true", + "sheet": "conditional_formatting", + "cell": "B7" + }, + "adapter_message": "Expected values did not match actual values: expected={'cf_rule': {'range': 'B7:B9', 'rule_type': 'cellIs', 'operator': 'lessThan', 'formula': '3', 'priority': 1, 'stop_if_true': True, 'format': {'bg_color': '#FF0000'}}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "CF: stop-if-true priority" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "dv_list_csv": { + "read": { + "passed": true, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: list from CSV" + }, + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "read": { + "passed": true, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: list from range" + }, + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "read": { + "passed": true, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: cross-sheet named range" + }, + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "read": { + "passed": true, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: custom formula" + }, + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "read": { + "passed": true, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: whole number with error" + }, + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "dv_list_csv": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "dv_list_csv": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_csv", + "sheet": "data_validation", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '\"Red,Green,Blue\"', 'allow_blank': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_range", + "sheet": "data_validation", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_cross_sheet", + "sheet": "data_validation", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_custom_formula", + "sheet": "data_validation", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_whole_between", + "sheet": "data_validation", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "dv_list_csv": { + "read": { + "passed": true, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: list from CSV" + }, + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "read": { + "passed": true, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: list from range" + }, + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "read": { + "passed": true, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: cross-sheet named range" + }, + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "read": { + "passed": true, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: custom formula" + }, + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "read": { + "passed": true, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: whole number with error" + }, + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "dv_list_csv": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_csv", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_list_csv", + "sheet": "data_validation", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '\"Red,Green,Blue\"', 'allow_blank': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_range", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "DV: list from range" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_list_range", + "sheet": "data_validation", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_cross_sheet", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_cross_sheet", + "sheet": "data_validation", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_custom_formula", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "DV: custom formula" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_custom_formula", + "sheet": "data_validation", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_whole_between", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_whole_between", + "sheet": "data_validation", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "data_validation", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "dv_list_csv": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_csv", + "sheet": "data_validation", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '\"Red,Green,Blue\"', 'allow_blank': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_list_csv", + "sheet": "data_validation", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '\"Red,Green,Blue\"', 'allow_blank': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_range", + "sheet": "data_validation", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: list from range" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_list_range", + "sheet": "data_validation", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_cross_sheet", + "sheet": "data_validation", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_cross_sheet", + "sheet": "data_validation", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_custom_formula", + "sheet": "data_validation", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: custom formula" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_custom_formula", + "sheet": "data_validation", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_whole_between", + "sheet": "data_validation", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_whole_between", + "sheet": "data_validation", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "xlwt", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "dv_list_csv": { + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_list_csv", + "sheet": "data_validation", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '\"Red,Green,Blue\"', 'allow_blank': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_list_range", + "sheet": "data_validation", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_cross_sheet", + "sheet": "data_validation", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_custom_formula", + "sheet": "data_validation", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_whole_between", + "sheet": "data_validation", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "dv_list_csv": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_csv", + "sheet": "data_validation", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '\"Red,Green,Blue\"', 'allow_blank': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_list_csv", + "sheet": "data_validation", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '\"Red,Green,Blue\"', 'allow_blank': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_range", + "sheet": "data_validation", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: list from range" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_list_range", + "sheet": "data_validation", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_cross_sheet", + "sheet": "data_validation", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_cross_sheet", + "sheet": "data_validation", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_custom_formula", + "sheet": "data_validation", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: custom formula" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_custom_formula", + "sheet": "data_validation", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_whole_between", + "sheet": "data_validation", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_whole_between", + "sheet": "data_validation", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "dv_list_csv": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "write": { + "passed": true, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "dv_list_csv": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_csv", + "sheet": "data_validation", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '\"Red,Green,Blue\"', 'allow_blank': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_range", + "sheet": "data_validation", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_cross_sheet", + "sheet": "data_validation", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_custom_formula", + "sheet": "data_validation", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_whole_between", + "sheet": "data_validation", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "dv_list_csv": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_csv", + "sheet": "data_validation", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '\"Red,Green,Blue\"', 'allow_blank': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_range", + "sheet": "data_validation", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_cross_sheet", + "sheet": "data_validation", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_custom_formula", + "sheet": "data_validation", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_whole_between", + "sheet": "data_validation", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "data_validation", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "dv_list_csv": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_csv", + "sheet": "data_validation", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '\"Red,Green,Blue\"', 'allow_blank': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B2", + "validation_type": "list", + "formula1": "\"Red,Green,Blue\"", + "allow_blank": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_list_csv", + "sheet": "data_validation", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B2', 'validation_type': 'list', 'formula1': '\"Red,Green,Blue\"', 'allow_blank': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: list from CSV" + } + }, + "dv_list_range": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_list_range", + "sheet": "data_validation", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: list from range" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B3", + "validation_type": "list", + "formula1": "=$D$2:$D$4" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_list_range", + "sheet": "data_validation", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B3', 'validation_type': 'list', 'formula1': '=$D$2:$D$4'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: list from range" + } + }, + "dv_cross_sheet": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_cross_sheet", + "sheet": "data_validation", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B4", + "validation_type": "list", + "formula1": "=RegionList" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_cross_sheet", + "sheet": "data_validation", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B4', 'validation_type': 'list', 'formula1': '=RegionList'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: cross-sheet named range" + } + }, + "dv_custom_formula": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_custom_formula", + "sheet": "data_validation", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: custom formula" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B5", + "validation_type": "custom", + "formula1": "=B5>C5" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_custom_formula", + "sheet": "data_validation", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B5', 'validation_type': 'custom', 'formula1': '=B5>C5'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "DV: custom formula" + } + }, + "dv_whole_between": { + "read": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "read", + "test_case_id": "dv_whole_between", + "sheet": "data_validation", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + }, + "write": { + "passed": false, + "expected": { + "validation": { + "range": "B6", + "validation_type": "whole", + "operator": "between", + "formula1": "1", + "formula2": "10", + "allow_blank": false, + "error_title": "Invalid", + "error": "Enter 1-10" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "data_validation", + "operation": "write", + "test_case_id": "dv_whole_between", + "sheet": "data_validation", + "cell": "B6" + }, + "adapter_message": "Expected values did not match actual values: expected={'validation': {'range': 'B6', 'validation_type': 'whole', 'operator': 'between', 'formula1': '1', 'formula2': '10', 'allow_blank': False, 'error_title': 'Invalid', 'error': 'Enter 1-10'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "DV: whole number with error" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "link_external": { + "read": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: external URL" + }, + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "read": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: internal sheet" + }, + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "read": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: mailto" + }, + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "read": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + }, + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "link_external": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "link_external": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_external", + "sheet": "hyperlinks", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_internal", + "sheet": "hyperlinks", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_mailto", + "sheet": "hyperlinks", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_long", + "sheet": "hyperlinks", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "link_external": { + "read": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: external URL" + }, + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "read": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: internal sheet" + }, + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "read": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: mailto" + }, + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "read": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + }, + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "link_external": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_external", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_external", + "sheet": "hyperlinks", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_internal", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_internal", + "sheet": "hyperlinks", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_mailto", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_mailto", + "sheet": "hyperlinks", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_long", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_long", + "sheet": "hyperlinks", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "hyperlinks", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "link_external": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_external", + "sheet": "hyperlinks", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_external", + "sheet": "hyperlinks", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_internal", + "sheet": "hyperlinks", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_internal", + "sheet": "hyperlinks", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_mailto", + "sheet": "hyperlinks", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_mailto", + "sheet": "hyperlinks", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_long", + "sheet": "hyperlinks", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_long", + "sheet": "hyperlinks", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "xlwt", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "link_external": { + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_external", + "sheet": "hyperlinks", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_internal", + "sheet": "hyperlinks", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_mailto", + "sheet": "hyperlinks", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_long", + "sheet": "hyperlinks", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "link_external": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_external", + "sheet": "hyperlinks", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_external", + "sheet": "hyperlinks", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_internal", + "sheet": "hyperlinks", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_internal", + "sheet": "hyperlinks", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_mailto", + "sheet": "hyperlinks", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_mailto", + "sheet": "hyperlinks", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_long", + "sheet": "hyperlinks", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_long", + "sheet": "hyperlinks", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "link_external": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "write": { + "passed": true, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "link_external": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_external", + "sheet": "hyperlinks", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_internal", + "sheet": "hyperlinks", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_mailto", + "sheet": "hyperlinks", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_long", + "sheet": "hyperlinks", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "link_external": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_external", + "sheet": "hyperlinks", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_internal", + "sheet": "hyperlinks", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_mailto", + "sheet": "hyperlinks", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_long", + "sheet": "hyperlinks", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "hyperlinks", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "link_external": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_external", + "sheet": "hyperlinks", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B2", + "target": "https://example.com/docs", + "display": "Example Docs", + "tooltip": "Go to docs", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_external", + "sheet": "hyperlinks", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B2', 'target': 'https://example.com/docs', 'display': 'Example Docs', 'tooltip': 'Go to docs', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: external URL" + } + }, + "link_internal": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_internal", + "sheet": "hyperlinks", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B3", + "target": "Targets!A1", + "display": "Go Target", + "tooltip": "Jump to target", + "internal": true + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_internal", + "sheet": "hyperlinks", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B3', 'target': 'Targets!A1', 'display': 'Go Target', 'tooltip': 'Jump to target', 'internal': True}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: internal sheet" + } + }, + "link_mailto": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_mailto", + "sheet": "hyperlinks", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B4", + "target": "mailto:test@example.com", + "display": "Email", + "tooltip": "Send email", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_mailto", + "sheet": "hyperlinks", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B4', 'target': 'mailto:test@example.com', 'display': 'Email', 'tooltip': 'Send email', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Hyperlink: mailto" + } + }, + "link_long": { + "read": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "read", + "test_case_id": "link_long", + "sheet": "hyperlinks", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + }, + "write": { + "passed": false, + "expected": { + "hyperlink": { + "cell": "B5", + "target": "https://example.com/search?q=excel%20bench&sort=desc#section-2", + "display": "Search", + "tooltip": "Encoded URL", + "internal": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "hyperlinks", + "operation": "write", + "test_case_id": "link_long", + "sheet": "hyperlinks", + "cell": "B5" + }, + "adapter_message": "Expected values did not match actual values: expected={'hyperlink': {'cell': 'B5', 'target': 'https://example.com/search?q=excel%20bench&sort=desc#section-2', 'display': 'Search', 'tooltip': 'Encoded URL', 'internal': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Hyperlink: long encoded URL" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "image_one_cell": { + "read": { + "passed": true, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Image: one-cell anchor" + }, + "write": { + "passed": true, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "read": { + "passed": true, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + }, + "write": { + "passed": true, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "image_one_cell": { + "write": { + "passed": true, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "write": { + "passed": true, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "image_one_cell": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "image_one_cell": { + "read": { + "passed": true, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Image: one-cell anchor" + }, + "write": { + "passed": true, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "read": { + "passed": true, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + }, + "write": { + "passed": true, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "image_one_cell": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_one_cell", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + }, + "write": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_two_cell_offset", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + }, + "write": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "images", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "image_one_cell": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + }, + "write": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + }, + "write": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "xlwt", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "image_one_cell": { + "write": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "write": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "image_one_cell": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + }, + "write": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + }, + "write": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "image_one_cell": { + "write": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "write": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "image_one_cell": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "image_one_cell": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "images", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "image_one_cell": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + }, + "write": { + "passed": false, + "expected": { + "image": { + "cell": "B2", + "path": "fixtures/images/sample.png", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_one_cell", + "sheet": "images", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'B2', 'path': 'fixtures/images/sample.png', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Image: one-cell anchor" + } + }, + "image_two_cell_offset": { + "read": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "read", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + }, + "write": { + "passed": false, + "expected": { + "image": { + "cell": "D6", + "path": "fixtures/images/sample.jpg", + "anchor": "oneCell" + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "images", + "operation": "write", + "test_case_id": "image_two_cell_offset", + "sheet": "images", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'image': {'cell': 'D6', 'path': 'fixtures/images/sample.jpg', 'anchor': 'oneCell'}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Image: two-cell anchor with offset" + } + } + }, + "notes": null + }, + { + "feature": "pivot_tables", + "library": "openpyxl", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "python-calamine", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "wolfxl", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "pylightxl", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "pivot_tables", + "library": "pyexcel", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "xlwt", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "pandas", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "openpyxl-readonly", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "polars", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "pivot_tables", + "library": "tablib", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Unsupported on macOS without a Windows-generated pivot fixture (fixtures/excel/tier2/15_pivot_tables.xlsx)." + }, + { + "feature": "comments", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "comment_legacy": { + "read": { + "passed": true, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Comment: legacy note" + }, + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "read": { + "passed": true, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: threaded" + }, + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "read": { + "passed": true, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: second author" + }, + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "comment_legacy": { + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "comment_legacy": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "comment_legacy": { + "read": { + "passed": true, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Comment: legacy note" + }, + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "read": { + "passed": true, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: threaded" + }, + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "read": { + "passed": true, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: second author" + }, + "write": { + "passed": true, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "comment_legacy": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "comments", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "comment_legacy": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "xlwt", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "comment_legacy": { + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "comment_legacy": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "comment_legacy": { + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "comment_legacy": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "comment_legacy": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "comments", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "comment_legacy": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B2", + "text": "Legacy note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_legacy", + "sheet": "comments", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B2', 'text': 'Legacy note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Comment: legacy note" + } + }, + "comment_threaded": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B3", + "text": "Threaded fallback", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_threaded", + "sheet": "comments", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B3', 'text': 'Threaded fallback', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: threaded" + } + }, + "comment_author": { + "read": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "read", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + }, + "write": { + "passed": false, + "expected": { + "comment": { + "cell": "B4", + "text": "Another note", + "threaded": false + } + }, + "actual": {}, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "comments", + "operation": "write", + "test_case_id": "comment_author", + "sheet": "comments", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'comment': {'cell': 'B4', 'text': 'Another note', 'threaded': False}}, actual={}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Comment: second author" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "freeze_b2": { + "read": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Freeze panes at B2" + }, + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "read": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Freeze panes at D5" + }, + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "read": { + "passed": true, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Split panes row=2 col=1" + }, + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "freeze_b2": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "freeze_b2": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": null, + "x_split": null, + "y_split": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "freeze_b2": { + "read": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Freeze panes at B2" + }, + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "read": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Freeze panes at D5" + }, + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "read": { + "passed": true, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Split panes row=2 col=1" + }, + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "freeze_b2": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": null, + "x_split": null, + "y_split": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "freeze_panes", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "freeze_b2": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": null, + "x_split": null, + "y_split": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": null, + "x_split": null, + "y_split": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "xlwt", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "freeze_b2": { + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "error": "'Sheet' object has no attribute 'frozen_row_count'" + }, + "notes": "Exception: AttributeError", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": "B2" + }, + "adapter_message": "AttributeError: 'Sheet' object has no attribute 'frozen_row_count'", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "error": "'Sheet' object has no attribute 'frozen_row_count'" + }, + "notes": "Exception: AttributeError", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": "B3" + }, + "adapter_message": "AttributeError: 'Sheet' object has no attribute 'frozen_row_count'", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "error": "'Sheet' object has no attribute 'frozen_row_count'" + }, + "notes": "Exception: AttributeError", + "diagnostics": [ + { + "category": "internal", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": "B4" + }, + "adapter_message": "AttributeError: 'Sheet' object has no attribute 'frozen_row_count'", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "freeze_b2": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": null, + "x_split": null, + "y_split": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": null, + "x_split": null, + "y_split": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 3 + }, + "test_cases": { + "freeze_b2": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "write": { + "passed": true, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "freeze_b2": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": null, + "x_split": null, + "y_split": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "freeze_b2": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": null, + "x_split": null, + "y_split": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "freeze_panes", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "freeze_b2": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "B2" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "freeze_b2", + "sheet": "FreezeB2", + "cell": "B2" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'B2'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "basic", + "label": "Freeze panes at B2" + } + }, + "freeze_d5": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "freeze", + "top_left_cell": "D5" + } + }, + "actual": { + "freeze": { + "mode": null, + "top_left_cell": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "freeze_d5", + "sheet": "FreezeD5", + "cell": "B3" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'freeze', 'top_left_cell': 'D5'}}, actual={'freeze': {'mode': None, 'top_left_cell': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Freeze panes at D5" + } + }, + "split_2x1": { + "read": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": null, + "x_split": null, + "y_split": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "read", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + }, + "write": { + "passed": false, + "expected": { + "freeze": { + "mode": "split", + "x_split": 1, + "y_split": 2 + } + }, + "actual": { + "freeze": { + "mode": null, + "x_split": null, + "y_split": null + } + }, + "notes": "Incorrect result", + "diagnostics": [ + { + "category": "data_mismatch", + "severity": "error", + "location": { + "feature": "freeze_panes", + "operation": "write", + "test_case_id": "split_2x1", + "sheet": "SplitPanes", + "cell": "B4" + }, + "adapter_message": "Expected values did not match actual values: expected={'freeze': {'mode': 'split', 'x_split': 1, 'y_split': 2}}, actual={'freeze': {'mode': None, 'x_split': None, 'y_split': None}}", + "probable_cause": "Adapter returned a value that differs from benchmark expectations." + } + ], + "importance": "edge", + "label": "Split panes row=2 col=1" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "nr_simple_cell": { + "read": { + "passed": true, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: single cell" + }, + "write": { + "passed": true, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "read": { + "passed": true, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: cell range" + }, + "write": { + "passed": true, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "read": { + "passed": true, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: used in formula" + }, + "write": { + "passed": true, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "read": { + "passed": true, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: sheet-scoped" + }, + "write": { + "passed": true, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "read": { + "passed": true, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: cross-sheet reference" + }, + "write": { + "passed": true, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "read": { + "passed": true, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: underscore name" + }, + "write": { + "passed": true, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "nr_simple_cell": { + "write": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "xlsxwriter does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_simple_cell", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "write": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "xlsxwriter does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cell_range", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "write": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "xlsxwriter does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_formula_ref", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "write": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "xlsxwriter does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_sheet_scope", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "write": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "xlsxwriter does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cross_sheet", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "write": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "xlsxwriter does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_special_chars", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "nr_simple_cell": { + "read": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "python-calamine does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_simple_cell", + "sheet": "named_ranges", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "read": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "python-calamine does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cell_range", + "sheet": "named_ranges", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "read": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "python-calamine does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_formula_ref", + "sheet": "named_ranges", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "read": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "python-calamine does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_sheet_scope", + "sheet": "named_ranges", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "read": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "python-calamine does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cross_sheet", + "sheet": "named_ranges", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "read": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "python-calamine does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_special_chars", + "sheet": "named_ranges", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "nr_simple_cell": { + "read": { + "passed": true, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42.0 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: single cell" + }, + "write": { + "passed": true, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "read": { + "passed": true, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: cell range" + }, + "write": { + "passed": true, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "read": { + "passed": true, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: used in formula" + }, + "write": { + "passed": true, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "read": { + "passed": true, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: sheet-scoped" + }, + "write": { + "passed": true, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "read": { + "passed": true, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: cross-sheet reference" + }, + "write": { + "passed": true, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "read": { + "passed": true, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: underscore name" + }, + "write": { + "passed": true, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "nr_simple_cell": { + "read": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_simple_cell", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Named range: single cell" + }, + "write": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "pylightxl does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_simple_cell", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "read": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cell_range", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Named range: cell range" + }, + "write": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "pylightxl does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cell_range", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "read": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_formula_ref", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + }, + "write": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "pylightxl does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_formula_ref", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "read": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_sheet_scope", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + }, + "write": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "pylightxl does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_sheet_scope", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "read": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cross_sheet", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + }, + "write": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "pylightxl does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cross_sheet", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "read": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_special_chars", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + }, + "write": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "pylightxl does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_special_chars", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "named_ranges", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "nr_simple_cell": { + "read": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "pyexcel does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_simple_cell", + "sheet": "named_ranges", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: single cell" + }, + "write": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "pyexcel does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_simple_cell", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "read": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "pyexcel does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cell_range", + "sheet": "named_ranges", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: cell range" + }, + "write": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "pyexcel does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cell_range", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "read": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "pyexcel does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_formula_ref", + "sheet": "named_ranges", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + }, + "write": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "pyexcel does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_formula_ref", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "read": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "pyexcel does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_sheet_scope", + "sheet": "named_ranges", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + }, + "write": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "pyexcel does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_sheet_scope", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "read": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "pyexcel does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cross_sheet", + "sheet": "named_ranges", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + }, + "write": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "pyexcel does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cross_sheet", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "read": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "pyexcel does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_special_chars", + "sheet": "named_ranges", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + }, + "write": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "pyexcel does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_special_chars", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "xlwt", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "nr_simple_cell": { + "write": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "xlwt does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_simple_cell", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "write": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "xlwt does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cell_range", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "write": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "xlwt does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_formula_ref", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "write": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "xlwt does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_sheet_scope", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "write": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "xlwt does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cross_sheet", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "write": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "xlwt does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_special_chars", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "nr_simple_cell": { + "read": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "pandas does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_simple_cell", + "sheet": "named_ranges", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: pandas does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: single cell" + }, + "write": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "pandas does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_simple_cell", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "read": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "pandas does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cell_range", + "sheet": "named_ranges", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: pandas does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: cell range" + }, + "write": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "pandas does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cell_range", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "read": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "pandas does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_formula_ref", + "sheet": "named_ranges", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: pandas does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + }, + "write": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "pandas does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_formula_ref", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "read": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "pandas does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_sheet_scope", + "sheet": "named_ranges", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: pandas does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + }, + "write": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "pandas does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_sheet_scope", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "read": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "pandas does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cross_sheet", + "sheet": "named_ranges", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: pandas does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + }, + "write": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "pandas does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cross_sheet", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "read": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "pandas does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_special_chars", + "sheet": "named_ranges", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: pandas does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + }, + "write": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "pandas does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_special_chars", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "nr_simple_cell": { + "write": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "xlsxwriter-constmem does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_simple_cell", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "write": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "xlsxwriter-constmem does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cell_range", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "write": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "xlsxwriter-constmem does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_formula_ref", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "write": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "xlsxwriter-constmem does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_sheet_scope", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "write": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "xlsxwriter-constmem does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cross_sheet", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "write": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "xlsxwriter-constmem does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_special_chars", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "nr_simple_cell": { + "read": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "openpyxl-readonly does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_simple_cell", + "sheet": "named_ranges", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "read": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "openpyxl-readonly does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cell_range", + "sheet": "named_ranges", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "read": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "openpyxl-readonly does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_formula_ref", + "sheet": "named_ranges", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "read": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "openpyxl-readonly does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_sheet_scope", + "sheet": "named_ranges", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "read": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "openpyxl-readonly does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cross_sheet", + "sheet": "named_ranges", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "read": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "openpyxl-readonly does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_special_chars", + "sheet": "named_ranges", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "nr_simple_cell": { + "read": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "polars does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_simple_cell", + "sheet": "named_ranges", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: polars does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "read": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "polars does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cell_range", + "sheet": "named_ranges", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: polars does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "read": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "polars does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_formula_ref", + "sheet": "named_ranges", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: polars does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "read": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "polars does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_sheet_scope", + "sheet": "named_ranges", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: polars does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "read": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "polars does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cross_sheet", + "sheet": "named_ranges", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: polars does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "read": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "polars does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_special_chars", + "sheet": "named_ranges", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: polars does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "named_ranges", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "nr_simple_cell": { + "read": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "tablib does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_simple_cell", + "sheet": "named_ranges", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: tablib does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: single cell" + }, + "write": { + "passed": false, + "expected": { + "name": "SingleCell", + "scope": "workbook", + "refers_to": "named_ranges!$B$2", + "value": 42 + }, + "actual": { + "error": "tablib does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_simple_cell", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: single cell" + } + }, + "nr_cell_range": { + "read": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "tablib does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cell_range", + "sheet": "named_ranges", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: tablib does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: cell range" + }, + "write": { + "passed": false, + "expected": { + "name": "DataRange", + "scope": "workbook", + "refers_to": "named_ranges!$B$3:$D$3" + }, + "actual": { + "error": "tablib does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cell_range", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: cell range" + } + }, + "nr_formula_ref": { + "read": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "tablib does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_formula_ref", + "sheet": "named_ranges", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: tablib does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + }, + "write": { + "passed": false, + "expected": { + "name": "TaxRate", + "scope": "workbook", + "refers_to": "named_ranges!$B$4", + "value": 0.08 + }, + "actual": { + "error": "tablib does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_formula_ref", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Named range: used in formula" + } + }, + "nr_sheet_scope": { + "read": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "tablib does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_sheet_scope", + "sheet": "named_ranges", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: tablib does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + }, + "write": { + "passed": false, + "expected": { + "name": "LocalName", + "scope": "sheet", + "refers_to": "named_ranges!$B$5", + "value": "local" + }, + "actual": { + "error": "tablib does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_sheet_scope", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: sheet-scoped" + } + }, + "nr_cross_sheet": { + "read": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "tablib does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_cross_sheet", + "sheet": "named_ranges", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: tablib does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + }, + "write": { + "passed": false, + "expected": { + "name": "OtherSheet", + "scope": "workbook", + "refers_to": "Targets!$A$1" + }, + "actual": { + "error": "tablib does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_cross_sheet", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: cross-sheet reference" + } + }, + "nr_special_chars": { + "read": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "tablib does not implement named range reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "read", + "test_case_id": "nr_special_chars", + "sheet": "named_ranges", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: tablib does not implement named range reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + }, + "write": { + "passed": false, + "expected": { + "name": "_my_range", + "scope": "workbook", + "refers_to": "named_ranges!$B$7" + }, + "actual": { + "error": "tablib does not implement named range writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "named_ranges", + "operation": "write", + "test_case_id": "nr_special_chars", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement named range writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Named range: underscore name" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "openpyxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "tbl_basic": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: basic 3-col" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: with totals row" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: no style" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: single column" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: header only (no data rows)" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: with autoFilter" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "xlsxwriter", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "tbl_basic": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "xlsxwriter does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_basic", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "xlsxwriter does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_with_totals", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "xlsxwriter does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_no_style", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "xlsxwriter does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_col", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "xlsxwriter does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_row", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "xlsxwriter does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_autofilter", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "python-calamine", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "tbl_basic": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "python-calamine does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_basic", + "sheet": "tables", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "python-calamine does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_with_totals", + "sheet": "tables", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "python-calamine does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_no_style", + "sheet": "tables", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "python-calamine does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_col", + "sheet": "tables", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "python-calamine does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_row", + "sheet": "tables", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "python-calamine does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_autofilter", + "sheet": "tables", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: python-calamine does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "wolfxl", + "scores": { + "read": 3, + "write": 3 + }, + "test_cases": { + "tbl_basic": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: basic 3-col" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: with totals row" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: no style" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: single column" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: header only (no data rows)" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "read": { + "passed": true, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: with autoFilter" + }, + "write": { + "passed": true, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "notes": null, + "diagnostics": [], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "pylightxl", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "tbl_basic": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_basic", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "pylightxl does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_basic", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_with_totals", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Table: with totals row" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "pylightxl does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_with_totals", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_no_style", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "basic", + "label": "Table: no style" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "pylightxl does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_no_style", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_col", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "Table: single column" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "pylightxl does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_col", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_row", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "pylightxl does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_row", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "expected string or bytes-like object, got 'NoneType'" + }, + "notes": "Failed to open workbook", + "diagnostics": [ + { + "category": "invalid_input", + "severity": "error", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_autofilter", + "sheet": null, + "cell": null + }, + "adapter_message": "TypeError: expected string or bytes-like object, got 'NoneType'", + "probable_cause": "Input workbook could not be opened by this adapter." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "pylightxl does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_autofilter", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pylightxl does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "xlrd", + "scores": { + "read": null, + "write": null + }, + "test_cases": {}, + "notes": "Not applicable: xlrd does not support .xlsx input" + }, + { + "feature": "tables", + "library": "pyexcel", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "tbl_basic": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "pyexcel does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_basic", + "sheet": "tables", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "pyexcel does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_basic", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "pyexcel does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_with_totals", + "sheet": "tables", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: with totals row" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "pyexcel does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_with_totals", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "pyexcel does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_no_style", + "sheet": "tables", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: no style" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "pyexcel does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_no_style", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "pyexcel does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_col", + "sheet": "tables", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: single column" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "pyexcel does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_col", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "pyexcel does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_row", + "sheet": "tables", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "pyexcel does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_row", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "pyexcel does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_autofilter", + "sheet": "tables", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "pyexcel does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_autofilter", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pyexcel does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "xlwt", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "tbl_basic": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "xlwt does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_basic", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "xlwt does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_with_totals", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "xlwt does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_no_style", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "xlwt does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_col", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "xlwt does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_row", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "xlwt does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_autofilter", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlwt does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "pandas", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "tbl_basic": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "pandas does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_basic", + "sheet": "tables", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: pandas does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "pandas does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_basic", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "pandas does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_with_totals", + "sheet": "tables", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: pandas does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: with totals row" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "pandas does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_with_totals", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "pandas does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_no_style", + "sheet": "tables", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: pandas does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: no style" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "pandas does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_no_style", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "pandas does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_col", + "sheet": "tables", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: pandas does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: single column" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "pandas does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_col", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "pandas does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_row", + "sheet": "tables", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: pandas does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "pandas does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_row", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "pandas does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_autofilter", + "sheet": "tables", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: pandas does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "pandas does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_autofilter", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: pandas does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "xlsxwriter-constmem", + "scores": { + "read": null, + "write": 0 + }, + "test_cases": { + "tbl_basic": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "xlsxwriter-constmem does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_basic", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "xlsxwriter-constmem does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_with_totals", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "xlsxwriter-constmem does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_no_style", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "xlsxwriter-constmem does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_col", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "xlsxwriter-constmem does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_row", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "write": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "xlsxwriter-constmem does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_autofilter", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: xlsxwriter-constmem does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "openpyxl-readonly", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "tbl_basic": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "openpyxl-readonly does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_basic", + "sheet": "tables", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "openpyxl-readonly does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_with_totals", + "sheet": "tables", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "openpyxl-readonly does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_no_style", + "sheet": "tables", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "openpyxl-readonly does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_col", + "sheet": "tables", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "openpyxl-readonly does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_row", + "sheet": "tables", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "openpyxl-readonly does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_autofilter", + "sheet": "tables", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: openpyxl-readonly does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "polars", + "scores": { + "read": 0, + "write": null + }, + "test_cases": { + "tbl_basic": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "polars does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_basic", + "sheet": "tables", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: polars does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "polars does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_with_totals", + "sheet": "tables", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: polars does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "polars does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_no_style", + "sheet": "tables", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: polars does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "polars does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_col", + "sheet": "tables", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: polars does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "polars does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_row", + "sheet": "tables", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: polars does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "polars does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_autofilter", + "sheet": "tables", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: polars does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + }, + { + "feature": "tables", + "library": "tablib", + "scores": { + "read": 0, + "write": 0 + }, + "test_cases": { + "tbl_basic": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "tablib does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_basic", + "sheet": "tables", + "cell": "B2" + }, + "adapter_message": "NotImplementedError: tablib does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "SalesData", + "ref": "E2:G5", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Name", + "Qty", + "Price" + ] + } + }, + "actual": { + "error": "tablib does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_basic", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: basic 3-col" + } + }, + "tbl_with_totals": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "tablib does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_with_totals", + "sheet": "tables", + "cell": "B3" + }, + "adapter_message": "NotImplementedError: tablib does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: with totals row" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "Summary", + "ref": "E7:G11", + "header_row": true, + "totals_row": true, + "style": "TableStyleLight1", + "columns": [ + "Item", + "Count", + "Total" + ], + "totals_row_count": 1 + } + }, + "actual": { + "error": "tablib does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_with_totals", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: with totals row" + } + }, + "tbl_no_style": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "tablib does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_no_style", + "sheet": "tables", + "cell": "B4" + }, + "adapter_message": "NotImplementedError: tablib does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "basic", + "label": "Table: no style" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "PlainTable", + "ref": "E13:F16", + "header_row": true, + "totals_row": false, + "style": null, + "columns": [ + "Key", + "Value" + ] + } + }, + "actual": { + "error": "tablib does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_no_style", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "basic", + "label": "Table: no style" + } + }, + "tbl_single_col": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "tablib does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_col", + "sheet": "tables", + "cell": "B5" + }, + "adapter_message": "NotImplementedError: tablib does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: single column" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "SingleCol", + "ref": "E18:E21", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium2", + "columns": [ + "Score" + ] + } + }, + "actual": { + "error": "tablib does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_col", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: single column" + } + }, + "tbl_single_row": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "tablib does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_single_row", + "sheet": "tables", + "cell": "B6" + }, + "adapter_message": "NotImplementedError: tablib does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "EmptyTable", + "ref": "E23:G23", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "A", + "B", + "C" + ] + } + }, + "actual": { + "error": "tablib does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_single_row", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: header only (no data rows)" + } + }, + "tbl_autofilter": { + "read": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "tablib does not implement table reads" + }, + "notes": "Exception: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "read", + "test_case_id": "tbl_autofilter", + "sheet": "tables", + "cell": "B7" + }, + "adapter_message": "NotImplementedError: tablib does not implement table reads", + "probable_cause": "Adapter raised an exception while evaluating this test case." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + }, + "write": { + "passed": false, + "expected": { + "table": { + "name": "Filtered", + "ref": "E25:G28", + "header_row": true, + "totals_row": false, + "style": "TableStyleMedium9", + "columns": [ + "Region", + "Sales", + "Year" + ], + "autofilter": true + } + }, + "actual": { + "error": "tablib does not implement table writes" + }, + "notes": "Write failed: NotImplementedError", + "diagnostics": [ + { + "category": "unsupported_feature", + "severity": "warning", + "location": { + "feature": "tables", + "operation": "write", + "test_case_id": "tbl_autofilter", + "sheet": null, + "cell": null + }, + "adapter_message": "NotImplementedError: tablib does not implement table writes", + "probable_cause": "Adapter could not create or save a workbook for this feature." + } + ], + "importance": "edge", + "label": "Table: with autoFilter" + } + } + }, + "notes": null + } + ] +} \ No newline at end of file From 023da0d40c5a47129bdb2543900dab2c1d2fe5a4 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:28:52 -0700 Subject: [PATCH 23/25] fix: sort apache poi wrapper imports --- tools/external-oracles/apache-poi/poi_oracle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/external-oracles/apache-poi/poi_oracle.py b/tools/external-oracles/apache-poi/poi_oracle.py index cb07ed0..c941789 100755 --- a/tools/external-oracles/apache-poi/poi_oracle.py +++ b/tools/external-oracles/apache-poi/poi_oracle.py @@ -4,10 +4,10 @@ from __future__ import annotations import json -import tempfile import shutil import subprocess import sys +import tempfile import zipfile from base64 import b64encode from pathlib import Path From 2fe73d249f776434a5056f0df6797e2ff839e0bf Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:34:16 -0700 Subject: [PATCH 24/25] fix: address semantic diff and report staleness review --- src/excelbench/harness/semantic_diff.py | 14 ++++------- src/excelbench/results/renderer.py | 1 + tests/test_renderer_utils.py | 31 +++++++++++++++++++++++++ tests/test_semantic_diff.py | 20 ++++++++++++++-- 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/excelbench/harness/semantic_diff.py b/src/excelbench/harness/semantic_diff.py index 193a071..eeda79a 100644 --- a/src/excelbench/harness/semantic_diff.py +++ b/src/excelbench/harness/semantic_diff.py @@ -158,17 +158,15 @@ def _collect_deltas( ) return if isinstance(left, list) and isinstance(right, list): - left_sorted = sorted(left, key=_sort_key) - right_sorted = sorted(right, key=_sort_key) - if left_sorted == right_sorted: + if left == right: return - max_len = max(len(left_sorted), len(right_sorted)) + max_len = max(len(left), len(right)) for index in range(max_len): _collect_deltas( category=category, path=f"{path}[{index}]", - left=left_sorted[index] if index < len(left_sorted) else None, - right=right_sorted[index] if index < len(right_sorted) else None, + left=left[index] if index < len(left) else None, + right=right[index] if index < len(right) else None, deltas=deltas, ) return @@ -176,10 +174,6 @@ def _collect_deltas( deltas.append(SemanticDelta(category=category, path=path, left=left, right=right)) -def _sort_key(value: Any) -> str: - return json.dumps(value, sort_keys=True, default=str) - - def _short(value: Any) -> str: text = json.dumps(value, sort_keys=True, default=str) return text if len(text) <= 180 else f"{text[:177]}..." diff --git a/src/excelbench/results/renderer.py b/src/excelbench/results/renderer.py index 3d53f12..0317787 100644 --- a/src/excelbench/results/renderer.py +++ b/src/excelbench/results/renderer.py @@ -880,6 +880,7 @@ def _render_why_failed(results: BenchmarkResults, path: Path) -> None: for tr in score.test_results: rows.append((score.feature, score.library, tr)) if not any(not tr.passed for _, _, tr in rows): + path.unlink(missing_ok=True) return path.write_text(render_why_failed(rows)) diff --git a/tests/test_renderer_utils.py b/tests/test_renderer_utils.py index 46530f3..ee72776 100644 --- a/tests/test_renderer_utils.py +++ b/tests/test_renderer_utils.py @@ -22,6 +22,7 @@ _render_fidelity_deltas, _render_per_test_table, render_markdown, + render_results, score_emoji, ) @@ -290,6 +291,36 @@ def test_render_markdown_hides_pyumya_and_includes_modify_label(tmp_path: Path) assert "pyumya" not in content +def test_render_results_removes_stale_why_failed_when_all_pass(tmp_path: Path) -> None: + failed = _make_results( + scores=[ + FeatureScore( + feature="cell_values", + library="openpyxl", + read_score=0, + test_results=[_make_tr("tc1", OperationType.READ, passed=False)], + ) + ] + ) + passed = _make_results( + scores=[ + FeatureScore( + feature="cell_values", + library="openpyxl", + read_score=3, + test_results=[_make_tr("tc1", OperationType.READ, passed=True)], + ) + ] + ) + + render_results(failed, tmp_path) + assert (tmp_path / "WHY_FAILED.md").exists() + + render_results(passed, tmp_path) + + assert not (tmp_path / "WHY_FAILED.md").exists() + + # ───────────────────────────────────────────────── # fidelity deltas # ───────────────────────────────────────────────── diff --git a/tests/test_semantic_diff.py b/tests/test_semantic_diff.py index 7cff4f8..6db3403 100644 --- a/tests/test_semantic_diff.py +++ b/tests/test_semantic_diff.py @@ -7,8 +7,8 @@ from openpyxl.comments import Comment from openpyxl.styles import PatternFill -from excelbench.harness.semantic_diff import diff_workbooks, write_diff_artifacts -from excelbench.harness.workbook_snapshot import snapshot_workbook +from excelbench.harness.semantic_diff import compare_snapshots, diff_workbooks, write_diff_artifacts +from excelbench.harness.workbook_snapshot import WorkbookSnapshot, snapshot_workbook def _write_workbook(path: Path, *, value: str = "Revenue", fill: str = "FFFF00") -> None: @@ -52,6 +52,22 @@ def test_diff_workbooks_reports_category_counts(tmp_path: Path) -> None: assert diff.category_counts()["cells"] >= 1 +def test_compare_snapshots_preserves_list_order() -> None: + left = WorkbookSnapshot( + workbook="left.xlsx", + categories={"sheets": {"names": ["Summary", "Data"]}}, + ) + right = WorkbookSnapshot( + workbook="right.xlsx", + categories={"sheets": {"names": ["Data", "Summary"]}}, + ) + + diff = compare_snapshots(left, right) + + assert not diff.passed + assert [delta.path for delta in diff.deltas] == ["sheets.names[0]", "sheets.names[1]"] + + def test_write_diff_artifacts(tmp_path: Path) -> None: left = tmp_path / "left.xlsx" right = tmp_path / "right.xlsx" From c75f98b48cd07253b8c9c63525757040eea8ada3 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:38:43 -0700 Subject: [PATCH 25/25] fix: harden optional oracle helper setup --- tests/test_external_fixture_pack.py | 17 ++++++++ tests/test_external_oracles.py | 41 ++++++++++++++++++- .../closedxml/closedxml-oracle.csproj | 2 +- .../libreoffice/libreoffice_oracle.py | 5 ++- .../external-oracles/npoi/npoi-oracle.csproj | 2 +- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/tests/test_external_fixture_pack.py b/tests/test_external_fixture_pack.py index 67c2e53..7073406 100644 --- a/tests/test_external_fixture_pack.py +++ b/tests/test_external_fixture_pack.py @@ -4,6 +4,7 @@ import os import shutil +import subprocess from pathlib import Path from zipfile import ZipFile @@ -24,6 +25,18 @@ ) +def _missing_dotnet_runtime(prefix: str) -> bool: + if shutil.which("dotnet") is None: + return True + completed = subprocess.run( + ["dotnet", "--list-runtimes"], + text=True, + capture_output=True, + check=False, + ) + return completed.returncode != 0 or f"Microsoft.NETCore.App {prefix}" not in completed.stdout + + def test_excelize_fixture_specs_are_stable() -> None: specs = excelize_fixture_specs() @@ -137,6 +150,10 @@ def test_workbook_protection_probe_reads_workbook_xml(tmp_path: Path) -> None: @pytest.mark.skipif(shutil.which("go") is None, reason="Go is required for Excelize fixture pack") +@pytest.mark.skipif( + _missing_dotnet_runtime("8."), + reason="Installed dotnet command lacks the .NET 8 runtime used by optional helpers", +) def test_generate_external_fixture_pack_without_validators(tmp_path: Path) -> None: repo_root = Path(__file__).resolve().parents[1] diff --git a/tests/test_external_oracles.py b/tests/test_external_oracles.py index dbdc80b..dbddaf2 100644 --- a/tests/test_external_oracles.py +++ b/tests/test_external_oracles.py @@ -3,7 +3,9 @@ from __future__ import annotations import shutil +import subprocess import sys +from importlib.util import module_from_spec, spec_from_file_location from pathlib import Path from zipfile import ZipFile @@ -18,6 +20,18 @@ ) +def _missing_dotnet_runtime(prefix: str) -> bool: + if shutil.which("dotnet") is None: + return True + completed = subprocess.run( + ["dotnet", "--list-runtimes"], + text=True, + capture_output=True, + check=False, + ) + return completed.returncode != 0 or f"Microsoft.NETCore.App {prefix}" not in completed.stdout + + def test_catalog_lists_planned_external_oracles() -> None: catalog = external_oracle_catalog() @@ -102,6 +116,26 @@ def test_oracle_request_serializes_paths() -> None: } +def test_libreoffice_helper_rejects_blank_input_path() -> None: + repo_root = Path(__file__).resolve().parents[1] + helper_path = repo_root / "tools" / "external-oracles" / "libreoffice" / "libreoffice_oracle.py" + spec = spec_from_file_location("excelbench_libreoffice_oracle", helper_path) + assert spec is not None + assert spec.loader is not None + module = module_from_spec(spec) + spec.loader.exec_module(module) + + response, exit_code = module.run_conversion( + soffice="/usr/bin/false", + request={"operation": "open_save_validate", "input_path": " "}, + extension="xlsx", + filter_name="Calc Office Open XML", + ) + + assert exit_code == 1 + assert response["error"] == "missing_input_path" + + def test_successful_oracle_helper_parses_json_stdout() -> None: tool = ExternalOracleTool( name="python-json-helper", @@ -261,7 +295,10 @@ def test_excelize_go_helper_writes_advanced_fixture(tmp_path: Path) -> None: assert "xl/charts/chart1.xml" in names -@pytest.mark.skipif(shutil.which("dotnet") is None, reason=".NET SDK is required for ClosedXML") +@pytest.mark.skipif( + _missing_dotnet_runtime("8."), + reason=".NET 8 runtime is required for ClosedXML", +) def test_closedxml_dotnet_helper_writes_pivot_fixture(tmp_path: Path) -> None: repo_root = Path(__file__).resolve().parents[1] output_path = tmp_path / "closedxml-smoke.xlsx" @@ -348,7 +385,7 @@ def test_closedxml_dotnet_helper_writes_pivot_fixture(tmp_path: Path) -> None: assert ":r>" in shared_strings_xml -@pytest.mark.skipif(shutil.which("dotnet") is None, reason=".NET SDK is required for NPOI") +@pytest.mark.skipif(_missing_dotnet_runtime("8."), reason=".NET 8 runtime is required for NPOI") def test_npoi_dotnet_helper_writes_formula_comment_fixture(tmp_path: Path) -> None: repo_root = Path(__file__).resolve().parents[1] output_path = tmp_path / "npoi-smoke.xlsx" diff --git a/tools/external-oracles/closedxml/closedxml-oracle.csproj b/tools/external-oracles/closedxml/closedxml-oracle.csproj index 6177645..441386c 100644 --- a/tools/external-oracles/closedxml/closedxml-oracle.csproj +++ b/tools/external-oracles/closedxml/closedxml-oracle.csproj @@ -1,7 +1,7 @@ Exe - net9.0 + net8.0 enable enable diff --git a/tools/external-oracles/libreoffice/libreoffice_oracle.py b/tools/external-oracles/libreoffice/libreoffice_oracle.py index 206d543..80b4145 100644 --- a/tools/external-oracles/libreoffice/libreoffice_oracle.py +++ b/tools/external-oracles/libreoffice/libreoffice_oracle.py @@ -75,9 +75,10 @@ def run_conversion( filter_name: str, ) -> tuple[JSONDict, int]: """Run LibreOffice headless conversion and return structured diagnostics.""" - input_path = Path(str(request.get("input_path") or request.get("output_path") or "")) - if not input_path: + input_path_value = request.get("input_path") or request.get("output_path") + if input_path_value is None or not str(input_path_value).strip(): return {"error": "missing_input_path", "message": "input_path is required"}, 1 + input_path = Path(str(input_path_value)) if not input_path.exists(): return {"error": "missing_input_path", "message": f"{input_path} does not exist"}, 1 diff --git a/tools/external-oracles/npoi/npoi-oracle.csproj b/tools/external-oracles/npoi/npoi-oracle.csproj index 28ec140..f916b8b 100644 --- a/tools/external-oracles/npoi/npoi-oracle.csproj +++ b/tools/external-oracles/npoi/npoi-oracle.csproj @@ -1,7 +1,7 @@ Exe - net9.0 + net8.0 enable enable