dhanush1601 commited on
Commit
5fd1a13
Β·
verified Β·
1 Parent(s): 95d07ab

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +130 -20
  2. app.py +682 -0
  3. requirements.txt +4 -3
README.md CHANGED
@@ -1,20 +1,130 @@
1
- ---
2
- title: Sample
3
- emoji: πŸš€
4
- colorFrom: red
5
- colorTo: red
6
- sdk: docker
7
- app_port: 8501
8
- tags:
9
- - streamlit
10
- pinned: false
11
- short_description: sample
12
- license: gemma
13
- ---
14
-
15
- # Welcome to Streamlit!
16
-
17
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
18
-
19
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
20
- forums](https://discuss.streamlit.io).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: AI Ticket Classifier
3
+ emoji: 🧠
4
+ colorFrom: indigo
5
+ colorTo: purple
6
+ sdk: streamlit
7
+ sdk_version: 1.41.1
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ # 🧠 AI Ticket Classifier
13
+
14
+ An AI-powered support ticket classification system that groups tickets by intent and generates automated responses using NLP techniques.
15
+
16
+ ---
17
+
18
+ ## πŸ“Œ Problem Statement
19
+
20
+ Organizations receive hundreds of support tickets daily. Manually categorizing and routing each ticket is slow and error-prone. This system uses **TF-IDF**, **Cosine Similarity**, and **Keyword Matching** to instantly classify, group, and respond to tickets.
21
+
22
+ ## πŸ“‹ Sample Tickets
23
+
24
+ | # | Ticket | Classified As |
25
+ |---|--------|---------------|
26
+ | 1 | "I forgot my password, how to reset it?" | πŸ” Password / Authentication |
27
+ | 2 | "I can't log in, as password is incorrect" | πŸ” Password / Authentication |
28
+ | 3 | "How to see leave balance?" | πŸ“Š HR / Leave Management |
29
+
30
+ **Result:** Tickets 1 & 2 β†’ grouped together (password-related), Ticket 3 β†’ separate group (HR-related).
31
+
32
+ ---
33
+
34
+ ## πŸ› οΈ Technologies Used
35
+
36
+ | Technology | Purpose |
37
+ |------------|---------|
38
+ | Python 3 | Core language |
39
+ | Streamlit | Interactive web UI |
40
+ | scikit-learn | TF-IDF vectorization, cosine similarity |
41
+ | pandas / numpy | Data handling |
42
+
43
+ ---
44
+
45
+ ## πŸ“‚ File Structure
46
+
47
+ ```
48
+ AI Design/
49
+ β”œβ”€β”€ ticket_classifier.py β†’ Main Streamlit app (NLP engine + UI)
50
+ β”œβ”€β”€ requirements.txt β†’ Python dependencies
51
+ β”œβ”€β”€ REPORT.md β†’ Detailed project report
52
+ └── README.md β†’ This file
53
+ ```
54
+
55
+ ---
56
+
57
+ ## πŸš€ How to Run
58
+
59
+ ### Prerequisites
60
+ - Python 3.8 or later β€” [Download](https://python.org)
61
+
62
+ ### Steps
63
+
64
+ ```bash
65
+ # 1. Navigate to the project folder
66
+ cd "c:\Users\sakthi\Documents\AI Design"
67
+
68
+ # 2. Install dependencies
69
+ pip install -r requirements.txt
70
+
71
+ # 3. Run the app
72
+ streamlit run ticket_classifier.py
73
+
74
+ # 4. Browser opens automatically at http://localhost:8501
75
+ ```
76
+
77
+ ---
78
+
79
+ ## βš™οΈ How It Works
80
+
81
+ ```
82
+ User enters ticket
83
+ β”‚
84
+ β–Ό
85
+ Text Preprocessing (lowercase, remove punctuation, stop words)
86
+ β”‚
87
+ β–Ό
88
+ Keyword Matching (compare against category keyword lists)
89
+ β”‚
90
+ β–Ό
91
+ Scoring & Classification (highest score β†’ category + confidence)
92
+ β”‚
93
+ β–Ό
94
+ Display Result (category badge, confidence bar, auto-response)
95
+ β”‚
96
+ β–Ό
97
+ Grouping (add to category group, update similarity matrix)
98
+ ```
99
+
100
+ ### NLP Techniques
101
+ - **Tokenization** β€” splits text into individual words
102
+ - **Stop Word Removal** β€” removes common words (a, the, is, are)
103
+ - **TF-IDF** β€” scores words by importance (frequent in one ticket, rare across all)
104
+ - **Cosine Similarity** β€” measures how similar two tickets are (0 = different, 1 = identical)
105
+ - **Keyword Matching** β€” matches ticket text against predefined category keywords
106
+
107
+ ---
108
+
109
+ ## πŸ“Š Supported Categories
110
+
111
+ | Category | Example Keywords |
112
+ |----------|-----------------|
113
+ | πŸ” Password / Auth | password, reset, forgot, login, credentials |
114
+ | πŸ“Š HR / Leave | leave, balance, salary, work from home, payroll |
115
+ | πŸ’» IT / Hardware | laptop, slow, crash, printer, software |
116
+ | 🌐 Network | vpn, wifi, internet, connection, firewall |
117
+ | πŸ”‘ Access / Permissions | access, permission, shared drive, repository |
118
+ | ❓ General | (fallback for unmatched tickets) |
119
+
120
+ ---
121
+
122
+ ## πŸ“ˆ Features
123
+
124
+ - βœ… Instant ticket classification with confidence scores
125
+ - βœ… Automated responses from knowledge base
126
+ - βœ… Visual ticket grouping by category
127
+ - βœ… Interactive similarity matrix heatmap
128
+ - βœ… 3 pre-loaded example tickets
129
+ - βœ… Premium dark-mode UI
130
+ - βœ… No external API or cloud services needed
app.py ADDED
@@ -0,0 +1,682 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import streamlit as st
4
+ import pandas as pd
5
+ import numpy as np
6
+ import re
7
+ from sklearn.feature_extraction.text import TfidfVectorizer
8
+ from sklearn.metrics.pairwise import cosine_similarity
9
+
10
+ # ── Page Configuration ──────────────────────────────────────────────────────────
11
+ st.set_page_config(
12
+ page_title="AI Ticket Classifier",
13
+ page_icon="🧠",
14
+ layout="wide",
15
+ initial_sidebar_state="collapsed"
16
+ )
17
+
18
+ # ── Custom CSS for Premium Dark Theme ────────────────────────────────────────────
19
+ st.markdown("""
20
+ <style>
21
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
22
+
23
+ /* Base */
24
+ .stApp {
25
+ background: linear-gradient(135deg, #0b0d17 0%, #111427 50%, #0d1020 100%);
26
+ font-family: 'Inter', sans-serif;
27
+ }
28
+
29
+ /* Header */
30
+ .main-header {
31
+ text-align: center;
32
+ padding: 2rem 0 1rem;
33
+ }
34
+ .main-header h1 {
35
+ font-size: 2.5rem;
36
+ font-weight: 800;
37
+ background: linear-gradient(135deg, #818cf8, #a855f7, #06b6d4);
38
+ -webkit-background-clip: text;
39
+ -webkit-text-fill-color: transparent;
40
+ margin-bottom: 0.3rem;
41
+ }
42
+ .main-header p {
43
+ color: #9fa2b4;
44
+ font-size: 1rem;
45
+ }
46
+
47
+ /* Stat Boxes */
48
+ .stat-container {
49
+ display: flex;
50
+ justify-content: center;
51
+ gap: 1.2rem;
52
+ margin: 1rem 0 2rem;
53
+ }
54
+ .stat-box {
55
+ background: rgba(17, 20, 39, 0.75);
56
+ border: 1px solid rgba(99, 102, 241, 0.2);
57
+ border-radius: 12px;
58
+ padding: 0.8rem 1.8rem;
59
+ text-align: center;
60
+ backdrop-filter: blur(10px);
61
+ min-width: 140px;
62
+ }
63
+ .stat-box .stat-label {
64
+ font-size: 0.7rem;
65
+ font-weight: 600;
66
+ text-transform: uppercase;
67
+ letter-spacing: 0.08em;
68
+ color: #5e6278;
69
+ }
70
+ .stat-box .stat-value {
71
+ font-size: 1.6rem;
72
+ font-weight: 700;
73
+ color: #818cf8;
74
+ margin-top: 2px;
75
+ }
76
+
77
+ /* Cards */
78
+ .glass-card {
79
+ background: rgba(17, 20, 39, 0.6);
80
+ border: 1px solid rgba(99, 102, 241, 0.15);
81
+ border-radius: 16px;
82
+ padding: 1.5rem;
83
+ margin-bottom: 1.2rem;
84
+ backdrop-filter: blur(12px);
85
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
86
+ transition: border-color 0.3s ease;
87
+ }
88
+ .glass-card:hover {
89
+ border-color: rgba(99, 102, 241, 0.35);
90
+ }
91
+ .card-title {
92
+ font-size: 1rem;
93
+ font-weight: 600;
94
+ color: #e8eaf6;
95
+ margin-bottom: 1rem;
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 8px;
99
+ }
100
+
101
+ /* Category Badges */
102
+ .badge {
103
+ display: inline-block;
104
+ padding: 6px 18px;
105
+ border-radius: 50px;
106
+ font-size: 0.85rem;
107
+ font-weight: 600;
108
+ color: white;
109
+ }
110
+ .badge-password { background: linear-gradient(135deg, #6366f1, #818cf8); }
111
+ .badge-hr { background: linear-gradient(135deg, #10b981, #34d399); }
112
+ .badge-it { background: linear-gradient(135deg, #f59e0b, #fbbf24); }
113
+ .badge-network { background: linear-gradient(135deg, #06b6d4, #22d3ee); }
114
+ .badge-access { background: linear-gradient(135deg, #a855f7, #c084fc); }
115
+ .badge-general { background: linear-gradient(135deg, #64748b, #94a3b8); }
116
+
117
+ /* Confidence Bar */
118
+ .conf-bar-bg {
119
+ width: 100%;
120
+ height: 10px;
121
+ background: rgba(255,255,255,0.06);
122
+ border-radius: 5px;
123
+ overflow: hidden;
124
+ margin: 6px 0;
125
+ }
126
+ .conf-bar-fill {
127
+ height: 100%;
128
+ border-radius: 5px;
129
+ background: linear-gradient(90deg, #6366f1, #10b981);
130
+ transition: width 0.8s ease;
131
+ }
132
+
133
+ /* Response Box */
134
+ .response-box {
135
+ padding: 1rem 1.2rem;
136
+ background: rgba(16, 185, 129, 0.08);
137
+ border: 1px solid rgba(16, 185, 129, 0.2);
138
+ border-radius: 10px;
139
+ color: #e8eaf6;
140
+ font-size: 0.9rem;
141
+ line-height: 1.7;
142
+ }
143
+
144
+ /* Keyword pills */
145
+ .kw-pill {
146
+ display: inline-block;
147
+ padding: 3px 14px;
148
+ margin: 3px 4px;
149
+ background: rgba(99, 102, 241, 0.12);
150
+ border: 1px solid rgba(99, 102, 241, 0.25);
151
+ border-radius: 50px;
152
+ font-size: 0.78rem;
153
+ font-weight: 500;
154
+ color: #818cf8;
155
+ }
156
+
157
+ /* Ticket Item */
158
+ .ticket-row {
159
+ display: flex;
160
+ align-items: center;
161
+ gap: 10px;
162
+ padding: 10px 14px;
163
+ margin-bottom: 6px;
164
+ border: 1px solid rgba(99, 102, 241, 0.12);
165
+ border-radius: 8px;
166
+ background: rgba(0,0,0,0.15);
167
+ font-size: 0.88rem;
168
+ color: #9fa2b4;
169
+ }
170
+ .ticket-num {
171
+ font-size: 0.72rem;
172
+ font-weight: 700;
173
+ color: #5e6278;
174
+ padding: 2px 8px;
175
+ background: rgba(255,255,255,0.04);
176
+ border-radius: 4px;
177
+ flex-shrink: 0;
178
+ }
179
+ .ticket-conf {
180
+ flex-shrink: 0;
181
+ font-size: 0.75rem;
182
+ font-weight: 600;
183
+ color: #10b981;
184
+ }
185
+
186
+ /* Group Header */
187
+ .group-header {
188
+ display: flex;
189
+ align-items: center;
190
+ gap: 10px;
191
+ margin-bottom: 8px;
192
+ margin-top: 16px;
193
+ }
194
+ .group-dot {
195
+ width: 10px;
196
+ height: 10px;
197
+ border-radius: 50%;
198
+ flex-shrink: 0;
199
+ }
200
+ .group-name {
201
+ font-size: 0.92rem;
202
+ font-weight: 600;
203
+ color: #e8eaf6;
204
+ }
205
+ .group-count {
206
+ font-size: 0.72rem;
207
+ padding: 2px 10px;
208
+ border-radius: 50px;
209
+ background: rgba(255,255,255,0.06);
210
+ color: #5e6278;
211
+ }
212
+
213
+ /* Similarity heatmap */
214
+ .sim-cell {
215
+ text-align: center;
216
+ padding: 8px;
217
+ border-radius: 4px;
218
+ font-weight: 600;
219
+ font-size: 0.8rem;
220
+ }
221
+
222
+ /* Hide Streamlit defaults */
223
+ #MainMenu {visibility: hidden;}
224
+ footer {visibility: hidden;}
225
+ header {visibility: hidden;}
226
+ .stDeployButton {display: none;}
227
+
228
+ /* Button styling */
229
+ .stButton > button {
230
+ background: linear-gradient(135deg, #6366f1, #a855f7) !important;
231
+ color: white !important;
232
+ border: none !important;
233
+ border-radius: 10px !important;
234
+ padding: 0.6rem 1.5rem !important;
235
+ font-weight: 600 !important;
236
+ font-size: 0.9rem !important;
237
+ transition: all 0.3s ease !important;
238
+ box-shadow: 0 4px 20px rgba(99, 102, 241, 0.3) !important;
239
+ }
240
+ .stButton > button:hover {
241
+ transform: translateY(-2px) !important;
242
+ box-shadow: 0 6px 28px rgba(99, 102, 241, 0.45) !important;
243
+ }
244
+
245
+ /* Text input */
246
+ .stTextArea textarea {
247
+ background: rgba(0,0,0,0.25) !important;
248
+ border: 1px solid rgba(99, 102, 241, 0.2) !important;
249
+ border-radius: 10px !important;
250
+ color: #e8eaf6 !important;
251
+ font-family: 'Inter', sans-serif !important;
252
+ }
253
+ .stTextArea textarea:focus {
254
+ border-color: #6366f1 !important;
255
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.25) !important;
256
+ }
257
+ </style>
258
+ """, unsafe_allow_html=True)
259
+
260
+
261
+ # ── Knowledge Base ───────────────────────────────────────────────────────────────
262
+ CATEGORIES = {
263
+ "password": {
264
+ "name": "πŸ” Password / Authentication",
265
+ "icon": "πŸ”",
266
+ "css": "password",
267
+ "color": "#6366f1",
268
+ "keywords": [
269
+ "password", "passwd", "reset", "forgot", "login", "log in", "sign in",
270
+ "signin", "authenticate", "credentials", "locked out", "lockout",
271
+ "incorrect password", "wrong password", "cant log", "can't log",
272
+ "unable to login", "access denied", "two factor", "2fa", "mfa", "otp"
273
+ ],
274
+ "response": (
275
+ "πŸ” **Password / Authentication Help**\n\n"
276
+ "To reset your password:\n"
277
+ "1. Go to the login page and click **Forgot Password**\n"
278
+ "2. Enter your registered email address\n"
279
+ "3. Check your inbox for the reset link (also check spam)\n"
280
+ "4. Create a new password (min 8 chars, 1 uppercase, 1 number)\n\n"
281
+ "If your account is locked, please wait 15 minutes or contact IT support at ext. 1234."
282
+ )
283
+ },
284
+ "hr": {
285
+ "name": "πŸ“Š HR / Leave Management",
286
+ "icon": "πŸ“Š",
287
+ "css": "hr",
288
+ "color": "#10b981",
289
+ "keywords": [
290
+ "leave", "leave balance", "vacation", "holiday", "sick leave",
291
+ "casual leave", "attendance", "absence", "time off", "pto",
292
+ "paid time off", "work from home", "wfh", "remote work", "salary",
293
+ "payslip", "pay slip", "payroll", "bonus", "appraisal", "hr",
294
+ "human resource", "benefits", "insurance", "medical",
295
+ "bank account", "bank details", "compensation"
296
+ ],
297
+ "response": (
298
+ "πŸ“Š **HR / Leave Management Help**\n\n"
299
+ "To check your leave balance:\n"
300
+ "1. Log in to the HR Portal (hr.company.com)\n"
301
+ "2. Navigate to **My Leave** β†’ **Leave Balance**\n"
302
+ "3. View category-wise balance (Casual, Sick, Earned)\n\n"
303
+ "To apply for leave or WFH, go to My Leave β†’ Apply Leave. "
304
+ "For salary/payroll queries, contact hr@company.com."
305
+ )
306
+ },
307
+ "it": {
308
+ "name": "πŸ’» IT / Hardware Support",
309
+ "icon": "οΏ½οΏ½οΏ½",
310
+ "css": "it",
311
+ "color": "#f59e0b",
312
+ "keywords": [
313
+ "laptop", "computer", "slow", "crash", "hang", "freeze",
314
+ "blue screen", "bsod", "restart", "reboot", "software", "install",
315
+ "update", "upgrade", "printer", "scanner", "monitor", "keyboard",
316
+ "mouse", "hardware", "performance", "disk", "storage", "memory", "ram"
317
+ ],
318
+ "response": (
319
+ "πŸ’» **IT / Hardware Support Help**\n\n"
320
+ "For hardware issues:\n"
321
+ "1. Try restarting your device first\n"
322
+ "2. Clear temporary files: Win+R β†’ type 'temp' β†’ delete all\n"
323
+ "3. Check Task Manager (Ctrl+Shift+Esc) for high CPU/memory usage\n\n"
324
+ "If the issue persists, raise a ticket at it-support.company.com or call ext. 5678."
325
+ )
326
+ },
327
+ "network": {
328
+ "name": "🌐 Network / Connectivity",
329
+ "icon": "🌐",
330
+ "css": "network",
331
+ "color": "#06b6d4",
332
+ "keywords": [
333
+ "vpn", "network", "internet", "wifi", "wi-fi", "connect",
334
+ "connection", "disconnect", "firewall", "proxy", "bandwidth",
335
+ "speed", "slow internet", "office network", "remote access",
336
+ "rdp", "remote desktop", "intranet", "server", "dns"
337
+ ],
338
+ "response": (
339
+ "🌐 **Network / Connectivity Help**\n\n"
340
+ "For VPN/network issues:\n"
341
+ "1. Disconnect and reconnect your VPN client\n"
342
+ "2. Ensure you're using the latest VPN client version\n"
343
+ "3. Try switching between Wi-Fi and wired connection\n"
344
+ "4. Restart your router/modem if working from home\n\n"
345
+ "For persistent issues, contact Network Operations at ext. 9012."
346
+ )
347
+ },
348
+ "access": {
349
+ "name": "πŸ”‘ Access / Permissions",
350
+ "icon": "πŸ”‘",
351
+ "css": "access",
352
+ "color": "#a855f7",
353
+ "keywords": [
354
+ "access", "permission", "role", "shared drive", "folder",
355
+ "repository", "repo", "github", "gitlab", "jira", "confluence",
356
+ "admin", "privileges", "unauthorized", "forbidden", "restricted",
357
+ "grant access", "request access", "onboarding"
358
+ ],
359
+ "response": (
360
+ "πŸ”‘ **Access / Permissions Help**\n\n"
361
+ "To request access:\n"
362
+ "1. Go to the Access Management Portal (access.company.com)\n"
363
+ "2. Click **New Access Request**\n"
364
+ "3. Search for the resource (shared drive, app, repo)\n"
365
+ "4. Select the required role/permission level\n"
366
+ "5. Submit β€” your manager will receive an approval request\n\n"
367
+ "For urgent access needs, contact your IT admin directly."
368
+ )
369
+ },
370
+ "general": {
371
+ "name": "❓ General Inquiry",
372
+ "icon": "❓",
373
+ "css": "general",
374
+ "color": "#64748b",
375
+ "keywords": [],
376
+ "response": (
377
+ "❓ **General Inquiry**\n\n"
378
+ "Thank you for reaching out! Your ticket has been received "
379
+ "and will be reviewed by our support team.\n\n"
380
+ "Expected response time: 2–4 business hours.\n\n"
381
+ "For urgent issues, please call the helpdesk at ext. 1111."
382
+ )
383
+ }
384
+ }
385
+
386
+ # ── Stop Words ───────────────────────────────────────────────────────────────────
387
+ STOP_WORDS = {
388
+ "i", "me", "my", "myself", "we", "our", "you", "your", "he", "she",
389
+ "it", "its", "they", "them", "what", "which", "who", "this", "that",
390
+ "am", "is", "are", "was", "were", "be", "been", "being", "have", "has",
391
+ "had", "do", "does", "did", "a", "an", "the", "and", "but", "if", "or",
392
+ "because", "as", "until", "while", "of", "at", "by", "for", "with",
393
+ "about", "between", "through", "during", "before", "after", "to", "from",
394
+ "up", "down", "in", "out", "on", "off", "over", "under", "again",
395
+ "then", "once", "here", "there", "when", "where", "why", "how", "all",
396
+ "both", "each", "few", "more", "most", "other", "some", "no", "not",
397
+ "only", "own", "same", "so", "than", "too", "very", "can", "will",
398
+ "just", "don", "should", "now", "please"
399
+ }
400
+
401
+
402
+ # ── NLP Functions ────────────────────────────────────────────────────────────────
403
+ def preprocess(text: str) -> str:
404
+ """Lowercase, remove punctuation, remove stop words."""
405
+ text = text.lower()
406
+ text = re.sub(r"[^a-z0-9\s'-]", " ", text)
407
+ tokens = text.split()
408
+ tokens = [t for t in tokens if t not in STOP_WORDS and len(t) > 1]
409
+ return " ".join(tokens)
410
+
411
+
412
+ def classify_ticket(text: str) -> dict:
413
+ """Classify a ticket into a category using keyword matching."""
414
+ lower = text.lower()
415
+ best_cat_id = "general"
416
+ best_score = 0
417
+ matched_keywords = []
418
+
419
+ for cat_id, cat in CATEGORIES.items():
420
+ if not cat["keywords"]:
421
+ continue
422
+ score = 0
423
+ matched = []
424
+ for kw in cat["keywords"]:
425
+ if kw in lower:
426
+ weight = len(kw.split()) * 2
427
+ score += weight
428
+ matched.append(kw)
429
+ if score > best_score:
430
+ best_score = score
431
+ best_cat_id = cat_id
432
+ matched_keywords = list(set(matched))
433
+
434
+ # Sigmoid-like confidence mapping
435
+ confidence = 0.15 if best_score == 0 else min(0.99, 1 - 1 / (1 + best_score * 0.4))
436
+
437
+ return {
438
+ "category_id": best_cat_id,
439
+ "category": CATEGORIES[best_cat_id],
440
+ "confidence": confidence,
441
+ "matched_keywords": matched_keywords
442
+ }
443
+
444
+
445
+ def compute_similarity_matrix(texts: list) -> np.ndarray:
446
+ """Compute pairwise cosine similarity using TF-IDF."""
447
+ if len(texts) < 2:
448
+ return np.array([[1.0]])
449
+ processed = [preprocess(t) for t in texts]
450
+ vectorizer = TfidfVectorizer()
451
+ tfidf_matrix = vectorizer.fit_transform(processed)
452
+ return cosine_similarity(tfidf_matrix)
453
+
454
+
455
+ def sim_color(val: float) -> str:
456
+ """Map similarity score to a background colour."""
457
+ if val > 0.8:
458
+ return "rgba(16, 185, 129, 0.7)"
459
+ if val > 0.6:
460
+ return "rgba(16, 185, 129, 0.45)"
461
+ if val > 0.4:
462
+ return "rgba(99, 102, 241, 0.45)"
463
+ if val > 0.2:
464
+ return "rgba(99, 102, 241, 0.25)"
465
+ return "rgba(255,255,255, 0.04)"
466
+
467
+
468
+ # ── Session State ────────────────────────────────────────────────────────────────
469
+ if "tickets" not in st.session_state:
470
+ # Pre-load the 3 example tickets
471
+ examples = [
472
+ "I forgot my password, how to reset it?",
473
+ "I can't log in, as password is incorrect",
474
+ "How to see leave balance?"
475
+ ]
476
+ st.session_state.tickets = []
477
+ for i, text in enumerate(examples):
478
+ result = classify_ticket(text)
479
+ st.session_state.tickets.append({
480
+ "id": i + 1,
481
+ "text": text,
482
+ **result
483
+ })
484
+ st.session_state.next_id = 4
485
+
486
+
487
+ # ── Header ───────────────────────────────────────────────────────────────────────
488
+ st.markdown("""
489
+ <div class="main-header">
490
+ <h1>🧠 AI Ticket Classifier</h1>
491
+ <p>Smart ticket grouping using TF-IDF & Cosine Similarity β€” powered by Python</p>
492
+ </div>
493
+ """, unsafe_allow_html=True)
494
+
495
+ # Stats
496
+ tickets = st.session_state.tickets
497
+ total = len(tickets)
498
+ categories_used = len(set(t["category_id"] for t in tickets)) if tickets else 0
499
+ avg_conf = (sum(t["confidence"] for t in tickets) / total * 100) if total > 0 else 0
500
+
501
+ st.markdown(f"""
502
+ <div class="stat-container">
503
+ <div class="stat-box">
504
+ <div class="stat-label">Total Tickets</div>
505
+ <div class="stat-value">{total}</div>
506
+ </div>
507
+ <div class="stat-box">
508
+ <div class="stat-label">Categories</div>
509
+ <div class="stat-value">{categories_used}</div>
510
+ </div>
511
+ <div class="stat-box">
512
+ <div class="stat-label">Avg Confidence</div>
513
+ <div class="stat-value">{avg_conf:.0f}%</div>
514
+ </div>
515
+ </div>
516
+ """, unsafe_allow_html=True)
517
+
518
+
519
+ # ── Two-Column Layout ───────────────────────────────────────────────────────────
520
+ col_left, col_right = st.columns([1, 1], gap="large")
521
+
522
+ # ── LEFT COLUMN ──────────────────────────────────────────────────────────────────
523
+ with col_left:
524
+ # Submit Panel
525
+ st.markdown('<div class="glass-card"><div class="card-title">βœ‰οΈ Submit a Ticket</div>', unsafe_allow_html=True)
526
+ ticket_text = st.text_area(
527
+ "Describe your issue",
528
+ placeholder="e.g. I forgot my password, how to reset it?",
529
+ height=100,
530
+ label_visibility="collapsed"
531
+ )
532
+
533
+ if st.button("⚑ Classify & Respond", use_container_width=True):
534
+ if ticket_text.strip():
535
+ result = classify_ticket(ticket_text.strip())
536
+ new_ticket = {
537
+ "id": st.session_state.next_id,
538
+ "text": ticket_text.strip(),
539
+ **result
540
+ }
541
+ st.session_state.tickets.append(new_ticket)
542
+ st.session_state.next_id += 1
543
+ st.session_state.last_result = new_ticket
544
+ st.rerun()
545
+ st.markdown('</div>', unsafe_allow_html=True)
546
+
547
+ # Classification Result
548
+ last = st.session_state.get("last_result")
549
+ if last:
550
+ cat = last["category"]
551
+ pct = int(last["confidence"] * 100)
552
+
553
+ st.markdown(f"""
554
+ <div class="glass-card" style="border-color: rgba(99,102,241,0.4); box-shadow: 0 0 30px rgba(99,102,241,0.2);">
555
+ <div class="card-title">🧠 Classification Result</div>
556
+ <div style="margin-bottom:12px;">
557
+ <div style="font-size:0.7rem;font-weight:600;text-transform:uppercase;letter-spacing:0.07em;color:#5e6278;margin-bottom:4px;">Category</div>
558
+ <span class="badge badge-{cat['css']}">{cat['name']}</span>
559
+ </div>
560
+ <div style="margin-bottom:12px;">
561
+ <div style="font-size:0.7rem;font-weight:600;text-transform:uppercase;letter-spacing:0.07em;color:#5e6278;margin-bottom:4px;">Confidence</div>
562
+ <div class="conf-bar-bg"><div class="conf-bar-fill" style="width:{pct}%"></div></div>
563
+ <span style="font-size:0.88rem;font-weight:700;color:#10b981;">{pct}%</span>
564
+ </div>
565
+ <div style="margin-bottom:12px;">
566
+ <div style="font-size:0.7rem;font-weight:600;text-transform:uppercase;letter-spacing:0.07em;color:#5e6278;margin-bottom:4px;">Auto-Response</div>
567
+ <div class="response-box">{cat['response'].replace(chr(10), '<br>')}</div>
568
+ </div>
569
+ <div>
570
+ <div style="font-size:0.7rem;font-weight:600;text-transform:uppercase;letter-spacing:0.07em;color:#5e6278;margin-bottom:4px;">Matched Keywords</div>
571
+ {''.join(f'<span class="kw-pill">{kw}</span>' for kw in last['matched_keywords']) if last['matched_keywords'] else '<span class="kw-pill">No specific keywords matched</span>'}
572
+ </div>
573
+ </div>
574
+ """, unsafe_allow_html=True)
575
+
576
+ # Example Tickets
577
+ st.markdown('<div class="glass-card"><div class="card-title">πŸ’‘ Try These Examples</div>', unsafe_allow_html=True)
578
+ examples = [
579
+ "I forgot my password, how to reset it?",
580
+ "I can't log in, as password is incorrect",
581
+ "How to see leave balance?"
582
+ ]
583
+ for ex in examples:
584
+ if st.button(ex, key=f"ex_{ex}", use_container_width=True):
585
+ result = classify_ticket(ex)
586
+ new_ticket = {
587
+ "id": st.session_state.next_id,
588
+ "text": ex,
589
+ **result
590
+ }
591
+ st.session_state.tickets.append(new_ticket)
592
+ st.session_state.next_id += 1
593
+ st.session_state.last_result = new_ticket
594
+ st.rerun()
595
+ st.markdown('</div>', unsafe_allow_html=True)
596
+
597
+
598
+ # ── RIGHT COLUMN ─────────────────────────────────────────────────────────────────
599
+ with col_right:
600
+ # Grouped Tickets
601
+ st.markdown('<div class="glass-card"><div class="card-title">πŸ“‚ Ticket Groups</div>', unsafe_allow_html=True)
602
+
603
+ if not tickets:
604
+ st.markdown("""
605
+ <div style="text-align:center;padding:40px 20px;color:#5e6278;">
606
+ <div style="font-size:2.5rem;margin-bottom:12px;">πŸ“­</div>
607
+ <p>No tickets submitted yet.<br/> Submit a ticket to see how they get grouped!</p>
608
+ </div>
609
+ """, unsafe_allow_html=True)
610
+ else:
611
+ # Group by category
612
+ groups = {}
613
+ for t in tickets:
614
+ cid = t["category_id"]
615
+ if cid not in groups:
616
+ groups[cid] = {"category": t["category"], "items": []}
617
+ groups[cid]["items"].append(t)
618
+
619
+ for cid, group in groups.items():
620
+ cat = group["category"]
621
+ count = len(group["items"])
622
+ st.markdown(f"""
623
+ <div class="group-header">
624
+ <span class="group-dot" style="background:{cat['color']};"></span>
625
+ <span class="group-name">{cat['name']}</span>
626
+ <span class="group-count">{count} ticket{'s' if count > 1 else ''}</span>
627
+ </div>
628
+ """, unsafe_allow_html=True)
629
+
630
+ for item in group["items"]:
631
+ pct = int(item["confidence"] * 100)
632
+ st.markdown(f"""
633
+ <div class="ticket-row">
634
+ <span class="ticket-num">#{item['id']}</span>
635
+ <span style="flex:1;">{item['text']}</span>
636
+ <span class="ticket-conf">{pct}%</span>
637
+ </div>
638
+ """, unsafe_allow_html=True)
639
+
640
+ if st.button("πŸ—‘οΈ Clear All", key="clear"):
641
+ st.session_state.tickets = []
642
+ st.session_state.next_id = 1
643
+ st.session_state.pop("last_result", None)
644
+ st.rerun()
645
+
646
+ st.markdown('</div>', unsafe_allow_html=True)
647
+
648
+ # Similarity Matrix
649
+ if len(tickets) >= 2:
650
+ st.markdown('<div class="glass-card"><div class="card-title">οΏ½οΏ½οΏ½ Similarity Matrix</div>', unsafe_allow_html=True)
651
+
652
+ texts = [t["text"] for t in tickets]
653
+ sim_matrix = compute_similarity_matrix(texts)
654
+
655
+ # Build HTML table
656
+ html = '<table style="width:100%;border-collapse:separate;border-spacing:3px;font-size:0.78rem;">'
657
+ html += '<tr><th style="padding:6px;color:#5e6278;"></th>'
658
+ for t in tickets:
659
+ html += f'<th style="padding:6px;color:#5e6278;text-align:center;">#{t["id"]}</th>'
660
+ html += '</tr>'
661
+
662
+ for i, t in enumerate(tickets):
663
+ html += f'<tr><th style="padding:6px;color:#5e6278;text-align:left;">#{t["id"]}</th>'
664
+ for j in range(len(tickets)):
665
+ val = sim_matrix[i][j]
666
+ pct = int(val * 100)
667
+ bg = sim_color(val)
668
+ txt_color = "#fff" if val > 0.5 else "#c4c7d9"
669
+ html += f'<td class="sim-cell" style="background:{bg};color:{txt_color};">{pct}%</td>'
670
+ html += '</tr>'
671
+ html += '</table>'
672
+
673
+ st.markdown(html, unsafe_allow_html=True)
674
+ st.markdown('</div>', unsafe_allow_html=True)
675
+
676
+
677
+ # ── Footer ───────────────────────────────────────────────────────────────────────
678
+ st.markdown("""
679
+ <div style="text-align:center;padding:24px;color:#5e6278;font-size:0.78rem;border-top:1px solid rgba(99,102,241,0.15);margin-top:2rem;">
680
+ AI Ticket Classifier β€” Python + Streamlit + scikit-learn (TF-IDF & Cosine Similarity)
681
+ </div>
682
+ """, unsafe_allow_html=True)
requirements.txt CHANGED
@@ -1,3 +1,4 @@
1
- altair
2
- pandas
3
- streamlit
 
 
1
+ streamlit
2
+ scikit-learn
3
+ pandas
4
+ numpy