# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['craft_ai', 'craft_ai.pandas']

package_data = \
{'': ['*']}

install_requires = \
['python-dateutil>=2.8.1,<3.0.0',
 'requests>=2.20.1,<3.0.0',
 'semver>=2.7.7,<3.0.0',
 'tzlocal>=1.2.2,<2.0.0']

extras_require = \
{'pandas': ['pandas>=1.0.1,<2.0.0', 'IPython>=5.8.0,<6.0.0']}

setup_kwargs = {
    'name': 'craft-ai',
    'version': '2.0.0rc4',
    'description': 'craft ai API python 3 client',
    'long_description': '# **craft ai** API python client #\n\n[![PyPI](https://img.shields.io/pypi/v/craft-ai.svg?style=flat-square)](https://pypi.python.org/pypi?:action=display&name=craft-ai) [![Build Status](https://img.shields.io/travis/craft-ai/craft-ai-client-python/master.svg?style=flat-square)](https://travis-ci.org/craft-ai/craft-ai-client-python) [![License](https://img.shields.io/badge/license-BSD--3--Clause-42358A.svg?style=flat-square)](LICENSE) [![python](https://img.shields.io/pypi/pyversions/craft-ai.svg?style=flat-square)](https://pypi.python.org/pypi?:action=display&name=craft-ai)\n\n[**craft ai**\'s Explainable AI API](http://craft.ai) enables product & operational teams to quickly deploy and run explainable AIs. craft ai decodes your data streams to deliver self-learning services.\n\n## Get Started!\n\n### 0 - Signup\n\nIf you\'re reading this you are probably already registered with **craft ai**, if not, head to [`https://beta.craft.ai/signup`](https://beta.craft.ai/signup).\n\n### 1 - Create a project\n\nOnce your account is setup, let\'s create your first **project**! Go in the \'Projects\' tab in the **craft ai** control center at [`https://beta.craft.ai/inspector`](https://beta.craft.ai/inspector), and press **Create a project**.\n\nOnce it\'s done, you can click on your newly created project to retrieve its tokens. There are two types of tokens: **read** and **write**. You\'ll need the **write** token to create, update and delete your agent.\n\n### 2 - Setup\n\n#### Install ####\n\n#### [PIP](https://pypi.python.org/pypi/pip/) / [PyPI](https://pypi.python.org/pypi) ####\n\nLet\'s first install the package from pip.\n\n```sh\npip install --upgrade craft-ai\n```\n_Depending on your setup you may need to use `pip3` or `pipenv` instead of `pip`._\n\nThen import it in your code\n\n```python\nimport craft_ai\n```\n> This client also provides helpers to use it in conjuction with [pandas](#pandas-support)\n\n#### Initialize ####\n\n```python\nclient = craft_ai.Client({\n  "token": "{token}"\n})\n```\n\n### 3 - Create an agent\n\n**craft ai** is based on the concept of **agents**. In most use cases, one agent is created per user or per device.\n\nAn agent is an independent module that stores the history of the **context** of its user or device\'s context, and learns which **decision** to take based on the evolution of this context in the form of a **decision tree**.\n\nIn this example, we will create an agent that learns the **decision model** of a light bulb based on the time of the day and the number of people in the room. This dataset is treated as continuous context updates. If your data is more like events, please refer to the [Advanced Configuration section](#advanced-configuration) to know how to configure your agent. Here, the agent\'s context has 4 properties:\n\n- `peopleCount` which is a `continuous` property,\n- `timeOfDay` which is a `time_of_day` property,\n- `timezone`, a property of type `timezone` needed to generate proper values for `timeOfDay` (cf. the [context properties type section](#context-properties-types) for further information),\n- and finally `lightbulbState` which is an `enum` property that is also the output.\n\n> :information_source: `timeOfDay` is auto-generated, you will find more information below.\n\n```python\nagent_id = "my_first_agent"\nconfiguration = {\n  "context": {\n    "peopleCount": {\n      "type": "continuous"\n    },\n    "timeOfDay": {\n      "type": "time_of_day"\n    },\n    "timezone": {\n      "type": "timezone"\n    },\n    "lightbulbState": {\n      "type": "enum"\n    }\n  },\n  "output": ["lightbulbState"]\n}\n\nagent = client.create_agent(configuration, agent_id)\nprint("Agent", agent["id"], "has successfully been created")\n```\n\nPretty straightforward to test! Open [`https://beta.craft.ai/inspector`](https://beta.craft.ai/inspector), select you project and your agent is now listed.\n\nNow, if you run that a second time, you\'ll get an error: the agent `\'my_first_agent\'` is already existing. Let\'s see how we can delete it before recreating it.\n\n```python\nagent_id = "my_first_agent"\nclient.delete_agent(agent_id)\nprint("Agent", agent_id, "no longer exists")\n\nconfiguration = ...\nagent = client.create_agent(configuration, agent_id)\nprint("Agent", agent["id"], "has successfully been created")\n```\n\n_For further information, check the [\'create agent\' reference documentation](#create)._\n\n### 4 - Add context operations\n\nWe have now created our first agent but it is not able to do much, yet. To learn a decision model it needs to be provided with data, in **craft ai** these are called context operations.\n\nPlease note that only value changes are sent, thus if an operation doesn\'t contain a value, the previous known value is used.\n\nIn the following we add 8 operations:\n\n1. The initial one sets the initial state of the agent, on July 25 2016 at 5:30, in Paris, nobody is there and the light is off;\n2. At 7:02, someone enters the room the light is turned on;\n3. At 7:15, someone else enters the room;\n4. At 7:31, the light is turned off;\n5. At 8:12, everyone leaves the room;\n6. At 19:23, 2 persons enter the room;\n7. At 22:35, the light is turned on;\n8. At 23:06, everyone leaves the room and the light is turned off.\n\n\n```python\nagent_id = "my_first_agent"\nclient.delete_agent(agent_id)\nprint("Agent", agent_id, "no longer exists")\n\nconfiguration = ...\nagent = client.create_agent(configuration, agent_id)\nprint("Agent", agent["id"], "has successfully been created")\n\ncontext_list = [\n  {\n    "timestamp": 1469410200,\n    "context": {\n      "timezone": "+02:00",\n      "peopleCount": 0,\n      "lightbulbState": "OFF"\n    }\n  },\n  {\n    "timestamp": 1469415720,\n    "context": {\n      "peopleCount": 1,\n      "lightbulbState": "ON"\n    }\n  },\n  {\n    "timestamp": 1469416500,\n    "context": {\n      "peopleCount": 2\n    }\n  },\n  {\n    "timestamp": 1469417460,\n    "context": {\n      "lightbulbState": "OFF"\n    }\n  },\n  {\n    "timestamp": 1469419920,\n    "context": {\n      "peopleCount": 0\n    }\n  },\n  {\n    "timestamp": 1469460180,\n    "context": {\n      "peopleCount": 2\n    }\n  },\n  {\n    "timestamp": 1469471700,\n    "context": {\n      "lightbulbState": "ON"\n    }\n  },\n  {\n    "timestamp": 1469473560,\n    "context": {\n      "peopleCount": 0,\n      "lightbulbState": "OFF"\n    }\n  }\n]\nclient.add_operations(agent_id, context_list)\nprint("Successfully added initial operations to agent", agent_id, "!")\n```\n\nIn real-world applications, you\'ll probably do the same kind of things when the agent is created and then, regularly throughout the lifetime of the agent with newer data.\n\n_For further information, check the [\'add context operations\' reference documentation](#add-operations)._\n\n### 5 - Compute the decision tree\n\nThe agent has acquired a context history, we can now compute a decision tree from it! A decision tree models the output, allowing us to estimate what the output would be in a given context.\n\nThe decision tree is computed at a given timestamp, which means it will consider the context history from the creation of this agent up to this moment. Let\'s first try to compute the decision tree at midnight on July 26, 2016.\n\n```python\nagent_id = "my_first_agent"\n\nclient.delete_agent(agent_id)\nprint("Agent", agent_id, "no longer exists")\n\nconfiguration = ...\nagent = client.create_agent(configuration, agent_id)\nprint("Agent", agent["id"], "has successfully been created")\n\ncontext_list = ...\nclient.add_operations(agent_id, context_list)\nprint("Successfully added initial operations to agent", agent_id, "!")\n\ndt_timestamp = 1469476800\ndecision_tree = client.get_decision_tree(agent_id, dt_timestamp)\nprint("The full decision tree at timestamp", dt_timestamp, "is the following:")\nprint(decision_tree)\n""" Outputted tree is the following\n  {\n    "_version":"2.0.0",\n    "trees":{\n      "lightbulbState":{\n        "output_values" : ["OFF", "ON"],\n        "children":[\n          {\n            "children":[\n              {\n                "prediction":{\n                  "confidence":0.6774609088897705,\n                  "distribution":[0.8, 0.2],\n                  "value":"OFF",\n                  "nb_samples": 5\n                },\n                "decision_rule":{\n                  "operand":0.5,\n                  "operator":"<",\n                  "property":"peopleCount"\n                }\n              },\n              {\n                "prediction":{\n                  "confidence":0.8630361557006836,\n                  "distribution":[0.1, 0.9],\n                  "value":"ON",\n                  "nb_samples": 10\n                },\n                "decision_rule":{\n                  "operand":0.5,\n                  "operator":">=",\n                  "property":"peopleCount"\n                }\n              }\n            ],\n            "decision_rule":{\n              "operand":[\n                5,\n                5.6666665\n              ],\n              "operator":"[in[",\n              "property":"timeOfDay"\n            }\n          },\n          {\n            "children":[\n              {\n                "prediction":{\n                  "confidence":0.9947378635406494,\n                  "distribution":[1.0, 0.0],\n                  "value":"ON",\n                  "nb_samples": 10\n                },\n                "decision_rule":{\n                  "operand":[\n                    5.6666665,\n                    20.666666\n                  ],\n                  "operator":"[in[",\n                  "property":"timeOfDay"\n                }\n              },\n              {\n                "children":[\n                  {\n                    "prediction":{\n                      "confidence":0.969236433506012,\n                      "distribution":[0.95, 0.05],\n                      "value":"OFF",\n                      "nb_samples": 10\n                    },\n                    "decision_rule":{\n                      "operand":1,\n                      "operator":"<",\n                      "property":"peopleCount"\n                    }\n                  },\n                  {\n                    "prediction":{\n                      "confidence":0.8630361557006836,\n                      "distribution":[0.2, 0.8],\n                      "value":"ON",\n                      "nb_samples": 15\n                    },\n                    "decision_rule":{\n                      "operand":1,\n                      "operator":">=",\n                      "property":"peopleCount"\n                    }\n                  }\n                ],\n                "decision_rule":{\n                  "operand":[\n                    20.666666,\n                    5\n                  ],\n                  "operator":"[in[",\n                  "property":"timeOfDay"\n                }\n              }\n            ],\n            "decision_rule":{\n              "operand":[\n                5.6666665,\n                5\n              ],\n              "operator":"[in[",\n              "property":"timeOfDay"\n            }\n          }\n        ]\n      }\n    },\n    "configuration":{\n      "time_quantum":600,\n      "learning_period":9000000,\n      "context":{\n        "peopleCount":{\n          "type":"continuous"\n        },\n        "timeOfDay":{\n          "type":"time_of_day",\n          "is_generated":True\n        },\n        "timezone":{\n          "type":"timezone"\n        },\n        "lightbulbState":{\n          "type":"enum"\n        }\n      },\n      "output":[\n        "lightbulbState"\n      ]\n    }\n  }\n"""\n```\n\nTry to retrieve the tree at different timestamps to see how it gradually learns from the new operations. To visualize the trees, use the [inspector](https://beta.craft.ai/inspector)!\n\n_For further information, check the [\'compute decision tree\' reference documentation](#compute)._\n\n### 6 - Take a decision\n\nOnce the decision tree is computed it can be used to take a decision. In our case it is basically answering this type of question: "What is the anticipated **state of the lightbulb** at 7:15 if there are 2 persons in the room ?".\n\n```python\nagent_id = "my_first_agent"\n\nclient.delete_agent(agent_id)\nprint("Agent", agent_id, "no longer exists")\n\nconfiguration = ...\nagent = client.create_agent(configuration, agent_id)\nprint("Agent", agent["id"], "has successfully been created")\n\ncontext_list = ...\nclient.add_operations(agent_id, context_list)\nprint("Successfully added initial operations to agent", agent_id, "!")\n\ndt_timestamp = 1469476800\ndecision_tree = client.get_decision_tree(agent_id, dt_timestamp)\nprint("The decision tree at timestamp", dt_timestamp, "is the following:")\nprint(decision_tree)\n\ncontext = {\n  "timezone": "+02:00",\n  "timeOfDay": 7.25,\n  "peopleCount": 2\n}\nresp = client.decide(decision_tree, context)\nprint("The anticipated lightbulb state is:", resp["output"]["lightbulbState"]["predicted_value"])\n```\n\n_For further information, check the [\'take decision\' reference documentation](#take-decision)._\n\n### Python starter kit ###\n\nIf you prefer to get started from an existing code base, the official Python starter kit can get you there! Retrieve the sources locally and follow the "readme" to get a fully working **Wellness Coach** example using _real-world_ data.\n\n> [:package: _Get the **craft ai** Python Starter Kit_](https://github.com/craft-ai/craft-ai-starterkit-python)\n\n## API\n\n### Project\n\n**craft ai** agents belong to **projects**. In the current version, each identified users defines a owner and can create projects for themselves, in the future we will introduce shared projects.\n\n### Configuration\n\nEach agent has a configuration defining:\n\n- the context schema, i.e. the list of property keys and their type (as defined in the following section),\n- the output properties, i.e. the list of property keys on which the agent takes decisions,\n\n> :warning: In the current version, only one output property can be provided.\n\n- the `time_quantum`, i.e. the minimum amount of time, in seconds, that is meaningful for an agent; context updates occurring faster than this quantum won\'t be taken into account. As a rule of thumb, you should always choose the largest value that seems right and reduce it, if necessary, after some tests.\n- the `learning_period`, i.e. the maximum amount of time, in seconds, that matters for an agent; the agent\'s decision model can ignore context that is older than this duration. You should generally choose the smallest value that fits this description.\n\n> :warning: if no time_quantum is specified, the default value is 600.\n\n> :warning: if no learning_period is specified, the default value is 15000 time quantums.\n\n> :warning: the maximum learning_period value is 55000 \\* time_quantum.\n\n#### Context properties types\n\n##### Base types: `enum`, `continuous` and `boolean`\n\n`enum`, `continuous` and `boolean` are the three base **craft ai** types:\n\n- an `enum` property is a string;\n- a `continuous` property is a real number.\n- a `boolean` property is a boolean value: `true` or `false`\n\n> :warning: the absolute value of a `continuous` property must be less than 10<sup>20</sup>.\n\nA base type property can be defined as *optional* if its value is likely to be unknown at some point in time and that it is to be considered as a normal behavior, and not as a missing property. You can achieve that by adding `is_optional: true` to the property definition in your configuration.\n\n> :warning: An optional property cannot be set as being an output of the agent.\n\nHere is a simple example of configuration :\n```json\n{\n  "context": {\n    "timezone": {\n      "type": "enum"\n    },\n    "temperature": {\n      "type": "continuous",\n      "is_optional": true\n    },\n    "lightbulbState": {\n      "type": "enum"\n    }\n  },\n  "output": ["lightbulbState"],\n  "time_quantum": 100,\n  "learning_period": 108000\n}\n```\n\n##### Time types: `timezone`, `time_of_day`, `day_of_week`, `day_of_month` and `month_of_year`\n\n**craft ai** defines the following types related to time:\n\n- a `time_of_day` property is a real number belonging to **[0.0; 24.0[**, each value represents the number of hours in the day since midnight (e.g. 13.5 means 13:30),\n- a `day_of_week` property is an integer belonging to **[0, 6]**, each value represents a day of the week starting from Monday (0 is Monday, 6 is Sunday).\n- a `day_of_month` property is an integer belonging to **[1, 31]**, each value represents a day of the month.\n- a `month_of_year` property is an integer belonging to **[1, 12]**, each value represents a month of the year.\n- a `timezone` property can be:\n  * a string value representing the timezone as an offset from UTC, supported formats are:\n\n    - **±[hh]:[mm]**,\n    - **±[hh][mm]**,\n    - **±[hh]**,\n\n    where `hh` represent the hour and `mm` the minutes from UTC (eg. `+01:30`)), between `-12:00` and\n    `+14:00`.\n\n  * an integer belonging to **[-720, 840]** which represents the timezone as an offset from UTC:\n\n    - in hours if the integer belongs to **[-15, 15]**\n    - in minutes otherwise\n\n  * an abbreviation among the following:\n\n    - **UTC** or **Z** Universal Time Coordinated,\n    - **GMT** Greenwich Mean Time, as UTC,\n    - **BST** British Summer Time, as UTC+1 hour,\n    - **IST** Irish Summer Time, as UTC+1,\n    - **WET** Western Europe Time, as UTC,\n    - **WEST** Western Europe Summer Time, as UTC+1,\n    - **CET** Central Europe Time, as UTC+1,\n    - **CEST** Central Europe Summer Time, as UTC+2,\n    - **EET** Eastern Europe Time, as UTC+2,\n    - **EEST** Eastern Europe Summer Time, as UTC+3,\n    - **MSK** Moscow Time, as UTC+3,\n    - **MSD** Moscow Summer Time, as UTC+4,\n    - **AST** Atlantic Standard Time, as UTC-4,\n    - **ADT** Atlantic Daylight Time, as UTC-3,\n    - **EST** Eastern Standard Time, as UTC-5,\n    - **EDT** Eastern Daylight Saving Time, as UTC-4,\n    - **CST** Central Standard Time, as UTC-6,\n    - **CDT** Central Daylight Saving Time, as UTC-5,\n    - **MST** Mountain Standard Time, as UTC-7,\n    - **MDT** Mountain Daylight Saving Time, as UTC-6,\n    - **PST** Pacific Standard Time, as UTC-8,\n    - **PDT** Pacific Daylight Saving Time, as UTC-7,\n    - **HST** Hawaiian Standard Time, as UTC-10,\n    - **AKST** Alaska Standard Time, as UTC-9,\n    - **AKDT** Alaska Standard Daylight Saving Time, as UTC-8,\n    - **AEST** Australian Eastern Standard Time, as UTC+10,\n    - **AEDT** Australian Eastern Daylight Time, as UTC+11,\n    - **ACST** Australian Central Standard Time, as UTC+9.5,\n    - **ACDT** Australian Central Daylight Time, as UTC+10.5,\n    - **AWST** Australian Western Standard Time, as UTC+8.\n\n> :information_source: By default, the values of the `time_of_day` and `day_of_week`\n> properties are generated from the [`timestamp`](#timestamp) of an agent\'s\n> state and the agent\'s current `timezone`. Therefore, whenever you use generated\n> `time_of_day` and/or `day_of_week` in your configuration, you **must** provide a\n> `timezone` value in the context. There can only be one `timezone` property.\n>\n> If you wish to provide their values manually, add `is_generated: false` to the\n> time types properties in your configuration. In this case, since you provide the values, the\n> `timezone` property is not required, and you must update the context whenever\n> one of these time values changes in a way that is significant for your system.\n\n##### Examples\n\nLet\'s take a look at the following configuration. It is designed to model the **color** of a lightbulb (the `lightbulbColor` property, defined as an output) depending on the **outside light intensity** (the `lightIntensity` property), the **TV status** (the `TVactivated` property) the **time of the day** (the `time` property) and the **day of the week** (the `day` property). Since `TVactivated` doesn\'t make any sense if the TV isn\'t here, we also specify this property as `is_optional: true`.\n\n`day` and `time` values will be generated automatically, hence the need for\n`timezone`, the current Time Zone, to compute their value from given\n[`timestamps`](#timestamp).\n\nThe `time_quantum` is set to 100 seconds, which means that if the lightbulb\ncolor is changed from red to blue then from blue to purple in less that 1\nminutes and 40 seconds, only the change from red to purple will be taken into\naccount.\n\nThe `learning_period` is set to 108 000 seconds (one month) , which means that\nthe state of the lightbulb from more than a month ago can be ignored when learning\nthe decision model.\n\n```json\n{\n  "context": {\n    "lightIntensity": {\n      "type": "continuous"\n    },\n    "TVactivated": {\n      "type": "boolean",\n      "is_optional": true\n    },\n    "time": {\n      "type": "time_of_day"\n    },\n    "day": {\n      "type": "day_of_week"\n    },\n    "timezone": {\n      "type": "timezone"\n    },\n    "lightbulbColor": {\n      "type": "enum"\n    }\n  },\n  "output": ["lightbulbColor"],\n  "time_quantum": 100,\n  "learning_period": 108000\n}\n```\n\nIn this second example, the `time` property is not generated, no property of\ntype `timezone` is therefore needed. However values of `time` must be manually\nprovided continuously.\n\n```json\n{\n  "context": {\n    "time": {\n      "type": "time_of_day",\n      "is_generated": false\n    },\n    "lightIntensity": {\n      "type": "continuous"\n    },\n      "TVactivated": {\n      "type": "boolean"\n    },\n    "lightbulbColor": {\n      "type": "enum"\n    }\n  },\n  "output": ["lightbulbColor"],\n  "time_quantum": 100,\n  "learning_period": 108000\n}\n```\n\n### Timestamp\n\n**craft ai** API heavily relies on `timestamps`. A `timestamp` is an instant represented as a [Unix time](https://en.wikipedia.org/wiki/Unix_time), that is to say the amount of seconds elapsed since Thursday, 1 January 1970 at midnight UTC. In most programming languages this representation is easy to retrieve, you can refer to [**this page**](https://github.com/techgaun/unix-time/blob/master/README.md) to find out how.\n\n#### `craft_ai.Time` #####\n\nThe `craft_ai.Time` class facilitates the handling of time types in **craft ai**. It is able to extract the different **craft ai** formats from various _datetime_ representations, thanks to [datetime](https://docs.python.org/3.5/library/datetime.html).\n\n```python\n# From a unix timestamp and an explicit UTC offset\nt1 = craft_ai.Time(1465496929, "+10:00")\n\n# t1 == {\n#   utc: "2016-06-09T18:28:49.000Z",\n#   timestamp: 1465496929,\n#   day_of_week: 4,\n#   time_of_day: 4.480277777777778,\n#   timezone: "+10:00"\n# }\n\n# From a unix timestamp and using the local UTC offset.\nt2 = craft_ai.Time(1465496929)\n\n# Value are valid if in Paris !\n# t2 == {\n#   utc: "2016-06-09T18:28:49.000Z",\n#   timestamp: 1465496929,\n#   day_of_week: 3,\n#   time_of_day: 20.480277777777776,\n#   timezone: "+02:00"\n# }\n\n# From a ISO 8601 string. Note that here it should not have any ":" in the timezone part\nt3 = craft_ai.Time("1977-04-22T01:00:00-0500")\n\n# t3 == {\n#   utc: "1977-04-22T06:00:00.000Z",\n#   timestamp: 230536800,\n#   day_of_week: 4,\n#   time_of_day: 1,\n#   timezone: "-05:00"\n# }\n\n# Retrieve the current time with the local UTC offset\nnow = craft_ai.Time()\n\n# Retrieve the current time with the given UTC offset\nnowP5 = craft_ai.Time(timezone="+05:00")\n```\n\n### Advanced configuration\n\nThe following **advanced** configuration parameters can be set in specific cases. They are **optional**. Usually you would not need them.\n\n- **`operations_as_events`** is a boolean, either `true` or `false`. The default value is `false`. If it is set to true, all context operations are treated as events, as opposed to context updates. This is appropriate if the data for an agent is made of events that have no duration, and if many events are more significant than a few. If `operations_as_events` is `true`, `learning_period` and the advanced parameter `tree_max_operations` must be set as well. In that case, `time_quantum` is ignored because events have no duration, as opposed to the evolution of an agent\'s context over time.\n- **`tree_max_operations`** is a positive integer. It **can and must** be set only if `operations_as_events` is `true`. It defines the maximum number of events on which a single decision tree can be based. It is complementary to `learning_period`, which limits the maximum age of events on which a decision tree is based.\n- **`tree_max_depth`** is a positive integer. It defines the maximum depth of decision trees, which is the maximum distance between the root node and a leaf (terminal) node. A depth of 0 means that the tree is made of a single root node. By default, `tree_max_depth` is set to 6 if the output is categorical (e.g. `enum`), or to 4 if the output is numerical (e.g. `continuous`).\n\nThese advanced configuration parameters are optional, and will appear in the agent information returned by **craft ai** only if you set them to something other than their default value. If you intend to use them in a production environment, please get in touch with us.\n\n### Agent\n\n#### Create\n\nCreate a new agent, and define its [configuration](#configuration).\n\n> The agent\'s identifier is a case sensitive string between 1 and 36 characters long. It only accepts letters, digits, hyphen-minuses and underscores (i.e. the regular expression `/[a-zA-Z0-9_-]{1,36}/`).\n\n```python\nclient.create_agent(\n  { # The configuration\n    "context": {\n      "peopleCount": {\n        "type": "continuous"\n      },\n      "timeOfDay": {\n        "type": "time_of_day"\n      },\n      "timezone": {\n        "type": "timezone"\n      },\n      "lightbulbState": {\n        "type": "enum"\n      }\n    },\n    "output": [ "lightbulbState" ],\n    "time_quantum": 100,\n    "learning_period": 108000\n  },\n  "my_new_agent" # id for the agent, if undefined a random id is generated\n)\n```\n\n#### Delete\n\n```python\nclient.delete_agent(\n  "my_new_agent" # The agent id\n)\n```\n\n#### Retrieve\n\n```python\nclient.get_agent(\n  "my_new_agent" # The agent id\n)\n```\n\n#### List\n\n```python\nclient.list_agents()\n# Return a list of agents\' name\n# Example: [ "my_new_agent", "joyful_octopus", ... ]\n\n```\n\n#### Create and retrieve shared url\n\nCreate and get a shareable url to view an agent tree.\nOnly one url can be created at a time.\n\n```python\nclient.get_shared_agent_inspector_url(\n  "my_new_agent", # The agent id.\n  1464600256 # optional, the timestamp for which you want to inspect the tree.\n)\n```\n\n#### Delete shared url\n\nDelete a shareable url.\nThe previous url cannot access the agent tree anymore.\n\n```python\nclient.delete_shared_agent_inspector_url(\n  \'my_new_agent\' # The agent id.\n)\n```\n\n\n\n### Generator\n\nThe craft ai API lets you generate decision trees built on data from one or several agents by creating a generator. It is useful to:\n  - test several hyper-parameters and features sets without reloading all the data for each try\n  - gather data from different agents to make new models on top of them, enhancing the possible data combinations and allowing you to inspect the global behavior across your agents\n\nWe define the data stream(s) used by a generator by specifying a list of agents as a filter in its configuration. Other than the filter, the configuration of a generator is similar to an agent\'s configuration. It has to verify some additional properties:\n\n- Every feature defined in the context configuration of the generator must be present in **all** the agent that match the filter, with the same context types.\n- The parameters `operations_as_events` must be set to true.\n- It follows that the parameters `tree_max_operations` and `learning_period` must be set with valid integers.\n- The agent names provided in the list must be valid agent identifiers.\n\n#### Create\n\nCreate a new generator, and define its [configuration](#configuration).\n\n> The generator\'s identifier is a case sensitive string between 1 and 36 characters long. It only accepts letters, digits, hyphen-minuses and underscores (i.e. the regular expression `/[a-zA-Z0-9_-]{1,36}/`).\n\n```python\n\nGENERATOR_NAME = \'smarthome_gen\'\nGENERATOR_FILTER = [\'smarthome\']\nGENERATOR_CONFIGURATION = {\n  "context": {\n      "light": {\n          "type": "enum"\n      },\n      "tz": {\n          "type": "timezone"\n      },\n      "movement": {\n          "type": "continuous"\n      },\n      "time": {\n          "type": "time_of_day",\n          "is_generated": True\n      }\n  },\n  "output": [\n      "light"\n  ],\n  "learning_period": 1500000,\n  "tree_max_operations": 15000,\n  "operations_as_events": True,\n  \'filter\': GENERATOR_FILTER\n}\n\nclient.create_generator(\n  GENERATOR_CONFIGURATION, # A valid configuration.\n  GENERATOR_NAME # The generator id.\n)\n```\n\n#### Delete\n\n```python\nGENERATOR_NAME = \'smarthome_gen\'\nclient.delete_generator(\n  GENERATOR_NAME\n)\n```\n\n#### Retrieve\n\n```python\nGENERATOR_NAME = \'smarthome_gen\'\nclient.get_generator(\n  GENERATOR_NAME\n)\n\n### Ouputted info is the following\n"""{\n    "_version": "2.0.0"\n    "id": "smarthome_gen",\n    "configuration": {\n        "operations_as_events": True,\n        "learning_period": 1500000,\n        "tree_max_operations": 15000,\n        "context": {\n            "light": {\n                "type": "enum"\n            },\n            "tz": {\n                "type": "timezone"\n            },\n            "movement": {\n                "type": "continuous"\n            },\n            "time": {\n                "type": "time_of_day",\n                "is_generated": True\n            }\n        },\n        "output": [\n            "light"\n        ],\n        "filter": [\n            "smarthome"\n        ]\n    },\n    "firstTimestamp": 1254836352,\n    "lastTimestamp": 1272721522,\n    "agents": [\n        "smarthome"\n    ],\n  }"""\n###\n\n```\n\n#### Retrieve generators list\n\n```python\nclient.list_generators() # Return the list of generators in the project.\n```\n\n#### List operations in the generator\n\nRetrieve the context operations of agents matching the generator\'s filter. Each operation also contains the identifier of the agent for which it was added, in the `agent_id` property.\n\n```python\nGENERATOR_NAME = \'smarthome_gen\'\nSTART_TIMESTAMP = 1478894153\nEND_TIMESTAMP = 1478895266\n\nclient.get_operations_list(\n  GENERATOR_NAME, # The agent id\n  START_TIMESTAMP, # Optional, the **start** timestamp from which the\n              # operations are retrieved (inclusive bound)\n  END_TIMESTAMP # Optional, the **end** timestamp up to which the\n              # operations are retrieved (inclusive bound)\n)\n```\n\n> This call can generate multiple requests to the craft ai API as results are paginated.\n\n#### Get decision tree\n\n```python\nDECISION_TREE_TIMESTAMP = 1469473600\nGENERATOR_NAME = \'smarthome_gen\'\nclient.get_generator_decision_tree(\n  GENERATOR_NAME, # The agent id\n  DECISION_TREE_TIMESTAMP # The timestamp at which the decision tree is retrieved\n)\n\n""" Outputted tree is the following\n{\n  "_version": "2.0.0",\n  "trees": {\n    "light": {\n        "children": [\n            {\n                "predicted_value": "OFF",\n                "confidence": 0.9966583847999572,\n                "decision_rule": {\n                    "operand": [\n                        7.25,\n                        22.65\n                    ],\n                    "operator": "[in[",\n                    "property": "time"\n                }\n            },\n            {\n                "children": [\n                    {\n                        "predicted_value": "ON",\n                        "confidence": 0.9618390202522278,\n                        "decision_rule": {\n                            "operand": [\n                                22.65,\n                                0.06666667\n                            ],\n                            "operator": "[in[",\n                            "property": "time"\n                        }\n                    },\n                    {\n                        "children": [\n                            {\n                                "predicted_value": "OFF",\n                                "confidence": 0.9797198176383972,\n                                "decision_rule": {\n                                    "operand": [\n                                        0.06666667,\n                                        0.6\n                                    ],\n                                    "operator": "[in[",\n                                    "property": "time"\n                                }\n                            },\n                            {\n                                "children": [\n                                    {\n                                        "predicted_value": "ON",\n                                        "confidence": 0.9585137963294984,\n                                        "decision_rule": {\n                                            "operand": [\n                                                0.6,\n                                                2.25\n                                            ],\n                                            "operator": "[in[",\n                                            "property": "time"\n                                        }\n                                    },\n                                    {\n                                        "children": [\n                                            {\n                                                "predicted_value": "OFF",\n                                                "confidence": 0.8077218532562256,\n                                                "decision_rule": {\n                                                    "operand": [\n                                                        2.25,\n                                                        2.4666667\n                                                    ],\n                                                    "operator": "[in[",\n                                                    "property": "time"\n                                                }\n                                            },\n                                        ],\n                                        "decision_rule": {\n                                            "operand": [\n                                                2.25,\n                                                7.25\n                                            ],\n                                            "operator": "[in[",\n                                            "property": "time"\n                                        }\n                                    }\n                                ],\n                                "decision_rule": {\n                                    "operand": [\n                                        0.6,\n                                        7.25\n                                    ],\n                                    "operator": "[in[",\n                                    "property": "time"\n                                }\n                            }\n                        ],\n                        "decision_rule": {\n                            "operand": [\n                                0.06666667,\n                                7.25\n                            ],\n                            "operator": "[in[",\n                            "property": "time"\n                        }\n                    }\n                ],\n                "decision_rule": {\n                    "operand": [\n                        22.65,\n                        7.25\n                    ],\n                    "operator": "[in[",\n                    "property": "time"\n                }\n            }\n        ]\n    }\n},\n"configuration": {\n    "operations_as_events": True,\n    "learning_period": 1500000,\n    "tree_max_operations": 15000,\n    "context": {\n        "light": {\n            "type": "enum"\n        },\n        "tz": {\n            "type": "timezone"\n        },\n        "movement": {\n            "type": "continuous"\n        },\n        "time": {\n            "type": "time_of_day",\n            "is_generated": True\n        }\n    },\n    "output": [\n        "light"\n    ],\n    "filter": [\n        "smarthome"\n    ]\n  }\n}\n"""\n```\n\n#### Get decision\n\n```python\nconst CONTEXT_OPS = {\n  "tz": "+02:00",\n  "movement": 2,\n  "time": 7.5\n};\nconst DECISION_TREE_TIMESTAMP = 1469473600;\nconst GENERATOR_NAME = \'smarthome_gen\';\n\nclient.computeGeneratorDecision(\n  GENERATOR_NAME, # The name of the generator\n  DECISION_TREE_TIMESTAMP, # The timestamp at which the decision tree is retrieved\n  CONTEXT_OPS # A valid context operation according to the generator configuration\n)\n"""\n{\n  "_version": "2.0.0",\n  "context": {\n      "tz": "+02:00",\n      "movement": 2,\n      "time": 7.5\n  },\n  "output": {\n    "light": {\n        "predicted_value": "OFF",\n        "confidence": 0.8386044502258301,\n        "decision_rules": [\n            {\n                "operand": [\n                    2.1166666,\n                    10.333333\n                ],\n                "operator": "[in[",\n                "property": "time"\n            },\n            {\n                "operand": [\n                    2.1166666,\n                    9.3\n                ],\n                "operator": "[in[",\n                "property": "time"\n            },\n            {\n                "operand": [\n                    2.1166666,\n                    8.883333\n                ],\n                "operator": "[in[",\n                "property": "time"\n            },\n            {\n                "operand": [\n                    3.5333333,\n                    8.883333\n                ],\n                "operator": "[in[",\n                "property": "time"\n            }\n        ],\n        "nb_samples": 442,\n        "decision_path": "0-0-0-0-1",\n        "distribution": [\n            0.85067874,\n            0.14932127\n        ]\n    }\n  }\n}"""\n```\n\n### Context\n\n#### Add operations\n\n```python\nclient.add_operations(\n  "my_new_agent", # The agent id\n  [ # The list of context operations\n    {\n      "timestamp": 1469410200,\n      "context": {\n        "timezone": "+02:00",\n        "peopleCount": 0,\n        "lightbulbState": "OFF"\n      }\n    },\n    {\n      "timestamp": 1469415720,\n      "context": {\n        "peopleCount": 1,\n        "lightbulbState": "ON"\n      }\n    },\n    {\n      "timestamp": 1469416500,\n      "context": {\n        "peopleCount": 2\n      }\n    },\n    {\n      "timestamp": 1469417460,\n      "context": {\n        "lightbulbState": "OFF"\n      }\n    },\n    {\n      "timestamp": 1469419920,\n      "context": {\n        "peopleCount": 0\n      }\n    },\n    {\n      "timestamp": 1469460180,\n      "context": {\n        "peopleCount": 2\n      }\n    },\n    {\n      "timestamp": 1469471700,\n      "context": {\n        "lightbulbState": "ON"\n      }\n    },\n    {\n      "timestamp": 1469473560,\n      "context": {\n        "peopleCount": 0,\n        "lightbulbState": "OFF"\n      }\n    }\n  ]\n)\n```\n\n##### Missing Values\n\nIf the value of a base type property is **missing**, you can send a `null` value. **craft ai** will take into account as much information as possible from this incomplete context.\n\nA context operation with a missing value looks like:\n```json\n[\n  {\n    "timestamp": 1469415720,\n    "context": {\n      "peopleCount": "OFF",\n      "lightbulbState": null\n    }\n  },\n  ...\n]\n```\n\n##### Optional Values\n\nIf the value of an **optional** property is not filled at some point—as should be expected from an optional value—send the empty JSON Object `{}` to **craft ai**:\n\nA context with an optional value looks like:\n```json\n[\n  {\n    "timestamp": 1469415720,\n    "context": {\n      "timezone": "+02:00",\n      "temperature": {},\n      "lightbulbState": "OFF"\n    }\n  },\n  ...\n]\n```\n\n#### List operations\n\n```python\nclient.get_operations_list(\n  "my_new_agent", # The agent id\n  1478894153, # Optional, the **start** timestamp from which the\n              # operations are retrieved (inclusive bound)\n  1478895266, # Optional, the **end** timestamp up to which the\n              # operations are retrieved (inclusive bound)\n)\n```\n\n> This call can generate multiple requests to the craft ai API as results are paginated.\n\n#### Retrieve state\n\n```python\nclient.get_context_state(\n  "my_new_agent", # The agent id\n  1469473600 # The timestamp at which the context state is retrieved\n)\n```\n\n#### Retrieve state history\n\n```python\nclient.get_state_history(\n  "my_new_agent", # The agent id\n  1478894153, # Optional, the **start** timestamp from which the\n              # operations are retrieved (inclusive bound)\n  1478895266, # Optional, the **end** timestamp up to which the\n              # operations are retrieved (inclusive bound)\n)\n```\n\n### Decision tree\n\nDecision trees are computed at specific timestamps, directly by **craft ai** which learns from the context operations [added](#add-operations) throughout time.\n\nWhen you [compute](#compute) a decision tree, **craft ai** returns an object containing:\n\n- the **API version**\n- the agent\'s configuration as specified during the agent\'s [creation](#create-agent)\n- the tree itself as a JSON object:\n\n  - Internal nodes are represented by a `"decision_rule"` object and a `"children"` array. The first one, contains the `"property`, and the `"property"`\'s value, to decide which child matches a context.\n  - Leaves have a `"predicted_value"`, `"confidence"` and `"decision_rule"` object for this value, instead of a `"children"` array. `"predicted_value`" is an estimation of the output in the contexts matching the node. `"confidence"` is a number between 0 and 1 that indicates how confident **craft ai** is that the output is a reliable prediction. When the output is a numerical type, leaves also have a `"standard_deviation"` that indicates a margin of error around the `"predicted_value"`.\n  - The root only contains a `"children"` array.\n\n#### Compute\n\n```python\nclient.get_decision_tree(\n  "my_new_agent", # The agent id\n  1469473600 # Optional the timestamp at which we want the decision\n             # tree, default behavior is to return the decision tree\n             # from the latest timestamp in context operations\n)\n```\n\n#### Take decision\n\n> :information_source: To take a decision, first compute the decision tree then use the **offline interpreter**.\n\n### Bulk\n\nThe craft ai API includes a bulk route which provides a programmatic option to perform asynchronous operations on agents. It allows the user to create, delete, add operations and compute decision trees for several agents at once.\n\n> :warning: the bulk API is a quite advanced feature. It comes on top of the basic routes to create, delete, add context operations and compute decision tree. If messages are not self-explanatory, please refer to the basic routes that does the same operation for a single agent.\n\n\n\n#### Bulk - Create\n\nTo create several agents at once, use the method `create_agents_bulk` as the following:\n```python\nagent_id_1 = \'my_first_agent\'\nagent_id_2 = \'my_second_agent\'\n\nconfiguration_1 = {\n  "context": {\n    "peopleCount": {\n      "type": "continuous"\n    },\n    "timeOfDay": {\n      "type": "time_of_day"\n    },\n    "timezone": {\n      "type": "timezone"\n    },\n    "lightbulbState": {\n      "type": "enum"\n    }\n  },\n  "output": ["lightbulbState"]\n}\nconfiguration_2 = { ... }\n\ncreation_bulk_payload = [\n  {\'id\': agent_id_1, \'configuration\': configuration_1},\n  {\'id\': agent_id_2, \'configuration\': configuration_2}\n]\n\ncreated_agents = client.create_agents_bulk(creation_bulk_payload)\nprint(created_agents)\n```\nThe variable `created_agents` is an **array of responses**. If an agent has been successfully created, the corresponding response is an object similar to the classic `create_agent()` response. When there are **mixed results**, `created_agents` should looks like:\n```python\n[\n  {\'_version\': \'2.0.0\',                                 # creation succeeded\n   \'configuration\': {\n      \'context\': {\n        ...\n      },\n      \'output\': ...,\n      \'learning_period\': 1500000,\n      \'time_quantum\': 100\n   },\n   \'id\': \'my_first_agent\'},\n  {\'error\': CraftAiBadRequestError(\'error-message\'),    # creation failed\n   \'id\': \'my_second_agent\'\n  }\n]\n```\n\n#### Bulk - Delete\n\nTo delete several agents at once, use the method `delete_agents_bulk` as the following:\n```python\nagent_id_1 = \'my_first_agent\'\nagent_id_2 = \'my_second_agent\'\n\ndeletion_bulk_payload = [\n  {\'id\': agent_id_1},\n  {\'id\': agent_id_2}\n]\n\ndeleted_agents = client.delete_agents_bulk(creation_bulk_payload)\nprint(agents_deleted)\n```\nThe variable `deleted_agents` is an **array of responses**. If an agent has been successfully deleted, the corresponding response is an object similar to the classic `delete_agent()` response. When there are **mixed results**, `deleted_agents` should looks like:\n\n```python\n[\n  {\'id\': \'my_first_agent\',                              # deletion succeeded\n   \'creationDate\': 1557492944277,\n   \'lastContextUpdate\': 1557492944277,\n   \'lastTreeUpdate\': 1557492944277,\n   \'configuration\': {\n      \'context\': {\n        ...\n      },\n      \'output\': ...,\n      \'learning_period\': 1500000,\n      \'time_quantum\': 100\n   },\n   \'_version\': \'2.0.0\'\n  },\n  {\'error\': CraftAiBadRequestError(\'error-message\'),    # deletion failed\n   \'id\': \'my_second_agent\'\n  },\n  {\'id\': \'my_unknown_agent\'}                            # deletion succeeded\n]\n```\n\n#### Bulk - Add context Operations\n\nTo add operations to several agents at once, use the method `add_operations_bulk` as the following:\n```python\nagent_id_1 = \'my_first_agent\'\nagent_id_2 = \'my_second_agent\'\n\noperations_agent_1 = [\n  {\n    \'timestamp\': 1469410200,\n    \'context\': {\n      \'timezone\': \'+02:00\',\n      \'peopleCount\': 0,\n      \'lightbulbState\': \'OFF\'\n    }\n  },\n  {\n    \'timestamp\': 1469410200,\n    \'context\': {\n      \'peopleCount\': 1,\n      \'lightbulbState\': \'ON\'\n    }\n  },\n    {\n    \'timestamp\': 1469410200,\n    \'context\': {\n      \'peopleCount\': 2\n    }\n  },\n    {\n    \'timestamp\': 1469410200,\n    \'context\': {\n      \'lightbulbState\': \'OFF\'\n    }\n  }\n]\noperations_agent_2 = [ ... ]\n\naddition_operations_bulk_payload = [\n  {\'id\': agent_id_1, \'operations\': operations_agent_1},\n  {\'id\': agent_id_2, \'operations\': operations_agent_2}\n]\n\nagents = client.addAgentContextOperationsBulk(addition_operations_bulk_payload)\n```\nThe variable `agents` is an **array of responses**. If an agent has successfully received operations, the corresponding response is an object similar to the classic `add_operations()` response. When there are **mixed results**, `agents` should looks like:\n\n```python\n[\n  {\n    \'status\': 201,                                # Addition of operation succeeded\n    \'message\': \'message\',\n    \'id\': \'my_first_agent\'\n  }\n  {\n    \'status\': 500,                                 # Addition of operation failed\n    \'error\': CraftAiBadRequestError(\'error_message\'),\n    \'id\': \'my_second_agent\'\n  }\n]\n```\n\n#### Bulk - Compute decision trees\n\nTo get the tree of several agents at once, use the method `get_decision_trees_bulk` as the following:\n\n```python\nagent_id_1 = \'my_first_agent\'\nagent_id_2 = \'my_second_agent\'\n\ndecision_tree_bulk_payload =  [\n  {\'id\': agent_id_1},\n  {\'id\': agent_id_2}\n]\n\ntrees = client.get_decision_trees_bulk(decision_tree_bulk_payload)\n```\nThe variable `trees` is an **array of responses**. If an agent’s model has successfully been created, the corresponding response is an object similar to the classic `get_decision_trees_bulk()` response. When there are **mixed results**, trees should looks like:\n\n```python\n[\n  {\'id\': \'my_first_agent\',                              # Getting of the tree succeeded\n   \'tree\': {\n     \'trees\': { ... }\n     \'_version\': \'1.1.0\',\n     \'configuration\': { ... }\n   }\n   \'timestamp\': 1458741735\n   },\n   {\n   \'error\': CraftAiBadRequestError(\'error_message\'),  # Getting of the tree failed\n   \'id\': \'my_second_agent\'\n   }\n   {\n   \'error\': CraftAiNotFoundError(\'error_message\'),    # Getting of the tree failed\n   \'id\': \'my_unknown_agent\'\n   }\n]\n\n  ```\n\n### Advanced client configuration ###\n\nThe simple configuration to create the `client` is just the token. For special needs, additional advanced configuration can be provided.\n\n#### Amount of operations sent in one chunk ####\n\n`client.add_operations` splits the provided operations into chunks in order to limit the size of the http requests to the craft ai API. In the client configuration, `operationsChunksSize` can be increased in order to limit the number of request, or decreased when large http requests cause errors.\n\n```python\nclient = craft_ai.Client({\n    # Mandatory, the token\n    "token": "{token}",\n    # Optional, default value is 200\n    "operationsChunksSize": {max_number_of_operations_sent_at_once}\n})\n```\n\n#### Timeout duration for decision trees retrieval ####\n\nIt is possible to increase or decrease the timeout duration of `client.get_decision_tree`, for exemple to account for especially long computations.\n\n```python\nclient = craft_ai.Client({\n    # Mandatory, the token\n    "token": "{token}",\n    # Optional, default value is 600000 (10 minutes)\n    "decisionTreeRetrievalTimeout": "{timeout_duration_for_decision_trees_retrieval}"\n})\n```\n\n#### Proxy ####\n\nIt is possible to provide proxy configuration in the `proxy` property of the client configuration. It will be used to call the craft ai API (through HTTPS). The expected format is a host name or IP and port, optionally preceded by credentials such as `http://user:pass@10.10.1.10:1080`.\n\n```python\nclient = craft_ai.Client({\n    # Mandatory, the token\n    "token": "{token}",\n    # Optional, no default value\n    "proxy": "http://{user}:{password}@{host_or_ip}:{port}"\n})\n```\n\n#### Advanced network configuration ####\n\nFor more advanced network configuration, it is possible to access the [Requests Session](http://docs.python-requests.org/en/master/user/advanced/#session-objects) used by the client to send requests to the craft ai API, through `client._requests_session`.\n\n```python\n# Disable SSL certificate verification\nclient._requests_session.verify = False\n```\n## Interpreter ##\n\nThe decision tree interpreter can be used offline from decisions tree computed through the API.\n\n### Take decision ###\n\nNote that the python interpreter takes an array of contexts.\n\n```python\ntree = { ... } # Decision tree as retrieved through the craft ai REST API\n\n# Compute the decision on a fully described context\ndecision = craft_ai.Interpreter.decide(\n  tree,\n  [{ # The context on which the decision is taken\n    "timezone": "+02:00",\n    "timeOfDay": 7.5,\n    "peopleCount": 3\n  }]\n)\n\n# Or Compute the decision on a context created from the given one and filling the\n# `day_of_week`, `time_of_day` and `timezone` properties from the given `Time`\n\ndecision = craft_ai.Interpreter.decide(\n  tree,\n  [{\n    "timezone": "+02:00",\n    "peopleCount": 3\n  },\n  craft_ai.Time("2010-01-01T07:30:30+0200")\n  ]\n)\n```\n\nA computed `decision` on an `enum` output type would look like:\n\n```python\n{\n  "context": { # In which context the decision was taken\n    "timezone": "+02:00",\n    "timeOfDay": 7.5,\n    "peopleCount": 3\n  },\n  "output": { # The decision itself\n    "lightbulbState": {\n      "predicted_value": "ON"\n      "confidence": 0.9937745256361138, # The confidence in the decision\n      "decision_rules": [ # The ordered list of decision_rules that were validated to reach this decision\n        {\n          "property": "timeOfDay",\n          "operator": ">=",\n          "operand": 6\n        },\n        {\n          "property": "peopleCount",\n          "operator": ">=",\n          "operand": 2\n        }\n      ],\n      "nb_samples": 25,\n      "distribution": [0.05, 0.95],\n      "decision_path" "0-1-1"\n    },\n  }\n}\n```\n\nA `decision` for a numerical output type would look like:\n\n```python\n  "output": {\n    "lightbulbIntensity": {\n      "predicted_value": 10.5,\n      "standard_deviation": 1.25, # For numerical types, this field is returned in decisions.\n      "min": 8.0,\n      "max": 11,\n      "nb_samples": 25,\n      "decision_rules": [ ... ],\n      "confidence": ...,\n      "decision_path" ...\n    }\n  }\n```\n\nA `decision` for a categorical output type would look like:\n\n```python\n  "output": {\n    "lightbulbState": {\n      "predicted_value": "OFF",\n      "nb_samples": 25,\n      "distribution" : [ ... ], # Distribution of the output classes normalized by the number of samples in the reached node.\n      "decision_rules": [ ... ],\n      "confidence": ...,\n      "decision_path" ...\n    }\n  }\n```\n\nA `decision` in a case where the tree cannot make a prediction:\n\n```python\n  "output": {\n    "lightbulbState": {\n      "predicted_value": None,\n      "distribution" : [ ... ], # Distribution of the output classes normalized by the number of samples in the reached node.\n      "confidence": 0, # Zero confidence if the decision is null\n      "decision_rules": [ ... ],\n      "decision_path" ...\n    }\n  }\n```\n\n### Reduce decision rules ###\n\nFrom a list of decision rules, as retrieved when taking a decision, when taking a decision compute an equivalent & minimal list of rules.\n\n```python\nfrom craft_ai import reduce_decision_rules\n\n# `decision` is the decision tree as retrieved from taking a decision\ndecision = craft_ai.Interpreter.decide( ... )\n\n# `decision_rules` is the decision rules that led to decision for the `lightBulbState` value\ndecision_rules = decision["output"]["lightBulbState"]["decision_rules"]\n\n# `minimal_decision_rules` has the mininum list of rules strictly equivalent to `decision_rules`\nminimal_decision_rules = reduce_decision_rules(decisionRules)\n```\n\n### Format decision rules ###\n\nFrom a list of decision rules, compute a _human readable_ version of these rules, in english.\n\n```python\nfrom craft_ai import format_decision_rules\n\n# `decision` is the decision tree as retrieved from taking a decision\ndecision = craft_ai.Interpreter.decide( ... )\n\n# `decision_rules` is the decision rules that led to decision for the `lightBulbState` value\ndecision_rules = decision["output"]["lightbulbState"]["decision_rules"]\n\n# `decision_rules_str` is a human readable string representation of the rules.\ndecision_rules_str = format_decision_rules(decision_rules)\n```\n\n## Error Handling ##\n\nWhen using this client, you should be careful wrapping calls to the API with `try/except` blocks, in accordance with the [EAFP](https://docs.python.org/3/glossary.html#term-eafp) principle.\n\nThe **craft ai** python client has its specific exception types, all of them inheriting from the `CraftAIError` type.\n\nAll methods which have to send an http request (all of them except `decide`) may raise either of these exceptions: `CraftAINotFoundError`, `CraftAIBadRequestError`, `CraftAICredentialsError` or `CraftAIUnknownError`.\n\nThe `decide`\xa0method only raises `CrafAIDecisionError` of `CraftAiNullDecisionError` type of exceptions. The latter is raised when no the given context is valid but no decision can be taken.\n\n## Pandas support ##\n\nThe craft ai python client optionally supports [pandas](http://pandas.pydata.org/) a very popular library used for all things data.\n\nBasically instead of importing the default module, you can do the following\n\n```python\nimport craft_ai.pandas\n\n# Most of the time you\'ll need the following\nimport numpy as np\nimport pandas as pd\n\n# Client must then be defined using craft_ai.pandas module\nclient = craft_ai.pandas.Client({\n  "token": "{token}"\n})\n```\n\nThe craft ai pandas module is derived for the _vanilla_ one, with the following methods are overriden to support pandas\' [`DataFrame`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html).\n\n#### `craft_ai.pandas.Client.get_operations_list` #####\n\nRetrieves the desired operations as a `DataFrame` where:\n\n- each operation is a row,\n- each context property is a column,\n- the index is [_time based_](https://pandas.pydata.org/pandas-docs/stable/timeseries.html), [timezone-aware](https://pandas.pydata.org/pandas-docs/stable/timeseries.html#working-with-time-zones) and matching the operations timestamps,\n- `np.NaN` means no value were given at this property for this timestamp.\n\n```python\ndf = client.get_operations_list("my_new_agent")\n\n# `df` is a pd.DataFrame looking like\n#\n#                            peopleCount  lightbulbState   timezone\n# 2013-01-01 00:00:00+00:00   0            OFF              +02:00\n# 2013-01-02 00:00:00+00:00   1            ON               NaN\n# 2013-01-03 00:00:00+00:00   2            NaN              NaN\n# 2013-01-04 00:00:00+00:00   NaN          OFF              NaN\n# 2013-01-05 00:00:00+00:00   0            NaN              NaN\n```\n\n#### `craft_ai.pandas.Client.add_operations` #####\n\nAdd a `DataFrame` of operations to the desired agent. The format is the same as above.\n\n```python\ndf = pd.DataFrame(\n  [\n    [0, "OFF", "+02:00"],\n    [1, "ON", np.nan], # timezone will be "+02:00"\n    [2, np.nan, np.nan],\n    [np.nan, "OFF", np.nan],\n    [0, np.nan, np.nan]\n  ],\n  columns=[\'peopleCount\', \'lightbulbState\', \'timezone\'],\n  index=pd.date_range(\'20130101\', periods=5, freq=\'D\').tz_localize("UTC")\n)\nclient.add_operations("my_new_agent", df)\n```\n\nGiven an object that is not a `DataFrame` this method behave like the _vanilla_ `craft_ai.Client.add_operations`.\n\nFurthermore, missing values and optional values can be handled by the craft ai pandas client. To do so, we introduce two new types that are `craft_ai.pandas.MISSING_VALUE` for [missing values](#missing-values) and `craft_ai.pandas.OPTIONAL_VALUE` for [optional values](#optional-values).\nTo send your `DataFrame` with actual missing values or optional values, you must use one of these types:\n\n```python\nfrom craft_ai.pandas import MISSING_VALUE, OPTIONAL_VALUE\n\ndf = pd.DataFrame(\n  [\n    [0, "+02:00"],\n    [1, MISSING_VALUE],\n    [2, MISSING_VALUE],\n    [1, OPTIONAL_VALUE],\n    [0, np.nan]\n  ],\n  columns=[\'peopleCount\', \'timezone\'],\n  index=pd.date_range(\'20130101\', periods=5, freq=\'D\').tz_localize("UTC")\n)\nclient.add_operations("my_new_agent", df)\n```\n\nTo ensure that all the missing values contained in your `DataFrame` are to the right format and can be handled by the craft ai pandas client, it is suggested to preprocess this latter by replacing all `na` values by the desired one:\n\n```python\ncontexts_df.fillna(MISSING_VALUE) # Or OPTIONAL_VALUE\n```\n\n#### `craft_ai.pandas.Client.get_state_history` #####\n\nRetrieves the desired state history as a `DataFrame` where:\n\n- each state is a row,\n- each context property is a column,\n- the index is [_time based_](https://pandas.pydata.org/pandas-docs/stable/timeseries.html), [timezone-aware](https://pandas.pydata.org/pandas-docs/stable/timeseries.html#working-with-time-zones) and matching the operations timestamps.\n\n```python\ndf = client.get_state_history("my_new_agent")\n\n# `df` is a pd.DataFrame looking like\n#\n#                            peopleCount  lightbulbState   timezone\n# 2013-01-01 00:00:00+00:00   0            OFF              +02:00\n# 2013-01-02 00:00:00+00:00   1            ON               +02:00\n# 2013-01-03 00:00:00+00:00   2            ON               +02:00\n# 2013-01-04 00:00:00+00:00   2            OFF              +02:00\n# 2013-01-05 00:00:00+00:00   0            OFF              +02:00\n```\n\n#### `craft_ai.pandas.Client.decide_from_contexts_df` #####\n\nTake multiple decisions on a given `DataFrame` following the same format as above.\n\n```python\ndecisions_df = client.decide_from_contexts_df(tree, pd.DataFrame(\n  [\n    [0, "+02:00"],\n    [1, "+02:00"],\n    [2, "+02:00"],\n    [1, "+02:00"],\n    [0, "+02:00"]\n  ],\n  columns=[\'peopleCount\', \'timezone\'],\n  index=pd.date_range(\'20130101\', periods=5, freq=\'D\').tz_localize("UTC")\n))\n# `decisions_df` is a pd.DataFrame looking like\n#\n#                            lightbulbState_predicted_value   lightbulbState_confidence  ...\n# 2013-01-01 00:00:00+00:00   OFF                              0.999449                  ...\n# 2013-01-02 00:00:00+00:00   ON                               0.970325                  ...\n# 2013-01-03 00:00:00+00:00   ON                               0.970325                  ...\n# 2013-01-04 00:00:00+00:00   ON                               0.970325                  ...\n# 2013-01-05 00:00:00+00:00   OFF                              0.999449                  ...\n```\n\nThis function also accepts craft ai missing values and optional values types, `craft_ai.pandas.MISSING_VALUE` and `craft_ai.pandas.OPTIONAL_VALUE`.\n\n```python\nfrom craft_ai.pandas import MISSING_VALUE, OPTIONAL_VALUE\n\ndecisions_df = client.decide_from_contexts_df(tree, pd.DataFrame(\n  [\n    [0, "+02:00"],\n    [MISSING_VALUE, "+02:00"],\n    [2, "+02:00"],\n    [MISSING_VALUE, "+02:00"],\n    [0, "+02:00"]\n  ],\n  columns=[\'peopleCount\', \'timezone\'],\n  index=pd.date_range(\'20130101\', periods=5, freq=\'D\').tz_localize("UTC")\n))\n```\n\nThis function never raises `CraftAiNullDecisionError`, instead it inserts these errors in the result `Dataframe` in a specific `error` column.\n\n#### `craft_ai.pandas.utils.create_tree_html` #####\n\nReturns a HTML version of the given decision tree. If this latter is saved in a `.html` file, it can be opened in\na browser to be visualized.\n\n```python\n\nfrom  craft_ai.pandas.utils import create_tree_html\n\ntree = client.get_decision_tree(\n  "my_agent", # The agent id\n  timestamp # The timestamp at which the decision tree is retrieved\n)\n\nhtml = create_tree_html(\n  tree, # The decision tree\n  decision_path, # (Optional) The path to a node. This will plot the tree with this node already selected. Default: ""\n  edge_type, # (Optional) The way the decision tree edges are plotted - ("constant", "absolute" or "relative"). Default: "constant"\n  folded_nodes, # (Optional) An array of nodes path to fold when the tree is plotted. Default: None\n  height # (Optional) The height in pixel of the created plot. Default: 500.\n)\n\n# ...\n# ... save the html string to visualize it in a browser\n# ...\n```\n\n#### `craft_ai.pandas.utils.display_tree` #####\n\nDisplay a decision tree in a Jupyter Notebook.\nThis function can be useful for analyzing the induced decision trees.\n\n```python\n\nfrom  craft_ai.pandas.utils import display_tree\n\ntree = client.get_decision_tree(\n  "my_agent", # The agent id\n  timestamp # The timestamp at which the decision tree is retrieved\n)\n\ndisplay_tree(\n  tree, # The decision tree\n  decision_path, # (Optional) The path to a node. This will plot the tree with this node already selected. Default: ""\n  edge_type, # (Optional) The way the decision tree edges are plotted - ("constant", "absolute" or "relative"). Default: "constant"\n  folded_nodes, # (Optional) An array of nodes path to fold when the tree is plotted. Default: None\n  height # (Optional) The height in pixel of the created plot. Default: 500.\n)\n```\n\n#### `craft_ai.pandas.client.add_operations_bulk` #####\n\nAdd operations to several agents at once.\n```python\nagent_id_1 = \'my_first_agent\'\nagent_id_2 = \'my_second_agent\'\n\noperations_agent_1 = pd.DataFrame(\n  [\n    [0, "OFF", "+02:00"],\n    [1, "ON", np.nan], # timezone will be "+02:00"\n    [2, np.nan, np.nan],\n    [np.nan, "OFF", np.nan],\n    [0, np.nan, np.nan]\n  ],\n  columns=[\'peopleCount\', \'lightbulbState\', \'timezone\'],\n  index=pd.date_range(\'20130101\', periods=5, freq=\'D\').tz_localize("UTC")\n)\noperations_agent_2 = pd.DataFrame([...])\n\naddition_operations_bulk_payload = [\n    {\'id\': agent_id_1, \'operations\': operations_agent_1},\n    {\'id\': agent_id_2, \'operations\': operations_agent_2}\n]\n\nclient.add_operations_bulk(addition_operations_bulk_payload)\n```\n<<<<<<< HEAD\nGiven an object that is not a `DataFrame` this method behave like the _vanilla_ `craftai.Client.add_operations_bulk`.\n=======\nGiven an object that is not a `DataFrame` this method behave like the _vanilla_ `craft_ai.Client.add_operations_bulk`.\n>>>>>>> Migrating to poetry + Renaming package craftai to craft_ai\n',
    'author': 'craft ai',
    'author_email': 'contact@craft.ai',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://craft.ai',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'extras_require': extras_require,
    'python_requires': '>=3.6.1,<4.0.0',
}


setup(**setup_kwargs)
