| """ |
| Keyword Extraction Utility |
| |
| Simple and effective keyword extraction for improved search capabilities. |
| Uses lightweight Python string operations without heavy NLP dependencies. |
| """ |
|
|
| import re |
|
|
| from .dictionaries import PRESERVE_KEYWORDS, STOP_WORDS, TECHNICAL_STOP_WORDS |
|
|
|
|
| class KeywordExtractor: |
| """Simple keyword extraction for search queries""" |
|
|
| def __init__(self): |
| self.stop_words = STOP_WORDS | TECHNICAL_STOP_WORDS |
| self.preserve_keywords = PRESERVE_KEYWORDS |
|
|
| def extract_keywords(self, query: str, min_length: int = 2, max_keywords: int = 10) -> list[str]: |
| """ |
| Extract meaningful keywords from a search query. |
| |
| Args: |
| query: The search query string |
| min_length: Minimum keyword length (default: 2) |
| max_keywords: Maximum number of keywords to return (default: 10) |
| |
| Returns: |
| List of extracted keywords, ordered by importance |
| """ |
| |
| query_lower = query.lower() |
|
|
| |
| |
| tokens = re.findall(r"[a-z0-9_-]+", query_lower) |
|
|
| |
| keywords = [] |
| for token in tokens: |
| |
| if len(token) < min_length: |
| continue |
|
|
| |
| if token in self.preserve_keywords: |
| keywords.append(token) |
| |
| elif token not in self.stop_words: |
| keywords.append(token) |
|
|
| |
| |
| compound_patterns = [ |
| (r"best\s+practice[s]?", "best_practices"), |
| (r"how\s+to", "howto"), |
| (r"step\s+by\s+step", "step_by_step"), |
| (r"real\s+time", "realtime"), |
| (r"full\s+text", "fulltext"), |
| (r"full[\s-]?stack", "fullstack"), |
| (r"back[\s-]?end", "backend"), |
| (r"front[\s-]?end", "frontend"), |
| (r"data[\s-]?base", "database"), |
| (r"web[\s-]?socket", "websocket"), |
| ] |
|
|
| for pattern, replacement in compound_patterns: |
| if re.search(pattern, query_lower): |
| keywords.append(replacement) |
|
|
| |
| seen = set() |
| unique_keywords = [] |
| for keyword in keywords: |
| if keyword not in seen: |
| seen.add(keyword) |
| unique_keywords.append(keyword) |
|
|
| |
| |
| |
| |
| prioritized = self._prioritize_keywords(unique_keywords, query) |
|
|
| |
| return prioritized[:max_keywords] |
|
|
| def _prioritize_keywords(self, keywords: list[str], original_query: str) -> list[str]: |
| """ |
| Prioritize keywords based on various factors. |
| |
| Args: |
| keywords: List of extracted keywords |
| original_query: The original search query |
| |
| Returns: |
| Keywords sorted by priority |
| """ |
| keyword_scores = [] |
|
|
| for keyword in keywords: |
| score = 0 |
|
|
| |
| if keyword in original_query: |
| score += 3 |
|
|
| |
| if keyword in self.preserve_keywords: |
| score += 2 |
|
|
| |
| if len(keyword) > 5: |
| score += 1 |
|
|
| |
| if any(c.isdigit() for c in keyword): |
| score += 1 |
|
|
| |
| count = original_query.lower().count(keyword) |
| if count > 1: |
| score += (count - 1) * 2 |
|
|
| keyword_scores.append((keyword, score)) |
|
|
| |
| keyword_scores.sort(key=lambda x: (-x[1], keywords.index(x[0]))) |
|
|
| return [kw for kw, _ in keyword_scores] |
|
|
| def build_search_terms(self, keywords: list[str]) -> list[str]: |
| """ |
| Build search terms from keywords, including variations. |
| |
| Args: |
| keywords: List of keywords |
| |
| Returns: |
| List of search terms including variations |
| """ |
| search_terms = [] |
|
|
| for keyword in keywords: |
| |
| search_terms.append(keyword) |
|
|
| |
| if keyword.endswith("s") and len(keyword) > 3 and not keyword.endswith("ss"): |
| |
| search_terms.append(keyword[:-1]) |
| elif not keyword.endswith("s") or keyword.endswith("ss"): |
| |
| |
| if keyword.endswith("ss"): |
| search_terms.append(keyword + "es") |
| elif keyword.endswith("s"): |
| search_terms.append(keyword + "es") |
| else: |
| search_terms.append(keyword + "s") |
|
|
| |
| if keyword.endswith("ing"): |
| |
| base = keyword[:-3] |
| if len(base) > 2: |
| search_terms.append(base) |
| search_terms.append(base + "e") |
|
|
| if keyword.endswith("ed"): |
| |
| base = keyword[:-2] |
| if len(base) > 2: |
| search_terms.append(base) |
| search_terms.append(base + "e") |
|
|
| |
| seen = set() |
| unique_terms = [] |
| for term in search_terms: |
| if term not in seen: |
| seen.add(term) |
| unique_terms.append(term) |
|
|
| return unique_terms |
|
|
|
|
| |
| keyword_extractor = KeywordExtractor() |
|
|
|
|
| def extract_keywords(query: str, min_length: int = 2, max_keywords: int = 10) -> list[str]: |
| """ |
| Convenience function to extract keywords from a query. |
| |
| Args: |
| query: The search query string |
| min_length: Minimum keyword length |
| max_keywords: Maximum number of keywords to return |
| |
| Returns: |
| List of extracted keywords |
| """ |
| return keyword_extractor.extract_keywords(query, min_length, max_keywords) |
|
|
|
|
| def build_search_terms(keywords: list[str]) -> list[str]: |
| """ |
| Convenience function to build search terms from keywords. |
| |
| Args: |
| keywords: List of keywords |
| |
| Returns: |
| List of search terms including variations |
| """ |
| return keyword_extractor.build_search_terms(keywords) |
|
|