Spaces:
Runtime error
Runtime error
ToolStore Agent
feat: calc-toolkit (eval, convert, stats), publish all toolsets, 7 live on HF Space
481db54 | """ | |
| calcβtoolkit β Evaluate math expressions, convert units, and compute statistics. | |
| =================================================================================== | |
| Safe expression evaluation with proper operator precedence, unit conversions, | |
| and basic statistical operations β all stdlib, no external deps. | |
| """ | |
| import math | |
| import operator | |
| import re | |
| from typing import Any | |
| try: | |
| from toolstore.toolset import tool | |
| except ImportError: | |
| def tool(fn): | |
| return fn | |
| # ββ Safe expression evaluator βββββββββββββββββββββββββββββββββββββββββ | |
| _SAFE_OPS = { | |
| "+": operator.add, "-": operator.sub, "*": operator.mul, | |
| "/": operator.truediv, "**": operator.pow, "%": operator.mod, | |
| "//": operator.floordiv, | |
| } | |
| _SAFE_FUNCTIONS = { | |
| "abs": abs, "round": round, "min": min, "max": max, "sum": sum, | |
| "sqrt": math.sqrt, "log": math.log, "log10": math.log10, | |
| "log2": math.log2, "exp": math.exp, | |
| "sin": math.sin, "cos": math.cos, "tan": math.tan, | |
| "pi": math.pi, "e": math.e, "tau": math.tau, | |
| "ceil": math.ceil, "floor": math.floor, "factorial": math.factorial, | |
| } | |
| # ββ eval_expression ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def eval_expression(*, expr: str) -> dict: | |
| """Safely evaluate a mathematical expression. | |
| Supports: +, -, *, /, **, //, %, sqrt(), log(), sin(), cos(), | |
| pi, e, tau, abs(), round(), min(), max(), factorial(), ceil(), floor(). | |
| Args: | |
| expr: Math expression string (e.g. "sqrt(16) + 2 * pi"). | |
| Returns: | |
| dict with "result" (number) or "error". | |
| """ | |
| import ast | |
| try: | |
| tree = ast.parse(expr.strip(), mode="eval") | |
| except SyntaxError as exc: | |
| return {"error": f"Syntax error: {exc}"} | |
| def _eval(node): | |
| if isinstance(node, ast.Expression): | |
| return _eval(node.body) | |
| if isinstance(node, ast.Constant): | |
| return node.value | |
| if isinstance(node, ast.Name): | |
| name = node.id | |
| if name in _SAFE_FUNCTIONS: | |
| return _SAFE_FUNCTIONS[name] | |
| return {"error": f"Unknown name: {name}"} | |
| if isinstance(node, ast.BinOp): | |
| op_name = type(node.op).__name__ | |
| python_op = { | |
| "Add": "+", "Sub": "-", "Mult": "*", "Div": "/", | |
| "Pow": "**", "Mod": "%", "FloorDiv": "//", | |
| }.get(op_name) | |
| if python_op not in _SAFE_OPS: | |
| return {"error": f"Unsupported operator: {op_name}"} | |
| left = _eval(node.left) | |
| right = _eval(node.right) | |
| if isinstance(left, dict) and "error" in left: | |
| return left | |
| if isinstance(right, dict) and "error" in right: | |
| return right | |
| try: | |
| return _SAFE_OPS[python_op](left, right) | |
| except Exception as exc: | |
| return {"error": f"Compute error: {exc}"} | |
| if isinstance(node, ast.Call): | |
| func = _eval(node.func) | |
| if isinstance(func, dict) and "error" in func: | |
| return func | |
| args = [_eval(a) for a in node.args] | |
| for a in args: | |
| if isinstance(a, dict) and "error" in a: | |
| return a | |
| try: | |
| return func(*args) | |
| except Exception as exc: | |
| return {"error": f"Function error: {exc}"} | |
| if isinstance(node, ast.UnaryOp): | |
| op_name = type(node.op).__name__ | |
| val = _eval(node.operand) | |
| if isinstance(val, dict) and "error" in val: | |
| return val | |
| if op_name == "USub": | |
| return -val | |
| if op_name == "UAdd": | |
| return +val | |
| return {"error": f"Unsupported expression: {type(node).__name__}"} | |
| result = _eval(tree) | |
| if isinstance(result, dict) and "error" in result: | |
| return result | |
| if isinstance(result, float) and result == int(result): | |
| result = int(result) | |
| return {"result": result, "expression": expr} | |
| # ββ convert_unit βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _UNIT_CONVERSIONS = { | |
| # Length | |
| ("m", "km"): 0.001, ("km", "m"): 1000, | |
| ("m", "cm"): 100, ("cm", "m"): 0.01, | |
| ("m", "mm"): 1000, ("mm", "m"): 0.001, | |
| ("km", "mile"): 0.621371, ("mile", "km"): 1.60934, | |
| ("ft", "m"): 0.3048, ("m", "ft"): 3.28084, | |
| ("in", "cm"): 2.54, ("cm", "in"): 0.393701, | |
| # Weight | |
| ("kg", "g"): 1000, ("g", "kg"): 0.001, | |
| ("kg", "lb"): 2.20462, ("lb", "kg"): 0.453592, | |
| ("lb", "oz"): 16, ("oz", "lb"): 0.0625, | |
| # Temperature (special handling) | |
| ("C", "K"): "add:273.15", ("K", "C"): "sub:273.15", | |
| ("C", "F"): "c_to_f", ("F", "C"): "f_to_c", | |
| # Time | |
| ("h", "min"): 60, ("min", "h"): 1/60, | |
| ("min", "s"): 60, ("s", "min"): 1/60, | |
| ("h", "s"): 3600, ("s", "h"): 1/3600, | |
| ("day", "h"): 24, ("h", "day"): 1/24, | |
| # Digital | |
| ("B", "KB"): 1/1024, ("KB", "B"): 1024, | |
| ("KB", "MB"): 1/1024, ("MB", "KB"): 1024, | |
| ("MB", "GB"): 1/1024, ("GB", "MB"): 1024, | |
| ("GB", "TB"): 1/1024, ("TB", "GB"): 1024, | |
| } | |
| def convert_unit(*, value: float, from_unit: str, to_unit: str) -> dict: | |
| """Convert a value between units (length, weight, temperature, time, digital). | |
| Supported units: | |
| Length: m, km, cm, mm, ft, in, mile | |
| Weight: kg, g, lb, oz | |
| Temp: C, F, K | |
| Time: h, min, s, day | |
| Digital: B, KB, MB, GB, TB | |
| Args: | |
| value: Numeric value to convert. | |
| from_unit: Source unit (caseβsensitive). | |
| to_unit: Target unit (caseβsensitive). | |
| Returns: | |
| dict with "result", "from_unit", "to_unit", "original_value" or "error". | |
| """ | |
| fkey = from_unit.strip() | |
| tkey = to_unit.strip() | |
| if fkey == tkey: | |
| return {"result": value, "from_unit": fkey, "to_unit": tkey, | |
| "original_value": value} | |
| factor = _UNIT_CONVERSIONS.get((fkey, tkey)) | |
| if factor is None: | |
| return {"error": f"Unknown conversion: {fkey} β {tkey}. " | |
| f"Supported: {sorted(set(k[0] for k in _UNIT_CONVERSIONS))}"} | |
| if isinstance(factor, str): | |
| if factor == "add:273.15": | |
| result = value + 273.15 | |
| elif factor == "sub:273.15": | |
| result = value - 273.15 | |
| elif factor == "c_to_f": | |
| result = value * 9/5 + 32 | |
| elif factor == "f_to_c": | |
| result = (value - 32) * 5/9 | |
| else: | |
| return {"error": f"Unknown conversion function: {factor}"} | |
| else: | |
| result = value * factor | |
| if isinstance(result, float) and result == int(result): | |
| result = int(result) | |
| return {"result": result, "from_unit": fkey, "to_unit": tkey, | |
| "original_value": value, "formula": f"{value} {fkey} β {result} {tkey}"} | |
| # ββ basic_stats ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def basic_stats(*, numbers: list) -> dict: | |
| """Compute basic statistics for a list of numbers. | |
| Args: | |
| numbers: List of numeric values. | |
| Returns: | |
| dict with: count, sum, min, max, mean, median, stdev, variance. | |
| """ | |
| if not numbers: | |
| return {"error": "Empty list"} | |
| try: | |
| nums = [float(n) for n in numbers] | |
| except (ValueError, TypeError) as exc: | |
| return {"error": f"Invalid number in list: {exc}"} | |
| n = len(nums) | |
| total = sum(nums) | |
| mean_val = total / n | |
| sorted_nums = sorted(nums) | |
| # Median | |
| if n % 2 == 0: | |
| median_val = (sorted_nums[n // 2 - 1] + sorted_nums[n // 2]) / 2 | |
| else: | |
| median_val = sorted_nums[n // 2] | |
| # Standard deviation (population) | |
| variance = sum((x - mean_val) ** 2 for x in nums) / n | |
| stdev = math.sqrt(variance) | |
| return { | |
| "count": n, "sum": total, "min": sorted_nums[0], | |
| "max": sorted_nums[-1], "mean": round(mean_val, 4), | |
| "median": round(median_val, 4), | |
| "stdev": round(stdev, 4), "variance": round(variance, 4), | |
| } | |