{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Purpose\n",
    "\n",
    "Weighter is designed to be a hacky weight tracking app using Slack as a frontend and Google Sheets as a database! \n",
    "Weights are entered through a Slack Channel, stored in a Google Sheet, and reported back to users through Slack. Users will have the option to view various stats and graphs by sending different slack messages. \n",
    "\n",
    "Weighter also features additive modeling forecasts using the Facebook Prophet library. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup Libraries and Access to the Google Sheet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 125,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# pandas and numpy for data manipulation\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "# fbprophet for additive models\n",
    "import fbprophet\n",
    "\n",
    "# gspread for Google Sheets access\n",
    "import gspread\n",
    "\n",
    "# slacker for interacting with Slack\n",
    "from slacker import Slacker\n",
    "\n",
    "# oauth2client for authorizing access to Google Sheets\n",
    "from oauth2client.service_account import ServiceAccountCredentials"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 126,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# matplotlib for plotting in the notebook\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "import matplotlib"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Google Sheet Access\n",
    "\n",
    "The json file is the credentials for accessing the google sheet generated from the Google Developers API. To access a specific sheet, you need to share the sheet with the email address in the json file. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 127,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:oauth2client.client:Refreshing access_token\n"
     ]
    }
   ],
   "source": [
    "# google sheets access\n",
    "scope = ['https://spreadsheets.google.com/feeds']\n",
    "\n",
    "# Use local stored credentials in json file\n",
    "# make sure to first share the sheet with the email in the json file\n",
    "credentials = ServiceAccountCredentials.from_json_keyfile_name('weighter-2038ffb4e5a6.json', scope)\n",
    "\n",
    "# Authorize access\n",
    "gc = gspread.authorize(credentials);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Open the sheet and convert to a pandas dataframe"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 129,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Open the sheet, need to share the sheet with email specified in json file\n",
    "gsheet = gc.open('Auto Weight Challenge').sheet1\n",
    "\n",
    "# List of lists with each row in the sheet as a list\n",
    "weight_lists = gsheet.get_all_values()\n",
    "\n",
    "# Headers are the first list\n",
    "# Pop returns the element (list in this case) and removes it from the list\n",
    "headers = weight_lists.pop(0)\n",
    "\n",
    "# Convert list of lists to a dataframe with specified column header\n",
    "weights = pd.DataFrame(weight_lists, columns=headers)\n",
    "\n",
    "# Record column should be a boolean\n",
    "weights['Record'] = weights['Record'].astype(bool)\n",
    "\n",
    "# Name column is a string\n",
    "weights['Name'] = weights['Name'].astype(str)\n",
    "\n",
    "# Convert dates to datetime, then set as index, then set the time zone\n",
    "weights['Date'] = pd.to_datetime(weights['Date'], unit='s')\n",
    "weights  = weights.set_index('Date', drop = True).tz_localize(tz='US/Eastern')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 130,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Name</th>\n",
       "      <th>Entry</th>\n",
       "      <th>Record</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Date</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2017-08-18 00:00:00-04:00</th>\n",
       "      <td>koehrcl</td>\n",
       "      <td>235.2</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2017-08-19 00:00:00-04:00</th>\n",
       "      <td>koehrcl</td>\n",
       "      <td>235.6</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2017-08-20 00:00:00-04:00</th>\n",
       "      <td>koehrcl</td>\n",
       "      <td>233</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2017-08-21 00:00:00-04:00</th>\n",
       "      <td>koehrcl</td>\n",
       "      <td>232.6</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2017-08-22 00:00:00-04:00</th>\n",
       "      <td>koehrcl</td>\n",
       "      <td>234.4</td>\n",
       "      <td>True</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                              Name  Entry  Record\n",
       "Date                                             \n",
       "2017-08-18 00:00:00-04:00  koehrcl  235.2    True\n",
       "2017-08-19 00:00:00-04:00  koehrcl  235.6    True\n",
       "2017-08-20 00:00:00-04:00  koehrcl    233    True\n",
       "2017-08-21 00:00:00-04:00  koehrcl  232.6    True\n",
       "2017-08-22 00:00:00-04:00  koehrcl  234.4    True"
      ]
     },
     "execution_count": 130,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "weights.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 131,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Name</th>\n",
       "      <th>Entry</th>\n",
       "      <th>Record</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Date</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2018-01-19 14:34:56-05:00</th>\n",
       "      <td>koehrcl</td>\n",
       "      <td>221.1</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2018-01-19 15:30:25-05:00</th>\n",
       "      <td>willkoehrsen</td>\n",
       "      <td>137.3</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2018-01-19 18:11:49-05:00</th>\n",
       "      <td>fletcher</td>\n",
       "      <td>188.4</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2018-01-20 15:39:12-05:00</th>\n",
       "      <td>willkoehrsen</td>\n",
       "      <td>137</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2018-01-20 15:49:52-05:00</th>\n",
       "      <td>koehrcl</td>\n",
       "      <td>220.4</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                   Name  Entry  Record\n",
       "Date                                                  \n",
       "2018-01-19 14:34:56-05:00       koehrcl  221.1   False\n",
       "2018-01-19 15:30:25-05:00  willkoehrsen  137.3   False\n",
       "2018-01-19 18:11:49-05:00      fletcher  188.4   False\n",
       "2018-01-20 15:39:12-05:00  willkoehrsen    137   False\n",
       "2018-01-20 15:49:52-05:00       koehrcl  220.4   False"
      ]
     },
     "execution_count": 131,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "weights.tail()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "+ Date is the index (in Eastern time here)\n",
    "+ Name is the slack username\n",
    "+ Entry is either weight or a string to display results\n",
    "+ Record is whether or not the entry has been processed by weighter"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Weighter Class\n",
    "\n",
    "The class will include a number of different methods for analyzing the data and graphing results. These results can then be sent back to Slack depending on the message entered by the user."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 136,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class Weighter():\n",
    "    \n",
    "    \"\"\"\n",
    "    When weighter is initialized, we need to convert the usernames,\n",
    "    get a dictionary of the unrecorded entries, construct a dictionary\n",
    "    of the actions to take, and make sure all data is formatted correctly\n",
    "    \"\"\"\n",
    "    \n",
    "    def __init__(self, weights):\n",
    "        \n",
    "        # Weights is a dataframe\n",
    "        self.weights = weights.copy()\n",
    "        \n",
    "        # Users is a list of the unique users in the data\n",
    "        self.users = list(set(self.weights['Name']))\n",
    "        \n",
    "        correct_names = []\n",
    "        # Name changes\n",
    "        for user in self.weights['Name']:\n",
    "            \n",
    "            # Have to hardcode in name changes\n",
    "            if user == 'koehrcl':\n",
    "                correct_names.append('Craig')\n",
    "            elif user == 'willkoehrsen':\n",
    "                correct_names.append('Will')\n",
    "            elif user == 'fletcher':\n",
    "                correct_names.append('Fletcher')\n",
    "            \n",
    "            # Currently do not handle new users\n",
    "            else:\n",
    "                print('New User Detected')\n",
    "                return\n",
    "            \n",
    "        self.weights['Name'] = correct_names\n",
    "        \n",
    "        # Users is a list of the unique users in the data\n",
    "        self.users = list(set(self.weights['Name']))\n",
    "        \n",
    "        # Create a dataframe of the unrecorded entries\n",
    "        self.unrecorded = self.weights[self.weights['Record'] != True]\n",
    "        \n",
    "        # Process the unrecorded entries\n",
    "        self.process_unrecorded()\n",
    "        \n",
    "        # The remaning entries will all be weights\n",
    "        self.weights['Entry'] = [float(weight) for weight in self.weights['Entry']]\n",
    "        \n",
    "        # Build the user dictionary\n",
    "        self.build_user_dict()\n",
    "        \n",
    "        \n",
    "    \"\"\" \n",
    "    Constructs a dictionary for each user with critical information\n",
    "    This forms the basis for the summarize function\n",
    "    \"\"\"\n",
    "    \n",
    "    def build_user_dict(self):\n",
    "        \n",
    "        user_dict = {}\n",
    "        \n",
    "        user_goals = {'Craig': 215.0, 'Fletcher': 200.0, 'Will': 155.0}\n",
    "        \n",
    "        for i, user in enumerate(self.users):\n",
    "            \n",
    "            user_weights = self.weights[self.weights['Name'] == user]\n",
    "            goal = user_goals.get(user)\n",
    "\n",
    "            start_weight = user_weights.ix[min(user_weights.index), 'Entry']           \n",
    "            \n",
    "            # Find minimum weight and date on which it occurs\n",
    "            min_weight =  min(user_weights['Entry'])\n",
    "            min_weight_date = ((user_weights[user_weights['Entry'] == min_weight].index)[0])\n",
    "            \n",
    "            # Find maximum weight and date on which it occurs\n",
    "            max_weight = max(user_weights['Entry'])\n",
    "            max_weight_date = ((user_weights[user_weights['Entry'] == max_weight].index)[0])\n",
    "            \n",
    "            most_recent_weight = user_weights.ix[max(user_weights.index), 'Entry']\n",
    "            \n",
    "            if goal < start_weight:\n",
    "                change = start_weight - most_recent_weight\n",
    "                obj = 'lose'\n",
    "            elif goal > start_weight:\n",
    "                change = most_recent_weight - start_weight\n",
    "                obj = 'gain'\n",
    "                \n",
    "            pct_change = 100 *change / start_weight\n",
    "            \n",
    "            pct_to_goal = 100 * (change / abs(start_weight - goal) )\n",
    "            \n",
    "            user_dict[user] = {'min_weight': min_weight, 'max_weight': max_weight,\n",
    "                               'min_date': min_weight_date, 'max_date': max_weight_date,\n",
    "                               'recent': most_recent_weight, 'abs_change': change,\n",
    "                               'pct_change': pct_change, 'pct_towards_goal': pct_to_goal,\n",
    "                               'objective': obj}\n",
    "       \n",
    "        self.user_dict = user_dict\n",
    "                                          \n",
    "    def process_unrecorded(self):\n",
    "        \n",
    "        entries = {name:[] for name in self.users}\n",
    "        drop = []\n",
    "        \n",
    "        for index in self.unrecorded.index:\n",
    "\n",
    "            entry = self.unrecorded.ix[index, 'Entry']\n",
    "            user = str(self.unrecorded.ix[index, 'Name'])\n",
    "            \n",
    "            try:\n",
    "                entry = float(entry)\n",
    "                entries[user].append(entry)\n",
    "                \n",
    "            except:  \n",
    "                entry = str(entry)\n",
    "                entries[user].append(entry)\n",
    "                \n",
    "                drop.append(index)\n",
    "\n",
    "                \n",
    "#                 if entry == 'Summary':\n",
    "#                     self.summarize()\n",
    "#                 elif entry == 'Future':\n",
    "#                     self.future()\n",
    "#                 elif entry == 'Trends':\n",
    "#                     self.trends()\n",
    "#                 elif entry == 'Percent':\n",
    "#                     self.percent()\n",
    "#                 elif entry == 'Change':\n",
    "#                     self.changepoints()\n",
    "\n",
    "        # Drop the rows which do not contain a weight\n",
    "        self.weights.drop(drop, axis=0, inplace=True)\n",
    "        \n",
    "        # Entries is all of the new entries\n",
    "        self.entries = entries\n",
    "        \n",
    "    # This will be automatically called for each new entry\n",
    "    def basic_message(self):\n",
    "        \n",
    "        for user in self.users:\n",
    "            if user in self.entries.keys():\n",
    "                user_entries = self.entries.get(user)\n",
    "                user_info = self.user_dict.get(user)\n",
    "                \n",
    "                for entry in user_entries:\n",
    "                    \n",
    "                    if type(entry) == float:\n",
    "                        \n",
    "                        if user_info['objective'] == 'lose':\n",
    "                            weight_change = user_info['max_weight'] - entry\n",
    "                            pct_change = 100 * weight_change / user_info['max_weight']\n",
    "                        \n",
    "                        elif user_info['objective'] == 'gain':\n",
    "                            weight_change = entry - user_info['min_weight']\n",
    "                            pct_change = 100 * weight_change / user_info['min_weight']\n",
    "                         \n",
    "                        print('\\nUser: {}'.format(user))\n",
    "                        print('Total Weight Change = {:.2f} lbs.'.format(weight_change))\n",
    "                        print('Percentage Weight Change = %{:.2f}.'.format(pct_change))\n",
    "       \n",
    "    # Display comprehensive stats about the users progress\n",
    "    def summary(self, user):\n",
    "        user_info = self.user_dict.get(user)\n",
    "        print(user_info)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 133,
   "metadata": {
    "collapsed": false,
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "weighter = Weighter(weights)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 134,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "User: Will\n",
      "Total Weight Change = 14.20 lbs.\n",
      "Percentage Weight Change = %11.49.\n",
      "\n",
      "User: Will\n",
      "Total Weight Change = 13.70 lbs.\n",
      "Percentage Weight Change = %11.08.\n",
      "\n",
      "User: Will\n",
      "Total Weight Change = 13.40 lbs.\n",
      "Percentage Weight Change = %10.84.\n",
      "\n",
      "User: Fletcher\n",
      "Total Weight Change = 4.00 lbs.\n",
      "Percentage Weight Change = %2.17.\n",
      "\n",
      "User: Fletcher\n",
      "Total Weight Change = 3.80 lbs.\n",
      "Percentage Weight Change = %2.06.\n",
      "\n",
      "User: Craig\n",
      "Total Weight Change = 15.40 lbs.\n",
      "Percentage Weight Change = %6.54.\n",
      "\n",
      "User: Craig\n",
      "Total Weight Change = 14.50 lbs.\n",
      "Percentage Weight Change = %6.15.\n",
      "\n",
      "User: Craig\n",
      "Total Weight Change = 15.20 lbs.\n",
      "Percentage Weight Change = %6.45.\n"
     ]
    }
   ],
   "source": [
    "weighter.basic_message()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 135,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'min_weight': 219.6, 'max_weight': 235.6, 'min_date': Timestamp('2017-12-27 15:14:17-0500', tz='US/Eastern'), 'max_date': Timestamp('2017-08-19 00:00:00-0400', tz='US/Eastern'), 'recent': 220.40000000000001, 'abs_change': 14.799999999999983, 'pct_change': 6.2925170068027141, 'pct_towards_goal': 73.267326732673226, 'objective': 'lose'}\n"
     ]
    }
   ],
   "source": [
    "weighter.summary('Craig')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
