muaazl's picture
first commit
cf4ac41
Raw
History Blame Contribute Delete
4.89 kB
import re
import emoji
from typing import List, Dict, Tuple, Any, Optional
import pandas as pd
import numpy as np
import spacy
from ..parser.schemas import RawMessage
from .schemas import PreprocessedMessage, MessageMetadata, PreprocessingResult
from .constants import (
URL_PATTERN,
REPEATED_PUNCTUATION_PATTERN,
REPEATED_CHARACTER_PATTERN,
WHITESPACE_PATTERN,
PUNCTUATION_REMOVAL_PATTERN
)
class MessageCleaner:
"""Preprocesses raw chat messages into analysis-ready formats."""
def __init__(self, model_name: str = "en_core_web_sm", nlp: Optional[spacy.language.Language] = None):
if nlp is not None:
self.nlp = nlp
else:
try:
self.nlp = spacy.load(model_name)
except OSError:
self.nlp = None
def _is_emoji(self, char: str) -> bool:
"""Check if a character is an emoji using the emoji library."""
return emoji.is_emoji(char)
def extract_emojis(self, text: str) -> List[str]:
"""Extract all emojis from a string."""
return [char for char in text if self._is_emoji(char)]
def get_caps_ratio(self, text: str) -> float:
"""Calculate ratio of uppercase letters to total letters."""
letters = [c for c in text if c.isalpha()]
if not letters:
return 0.0
return sum(1 for c in letters if c.isupper()) / len(letters)
def count_repeated_patterns(self, text: str, pattern: re.Pattern) -> int:
"""Count matches for a specific regex pattern."""
return len(pattern.findall(text))
def clean_base(self, text: str) -> str:
"""Standard cleaning: trimming and workspace normalization."""
text = text.strip()
text = WHITESPACE_PATTERN.sub(" ", text)
return text
def generate_variants(self, text: str, emojis: List[str]) -> Dict[str, str]:
"""Generate specific cleaned versions of the text for different ML tasks."""
base = self.clean_base(text)
sentiment_v = URL_PATTERN.sub("[URL]", base)
topic_v = "".join([c for c in base if not self._is_emoji(c)])
topic_v = URL_PATTERN.sub("", topic_v)
topic_v = PUNCTUATION_REMOVAL_PATTERN.sub("", topic_v)
topic_v = topic_v.lower().strip()
topic_v = WHITESPACE_PATTERN.sub(" ", topic_v)
return {
"sentiment": sentiment_v,
"toxicity": sentiment_v,
"topic": topic_v,
"speaker": base
}
def process_message(self, index: int, raw: RawMessage) -> PreprocessedMessage:
"""Main processing flow for a single message."""
content = raw.content
emojis = self.extract_emojis(content)
metadata = MessageMetadata(
emoji_count=len(emojis),
emoji_list=emojis,
caps_ratio=self.get_caps_ratio(content),
repeated_punctuation_count=self.count_repeated_patterns(content, REPEATED_PUNCTUATION_PATTERN),
repeated_char_count=self.count_repeated_patterns(content, REPEATED_CHARACTER_PATTERN),
url_count=len(URL_PATTERN.findall(content)),
has_media=raw.is_media,
message_length=len(content),
word_count=len(content.split())
)
base_clean = self.clean_base(content)
variants = self.generate_variants(content, emojis)
return PreprocessedMessage(
message_id=index,
raw=raw,
base_clean=base_clean,
variants=variants,
metadata=metadata
)
def process_sequence(self, raw_messages: List[RawMessage]) -> PreprocessingResult:
"""Process a stream of messages and build global metadata."""
preprocessed = []
emoji_map = {}
for i, msg in enumerate(raw_messages):
processed = self.process_message(i, msg)
preprocessed.append(processed)
for emoji in processed.metadata.emoji_list:
emoji_map[emoji] = emoji_map.get(emoji, 0) + 1
df = pd.DataFrame([
{
"sender": p.raw.sender,
"msg_len": p.metadata.message_length,
"caps": p.metadata.caps_ratio,
"emojis": p.metadata.emoji_count,
"is_media": p.metadata.has_media
} for p in preprocessed if p.raw.sender
])
participant_stats = {}
if not df.empty:
participant_stats = df.groupby("sender").agg({
"msg_len": "mean",
"caps": "mean",
"emojis": "sum",
"is_media": "sum"
}).to_dict(orient="index")
return PreprocessingResult(
messages=preprocessed,
global_metadata={
"total_emojis": emoji_map,
"participant_stats": participant_stats
}
)