{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deploy a custom model with Pulumi\n",
    "\n",
    "<a class=\"md-button\" href=\"https://s3.amazonaws.com/datarobot-doc-assets/deploy-custom-model.ipynb\">Download this notebook</a>\n",
    "\n",
    "This notebook outlines how to use Pulumi to deploy a Scikit-learn classifier. Before proceeding, [download the necessary assets](https://datarobot-doc-assets.s3.us-east-1.amazonaws.com/deploy_custom_model.zip) to execute the tasks in this notebook."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Initialize the environment\n",
    "\n",
    "Start the environment and import the DataRobot library."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "import datarobot as dr\n",
    "\n",
    "os.environ['PULUMI_CONFIG_PASSPHRASE'] = 'default'\n",
    "\n",
    "assert 'DATAROBOT_API_TOKEN' in os.environ, 'Please set the DATAROBOT_API_TOKEN environment variable'\n",
    "assert 'DATAROBOT_ENDPOINT' in os.environ, 'Please set the DATAROBOT_ENDPOINT environment variable'\n",
    "\n",
    "dr.Client()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set up a project\n",
    "\n",
    "Set up functions to create and build or destroy your Pulumi stack."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pulumi import automation as auto\n",
    "\n",
    "\n",
    "def stack_up(project_name: str, stack_name: str, program: callable) -> auto.Stack:\n",
    "    # create (or select if one already exists) a stack that uses our inline program\n",
    "    stack = auto.create_or_select_stack(\n",
    "        stack_name=stack_name, project_name=project_name, program=program\n",
    "    )\n",
    "\n",
    "    stack.refresh(on_output=print)\n",
    "\n",
    "    stack.up(on_output=print)\n",
    "    return stack\n",
    "\n",
    "\n",
    "def destroy_project(stack: auto.Stack):\n",
    "    \"\"\"Destroy pulumi project\"\"\"\n",
    "    stack_name = stack.name\n",
    "    stack.destroy(on_output=print)\n",
    "\n",
    "    stack.workspace.remove_stack(stack_name)\n",
    "    print(f\"stack {stack_name} in project removed\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create a declarative custom model deployment\n",
    "\n",
    "Use this cell to create a custom model deployment. Add your source code to DataRobot, register the model, and then initialize the deployment. The `make_custom_deployment` function below shows how to declaratively do this. You'll see some variant of this across all application templates.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pulumi_datarobot as datarobot\n",
    "import pulumi\n",
    "\n",
    "\n",
    "def make_custom_inference_deployment():\n",
    "    \"\"\"\n",
    "    Deploy a trained model onto DataRobot's prediction environment.\n",
    "\n",
    "    Upload source code to create a custom model version.\n",
    "    Then create a registered model and deploy it to a prediction environment.\n",
    "    \"\"\"\n",
    "\n",
    "    # ID for Python 3.9 Scikit learn drop in environment\n",
    "    base_environment_id = \"5e8c889607389fe0f466c72d\"\n",
    "\n",
    "    # ID for the default prediction server\n",
    "    default_prediction_server_id = \"5dd7fa2274a35f003102f60d\"\n",
    "\n",
    "    custom_model_name = \"App Template Minis - Readmitted Custom Model\"\n",
    "    registered_model_name = \"App Template Minis - Readmitted Registered Model\"\n",
    "    deployment_name = \"App Template Minis - Readmitted Deployed Model\"\n",
    "\n",
    "    deployment_files = [\n",
    "        (\"./model_package/requirements.txt\", \"requirements.txt\"),\n",
    "        (\"./model_package/custom.py\", \"custom.py\"),\n",
    "        (\"./model_package/model.pkl\", \"model.pkl\"),\n",
    "    ]\n",
    "\n",
    "    custom_model = datarobot.CustomModel(\n",
    "        resource_name=custom_model_name,\n",
    "        files=deployment_files,\n",
    "        base_environment_id=base_environment_id,\n",
    "        language=\"python\",\n",
    "        target_type=\"Binary\",\n",
    "        target_name=\"readmitted\",\n",
    "    )\n",
    "\n",
    "    registered_model = datarobot.RegisteredModel(\n",
    "        resource_name=registered_model_name,\n",
    "        custom_model_version_id=custom_model.version_id,\n",
    "    )\n",
    "\n",
    "    deployment = datarobot.Deployment(\n",
    "        resource_name=deployment_name,\n",
    "        label=deployment_name,\n",
    "        registered_model_version_id=registered_model.version_id,\n",
    "        prediction_environment_id=default_prediction_server_id,\n",
    "    )\n",
    "\n",
    "    pulumi.export(\"custom_model_id\", custom_model.id)\n",
    "    pulumi.export(\"registered_model_id\", registered_model.id)\n",
    "    pulumi.export(\"deployment_id\", deployment.id)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Run the Pulumi stack\n",
    "\n",
    "You can now run the Pulumi stack. Doing so takes the files in the `model_package` directory from the downloaded assets, puts them into DataRobot as a custom model, registers that model, and deploys the result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "project_name = \"AppTemplateMinis-CustomInferenceModels\"\n",
    "stack_name = \"MarshallsCustomReadmissionsPredictor\"\n",
    "\n",
    "stack = stack_up(project_name, stack_name, program=make_custom_inference_deployment)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interact with outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from datarobot_predict.deployment import predict\n",
    "import pandas as pd\n",
    "\n",
    "\n",
    "df = pd.read_csv(\n",
    "    \"https://s3.amazonaws.com/datarobot_public_datasets/10k_diabetes.csv\"\n",
    ").tail(100)\n",
    "\n",
    "\n",
    "deployment_id = stack.outputs().get(\"deployment_id\").value\n",
    "deployment = dr.Deployment.get(deployment_id)\n",
    "\n",
    "predict(deployment, data_frame=df).dataframe.head(10).iloc[:, :2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Clear your work\n",
    "\n",
    "You may not be interested in keeping the custom model. Use this cell to shut down the stack, deleting any assets created in DataRobot."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "destroy_project(stack)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Appendix\n",
    "\n",
    "### How does scoring code work?\n",
    "\n",
    "The code below shows what you upload so that DataRobot knows how to interact with the custom model. Since you are deploying a custom inference model with minimal transformations, it only defines two hooks to interact with the model, but you could add [others too](https://docs.datarobot.com/en/docs/api/code-first-tools/drum/custom-model-components.html). Since the example model is a standard Scikit-learn binary classifier, DataRobot can figure out how to interact with it without you defining any hooks. However, most model artifacts require some custom scoring logic, so the example includes a `custom.py` file anyway."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import Code\n",
    "\n",
    "Code(filename=\"./model_package/custom.py\", language=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### What did I deploy?\n",
    "\n",
    "If you're curious how you got the fitted model in the first place, `fit_custom_model.py` shows the dataset and model fitting code. This example trains a random forest binary classifier using the 10K diabetes dataset. The code below is used to train and pickle the model. It's not important for running the template."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import Code\n",
    "\n",
    "Code(filename=\"./fit_custom_model.py\", language=\"python\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
