- Implemented calc_audit.py script for verifying arithmetic expressions in blog calculations - Added humanizer-style-guide.md with guidelines for removing AI-sounding language from Chinese blogs - Created openai.yaml configuration for blog review publish workflow interface - Established publishing-preview-checklist.md for pre/post publication verification - Developed 12-dimension review rubric for systematic article evaluation - Built complete SKILL.md documentation covering blog writing, reviewing, and publishing workflows - Added source-rights-policy.md for copyright and attribution guidelines - Defined hard gates and quality standards for publication approval process - Implemented multi-mode workflow selection based on task requirements - Created standardized output templates for draft delivery and review reports
116 lines
3.3 KiB
Python
116 lines
3.3 KiB
Python
#!/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())
|