fix: tolerate spreadsheet decimal roundoff

This commit is contained in:
Wyndham ARR
2026-08-04 14:49:38 +08:00
parent a2b86cdf10
commit aae3d8e1db
5 changed files with 144 additions and 7 deletions

View File

@@ -90,6 +90,7 @@ class OpenpyxlWorkbookBuilder:
_INTEGER_HEADERS = frozenset({"NIGHTS", "ADULTS", "CHILDREN", "NO_OF_ROOMS"})
_DECIMAL_HEADERS = frozenset({"RATE_AMOUNT", "REAL PRICE", "TOTAL PRICE", KB_HEADER})
_DECIMAL_TOLERANCE = Decimal("0.000001")
def __init__(self, *_legacy_args: Any, **_legacy_options: Any) -> None:
# The ignored arguments keep older local wrappers import-compatible while
@@ -130,6 +131,17 @@ class OpenpyxlWorkbookBuilder:
return float(Decimal(str(value)))
return cls._safe_text(value)
@classmethod
def _decimal_matches(cls, actual: Any, expected: Any) -> bool:
try:
actual_decimal = Decimal(str(actual))
expected_decimal = Decimal(str(expected))
except (InvalidOperation, TypeError, ValueError):
return False
if not actual_decimal.is_finite() or not expected_decimal.is_finite():
return False
return abs(actual_decimal - expected_decimal) <= cls._DECIMAL_TOLERANCE
@staticmethod
def _style_sheet(worksheet: Any, headers: list[str]) -> None:
worksheet.freeze_panes = "A2"
@@ -185,14 +197,20 @@ class OpenpyxlWorkbookBuilder:
for channel, expected_row_count in zip(report.channels, expected_rows):
worksheet = workbook[channel.worksheet]
headers = list(channel.headers)
if worksheet.max_row != expected_row_count + 1 or worksheet.max_column != len(headers):
raise ValueError("worksheet dimensions do not match")
if [worksheet.cell(1, index).value for index in range(1, len(headers) + 1)] != headers:
row_iterator = worksheet.iter_rows(values_only=False)
header_cells = next(row_iterator, None)
if (
header_cells is None
or len(header_cells) != len(headers)
or [cell.value for cell in header_cells] != headers
):
raise ValueError("worksheet headers do not match")
for row_index, expected_row in enumerate(channel.rows, start=2):
cells = next(row_iterator, None)
if cells is None or len(cells) != len(headers):
raise ValueError("worksheet dimensions do not match")
payload_row = expected_row.to_payload(channel.worksheet == "DY-AI-Easy-KB")
for column, header in enumerate(headers, start=1):
cell = worksheet.cell(row_index, column)
for header, cell in zip(headers, cells):
if header == "TOTAL PRICE":
expected_formula = f"=R{row_index}*C{row_index}*G{row_index}"
if cell.value != expected_formula or cell.data_type != "f":
@@ -206,11 +224,13 @@ class OpenpyxlWorkbookBuilder:
elif header in cls._INTEGER_HEADERS:
matches = actual == int(expected)
elif header in cls._DECIMAL_HEADERS:
matches = Decimal(str(actual)) == Decimal(str(expected))
matches = cls._decimal_matches(actual, expected)
else:
matches = ("" if actual is None else actual) == cls._safe_text(expected)
if not matches:
raise ValueError("worksheet values do not match")
if next(row_iterator, None) is not None:
raise ValueError("worksheet dimensions do not match")
if formula_count != report.row_count:
raise ValueError("formula count does not match")
if cls._semantic_sha256(payload) != cls._semantic_sha256(report.to_workbook_payload()):