Skip to main content
Glama
io.py7.5 kB
import os from typing import Annotated, Optional import math from mcp.server.fastmcp import Context from pydantic import Field from datetime import datetime from chronulus.estimator import NormalizedForecaster from chronulus.estimator import BinaryPredictor from chronulus.prediction import RescaledForecast from chronulus_mcp.assets import get_html_template async def save_forecast( prediction_id: Annotated[str, Field(description="The prediction_id from a prediction result")], output_path: Annotated[str, Field(description="The path where the CSV file should be saved. Should end in .csv")], csv_name: Annotated[str, Field(description="The path where the CSV file should be saved. Should end in .csv")], txt_name: Annotated[str, Field(description="The name of the TXT file to be saved. Should end in .txt")], ctx: Context, y_min: Annotated[float, Field(default=0.0, description="The expected smallest value for the use case. E.g., for product sales, 0 would be the least possible value for sales.")], y_max: Annotated[float, Field(default=1.0, description="The expected largest value for the use case. E.g., for product sales, 0 would be the largest possible value would be given by the user or determined from this history of sales for the product in question or a similar product.")], invert_scale: Annotated[bool, Field(default=False, description="Set this flag to true if the scale of the new units will run in the opposite direction from the inputs.")], ) -> str: """Saves the forecast from a NormalizedForecaster agent to CSV and the explanation to TXT Args: prediction_id (str): The prediction_id for the prediction you would like to rescale as returned by the forecasting agent output_path (str): The path where the CSV and TXT file should be saved. csv_name (str): The name of the CSV file to be saved. Should end in .csv txt_name (str): The name of the TXT file to be saved. Should end in .txt ctx (Context): Context object providing access to MCP capabilities. y_min (float): The expected smallest value for the use case. E.g., for product sales, 0 would be the least possible value for sales. y_max (float): The expected largest value for the use case. E.g., for product sales, 0 would be the largest possible value would be given by the user or determined from this history of sales for the product in question or a similar product. invert_scale (bool): Set this flag to true if the scale of the new units will run in the opposite direction from the inputs. Returns: str: A message confirming the file was saved and its location """ # Get normalized forecast and rescale it _ = await ctx.info(f"Fetching prediction data for {prediction_id}") normalized_forecast = NormalizedForecaster.get_prediction_static(prediction_id, verbose=False) rescaled_forecast = RescaledForecast.from_forecast( forecast=normalized_forecast, y_min=y_min, y_max=y_max, invert_scale=invert_scale ) # Convert to pandas using built-in method df = rescaled_forecast.to_pandas() # Save to CSV df.to_csv(os.path.join(output_path, csv_name), index_label="ds") with open(os.path.join(output_path, txt_name), "w") as f: f.write(normalized_forecast.text) return f"Forecast saved successfully to {output_path}" async def save_prediction_analysis_html( request_id: Annotated[str, Field(description="The request_id from the BinaryPredictor result")], output_path: Annotated[str, Field(description="The path where the HTML file should be saved.")], html_name: Annotated[str, Field(description="The path where the HTML file should be saved.")], title: Annotated[str, Field(description="Title of analysis")], plot_label: Annotated[str, Field(description="Label for the Beta plot")], chronulus_prediction_summary: Annotated[str, Field(description="A summary paragraph distilling prediction results and expert opinions provided by Chronulus")], dist_shape: Annotated[str, Field(description="A one line description of the shape of the distribution of predictions")], dist_shape_interpretation: Annotated[str, Field(description="2-3 sentences interpreting the shape of the distribution of predictions in layman's terms")], #ctx: Context, ) -> str: """Saves the analysis from a BinaryPredictor prediction to an HTML file Args: request_id (str): The request_id from the BinaryPredictor result output_path (str): The path where the CSV and TXT file should be saved. html_name (str): The name of the HTML file to be saved. Should end in .html title (str): Title of analysis plot_label (str): Label for the Beta plot chronulus_prediction_summary (str) : A summary paragraph distilling prediction results and expert opinions provided by Chronulus dist_shape (str) : A one line description of the shape of the distribution of predictions dist_shape_interpretation (str) : A 2-3 sentences interpreting the shape of the distribution of predictions in layman's terms Returns: str: A message confirming the file was saved and its location """ # Get normalized forecast and rescale it #_ = await ctx.info(f"Fetching prediction data for request_id: {request_id}") html = get_html_template("binary_predictor_analysis.html") prediction_set = BinaryPredictor.get_request_predictions_static(request_id, verbose=False) mean = prediction_set.prob_a a, b = prediction_set.beta_params.alpha, prediction_set.beta_params.beta variance = (a*b) / (((a+b)**2)*(a+b+1)) stdev = math.sqrt(variance) divergent = a <= 1 or b <= 1 mode = (a - 1) / (a + b - 2) mode_txt = f"{mode: 16.4f}" if not divergent else 'Diverges' html = html.replace("[TITLE_OF_ANALYSIS]", title) html = html.replace("[PLOT_LABEL]", plot_label) html = html.replace("[CHRONULUS_PREDICTION_SUMMARY]", chronulus_prediction_summary) html = html.replace("[DIST_SHAPE_DESCRIPTION]", dist_shape) html = html.replace("[DIST_SHAPE_INTERPRETATION]", dist_shape_interpretation) html = html.replace("[ALPHA]", f"{a: 16.16f}") html = html.replace("[BETA]", f"{b: 16.16f}") html = html.replace("[MEAN]", f"{mean: 16.4f}") html = html.replace("[VARIANCE]", f"{variance: 16.4f}") html = html.replace("[STDEV]", f"{stdev: 16.4f}") html = html.replace("[MODE]", mode_txt) date = datetime.today().strftime("%B %d, %Y") html = html.replace("[DATE]", date) expert_opinion_list = [] for i, p in enumerate(prediction_set): pos_text = p.opinion_set.positive.text neg_text = p.opinion_set.negative.text pos = f""" <div class="expert-opinion positive-case"> <h3>Expert {i+1} - Positive Case</h3> <pre>{pos_text}</pre> </div> """ neg = f""" <div class="expert-opinion negative-case"> <h3>Expert {i+1} - Negative Case</h3> <pre>{neg_text}</pre> </div> """ expert_opinion_list.append(pos) expert_opinion_list.append(neg) expert_opinions = "\n\n".join(expert_opinion_list) html = html.replace("[EXPERT_OPINIONS]", expert_opinions) with open(os.path.join(output_path, html_name), "w") as f: f.write(html) return f"BinaryPredictor analysis saved successfully to {output_path}"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ChronulusAI/chronulus-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server