"""Metadata/tag routing; lexical stand-in for a fast-model relevance router.

Only tags and summaries are scored. Bodies are not used by the router.
Exact tag pruning intentionally exposes a recall limitation; see README.
"""
import re
from memory_index import build_index, sample_memories


def terms(text):
    return set(re.findall(r"[a-z0-9]+", text.lower()))


def metadata_score(query, memory):
    wanted = terms(query)
    available = set(memory.tags) | terms(memory.summary)
    return len(wanted & available) / max(1, len(wanted))


def route(root, query, limit=8):
    if limit < 1:
        raise ValueError("limit must be positive")
    wanted = terms(query)
    candidates = []
    pending = [root]
    while pending:
        node = pending.pop()
        if not wanted.intersection(node.tags):
            continue
        candidates.extend(m for m in node.objects if metadata_score(query, m) > 0)
        pending.extend(node.children.values())
    return sorted(candidates, key=lambda m: (-metadata_score(query, m), m.id))[:limit]


if __name__ == "__main__":
    for memory in route(build_index(sample_memories()), "atlas launch"):
        print(memory.id, metadata_score("atlas launch", memory), memory.summary)
