feat: Add Python consistency tests for calculator definitions and refactor QuickConversionTable to explicitly pass calculator configuration to its row builder.

This commit is contained in:
Ben
2026-03-08 13:41:50 -07:00
parent 193affca27
commit 2794835590
3 changed files with 113 additions and 5 deletions

106
tests/test_consistency.py Normal file
View File

@@ -0,0 +1,106 @@
import math
import re
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CALCULATORS_TS = ROOT / "hdyc-svelte" / "src" / "lib" / "data" / "calculators.ts"
def _js_fmt(n: float) -> str:
"""Mimics the fmt() function in engine.ts"""
if not math.isfinite(n):
return str(n)
if n == 0:
return "0"
if abs(n) < 1e-6:
return f"{n:.6e}".replace("e-0", "e-").replace("e+0", "e+")
# engine.ts uses parseFloat(n.toFixed(6)).toString()
rounded = round(n, 6)
if rounded == 0: # Handle -0.0
return "0"
if rounded == int(rounded):
return str(int(rounded))
return str(rounded)
def _js_fmt_precise(n: float) -> str:
"""Mimics formatExampleValue in QuickConversionExample.svelte"""
if n is None or math.isnan(n):
return ""
if not math.isfinite(n):
return str(n)
if n == 0:
return "0"
rounded = round(n, 6)
if rounded != 0:
if rounded == int(rounded):
return str(int(rounded))
return str(rounded)
# Precise version for very small numbers
precise = f"{n:.12f}".rstrip('0').rstrip('.')
return precise if precise else "0"
class TestCalculatorsConsistency(unittest.TestCase):
def test_standard_calculators_consistency(self):
text = CALCULATORS_TS.read_text(encoding="utf-8")
# Extract the calculators array content
match = re.search(r"export const calculators: CalculatorDef\[\] = \[(.*?)\];", text, re.S)
self.assertTrue(match, "Could not find calculators array in calculators.ts")
body = match.group(1)
# Split by '{"slug":' to avoid splitting on nested braces
raw_entries = body.split('{"slug":')
errors = []
for raw_entry in raw_entries:
if not raw_entry.strip():
continue
entry = '{"slug":' + raw_entry
slug_match = re.search(r'"slug": "(.*?)"', entry)
if not slug_match:
continue
slug = slug_match.group(1)
type_match = re.search(r'"type": "(.*?)"', entry)
if not type_match or type_match.group(1) != "standard":
continue
# Use non-greedy search for factor/offset and handle potential whitespace
factor_match = re.search(r'"factor":\s*([0-9.eE+-]+)', entry)
offset_match = re.search(r'"offset":\s*([0-9.eE+-]+)', entry)
factor = float(factor_match.group(1)) if factor_match else 1.0
offset = float(offset_match.group(1)) if offset_match else 0.0
# 1. Formula Hint vs Chart Row 1 Consistency
# Logic: solve(config, 1, "1") -> val2 = fmt(1 * factor + offset)
row_one_output = _js_fmt(1.0 * factor + offset)
# 2. How to convert Examples Consistency (Inverse)
if factor != 0:
reverse_val = (1.0 - offset) / factor
formatted_reverse = _js_fmt_precise(reverse_val)
# Specific check for Réaumur to Kelvin (User request)
if slug == "reaumur-to-kelvin":
if formatted_reverse != "-217.72":
errors.append(f"[{slug}] Reverse example mismatch: expected -217.72, got {formatted_reverse}")
if row_one_output != "274.4":
errors.append(f"[{slug}] Chart row 1 mismatch: expected 274.4, got {row_one_output}")
# Specific check for Feet per minute to Knots (Previous bug fix)
if slug == "feet-per-minute-to-knots":
if row_one_output != "0.009875":
errors.append(f"[{slug}] Chart row 1 mismatch: expected 0.009875, got {row_one_output}")
if formatted_reverse != "101.268504":
errors.append(f"[{slug}] Reverse example mismatch: expected 101.268504, got {formatted_reverse}")
if errors:
self.fail("\n" + "\n".join(errors))
if __name__ == "__main__":
unittest.main()