TanujInsane/document-classification-env
3
1"""2Generate realistic synthetic customer support and internal escalation documents.3Features multi-format data (JSON, Slack, Logs, Emails), ambiguity, and VIP handling.4"""5 6import numpy as np7import random8import string9import json10from typing import Tuple, List, Dict11from sklearn.feature_extraction.text import TfidfVectorizer12from constants import CATEGORIES13 14 15def get_task_config(difficulty: str) -> Dict:16 configs = {17 "easy": {18 "num_documents": 100,19 "num_categories": 5,20 "feature_dim": 100,21 "max_word_count": 200,22 "time_limit_per_action": None,23 "time_limit_per_episode": None,24 "ambiguity_rate": 0.0,25 "sla_seconds": None,26 },27 "medium": {28 "num_documents": 500,29 "num_categories": 10,30 "feature_dim": 100,31 "max_word_count": 500,32 "time_limit_per_action": 2.0,33 "time_limit_per_episode": 1800,34 "ambiguity_rate": 0.15,35 "sla_seconds": 120.0,36 },37 "hard": {38 "num_documents": 1000,39 "num_categories": 22,40 "feature_dim": 100,41 "max_word_count": 1000,42 "time_limit_per_action": 1.0,43 "time_limit_per_episode": 1200,44 "ambiguity_rate": 0.30,45 "sla_seconds": 60.0,46 }47 }48 return configs[difficulty]49 50FIRST_NAMES = ["James", "Sarah", "Michael", "Emily", "David", "Jessica", "Robert", "Wei", "Fatima", "Carlos"]51LAST_NAMES = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Chen", "Al-Rashid"]52COMPANIES = ["Acme Corp", "TechForward Inc", "Global Solutions Ltd", "Stripe", "AWS", "Datadog"]53 54def _ctx():55 name = f"{random.choice(FIRST_NAMES)} {random.choice(LAST_NAMES)}"56 is_vip = random.random() < 0.15 # 15% chance of being a VIP enterprise client57 return {58 "name": name,59 "email": f"{name.lower().replace(' ', '.')}@{random.choice(['gmail.com', 'company.com', 'enterprise.io'])}",60 "company": random.choice(COMPANIES),61 "order_id": f"ORD-{random.randint(100000, 999999)}",62 "amount": f"${random.randint(50, 9999)}.{random.randint(0, 99):02d}",63 "date": f"2025-{random.randint(1,12):02d}-{random.randint(1,28):02d}",64 "is_vip": is_vip,65 "urgency_level": "CRITICAL" if is_vip else random.choice(["LOW", "MEDIUM", "HIGH"])66 }67 68class TaskDataGenerator:69 def __init__(self, difficulty: str, seed: int = None):70 self.difficulty = difficulty71 self.config = get_task_config(difficulty)72 if seed is not None:73 np.random.seed(seed)74 random.seed(seed)75 76 self.categories = list(CATEGORIES[difficulty].values())77 78 # Simple vocabulary for TF-IDF training79 self.vectorizer = TfidfVectorizer(max_features=self.config["feature_dim"], lowercase=True, stop_words='english')80 self.vectorizer.fit(["invoice billing refund", "technical bug crash", "legal contract compliance", "hr payroll benefits complaint", "marketing operations finance strategy tax social"])81 82 def _get_corpus_text(self) -> str:83 if not hasattr(self, "_hf_corpus"):84 try:85 from datasets import load_dataset86 ds = load_dataset("ag_news", split="test")87 self._hf_corpus = [x["text"] for x in ds]88 except Exception:89 self._hf_corpus = ["Please assist with process.", "Error encountered in system."]90 return random.choice(self._hf_corpus)91 92 def _generate_format(self, category: str, ctx: dict) -> str:93 """Generates content in various formats (JSON, Slack, Log, Email) with Real Corpus Injection"""94 fmt = random.choice(["json", "slack", "log", "email"])95 cat_lower = category.lower()96 real_text = self._get_corpus_text()97 98 content = ""99 if fmt == "json":100 payload = {101 "event_id": f"evt_{random.randint(1000,9999)}",102 "source": "webhook_listener",103 "timestamp": ctx["date"],104 "customer": {"id": ctx["email"], "vip_tier": "Enterprise" if ctx["is_vip"] else "Standard"},105 "payload": f"Category: {cat_lower}. Order {ctx['order_id']} value {ctx['amount']}. Extract: {real_text}",106 "metadata": {"raw_category_hint": category, "severity": ctx["urgency_level"]}107 }108 content = json.dumps(payload, indent=2)109 110 elif fmt == "slack":111 content = f"**Slack Transcript - Channel: #triage-incoming**\n"112 content += f"[{ctx['date']} 09:12 AM] @system_bot: New ticket from {ctx['name']} ({ctx['company']}). VIP: {ctx['is_vip']}\n"113 content += f"[{ctx['date']} 09:13 AM] {ctx['name']}: Hey team, we have an issue regarding {cat_lower}.\n"114 content += f"[{ctx['date']} 09:14 AM] {ctx['name']}: For context: {real_text}"115 116 elif fmt == "log":117 content = f"2025-01-01T12:00:00Z WARN [req_id={random.randint(100,999)}] subsystem={category.split('-')[0]} action=PROCESS userId={ctx['email']} msg=\"{real_text}\""118 119 else: # Email120 vip_tag = "[VIP - URGENT] " if ctx['is_vip'] else ""121 content = f"Subject: {vip_tag}Inquiry regarding {cat_lower}\nFrom: {ctx['email']}\n\nTo whom it may concern,\nWe are reaching out about order {ctx['order_id']} value {ctx['amount']}.\n\nAdditional details: {real_text}\n\nRegards,\n{ctx['name']}"122 123 return content124 125 def generate_task_data(self) -> Tuple[List[Dict], np.ndarray]:126 documents = []127 labels = []128 num_docs = self.config["num_documents"]129 ambiguity_rate = self.config.get("ambiguity_rate", 0.0)130 131 for i in range(num_docs):132 ctx = _ctx()133 134 if random.random() < ambiguity_rate:135 # Deliberate heavy ambiguity: Mention multiple departments136 cat1 = random.choice(self.categories)137 cat2 = random.choice(self.categories)138 true_cat = random.choice([cat1, cat2])139 content = self._generate_format(f"{cat1} and {cat2}", ctx)140 else:141 true_cat = random.choice(self.categories)142 content = self._generate_format(true_cat, ctx)143 144 features = self.vectorizer.transform([content]).toarray()[0]145 if np.max(features) > 0: features = 2 * features / (np.max(features) + 1e-8) - 1146 147 doc = {148 "id": f"doc_{i:06d}",149 "content": content,150 "word_count": len(content.split()),151 "has_urgency_markers": ctx["is_vip"], # VIP flag passed via urgency152 "features": features.tolist(),153 "true_category": true_cat,154 "is_vip": ctx["is_vip"]155 }156 157 documents.append(doc)158 labels.append(self.categories.index(true_cat))159 160 return documents, np.array(labels)161 