Spaces:
Sleeping
Sleeping
| """ | |
| Entity Extraction Engine | |
| ======================== | |
| Rule-based NER (Named Entity Recognition) using regex pattern matching. | |
| Extracts PERSON, ORG, LOCATION, DATE, and TECHNOLOGY entities from text, | |
| then infers relationships via sentence-level co-occurrence. | |
| """ | |
| import re | |
| from typing import List, Dict, Tuple | |
| # --------------------------------------------------------------------------- | |
| # Pattern banks – curated regex patterns for each entity type | |
| # --------------------------------------------------------------------------- | |
| PERSON_PATTERNS = [ | |
| # Titles followed by capitalized names | |
| r"(?:Dr|Prof|Mr|Mrs|Ms|Sir|Lord|President|CEO|CTO|Director)\.\s+[A-Z][a-z]+(?:\s+[A-Z][a-z]+)+", | |
| # Common well-known names (seed list) | |
| r"\b(?:Elon Musk|Jeff Bezos|Sam Altman|Demis Hassabis|Yann LeCun|Geoffrey Hinton|" | |
| r"Fei-Fei Li|Andrew Ng|Ilya Sutskever|Jensen Huang|Satya Nadella|Tim Cook|" | |
| r"Mark Zuckerberg|Sundar Pichai|Dario Amodei|Andrej Karpathy|" | |
| r"Alan Turing|Ada Lovelace|John von Neumann|Claude Shannon|" | |
| r"Albert Einstein|Isaac Newton|Marie Curie|Nikola Tesla|" | |
| r"Napoleon Bonaparte|Winston Churchill|Abraham Lincoln|Mahatma Gandhi|" | |
| r"Alexander Hamilton|Thomas Jefferson|Benjamin Franklin|George Washington|" | |
| r"Leonardo da Vinci|Galileo Galilei|Charles Darwin|Stephen Hawking)\b", | |
| # Two or three capitalized words that look like person names | |
| r"\b[A-Z][a-z]{2,15}\s+(?:[A-Z]\.\s+)?[A-Z][a-z]{2,15}\b", | |
| ] | |
| ORG_PATTERNS = [ | |
| r"\b(?:Google|Microsoft|Apple|Amazon|Meta|OpenAI|DeepMind|Anthropic|Tesla|" | |
| r"NVIDIA|IBM|Intel|AMD|Qualcomm|Samsung|TSMC|Oracle|Salesforce|Adobe|" | |
| r"Netflix|Spotify|Twitter|LinkedIn|GitHub|Stack Overflow|" | |
| r"MIT|Stanford|Harvard|Oxford|Cambridge|Berkeley|Carnegie Mellon|" | |
| r"NASA|CERN|WHO|UNESCO|United Nations|European Union|" | |
| r"IEEE|ACM|NeurIPS|ICML|ICLR|AAAI|CVPR|" | |
| r"Goldman Sachs|JPMorgan|Morgan Stanley|BlackRock)\b", | |
| r"\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\s+(?:Inc|Corp|Ltd|LLC|Group|Foundation|" | |
| r"Institute|University|Laboratory|Labs|Research|Association|Organization)\b", | |
| r"\b(?:University|Institute|Academy)\s+of\s+[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b", | |
| ] | |
| LOCATION_PATTERNS = [ | |
| r"\b(?:New York|San Francisco|Silicon Valley|Los Angeles|Chicago|Boston|Seattle|" | |
| r"Washington D\.C\.|London|Paris|Berlin|Tokyo|Beijing|Shanghai|Mumbai|" | |
| r"Bangalore|Toronto|Montreal|Sydney|Singapore|Hong Kong|Dubai|" | |
| r"California|Texas|Massachusetts|Virginia|" | |
| r"United States|United Kingdom|China|India|Japan|Germany|France|Canada|" | |
| r"Australia|South Korea|Israel|Switzerland|" | |
| r"Europe|Asia|North America|South America|Africa)\b", | |
| ] | |
| DATE_PATTERNS = [ | |
| # Full dates | |
| r"\b(?:January|February|March|April|May|June|July|August|September|" | |
| r"October|November|December)\s+\d{1,2},?\s+\d{4}\b", | |
| # Month Year | |
| r"\b(?:January|February|March|April|May|June|July|August|September|" | |
| r"October|November|December)\s+\d{4}\b", | |
| # Year ranges & standalone years | |
| r"\b(?:19|20)\d{2}[-–]\d{2,4}\b", | |
| r"\b(?:19|20)\d{2}s?\b", | |
| # Relative dates | |
| r"\b(?:Q[1-4]\s+\d{4})\b", | |
| ] | |
| TECHNOLOGY_PATTERNS = [ | |
| r"\b(?:GPT-[0-9]+|GPT|BERT|Transformer|LLM|LLMs|DALL[-·]E|Stable Diffusion|" | |
| r"ChatGPT|Copilot|AlphaFold|AlphaGo|" | |
| r"Python|JavaScript|TypeScript|Rust|Go|Java|C\+\+|SQL|" | |
| r"TensorFlow|PyTorch|Keras|scikit-learn|Hugging Face|LangChain|" | |
| r"Kubernetes|Docker|AWS|Azure|GCP|" | |
| r"blockchain|quantum computing|machine learning|deep learning|" | |
| r"artificial intelligence|natural language processing|NLP|" | |
| r"computer vision|reinforcement learning|neural network|neural networks|" | |
| r"convolutional neural network|CNN|RNN|LSTM|GAN|GANs|" | |
| r"large language model|retrieval-augmented generation|RAG|" | |
| r"knowledge graph|attention mechanism|self-attention)\b", | |
| ] | |
| # Map label -> compiled patterns | |
| ENTITY_PATTERNS: Dict[str, List[re.Pattern]] = { | |
| "TECHNOLOGY": [re.compile(p, re.IGNORECASE) for p in TECHNOLOGY_PATTERNS], | |
| "ORG": [re.compile(p) for p in ORG_PATTERNS], | |
| "LOCATION": [re.compile(p) for p in LOCATION_PATTERNS], | |
| "DATE": [re.compile(p) for p in DATE_PATTERNS], | |
| "PERSON": [re.compile(p) for p in PERSON_PATTERNS], | |
| } | |
| # Words that should never be tagged as PERSON | |
| PERSON_STOPWORDS = { | |
| "The", "This", "That", "These", "Those", "Here", "There", | |
| "However", "Moreover", "Furthermore", "Although", "Because", | |
| "While", "During", "After", "Before", "Since", "Within", | |
| "Between", "Through", "About", "Their", "Where", "Which", | |
| "Every", "Other", "Another", "First", "Second", "Third", | |
| "Many", "Most", "Some", "Such", "Each", "Both", "Several", | |
| "Recent", "Major", "Large", "Small", "High", "Early", "Late", | |
| "With", "From", "Into", "Over", "Under", "Also", "Just", | |
| "More", "Very", "Much", "Well", "Even", "Still", "Already", | |
| "Knowledge Graph", "Construction", "Reasoning", "Engine", | |
| "Research", "Development", "Analysis", "Processing", "Learning", | |
| } | |
| class EntityExtractor: | |
| """ | |
| Rule-based Named Entity Recognition engine. | |
| Uses curated regex patterns to identify entities in text without | |
| requiring large spaCy model downloads. | |
| """ | |
| def __init__(self): | |
| self.patterns = ENTITY_PATTERNS | |
| # ------------------------------------------------------------------ | |
| # Public API | |
| # ------------------------------------------------------------------ | |
| def extract(self, text: str) -> List[Dict]: | |
| """ | |
| Extract named entities from *text*. | |
| Returns a list of dicts: | |
| [{"text": ..., "label": ..., "start": ..., "end": ...}, ...] | |
| """ | |
| raw_entities: List[Dict] = [] | |
| for label, compiled_patterns in self.patterns.items(): | |
| for pattern in compiled_patterns: | |
| for match in pattern.finditer(text): | |
| entity_text = match.group().strip() | |
| # Filter noisy PERSON matches | |
| if label == "PERSON" and entity_text in PERSON_STOPWORDS: | |
| continue | |
| if label == "PERSON" and len(entity_text.split()) < 2: | |
| continue | |
| raw_entities.append({ | |
| "text": entity_text, | |
| "label": label, | |
| "start": match.start(), | |
| "end": match.end(), | |
| }) | |
| # Deduplicate overlapping spans (prefer longer matches) | |
| entities = self._resolve_overlaps(raw_entities) | |
| return entities | |
| def extract_relationships( | |
| self, text: str, entities: List[Dict] | None = None | |
| ) -> List[Dict]: | |
| """ | |
| Infer relationships between entities via sentence co-occurrence. | |
| Returns a list of dicts: | |
| [{"source": ..., "target": ..., "relation": ..., "sentence": ...}, ...] | |
| """ | |
| if entities is None: | |
| entities = self.extract(text) | |
| sentences = self._split_sentences(text) | |
| relationships: List[Dict] = [] | |
| seen: set = set() | |
| for sentence in sentences: | |
| # Find entities present in this sentence | |
| present = [ | |
| e for e in entities | |
| if e["text"] in sentence | |
| ] | |
| for i, src in enumerate(present): | |
| for tgt in present[i + 1:]: | |
| key = (src["text"], tgt["text"]) | |
| if key in seen: | |
| continue | |
| seen.add(key) | |
| relation = self._infer_relation(src, tgt, sentence) | |
| relationships.append({ | |
| "source": src["text"], | |
| "target": tgt["text"], | |
| "source_label": src["label"], | |
| "target_label": tgt["label"], | |
| "relation": relation, | |
| "sentence": sentence.strip(), | |
| }) | |
| return relationships | |
| # ------------------------------------------------------------------ | |
| # Internal helpers | |
| # ------------------------------------------------------------------ | |
| def _resolve_overlaps(entities: List[Dict]) -> List[Dict]: | |
| """Keep the longest span when two entities overlap.""" | |
| # Sort by start, then by descending length | |
| entities.sort(key=lambda e: (e["start"], -(e["end"] - e["start"]))) | |
| result: List[Dict] = [] | |
| last_end = -1 | |
| for ent in entities: | |
| if ent["start"] >= last_end: | |
| result.append(ent) | |
| last_end = ent["end"] | |
| return result | |
| def _split_sentences(text: str) -> List[str]: | |
| """Naive sentence splitter.""" | |
| return re.split(r"(?<=[.!?])\s+", text) | |
| def _infer_relation(src: Dict, tgt: Dict, sentence: str) -> str: | |
| """Heuristic relation labelling based on entity types and context.""" | |
| pair = (src["label"], tgt["label"]) | |
| # Keyword-based relation detection | |
| s_lower = sentence.lower() | |
| if any(kw in s_lower for kw in ["founded", "co-founded", "started", "created"]): | |
| if pair in [("PERSON", "ORG"), ("PERSON", "TECHNOLOGY")]: | |
| return "FOUNDED" | |
| if any(kw in s_lower for kw in ["acquired", "bought", "purchased", "merged"]): | |
| return "ACQUIRED" | |
| if any(kw in s_lower for kw in ["works at", "joined", "hired", "employed"]): | |
| return "WORKS_AT" | |
| if any(kw in s_lower for kw in ["located in", "based in", "headquartered"]): | |
| return "LOCATED_IN" | |
| if any(kw in s_lower for kw in ["developed", "built", "designed", "invented"]): | |
| return "DEVELOPED" | |
| if any(kw in s_lower for kw in ["published", "released", "announced", "launched"]): | |
| return "RELEASED" | |
| if any(kw in s_lower for kw in ["uses", "using", "powered by", "built on", "leverages"]): | |
| return "USES" | |
| if any(kw in s_lower for kw in ["competed", "versus", "rivaling", "competing"]): | |
| return "COMPETES_WITH" | |
| if any(kw in s_lower for kw in ["collaborated", "partnered", "partnership"]): | |
| return "COLLABORATES_WITH" | |
| if any(kw in s_lower for kw in ["invested", "funding", "backed"]): | |
| return "INVESTED_IN" | |
| # Fallback: type-pair heuristics | |
| relation_map = { | |
| ("PERSON", "ORG"): "AFFILIATED_WITH", | |
| ("PERSON", "TECHNOLOGY"): "WORKS_ON", | |
| ("PERSON", "LOCATION"): "LOCATED_IN", | |
| ("ORG", "TECHNOLOGY"): "DEVELOPS", | |
| ("ORG", "LOCATION"): "LOCATED_IN", | |
| ("ORG", "ORG"): "RELATED_TO", | |
| ("TECHNOLOGY", "TECHNOLOGY"): "RELATED_TO", | |
| ("PERSON", "PERSON"): "ASSOCIATED_WITH", | |
| ("PERSON", "DATE"): "ACTIVE_IN", | |
| ("ORG", "DATE"): "ACTIVE_IN", | |
| ("TECHNOLOGY", "DATE"): "EMERGED_IN", | |
| } | |
| return relation_map.get(pair, relation_map.get((tgt["label"], src["label"]), "RELATED_TO")) | |