# AUTOGENERATED! DO NOT EDIT! File to edit: ../../notebooks/API_Model.ipynb.

# %% auto 0
__all__ = ['Model']

# %% ../../notebooks/API_Model.ipynb 4
from typing import *

# %% ../../notebooks/API_Model.ipynb 5
import os

import pandas as pd
from fastcore.foundation import patch

from airt.logger import get_logger, set_level
from airt.helper import (
    get_data,
    post_data,
    delete_data,
    add_ready_column,
    generate_df,
    get_attributes_from_instances,
    add_example_to_docs,
)

from airt.components.client import Client
from airt.components.progress_status import ProgressStatus
from airt.components.prediction import Prediction

# %% ../../notebooks/API_Model.ipynb 7
logger = get_logger(__name__)

# %% ../../notebooks/API_Model.ipynb 10
class Model(ProgressStatus):
    """A class for querying the model training, evaluation, and prediction status.

    The **Model** class is instantiated automatically when the `DataSource.train` method is called on a datasource. Currently,
    it is the only way to instantiate the **Model** class.

    The model is trained to predict a specific event in the future and we assume the input data to have:

    - a column identifying a client (**client_column**). E.g: person, car, business, etc.,
    - a column specifying a type of event to predict (**target_column**). E.g: buy, checkout, etc.,
    - a timestamp column (**timestamp_column**) specifying the time of an occurred event.

    Along with the above mandatory columns, the input data can have additional columns of any type (int, category, float,
    datetime type, etc.,). These additional columns will be used in the model training for making more accurate predictions.

    Finally, we need to know how much ahead we wish to make predictions. This lead time varies widely for each use case
    and can be in minutes for a webshop or even several weeks for a banking product such as a loan.

    As always, the model training and prediction is an asynchronous process and can take a few hours to finish depending
    on the size of your dataset. The progress for the same can be checked by calling the `ProgressStatus.is_ready` method on the **Model**
    instance. Alternatively, you can call the `ProgressStatus.progress_bar` method to monitor the status interactively.
    """

    BASIC_MODEL_COLS = ["uuid", "created", "total_steps", "completed_steps"]

    ALL_MODEL_COLS = BASIC_MODEL_COLS + [
        "datasource",
        "user",
        "client_column",
        "target_column",
        "target",
        "predict_after",
        "timestamp_column",
        "region",
        "cloud_provider",
        "error",
        "disabled",
    ]

    COLS_TO_RENAME = {
        "uuid": "model_uuid",
        "datasource": "datasource_uuid",
        "user": "user_uuid",
    }

    def __init__(
        self,
        uuid: str,
        datasource: Optional[str] = None,
        client_column: Optional[str] = None,
        target_column: Optional[str] = None,
        target: Optional[str] = None,
        predict_after: Optional[str] = None,
        timestamp_column: Optional[str] = None,
        total_steps: Optional[int] = None,
        completed_steps: Optional[int] = None,
        region: Optional[str] = None,
        cloud_provider: Optional[str] = None,
        error: Optional[str] = None,
        disabled: Optional[bool] = None,
        created: Optional[str] = None,
        user: Optional[str] = None,
    ):
        """Constructs a new `Model` instance

        Warning:
            Do not construct this object directly by calling the constructor, please use
            `DataSource.train` method instead.

        Args:
            uuid: Model uuid.
            datasource: DataSource uuid.
            client_column: The column name that uniquely identifies the users/clients.
            target_column: Target column name that indicates the type of the event.
            target: Target event name to train and make predictions. You can pass the target event as a string or as a
                regular expression for predicting more than one event. For example, passing ***checkout** will
                train a model to predict any checkout event.
            predict_after: Time delta in hours of the expected target event.
            timestamp_column: The timestamp column indicating the time of an event. If not passed,
                then the default value **None** will be used.
            total_steps: No of steps needed to complete the model training.
            completed_steps: No of steps completed so far in the model training.
            region: The region name of the cloud provider where the model is stored.
            cloud_provider: The name of the cloud storage provider where the model is stored.
            error: Contains the error message if the training of the model fails.
            disabled: A flag that indicates the model's status. If the model is deleted, then **False** will be set.
            created: Model creation date.
            user: The uuid of the user who created the model.
        """
        self.uuid = uuid
        self.datasource = datasource
        self.client_column = client_column
        self.target_column = target_column
        self.target = target
        self.predict_after = predict_after
        self.timestamp_column = timestamp_column
        self.total_steps = total_steps
        self.completed_steps = completed_steps
        self.region = region
        self.cloud_provider = cloud_provider
        self.error = error
        self.disabled = disabled
        self.created = created
        self.user = user
        ProgressStatus.__init__(self, relative_url=f"/model/{self.uuid}")

    @staticmethod
    def ls(
        offset: int = 0,
        limit: int = 100,
        disabled: bool = False,
        completed: bool = False,
    ) -> List["Model"]:
        """Return the list of Model instances available in the server.

        Args:
            offset: The number of models to offset at the beginning. If None, then the default value **0** will be used.
            limit: The maximum number of models to return from the server. If None,
                then the default value **100** will be used.
            disabled: If set to **True**, then only the deleted models will be returned. Else, the default value
                **False** will be used to return only the list of active models.
            completed: If set to **True**, then only the models that are successfully processed in server will be returned.
                Else, the default value **False** will be used to return all the models.

        Returns:
            A list of Model instances available in the server.

        Raises:
            ConnectionError: If the server address is invalid or not reachable.
        """
        lists = Client._get_data(
            relative_url=f"/model/?disabled={disabled}&completed={completed}&offset={offset}&limit={limit}"
        )

        mx = [
            Model(
                uuid=model["uuid"],
                datasource=model["datasource"],
                client_column=model["client_column"],
                target_column=model["target_column"],
                target=model["target"],
                predict_after=model["predict_after"],
                timestamp_column=model["timestamp_column"],
                total_steps=model["total_steps"],
                completed_steps=model["completed_steps"],
                region=model["region"],
                cloud_provider=model["cloud_provider"],
                error=model["error"],
                disabled=model["disabled"],
                created=model["created"],
                user=model["user"],
            )
            for model in lists
        ]

        return mx

    @staticmethod
    def as_df(mx: List["Model"]) -> pd.DataFrame:
        """Return the details of Model instances as a pandas dataframe.

        Args:
            mx: List of Model instances.

        Returns:
            Details of all the models in a dataframe.

        Raises:
            ConnectionError: If the server address is invalid or not reachable.
        """
        model_lists = get_attributes_from_instances(mx, Model.BASIC_MODEL_COLS)  # type: ignore

        df = generate_df(model_lists, Model.BASIC_MODEL_COLS)

        df = df.rename(columns=Model.COLS_TO_RENAME)

        return add_ready_column(df)

    def details(self) -> pd.DataFrame:
        raise NotImplementedError()

    def delete(self) -> pd.DataFrame:
        raise NotImplementedError()

    def predict(self, data_uuid: Optional[str]) -> "airt.client.Prediction":  # type: ignore
        raise NotImplementedError()

    def evaluate(self) -> pd.DataFrame:
        raise NotImplementedError()

# %% ../../notebooks/API_Model.ipynb 11
def _docstring_example():
    """
    Example:

        ```python
        # Importing necessary libraries
        from datetime import timedelta

        from  airt.client import Client, DataBlob, Model

        # Authenticate
        Client.get_token(username="{fill in username}", password="{fill in password}")

        # Create a datablob
        # In this example, the datablob will be stored in an AWS S3 bucket. The
        # access_key and the secret_key are set in the AWS_ACCESS_KEY_ID and
        # AWS_SECRET_ACCESS_KEY environment variables, and the region is set to
        # eu-west-3; feel free to change the cloud provider and the region to
        # suit your needs.
        db = DataBlob.from_s3(
            uri="{fill in uri}",
            cloud_provider="aws",
            region="eu-west-3"
        )

        # Display the status in a progress bar
        db.progress_bar()

        # Create a datasource
        ds = db.to_datasource(
            file_type="{fill in file_type}",
            index_column="{fill in index_column}",
            sort_by="{fill in sort_by}",
        )

        # Display the status in a progress bar
        ds.progress_bar()

        # Train a model to predicts which users will perform a purchase
        # event ("*purchase") three hours before they actually do it.
        model = ds.train(
            client_column="{fill in client_column}",
            target_column="{fill in target_column}",
            target="*purchase",
            predict_after=timedelta(hours=3)
        )

        # Display the status in a progress bar
        model.progress_bar()

        # Print the details of the newly created model
        print(model.details())

        # Display the details of all models created by the currently
        # logged-in user
        print(Model.as_df(Model.ls()))

        # Evaluate the newly created model
        print(model.evaluate())

        # Run predictions on the newly created model
        prediction = model.predict()

        # Display the prediction status in a progress bar
        prediction.progress_bar()

        # Display details of the predictions
        print(prediction.details())

        # Delete the newly created model
        print(model.delete())
        ```
    """
    pass

# %% ../../notebooks/API_Model.ipynb 13
add_example_to_docs(Model, _docstring_example.__doc__)  # type: ignore
add_example_to_docs(Model.ls, _docstring_example.__doc__)  # type: ignore
add_example_to_docs(Model.as_df, _docstring_example.__doc__)  # type: ignore

# %% ../../notebooks/API_Model.ipynb 18
@patch
def details(self: Model) -> pd.DataFrame:
    """Return the details of a model.

    Returns:
        A pandas DataFrame encapsulating the details of the model.

    Raises:
        ConnectionError: If the server address is invalid or not reachable.
    """

    response = Client._get_data(relative_url=f"/model/{self.uuid}")

    df = pd.DataFrame(response, index=[0])[Model.ALL_MODEL_COLS]

    df = df.rename(columns=Model.COLS_TO_RENAME)

    return add_ready_column(df)

# %% ../../notebooks/API_Model.ipynb 19
add_example_to_docs(Model.details, _docstring_example.__doc__)  # type: ignore

# %% ../../notebooks/API_Model.ipynb 22
@patch
def delete(self: Model) -> pd.DataFrame:
    """Delete a model from the server.

    Returns:
        A pandas DataFrame encapsulating the details of the deleted model.

    Raises:
        ConnectionError: If the server address is invalid or not reachable.
    """

    response = Client._delete_data(relative_url=f"/model/{self.uuid}")

    df = pd.DataFrame(response, index=[0])[Model.BASIC_MODEL_COLS]

    df = df.rename(columns=Model.COLS_TO_RENAME)

    return add_ready_column(df)

# %% ../../notebooks/API_Model.ipynb 23
add_example_to_docs(Model.delete, _docstring_example.__doc__)  # type: ignore

# %% ../../notebooks/API_Model.ipynb 25
@patch
def evaluate(self: Model) -> pd.DataFrame:
    """Return the evaluation metrics of the trained model.

    Currently, this method returns the model's accuracy, precision, and recall. In the
    future, more performance metrics will be added.

    Returns:
        The performance metrics of the trained model as a pandas series.

    Raises:
        ConnectionError: If the server address is invalid or not reachable.
    """
    model_evaluate = Client._get_data(relative_url=f"/model/{self.uuid}/evaluate")
    return pd.DataFrame(dict(model_evaluate), index=[0]).T.rename(columns={0: "eval"})

# %% ../../notebooks/API_Model.ipynb 26
add_example_to_docs(Model.evaluate, _docstring_example.__doc__)  # type: ignore

# %% ../../notebooks/API_Model.ipynb 28
@patch
def predict(self: Model, data_uuid: Optional[int] = 0) -> Prediction:
    """Run predictions against the trained model.

    The progress for the same can be checked by calling the `is_ready` method on the `Model` instance.
    Alternatively, you can call the `progress_bar` method to monitor the status interactively.

    Args:
        data_uuid: The datasource uuid to run the predictions. If not set, then the datasource used for training
            the model will be used for prediction aswell.

    Returns:
        An instance of the `Prediction` class.

    Raises:
        ConnectionError: If the server address is invalid or not reachable.
    """

    req_json = dict(data_uuid=data_uuid) if data_uuid else None

    response = Client._post_data(
        relative_url=f"/model/{self.uuid}/predict", json=req_json
    )

    return Prediction(uuid=response["uuid"], datasource=response["datasource"])

# %% ../../notebooks/API_Model.ipynb 29
add_example_to_docs(Model.predict, _docstring_example.__doc__)  # type: ignore
