"""Phase W - the lightweight, from-scratch classifier we own. A transparent weighted-lexical scorer: it runs the rule sets in rules.py over a normalized copy of the text and returns a per-category score in [0, 1] (the max weight of any matching signal for that category). It is intentionally simple and dependency-free; the `score()` method is the seam a trained model can later replace without touching any caller. """ from server.safety import rules from server.safety.config import CATEGORIES class LexicalClassifier: """Rule-driven scorer. Swap-compatible with a future learned model.""" def score(self, text: str) -> dict: scores = {c: 0.0 for c in CATEGORIES} norm = rules.normalize(text) if not norm: return scores for rx, weight, cat in rules.RULES: if weight > scores[cat] and rx.search(norm): scores[cat] = weight for cat, a_rx, b_rx, weight in rules.COOCCURRENCE: if weight > scores[cat] and a_rx.search(norm) and b_rx.search(norm): scores[cat] = weight return scores default_classifier = LexicalClassifier()