|
29 | 29 | from .config import MempalaceConfig, normalize_wing_name |
30 | 30 | from .knowledge_graph import KnowledgeGraph |
31 | 31 | from .palace_graph import build_graph as _build_graph |
| 32 | +from .hallways import list_hallways |
32 | 33 |
|
33 | 34 | logger = logging.getLogger("mempalace_gossip") |
34 | 35 |
|
@@ -372,18 +373,83 @@ def detect_topic(self, text: str, priority: Optional[str] = None) -> tuple[str, |
372 | 373 | return "general", priority |
373 | 374 | return "general", "normal" |
374 | 375 |
|
| 376 | + def _get_hallway_context( |
| 377 | + self, message: GossipMessage |
| 378 | + ) -> tuple[dict[str, float], set[str]]: |
| 379 | + """Return (hall_scores, related_entities) derived from within-wing hallways. |
| 380 | +
|
| 381 | + Hallways are entity-pair co-occurrence records built at mine time. When a |
| 382 | + gossip message mentions an entity that co-occurs with other entities in |
| 383 | + the source wing, we use those co-occurrences to boost chatter nodes that |
| 384 | + live in the same rooms/halls or cover the related entities. |
| 385 | + """ |
| 386 | + if not message.source_wing: |
| 387 | + return {}, set() |
| 388 | + try: |
| 389 | + hallways = list_hallways( |
| 390 | + wing=message.source_wing, config=self.mempalace_config |
| 391 | + ) |
| 392 | + except Exception: |
| 393 | + logger.debug("gossip: could not load hallways", exc_info=True) |
| 394 | + return {}, set() |
| 395 | + |
| 396 | + entities = {message.subject.lower(), message.obj.lower()} |
| 397 | + hall_scores: dict[str, float] = {} |
| 398 | + related: set[str] = set() |
| 399 | + |
| 400 | + for h in hallways: |
| 401 | + a = (h.get("entity_a") or "").lower() |
| 402 | + b = (h.get("entity_b") or "").lower() |
| 403 | + if a in entities or b in entities: |
| 404 | + other = b if a in entities else a |
| 405 | + related.add(other) |
| 406 | + count = h.get("co_occurrence_count") or 1 |
| 407 | + for room in h.get("rooms") or []: |
| 408 | + room_key = room.lower() |
| 409 | + # Accumulate a small boost per co-occurrence in this room. |
| 410 | + hall_scores[room_key] = hall_scores.get(room_key, 0.0) + min( |
| 411 | + 0.15, 0.05 + count * 0.01 |
| 412 | + ) |
| 413 | + |
| 414 | + return hall_scores, related |
| 415 | + |
375 | 416 | def select_chatter_nodes( |
376 | 417 | self, |
377 | 418 | message: GossipMessage, |
378 | 419 | fanout: Optional[int] = None, |
379 | 420 | ) -> list[ChatterNode]: |
380 | | - """Rank and select chatter nodes for a given message.""" |
| 421 | + """Rank and select chatter nodes for a given message. |
| 422 | +
|
| 423 | + Selection combines the base specialty/topic score with within-wing |
| 424 | + hallway context: chatter nodes in the source wing whose hall/room |
| 425 | + appears in co-occurrence records for the subject/object get a boost, as |
| 426 | + do nodes whose specialties overlap with related entities. |
| 427 | + """ |
381 | 428 | fanout = fanout if fanout is not None else self.config.get("fanout", 5) |
382 | | - scored = [ |
383 | | - (node, node.match_score(message.text, message.source_wing)) |
384 | | - for node in self._chatter_nodes |
385 | | - ] |
386 | | - scored = [(n, s) for n, s in scored if s > 0] |
| 429 | + hall_scores, related_entities = self._get_hallway_context(message) |
| 430 | + |
| 431 | + scored = [] |
| 432 | + for node in self._chatter_nodes: |
| 433 | + score = node.match_score(message.text, message.source_wing) |
| 434 | + |
| 435 | + # Hallway-aware boosts (only for nodes in the source wing). |
| 436 | + if message.source_wing and normalize_wing_name( |
| 437 | + message.source_wing |
| 438 | + ) == normalize_wing_name(node.wing): |
| 439 | + if node.hall and node.hall.lower() in hall_scores: |
| 440 | + score += hall_scores[node.hall.lower()] |
| 441 | + |
| 442 | + # Also boost if a related entity matches a specialty. |
| 443 | + if related_entities and node.specialties: |
| 444 | + overlaps = related_entities & { |
| 445 | + s.lower() for s in node.specialties |
| 446 | + } |
| 447 | + score += min(0.3, len(overlaps) * 0.1) |
| 448 | + |
| 449 | + score = max(0.0, min(2.0, score)) |
| 450 | + if score > 0: |
| 451 | + scored.append((node, score)) |
| 452 | + |
387 | 453 | scored.sort(key=lambda x: x[1], reverse=True) |
388 | 454 | return [n for n, _ in scored[:fanout]] |
389 | 455 |
|
|
0 commit comments