fsi-anomaly / research /entitygraph.py
FerrellSyntheticIntelligence's picture
backup all: 100 files (batch)
8b8e59d verified
Raw
History Blame Contribute Delete
4.05 kB
"""Entity relationship graph (journalism suite layer 5).
Deterministic proper-noun extraction + co-occurrence edges. Entities that
share a sentence repeatedly are surfaced as a RELATIONSHIP for the human to
investigate. No model call; no hidden inference. This is the graph half of
"who is connected to whom" the suit materializes while the brain reasons.
Usage:
from research.entitygraph import EntityGraph
g = EntityGraph()
g.add_doc("s1", "The Central Bank met Delta Corp. Delta Corp hired Smith.")
g.report()
"""
import re
from collections import defaultdict
from itertools import combinations
_PHRASE = re.compile(r"\b[A-Z][a-zA-Z]{1,25}(?:\s+[A-Z][a-zA-Z]{1,25}){0,3}\b")
_ORG_SUFFIX = re.compile(r"\b(?:Inc|Corp|Corporation|Ltd|Agency|Department|"
r"Committee|Commission|University|Institute|Bureau|"
r"Administration|Bank|Fund|Office|Council|Force|"
r"Group|Industries|Systems|Media|News|Post|Times)\b")
_STOP = {"The", "This", "That", "These", "Those", "A", "An", "One", "Two",
"Mr", "Mrs", "Ms", "Dr", "January", "February", "March", "April",
"May", "June", "July", "August", "September", "October", "November",
"December", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
"Saturday", "Sunday"}
class EntityGraph:
def __init__(self):
self.docs = {} # source_id -> text
self.sentences = [] # (source_id, sentence_text)
def add_doc(self, source_id, text):
self.docs[source_id] = text
for s in re.split(r"(?<=[.!?])\s+", text):
s = s.strip()
if s:
self.sentences.append((source_id, s))
def extract(self, text):
"""Proper-noun phrases; drop stop-word-led matches (deterministic)."""
out = []
for m in _PHRASE.finditer(text):
p = m.group(0).strip()
if p.split()[0] in _STOP:
continue
out.append(p)
return sorted(set(out))
def _nodes(self):
nodes = defaultdict(int)
for _, sent in self.sentences:
for e in self.extract(sent):
nodes[e] += 1
return nodes
def edges(self, min_cooccur=2):
"""Entity pairs sharing a sentence; weight = co-occurrence count."""
pair_w = defaultdict(int)
for _, sent in self.sentences:
ents = sorted(set(self.extract(sent)))
for a, b in combinations(ents, 2):
pair_w[(a, b)] += 1
return {k: v for k, v in pair_w.items() if v >= min_cooccur}
def central(self, top=10):
"""Degree centrality: entities with most distinct graph neighbors."""
deg = defaultdict(int)
for (a, b) in self.edges():
deg[a] += 1
deg[b] += 1
return sorted(deg.items(), key=lambda kv: -kv[1])[:top]
def report(self, min_cooccur=2):
lines = ["# Entity Relationship Graph", ""]
lines.append("## Entities (mentions)")
nodes = self._nodes()
for e, n in sorted(nodes.items(), key=lambda kv: -kv[1])[:30]:
lines.append(f"- {e}: x{n}")
lines.append("")
lines.append("## Relationships (co-occurrence)")
edges = self.edges(min_cooccur)
for (a, b), w in sorted(edges.items(), key=lambda kv: -kv[1])[:25]:
lines.append(f"- {a} <-> {b} (x{w})")
if not edges:
lines.append(f"- none above min_cooccur={min_cooccur}")
lines.append("")
lines.append("## Central entities (degree)")
for e, d in self.central():
lines.append(f"- {e}: {d} neighbors")
return "\n".join(lines)
def to_dot(self, min_cooccur=2):
lines = ["digraph entities {"]
for e in self._nodes():
lines.append(f' "{e}";')
for (a, b), w in self.edges(min_cooccur).items():
lines.append(f' "{a}" -> "{b}" [label="{w}"];')
lines.append("}")
return "\n".join(lines)