muhammad1707's picture
Move master code to main
8421ec4
Raw
History Blame Contribute Delete
6.59 kB
import math
import requests
import ast
import operator
from typing import Union, Dict, Any
# ==============================
# MATH TOOL
# ==============================
def calculate_expression(expression: str) -> Union[float, str]:
"""
Safely evaluate a mathematical expression.
Supported operators: +, -, *, /, **, %, ^ (as power), sqrt, abs, round, sin, cos, tan, log, pi, e
"""
# Safe operators map
operators = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
ast.Mod: operator.mod,
ast.USub: operator.neg,
ast.UAdd: operator.pos,
}
# Safe functions map
functions = {
"sqrt": math.sqrt,
"abs": abs,
"round": round,
"sin": math.sin,
"cos": math.cos,
"tan": math.tan,
"log": math.log,
"max": max,
"min": min,
"ceil": math.ceil,
"floor": math.floor,
"degrees": math.degrees,
"radians": math.radians,
}
# Safe constants
constants = {
"pi": math.pi,
"e": math.e,
"tau": math.tau,
}
def eval_node(node):
if isinstance(node, ast.Num): # < 3.8
return node.n
elif isinstance(node, ast.Constant): # >= 3.8
if isinstance(node.value, (int, float)):
return node.value
raise ValueError(f"Unsupported constant type: {type(node.value)}")
elif isinstance(node, ast.BinOp): # <left> <operator> <right>
op = type(node.op)
if op not in operators:
raise ValueError(f"Unsupported operator: {op}")
return operators[op](eval_node(node.left), eval_node(node.right))
elif isinstance(node, ast.UnaryOp): # <operator> <operand> (e.g., -1)
op = type(node.op)
if op not in operators:
raise ValueError(f"Unsupported unary operator: {op}")
return operators[op](eval_node(node.operand))
elif isinstance(node, ast.Call): # Function calls like sqrt(4)
if not isinstance(node.func, ast.Name):
raise ValueError("Only named functions are supported")
if node.func.id not in functions:
raise ValueError(f"Unsupported function: {node.func.id}")
args = [eval_node(arg) for arg in node.args]
return functions[node.func.id](*args)
elif isinstance(node, ast.Name): # Variables/Constants
if node.id in constants:
return constants[node.id]
raise ValueError(f"Unsupported name: {node.id}")
else:
raise TypeError(f"Unsupported expression node: {type(node)}")
try:
# Pre-process: replace ^ with ** for power
expression = expression.replace("^", "**")
node = ast.parse(expression, mode='eval')
result = eval_node(node.body)
return float(result)
except Exception as e:
return f"Error calculating '{expression}': {str(e)}"
# ==============================
# WEATHER TOOL
# ==============================
def get_current_weather(location: str) -> Dict[str, Any]:
"""
Get current weather for a specific city using Open-Meteo API.
Returns temperature (C), humidity, wind speed, etc.
"""
try:
# 1. Geocoding
geo_url = "https://geocoding-api.open-meteo.com/v1/search"
geo_params = {"name": location, "count": 1, "language": "en", "format": "json"}
geo_res = requests.get(geo_url, params=geo_params, timeout=5)
geo_data = geo_res.json()
if not geo_data.get("results"):
return {"error": f"City '{location}' not found."}
location = geo_data["results"][0]
lat = location["latitude"]
lon = location["longitude"]
city_name = location["name"]
country = location.get("country", "")
# 2. Weather Data
weather_url = "https://api.open-meteo.com/v1/forecast"
weather_params = {
"latitude": lat,
"longitude": lon,
"current": "temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,rain,showers,snowfall,weather_code,cloud_cover,wind_speed_10m",
"timezone": "auto"
}
w_res = requests.get(weather_url, params=weather_params, timeout=5)
w_data = w_res.json()
if "current" not in w_data:
return {"error": "{location} Weather data not available."}
current = w_data["current"]
current_units = w_data["current_units"]
# Decode WMO Weather Code
# source: https://open-meteo.com/en/docs
wmo_code = current["weather_code"]
condition = "Unknown"
if wmo_code == 0: condition = "Clear sky"
elif 1 <= wmo_code <= 3: condition = "Mainly clear, partly cloudy, and overcast"
elif 45 <= wmo_code <= 48: condition = "Fog and depositing rime fog"
elif 51 <= wmo_code <= 55: condition = "Drizzle: Light, moderate, and dense intensity"
elif 56 <= wmo_code <= 57: condition = "Freezing Drizzle: Light and dense intensity"
elif 61 <= wmo_code <= 65: condition = "Rain: Slight, moderate and heavy intensity"
elif 66 <= wmo_code <= 67: condition = "Freezing Rain: Light and heavy intensity"
elif 71 <= wmo_code <= 75: condition = "Snow fall: Slight, moderate, and heavy intensity"
elif 77: condition = "Snow grains"
elif 80 <= wmo_code <= 82: condition = "Rain showers: Slight, moderate, and violent"
elif 85 <= wmo_code <= 86: condition = "Snow showers slight and heavy"
elif 95: condition = "Thunderstorm: Slight or moderate"
elif 96 <= wmo_code <= 99: condition = "Thunderstorm with slight and heavy hail"
return {
"location": f"{city_name}, {country}",
"temperature": f"{current['temperature_2m']} {current_units['temperature_2m']}",
"feels_like": f"{current['apparent_temperature']} {current_units['apparent_temperature']}",
"humidity": f"{current['relative_humidity_2m']} {current_units['relative_humidity_2m']}",
"wind_speed": f"{current['wind_speed_10m']} {current_units['wind_speed_10m']}",
"condition": condition,
"cloud_cover": f"{current['cloud_cover']} {current_units['cloud_cover']}",
"timestamp": current["time"]
}
except Exception as e:
return {"error": f"Failed to fetch weather: {str(e)}"}