{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Amazon Fraud Detector from End to End for Account Takeover Insights \n", "### Un-supervised fraud detection \n", "-------\n", "- [Introduction](#Introduction)\n", "- [Data preparation](#Data_preparation)\n", "- [Set up AWS credentials & permissions](#Set_up_AWS_credentials_permissions)\n", "- [Plan](#Plan)\n", "\n", "\n", "## Introduction\n", "-------\n", "\n", "Amazon Fraud Detector (AFD) is a fully managed service that makes it easy to identify potentially fraudulent online activities such as online payment fraud, creation of fake accounts, and account takeovers. Fraud Detector capitalizes on the latest advances in machine learning (ML) and 20 years of fraud detection expertise from AWS and Amazon.com to automatically identify potentially fraudulent activities so you can catch more fraud faster. \n", "\n", "In this notebook, we'll use the Amazon Fraud Detector API to define an entity and an event of interest and send events to AFD from a CSV file stored in S3. Next, we'll train a model with Account Takeover Insight template using the events we have sent. After that, we'll derive some rules and create a \"detector\" by combining our entity, event, model, and rules into a single endpoint. Finally, we'll apply the detector to a sample of our data to identify potentially fraudulent events.\n", "\n", "After running this notebook you should be able to: \n", "\n", "- Define an Entity and an Event\n", "- Send Events to AFD\n", "- Train a Machine Learning (ML) Model\n", "- Author Rules to identify potential fraud based on the model's score\n", "- Create a Fraud Detector\n", "- Apply the Detector's \"predict\" function, to generate a model score and rule outcomes on data \n", "\n", "If you would like to know more, please check out [Fraud Detector's Documentation](https://docs.aws.amazon.com/frauddetector/). \n", "\n", "\n", "## Data preparation \n", "------\n", "\n", "Before you train a Transaction Fraud Insights model, ensure that you have stored at least 10,000 records in your training dataset within Amazon Fraud Detector. We recommend that you collect at least 3-6 weeks of historic data with at least 1,500 unique entities. The Transaction Fraud Insights model will calculate aggregates likes account’s age, transaction counts based on the account’s history. Therefore, providing the full history of the accounts will help the model to calculate the aggregates correctly to capture the fraud patterns.\n", "\n", "In addition to the event variables, the training dataset must contain the following headers:\n", "* ENTITY_TYPE - Who is performing the activity, e.g. customer. Currently, AFD only support one ENTITY_TYPE per EVENT_TYPE. That column should contain a single value. \n", "* ENTITY_ID - An identifier for who is performing the activity, e.g. customer id\n", "* EVENT_ID - An identifier for the event, e.g. order id\n", "* EVENT_TIMESTAMP - The timestamp of when the event occurred. The timestamp must be in ISO 8601 standard in UTC.\n", "* EVENT_LABEL - Classifies the event as fraudulent or legitimate. The values in the column must correspond to the values defined in the event type.\n", "* LABEL_TIMESTAMP - Timestamp of when the label is updated. LABEL_TIMESTAMP is required if EVENT_LABEL is included. If you do not have that data, you can duplicate the EVENT_TIMESTAMP column and rename it as LABEL_TIMESTAMP.\n", "\n", "\n", "\n", "## Set up AWS credentials & permissions \n", "----\n", "\n", "https://docs.aws.amazon.com/frauddetector/latest/ug/set-up.html\n", "\n", "To use Amazon Fraud Detector, you have to set up permissions that allow access to the Amazon Fraud Detector console and API operations. You also have to allow Amazon Fraud Detector to perform tasks on your behalf and to access resources that you own. We recommend creating an AWS Identify and Access Management (IAM) user with access restricted to Amazon Fraud Detector operations and required permissions. You can add other permissions as needed.\n", "\n", "The following policies provide the required permission to use Amazon Fraud Detector. If you are using SageMaker Notebook Instance, add the following two policies to the Instance's IAM role and restart your kernel:\n", "\n", "- *AmazonFraudDetectorFullAccessPolicy* \n", " Allows you to perform the following actions: \n", " - Access all Amazon Fraud Detector resources \n", " - List and describe all model endpoints in Amazon SageMaker \n", " - List all IAM roles in the account \n", " - List all Amazon S3 buckets \n", " - Allow IAM Pass Role to pass a role to Amazon Fraud Detector \n", "\n", "- *AmazonS3FullAccess* \n", " Allows full access to Amazon S3. This is required to upload training files to S3.\n", " \n", " \n", " \n", "## Plan\n", "------\n", "A *Detector* contains the event, model(s) and rule(s) for a particular type of fraud that you want to detect. We'll use the following 8 steps to plan a Fraud Detector:\n", "\n", "1. [Setup notebook](#setup_notebook)
\n", " a. Name the major components: Event, Entity, Model, Detector
\n", " b. Specify the MODEL_TYPE you want to train
\n", " c. Plug in your S3 Bucket and CSV File\n", " \n", "2. [Load and profile your dataset](#load_and_profile_your_data)
\n", " a. This will give you an idea of what your dataset contains
\n", " b. This will also identify the variables and labels that will need to be created to define your event\n", "\n", "3. [Create event variables and labels](#create_event_variables_and_labels)
\n", " a. This will create the variables and labels in fraud detector \n", "\n", "4. [Define your Entity and Event Type](#define_your_entity_and_event_type)
\n", " a. What is activity that you are detecting? That's likely your Event Type (e.g. transaction)
\n", " b. Who is performing this activity? That's likely your Entity (e.g. customer)\n", "\n", "5. [Send events](#send_events)
\n", " a. This will ingest your events to AFD \n", " \n", "6. [Create and train your model](#create_and_train_your_model)\t\n", " a. Model training takes anywhere from 45-60 minutes. Once complete you need to promote your endpoint
\n", " b. Promote your model\n", "\n", "7. [Create a Fraud Detector, generate Rules and assemble your Detector](#create_detector)
\n", " a. Create your detector
\n", " b. Define outcomes, e.g. fraud, investigate and approve
\n", " c. Create rules based on your model scores
\n", " d. Assemble your detector: combine your rule(s) and model into a \"detector\"\n", "\n", "8. [Make predictions](#make_predictions)
\n", " a. Interactively call predict API on a handful of records " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Current boto3 version: 1.24.36\n" ] } ], "source": [ "# -- check boto3 version, if it is lower than 1.18.59, update it and restart the kernel --\n", "import boto3\n", "import os\n", "\n", "current_boto3_version = boto3.__version__\n", "print('Current boto3 version:', current_boto3_version)\n", "\n", "if current_boto3_version < '1.24.36':\n", " print('update boto3...')\n", " %pip install 'boto3>=1.24.36'\n", " os._exit(00) " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# -- import packages --\n", "import boto3\n", "import time\n", "import logging\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from multiprocessing import Pool\n", "from datetime import datetime, date\n", "from dateutil.relativedelta import relativedelta\n", "import csv, codecs\n", "from sklearn.metrics import roc_curve, roc_auc_score, auc, roc_auc_score\n", "\n", "# -- for display --\n", "from IPython.core.display import display, HTML\n", "from IPython.display import clear_output\n", "display(HTML(\"\"))\n", "pd.set_option('display.max_rows', 500)\n", "pd.set_option('display.max_columns', 500)\n", "pd.set_option('display.width', 1000)\n", "%matplotlib inline " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# -- initialize the AFD client --\n", "client = boto3.client('frauddetector')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1. Setup notebook \n", "-----\n", "\n", "***To get started***\n", "\n", "1. Name the major components of Fraud Detector\n", "2. Specify the MODEL_TYPE you want to train: ACCOUNT_TAKEOVER_INSIGHTS. \n", "2. Plug in your S3 Bucket and CSV file path\n", "\n", "Then you can interactively exeucte the code cells in the notebook, no need to change anything unless you want to. \n", "\n", "\n", "
Major Fraud Detector Components \n", " \n", "- **EVENT_TYPE** is a business activity that you want evaluated for fraud risk \n", "- **ENTITY_TYPE** represents the \"what or who\" that is performing the event you want to evaluate\n", "- **MODEL_NAME** is the name of your supervised machine learning model that Fraud Detector trains on your behalf\n", "- **DETECTOR_NAME** is the name of the detector that contains the detection logic (model and rules) that you apply to events that you want to evaluate for fraud\n", "\n", "
\n", "\n", "\n", "Identify the following assets:\n", "\n", "
Bucket, File and ARN Role\n", "\n", "- **S3_BUCKET** is the name of the bucket where your file lives\n", "- **S3_FILE** is the URL to your s3 file\n", "- **ARN_ROLE** is the role Fraud Detector use to access your data in s3 bucket\n", "\n", "
\n", "\n", "\n", "_**Note**: To use Amazon Fraud Detector, you have to set up permissions that allow access to the Amazon Fraud Detector console and API operations. You also have to allow Amazon Fraud Detector to perform tasks on your behalf and to access resources that you own. We recommend creating an AWS Identify and Access Management (IAM) user with access restricted to. Amazon Fraud Detector operations and required permissions. You can add other permissions as needed. See \"Create an IAM User and Assign Required Permissions\" in the user's guide: https://docs.aws.amazon.com/frauddetector/latest/ug/set-up.html_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "Update following section: Define event, model, detector names, and data location\n", "
" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# -- this is all you need to fill out, once complete simply interactively run each code cell -- \n", "EVENT_TYPE = \"your_event_name\"\n", "EVENT_DESC = \"your event description\"\n", "\n", "MODEL_NAME = \"your_model_name\"\n", "MODEL_DESC = \"your model description\"\n", "\n", "DETECTOR_NAME = \"your_detector_name\" \n", "DETECTOR_DESC = \"your detector description\"\n", "\n", "MODEL_TYPE = \"ACCOUNT_TAKEOVER_INSIGHTS\" \n", "\n", "S3_BUCKET = \"your-s3-bucket-with-data\" \n", "S3_FILE = \"path-to-your-data-file\" \n", "ARN_ROLE = \"your-arn-role\"\n", "\n", "# -- percentage of data used in model training (by default: 80%). \n", "TRAINING_PERC = 0.8 " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Header names in your CSV files" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "EVENT_ID\n", "ENTITY_ID\n", "ENTITY_TYPE\n", "EVENT_TIMESTAMP\n", "EVENT_LABEL\n", "LABEL_TIMESTAMP\n", "ip\n", "useragent\n", "fp\n", "session_id\n", "are_credentials_valid\n" ] } ], "source": [ "s3 = boto3.resource('s3')\n", "obj = s3.Object(S3_BUCKET, S3_FILE)\n", "body = obj.get()['Body']\n", "reader = csv.DictReader(codecs.getreader(\"utf-8\")(body))\n", "header_names = reader.fieldnames\n", "for item in header_names:\n", " print(item)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Map header names to mandatory/optional variable types" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "Update following section: Fill the mapping with CSV header names printed above. \n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# -- map your data to the variable types --\n", "# Except the fixed-naming columns (EVENT_ID, ENTITY_ID, ENTITY_TYPE, EVENT_TIMESTAMP), use the VARIABLES_MAP dictionary \n", "# to map the variable type to the column names in your data.\n", "# Mandatory variable types include: IP_ADDRESS, USERAGENT, ARE_CREDENTIALS_VALID\n", "# Optional variable types: FINGERPRINT, SESSION_ID\n", "\n", "VARIABLES_MAP = {\n", " # Mandatory variables\n", " \"IP_ADDRESS\": \"ip\", # e.g. ip\n", " \"USERAGENT\": \"useragent\", # e.g. user_agent\n", " \"ARE_CREDENTIALS_VALID\": \"are_credentials_valid\", # e.g. are_credentials_valid\n", " \n", " # Optional variables\n", " \"FINGERPRINT\": \"fp\", # e.g. fingerprint\n", " \"SESSION_ID\": \"session_id\" # e.g. session_id\n", "}\n", "\n", "for c in VARIABLES_MAP.keys():\n", " if VARIABLES_MAP[c]!=\"\" and VARIABLES_MAP[c] not in header_names:\n", " raise ValueError(f'Variable {VARIABLES_MAP[c]} (type {c}) not in CSV header!')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2. Load and profile your dataset \n", "-----\n", "\n", "The functions below will: 1) profile your data, creating descriptive statististics, 2) perform basic data quality checks (nulls, unique variables, etc.), and 3) return summary statistics and the EVENT and MODEL schemas used to define your EVENT_TYPE and train your model. \n", "\n", "\n", "_**Important Note**: The functions below provides a layman guess for the fraud/legit labels and variable mapping. Please review the summary stats, event variables, event labels and training data schema and make sure they are aligned with how you want to use the data. You can always manually modify them if needed._\n", "\n", "
💡 summary stats, event variables, event labels and training data schema \n", "\n", "- summary stats: data quality and summary statistics of the data; used to create variables of the specific feature types\n", "- event variables: variables associated with the specific event type; used when creating event type and sending events\n", "- event labels: labels associated with the event type; used when creating event type\n", "- training data schema: define the variables to build the model, labels to be used as fraud/legit, and how to treat the unlabeled events; By default, we identify the rare event as fraud, and the rest as not-fraud. If you have more than 2 labels in the data or want to map them in a different way, you can manually modify the training data schema \n", "\n", "
\n", "\n" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "# --- no changes; just run this code block ---\n", "def summary_stats(df, variables_map):\n", " \"\"\"\n", " Generate summary statistics for a pandas data frame \n", " \"\"\"\n", " rowcnt = len(df)\n", " \n", " # -- calculating data statistics and data types -- \n", " df_s1 = df.agg(['count', 'nunique']).transpose().reset_index().rename(columns={\"index\":\"feature_name\"})\n", " df_s1[\"null\"] = (rowcnt - df_s1[\"count\"]).astype('int64')\n", " df_s1[\"not_null\"] = rowcnt - df_s1[\"null\"]\n", " df_s1[\"null_pct\"] = df_s1[\"null\"] / rowcnt\n", " df_s1[\"nunique_pct\"] = df_s1['nunique']/ rowcnt\n", "\n", " dt = pd.DataFrame(df.dtypes).reset_index().rename(columns={\"index\":\"feature_name\", 0:\"dtype\"})\n", " df_stats = pd.merge(dt, df_s1, on='feature_name', how='inner').round(4)\n", " df_stats['nunique'] = df_stats['nunique'].astype('int64')\n", " df_stats['count'] = df_stats['count'].astype('int64')\n", " \n", " \n", " # -- variable type mapper: map mandatory variables and variables_map -- \n", " flatten_var_maps = []\n", " for vartype in variables_map.keys():\n", " if isinstance(variables_map[vartype], list):\n", " for var in variables_map[vartype]:\n", " flatten_var_maps.append([vartype, var])\n", " else:\n", " flatten_var_maps.append([vartype, variables_map[vartype]])\n", " \n", " for vartype in ['ENTITY_TYPE','ENTITY_ID','EVENT_ID','EVENT_TIMESTAMP']:\n", " flatten_var_maps.append([vartype, vartype])\n", " for vartype in ['EVENT_LABEL','LABEL_TIMESTAMP']:\n", " if vartype in df.columns:\n", " flatten_var_maps.append([vartype, vartype])\n", "\n", " df_schema = pd.DataFrame(flatten_var_maps, columns = ['feature_type', 'feature_name'])\n", " df_stats = pd.merge(df_stats, df_schema, how = 'left', on = 'feature_name')\n", " \n", " # -- variable type mapper: map the rest types based on data type -- \n", " df_stats.loc[(df_stats['feature_type'].isna())&(df_stats[\"dtype\"] == object), 'feature_type'] = \"CATEGORICAL\"\n", " df_stats.loc[(df_stats['feature_type'].isna())&((df_stats[\"dtype\"] == \"int64\") | (df_stats[\"dtype\"] == \"float64\")), 'feature_type'] = \"NUMERIC\"\n", " \n", " # -- variable validation -- \n", " df_stats['feature_warning'] = \"NO WARNING\"\n", " df_stats.loc[(df_stats[\"nunique\"] != 2) & (df_stats[\"feature_name\"] == \"EVENT_LABEL\"),'feature_warning' ] = \"LABEL WARNING, NON-BINARY EVENT LABEL\"\n", " df_stats.loc[(df_stats[\"nunique_pct\"] > 0.9) & (df_stats['feature_type'] == \"CATEGORICAL\") ,'feature_warning' ] = \"EXCLUDE, GT 90% UNIQUE\"\n", " df_stats.loc[(df_stats[\"null_pct\"] > 0.2) & (df_stats[\"null_pct\"] <= 0.75), 'feature_warning' ] = \"NULL WARNING, GT 20% MISSING\"\n", " df_stats.loc[df_stats[\"null_pct\"] > 0.75,'feature_warning' ] = \"EXCLUDE, GT 75% MISSING\"\n", " df_stats.loc[((df_stats['dtype'] == \"int64\" ) | (df_stats['dtype'] == \"float64\" ) ) & (df_stats['nunique'] < 0.2), 'feature_warning' ] = \"LIKELY CATEGORICAL, NUMERIC w. LOW CARDINALITY\"\n", " return df_stats[['feature_name', 'feature_type', 'dtype', 'count', 'null', 'null_pct', 'nunique', 'nunique_pct', 'feature_warning']]\n", "\n", "\n", "def prepare_schema(df, df_stats, variables_map):\n", " \"\"\"\n", " Prepare schema for following steps\n", " \"\"\"\n", " # -- prepare event variables --\n", " exclude_list = ['ENTITY_TYPE','ENTITY_ID','EVENT_ID','EVENT_TIMESTAMP','EVENT_LABEL','LABEL_TIMESTAMP','UNKNOWN']\n", " event_variables = df_stats.loc[(~df_stats['feature_type'].isin(exclude_list))]['feature_name'].to_list()\n", " \n", " # -- define training_data_schema, Stored events need to specify unlabeledEventsTreatment --\n", " training_data_schema = {\n", " 'modelVariables' : df_stats.loc[~(df_stats['feature_type'].isin(exclude_list))]['feature_name'].to_list(),\n", " }\n", " \n", " if 'EVENT_LABEL' in df.columns:\n", " # -- target -- \n", " label_value_count = df['EVENT_LABEL'].dropna().astype('str', errors='ignore').value_counts()\n", " event_labels = label_value_count.index.unique().tolist() \n", " training_data_schema['labelSchema'] = {\n", " # we assume the rare event as fraud, and the rest as not-fraud. \n", " # if you have more than 2 labels in the data or want to map them in a different way, you can manually modify the training data schema\n", " 'labelMapper' : {\n", " 'FRAUD' : [str(label_value_count.idxmin())],\n", " 'LEGIT' : [i for i in event_labels if i not in [str(label_value_count.idxmin())]]\n", " },\n", " # there are there options for unlabeledEventsTreatment: \n", " 'unlabeledEventsTreatment': 'LEGIT'\n", " }\n", " else:\n", " event_labels = None\n", " \n", " return training_data_schema, event_variables, event_labels\n", "\n", "\n", "def profiling(df, variables_map):\n", " \"\"\"\n", " profiling the input pandas data frame and prepare schema for following steps \n", " \n", " Arguments:\n", " df (DataFrame) - panda's dataframe to create summary statistics for\n", " variables_map (dictionary) - variables map dictionary - key is the variable type and value is the list of variable name\n", " \n", " Returns:\n", " DataFrame of summary statistics, training data schema, event variables and event labels \n", " \"\"\"\n", " df = df.copy()\n", " \n", " # -- check required variables --\n", " required_var_type = ['ENTITY_TYPE','ENTITY_ID','EVENT_ID','EVENT_TIMESTAMP', 'IP_ADDRESS', 'USERAGENT', 'ARE_CREDENTIALS_VALID']\n", " required_var_name = []\n", " for item in required_var_type:\n", " if VARIABLES_MAP.get(item):\n", " required_var_name.append(VARIABLES_MAP.get(item))\n", " else:\n", " required_var_name.append(item)\n", "\n", " missing_required_vars = [i for i in required_var_name if i not in set(df.columns)]\n", " if len(missing_required_vars) != 0:\n", " raise ValueError(f'Required columns {missing_required_vars} are not included in the training data.')\n", " \n", " # -- check if entity types only contains one value --\n", " entity_types = list(df['ENTITY_TYPE'].unique())\n", " if len(entity_types)> 1:\n", " raise ValueError('Currently, Amazon Fraud Detector only support one ENTITY_TYPE per EVENT_TYPE.')\n", " \n", " # -- get data summary --\n", " df_stats = summary_stats(df, variables_map)\n", " \n", " # -- prepare schema for following steps -- \n", " training_data_schema, event_variables, event_labels = prepare_schema(df, df_stats, variables_map)\n", " \n", " print(\"--- summary stats ---\")\n", " print(df_stats)\n", " print(\"\\n\")\n", " print(\"--- event variables ---\")\n", " print(event_variables)\n", " print(\"\\n\")\n", " print(\"--- event labels ---\")\n", " print(event_labels)\n", " print(\"\\n\")\n", " print(\"--- training data schema ---\")\n", " print(training_data_schema)\n", " \n", " return df_stats, training_data_schema, event_variables, event_labels\n" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--- event timestamp ---\n", "earliest event: 2022-01-01T00:00:31Z , latest event: 2022-06-30T23:59:49Z\n", "split training and test set at: 2022-05-27T21:56:51Z\n", "\n", "\n", "--- summary stats ---\n", " feature_name feature_type dtype count null null_pct nunique nunique_pct feature_warning\n", "0 EVENT_ID EVENT_ID object 735683 0 0.0000 735683 1.0000 NO WARNING\n", "1 ENTITY_ID ENTITY_ID object 735683 0 0.0000 100000 0.1359 NO WARNING\n", "2 ENTITY_TYPE ENTITY_TYPE object 735683 0 0.0000 1 0.0000 NO WARNING\n", "3 EVENT_TIMESTAMP EVENT_TIMESTAMP object 735683 0 0.0000 718441 0.9766 NO WARNING\n", "4 EVENT_LABEL EVENT_LABEL object 529 735154 0.9993 2 0.0000 EXCLUDE, GT 75% MISSING\n", "5 LABEL_TIMESTAMP LABEL_TIMESTAMP object 529 735154 0.9993 529 0.0007 EXCLUDE, GT 75% MISSING\n", "6 ip IP_ADDRESS object 735683 0 0.0000 190546 0.2590 NO WARNING\n", "7 useragent USERAGENT object 735683 0 0.0000 15715 0.0214 NO WARNING\n", "8 fp FINGERPRINT object 735683 0 0.0000 270396 0.3675 NO WARNING\n", "9 session_id SESSION_ID object 735683 0 0.0000 426005 0.5791 NO WARNING\n", "10 are_credentials_valid ARE_CREDENTIALS_VALID bool 735683 0 0.0000 2 0.0000 NO WARNING\n", "\n", "\n", "--- event variables ---\n", "['ip', 'useragent', 'fp', 'session_id', 'are_credentials_valid']\n", "\n", "\n", "--- event labels ---\n", "['0', '1']\n", "\n", "\n", "--- training data schema ---\n", "{'modelVariables': ['ip', 'useragent', 'fp', 'session_id', 'are_credentials_valid'], 'labelSchema': {'labelMapper': {'FRAUD': ['1'], 'LEGIT': ['0']}, 'unlabeledEventsTreatment': 'LEGIT'}}\n" ] } ], "source": [ "# -- connect to S3, snag file, and convert to a panda's dataframe --\n", "s3 = boto3.resource('s3')\n", "obj = s3.Object(S3_BUCKET, S3_FILE)\n", "body = obj.get()['Body']\n", "df = pd.read_csv(body, dtype={'EVENT_LABEL': object})\n", " \n", "# -- by default, we split the data into training (80%) and test set (20%) --\n", "earliest_event = df['EVENT_TIMESTAMP'].min()\n", "latest_event = df['EVENT_TIMESTAMP'].max()\n", "if TRAINING_PERC > 1 or TRAINING_PERC <= 0:\n", " raise ValueError(\"TRAINING_PERC should be in (0,1]\")\n", "else:\n", " test_split_row = int(df.shape[0]*TRAINING_PERC)\n", " test_split_time = df.sort_values(by = 'EVENT_TIMESTAMP').iloc[test_split_row]['EVENT_TIMESTAMP']\n", " test_split_time = pd.to_datetime(test_split_time).strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n", "\n", "print(\"--- event timestamp ---\")\n", "print(\"earliest event:\", earliest_event, \", latest event:\", latest_event) \n", "print(\"split training and test set at:\", test_split_time) \n", "print(\"\\n\")\n", "\n", "# -- call profiling function -- \n", "df_stats, training_data_schema, event_variables, event_labels = profiling(df, VARIABLES_MAP)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3. Create event variables and labels \n", "-----\n", "\n", "The following section will automatically create your modeling input variables for you. \n", "\n", "\n", "\n", "
💡 APIs for Creating/Deleting Variables and Labels \n", " \n", "- **create_variable**: Creates a variable in Fraud Detector\n", "- **get_variables**: Gets all of the variables or a specific label if name is provided\n", "- **delete_variables**: Delete a variable; If you have events, models or detectors created using the variable, you need to delect the associated resource first\n", "- **put_label**: Creates a label\n", "- **get_labels**: Gets all labels or a specific label if name is provided\n", "- **delete_label**: Delete a label \n", "
" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " --- model variable dict --\n", "ip has been defined, data type: STRING\n", "useragent has been defined, data type: STRING\n", "fp has been defined, data type: STRING\n", "session_id has been defined, data type: STRING\n", "Creating variable: are_credentials_valid\n", "\n", "\n", "{'ip': 'STRING', 'useragent': 'STRING', 'fp': 'STRING', 'session_id': 'STRING', 'are_credentials_valid': 'ARE_CREDENTIALS_VALID'}\n" ] } ], "source": [ "# -- function to create all your variables --- \n", "def create_variables(features_dict):\n", " \"\"\"\n", " Check if variables exist, if not, adds the variable to Fraud Detector \n", " \n", " Arguments: \n", " features_dict - a dictionary maps your variables to variable type\n", " \"\"\"\n", " for feature in features_dict.keys(): \n", " if features_dict[feature] in ['NUMERIC','PRICE']:\n", " DATA_TYPE = 'FLOAT'\n", " DEFAULT_VALUE = '0.0'\n", " elif features_dict[feature]=='ARE_CREDENTIALS_VALID':\n", " DATA_TYPE = 'BOOLEAN'\n", " DEFAULT_VALUE = 'false'\n", " else:\n", " DATA_TYPE = 'STRING'\n", " DEFAULT_VALUE = ''\n", " \n", " try:\n", " resp = client.get_variables(name = feature)\n", " features_dict[feature] = resp['variables'][0]['dataType']\n", " print(\"{0} has been defined, data type: {1}\".format(feature, features_dict[feature]))\n", " except:\n", " print(\"Creating variable: {0}\".format(feature))\n", " resp = client.create_variable(\n", " name = feature,\n", " dataType = DATA_TYPE,\n", " dataSource ='EVENT',\n", " defaultValue = DEFAULT_VALUE, \n", " description = feature,\n", " variableType = features_dict[feature])\n", " return features_dict\n", "\n", "exclude_list = ['ENTITY_TYPE','ENTITY_ID','EVENT_ID','EVENT_TIMESTAMP','EVENT_LABEL','LABEL_TIMESTAMP','UNKNOWN']\n", "features_dict = df_stats.loc[(~df_stats['feature_type'].isin(exclude_list))].set_index('feature_name')['feature_type'].to_dict()\n", "print(\"\\n --- model variable dict --\")\n", "features_dict = create_variables(features_dict)\n", "print(\"\\n\")\n", "print(features_dict)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 2. Create labels (Optional)" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " --- model label schema dict --\n", "{'FRAUD': ['1'], 'LEGIT': ['0']}\n" ] } ], "source": [ "# -- function to create all your labels --- \n", "def create_label(label_mapper):\n", " \"\"\"\n", " Add labels to Fraud Detector\n", " \n", " Arguments:\n", " label_mapper - a dictionary maps Fraud/Legit to your labels in data\n", " \"\"\"\n", " for label in label_mapper['FRAUD']:\n", " response = client.put_label(\n", " name = label,\n", " description = \"FRAUD\")\n", " \n", " for label in label_mapper['LEGIT']:\n", " response = client.put_label(\n", " name = label,\n", " description = \"LEGIT\")\n", "\n", "if 'labelSchema' in training_data_schema:\n", " label_mapper = training_data_schema['labelSchema']['labelMapper']\n", " print(\"\\n --- model label schema dict --\")\n", " print(label_mapper)\n", " create_label(label_mapper)\n", "else:\n", " print('Labels not defined, skip.')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4. Define your Entity and Event Types \n", "-----\n", " \n", "The following code block will automatically create your entity and event types for you.\n", "\n", "
💡 APIs for Entity and Event Types \n", "\n", "- **put_entity_type**: Creates or updates an entity type. An entity represents who is performing the event. An entity type classifies the entity. Example classifications include customer, merchant, or account\n", "- **get_entity_type**: Gets all entity types or a specific entity type if a name is specified\n", "- **delete_entity_type**: Deletes an entity type. If you have an event type associated with the entity type, you need to delete that event type first \n", "- **put_event_type**: Creates or updates an event type. An event is a business activity that is evaluated for fraud risk. Example event types include online payment transactions, account registrations, and authentications\n", "- **get_event_type**: Gets all event types or a specific event type if name is provided\n", "- **delete_event_typ**e: Delete one event type \n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# --- no changes just run this code block ---\n", "\n", "# -- create entity types if not exist --\n", "entity_type = list(df['ENTITY_TYPE'].unique())[0]\n", "\n", "try:\n", " response = client.get_entity_types(name = entity_type)\n", " print(\"-- entity type exists --\")\n", " print(response)\n", "except:\n", " response = client.put_entity_type(\n", " name = entity_type,\n", " description = entity_type\n", " )\n", " print(\"-- create entity type --\")\n", " print(response)\n", " \n", "\n", "# -- create event type --\n", "try:\n", " response = client.get_event_types(name = EVENT_TYPE)\n", " print(\"\\n-- event type exists --\")\n", " print(response)\n", "except:\n", " response = client.put_event_type (\n", " name = EVENT_TYPE,\n", " eventVariables = event_variables,\n", " labels = event_labels,\n", " eventIngestion = 'ENABLED',\n", " entityTypes = [entity_type])\n", " print(\"\\n-- create event type --\")\n", " print(response)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5. Send Events \n", "\n", "***Send Events use Batch Import***\n", "\n", "The following section will automatically stream in data from S3 bucket to AFD using the Batch Import function. After sending all your events, you can also go to the console, click \"Refresh events data\" to check the number of events, the earliest and the latest event timestamp, etc.\n", "\n", "
💡 APIs for Sending, Getting and Deleting Events \n", "\n", "- **send_event**: Send one event to AFD\n", "- **get_event**: Get the specified event by its eventId\n", "- **delete_events_by_event_type**: Delete all events associated with the event type\n", "- **delete_event**: Deletes the specified event\n", "- **create_batch_import_job**: Batch import all event in a csv file on S3 bucket to AFD\n", "- **get_batch_import_jobs**: Check the status of batch import job \n", "\n", "
\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## -- create batch import job --\n", "client.create_batch_import_job(\n", " jobId = f'batch_import_{EVENT_TYPE}_v2',\n", " inputPath = f's3://{S3_BUCKET}/{S3_FILE}',\n", " outputPath = f's3://{S3_BUCKET}',\n", " eventTypeName = EVENT_TYPE,\n", " iamRoleArn = ARN_ROLE\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# -- takes sometime to import all data into AFD -- \n", "print(\"-- wait for model training to complete --\") \n", "stime = time.time()\n", "while True:\n", " clear_output(wait = True)\n", " response = client.get_batch_import_jobs(jobId = f'batch_import_{EVENT_TYPE}_v2')\n", " status = response['batchImports'][0]['status']\n", " if status in ['IN_PROGRESS', 'IN_PROGRESS_INITIALIZING']:\n", " print(f\"current progress: {(time.time() - stime)/60:{3}.{3}} minutes\")\n", " time.sleep(60) # -- sleep for 60 seconds \n", " else:\n", " print(f\"Model status : {status}\")\n", " break\n", "etime = time.time()\n", "\n", "# -- summarize --\n", "print(\"\\n --- batch import complete --\")\n", "print(\"Elapsed time : %s\" % (etime - stime) + \" seconds \\n\" )\n", "print(response)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "-----\n", "***(Optional) Update Label***\n", "\n", "After streaming in events, you can always update their event labels after investigation.\n", "\n", "
💡 API for Updating Event Labels \n", "\n", "- **update_event_label**: Update one event's label\n", "\n", "
\n", "\n", "For example, we can update the label of the event we just sent from '0' to '1' by running:\n", "\n", "```python\n", "client.update_event_label(\n", " eventId = '3200002a7ee5e7d0dc80ad7906d792373b',\n", " eventTypeName = EVENT_TYPE,\n", " assignedLabel = '1',\n", " labelTimestamp = '2021-05-01T17:01:00Z'\n", ")\n", "```\n", "\n", "Check the updated event:\n", "```python\n", "client.get_event(eventId = '3200002a7ee5e7d0dc80ad7906d792373b', eventTypeName = EVENT_TYPE)\n", "```\n", "\n", "You will see the `currentLabel` has been updated to '1':\n", "\n", "```python\n", "{\n", " 'event': {\n", " 'eventId': '3200002a7ee5e7d0dc80ad7906d792373b',\n", " 'eventTypeName': 'accounttakeover',\n", " 'eventTimestamp': '2020-11-28T14:59:50Z',\n", " 'eventVariables': {\n", " 'ip': '1.1.1.1',\n", " 'user_agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows 95; Trident/3.0)'\n", " },\n", " 'currentLabel': '1',\n", " 'labelTimestamp': '2021-05-01T17:01:00Z',\n", " 'entities': [{'entityType': 'user', 'entityId': '153-04-1621'}]\n", " },\n", " 'ResponseMetadata': ...\n", "}\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 6. Create and train your model \n", "-----\n", "\n", "After sending all your events, we recommend waiting 10-20 minutes to ensure that they are fully ingested by the system. Then, you can start to create and train your model. The following section will automatically train and activate your model for you. By default, we use all features available, if you want to exclude features from training, you can review and modify the `training_data_schema`.\n", " \n", "
💡 APIs for Creating and Training Model \n", "\n", "- **create_model**: Creates a model using the specified model type. Available model types include: ONLINE_FRAUD_INSIGHTS, TRANSACTION_FRAUD_INSIGHTS\n", "- **update_model**: Updates a model. You can update the description attribute using this action\n", "- **get_models**: Gets one or more models. Gets all models for the AWS account if no model type and no model id provided\n", "- **create_model_version**: Creates a version of the model using the specified model type and model id\n", "- **update_model_version**: Updates a model version. Updating a model version retrains an existing model version using updated training data and produces a new minor version of the model. This action creates and trains a new minor version of the model, for example version 1.01, 1.02, 1.03\n", "- **describe_model_versions**: Gets all of the model versions for the specified model type or for the specified model type and model ID. You can also get details for a single, specified model version\n", "- **get_model_version**: Gets the details of the specified model version\n", "- **put_external_model**: Creates or updates an Amazon SageMaker model endpoint. You can also use this action to update the configuration of the model endpoint, including the IAM role and/or the mapped variables\n", "- **get_external_models**: Gets the details for one or more Amazon SageMaker models that have been imported into the service\n", "- **update_model_version_status**: Updates the status of a model version. You can 1) Change the TRAINING_COMPLETE status to ACTIVE, 2) Change ACTIVE to INACTIVE\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# --- no changes; just run this code block ---\n", "# -- create our model --\n", "try:\n", " response = client.create_model(\n", " description = MODEL_DESC,\n", " eventTypeName = EVENT_TYPE,\n", " modelId = MODEL_NAME,\n", " modelType = MODEL_TYPE)\n", " print(\"-- initalize model --\")\n", " print(response)\n", "except Exception:\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# --- no changes; just run this code block ---\n", "# -- initalizes the model, it's now ready to train --\n", "response = client.create_model_version(\n", " modelId = MODEL_NAME,\n", " modelType = MODEL_TYPE,\n", " trainingDataSource = 'INGESTED_EVENTS',\n", " trainingDataSchema = training_data_schema,\n", " ingestedEventsDetail={\n", " 'ingestedEventsTimeWindow': {\n", " 'startTime': df['EVENT_TIMESTAMP'].min(),\n", " 'endTime': test_split_time\n", " }\n", " }\n", ")\n", "model_version = response['modelVersionNumber']\n", "print(\"-- model training --\")\n", "print(response)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# -- model training can take a long time, we'll loop until it's complete -- \n", "print(\"-- wait for model training to complete --\") \n", "stime = time.time()\n", "while True:\n", " clear_output(wait = True)\n", " response = client.get_model_version(modelId = MODEL_NAME, modelType = MODEL_TYPE, modelVersionNumber = model_version)\n", " if response['status'] == 'TRAINING_IN_PROGRESS':\n", " print(f\"current progress: {(time.time() - stime)/60:{3}.{3}} minutes\")\n", " time.sleep(60) # -- sleep for 60 seconds \n", " if response['status'] != 'TRAINING_IN_PROGRESS':\n", " print(\"Model status : \" + response['status'])\n", " break\n", "etime = time.time()\n", "\n", "# -- summarize --\n", "print(\"\\n --- model training complete --\")\n", "print(f\"Elapsed time: {(etime - stime)/60:{3}.{3}} minutes \\n\" )\n", "print(response)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# -- activate the model version --\n", "response = client.update_model_version_status (\n", " modelId = MODEL_NAME,\n", " modelType = MODEL_TYPE,\n", " modelVersionNumber = model_version,\n", " status = 'ACTIVE'\n", ")\n", "print(\"-- activating model --\")\n", "print(response)\n", "\n", "# -- wait until model is active --\n", "print(\"--- waiting until model status is active \")\n", "stime = time.time()\n", "while True:\n", " clear_output(wait=True)\n", " response = client.get_model_version(modelId=MODEL_NAME, modelType = MODEL_TYPE, modelVersionNumber = model_version)\n", " if response['status'] != 'ACTIVE':\n", " print(f\"current progress: {(time.time() - stime)/60:{3}.{3}} minutes\")\n", " time.sleep(60) # sleep for 1 minute \n", " if response['status'] == 'ACTIVE':\n", " print(\"Model status : \" + response['status'])\n", " break\n", " \n", "etime = time.time()\n", "print(f\"Elapsed time: {(etime - stime)/60:{3}.{3}} minutes \\n\" )\n", "print(response)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "trainingMetrics = client.describe_model_versions(\n", " modelId = MODEL_NAME,\n", " modelVersionNumber = model_version,\n", " modelType = MODEL_TYPE,\n", " maxResults = 10\n", ")" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfEAAAHwCAYAAAC2blbYAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAABZ5UlEQVR4nO3dd3xUVd7H8c8vjRpq6EUUUUEUFBQUlY6ABUWkg2Bb3dVdy67uurqr66OPZXefXVddxQYIiAUVRASkRMUuShFBBekgvSQQUs/zx0xCiEkYSGbuTO73/XrllZl779z55aR8c849915zziEiIiKxJ87rAkREROT4KMRFRERilEJcREQkRinERUREYpRCXEREJEYpxEVERGKUQlxERCRGKcQlapjZM2Z233G8zpnZyeGoSaJXqN93M+tmZpsiUZNIpCnExRNmNsbMFhVe5py7yTn3oFc1lQcz62xm75vZbjPbYWavm1mjQuvNzB41s13Bj8fMzILr6pvZK2a2xcz2mdnHZtap0GsvMbNFZrbXzH42s+fMLLnQ+kpm9qKZ7Q+uv6PQugvNLL3IhzOzq4LrrzGzxcHXbgrWlVBovy+Y2XozSzOzb8ysXyTa0ytmdqKZ5ZnZ08WsG2BmS4JttdPM5ptZi+C6+81s0jG8T/tgux8Mfm5fyrZNzGx68Gdrk5ndVGR9DzP7OljXT2Z2Ywn7WRD83ieEWqdEL4W4SPmqDYwDWgAnAGnAS4XW3whcAbQDzgQuBX4VXFcd+BLoANQBJgDvmln14PqawP8AjYHWQFPg8UL7vh9oFXzf7sBdZtYXwDn3kXOuev5H8H3TgdnB11YFbgNSgE5AT+D3wXUJwEaga7CG+4DX8oOrghoN7AGGmlml/IXBnv9E4E4CbXEi8DSQd6xvYGZJwHRgEoGfmwnA9ODy4kwC1gINgEuAh82se3BficBbwLPBuoYA/zSzdkXecwSB76dUFM45fegjLB/AH4E1BILsO+DK4PLWwCEgl0CQ7A0uHw/8Twj7/QOwFdgCXAs44OTgukrA34ENwDbgGaBKcF03YBNwF7A9uI8rgP7AD8Bu4J5C71MJ+FfwfbYEH1c6xjY4G0gr9PwT4MZCz68DPivl9fuBDiWsGwgsL/R8M9Cn0PMHgaklvPYl4KVS3vcO4J1S1i8DrjrK116u7V3W7/sxft/WADcH9zWo0PJBwJJSXnc/MCnE9+gT/J5ZoWUbgL7FbFs9+PXWK7RsHPBy8HGD4PqqhdZ/CQwr9LxmsN07B7dNONbfaX1E34d64hJOa4ALCfzxeACYZGaNnHMrgZuAT12gZ1gr1B0Ge5a/B3oT6HX2KrLJo8ApQHvgZKAJ8JdC6xsClQstfw4YSaD3eyHwFzM7Kbjtnwn8wWtPoOd8LnBvqLUGXQSsKPT8dGBpoedLg8t+ITi0mgSsPtq+zaw2gR76UfdtZlUJhNGEY6i78OsbEGjjYtcXUS7tXQ7f95CZ2YUERjmmAq8R6JXn+xo4zcz+z8y6FxolKWlfM83sjyWsPh1Y5oIJG7SM4n8erMjn/MdtAZxz24BXgLFmFm9m5xEYkSl8yOph4L/Az6XVLDHG6/8i9OGfD2AJMCD4eAywqMj68RylJw68CDxS6PkpBHtkBP6oHQBaFlp/HrA2+LgbkAHEB58nB1/bqdD2i4Ergo/XAP0LrbsYWHcMX++ZBHqbFxZalgucVuh5q2ANVuS1NYDlwJ9K2HdvAsO9pwSfNwvup3KRbX5RLzCKwLCslbDvsQR60CnFrEsE5gHPhvD1l1t7l8P3PeSeOPA88Hah/WQD9Qut70wg3HcQGFEaD1QPrruf0Hvi91FkpASYDNxfwvaLgP8Q+Kfo7ODP1veF1l9GYOQgJ/hxQ6F1HQn8/iUQONSjnngF+VBPXMLGzEYHJwDtNbO9BHoNKWXcbWMCx2fzrS/0uB6BY7uLC73n7ODyfLucc7nBxxnBz9sKrc8gMHSZ/16F978+uOyogsdO3wN+55z7qNCqdAIBna8GkO6Cf2mDr60CvENgmP1/i9l3Z2AKgWHeHwrtN39/hfedVkx51wATC79noX1fATwC9HPO7SyyLg54GcgCbilmv8Upr/Yu6/c9JMG2v5pAmOKc+5TAEPfw/G2cc5855wY75+oRGE24iMAowrEq+rMAJX/PAEYQOAa/kUCPejKBf7Yws9OAVwmMGiQR6M3fFZwMGUfguP3vnHM5x1GnRDGFuISFmZ1AYOj0FqCuCwyZf8vh4cDjvQfuVgK9znzNCz3eSSAUTnfO1Qp+1HSBiVzHYwuBIcnC77XlaC8Kfu3zgAedcy8XWb2CwFBxvnYUGpYOTqJ6m8Cx0l9RhJmdBcwArnXOzc9f7pzbQ6BtStx38PXNCPRMJxaz774EvmeXOeeWF1lnwAsEjr1e5ZzL/uVXXmaltXekvu9XEgjSp4Mz/H8mMDQ/uriNnXNfAm8SHNY+RiuAM4Ntm+9MSjhM4Zxb75y71DlXzznXCagLfBFc3ZZAr3yOcy7POfc98C7QL/j1dAReDX49XwZfsyl46EBimEJcwqUagaDeAWBmYznyD902oGkpM3FL8howxszaBI/t/jV/hXMuj0AI/Z+Z1Q++bxMzu/g4v4ZXgHvNrJ6ZpRA4xlrq6UNm1gRYADzlnHummE0mAncE62pMYJbz+OBrE4E3CATS6ODXU3jfbQn0MG91zr1Twr7vNbPawZ7ZDfn7LmQU8Ilzbk2Rffcg0LO7yjn3Bb/0XwITEi9zzmUUs748lNbe5fZ9N7PxZja+hBquITB0fwaB4+vtgS5AezM7w8wuMLMbCr3PacDlwGfH8fWmEji88lsLnMaXP7qxoIS6W5tZspklmdlIAhPj/hlc/Q3QKniamZlZSwJnICwF9hEYycj/evoHX9MB+Pw46pZo4vV4vj4q7gfwEIHjdjsJ/LH5ALg+uC6JQE9hN7AzuGw8oc1O/yOByTnFzVKuTGACz08EZnavBH4bXNeNQsdGCRwfdECLQssWASML7esJAr3ArcHHlY9S21+D+0wv/FFovQGPBb/u3cHHFlzXNfjag0Vef2Fw/UsETmUqvG5FoX1XIhBA+wn8k3RHMfWtAq4rZvlCAsdRC+/7veC6E4J1HSqyfsRR2qJc27scv+/zKXS8uNDyJsE2OKOYdbMIzH5vS+BQx7ZgG6wjMKkuMbjd/RQ6Jk7gkMo9pbTRWQTmBWQQmDR3VqF1I4p8f28j8E/xgWC7dSyyr8EERrvSCAyzPwrEFfOeLdAx8Qrzkf/HQ0SkwguO/CwFznThOSQgElEKcRERkRilY+ISdczsHvvlJULTzew9r2uD6K8vktQWIt5ST1xERCRGqScuIiISo2LuQvgpKSmuRYsW5ba/AwcOUK1atXLbn1+pHctObVh2asOyUxuWXTjacPHixTtd4AJDR4i5EG/RogVfffVVue0vNTWVbt26ldv+/ErtWHZqw7JTG5ad2rDswtGGZra+uOUaThcREYlRCnEREZEYpRAXERGJUQpxERGRGKUQFxERiVEKcRERkRilEBcREYlRCnEREZEYpRAXERGJUQpxERGRGKUQFxERiVEKcRERkRilEBcREYlRCnEREZEYFbYQN7MXzWy7mX1bwnozsyfMbLWZLTOzs8NVi4iISEUUzp74eKBvKev7Aa2CHzcC/w1jLSIiIhVOQrh27Jz70MxalLLJAGCic84Bn5lZLTNr5JzbGq6aoklmdiYLv1/IoexDXpdSsuyDsH89ZO0/6qbrN6xn78E5ESiq4lIblp3asOzUhmXjHGzYuJ5TW9WiUZP2YX+/sIV4CJoAGws93xRc5osQf3jWw/xt5t+8LqN8Lfa6gApAbVh2asOyUxsenwPJMP9qOD+VFie0YEAFD3ErZpkrdkOzGwkMudOgQQNSU1PLrYj09PRy3V+oPlz2IQCn1T2NulXqHtc+4nMPEecyScreQ6XsHZjLxVxuudXoMHLjq5IXV+mo2+Y5R5wV9y2VUKkNy05tWHZqw+OTsb86X7w3kIN7a1F7cX/27MiOSLZ4GeKbgGaFnjcFthS3oXNuHDAOoGPHjq5bt27lVkRqairlub9Q/e2rQC/8idFP0LtN75I3PLQHvv43rH4LMnYeXp59EDL3/nL7yrWhfgewo0x3iIuHOm2gzmmBx8nNoEEHSKhSaJvEwLoQeNWOFYnasOzUhmWnNjx269btpUePCRzcu5f27Rty//3nM2BAKX/Xy5GXIT4DuMXMpgKdgH1+OR4OsDM9EMj1qtf75cq8HFj6LCx7JnhMOq34nVStD7VaQY0T4KxbofYpUKlmyMErIiJl98ILX7N27V7OOacxc+aMZOnSzyP23mELcTN7BegGpJjZJuCvQCKAc+4ZYBbQH1gNHATGhquWaLQjfQcAKdVTDi/cuQK+fAzWTIfMfYeXN+sG594DddscXhaXEAhxDXuJiHjqgQe6k5xciV/9qgM1a1aO6HuHc3b6sKOsd8BvwvX+0cw5V9ATT0kOhvjKKTDnWsjNDDyv0xrOfwAadQoMdSusRUSixqpVO6lXryp161YlLs64664untTh5XC6b+3L2EdObg7VK1Wncs5B+Oox+PQBcHnQ9lo4949Qu5XXZYqISDGWLv2ZXr1epnnzmsyfP5patSLb+y5MIe6BHWmBofR61evCK51hz4+BFW2vg4uf97AyEREpzddfb6V375fZvTuDjh0bU6mSt3OQFOIe2Ll/GwApGVtgTzZUrgsd74Czb/O2MBERKdHnn2/i4osnsW9fJpdddgqvv341lSp5G6MKcQ/s+PyfANSz7MAx78umQXITj6sSEZGSfPzxBvr1m0xaWhYDB7bmlVeuIinJ+zOBFOLl5LHZj7Fiy4qjbOVg+zJ+2rIUgHrNOsHQRYGZ5iIiEpV++GEXF188iQMHshk6tC0TJ15BYqL3AQ4K8XKxZvsa7p529zG/ruWplyjARUSiXKtWdRgx4gwyMnJ46aUBxMdHz128lSDlYNeBXQCcmHIif73sr7/cYNvXsPw5yMmAhKrQ7iaqNunMJWdcEuFKRUQkVHl5jrg4w8z4738vxTkXVQEOCvFysT8jcJevE1NO5Jrzrzly5Z7V8NUtUC0DGp0H/SZC7ZM9qFJEREI1ffoqHnroI957b0TBueDF3/LDW9H1L0WM2n8oEOI1Ktc4csWhvTBzMGSnwymDYNjHCnARkSj3+usrGDTodb78cgsTJy71upxSqSdeDvJ74jWqBEM8Kx02fQDzfg1pG6DmSdDneV11TUQkyk2ZspxRo94iL89x991duO22zl6XVCqFeDko6ImTDW8PgDUzDq+s1x6umB64MYmIiEStCROWMHbsdJyD++67iAce6IZFeedLIV4OCnria6bBnqzALTwr1YIzrocuD+quYiIiUe655xbzq1/NxDl48MHu3HvvRV6XFBKFeDko6Im7LDjp0sDQebUGHlclIiKhWr58O87BY4/14g9/8OZmJsdDIV5W6+ez/+unAKiRkAAXvxC4RaiIiMSMf/2rLwMGnErPnid5Xcox0ez0ssjLgTnXsj/zIAA1zhyrABcRiRHPP/81O3YcACAuzmIuwEEhXjZzr+eJ9Rt4JTCaTo1Wl3lbj4iIHJVzjr/97QNuuOEd+vadTE5OntclHTcNpx+vb1+CFROYmnZ40VnNzvKuHhEROSrnHPfeu4CHH15EXJxx222dSEiI3f6sQvx4ZKXD/FsA2JdYCzL2svjexTSt09TbukREpETOOe66633+/vdPiY83Jk8eyJAhbb0uq0wU4sfjx2mQEzgOvs+qAHtJqZ7ibU0iIlIi5xy33TabJ574gsTEOKZOHcTAga29LqvMYncMwUsrxgc+936O/ZnpANSsoou5iIhEq2nTVvLEE1+QlBTPtGmDK0SAg0L82O1bCxtTIaEKua0GknYocFA8uXKyp2WJiEjJrrqqNbff3pnp04dy2WWnel1OudFw+rFaMTHwudVA0lzgf6DkysnExen/IRGRaJKTk8f+/ZnUqVMFM+Of/7zY65LKnZLnWLg8+G5C4PHpYwout6qhdBGR6JKdncuIEW/Stet4du486HU5YaMQPxYbUwPD6cnNoFl39mXsAxTiIiLRJCsrlyFD3uC111awYcM+1q7d43VJYaPh9GPxzX8Cn9teB3HxBSFecAtSERHx1KFDOQwa9BrvvvsjtWpVZu7ckZxzThOvywobhXgJvt38Ldv3bz+84MBWWDYdLB6qtIWVC/hi3ReAeuIiItHg4MFsrrzyVebOXUPdulV4//1RnHVWI6/LCiuFeDE+Wf0JXR4t6S42ufD0oCOW1KpSK+w1iYhIyQ4dyuHSS6ewcOE66tevxrx5ozjjjIp/N0mFeDHW7lwLQP3k+pze+HRI3wx7fgisbNABkg4PnyclJHFrj1u9KFNERIIqVYrnzDMbsHLlThYsGE3r1vW8LikiFOLFyMrNAqD/Gf15acjj8FxzSAZOHwt9X/S2OBER+QUz4//+72L++McLaNiwutflRIxmpxcjKycQ4kkJSfDTTMjJgOY9FeAiIlFk9+4MRox4k+3bA7cTNTNfBTioJ16sghCPT4Kf3gksPPlKDysSEZHCdu48SK9eE1m6dBsHDmTx9ttDvS7JEwrxYuQPpyfFxcO6uYGFLXWvcBGRaLBtWzo9e05kxYodnHJKXZ56qr/XJXlGIV6M/J544rp3ITEd6p8FNZp7XJWIiGzZkkbPnhNZtWonbdrUY968UTRq5N97V+iYeDEKhtP3rg4sOO9+74oREREANm7cR9eu41m1aidnnFGfhQuv8XWAg0K8WNm52QAkGRCXACdf7m1BIiLClCnLWb16N2ef3YiFC6+hfv1qXpfkOQ2nF6PgmLgBHe7wthgREQHgrru6UKlSAtdc047atat4XU5UUIgXkZGVweNzHgeCId5SvXAREa98//1OatSoRKNGyZgZt93W2euSooqG04uYtXxWweN61etC4/M8rEZExL++/XY7XbuOp1evl9mx44DX5UQlhXgR+w8F7hGeAFx96nlgaiIRkUhbuvRnunefwLZtB2jSJJlq1ZK8LikqKaGKyJ/UNqYmJF3wPx5XIyLiP4sXb6F79wns3HmQ/v1bMWPGMKpWTfS6rKikEC8iO30rAIkJiVDvDI+rERHxl88+20TPnhPZs+cQAwacyptvDqZyZU3fKolapojsXasASKzRXEPpIiIRtG7dXvr0eZm0tCwGDWrDlCkDSUyM97qsqKYQLyL74A4AEqv64zZ2IiLR4oQTanLjjR3YujWdCROuICFBHamjUYgXkb1nDQCJ1Rp6XImIiD/k5uYRHx+HmfH4473Jy3PExyvAQ6FWKiI7Yw8AiTWaelyJiEjF9+67P3D22eP4+ed0IHA7UQV46NRSRWRlB85FTKpcy9tCREQquLfeWsmVV77KsmXbGD9+idflxCSFeGHZB8nOzQEgMUnX5BURCZfXXlvB1Ve/TnZ2Hnfc0Zm77+7idUkxSSFeWNpGpgSu9UJivC4sICISDpMnL2PYsGnk5jr+9KcL+Pvf+2BmXpcVkxTihe1aQa1giyRX9vft7UREwuGll75h1Ki3yMtz/PWvXXnooR4K8DLQ7PTCdq4gywUeXnTKRd7WIiJSAf34426cg4ce6sE991zodTkxTyFe2J4fyAyGeKWESt7WIiJSAT30UA/69GlJt24tvC6lQtBwemH71xX0xJMSdExcRKQ8PP/812zZkgYETiFTgJcfhXhh+9erJy4iUo4efvgjbrjhHXr1mkhmZo7X5VQ4CvF8udmQvlk9cRGRcuCc4/77U/nznxdgBr///flUqqQjuOVNLZovfTO4PDKDT9UTFxE5Ps457rlnPo888jFxccaECVcwcuSZXpdVISnE8+1fT56DnGBPPDFe964VETlWzjl+//u5/POfnxEfb0yePJAhQ9p6XVaFpRDPt3/9EUPpOm9RROTYzZr1I//852ckJsbx6quDuPLK1l6XVKEpxPPtX6dJbSIiZdS/fyvuvfdCOnVqyqWXnuJ1ORWeQjzf/vX8767Aw/g43YReRCRUubl57NqVQf361TAzHnywh9cl+YZmp+fbv56twbMfTko5ydtaRERiRE5OHqNHv02XLi8WnAsukaMQz1fomPidfe70thYRkRiQnZ3L8OHTmDJlOT//nM769Xu9Lsl3NJwOkJf7i4ltIiJSsszMHIYMeYPp07+nRo1KzJ49gvPOa+Z1Wb6jEAdI3wR52WTFVwIySdJtSEVESnToUA5XXfUas2b9SO3alZk7dxQdOzb2uixfUogD7F8PQHZ8ZSBTPXERkRJkZ+dy+eWv8P77P5GSUpX33x9F+/YNvS7Lt3RMHODQXgCyLPA/jXriIiLFS0yMp3PnpjRoUI2FC69RgHtMIQ6QtS/wyQWaQz1xEZGSPfBAN5Ytu5m2bet7XYrvKcQBMoMhTuAqbQpxEZHD9u49xNChb7Bp034gcDvR+vWreVyVgI6JB2TuBTg8O13D6SIiAOzenUHv3i/z9ddb2bPnEHPmjPS6JClEIQ6QsZMfsmDpnu2AeuIiIgA7dhygV6+XWbZsGy1b1ua55y7zuiQpQiEOcHAHf9p++GmtqrU8K0VEJBr8/HM6PXtO5LvvdnDqqXWZP380TZrU8LosKSKsx8TNrK+ZfW9mq83sj8Wsr2lm75jZUjNbYWZjw1lPiTJ2sCcv8PCW7rfQuJbOdxQR/9q8eT9du47nu+920KZNPVJTxyjAo1TYQtzM4oGngH5AG2CYmbUpstlvgO+cc+2AbsA/zCzyY9kZOwuOhw85Z0jE315EJJq8+eZKfvhhF+3aNSA19RoaNqzudUlSgnAOp58LrHbO/QRgZlOBAcB3hbZxQLIFbt5dHdgN5ISxpuId3FFwG1IdDxcRv7vllnNJSIhjyJC21KlTxetypBThDPEmwMZCzzcBnYps8yQwA9gCJANDnHN5Yazpl5yDjB2amS4ivvbjj7tISgrchtnMuPnmczyuSEIRzhC3Ypa5Is8vBpYAPYCWwPtm9pFzbv8ROzK7EbgRoEGDBqSmppZbkRlpOyA3kyxngGPpN0vZ+9Pectu/X6Snp5fr98WP1IZlpzY8PuvXH+DOO5dRqVIcDz/cSm1YRpH8OQxniG8CCt/SpimBHndhY4FHnHMOWG1ma4HTgC8Kb+ScGweMA+jYsaPr1q1buRX52ZxXAMi0eCCHC867gJb1W5bb/v0iNTWV8vy++JHasOzUhsfu22+3M2TIRHbtyqJ79xbUr19TbVhGkfw5DOfs9C+BVmZ2YnCy2lACQ+eFbQB6AphZA+BU4Kcw1vQLcXmHAHQbUhHxnW++2Uq3buPZvv0AvXufxMyZw6lSJd7rsuQYhK0n7pzLMbNbgDlAPPCic26Fmd0UXP8M8CAw3syWExh+v9s5tzNcNRXHXC4AWS6Q4gpxEfGDL7/cTJ8+k9i79xD9+7di2rTBVK6sS4fEmrB+x5xzs4BZRZY9U+jxFqBPOGs4GiMwjy4rOJ2uUkIlD6sREQm/LVvS6NXrZfbvz2TAgFN59dVBVKqkAI9Fvv+u5ffEM4OT4jU7XUQqusaNk7nzzvP49tvtTJ48kMREDaHHKoW4y++JazhdRCq2nJw8EhICU6Huu+8inIO4uOJOJJJY4ftbkZrLJddBHhBncSTE+/7/GhGpgObMWc0ZZ/yX9ev3AoFzwRXgsU8hTq5mpotIhTZz5g9cfvlUVq3ayYsvfuN1OVKOFOIuT5dcFZEK6623VjJw4KtkZeVyyy3ncP/93bwuScqRQtzl6pKrIlIhvfrqt1x99etkZ+dx553n8cQT/QjcqkIqCt+HOIWG03V6mYhUFJMmLWP48DfJzXX86U8X8PjjvRXgFZDvQ/yInriG00Wkgti0aT95eY777+/KQw/1UIBXUL6fim0uV8fERaTC+eMfL6BLl2ZceOEJXpciYaSeuMvTMXERqRBeeOFr1q7dU/BcAV7xKcR1TFxEKoC///0Trr/+HXr2nMjBg9lelyMRohB3ecw9EHisC72ISCx66KEP+cMf3scsMIxetWqi1yVJhPg+tczlkht8fCj7kKe1iIgcC+cc99+fyt/+9iFm8OKLAxgzpr3XZUkEKcTJIyc4nH7lWVd6W4yISIicc9xzz3weeeRj4uONiROvZPjwM7wuSyJMIe5yyQk+TojzfXOISIz44IP1PPLIxyQkxPHKK1cxaFAbr0sSD/g+tfJvgAI6Ji4isaNbtxY88khPTjsthQEDTvO6HPGIUsvlFgynx8fpnroiEr3y8hzbtqXTqFEyAHfffYHHFYnXNDudPA2ni0jUy83NY+zY6XTq9Dzr1u31uhyJEgrxQj1xDaeLSDTKyclj1Ki3mDhxKbt3Z7Bx4z6vS5Io4fvUKnyKWbxpOF1EoktWVi7Dh09j2rSVJCcn8d57I+jSpbnXZUmUUIirJy4iUSozM4err36dd975gZo1KzFnzkg6dWrqdVkSRXyfWkYeb6cHHmtim4hEi9zcPK688lXee281depUYe7ckXTo0NjrsiTK+P6Y+LZDaewKjqfXqFzD22JERILi4+Po1q0F9epVZcGC0QpwKZbve+Jp2ZkFjy858xIPKxEROdJdd3Xh2mvPIiWlqtelSJTyfU/cucAJZm1rN6RyYmWPqxERP9u37xCDB7/OmjW7C5YpwKU0vu+JOxcYS4+LM48rERE/27Mng4svnsSXX25h69Z0PvxwDGb6uySl832I57o8AOLM94MSIuKRXbsO0rv3y3zzzc+0aFGLl1++UgEuIfF9iLu8QE9c54iLiBe2bz9Ar14TWb58OyefXIcFC0bTrFlNr8uSGKEQD17qRT1xEYm0rVvT6NlzIitX7uS001KYP380jRsne12WxBCFuFOIi4g3Zs9ezcqVO2nbtj7z5o2iQYPqXpckMcb3IZ6Xf0xcE9tEJMLGjj0LM+PSS0/RLHQ5LgpxF7jmqnriIhIJP/20h5ycPE45pS4AY8a097YgiWm+D/FdwYu9xCvERSTMfvhhFz16TABg0aJradGilrcFSczzfXJl5AaOia/as9XjSkSkIvvuux107TqezZvTOOmk2tStW8XrkqQC8H1PHALD6d2btfG4DhGpqJYv30bPnhPZseMgPXqcyIwZQ6lWLcnrsqQC8H1PPDd4TLxyfKLHlYhIRfTNN1vp3n0CO3YcpE+flsycOUwBLuVGIR6cna6LvYhIedu58yA9e05k164MLrmkFdOnD6VKFXUYpPz4fjg9LzicHh/n+/9nRKScpaRU5b77LuKjjzYwdeogkpLUWZDy5fvkys0L9sTj9MslIuUjOzu34PHtt5/HG28MVoBLWPg+xPN74gkKcREpB/Pn/0Tr1k/x44+7CpbpYlISLr4P8RyXP5yuEBeRspk9ezWXXvoKa9bs4YUXvvG6HPEB34f44Yltvm8KESmDd975ngEDpnLoUA433dSBhx/u6XVJ4gO+T6489cRFpIymTfuOgQNfIysrl9/+9lyefvoSDaFLRPg+xPOH03VMXESOx9Sp3zJkyBvk5OTxhz+cz7/+1RczBbhEhu9DPE/niYtIGezceZDcXMef/3whjz7aSwEuEeX788Rz0XC6iBy/W245lw4dGnHeec28LkV8SD3x/OH0eIW4iITmhRe+ZtWqnQXPFeDiFd+HeMEpZhpOF5EQ/Otfn3H99e/Qs+dE9u/P9Loc8Tnfh7hmp4tIqB59dBG33z4HgD//+UJq1KjkcUXidzomrhAXkRA8+OAH/OUvqZjBuHGXcf31Z3tdkohCPCc4O12nmIlIcZxz3HffQh566CPi4oyXXhrA6NHtvC5LBFCIF7qLmUJcRH7p888389BDHxEfb7z88pUMG3aG1yWJFPB9iGs4XURK07lzU/7zn340bFidQYPaeF2OyBF8H+Lbs7MBSIjzfVOISFBenmPLljSaNq0BBM4FF4lGvp+dviM7B4CMnCyPKxGRaJCbm8cNN8ygY8dxfP/9zqO/QMRDvg/xeomBHnjtKjU8rkREvJaTk8fYsdN58cUl7N+fyebNaV6XJFIqjSEHJVeq5nUJIuKh7OxcRo9+m6lTv6VatUTefXc4Xbu28LoskVL5PsRdcGKbiPhXVlYuw4ZN4803V5KcnMR7742gS5fmXpclclQK8eBnM98fWRDxJeccgwe/zvTp31OzZiXmzBlJp05NvS5LJCS+Ty4XjHGFuIg/mRkXX9ySunWrsGDBNQpwiSm+74nj8kNc9wAW8aubbz6HIUPaUqdOFa9LETkmvu9+ajhdxH/S0jIZNOg1VqzYXrBMAS6xSD3xIIW4iD/s23eIfv0m8+mnm1i3bi9ffnmDRuIkZvk+xN3hvrindYhI+O3Zk8HFF0/iyy+30Lx5TV59dZACXGKaQjyY4fpFFqnYdu48SJ8+L/PNNz9z4om1WLjwGk44oZbXZYmUie9DnILZ6boBikhFtX37AXr1msjy5dtp1aoOCxZcU3BddJFY5vsQPzyxTT1xkYpq4cK1LF++ndatU5g/fzSNGiV7XZJIuVCIBz9rYptIxTVkSFvy8hw9e55E/fq6xLJUHL4PcXSxF5EKad26vaSlZXLGGQ0AGDbsDI8rEil/vk+uw5dO13C6SEWxZs1uunYdT8+eE3U7UanQwhriZtbXzL43s9Vm9scStulmZkvMbIWZfRDOeopTMJwe5/v/Z0QqhO+/38lFF41nw4Z9tGpVl4YNq3tdkkjYhG043QLTvZ8CegObgC/NbIZz7rtC29QCngb6Ouc2mFn9cNVTkoJrp6snLhLz1q49wNCh49m27QAXXXQCM2cOIzm5ktdliYRNOLuf5wKrnXM/OeeygKnAgCLbDAfedM5tAHDObSfCNLFNpGJYuvRnbr99Kdu2HaBnzxOZNWu4AlwqvHAmVxNgY6Hnm4LLCjsFqG1mqWa22MxGh7Ge4hVc7EUhLhKr9u49RK9eL7NvXzZ9+57MO+8Mo1q1JK/LEgm7cM5OL2582hV5ngB0AHoCVYBPzewz59wPR+zI7EbgRoAGDRqQmppabkXmD6evWvU9cQfLb79+k56eXq7fFz9SG5bNNdc0ZdGibdx+eyM+//xjr8uJWfo5LLtItmE4Q3wT0KzQ86bAlmK22emcOwAcMLMPgXbAESHunBsHjAPo2LGj69atW/lV+XbgU+vWbeh2Tjnu12dSU1Mp1++LD6kNj11WVi5JSYGrLXbrBgsXLqR79+7eFhXj9HNYdpFsw3COIX8JtDKzE80sCRgKzCiyzXTgQjNLMLOqQCdgZRhr+gXNTheJTamp6zjllP+wfPm2gmW68qL4zVGTy8yqmtl9ZvZc8HkrM7v0aK9zzuUAtwBzCATza865FWZ2k5ndFNxmJTAbWAZ8ATzvnPv2+L+cY+d0sReRmDNv3k/07z+Z9ev38cIL33hdjohnQhlOfwlYDJwXfL4JeB2YebQXOudmAbOKLHumyPPHgcdDKTYcnCa2icSUWbN+ZODAV8nMzOW6687iH//o43VJIp4JJblaOuceA7IBnHMZVKDLm6knLhI7pk9fxRVXTCUzM5ebb+7IuHGXER+v313xr1B++rPMrArBw8dm1hLIDGtVHtDFXkSi2xtvfMegQa+TnZ3Hbbd14qmn+hMXp99b8bdQhtPvJ3DcupmZTQa6AGPDWVQkHb50uv6bF4lm6elZ5OTkcddd5/PII700iU2EEELcOTfXzBYDnQkMo//OOVdx7iigY+IiMWHMmPa0bVufDh0aKcBFgkKZnT7fObfLOfeuc26mc26nmc2PRHGRcPiYuP4oiESbl176hiVLfi543rFjY/2uihRSYk/czCoDVYEUM6vN4clsNYDGEagtIg5fOz3e0zpE5EhPPfUFt9zyHikpVVm16jfUrVvV65JEok5pw+m/Am4jENiLORzi+wncnaxCMU2QEYka//d/n3LHHXMBuPfeCxXgIiUoMcSdc/8G/m1mtzrn/hPBmiLKHe6Le1qHiAQ88sgi/vSnwBG7p5/uz803n+NxRSLRK5SJbf8xs7ZAG6ByoeUTw1lYpByOcE1sE/GSc44HH/yQv/41FTN47rnLuO66s70uSySqHTXEzeyvQDcCIT4L6AcsAipGiOfPTo/TMXERLy1bto37708lLs4YP34Ao0a187okkagXynnigwjcWewb59xYM2sAPB/esiKnYHa6QlzEU+3aNeSFFy6nSpVEhg5t63U5IjEhlBDPcM7lmVmOmdUAtgMnhbmuiNPsdJHIc86xYcM+TjihFgBjx57lbUEiMSaUA8FfmVkt4DkCs9S/JnDHsQpFPXGRyMrLc9x887ucddazLF3689FfICK/EMrEtl8HHz5jZrOBGs65ZeEtK3KcO/o2IlK+cnPzuOGGd3jppSVUrpzAtm0HvC5JJCaV2hM3s3gzSym0aAvQ2cxWhresyDk8O12nmIlEQk5OHmPGTOell5ZQpUoCM2cOo0+fll6XJRKTSgxxMxsK7AaWmdkHZtYd+InA7PQREaov7HTZVZHIyc7OZcSIN5k0aRnVqycxe/ZIevascFNsRCKmtOH0e4EOzrnVZnY28Ckw1Dn3VmRKi4zDl13VeeIi4eScY/jwN3njje+oUaMSs2eP4LzzmnldlkhMKy25spxzqwGcc18DaytagINCXCRSzIzLLz+FunWrMG/eKAW4SDkorSde38zuKPS8euHnzrl/hq+syNMxcZHwGzWqHZdddiq1alU++sYiclSldT+fA5ILfRR9XiEUTE7XMXGRcnfgQBYDB77K4sVbCpYpwEXKT2k3QHkgkoV4peCyqwpxkXK1f38ml1wyhUWLNvDjj7tZuvQm4nS3QJFyFcoV2yo0nWImUv727j1E376T+PzzzTRtWoM33xysABcJA4V48LN64iLlY/fuDPr0eZnFi7dywgk1WbjwGk48sbbXZYlUSKHcxSzeOZcbiWK8oNnpIuVnx44D9O79MkuXbuOkk2qzYMHoguuii0j5CyW5VpvZ42bWJuzVeEg9cZGy+/zzzSxfvp1TTqnLhx+OUYCLhFkow+lnAkOB5y3QXX0RmOqc2x/WyiLk8KXTFeIiZXXppafwxhtX07lzUxo1qjAnsYhEraP2xJ1zac6555xz5wN3AX8FtprZBDM7OewVhpmG00XKZuPGfXz11eFTyK68srUCXCRCjppcwZugXG5mbwH/Bv5B4H7i7wCzwlxf2OkUM5Hjt3btHi66aDy9e7/MsmXbvC5HxHdCGU7/EVgIPO6c+6TQ8jfM7KLwlBU56omLHJ/Vq3fTo8cENm7cz7nnNqFZsxpelyTiO6WGuJnFA+Odc38rbr1z7rdhqSqCdJ64yLFbtWonPXtOZMuWNM4/vxnvvTeCGjUqeV2WiO+U2v0MnlrWPUK1eMri1BMXCcW3326nW7fxbNmSRteuJzBnzkgFuIhHQhlO/8TMngReBQ7kLwze2Sy2OVdodrqIHM2BA1n07v0y27YdoFevk5g+fShVqyZ6XZaIb4US4ucHPxceUndAj/IvJ9KchtNFjkG1akn88599mDx5Oa+/fjVVqijARbx01BB3zlXo4XRddlXk6DIzc6hUKfDnYtiwMxg6tK1+Z0SiQCinmDUwsxfM7L3g8zZmdl34S4sA5w6fYqaeuEixFi3awMkn/4cvv9xcsEwBLhIdQpnNNR6YAzQOPv8BuC1M9XhGf5REfmnhwrVcfPEkNm3az4svfuN1OSJSRCghnuKcew3IA3DO5QAV5IYomtgmUpK5c9fQv/8UDh7M5ppr2vHkk/29LklEigglxA+YWV2Ch4/NrDOwL6xVRZCOiYv80rvv/sBll73CoUM53HDD2bz44gDi43Uapki0CWV2+p3ADKClmX0M1AMGhbWqSHGanS5S1Ntvr2Lw4NfJzs7jN785hyee6EdcnH4/RKJRKLPTF5tZV+BUArf6+t45lx32yiJEPXGRI+Xk5JGX57jjjs78/e999LshEsWOGuJmtpTAhV5edc6tCX9JkaSeuEhRgwa14auvbqRduwYKcJEoF8pBrsuBHOA1M/vSzH5vZs3DXFdkuEJ3E9cfK/GxiROX8umnGwuet2/fUL8TIjEglPuJr3fOPeac6wAMB84E1oa9sgjR7HTxu3HjFnPNNW/Tr99kfv453etyROQYhDKxDTNrAQwGhhA4veyuMNYUQU73Exdfe/LJL7j11vcAuO++i2jYsLrHFYnIsQjlmPjnQCLwOnC1c+6nsFcVQTomLn71j398wu9//z4ATzzRl1tv7eRxRSJyrELpiV/jnFsV9kq8UPgUM/XExUcefvgj/vznBQA888wl/OpXHT2uSESORygT27aa2T/N7Kvgxz/MrGbYK4sQhbj4zapVO/nLXxZiBi++eLkCXCSGhdITfxH4lsAxcYBRwEvAwHAVFTmFZqdrOF184rTTUpgy5SqysnIZOfJMr8sRkTIIJcRbOueuKvT8ATNbEqZ6IkzXThd/cM7x0097aNmyDgCDB5/ucUUiUh5CGU7PMLML8p+YWRcgI3wlRZaG06Wiy8tz/Pa379G+/bN89tkmr8sRkXIUSk/8ZmBCoePge4AxYasoknQ/cang8vIcN900k+ee+5qkpHh27TrodUkiUo5CuXb6EqCdmdUIPt8f7qIiST1xqahyc/O4/vp3GD9+CZUrJzB9+lD69GnpdVkiUo6OOpxuZg+bWS3n3H7n3H4zq21m/xOJ4sJPp5hJxZSTk8fo0W8zfvwSqlZN5N13hyvARSqgUI6J93PO7c1/4pzbA/QPW0Ue0XC6VCRjxrzNlCnLqV49idmzR9Cjx4lelyQiYRBKiMebWaX8J2ZWBahUyvaxw2l2ulRMAwe2pm7dKsydO5ILLzzB63JEJExCmdg2CZhvZi8ROIR8LTAhrFVFkIbTpSIaOLA1vXqdRI0aFeP/bREpXih3MXsM+B+gNXA68GBwWQWg+4lLxXDwYDYDB77KokUbCpYpwEUqvlBugFINmOucm21mpwKnmlmicy47/OWFma6dLhVAenoWl132Cqmp61i+fDsrV/6GhIRQjpSJSKwL5Tf9Q6CymTUB5gFjgfHhLCqSdCtSiWX792fSt+8kUlPX0ahRdd55Z5gCXMRHQvltN+fcQQLXSv+Pc+5KoE14y4oUXTtdYtfevYfo0+dlPv54I02b1uCDD8Zw2mkpXpclIhEUUoib2XnACODd4LJQJsTFBM1Ol1i0e3cGvXpN5PPPN3PCCTX58MMxtGpV1+uyRCTCQgnj24A/AW8551aY2UnAwrBWFSk6Ji4x6ptvtrJ06TZatqzNggXX0Lx5hbk7sIgcg1Auu/oB8EGh5z8Bvw1nUZGk2ekSi3r2PInp04fSrl0DmjSp4XU5IuKREkPczP7lnLvNzN6hmFFn59zlYa0sItQTl9ixefN+1q3bS5cuzQHo37+VxxWJiNdK64m/HPz890gU4glXaGKbQlyi2IYN++jRYwI//5zOggXXcO65TbwuSUSiQIkh7pxbHPz8gZnVCz7eEanCIkUT2yTarV27hx49JrJu3V46dGjEySfX8bokEYkSJc5Ot4D7zWwnsAr4wcx2mNlfIldeuOl+4hLdfvxxFxddNJ516/bSuXNT5s0bTZ06VbwuS0SiRGmnmN0GdAHOcc7Vdc7VBjoBXczs9kgUFwk6Ji7RauXKHXTtOp5Nm/ZzwQXNmTt3JLVqVfa6LBGJIqWF+GhgmHNubf6C4Mz0kcF1FYCunS7R6dChHC6+eBJbt6bTvXsLZs8eQXKyroUuIkcqLcQTnXM7iy4MHhdPDF9JkaWeuESjypUTePLJ/lxySStmzhxOtWpJXpckIlGotBDPOs51Bcysr5l9b2arzeyPpWx3jpnlmtmgUPZbbjQ7XaJMRsbh+wpdfvmpvPPOMKpWrTD/M4tIOSstxNuZ2f5iPtKAM462YzOLB54C+hG41vowM/vFNdeD2z0KzDm+L6FsNDtdosWKFfto2fIJPvxwfcEy/XMpIqUpMcSdc/HOuRrFfCQ750LpGpwLrHbO/eScywKmAgOK2e5WYBqw/bi+gjLRMXGJDh9+uJ4//GE5W7emM378Eq/LEZEYEc57FjYBNhZ6vim4rEDw9qZXAs+EsY6SaThdosD8+T/Rr99kMjJyGTHiDMaNu8zrkkQkRoTzbmTFpWLR0et/AXc753JLC1EzuxG4EaBBgwakpqaWS4GJWYfn7X3wwQelbClHk56eXm7fFz/54ovd3HffCrKy8ujZsy5jx9Zm0aIPvS4rZunnsOzUhmUXyTYMZ4hvApoVet4U2FJkm47A1GCApwD9zSzHOfd24Y2cc+OAcQAdO3Z03bp1K5cC8/ZvgtcDj8trn36VmpqqNjxGM2f+wH33LSIrK49f/aoDgwdXo0eP7l6XFdP0c1h2asOyi2QbhnM4/UuglZmdaGZJwFBgRuENnHMnOudaOOdaAG8Avy4a4JGggXTxQlyc4Zzj1lvP5b//vYS4OP0kisixCVtP3DmXY2a3EJh1Hg+8GLwf+U3B9d4cBy/EuTyvSxAf69+/FYsX30jbtvU1J0NEjks4h9Nxzs0CZhVZVmx4O+fGhLOWEt4TUE9cImfSpGU0bpxMjx4nAnDGGQ08rkhEYllYQzzaOQI9cYW4RMKLL37D9dfPoEqVRL777teccEItr0sSkRgXzmPiUa+gJ64UlzB75pmvuO66GTgH9957oQJcRMqFv3viTj1xCb8nnvic3/1uNgB//3tv7rzzfI8rEpGKwtchjobTJcwef/xj7rprHgBPPNGXW2/t5HFFIlKR+DrEXZ5mp0v4rF27h/vuWwjAs89eyo03dvC4IhGpaPwd4i4X0HXTJTxOPLE206YNZvv2A4wde5bX5YhIBeTvEM/TcLqUL+ccP/ywi1NPTQHgkktO8bgiEanIfD47PdgTV4pLOXDOcccdc2jf/lkWLFjrdTki4gM+74nnD6eLlE1enuPWW2fx9NNfkZgYR1paptcliYgP+DrE82+qphCXssjLc/zqV+/w/PPfUKlSPG++OYT+/Vt5XZaI+ICvQ1zXTpeyys3N47rrZjBhwlKqVElg+vSh9O7d0uuyRMQn/B3ieZqdLmVz443vMGHCUqpVS2TmzOF069bC65JExEf8PbEt/2IvynA5ToMHn05KSlVmzx6pABeRiPN5T1ynmEnZXHzxyaxd+zuqV0/yuhQR8SFf98TRtdPlGGVkZDNw4KvMnbumYJkCXES84usQzx9OV4xLKA4ezObyy6fy1luruOGGd8jMzPG6JBHxOZ8Pp+tiLxKa9PQsLr10Ch98sJ4GDaoxa9ZwKlXy9a+PiEQBX/8V0q1IJRT79h2if/8pfPLJRho3TmbBgtEFl1UVEfGSv0O8YGKbYlyKt2dPBn37TuaLLzbTrFkNFiy4hpNPruN1WSIigN9DXNdOl6P47rsdLFu2jRYtarFw4TW0aFHL65JERAr4OsQ1O12OpkuX5sycOYxTTqlLs2Y1vS5HROQIPp+d7rwuQaLQ1q1pR9yFrGfPkxTgIhKV/B3iLv8GKOqLS8CmTfvp2nU8/ftP5sMP13tdjohIqfwd4mg4XQ5bv34vXbuO58cfd9O6dT3atKnndUkiIqXy9THxYEdcE9uENWt206PHRDZs2EfHjo2ZM2ckdepU8bosEZFS+TzE1RMX+P77nfTsOZHNm9Po3Lkps2ePoGbNyl6XJSJyVL4eTs+nY+L+lZ2dS//+U9i8OY0LL2zO3LkjFeAiEjN8HeL5PXHxr8TEeMaNu5T+/Vvx3nsjSE6u5HVJIiIh8/dwevCzjon7z8GD2VStmggETiHr0eNETD8IIhJjfN4Tzz/FTPzkiy82c9JJ/2b27NUFyxTgIhKLfB7iuna633zyyUZ69ZrItm0HmDhxqdfliIiUic9DPNgTV4b7wocfrqdPn5dJS8ti8ODTmTDhCq9LEhEpE1+HeD5leMU3f/5P9O07iQMHshk58kwmTx5IYmK812WJiJSJr0Nc1073h9mzV3Pppa+QkZHD2LHtGT9+AAkJvv7RF5EKwtd/yXQ/cX+oXDkBM7jppg48//zlxMf7+sdeRCoQn59iptnpftCtWwsWL76R005L0Sx0EalQfN0l0bXTK65XX/2WmTN/KHjeunU9BbiIVDi+7omjnniFNHHiUsaOnU5CQhzffnszrVrV9bokEZGw8HdP/PA12zytQ8rPCy98zZgxb5OX57j33gsV4CJSofm6J64rtlUs//3vl/z617MAePTRXtx1VxePKxIRCS+FODomXhH8+9+fcdttcwD4v/+7mNtu6+xxRSIi4efvEEenmFUEW7ak8ec/LwDgqaf68+tfn+NxRSIikeHvENdweoXQuHEyM2cOZ82a3Vx33dlelyMiEjG+ntiWT8Ppscc5x3ff7Sh43q1bCwW4iPiOr0Ncl12NTc457r57Hu3bP3PEueAiIn6j4XR0TDyWOOe4/fY5/Pvfn5OQEEdmZo7XJYmIeEYhjo6Jx4q8PMdvfvMuzzyzmKSkeF5//Wouv/xUr8sSEfGMQhwdE48Fubl53HjjO7z44hIqVYrn7beH0rfvyV6XJSLiKX+HuE4xixm33voeL764hCpVEpgxYxi9ep3kdUkiIp7z9cS2fIrw6DdsWFvq16/Ge++NUICLiAT5vCcu0cw5V3DnsQsvPIG1a39H1aqJHlclIhI9fN0Td3n5w+kSbTIzcxg06HWmT19VsEwBLiJyJH+HeP6tSDWzLapkZGQzYMBU3nxzJTfd9C4HD2Z7XZKISFTy93C6TjGLOgcOZHH55VNZsGAtKSlVmT17hHrgIiIl8HeIo4u9RJO0tEwuuWQKH320gQYNqjF//mhOP72+12WJiEQtX4c4Ok88auzbd4h+/Sbz6aebaNw4mQULRnPqqSlelyUiEtV8HeKanR49Vq/ezfLl22nevCYLFoymZcs6XpckIhL1/B3iOiYeNTp0aMzs2SNo2rQGJ5xQy+tyRERigmano2PiXtm2LZ1Zs34seN6lS3MFuIjIMfB3iOuYuGe2bk2jW7cJDBgwlTlzVntdjohITPJ5iOtiL17YtGk/XbuOZ9WqnbRuncJZZzXyuiQRkZjk62Pi+TScHjnr1u2lR48JrF27l/btG/L++6NISanqdVkiIjHJ1yGeP5wukbFmzW569JjIhg37OOecxsyZM5Latat4XZaISMzy93C6LrsaMbm5eVx++VQ2bNjH+ec34/33RynARUTKyN8hHuyIK8LDLz4+jueeu4xLLmnF7NkjqFmzstcliYjEPJ8Pp2tiW7ilp2dRvXoSAOef34yZM4d7XJGISMXh6554Pg2nh8fXX2+lZcsnmDbtO69LERGpkHwd4k4XXg2bzz/fRI8eE9i+/QCvvPKtJhGKiISBv0Ncl10Ni48/3kDv3i+zb18mV13VmilTrtJoh4hIGPg7xAkeE1fAlJvU1HVcfPEk0tKyGDq0LVOnDiIpKd7rskREKqSwhriZ9TWz781stZn9sZj1I8xsWfDjEzNrF856itLs9PI1b95P9O8/mQMHshk16kwmTbqShARf/58oIhJWYfsLa2bxwFNAP6ANMMzM2hTZbC3Q1Tl3JvAgMC5c9RTn8A1QpDwkJyeRkBDHddedxUsvDSA+XgEuIhJO4TzF7FxgtXPuJwAzmwoMAAqmKjvnPim0/WdA0zDW80tOdzErT506NWXx4htp2bIOcXFqUxGRcAtniDcBNhZ6vgnoVMr21wHvhbGeXyiYL628OW6vv76CvDxHgwaB561a1fW2IBERHwlniBcXjcWeZ2Rm3QmE+AUlrL8RuBGgQYMGpKamlkuBa9f+BEBOdna57dNP5s3bxv/+7yoAnniiDZDqaT2xLj09XT+HZaQ2LDu1YdlFsg3DGeKbgGaFnjcFthTdyMzOBJ4H+jnndhW3I+fcOILHyzt27Oi6detWLgXmZi+ELyApMYny2qdfjB+/hIcf/gDn4C9/uYg2bVAbllFqaqrasIzUhmWnNiy7SLZhOGcefQm0MrMTzSwJGArMKLyBmTUH3gRGOed+CGMtxSq47KqG04/JuHGLGTt2Os7Bgw9254EHuus0PRERD4StJ+6cyzGzW4A5QDzwonNuhZndFFz/DPAXoC7wdDAEcpxzHcNV0y9rDHzWxLbQPfXUF9xyS2DqwmOP9eIPf+jicUUiIv4V1hugOOdmAbOKLHum0OPrgevDWUPpdIrZsdix4wD33rsQgH/962J+97vOHlckIuJv/r6Lma6dfkzq1avG7NkjWLp0Gzfe2MHrckREfM/fIZ5/nriO55bIOce3327njDMC55B16tSUTp0iezq/iIgUz9eX1NINUErnnOPeexfQvv2zvPbaCq/LERGRIvzdE0dXbCuJc4677nqfv//9U+Ljjbw8HXoQEYk2/g7xguF0jwuJMs45brttNk888QUJCXFMnXoVV11V9LL3IiLiNV+HeD71xA/Ly3P8+tfv8uyzi0lKiueNN67msstO9bosEREphq9DPL8nLofdeeccnn12MZUrJ/DWW0Po2/dkr0sSEZES+HtiGxpOL2rEiDNp1Kg6M2cOU4CLiEQ59cTRcLpzruA0u44dG7NmzW+pUiXR46pERORo1BPH36eYZWXlMnjwG0yevKxgmQJcRCQ2+DvEfX6xl0OHcrjqqtd4443vuO22Oezfn+l1SSIicgx8PZyez48RnpGRzRVXvMrcuWuoW7cKc+eOpEaNSl6XJSIix8DXIe7Xa6cfOJDFZZe9wsKF66hfvxrz5o0quKyqiIjEDn+HuA8ntqWlZXLJJVP46KMNNGpUnfnzR9O6dT2vyxIRkePg8xDPA/x1THzDhn0sX76dpk1rsGDBaFq1qut1SSIicpz8HeJ5uYC/Qvz00+vz/vujqFOnCiedVNvrckREpAx8PTsd548Q37HjAG+/vargeceOjRXgIiIVgK9D3OVlBx5YvLeFhNHPP6fTvfsEBg589YggFxGR2Ofv4fTg5PSKOrFty5Y0evSYwPff76JNm3p07tzU65JERKQc+TzE8ye2eVxIGGzcuI8ePSayevVuzjyzAfPmjaJevWpelyUiIuXI3yEe/FzReuJr1+6hR4+JrFu3l7PPbsTcuSOpW7eq12WJiEg583eIu4p37XTnHFdd9Rrr1u3l3HObMGfOSGrVqux1WSIiEga+ntiWryLNTjcznn/+ci699BTef3+UAlxEpALzdYhXpMuuFr55ydlnN+Kdd4bpWugiIhWcv0O8ggynL136M61a/YcJE5Z4XYqIiESQv0Oc2L8V6VdfbaF79wls336AN95YWfCPiYiIVHz+DvEYvwHKZ59tomfPiezZc4gBA07ljTeujul/SERE5Nj4O8QLeuIeF3IcFi3aQO/eL7N/fyaDBrXh9devplIlX59sICLiO74O8Xyx1hNPTV3HxRdPIj09i2HD2vLKK1eRmFhxLx0rIiLF83XXLVaPH9euXZnKlRO4+uo2vPDC5cTH638xERE/8neIBz/HWk+8XbuGfPXVDZxwQi3i4mKrdhERKT++7sLF0rXT3357FS+88HXB8xNPrK0AFxHxOX/3xPPvYhblKf766ysYPvxNcnPzOPPMBpxzThOvSxIRkSjg75440X+xl8mTlzF06DRycvK4++4udOzY2OuSREQkSvg6xPNF6zHx8eOXMGrUW+TlOf761648/HDPqB81EBGRyPH3cHoUXzt93LjF/OpXMwF46KEe3HPPhR5XJCIi0cbfIe6i82Ive/ce4r77FgLw+OO9+f3vz/e4IhERiUY+D/HA52gbTq9VqzJz547k0083cdNNHb0uR0REopS/Q5zgKWZREuJLlvxM+/YNgcC54O3aNfS4IhERiWa+ntgWLaeYOed44IFUzjrrWV588RtPaxERkdjh6554Pi8j3DnHn/+8gP/930XExRlJSboGuoiIhMbXIe71tdOdc/z+93P55z8/Iz7emDx5IEOGtPW0JhERiR3+DvGCW5FGvi+el+f43e/e48knvyQxMY5XXx3ElVe2jngdIiISuxTieDOcfs8983nyyS9JSopn2rTBXHrpKR5UISIisUwT2/CmJz5y5Jk0a1aDGTOGKsBFROS4+LonTkFPPDIh7pwr+Iehbdv6/PjjrVSq5PNvgYiIHDdfJ0gkJ7ZlZ+cyYsSb9Op1Ejfe2AFAAS4iMSEvL49NmzZx4MABr0uJCTVr1mTlypXH9JrExETq169PjRo1jul1vk6R/AgP93B6ZmYOQ4a8wfTp3zNv3k8MGtSGOnWqhPU9RUTKy86dOzEzTj31VOLifH0UNiRpaWkkJyeHvL1zjoyMDDZv3gxwTEHu6+9GwbXTw/gehw7lMHDga0yf/j21a1dm7txRCnARiSl79+6lQYMGCvAwMTOqVq1KkyZN2L59+zG91uc98fCeYnbwYDYDBkxl3ryfSEmpyvvvjyq4rKqISKzIzc0lMTHR6zIqvCpVqpCdnX1Mr1GIE56eeHp6Fpdd9gqpqeto0KAa8+aNpm3b+mF4JxGR8PP68tR+cDxt7OsQzxeOH86tW9P47rsdNGpUnQULruG001LK/T1ERMTffH2AI5yT01u1qsv8+aP58MOxCnARkTDr1q0btWvXJjMzs2DZmDFjSEpKIjk5meTkZNq2bcuf/vQn9u3bV7DN+PHjiY+Pp3r16tSoUYN27doxc+ZML76E4+LzEC/f88R37TrIq69+W/C8bdv6nHxynXLZt4iIFG/dunV89NFHmBkzZsw4Yt1dd91FWloaO3bs4KWXXuKzzz6jS5cuR5wud95555Gens7evXv59a9/zdChQ9m7d2+Ev4rj4+8QL8eJbdu3H6BHj4kMHTqNKVOWl3l/IiISmokTJ9K5c2fGjBnDhAkTit2mcuXKnHPOOcyYMYNdu3bx0ksv/WKbuLg4Ro0axYEDB/jxxx/DXXa5UIhT9oltP/+cTvfuE1i2bBunnlqXrl1PKHtxIiISkokTJzJixAhGjBjBnDlz2LZtW4nbJicn07t3bz766KNfrMvNzeWll14iMTGRE06Ijb/jvp7YVnDt9DLE+ObN++nRYyI//LCLNm3qMX/+aBo2rF5OFYqIRKF/RGim+p1Hn7i0aNEi1q9fz+DBg0lJSaFly5ZMmTKF22+/vcTXNG7cmMWLFxc8/+yzz6hVqxYHDhwgISGBSZMmUb9+bJxN5OueeL7jHU7fsGEfXbuO54cfdnHmmQ1ITb1GAS4iEkETJkygT58+pKQEJhAPHz68xCH1fJs3b6ZOncPzlTp37szevXvZs2cPl19+ebG99Gjl8554Xhle6xg69A3WrNnD2Wc3Yu7ckdStW7UcqxMRiVIh9JAjISMjg9dee43c3FwaNgxcSCszM5O9e/eydOnSYl+Tnp7OvHnz+POf//yLddWrV+fpp5+mZcuWXHvttZx11llhrb88+LonXpZrp5sZL7xwOZdddgrz549WgIuIRNjbb79NfHw83333HUuWLGHJkiWsXLmSCy+8kIkTJx6xbWZmJosXL+aKK66gdu3ajB07tth91q1bl+uvv56//e1vkfgSyszfIX4c107fu/dQwePWresxY8YwatWqXM6ViYjI0UyYMIGxY8fSvHlzGjZsWPBxyy23MHnyZHJycnjsscdITk6mTp06jB49mg4dOvDJJ59QrVq1Evd72223MWvWLJYtWxbBr+b4+Hs4/RjvJ/7tt9vp1Wsi9913Eb/5zbnhLE1ERI5i9uzZxS4fPHgwgwcPBmDSpEml7mPMmDGMGTPmiGVNmzY94qIx0Uw9cSCU0fQlS36mW7fxbNt2gHfe+YG8vOg4JiQiIv7l6xDPd7Se+FdfbaFHjwns2pXBJZe04u23hxIXp5sBiIiIt3wd4qH0pT/9dCM9e05kz55DXHHFabz55hAqV/b1UQgREYkS/g5xV/plVxct2kCfPpPYvz+Tq69uw2uvDSIpKT6SJYqIiJTI113Ko01sq1+/GtWrJzFgwKmMH38FCQm+/p9HRESijL9D/CgT2045pS6ff349TZokEx+vABcRkeji62QquNhLoZ74zJk/8J//fF7wvHnzmgpwERGJSr7uiefLPyb+1lsrGTLkDbKz82jfviEXXhgbd7ERERF/CmsX08z6mtn3ZrbazP5YzHozsyeC65eZ2dnhrKeo/OF0gFdf/Zarr36d7Ow87rzzPC64oHkkSxERETlmYQtxM4sHngL6AW2AYWbWpshm/YBWwY8bgf+Gq57i5E9sW/ZFQ4YPf5PcXMc991zA44/3Pu47m4mIiERKOHvi5wKrnXM/OeeygKnAgCLbDAAmuoDPgFpm1iiMNR3B4eD7s3j75dPIy3M88EA3/ud/eijARURiTLdu3ahduzaZmZncdNNNVK9enerVq5OUlERiYmLB8379+gGBG6L86U9/onnz5lSpUoVWrVrx+OOPHzFCW9p7Va5cmeTkZGrUqEGHDh145JFHjrhU6/3331/wvrVq1eL888/n008/LfevO5wh3gTYWOj5puCyY90mbDIPxcHX3cAZDz/cg7/8pasCXEQkxqxbt46PPvoIM2PGjBk888wzpKenk56ezj333MOQIUMKnr/33nsAXH311cyfP59Zs2aRlpbGyy+/zLhx4/jd734X0ns++eSTpKWlsXXrVv7xj38wdepU+vfvf8Q/Afnvu3PnTrp3787VV19d7l97OCe2FZeGRf/FCWUbzOxGAsPtNGjQgNTU1DIXB3Dw0B7oN5EemRdx3nldy22/fpSenq72KyO1YdmpDcuuuDasWbMmaWlp3hQUgueee45zzjmHjh078sILL9C3b9+CdZmZmWRnZx9Rf2pqKnPnzuWbb76hadOmZGRkcPrpp/Pss8/Su3dvrr32Wlq2bFni++Xm5nLo0KGCfXbo0IEpU6bQsWNHXn/9dfr06fOL973iiit4+OGHWbt2LSkpKSXu+9ChQ8f0MxzOEN8ENCv0vCmw5Ti2wTk3DhgH0LFjR9etW7dyKbDL+Z9x+QcL6Na1B4lJuh94WaSmplJe3xe/UhuWndqw7Iprw5UrV5KcnFzw3G6IzIiley60G029+uqr3HHHHXTq1InOnTtz8OBBGjRoAEClSpVITEw8ov6PP/6YTp060bp16yP206NHD5o2bcrnn39O+/btS3y/+Pj4guH0fG3atKFjx44sXryYfv36HfG+WVlZTJs2jbp169KsWTMSEkqO3sqVK3PWWWeF9HVDeIfTvwRamdmJZpYEDAVmFNlmBjA6OEu9M7DPObc1jDUdITGpKomJ1RXgIiIxatGiRaxfv57BgwfToUMHWrZsyZQpU0p9zc6dO2nUqPjpV40aNWLnzp3HVUvjxo3ZvXt3wfPXXnuNWrVqUaVKFZ577jneeOONUgP8eIStJ+6cyzGzW4A5QDzwonNuhZndFFz/DDAL6A+sBg4CY8NVj4iIlI9Qe8iRMGHCBPr06VMwRD18+HAmTJjA7bffXuJrUlJS+PHHH4tdt3Xr1lKHu0uzefNmzj///ILngwcPZtKkSezcuZOrrrqKxYsXl/tIUVgv9uKcm0UgqAsve6bQYwf8Jpw1iIhIxZSRkcFrr71Gbm4uDRs2BALHwPfu3cvSpUtp165dsa/r1asX//rXv9i4cSPNmh0+ovvFF1+wceNGevToccy1bNy4kcWLF3P33Xf/Yl1KSgrPPvss55xzDsOHDy9xFOB46HqiIiISk95++23i4+P57rvvWLJkCUuWLGHlypVceOGFTJw4scTX9erVi549e3LVVVexYsUKcnNz+eyzzxgxYgQ333wzrVq1CrmGgwcP8sEHHzBgwADOPfdc+vfvX+x2p512GhdffDGPPfbYMX+dpVGIi4hITJowYQJjx46lefPmNGzYsODjlltuYfLkyeTk5JT42mnTptG9e3f69u1L9erVGTlyJNdddx3/+c9/QnrvW265heTkZBo0aMBtt93GVVddxezZs4mLKzlW//CHPzBu3Di2b99+zF9rSXTtdBERiUmzZ88udvngwYMZPHgwELjoSnEqV67Mo48+yqOPPnrM7xvKKWDFvW+nTp04cODAMb9fadQTFxERiVEKcRERkSLyL9Na9OOjjz7yurQjaDhdRESkiPT0dK9LCIl64iIiclSh3BhEyuZ42lghLiIipYqPjyc7O9vrMiq8jIwMEhMTj+k1CnERESlVrVq12LZtG3l5eV6XUiE55zh48CCbN2+mfv36x/RaHRMXEZFSpaSksGnTJr7//nuvS4kJhw4donLlysf0msTERBo0aECNGjWO6XUKcRERKVVcXBzNmzf3uoyYkZqaekx3IisLDaeLiIjEKIW4iIhIjFKIi4iIxCiFuIiISIyyWDuB38x2AOvLcZcpwM5y3J9fqR3LTm1YdmrDslMbll042vAE51y9ogtjLsTLm5l95Zzr6HUdsU7tWHZqw7JTG5ad2rDsItmGGk4XERGJUQpxERGRGKUQh3FeF1BBqB3LTm1YdmrDslMbll3E2tD3x8RFRERilXriIiIiMco3IW5mfc3sezNbbWZ/LGa9mdkTwfXLzOxsL+qMZiG04Yhg2y0zs0/MrJ0XdUazo7Vhoe3OMbNcMxsUyfpiRSjtaGbdzGyJma0wsw8iXWO0C+H3uaaZvWNmS4NtONaLOqOVmb1oZtvN7NsS1kcmU5xzFf4DiAfWACcBScBSoE2RbfoD7wEGdAY+97ruaPoIsQ3PB2oHH/dTGx57GxbabgEwCxjkdd3R9hHiz2It4DugefB5fa/rjqaPENvwHuDR4ON6wG4gyevao+UDuAg4G/i2hPURyRS/9MTPBVY7535yzmUBU4EBRbYZAEx0AZ8BtcysUaQLjWJHbUPn3CfOuT3Bp58BTSNcY7QL5ecQ4FZgGrA9ksXFkFDacTjwpnNuA4BzTm15pFDa0AHJZmZAdQIhnhPZMqOXc+5DAm1Skohkil9CvAmwsdDzTcFlx7qNnx1r+1xH4L9QOeyobWhmTYArgWciWFesCeVn8RSgtpmlmtliMxsdsepiQyht+CTQGtgCLAd+55zLi0x5FUJEMsUv9xO3YpYVnZYfyjZ+FnL7mFl3AiF+QVgrij2htOG/gLudc7mBDpAUI5R2TAA6AD2BKsCnZvaZc+6HcBcXI0Jpw4uBJUAPoCXwvpl95JzbH+baKoqIZIpfQnwT0KzQ86YE/rs81m38LKT2MbMzgeeBfs65XRGqLVaE0oYdganBAE8B+ptZjnPu7YhUGBtC/X3e6Zw7ABwwsw+BdoBCPCCUNhwLPOICB3hXm9la4DTgi8iUGPMikil+GU7/EmhlZieaWRIwFJhRZJsZwOjgjMLOwD7n3NZIFxrFjtqGZtYceBMYpR5PsY7ahs65E51zLZxzLYA3gF8rwH8hlN/n6cCFZpZgZlWBTsDKCNcZzUJpww0ERjIwswbAqcBPEa0ytkUkU3zRE3fO5ZjZLcAcArMyX3TOrTCzm4LrnyEwE7g/sBo4SOC/UAkKsQ3/AtQFng72JHOcbqRQIMQ2lKMIpR2dcyvNbDawDMgDnnfOFXsqkB+F+LP4IDDezJYTGBq+2zmnu5sFmdkrQDcgxcw2AX8FEiGymaIrtomIiMQovwyni4iIVDgKcRERkRilEBcREYlRCnEREZEYpRAXERGJUQpxkShkZg3NbKqZrTGz78xslpmdErwz18xj3Nf4/LuhBS9D6slpf8H3/j54V6wvzaz9UbavZWa/jlB5IjFJIS4SZYI3nHgLSHXOtXTOtSFwR6kG3lZWLkY459oBTwOPH2XbWoBCXKQUCnGR6NMdyC588Rfn3BLn3EfBp9XN7A0zW2Vmk4Ohj5n9JdjD/dbMxuUvL4mZ9TGzT83sazN73cyqB5evM7MHgsuXm9lpweX1zOz94PJnzWy9maUE1400sy8scP/uZ80s/ihf46cEbwZhZtXNbH6h98u/m9YjQMvgPh8PbvuH4Ne4zMweOJZGFamIFOIi0actsLiU9WcBtwFtCNwPuktw+ZPOuXOcc20J3PTj0pJ2EAzfe4Fezrmzga+AOwptsjO4/L/A74PL/gosCC5/C2ge3FdrYAjQxTnXHsgFRhzla+wLvB18fAi4Mrjf7sA/gv+A/BFY45xr75z7g5n1AVoRuI1me6CDmV10lPcRqdB8cdlVkQrmC+fcJgAzWwK0ABYB3c3sLqAqUAdYAbxTwj46E/gn4ONghz2JQO8435vBz4uBgcHHFxC4TSrOudlmln/v+J4E7hj2ZXBfVSj5XuiTzawagUt9nh1cZsDDwUDOI9BDL+7QQZ/gxzfB59UJhPqHJbyXSIWnEBeJPiuAQaWszyz0OBdIMLPKBI4zd3TObTSz+4HKpezDgPedc8OO8h65HP47UdLwvAETnHN/KuX98o0AlhIYKn+KwD8II4B6QAfnXLaZrSuhdgP+1zn3bAjvI+ILGk4XiT4LgEpmdkP+AjM7x8y6lvKa/NDbGTy2Xdo/AQCfAV3M7OTg/qua2SlHec0iYHBw+z5A7eDy+cAgM6sfXFfHzE4oaSfOuWwCQ/mdg0PxNYHtwQDvDuS/Ng1ILvTSOcC1hY7dN8l/TxG/UoiLRJng/ZuvBHoHTzFbAdxPKfcids7tBZ4DlhM41vzlUd5jBzAGeMXMlhEI9dOOUtoDQB8z+xroB2wF0pxz3xEI5bnBfb0PNDrK+2cA/yBwvH0y0NHMviLQK18V3GYXgeH+b83scefcXGAK8GnwzlpvcGTIi/iO7mImIiExs0pAbvA2lucB/w1OZBMRj+iYuIiEqjnwmpnFAVnADUfZXkTCTD1xERGRGKVj4iIiIjFKIS4iIhKjFOIiIiIxSiEuIiISoxTiIiIiMUohLiIiEqP+HzuQwkIbronRAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# -- model performance summary -- \n", "trainingMetrics = client.describe_model_versions(\n", " modelId = MODEL_NAME,\n", " modelVersionNumber = model_version,\n", " modelType = MODEL_TYPE,\n", " maxResults = 10\n", ")['modelVersionDetails'][0]['trainingResultV2']['trainingMetricsV2']['ati']\n", "\n", "perf_asi = trainingMetrics['modelPerformance']['asi']\n", "df_model = pd.DataFrame(trainingMetrics['metricDataPoints'])\n", "\n", "# -- ROC Chart -- \n", "plt.figure(figsize = (8,8))\n", "plt.plot(df_model[\"cr\"], df_model[\"adr\"], color='darkorange', lw=2, label='ADR')\n", "plt.plot(df_model[\"cr\"], df_model[\"atodr\"], color='darkgreen', lw=2, label='ATO_DR')\n", "plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')\n", "plt.xlabel('Challenge Rate')\n", "plt.ylabel('Discovery Rate')\n", "plt.title(f'{MODEL_NAME}, ASI: {perf_asi:.3f}')\n", "plt.legend(loc=\"lower right\", fontsize=12)\n", "plt.grid()\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "image/png": "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220A3HHHHey+++4MHTqUs846a/0v6a+++iqDBw9m//33Z8yYMet/ta+pqWHYsGF86Utfol+/fqxdu5ZLLrmE/fffn/79+/PTn/4UgHXr1nHuuefSp08fjjnmGI4++uj1v+5fddVV7L///vTt25ezzz6biGD8+PG88MILjBw5kgEDBrBy5UqmT5/OkCFDGDhwIEceeSRvvPEGANOnT2efffbhoIMO4sc//vH68zvttNO477771r//9a9/zVFHHcW6des47LDD2G+//ejXrx+/+c1vgGz0Y6+99uLcc89lv/32Y/HixfTs2ZO33357g/763//93zp9edFFF7Hffvtx2GGH8dZbb23Q1w3FfuONN7L33nvTv39/Tj311A2OGzVqFF//+tcZNmwYvXv35sknn+TMM89kr732YtSoUevLdenSpWgMM2fOZPDgwfTv358TTjiBd9/Nnqk8dOhQLrvsMoYMGcJ3vvMdJk6cyCWXXMKAAQN49dVXuf3229l///3ZZ599OOmkk1ixYsX6eEaPHs2nP/1pevfuXWeE5vrrr6dfv37ss88+XHrppeu/N0cddRQDBw7kkEMO4eWXXyYPTjDMzMzMNkF33nkn06dP54UXXuDGG2/kb3/7G1dffTVTp07lD3/4Q52LwfPPP5/zzz+fadOmbTCi8Pzzz3Pttdcyf/587rjjDrp168a0adOYNm0at99+OwsXLuShhx5i0aJFzJkzh5/97Gc8++yz648/77zzmDZtGnPnzmXlypVMmjSJk08+mUGDBnH33Xczc+ZM2rVrxze+8Q3Gjx/P9OnTOfPMM7n88ssBOOOMM7jxxhvr1Alw1FFHMX36dJYsWQLAfffdx2mnnUaHDh349a9/zYwZM5g8eTIXXXQRtXc9XbBgAV/5yld48cUX2WWXuo8AK+yvW2+9dX29H3zwAfvttx8zZsxgyJAhXHnllXWOW716dYOxX3fddbz44ovMnj2bW2+9tejn9O677/LHP/6RH/7whxx77LFceOGFzJs3jzlz5jBz5sxGY/jKV77Cd77zHWbPnk2/fv3qxLZ06VKefPJJLr/8co477ji++93vMnPmTHbddVdOPPFEpk2bxqxZs9hrr72444471h/3xhtvMGXKFCZNmrQ+kfj973/PhAkTeO6555g1axbf/OY3ATj77LO56aabmD59Ot/73vc499xzi55jc3mKlJmZmdkm6MYbb+TXv/41AIsXL+auu+5iyJAhbL119tzeU045hT//+c8APPvss0yYMAGAL33pS1x88cXr6znggAPW32L0scceY/bs2et/2V62bBmvvPIKU6ZM4ZRTTqFNmzZ8/OMfZ9iwYeuPnzx5Mtdffz0rVqzgnXfeoU+fPhx77LF1Yl2wYAFz587lc5/LHve1du1adtxxR5YtW8bSpUsZMmQIAF/+8pf5/e9/D8DHPvYxjjvuOMaPH89JJ53EzJkzOeKII4gILrvsMp566inatGnD3/72N/7xj+xmjLvssguDBxd/FnJhf/3tb3/jlVdeYZtttqFNmzZ88YtfBOD000/nxBNPLCl2gP79+zNy5EhGjBjBiBEjirZ77LHHIol+/fqxww470K9fPwD69OnDokWLGDBgQNEY6vfNV7/6VU455cP759SWL2bu3Ll861vfYunSpSxfvpwjjzxy/b4RI0bQpk0b9t577/X99vjjj3PGGWfQqVMnALbeemuWL1/On/70pzpt/vOf/2ywzeZwgmG2iejYvi0L/NwLMzMjm9r0+OOP8+yzz9KpUyeGDh3KHnvssVEPj+vcufP61xHBTTfdVOeCFODhh4uvM1m1ahXnnnsuL7zwAjvvvDNjx44tesvSiKBPnz4bjFIsXbq00bsQnXbaaVxzzTVEBMcffzzt27dn3LhxvPXWW0yfPp327dvTs2fP9W0Wnkuh+v11yCGHNHhr1frxNBQ7ZP3y1FNPMXHiRK6++mrmzZu3QZkttshuItqmTZv1r2vfr1mzwbN9i8ZQTEPnCtlUqAkTJrDPPvswbtw4ampqNoin9txq/67f5rp16+jevfv6UZY8eYqUmZmZ2SZm2bJlbLXVVnTq1ImXX36ZqVOnsmLFCp588kneffdd1qxZw4MPPri+/ODBg9e/L1zXUN+RRx7JT37yE1avXg3An//8Zz744AMOPvhgHnzwQdatW8c//vGP9RestRfp2267LcuXL68zp79r1668//77AOyxxx689dZb6y/SV69ezbx58+jevTvdunVjypQpANx999114hk2bBivvPIKP/7xjznttNPWn/v2229P+/btmTx5Mq+99lqz+2vatGnr961bt2593Pfccw8HH3xwnWMbin3dunUsXryYYcOGcf31168fLdgYxWLo1q0bW221FU8//TTA+hGqYgr7GrIHCe64446sXr16gz4t5ogjjuDOO+9cv1bjnXfeYcstt6RXr1488MADQJaEzJo1a6POrz6PYJiZmZltYo466ihuvfVW+vfvzx577MHgwYP55Cc/yWWXXcaBBx7IJz7xCfbee2+6desGwI9+9CNOP/10vv/97zN8+PD12+v7t3/7NxYtWsR+++1HRLDddtsxYcIETjrpJJ544gn69u3L7rvvzoEHHki3bt3o3r07Z511Fv369aNnz57sv//+6+saNWoU55xzDh07duTZZ59l/PjxjB49mmXLlrFmzRouuOCC9QuuzzzzTDp16rTByEmbNm046aSTeOCBBzj00EMBGDlyJMceeyyDBg1iwIAB7Lnnns3ur8I4O3fuzLx58xg4cCDdunXj/vvvr3Psxz72saKx77777px++uksW7aMiODCCy+ke/fuJX1+9TUUw89//nPOOeccVqxYQe/evTdYnF7r1FNP5ayzzuLGG29k/PjxXH311Rx44IHssssu9OvXr07y0VD/zJw5k0GDBvGxj32Mo48+mv/+7//m7rvv5utf/zrXXHMNq1ev5tRTT2WfffbZqHMspNqhEzOrrj322CMWLFhQ7TBanZqaGoYOHVrtMFod93t1uN9L89JLL7HXXnvlUtf7779P165dc6kLYPny5XTp0oU1a9ZwwgkncOaZZ3LCCSewYsUKOnbsiCTuu+8+7r333vV3X2pu3UuWLOGAAw7gmWee4eMf/3husVdS3v3+UXXp0mWjRz82BcX+TUiaHhGDipX3CIaZmZlZCzF27Fgef/xxVq1axRFHHLF+4fH06dM577zziAi6d+/OnXfe2ey6jznmGJYuXcq//vUvrrjiihabXFj1OcEwMzMzayG+973vFd1+yCGHfOT584ULhS1fLXn0YmN4kbeZmZmZmeXGCYaZmZlZPV6japbZmH8LTjDMzMzMCnTo0IElS5Y4ybBWLyJYsmQJHTp0aNZxXoNhZmZmVmCnnXbi9ddf56233vrIda1atarZF2f20bnf89OhQwd22mmnZh3jBMPMzMysQPv27enVq1cuddXU1LDvvvvmUpeVzv1eXZ4iZWZmZmZmuXGCYWZmZmZmuXGCYWZmZmZmuZHvkGC2aZD0PrCg2nG0QtsCb1c7iFbI/V4d7vfKc59Xh/u9/HaJiO2K7fAib7NNx4KIGFTt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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# -- variable importance summary -- \n", "varImpMetrics = client.describe_model_versions(\n", " modelId = MODEL_NAME,\n", " modelVersionNumber = model_version,\n", " modelType = MODEL_TYPE,\n", " maxResults = 10\n", ")['modelVersionDetails'][0]['trainingResultV2']['aggregatedVariablesImportanceMetrics'] \n", "\n", "df_var_imp = pd.DataFrame(varImpMetrics['logOddsMetrics']).sort_values(by='aggregatedVariablesImportance')\n", "\n", "# -- Variable importance Chart -- \n", "df_var_imp.plot.barh(x='variableNames',y='aggregatedVariablesImportance',figsize=(10,int(df_var_imp.shape[0])))\n", "plt.xlabel('Variable Importance (logOdds)')\n", "plt.grid()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 7. Create a Fraud Detector, generate Rules and assemble your Detector \n", "-----\n", "The following section will automatically generate a number of fraud, investigate and approve rules based on the false positive rate and score thresholds of your model. These are just example rules that you could create, it is recommended that you fine tune your rules specifically to your business use case. \n", " \n", "
💡 Key APIs for Generating Rules, Creating and Publishing a Detector \n", " \n", "- **put_detector**: Creates or updates a detector\n", "- **put_outcome**: Creates or updates an outcome\n", "- **create_rule**: Creates a rule for use with the specified detector\n", "- **update_rule_version**: Updates a rule version resulting in a new rule version (version 1, 2, 3 ...)\n", "- **create_detector_version**: Creates a detector version. The detector version starts in a DRAFT status\n", "- **update_detector_version**: Updates a detector version. The detector version attributes that you can update include models, external model endpoints, rules, rule execution mode, and description. You can only update a DRAFT detector version\n", "- **update_detector_version_status**: Updates the detector version’s status. You can perform the following promotions or demotions using UpdateDetectorVersionStatus: DRAFT to ACTIVE, ACTIVE to INACTIVE, and INACTIVE to ACTIVE\n", "- **describe_detector**: Gets all versions for a specified detector\n", "
" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# -- put detector, initalizes your detector -- \n", "response = client.put_detector(\n", " detectorId = DETECTOR_NAME, \n", " description = DETECTOR_DESC,\n", " eventTypeName = EVENT_TYPE )" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " --- score thresholds 1% to 6% --- \n", " cr adr atodr threshold\n", "0 0.01 0.86 0.84 750.0\n", "1 0.02 0.90 0.92 670.0\n", "2 0.03 0.92 0.92 620.0\n", "3 0.04 0.95 0.92 575.0\n", "4 0.05 0.96 0.97 540.0\n", "5 0.06 0.96 0.97 515.0\n" ] } ], "source": [ "# -- check the score thresholds with FPR from 1% to 6% --\n", "model_stat = df_model.sort_values(by='cr')\n", "model_stat['cr_bin']=np.ceil(model_stat['cr']*100)*0.01\n", "m = model_stat.loc[model_stat.groupby([\"cr_bin\"])[\"threshold\"].idxmin()] \n", "m = m.round(decimals=2)[['cr','adr','atodr','threshold']]\n", "print (\" --- score thresholds 1% to 6% --- \")\n", "print(m.loc[(m['cr'] > 0.0 ) & (m['cr'] <= 0.06)].reset_index(drop=True))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# -- decide what threshold and corresponding outcome you want to add -- \n", "# here, we create three simple rules by cutting the score at [950,750], \n", "# and create three outcome ['investigate', 'challenge', 'approve'] \n", "# it will create 3 rules:\n", "# score > 950: investigate\n", "# score > 750: challenge \n", "# score <= 750: approve\n", "\n", "score_cuts = [950,750] # Note: recommend to fine tune this based on your business use case\n", "outcomes = ['investigate', 'challenge', 'approve'] # Note: recommend to define this based on your business use case\n", "\n", "def create_outcomes(outcomes):\n", " \"\"\" \n", " Create Fraud Detector Outcomes \n", " \"\"\" \n", " for outcome in outcomes:\n", " print(\"creating outcome variable: {0} \".format(outcome))\n", " response = client.put_outcome(name = outcome, description = outcome)\n", "\n", "def create_rules(score_cuts, outcomes):\n", " \"\"\"\n", " Creating rules \n", " \n", " Arguments:\n", " score_cuts - list of score cuts to create rules\n", " outcomes - list of outcomes associated with the rules\n", " \n", " Returns:\n", " a rule list to used when creating detector\n", " \"\"\"\n", " \n", " if len(score_cuts)+1 != len(outcomes):\n", " logging.error('Your socre cuts and outcomes are not matched.')\n", " \n", " rule_list = []\n", " for i in range(len(outcomes)):\n", " # rule expression\n", " if i < (len(outcomes)-1):\n", " rule = \"${0}_insightscore > {1}\".format(MODEL_NAME,score_cuts[i])\n", " else:\n", " rule = \"${0}_insightscore <= {1}\".format(MODEL_NAME,score_cuts[i-1])\n", " \n", " # append to rule_list (used when create detector)\n", " rule_id = \"rules{0}_{1}\".format(i, MODEL_NAME)\n", " \n", " rule_list.append({\n", " \"ruleId\": rule_id, \n", " \"ruleVersion\" : '1',\n", " \"detectorId\" : DETECTOR_NAME\n", " })\n", " \n", " # create rules\n", " print(\"creating rule: {0}: IF {1} THEN {2}\".format(rule_id, rule, outcomes[i]))\n", " try:\n", " response = client.create_rule(\n", " ruleId = rule_id,\n", " detectorId = DETECTOR_NAME,\n", " expression = rule,\n", " language = 'DETECTORPL',\n", " outcomes = [outcomes[i]]\n", " )\n", " except:\n", " print(\"this rule already exists in this detector\")\n", " \n", " return rule_list\n", " \n", "# -- create outcomes -- \n", "print(\" -- create outcomes --\")\n", "create_outcomes(outcomes)\n", "\n", "# -- create rules --\n", "print(\" -- create rules --\")\n", "rule_list = create_rules(score_cuts, outcomes)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# -- create detector version --\n", "client.create_detector_version(\n", " detectorId = DETECTOR_NAME,\n", " rules = rule_list,\n", " modelVersions = [{\"modelId\": MODEL_NAME, \n", " \"modelType\": MODEL_TYPE,\n", " \"modelVersionNumber\": model_version}],\n", " # there are 2 options for ruleExecutionMode:\n", " # 'ALL_MATCHED' - return all matched rules' outcome\n", " # 'FIRST_MATCHED' - return first matched rule's outcome\n", " ruleExecutionMode = 'FIRST_MATCHED'\n", ")\n", "\n", "print(\"\\n -- detector created -- \")\n", "print(response) " ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Latest Detector Version: 1\n", "\n", " -- detector activated -- \n", "{'ResponseMetadata': {'RequestId': 'a1df5bee-a034-4459-886a-91d1c2c5d9da', 'HTTPStatusCode': 200, 'HTTPHeaders': {'date': 'Fri, 22 Jul 2022 23:21:28 GMT', 'content-type': 'application/x-amz-json-1.1', 'content-length': '2', 'connection': 'keep-alive', 'x-amzn-requestid': 'a1df5bee-a034-4459-886a-91d1c2c5d9da'}, 'RetryAttempts': 0}}\n" ] } ], "source": [ "# -- activate the latest detector version --\n", "detector_version_summaries = client.describe_detector(detectorId=DETECTOR_NAME)['detectorVersionSummaries']\n", "latest_detector_version = max([det['detectorVersionId'] for det in detector_version_summaries])\n", "print('Latest Detector Version:', latest_detector_version)\n", "\n", "response = client.update_detector_version_status(\n", " detectorId = DETECTOR_NAME,\n", " detectorVersionId = latest_detector_version,\n", " status = 'ACTIVE'\n", ")\n", "print(\"\\n -- detector activated -- \")\n", "print(response)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 8. Make predictions \n", "-----\n", "\n", "The following section will apply your detector to the latest 20% of your data to check the model performance. \n", "\n", "
💡 API for Making Predictions \n", "\n", "- **get_event_prediction**: Evaluates an event against a detector version. If a version ID is not provided, the detector’s (ACTIVE) version is used. \n", "\n", "\n", "
\n" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "N_pred = 1000" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 193 ms, sys: 107 ms, total: 301 ms\n", "Wall time: 1min 59s\n" ] } ], "source": [ "%%time\n", "def _predict(record):\n", " \"\"\"\n", " Get prediction on one event\n", " \"\"\"\n", " event_id = str(record[0])\n", " entity_id = str(record[1])\n", " event_timestamp = str(record[2])\n", " label_timestamp = str(record[4])\n", " \n", " try:\n", " rec_content = {event_variables[i]: str(record[5:][i]) for i in range(len(event_variables)) if pd.isnull(record[5+i])==False}\n", " pred = client.get_event_prediction(\n", " detectorId = DETECTOR_NAME,\n", " detectorVersionId = latest_detector_version,\n", " eventId = event_id,\n", " eventTypeName = EVENT_TYPE,\n", " eventTimestamp = event_timestamp, \n", " entities = [{\n", " 'entityType': entity_type, \n", " 'entityId': entity_id\n", " }],\n", " eventVariables = rec_content) \n", " record.append(pred['modelScores'][0]['scores'][\"{0}_insightscore\".format(MODEL_NAME)])\n", " record.append(pred['ruleResults'][0]['outcomes'])\n", " except:\n", " record.append(\"-999\")\n", " record.append([\"error\"])\n", " \n", " return record\n", "\n", "# -- get predictions in parallel --\n", "if TRAINING_PERC < 1:\n", " df_test = df[df['EVENT_TIMESTAMP'] > test_split_time]\n", "else: \n", " # used all data to train the model, GEP on the last 100 events to demonstrate the API\n", " df_test = df.tail(100)\n", "\n", "df_test = df_test.head(N_pred)\n", "cols_keep = ['EVENT_ID', 'ENTITY_ID', 'EVENT_TIMESTAMP', 'EVENT_LABEL', 'LABEL_TIMESTAMP'] + event_variables\n", "df_list = df_test[cols_keep].values.tolist()\n", "with Pool(processes = 5) as p:\n", " result = p.map(_predict, df_list)\n", "predictions = pd.DataFrame(result, columns = cols_keep + ['score', 'outcomes'])" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
EVENT_IDENTITY_IDEVENT_TIMESTAMPEVENT_LABELLABEL_TIMESTAMPipuseragentfpsession_idare_credentials_validscoreoutcomes
0ev7fz3ebsvgs97A34033244112022-05-27T21:56:54ZNaNNaN142.135.19.20Mozilla/5.0 (X11; Linux x86_64; rv:1.9.7.20) G...FP-e156e30473ee46dfSID-98c30b09877d6031True432.0[approve]
1evhfmud6ke2encA74688060852022-05-27T21:56:59ZNaNNaN177.186.170.126Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_8_1...FP-f54a5390ef306380SID-274478ebb420a1fcTrue34.0[approve]
2ev8ea5sbd0r5c9A02327632132022-05-27T21:57:32ZNaNNaN27.238.226.94Opera/9.85.(X11; Linux i686; ps-AF) Presto/2.9...FP-c9a75b1f6054c108SID-3b36e5cb3f499881True53.0[approve]
3evnqe2cutauoqxA60992691912022-05-27T21:57:41ZNaNNaN144.67.153.38Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_3 like...FP-b4e68a1de7a0e00aSID-41efa10b3354f9f2True831.0[challenge]
4evpl1oijpwktuvA74152071542022-05-27T21:57:44ZNaNNaN169.106.206.11Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0)...FP-ed13b8f486bc0b9fSID-6bbfb3b371ddd3b3True178.0[approve]
\n", "
" ], "text/plain": [ " EVENT_ID ENTITY_ID EVENT_TIMESTAMP EVENT_LABEL LABEL_TIMESTAMP ip useragent fp session_id are_credentials_valid score outcomes\n", "0 ev7fz3ebsvgs97 A3403324411 2022-05-27T21:56:54Z NaN NaN 142.135.19.20 Mozilla/5.0 (X11; Linux x86_64; rv:1.9.7.20) G... FP-e156e30473ee46df SID-98c30b09877d6031 True 432.0 [approve]\n", "1 evhfmud6ke2enc A7468806085 2022-05-27T21:56:59Z NaN NaN 177.186.170.126 Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_8_1... FP-f54a5390ef306380 SID-274478ebb420a1fc True 34.0 [approve]\n", "2 ev8ea5sbd0r5c9 A0232763213 2022-05-27T21:57:32Z NaN NaN 27.238.226.94 Opera/9.85.(X11; Linux i686; ps-AF) Presto/2.9... FP-c9a75b1f6054c108 SID-3b36e5cb3f499881 True 53.0 [approve]\n", "3 evnqe2cutauoqx A6099269191 2022-05-27T21:57:41Z NaN NaN 144.67.153.38 Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_3 like... FP-b4e68a1de7a0e00a SID-41efa10b3354f9f2 True 831.0 [challenge]\n", "4 evpl1oijpwktuv A7415207154 2022-05-27T21:57:44Z NaN NaN 169.106.206.11 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0)... FP-ed13b8f486bc0b9f SID-6bbfb3b371ddd3b3 True 178.0 [approve]" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# -- check the first 5 rows --\n", "predictions.head()" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "scrolled": false }, "outputs": [ { "data": { "image/png": "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E7LHHntkyy23zPHHH58zzjgjDzzwQM4444z8zu/8Tl7wghfkMY95TD7wgQ+karzZeh69hjZHUmvtDWtYdcQa+p+Q5IRh1QMAAACwLtdee21e+9rXZostfj32ZsqUKbn55puzePHizJ7965l5ttlmm8yYMWMUZY7MRF/aBgAAADBpzZ49O1//+tezbNmylY977703u+++e3bdddcsWrRoZd977rknv/zlL8fdz6N1pJIgCQAAANhsLV++PPfee+/Kx1ve8pa85z3vybXXXpskWbJkSc4+++wkySGHHJJzzjkn//7v/577778/73//+9PauDedz6xZs7LFFlvkF7/4xYS9l4kgSAIAAAA2WwceeGC23nrrlY/bbrstr3nNa/Kyl70s22+/fZ7znOfkwgsvTJI89alPzd/8zd/k8MMPz6677prtt98+u+yyS7bccsvV9rvNNtvkPe95T57//Odn+vTpueCCCyb6rQ1FrSk52xTMmzevXXzxxaMuY8LNXTB3tbaF8xeOoBIAAABYu8svvzz77rvvqMsYijvvvDPTp0/PlVdemb333nvU5ay3NX03VfWj1tq88bYxIgkAAACgp3POOSd333137rrrrvzZn/1Z5s6dmzlz5oy6rAkjSAIAAADo6eyzz85uu+2W3XbbLVdeeWW+9KUvPWon1h7P1FEXAAAAALCpOOmkk3LSSSeNuoyRMSIJAAAAgF4ESQAAAAD0IkgCAAAAoBdBEgAAAAC9CJIAAAAA6EWQBAAAADBB3vjGN+a9733vqMvYYFNHXQAAAACwGTl+xyHv//beXefMmZObb745U6ZMWdn285//PLvtttswKntUMCIJAAAA2Gydc845ufPOO1c+xoZIDzzwwAgrm5wESQAAAACdqsqnPvWp7LPPPtlnn32SJO94xzsye/bs7LDDDvmN3/iNfPe7313Zf9VL1c4777zsscceK5d/8pOf5FnPela23377HHbYYbn33nsn7s0MgSAJAAAAYIyzzjorF154YS677LIkybOf/exccsklWbp0aX7v934vhx56aK9A6P7778/BBx+cI488MkuXLs2hhx6ar3zlK8Muf6gESQAAAMBm6+CDD8706dMzffr0HHzwwUmS4447LjvvvHO23nrrJMkRRxyRGTNmZOrUqfnTP/3T3HfffbniiivWue8LLrggy5cvzzHHHJNp06blkEMOybOf/exhvp2hEyQBAAAAm62zzjory5Yty7Jly3LWWWclSWbPnv2wPh/96Eez7777Zscdd8z06dNz++2359Zbb13nvhcvXpzdd989VbWyba+99tqo9U80QRIAAADAGGODn+9+97v5q7/6q5x22mm57bbbsmzZsuy4445prSVJtt1229x9990r+990000rX++666654YYbVvZNkuuuu24C3sHwCJIAAAAA1uBXv/pVpk6dmlmzZuWBBx7IBz7wgdxxxx0r1z/jGc/I1772tSxdujQ33XRTPvGJT6xc99znPjdTp07NJz/5yTzwwAM588wzc9FFF43gXWw8giQAAACANXj5y1+eV77ylXnSk56UvfbaK1tttdXDLn078sgj8/SnPz1z5szJy172shx22GEr1z3mMY/JmWeemc9//vPZaaed8uUvfzmve93rRvE2NpoaO7xqUzNv3rx28cUXj7qMCTd3wdzV2hbOXziCSgAAAGDtLr/88uy7776jLoNxrOm7qaoftdbmjbeNEUkAAAAA9CJIAgAAAKAXQRIAAAAAvQiSAAAAAOhFkAQAAABAL4IkAAAAAHoRJAEAAADQiyAJAAAAgF4ESQAAAAAb4I1vfGPe+9739ur74he/OCeddNIGHWdU245n6kbbEwAAAMA6zF0wd6j7Xzh/Ye++VZUrr7wyT3ziE1e2HX/88bnqqqtyyimnDKO8TZ4RSQAAAAD0IkgCAAAAGMd5552XPfbYIx/96Eezyy67ZNddd83nPve5cfvedtttefWrX51Zs2Zlp512yqtf/eosWrToYX3+67/+KwcccEB23HHHHHTQQVm6dOnKdRdccEGe97znZfr06Xn605+e8847b411nXzyydl3332z00475eUvf3muvfbalevOPffcPOUpT8mOO+6Yt73tbWmtPbIPYRWCJAAAAIA1uOmmm3L77bfnhhtuyGc/+9kcffTRue2221br99BDD+VNb3pTrr322lx33XXZeuut87a3ve1hfb7whS/k5JNPzuLFizN16tS8/e1vT5LccMMNedWrXpX3vve9Wbp0aT7ykY/k9a9/fZYsWbLacc4666x86EMfyplnnpklS5bkhS98Yd7whjckSW699da8/vWvzwc/+MHceuutecITnpDvf//7G/XzECQBAAAArMG0adPyvve9L9OmTcuBBx6Y7bbbLldcccVq/WbMmJHXv/712WabbbL99tvnPe95T77zne88rM+RRx6Z/fffP9tuu23+4i/+IqeddloefPDBnHLKKTnwwANz4IEHZosttshLX/rSzJs3L1/72tdWO86JJ56Y4447Lvvuu2+mTp2ad7/73bnkkkty7bXX5mtf+1r222+/HHLIIZk2bVqOOeaYPO5xj9uon4cgCQAAANgsTZkyJcuXL39Y2/LlyzNt2rSVyzNmzMjUqb++V9k222yTO++8c7V93X333fnDP/zD7LXXXtlhhx3yohe9KMuWLcuDDz64ss/s2bNXvt5rr72yfPny3Hrrrbn22mtz+umnZ/r06Ssf3/ve93LjjTeudpxrr70273jHO1b223nnndNayw033JDFixc/7BhV9bDljUGQBAAAAGyW9txzz1xzzTUPa7v66quz1157rfe+PvrRj+aKK67IhRdemDvuuCPnn39+kjxsjqLrr79+5evrrrsu06ZNy8yZMzN79uwceeSRWbZs2crHXXfdlWOPPXa148yePTsnnnjiw/rec889ed7znpddd931YcdorT1seWMQJAEAAACbpcMOOywf/OAHs2jRojz00EP51re+lXPOOSeHHHLIeu/rV7/6VbbeeutMnz49S5cuzZ//+Z+v1ueUU07JZZddlrvvvjvve9/7csghh2TKlCk54ogjcs455+Qb3/hGHnzwwdx7770577zzVpusO0ne+ta35i//8i9z6aWXJkluv/32nH766UmSV73qVbn00ktz5pln5oEHHsgnP/nJ3HTTTev9XtZGkAQAAABslt73vvflec97Xl7wghdkp512yjvf+c6ceuqp2X///dd7X8ccc0zuueeezJw5M895znPyile8YrU+Rx55ZN74xjfmcY97XO6999588pOfTDIYZXT22WfnQx/6UGbNmpXZs2fnr//6r/PQQw+tto/Xvva1ede73pXDDz88O+ywQ/bff/98/etfT5LMnDkzp59+eo499tjMmDEjV155ZZ7//Oev93tZm9rYt4GbSPPmzWsXX3zxqMuYcHMXzF2tbeH8hSOoBAAAANbu8ssvz7777jvqMhjHmr6bqvpRa23eeNsYkQQAAABAL4IkAAAAAHoZWpBUVSdX1S1V9dNx1v1ZVbWqmjmm7biquqqqrqiqlw+rLgAAAAA2zDBHJH0+yWozS1XV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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# -- check the distribution by labels --\n", "plt.figure(figsize = (20,8))\n", "np.warnings.filterwarnings('ignore', category=np.VisibleDeprecationWarning)\n", "plt.hist([predictions[predictions['EVENT_LABEL'].isin(label_mapper['LEGIT'])]['score'], \n", " predictions[predictions['EVENT_LABEL'].isin(label_mapper['FRAUD'])]['score'], \n", " predictions[predictions['EVENT_LABEL'].isna()]['score']], bins = 50)\n", "plt.legend([\"Legit\", \"Fraud\", \"Unlabeled\"], fontsize=12)\n", "plt.title(\"Predicted Score Distribution By Label\")\n", "plt.xlabel(\"Predicted Score\")\n", "plt.ylabel(\"Frequency\")\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfEAAAHwCAYAAAC2blbYAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAABPi0lEQVR4nO3dd3iV9fnH8fdN2BvZQ0AREBw4UHEgGKYooMhe7tVqtWq1dbRaq7VqrdJalQpCEARFHCACIuBeoLJVUJApe+8k9++P85DfIYbkBHLy5CSf13Wdi/PsT74n5D7fZ5q7IyIiIomnWNgBRERE5MioiIuIiCQoFXEREZEEpSIuIiKSoFTERUREEpSKuIiISIJSERcREUlQKuJyVMzseTN74AiWczM7IR6ZpOCK9XM3s7Zmtio/MhUmZnajmT2dT9s61cw+zY9tyeGpiEvMzOwqM/s4epy73+TuD4eVKS+YWSsze8/MNpvZBjN7zcxqR003M/uHmW0KXo+bmQXTapjZK2a2xsy2mdknZnZO1LKXmNnHZrbVzH4xs/+ZWYWo6aXMbLiZbQ+m3xE1rbWZ7cz0cjO7Iph+pZnNCZZdFeQqHrXeYWb2s5ntMLNvzOzi/GjP/BC0w66gTVab2VNmlpRpnkvN7Mtgvk1mNtrM6mWap3bQTmuDdvrOzB4ys3KH2W5JM3vQzJYE610efH4N4/jjxsTMSgL3A09k+t3ZFbRX9O9R/SNY/yFfwNx9HrDVzLrm4Y8huaQiLgJVgKFAQ6ABsAN4KWr6DcBlQAvgVOBS4MZgWnngK+BM4BhgJPCOmZUPplcC/gbUAZoB9YAnotb9INA42O5FwN1m1hnA3T9y9/IHX8F2dwJTgmXLArcD1YBzgHbAXcG04sBKoE2Q4QHg1YJQbPJQi6Bd2gB9gGsOTjCznsAY4Bki7XMSsA/42MyqBPMcA3wGlAHOdfcKQAegMtDoMNscD3QD+hNp1xbAHCJtnysHv3Dloe7Ad+6+Ovp3h8jPDlA56vdpRR5tczT//39BwuDueumV8QL+CPxIpJAtAi4PxjcD9gJpRArJ1mD8COBvMaz3D8BaYA2RP7YOnBBMKwU8CawA1gHPA2WCaW2BVcDdwPpgHZcBXYAfgM3AvVHbKQU8HWxnTfC+VC7b4AxgR9Twp8ANUcPXAp9ns/x24MzDTOsBzI8aXg10jBp+GBh7mGVfAl7KZrt3ABOzmT4PuCKHnz1P2/toP/dscmasJxh+FXg2eG/Az8DdmZYpBiwA/hoM/w2YDxSL8feiPbAHODabeZYD7aOGHwReDt43DHJfG/zMHxL5QnZLpnXMBXoE708E3gva/XugdzbbHg7cn8X4g9stHgxXAoYFn8vqoB2SgmknAB8A24CNwLhg/IfBOnYR+f/fJxhfN2iTXP0f0yvvXuqJS2Y/Aq2J/Ed/CHjZzGq7+2LgJuAzj3yTrxzrCoOe5V1EejmNifwxjPYPoAlwGpE/InWBP0dNrwWUjhr/P2Agkd5va+DPZnZ8MO99QKtgXS2As4nsYsyNC4GFUcMnEfnDetBc/r93cwgzOw0oCSzNad1Bj7BOLOs2s7JATyI9/VhzRy9fk0gbZzk9kzxp7zz43GNiZicGuQ62eVOgPvBa9Hzung68HuQhyDMhGB+L9sCX7r4ytxkzaUPkS3EnInsL+h2cYGbNieyVeSfYpf9eME+NYL7/mlmWv3vAKUQKfU5GAqlE2vx0oCNwXTDtYWAakb1T9YB/A7j7hcH0FsH//3HB+NXAASJtLmEI+1uEXgX7BXwLdA/eXwV8nGn6CHLoiRPpITwWNdyEoCdFpNe0C2gUNf1cYFnwvi2Rb/oHewoVgmXPiZp/DnBZ8P5HoEvUtE7A8lz8vKcS6fW0jhqXBpwYNdw4yGCZlq1IpGf3p8OsuwOwBWgSDB8brKd0pnl+lRcYBCzLvM2o6VcT6UFXy2JaCWA68EIMP3+etXcefO459cS3B+tw4BWC3iBwQeZ2jVruJmBJ8H4JcFMufjf+x2H2kkTNs5yce+LHR02vEPwMDYLhR4Dhwfs+wEeZ1v8C8JfDbHsJ0DmL8Qe3WxyoSeSwQpmo6f2AmcH7FCKHluodps1PyGL8auDCWNtRr7x9qScuhzCzwWb2bXAi1lbgZCLHFI9GHSLHZw/6Oep9dSLHdudEbXNKMP6gTe6eFrzfE/y7Lmr6HiLHpg9uK3r9PwfjchSctPMucJu7fxQ1aSeRAn1QRWCnB3/BgmXLABOJ7Gb/exbrbkWkR9XT3X+IWu/B9UWve0cW8a4EUqK3GbXuy4DHgIvdfWOmacWAUcB+4JYs1puVvGrvo/3cc3JGkKMPkXMCDp6MdrANamexTO2o6ZsOM8/h5Hb+w8loE3ffAbwD9A1G9SVynBkiPfJzDrZP0EYDiOwpycoWIl8KstOAyJe6tVHrfIFITx8ih1EM+NLMFprZNVmv5hAVgK0xzCdxoCIuGcysAZHexi1AVY/sMl9A5D81RL6JH4m1RHqdB0WfGbuRSFE4yd0rB69KHjkh50isIfKHKnpba3JaKPjZpwMPu/uoTJMXEtlVfFALonZLm1kp4E0iPZJfneRjZqcDbwPXuPv7B8e7+xYibXPYdQfLH0ukZ5qSxbo7E/nMurr7/EzTjMixz5pEjoUf+PVPftSya++4f+4e8SqRE9QO7or/nsheiV7R8wZfaK4ADn4G04HLg/GxmA6cnfkM90x2EflyclBWBTfz/6NXgH5mdi6Rk+xmBuNXAh9EtU9lj+zKvvkw255HZG9HdlYS6YlXi1pnRXc/CcDdf3H36929DpHf5f9aNpcEmlkdIoePYtmNL3GgIi7RyhH5A7MBwMyuJtITP2gdUC+4lCU3XgWuMrPmwbHdvxyc4JHjkf8D/mVmNYLt1jWzTkf4M7wC3G9m1c2sGpE/7C9nt4CZ1QVmEDkx6vksZkkB7ghy1QHuJHIYATMrQeSM5T3AYM90fNXMTibSw7zV3SceZt33m1mV4Nju9QfXHWUQ8Km7/5hp3clEem1XuPuXWaz7OSLHXru6+54spueF7No7Pz/3x4AbzKxWsLfiriBXfzMrY2a1gBeJ7On4V7DMU8HwyOBL3MEMT5nZqZk34O7TiRyjfsPMzjSz4mZWwcxuiuqxfgv0NbMSZtaSyHkMOZlM5IvQX4mcSHbwd2gS0MTMBgXrK2FmZ5lZs2zW0ya7Dbn7WiLHvP9pZhXNrJiZNTKzNsHP3yvqS8oWIn8PDu6VWQccn2mVbYEZ7r4vhp9T4iHs/fl6FawXkWNym4n0lJ4icqbqdcG0kkR2/W0GNgbjRhDb2el/BH4h67OUSwOPAj8ROc65GPhdMK0tUcdGiRzXc6Bh1LiPgYFR6xpCpBe4Nnj/q2OjmbL9JVjnzuhX1HQDHg9+7s3BewumtQmW3Z1p+dbB9JeA9EzTFkatuxSRY8fbifyRvCOLfN8B12YxfiaRE5Si1/1uMK1BkGtvpukDcmiLPG3vvPrcs8j5q+OzRA6F/DNquDuRy/92BZ/bK2Q6s5zILv/hQcYdQVv/BSh7mO2WJHLC59JgvT8T+XJQP5h+PPBF0NbvBO2R+Zh48SzWOyyYdlam8U2D9Wwgsjt/BnDaYbKVIHLWe51M4w/ZLpGTVp8jsrdiG/AN0DeY9jiRPUo7iZzvEH1Vxk3BZ7yV4Cz5IFu3sP9uFeXXwT9EIiKS4MzsBqC5u9+eD9s6BRjq7ufGe1tyeCriIiIiCUrHxCVPmNm99utbhO40s3fDzgYFP19+UluIFB7qiYuIiCQo9cRFREQSVF7fgD/uqlWr5g0bNgw7hoiISL6ZM2fORnf/1c2QEq6IN2zYkNmzZ4cdQ0REJN+Y2c9ZjdfudBERkQSlIi4iIpKgVMRFREQSlIq4iIhIglIRFxERSVAq4iIiIglKRVxERCRBqYiLiIgkKBVxERGRBKUiLiIikqBUxEVERBKUiriIiEiCUhEXERFJUCriIiIiCSpuRdzMhpvZejNbcJjpZmZDzGypmc0zszPilUVERKQwimdPfATQOZvpFwONg9cNwHNxzCIiIlLoxK2Iu/uHwOZsZukOpHjE50BlM6sdrzwiIiLx5u75ur3i+bq1Q9UFVkYNrwrGrQ0nTowmXALLJoedQkRECpg12yrQa1Qv/n3Zu5zxrzX5ss0wT2yzLMZl+RXGzG4ws9lmNnvDhg1xjpUDFXAREclk5daKtHnuKj5dXp87J3bMtx55mD3xVcCxUcP1gCy/urj7UGAoQMuWLfN3X8Xh3FkwYoiISLiWL99KcvJIlm3cymmn1eK19/6AWVb91LwXZk/8bWBwcJZ6K2CbuxfsXekiIiKZDBv2NcuWbeWss+owY8ZgqlUrm2/bjltP3MxeAdoC1cxsFfAXoASAuz8PTAa6AEuB3cDV8coiIiISLw89dBEVKpTixhvPpFKl0vm67bgVcXfvl8N0B34br+2LiIjEy3ffbaR69bJUrVqWYsWMu+8+P5QcumObiIhILsyd+wutW79Ex44vs3Xr3lCzqIiLiIjE6Ouv15KcnMLGjbupUaMcpUolhZpHRVxERCQGX3yxiuTkkWzevIeuXZvw5pt9KFOmRKiZVMRFRERy8MknK+jQYRTbtu2jR49mjB/fm1KlwrxKO0JFXEREJBs//LCJTp1eZseO/fTtezJjx15ByZLh7kY/KPyvESIiIgVY48bHMGDAKezZk8pLL3UnKang9H9VxEVERLKQnu4UK2aYGc89dynuXqAKOGh3uoiIyK+89dZ3tGr1Ips27QagWDErcAUcVMRFREQO8dprC+nZ8zW++moNKSlzw46TLRVxERGRwJgx8+nb93VSU9O5557zuf32VmFHypaKuIiICDBy5LcMHDiB9HTngQcu5O9/b5dvTyM7UjqxTUREirz//W8ON944CXd4+OGLuP/+C8OOFBMVcRERKfLmz1+POzz+eHv+8IdwHmZyJFTERUSkyHv66c50796Udu2ODztKruiYuIiIFEkvvvg1GzbsAiKXkCVaAQcVcRERKWLcnb/+9QOuv34inTuPJjU1PexIR0y700VEpMhwd+6/fwaPPvoxxYoZt99+DsWLJ25/VkVcRESKBHfn7rvf48knPyMpyRg9ugd9+pwcdqyjoiIuIiKFnrtz++1TGDLkS0qUKMbYsT3p0aNZ2LGOmoq4iIgUeq+/vpghQ76kZMkkxo/vRdeuTcOOlCdUxEVEpNC74opm/P73rejYsRGdO58Qdpw8oyIuIiKFUmpqOtu37+OYY8pgZjz1VKewI+W5xD0lT0RE5DAOHEhjwIAJtGkzgo0bd4cdJ27UExcRkUJl//40+vYdzxtvfEfFiqVYtmwL1aqVDTtWXKiIi4hIobF3byo9e77KO+8soXLl0kybNpCzzqobdqy4UREXEZFCYffuA1x++TimTfuRqlXL8N57gzj99Nphx4orFXEREUl4e/emcumlY5g5czk1apRj+vRBnHJKzbBjxZ1ObBMRkYRXqlQSp55ak1q1yjNr1pVFooCDiriIiBQCZsa//tWJb765kWbNqocdJ9+oiIuISELavHkPAwZMYP36yONEzYxatcqHnCp/6Zi4iIgknI0bd9O+fQpz565j1679vPlm37AjhUJFXEREEsq6dTtp1y6FhQs30KRJVZ59tkvYkUKjIi4iIgljzZodtGuXwnffbaR58+pMnz6I2rUrhB0rNCriIiKSEFau3EZycgpLl27mlFNqMH36YGrUKBd2rFDpxDYREUkIY8bMZ+nSzZxxRm1mzryyyBdwUE9cREQSxN13n0+pUsW58soWVKlSJuw4BYKKuIiIFFjff7+RihVLUbt2BcyM229vFXakAkW700VEpEBasGA9bdqMoH37UWzYsCvsOAWSiriIiBQ4c+f+wkUXjWTdul3UrVuBcuVKhh2pQFIRFxGRAmXOnDVcdNFINm7cTZcujXn77X6ULVsi7FgFkoq4iIgUGJ9/vop27VLYsmUv3bs3ZcKE3pQurdO3DkctIyIiBcLy5Vvp2HEUO3bsp2fP5owZ04MSJZLCjlWgqYiLiEiB0KBBJW644UzWrt3JyJGXUby4dhbnREVcRERClZaWTlJSMcyMJ57oQHq6k5SkAh4LtZKIiITmnXd+4IwzhvLLLzuByONEVcBjp5YSEZFQvPHGYi6/fBzz5q1jxIhvw46TkFTERUQk37366kJ69XqNAwfSueOOVtxzz/lhR0pIKuIiIpKvRo+eR79+r5OW5vzpTxfw5JMdMbOwYyUkFXEREck3L730DYMGvUF6uvOXv7ThkUeSVcCPgs5OFxGRfLNkyWbc4ZFHkrn33tZhx0l4KuIiIpJvHnkkmY4dG9G2bcOwoxQK2p0uIiJx9eKLX7NmzQ4gcgmZCnjeUREXEZG4efTRj7j++om0b5/Cvn2pYccpdFTERUQkz7k7Dz44i/vum4EZ3HXXeZQqpSO4eU0tKiIiecrduffe93nssU8oVswYOfIyBg48NexYhZKKuIiI5Bl35667pvHUU5+TlGSMHt2DPn1ODjtWoaUiLiIieWby5CU89dTnlChRjHHjenL55c3CjlSoqYiLiEie6dKlMfff35pzzqnHpZc2CTtOoaciLiIiRyUtLZ1Nm/ZQo0Y5zIyHH04OO1KRobPTRUTkiKWmpjN48Jucf/7wjGvBJf+oiIuIyBE5cCCN/v1fZ8yY+fzyy05+/nlr2JGKHO1OFxGRXNu3L5U+fcbz1lvfU7FiKaZMGcC55x4bdqwiR0VcRERyZe/eVK644lUmT15ClSqlmTZtEC1b1gk7VpGkIi4iIjE7cCCNbt1e4b33fqJatbK8994gTjutVtixiiwdExcRkZiVKJFEq1b1qFmzHDNnXqkCHjIVcRERyZWHHmrLvHk3c/LJNcKOUuSpiIuISLa2bt1L377jWbVqOxB5nGiNGuVCTiWgY+IiIpKNzZv30KHDKL7+ei1btuxl6tSBYUeSKCriIiKSpQ0bdtG+/SjmzVtHo0ZV+N//uoYdSTJRERcRkV/55ZedtGuXwqJFG2jatCrvvz+YunUrhh1LMonrMXEz62xm35vZUjP7YxbTK5nZRDOba2YLzezqeOYREZGcrV69nTZtRrBo0QaaN6/OrFlXqYAXUHEr4maWBDwLXAw0B/qZWfNMs/0WWOTuLYC2wD/NrGS8MomISM4mTFjMDz9sokWLmsyadSW1apUPO5IcRjx3p58NLHX3nwDMbCzQHVgUNY8DFczMgPLAZiA1jplERCQHt9xyNsWLF6NPn5M55pgyYceRbMSziNcFVkYNrwLOyTTPf4C3gTVABaCPu6fHMZOIiGRhyZJNlCyZRIMGlTEzbr75rLAjSQzieUzcshjnmYY7Ad8CdYDTgP+Y2a8OvJjZDWY228xmb9iwIa9ziogUaYsXb6BNmxEkJ6ewevX2sONILsSziK8Coh9pU49Ijzva1cAEj1gKLANOzLwidx/q7i3dvWX16tXjFlhEpKhZsGA9bduOZO3anTRoUIlKlUqHHUlyIZ5F/CugsZkdF5ys1pfIrvNoK4B2AGZWE2gK/BTHTCIiEvjmm7W0bTuC9et30aHD8Uya1J/y5XVucSKJ2zFxd081s1uAqUASMNzdF5rZTcH054GHgRFmNp/I7vd73H1jvDKJiEjEV1+tpmPHl9m6dS9dujTm9dd7U7q0bh2SaOL6ibn7ZGBypnHPR71fA3SMZwYRETnUmjU7aN9+FNu376N796aMG9eTUqVUwBORPjURkSKmTp0K3HnnuSxYsJ7Ro3tQokRS2JHkCKmIi4gUEamp6RQvHjkV6oEHLsQdihXL6kIiSRR6FKmISBEwdepSTjnlOX7+eSsQeZyoCnjiUxEXESnkJk36gW7dxvLddxsZPvybsONIHlIRFxEpxN54YzE9eoxj//40brnlLB58sG3YkSQPqYiLiBRS48YtoFev1zhwIJ077zyXIUMuJvKoCiksVMRFRAqhl1+eR//+E0hLc/70pwt44okOKuCFkIq4iEghtGrVdtLTnQcfbMMjjySrgBdSusRMRKQQ+uMfL+D884+ldesGYUeROFJPXESkkBg27GuWLduSMawCXvipiIuIFAJPPvkp1103kXbtUti9+0DYcSSfqIiLiCS4Rx75kD/84T3MIrvRy5YtEXYkySc6Ji4ikqDcnQcfnMVf//ohZjB8eHeuuuq0sGNJPlIRFxFJQO7Ovfe+z2OPfUJSkpGScjn9+58SdizJZyriIiIJ6IMPfuaxxz6hePFivPLKFfTs2TzsSBICFXERkQTUtm1DHnusHSeeWI3u3U8MO46EREVcRCRBpKc769btpHbtCgDcc88FISeSsOnsdBGRBJCWls7VV7/FOee8yPLlW8OOIwWEiriISAGXmprOoEFvkJIyl82b97By5bawI0kBod3pIiIF2P79afTv/zqvv76YChVK8u67Azj//Pphx5ICQkVcRKSA2rcvlV69XmPixB+oVKkUU6cO5Jxz6oUdSwoQFXERkQIoLS2dyy8fx7vvLuWYY8owbdpAzjyzTtixpIDRMXERkQIoKakYbds2pHr1ssyYMVgFXLKknriISAF1993nc801p1OtWtmwo0gBpZ64iEgBsW3bXnr3fo0ff9ycMU4FXLKjnriISAGwZcseOnV6ma++WsPatTv58MOrMLOwY0kBpyIuIhKyTZt206HDKL755hcaNqzMqFGXq4BLTFTERURCtH79Ltq3T2H+/PWccMIxzJgxmGOPrRR2LEkQKuIiIiFZu3YH7dqlsHjxRk48sRrvvz+YOnUqhB1LEoiKuIhISKZMWcrixRs5+eQaTJ8+iJo1y4cdSRKMiriISEiuvvp0zIxLL22is9DliKiIi4jko59+2kJqajpNmlQF4KqrTgs3kCQ0FXERkXzyww+bSE4eCcDHH19Dw4aVww0kCU83exERyQeLFm2gTZsRrF69g+OPr0LVqmXCjiSFgHriIiJxNn/+Otq1S2HDht0kJx/H22/3pVy5kmHHkkJAPXERkTj65pu1XHTRSDZs2E3Hjo2YNKmfCrjkGRVxEZE42bhxN+3apbBp0x4uuaQxb73VlzJlSoQdSwoR7U4XEYmTatXK8sADF/LRRysYO7YnJUsmhR1JChn1xEVE8tiBA2kZ73//+3MZP763CrjEhYq4iEgeev/9n2jW7FmWLNmUMa5YMT3MROJDRVxEJI9MmbKUSy99hR9/3MKwYd+EHUeKABVxEZE8MHHi93TvPpa9e1O56aYzefTRdmFHkiJARVxE5Ci9/voievR4lf370/jd787mv/+9RLvQJV+oiIuIHIWxYxfQp894UlPT+cMfzuPppztjpgIu+UNFXETkKGzcuJu0NOe++1rzj3+0VwGXfKXrxEVEjsItt5zNmWfW5txzjw07ihRB6omLiOTSsGFf8913GzOGVcAlLCriIiK58PTTn3PddRNp1y6F7dv3hR1HijgVcRGRGP3jHx/z+99PBeC++1pTsWKpkBNJUadj4iIiMXj44Q/4859nYQZDh3bluuvOCDuSiIq4iEh23J0HHpjJI498RLFixksvdWfw4BZhxxIBVMRFRLL1xRer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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# -- check AUC --\n", "predictions['event_label_int'] = np.nan\n", "predictions.loc[predictions['EVENT_LABEL'].isna(), 'event_label_int'] = 0\n", "predictions.loc[predictions['EVENT_LABEL'].isin(label_mapper['LEGIT']), 'event_label_int'] = 0\n", "predictions.loc[predictions['EVENT_LABEL'].isin(label_mapper['FRAUD']), 'event_label_int'] = 1\n", " \n", "fpr, tpr, threshold = roc_curve(predictions['event_label_int'], predictions['score'])\n", "test_auc = auc(fpr,tpr)\n", "\n", "plt.figure(figsize=(8,8))\n", "plt.plot(fpr, tpr, color='darkorange', lw=2, label=f\"AUC: {test_auc:.2f}\") \n", "plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')\n", "plt.title(MODEL_NAME+\" ROC Curve (Test)\")\n", "plt.xlabel('False Positive Rate (FPR)')\n", "plt.ylabel('True Positive Rate (FPR)')\n", "plt.legend(loc=\"lower right\", fontsize=12)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 9. Get reason codes for GEP calls\n" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "EVENT_ID: ev7fz3ebsvgs97\n", " eventVariableNames relativeImpact logOddsImpact\n", "0 [are_credentials_valid, user, useragent] 5 2.132089\n", "1 [are_credentials_valid, user] 0 0.007980\n", "2 [are_credentials_valid, fp, user] 0 -0.011876\n", "3 [user] 0 -0.151291\n", "4 [are_credentials_valid, ip, user] 5 3.638705\n" ] } ], "source": [ "for eid in predictions['EVENT_ID'].head(1):\n", " # Retrieve event_prediction_time\n", " response = client.list_event_predictions(\n", " eventId={\n", " 'value': eid\n", " },\n", " eventType={\n", " 'value': EVENT_TYPE\n", " },\n", " detectorId={\n", " 'value': DETECTOR_NAME\n", " },\n", " detectorVersionId={\n", " 'value': latest_detector_version\n", " },\n", " predictionTimeRange={\n", " 'startTime': '2022-01-01T00:00:00Z',\n", " 'endTime': '2024-01-01T00:00:00Z'\n", " }\n", " )\n", " last_prediction_timestamp = response['eventPredictionSummaries'][-1]['predictionTimestamp']\n", " \n", " # Get event prediction explanations\n", " response = client.get_event_prediction_metadata(\n", " eventId=eid,\n", " eventTypeName=EVENT_TYPE,\n", " detectorId=DETECTOR_NAME,\n", " detectorVersionId=latest_detector_version,\n", " predictionTimestamp=last_prediction_timestamp\n", " )\n", " print('EVENT_ID: ', eid)\n", " print(pd.DataFrame(response['evaluatedModelVersions'][0]['evaluations'][0]['predictionExplanations']['aggregatedVariablesImpactExplanations']))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### (Optional) Write Predictions to File\n", "\n", "
💡 Write Predictions \n", "\n", "- You can write your prediction dataset to a CSV to manually review predictions\n", "- Simply add a cell below and copy the code below\n", "\n", "
\n", "\n", "\n", "\n", "```python\n", "\n", "# -- optionally write predictions to a CSV file -- \n", "predictions.to_csv(MODEL_NAME + \".csv\", index=False)\n", "# -- or to a XLS file \n", "predictions.to_excel(MODEL_NAME + \".xlsx\", index=False)\n", "\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# predictions.to_csv(MODEL_NAME + \".csv\", index=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### (Optional) Clean up resources" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "CLEAN_UP_RESOURCES = False" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if CLEAN_UP_RESOURCES:\n", " \n", " # Deactivate detector\n", " response = client.update_detector_version_status(\n", " detectorId=DETECTOR_NAME,\n", " detectorVersionId=latest_detector_version,\n", " status='INACTIVE'\n", " )\n", "\n", " # Delete detector version\n", " response = client.get_detector_version(\n", " detectorId=DETECTOR_NAME,\n", " detectorVersionId=latest_detector_version\n", " )\n", " print(f'De-activating detector {DETECTOR_NAME}')\n", " while response['status']=='ACTIVE':\n", " time.sleep(60)\n", " response = client.get_detector_version(\n", " detectorId=DETECTOR_NAME,\n", " detectorVersionId=latest_detector_version\n", " )\n", " response = client.delete_detector_version(\n", " detectorId=DETECTOR_NAME,\n", " detectorVersionId=latest_detector_version\n", " )\n", " print(f'Deleted detector {DETECTOR_NAME}, version {latest_detector_version}')\n", "\n", " # Delete rules\n", " rules = client.get_rules(\n", " detectorId=DETECTOR_NAME\n", " )\n", " for rule in rules['ruleDetails']:\n", " response = client.delete_rule(\n", " rule={\n", " 'detectorId': DETECTOR_NAME,\n", " 'ruleId': rule['ruleId'],\n", " 'ruleVersion': rule['ruleVersion']\n", " }\n", " )\n", " print(f'Deleted rules of detector {DETECTOR_NAME}')\n", "\n", " # Delete Detector\n", " response = client.delete_detector(\n", " detectorId=DETECTOR_NAME\n", " )\n", " print(f'Deleted detector {DETECTOR_NAME}')\n", "\n", " # De-activate model version\n", " response = client.update_model_version_status(\n", " modelId=MODEL_NAME,\n", " modelType=MODEL_TYPE,\n", " modelVersionNumber=model_version,\n", " status='INACTIVE'\n", " )\n", " response = client.get_model_version(\n", " modelId=MODEL_NAME,\n", " modelType=MODEL_TYPE,\n", " modelVersionNumber=model_version\n", " )\n", " print(f'De-activating model {MODEL_NAME}')\n", "\n", " while response['status']!='TRAINING_COMPLETE':\n", " time.sleep(60)\n", " response = client.get_model_version(\n", " modelId=MODEL_NAME,\n", " modelType=MODEL_TYPE,\n", " modelVersionNumber=model_version\n", " )\n", "\n", " # Delete model version\n", " response = client.delete_model_version(\n", " modelId=MODEL_NAME,\n", " modelType=MODEL_TYPE,\n", " modelVersionNumber=model_version\n", " )\n", "\n", " # Delete model\n", " response = client.delete_model(\n", " modelId=MODEL_NAME,\n", " modelType=MODEL_TYPE\n", " )\n", " print(f'Deleted model {MODEL_NAME}')\n", "\n", " # Delete stored events\n", " response = client.delete_events_by_event_type(\n", " eventTypeName=EVENT_TYPE\n", " )\n", " response = client.get_delete_events_by_event_type_status(\n", " eventTypeName=EVENT_TYPE\n", " )\n", " while 'IN_PROGRESS' in response['eventsDeletionStatus']:\n", " time.sleep(60)\n", " print(f'Deleting event type: {EVENT_TYPE}')\n", " response = client.get_delete_events_by_event_type_status(\n", " eventTypeName=EVENT_TYPE\n", " )\n", "\n", " # Delete event type\n", " response = client.delete_event_type(\n", " name=EVENT_TYPE\n", " )\n", " print(f'Deleted event type {EVENT_TYPE}')" ] } ], "metadata": { "kernelspec": { "display_name": "conda_mxnet_latest_p37", "language": "python", "name": "conda_mxnet_latest_p37" }, "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.7.10" } }, "nbformat": 4, "nbformat_minor": 4 }