""" Classifier Service — Loads the trained DistilBert sequence classifier and predicts. The model outputs combined "Category | SubCategory" labels. Priority and other fields are derived from the category mapping. """ import os import json import torch import torch.nn.functional as F from transformers import DistilBertTokenizerFast, DistilBertForSequenceClassification SAVE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "models", "classifier") DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") MAX_LEN = 128 # Priority mapping based on sub-category severity PRIORITY_MAP = { "Blue Screen": "Critical", "Overheating": "Critical", "Data Loss": "Critical", "Hardware Failure": "Critical", "Application Crash": "High", "Login Failure": "High", "Password Reset": "High", "VPN Connection": "High", "Firewall Block": "High", "DNS Problem": "High", "MFA Problem": "High", "Account Expired": "High", "Permission Issue": "Medium", "Access Request": "Medium", "Software Install": "Medium", "Update Problem": "Medium", "Compatibility": "Medium", "Configuration": "Medium", "License Issue": "Medium", "Performance": "Medium", "Internet Slow": "Medium", "WiFi Issue": "Medium", "Remote Access": "Medium", "Proxy Error": "Medium", "Network Drive": "Medium", "Role Change": "Medium", "Account Unlock": "Low", "Keyboard/Mouse": "Low", "Monitor Problem": "Low", "Printer Error": "Low", "Battery Issue": "Low", "Laptop Issue": "Low", } # Team assignment based on category TEAM_MAP = { "Access": "IAM Team", "Network": "Network Support", "Software": "Application Support", "Hardware": "Hardware Support", } # Auto-resolve: simple issues that can be auto-resolved AUTO_RESOLVE_SUBS = { "Password Reset", "Account Unlock", "Software Install", "WiFi Issue", "Printer Error", "Monitor Problem", } class ClassifierService: def __init__(self): self.model = None self.tokenizer = None self.id2label = None self.label2id = None self._loaded = False def load(self): """Load model, tokenizer, and label mappings from disk.""" if self._loaded: return abs_dir = os.path.abspath(SAVE_DIR) if not os.path.exists(os.path.join(abs_dir, "model.safetensors")): raise FileNotFoundError( f"Classifier model not found at {abs_dir}. " "Please ensure model files are present." ) # Load label mappings with open(os.path.join(abs_dir, "id2label.json"), "r") as f: self.id2label = json.load(f) with open(os.path.join(abs_dir, "label2id.json"), "r") as f: self.label2id = json.load(f) # Load tokenizer self.tokenizer = DistilBertTokenizerFast.from_pretrained(abs_dir) # Load model self.model = DistilBertForSequenceClassification.from_pretrained(abs_dir) self.model.to(DEVICE) self.model.eval() self._loaded = True print("Classifier loaded successfully") def predict(self, text: str) -> dict: """ Predict category, subcategory, priority, auto_resolve, assigned_team, and confidence. """ self.load() encoding = self.tokenizer( text, truncation=True, padding="max_length", max_length=MAX_LEN, return_tensors="pt", ) input_ids = encoding["input_ids"].to(DEVICE) attention_mask = encoding["attention_mask"].to(DEVICE) with torch.no_grad(): outputs = self.model(input_ids=input_ids, attention_mask=attention_mask) logits = outputs.logits probs = F.softmax(logits, dim=1) confidence, pred_idx = torch.max(probs, dim=1) pred_idx = pred_idx.item() confidence = round(confidence.item(), 4) # Decode the combined label "Category | SubCategory" combined_label = self.id2label.get(str(pred_idx), "Unknown | Unknown") parts = combined_label.split(" | ", 1) category = parts[0].strip() if len(parts) > 0 else "Unknown" subcategory = parts[1].strip() if len(parts) > 1 else "Unknown" # Derive priority priority = PRIORITY_MAP.get(subcategory, "Medium") # Derive assigned team assigned_team = TEAM_MAP.get(category, "General Support") # Derive auto_resolve auto_resolve = subcategory in AUTO_RESOLVE_SUBS # --- Regex Override Layer (Boost for Technical Keywords) --- tech_keywords = { "Network": ["IP address", "hostname", "connection", "network", "bandwidth", "DNS", "firewall", "VPN", "Connectivity", "Latency", "Routing", "Spikes"], "Software": ["crash", "load", "website", "application", "error", "bug", "failing", "software", "SQL", "Cluster", "Database", "Production", "Latency"], "Access": ["login", "password", "access", "authentication", "account", "permission", "MFA", "OAuth"] } lower_text = text.lower() for cat, keywords in tech_keywords.items(): if any(k.lower() in lower_text for k in keywords): # If current prediction is generic, or we have a high-value technical keyword if category == "General" or confidence < 0.9: category = cat assigned_team = TEAM_MAP.get(cat, "General Support") # Boost confidence significantly for verified technical signals confidence = max(confidence, 0.92) break return { "category": category, "subcategory": subcategory, "priority": priority, "auto_resolve": auto_resolve, "assigned_team": assigned_team, "confidence": confidence, }