#!/usr/bin/env python3 """Evaluate arithmetic expressions for blog calculation audits. Input JSON format: [ { "id": "1", "claim": "monthly cost is 12.50", "expression": "0.25 * 50", "expected": 12.5, "tolerance": 0.000001, "note": "unit: usd/month" } ] The script intentionally supports arithmetic only, not arbitrary Python code. """ from __future__ import annotations import ast import json import math import operator import sys from pathlib import Path from typing import Any ALLOWED_BINOPS = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv, ast.Mod: operator.mod, ast.Pow: operator.pow, } ALLOWED_UNARYOPS = {ast.UAdd: operator.pos, ast.USub: operator.neg} def eval_expr(expr: str) -> float: node = ast.parse(expr, mode="eval") def walk(n: ast.AST) -> float: if isinstance(n, ast.Expression): return walk(n.body) if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return float(n.value) if isinstance(n, ast.BinOp) and type(n.op) in ALLOWED_BINOPS: return float(ALLOWED_BINOPS[type(n.op)](walk(n.left), walk(n.right))) if isinstance(n, ast.UnaryOp) and type(n.op) in ALLOWED_UNARYOPS: return float(ALLOWED_UNARYOPS[type(n.op)](walk(n.operand))) raise ValueError(f"unsupported expression element: {ast.dump(n)}") return walk(node) def fmt(value: Any) -> str: if value is None: return "" if isinstance(value, float): if math.isfinite(value): return f"{value:.12g}" return str(value) def main() -> int: if len(sys.argv) != 2: print("usage: calc_audit.py calculations.json", file=sys.stderr) return 2 path = Path(sys.argv[1]) items = json.loads(path.read_text(encoding="utf-8")) if not isinstance(items, list): print("input must be a json list", file=sys.stderr) return 2 rows = [] for item in items: if not isinstance(item, dict): raise ValueError("each item must be an object") result = eval_expr(str(item["expression"])) expected = item.get("expected") tolerance = float(item.get("tolerance", 1e-9)) if expected is None: status = "computed" delta = None else: expected_float = float(expected) delta = result - expected_float status = "pass" if abs(delta) <= tolerance else "fail" rows.append( [ str(item.get("id", "")), str(item.get("claim", "")), str(item.get("expression", "")), fmt(result), fmt(expected), fmt(delta), status, str(item.get("note", "")), ] ) headers = ["id", "claim", "expression", "result", "expected", "delta", "status", "note"] print("| " + " | ".join(headers) + " |") print("|" + "|".join(["---"] * len(headers)) + "|") for row in rows: safe = [cell.replace("|", "\\|").replace("\n", " ") for cell in row] print("| " + " | ".join(safe) + " |") return 0 if __name__ == "__main__": raise SystemExit(main())