107 lines
4.1 KiB
Python
107 lines
4.1 KiB
Python
|
|
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()
|