{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Sending Each Complaint to the Right Desk\n",
    "\n",
    "complaint_routing.py\n",
    "\n",
    "AI project on Natural Language Processing: reading a citizen's complaint\n",
    "and sending it to the right department - Water, Electricity, Roads or\n",
    "Sanitation.\n",
    "\n",
    "CBSE Class X, Artificial Intelligence (417), Part C project work.\n",
    "SDG 11 (Sustainable Cities and Communities) and SDG 6 (Clean Water and\n",
    "Sanitation).\n",
    "\n",
    "This is text classification with FOUR classes. Two models are compared:\n",
    "  - KEYWORD RULES (rule based): a person writes a list of words for each\n",
    "    department; the department with the most matching words wins.\n",
    "  - WORD PROFILES (learning based): from the training complaints, the\n",
    "    program learns how often each word is used in each department's\n",
    "    complaints, and sends a new complaint to the department whose words\n",
    "    it uses most.\n",
    "\n",
    "Libraries: pandas (the complaints), numpy, matplotlib."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 1 - Problem scoping"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=\" * 60)\n",
    "print(\"SENDING EACH COMPLAINT TO THE RIGHT DESK\")\n",
    "print(\"=\" * 60)\n",
    "print(\"Who   : the city helpline, and citizens who complain\")\n",
    "print(\"What  : complaints are sorted by hand, slowly, and often wrongly\")\n",
    "print(\"Where : a city's online complaint portal\")\n",
    "print(\"Why   : a complaint at the right desk gets fixed sooner\")\n",
    "print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 2 - Data acquisition"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "pd.set_option(\"display.width\", 140)\n",
    "pd.set_option(\"display.max_colwidth\", 90)\n",
    "\n",
    "data = pd.read_csv(\"complaints.csv\")\n",
    "departments = [\"Water\", \"Electricity\", \"Roads\", \"Sanitation\"]\n",
    "\n",
    "print(\"--- Stage 2: Data acquisition ---\")\n",
    "print(\"Complaints read :\", len(data))\n",
    "print()\n",
    "print(data.head(6).to_string(index=False))\n",
    "print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 3 - Data exploration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"--- Stage 3: Data exploration ---\")\n",
    "blank = (data[\"complaint\"].str.strip() == \"\").sum()\n",
    "data = data[data[\"complaint\"].str.strip() != \"\"]\n",
    "print(\"Blank complaints   :\", blank, \"(removed)\")\n",
    "print(\"Complaints left    :\", len(data))\n",
    "print()\n",
    "counts = data[\"department\"].value_counts().reindex(departments)\n",
    "print(\"Complaints for each department:\")\n",
    "print(counts.to_string())\n",
    "print()\n",
    "\n",
    "stop_words = [\"the\", \"a\", \"an\", \"is\", \"are\", \"was\", \"for\", \"of\", \"in\", \"on\", \"at\", \"to\",\n",
    "              \"and\", \"our\", \"since\", \"very\", \"has\", \"been\", \"does\", \"not\", \"no\", \"please\",\n",
    "              \"help\", \"kindly\", \"do\", \"something\", \"nobody\", \"listening\", \"before\", \"also\",\n",
    "              \"near\", \"behind\", \"opposite\", \"sector\", \"ward\", \"block\", \"days\", \"day\", \"week\",\n",
    "              \"two\", \"three\", \"morning\", \"monday\", \"hai\", \"ho\", \"rahi\", \"raha\", \"nahi\", \"ki\",\n",
    "              \"aa\", \"complained\", \"every\", \"being\", \"it\", \"that\", \"too\",\n",
    "              \"colony\", \"market\", \"temple\", \"school\", \"hospital\", \"station\", \"bus\", \"stand\"]\n",
    "\n",
    "tokens_list = []\n",
    "for text in data[\"complaint\"]:\n",
    "    clean = \"\"\n",
    "    for ch in text.lower():\n",
    "        if ch.isalpha():\n",
    "            clean = clean + ch\n",
    "        else:\n",
    "            clean = clean + \" \"\n",
    "    tokens = []\n",
    "    for w in clean.split():\n",
    "        if w not in stop_words and len(w) > 1:\n",
    "            tokens.append(w)\n",
    "    tokens_list.append(tokens)\n",
    "data[\"tokens\"] = tokens_list\n",
    "\n",
    "print(\"Text normalisation of one complaint:\")\n",
    "print(\"  Original :\", data.iloc[0][\"complaint\"])\n",
    "print(\"  Tokens   :\", data.iloc[0][\"tokens\"])\n",
    "print()\n",
    "\n",
    "# Chart 1: the most common words in each department's complaints.\n",
    "plt.figure(figsize=(10, 6))\n",
    "colours = [\"#4e9fd1\", \"#f2b134\", \"#8c8c8c\", \"#59a14f\"]\n",
    "for k in range(4):\n",
    "    word_count = {}\n",
    "    for tokens in data[data[\"department\"] == departments[k]][\"tokens\"]:\n",
    "        for w in tokens:\n",
    "            word_count[w] = word_count.get(w, 0) + 1\n",
    "    top = pd.Series(word_count).sort_values(ascending=False).head(8)\n",
    "    plt.subplot(2, 2, k + 1)\n",
    "    plt.barh(top.index[::-1], top.values[::-1], color=colours[k])\n",
    "    plt.title(departments[k])\n",
    "plt.suptitle(\"Most common words in each department's complaints\")\n",
    "plt.tight_layout()\n",
    "plt.savefig(\"chart1_department_words.png\")\n",
    "plt.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 4 - Modelling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"--- Stage 4: Modelling ---\")\n",
    "data = data.sample(frac=1, random_state=21)\n",
    "cut = int(len(data) * 0.8)\n",
    "train = data[:cut]\n",
    "test = data[cut:]\n",
    "print(\"Training complaints :\", len(train))\n",
    "print(\"Testing complaints  :\", len(test))\n",
    "print()\n",
    "\n",
    "# Model A - KEYWORD RULES, written by a person.\n",
    "keywords = {\n",
    "    \"Water\":       [\"water\", \"tap\", \"taps\", \"pipe\", \"tanker\", \"supply\"],\n",
    "    \"Electricity\": [\"power\", \"light\", \"lights\", \"electric\", \"electricity\", \"transformer\", \"voltage\", \"wire\"],\n",
    "    \"Roads\":       [\"road\", \"pothole\", \"footpath\", \"traffic\", \"divider\"],\n",
    "    \"Sanitation\":  [\"garbage\", \"drain\", \"dustbin\", \"toilet\", \"sewer\", \"smell\"],\n",
    "}\n",
    "\n",
    "# Model B - WORD PROFILES, learned. For each department: what share of all\n",
    "# the words in its training complaints is each word?\n",
    "profile = {}\n",
    "for d in departments:\n",
    "    word_count = {}\n",
    "    total = 0\n",
    "    for tokens in train[train[\"department\"] == d][\"tokens\"]:\n",
    "        for w in tokens:\n",
    "            word_count[w] = word_count.get(w, 0) + 1\n",
    "            total = total + 1\n",
    "    shares = {}\n",
    "    for w in word_count:\n",
    "        shares[w] = word_count[w] / total\n",
    "    profile[d] = shares\n",
    "\n",
    "for w in [\"water\", \"paani\", \"bijli\", \"kachra\", \"road\"]:\n",
    "    line = \"Learned share of '\" + w + \"'\"\n",
    "    parts = []\n",
    "    for d in departments:\n",
    "        parts.append(d + \" \" + str(round(profile[d].get(w, 0) * 100, 1)) + \"%\")\n",
    "    print(line.ljust(26), \":\", \", \".join(parts))\n",
    "print()\n",
    "\n",
    "rule_said = []\n",
    "learned_said = []\n",
    "for tokens in test[\"tokens\"]:\n",
    "    best_rule = \"Unknown\"\n",
    "    best_hits = 0\n",
    "    best_learned = None\n",
    "    best_score = -1\n",
    "    for d in departments:\n",
    "        hits = 0\n",
    "        score = 0\n",
    "        for w in tokens:\n",
    "            if w in keywords[d]:\n",
    "                hits = hits + 1\n",
    "            score = score + profile[d].get(w, 0)\n",
    "        if hits > best_hits:\n",
    "            best_hits = hits\n",
    "            best_rule = d\n",
    "        if score > best_score:\n",
    "            best_score = score\n",
    "            best_learned = d\n",
    "    rule_said.append(best_rule)\n",
    "    learned_said.append(best_learned)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 5 - Evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"--- Stage 5: Evaluation ---\")\n",
    "actual = list(test[\"department\"])\n",
    "rule_right = 0\n",
    "learned_right = 0\n",
    "unknown = 0\n",
    "for i in range(len(actual)):\n",
    "    if rule_said[i] == actual[i]:\n",
    "        rule_right = rule_right + 1\n",
    "    if learned_said[i] == actual[i]:\n",
    "        learned_right = learned_right + 1\n",
    "    if rule_said[i] == \"Unknown\":\n",
    "        unknown = unknown + 1\n",
    "rule_acc = rule_right / len(actual) * 100\n",
    "learned_acc = learned_right / len(actual) * 100\n",
    "print(\"Keyword rules  :\", rule_right, \"of\", len(actual), \"correct\")\n",
    "print(\"Word profiles  :\", learned_right, \"of\", len(actual), \"correct\")\n",
    "print(\"Accuracy of the keyword rules :\", round(rule_acc, 1), \"%\")\n",
    "print(\"Accuracy of the word profiles :\", round(learned_acc, 1), \"%\")\n",
    "print(\"Complaints the keyword rules could not place :\", unknown)\n",
    "print()\n",
    "\n",
    "matrix = pd.crosstab(pd.Series(actual, name=\"Actual\"), pd.Series(learned_said, name=\"Sent to\"))\n",
    "matrix = matrix.reindex(index=departments, columns=departments, fill_value=0)\n",
    "print(\"Confusion matrix of the word profiles:\")\n",
    "print(matrix)\n",
    "print()\n",
    "\n",
    "print(\"Where the keyword rules went wrong (first 5):\")\n",
    "shown = 0\n",
    "for i in range(len(actual)):\n",
    "    if rule_said[i] != actual[i] and shown < 5:\n",
    "        print(\"  [\" + actual[i] + \", rules said \" + rule_said[i] + \"]\", test.iloc[i][\"complaint\"])\n",
    "        shown = shown + 1\n",
    "print()\n",
    "\n",
    "# Chart 2: confusion matrix.\n",
    "plt.figure(figsize=(6, 5))\n",
    "plt.imshow(matrix.values, cmap=\"Blues\")\n",
    "for r in range(4):\n",
    "    for c in range(4):\n",
    "        value = matrix.values[r][c]\n",
    "        if value > matrix.values.max() / 2:\n",
    "            ink = \"white\"\n",
    "        else:\n",
    "            ink = \"black\"\n",
    "        plt.text(c, r, value, ha=\"center\", va=\"center\", fontsize=14, color=ink)\n",
    "plt.xticks(range(4), departments, rotation=15)\n",
    "plt.yticks(range(4), departments)\n",
    "plt.xlabel(\"Sent to\")\n",
    "plt.ylabel(\"Right department\")\n",
    "plt.title(\"Word profiles (testing complaints)\")\n",
    "plt.tight_layout()\n",
    "plt.savefig(\"chart2_matrix.png\")\n",
    "plt.close()\n",
    "\n",
    "# Chart 3: share of each department's complaints sent to the right desk.\n",
    "rule_recall = []\n",
    "learned_recall = []\n",
    "for d in departments:\n",
    "    total = 0\n",
    "    r_ok = 0\n",
    "    l_ok = 0\n",
    "    for i in range(len(actual)):\n",
    "        if actual[i] == d:\n",
    "            total = total + 1\n",
    "            if rule_said[i] == d:\n",
    "                r_ok = r_ok + 1\n",
    "            if learned_said[i] == d:\n",
    "                l_ok = l_ok + 1\n",
    "    rule_recall.append(r_ok / total * 100)\n",
    "    learned_recall.append(l_ok / total * 100)\n",
    "x = np.arange(4)\n",
    "plt.figure(figsize=(8, 4.5))\n",
    "plt.bar(x - 0.2, rule_recall, width=0.4, label=\"Keyword rules\", color=\"#9c9c9c\")\n",
    "plt.bar(x + 0.2, learned_recall, width=0.4, label=\"Word profiles\", color=\"#4e79a7\")\n",
    "plt.xticks(x, departments)\n",
    "plt.ylim(0, 105)\n",
    "plt.ylabel(\"Complaints sent to the right desk (%)\")\n",
    "plt.title(\"Each department: how many complaints reached it\")\n",
    "plt.legend()\n",
    "plt.tight_layout()\n",
    "plt.savefig(\"chart3_by_department.png\")\n",
    "plt.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stage 6 - Deployment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"--- Stage 6: Deployment ---\")\n",
    "new_complaints = [\n",
    "    \"Bijli nahi hai in Sector 12 since yesterday night\",\n",
    "    \"Huge pothole filled with rain water on MG Road\",\n",
    "    \"Kachra pada hai near the bus stand, nobody is cleaning\",\n",
    "    \"Drinking water tanker has not come to Ward 7\",\n",
    "]\n",
    "for text in new_complaints:\n",
    "    clean = \"\"\n",
    "    for ch in text.lower():\n",
    "        if ch.isalpha():\n",
    "            clean = clean + ch\n",
    "        else:\n",
    "            clean = clean + \" \"\n",
    "    best = None\n",
    "    best_score = -1\n",
    "    for d in departments:\n",
    "        score = 0\n",
    "        for w in clean.split():\n",
    "            if w not in stop_words:\n",
    "                score = score + profile[d].get(w, 0)\n",
    "        if score > best_score:\n",
    "            best_score = score\n",
    "            best = d\n",
    "    print(text.ljust(56), \"->\", best, \"department\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
