# ---------------------------------------------------------------------------
# complaint_routing.py
#
# AI project on Natural Language Processing: reading a citizen's complaint
# and sending it to the right department - Water, Electricity, Roads or
# Sanitation.
#
# CBSE Class X, Artificial Intelligence (417), Part C project work.
# SDG 11 (Sustainable Cities and Communities) and SDG 6 (Clean Water and
# Sanitation).
#
# This is text classification with FOUR classes. Two models are compared:
#   - KEYWORD RULES (rule based): a person writes a list of words for each
#     department; the department with the most matching words wins.
#   - WORD PROFILES (learning based): from the training complaints, the
#     program learns how often each word is used in each department's
#     complaints, and sends a new complaint to the department whose words
#     it uses most.
#
# Libraries: pandas (the complaints), numpy, matplotlib.
# ---------------------------------------------------------------------------

# %% Stage 1 - Problem scoping
print("=" * 60)
print("SENDING EACH COMPLAINT TO THE RIGHT DESK")
print("=" * 60)
print("Who   : the city helpline, and citizens who complain")
print("What  : complaints are sorted by hand, slowly, and often wrongly")
print("Where : a city's online complaint portal")
print("Why   : a complaint at the right desk gets fixed sooner")
print()

# %% Stage 2 - Data acquisition
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

pd.set_option("display.width", 140)
pd.set_option("display.max_colwidth", 90)

data = pd.read_csv("complaints.csv")
departments = ["Water", "Electricity", "Roads", "Sanitation"]

print("--- Stage 2: Data acquisition ---")
print("Complaints read :", len(data))
print()
print(data.head(6).to_string(index=False))
print()

# %% Stage 3 - Data exploration
print("--- Stage 3: Data exploration ---")
blank = (data["complaint"].str.strip() == "").sum()
data = data[data["complaint"].str.strip() != ""]
print("Blank complaints   :", blank, "(removed)")
print("Complaints left    :", len(data))
print()
counts = data["department"].value_counts().reindex(departments)
print("Complaints for each department:")
print(counts.to_string())
print()

stop_words = ["the", "a", "an", "is", "are", "was", "for", "of", "in", "on", "at", "to",
              "and", "our", "since", "very", "has", "been", "does", "not", "no", "please",
              "help", "kindly", "do", "something", "nobody", "listening", "before", "also",
              "near", "behind", "opposite", "sector", "ward", "block", "days", "day", "week",
              "two", "three", "morning", "monday", "hai", "ho", "rahi", "raha", "nahi", "ki",
              "aa", "complained", "every", "being", "it", "that", "too",
              "colony", "market", "temple", "school", "hospital", "station", "bus", "stand"]

tokens_list = []
for text in data["complaint"]:
    clean = ""
    for ch in text.lower():
        if ch.isalpha():
            clean = clean + ch
        else:
            clean = clean + " "
    tokens = []
    for w in clean.split():
        if w not in stop_words and len(w) > 1:
            tokens.append(w)
    tokens_list.append(tokens)
data["tokens"] = tokens_list

print("Text normalisation of one complaint:")
print("  Original :", data.iloc[0]["complaint"])
print("  Tokens   :", data.iloc[0]["tokens"])
print()

# Chart 1: the most common words in each department's complaints.
plt.figure(figsize=(10, 6))
colours = ["#4e9fd1", "#f2b134", "#8c8c8c", "#59a14f"]
for k in range(4):
    word_count = {}
    for tokens in data[data["department"] == departments[k]]["tokens"]:
        for w in tokens:
            word_count[w] = word_count.get(w, 0) + 1
    top = pd.Series(word_count).sort_values(ascending=False).head(8)
    plt.subplot(2, 2, k + 1)
    plt.barh(top.index[::-1], top.values[::-1], color=colours[k])
    plt.title(departments[k])
plt.suptitle("Most common words in each department's complaints")
plt.tight_layout()
plt.savefig("chart1_department_words.png")
plt.close()

# %% Stage 4 - Modelling
print("--- Stage 4: Modelling ---")
data = data.sample(frac=1, random_state=21)
cut = int(len(data) * 0.8)
train = data[:cut]
test = data[cut:]
print("Training complaints :", len(train))
print("Testing complaints  :", len(test))
print()

# Model A - KEYWORD RULES, written by a person.
keywords = {
    "Water":       ["water", "tap", "taps", "pipe", "tanker", "supply"],
    "Electricity": ["power", "light", "lights", "electric", "electricity", "transformer", "voltage", "wire"],
    "Roads":       ["road", "pothole", "footpath", "traffic", "divider"],
    "Sanitation":  ["garbage", "drain", "dustbin", "toilet", "sewer", "smell"],
}

# Model B - WORD PROFILES, learned. For each department: what share of all
# the words in its training complaints is each word?
profile = {}
for d in departments:
    word_count = {}
    total = 0
    for tokens in train[train["department"] == d]["tokens"]:
        for w in tokens:
            word_count[w] = word_count.get(w, 0) + 1
            total = total + 1
    shares = {}
    for w in word_count:
        shares[w] = word_count[w] / total
    profile[d] = shares

for w in ["water", "paani", "bijli", "kachra", "road"]:
    line = "Learned share of '" + w + "'"
    parts = []
    for d in departments:
        parts.append(d + " " + str(round(profile[d].get(w, 0) * 100, 1)) + "%")
    print(line.ljust(26), ":", ", ".join(parts))
print()

rule_said = []
learned_said = []
for tokens in test["tokens"]:
    best_rule = "Unknown"
    best_hits = 0
    best_learned = None
    best_score = -1
    for d in departments:
        hits = 0
        score = 0
        for w in tokens:
            if w in keywords[d]:
                hits = hits + 1
            score = score + profile[d].get(w, 0)
        if hits > best_hits:
            best_hits = hits
            best_rule = d
        if score > best_score:
            best_score = score
            best_learned = d
    rule_said.append(best_rule)
    learned_said.append(best_learned)

# %% Stage 5 - Evaluation
print("--- Stage 5: Evaluation ---")
actual = list(test["department"])
rule_right = 0
learned_right = 0
unknown = 0
for i in range(len(actual)):
    if rule_said[i] == actual[i]:
        rule_right = rule_right + 1
    if learned_said[i] == actual[i]:
        learned_right = learned_right + 1
    if rule_said[i] == "Unknown":
        unknown = unknown + 1
rule_acc = rule_right / len(actual) * 100
learned_acc = learned_right / len(actual) * 100
print("Keyword rules  :", rule_right, "of", len(actual), "correct")
print("Word profiles  :", learned_right, "of", len(actual), "correct")
print("Accuracy of the keyword rules :", round(rule_acc, 1), "%")
print("Accuracy of the word profiles :", round(learned_acc, 1), "%")
print("Complaints the keyword rules could not place :", unknown)
print()

matrix = pd.crosstab(pd.Series(actual, name="Actual"), pd.Series(learned_said, name="Sent to"))
matrix = matrix.reindex(index=departments, columns=departments, fill_value=0)
print("Confusion matrix of the word profiles:")
print(matrix)
print()

print("Where the keyword rules went wrong (first 5):")
shown = 0
for i in range(len(actual)):
    if rule_said[i] != actual[i] and shown < 5:
        print("  [" + actual[i] + ", rules said " + rule_said[i] + "]", test.iloc[i]["complaint"])
        shown = shown + 1
print()

# Chart 2: confusion matrix.
plt.figure(figsize=(6, 5))
plt.imshow(matrix.values, cmap="Blues")
for r in range(4):
    for c in range(4):
        value = matrix.values[r][c]
        if value > matrix.values.max() / 2:
            ink = "white"
        else:
            ink = "black"
        plt.text(c, r, value, ha="center", va="center", fontsize=14, color=ink)
plt.xticks(range(4), departments, rotation=15)
plt.yticks(range(4), departments)
plt.xlabel("Sent to")
plt.ylabel("Right department")
plt.title("Word profiles (testing complaints)")
plt.tight_layout()
plt.savefig("chart2_matrix.png")
plt.close()

# Chart 3: share of each department's complaints sent to the right desk.
rule_recall = []
learned_recall = []
for d in departments:
    total = 0
    r_ok = 0
    l_ok = 0
    for i in range(len(actual)):
        if actual[i] == d:
            total = total + 1
            if rule_said[i] == d:
                r_ok = r_ok + 1
            if learned_said[i] == d:
                l_ok = l_ok + 1
    rule_recall.append(r_ok / total * 100)
    learned_recall.append(l_ok / total * 100)
x = np.arange(4)
plt.figure(figsize=(8, 4.5))
plt.bar(x - 0.2, rule_recall, width=0.4, label="Keyword rules", color="#9c9c9c")
plt.bar(x + 0.2, learned_recall, width=0.4, label="Word profiles", color="#4e79a7")
plt.xticks(x, departments)
plt.ylim(0, 105)
plt.ylabel("Complaints sent to the right desk (%)")
plt.title("Each department: how many complaints reached it")
plt.legend()
plt.tight_layout()
plt.savefig("chart3_by_department.png")
plt.close()

# %% Stage 6 - Deployment
print("--- Stage 6: Deployment ---")
new_complaints = [
    "Bijli nahi hai in Sector 12 since yesterday night",
    "Huge pothole filled with rain water on MG Road",
    "Kachra pada hai near the bus stand, nobody is cleaning",
    "Drinking water tanker has not come to Ward 7",
]
for text in new_complaints:
    clean = ""
    for ch in text.lower():
        if ch.isalpha():
            clean = clean + ch
        else:
            clean = clean + " "
    best = None
    best_score = -1
    for d in departments:
        score = 0
        for w in clean.split():
            if w not in stop_words:
                score = score + profile[d].get(w, 0)
        if score > best_score:
            best_score = score
            best = d
    print(text.ljust(56), "->", best, "department")
