clanker / engine /zones_impl.py
deucebucket's picture
feat: vendor engine + scorer adapter (text -> Score)
6522c75
Raw
History Blame Contribute Delete
8.64 kB
"""Emotional Zone Classification β€” match VADUGWI to named emotional states.
Instead of raw V thresholds, classify by which ZONE the coordinates
land in. Different sentences β†’ same zone β†’ same emotional state.
The zones are convergence regions: areas in 7D VADUGWI space where
structurally different sentences resolve to the same emotional meaning.
"Whatever" β†’ RESIGNATION zone
"I give up" β†’ RESIGNATION zone
"Fine do what you want" β†’ RESIGNATION zone
Different words. Same zone. Same state.
Usage:
from engine.zones import ZoneClassifier
zc = ZoneClassifier()
result = zc.classify(vadug)
print(result.zone) # "RESIGNATION"
print(result.confidence) # 0.85
"""
import json
import os
from dataclasses import dataclass
from typing import List, Tuple
from .shared import VADUG
@dataclass
class ZoneResult:
"""Which emotional zone a VADUGWI state lands in."""
zone: str # JOY, RAGE, GRIEF, RESIGNATION, etc.
confidence: float # 0.0-1.0 how clearly it falls in this zone
distance: float # distance to zone center (lower = better match)
alternatives: list # other zones it's close to, sorted by distance
# Zone definitions: center point + radius for each dimension
# Derived from convergence analysis of real sentence clusters
ZONES = {
"JOY": {
"center": {"v": 156, "d": 146, "g": 137},
"radius": {"v": 30, "d": 20, "g": 10},
"description": "high V, high D (agency), light G",
},
"RAGE": {
"center": {"v": 77, "d": 175, "g": 160},
"radius": {"v": 35, "d": 45, "g": 30},
"description": "low V, VERY high D (anger IS power), high G",
},
"GRIEF": {
"center": {"v": 105, "d": 100, "g": 113},
"radius": {"v": 25, "d": 25, "g": 15},
"description": "moderate-low V, low D (helpless), heavy G",
},
"RESIGNATION": {
"center": {"v": 120, "d": 117, "g": 124},
"radius": {"v": 15, "d": 10, "g": 6},
"description": "near-neutral V, consistently low D",
},
"ANXIETY": {
"center": {"v": 101, "d": 93, "g": 134},
"radius": {"v": 30, "d": 35, "g": 25},
"description": "low V, low D, HIGH G (ungrounded/floating)",
},
"CRISIS": {
"center": {"v": 81, "d": 82, "g": 89},
"radius": {"v": 35, "d": 35, "g": 30},
"description": "low everything β€” V, D, G all sinking",
},
"DEFLECTION": {
"center": {"v": 124, "d": 122, "g": 128},
"radius": {"v": 5, "d": 10, "g": 3},
"description": "near-neutral EVERYTHING (the mask)",
},
"EMPOWERMENT": {
"center": {"v": 149, "d": 131, "g": 131},
"radius": {"v": 30, "d": 25, "g": 8},
"description": "high V + moderate-high D (agency)",
},
"NEUTRAL": {
"center": {"v": 128, "d": 128, "g": 128},
"radius": {"v": 8, "d": 8, "g": 8},
"description": "dead center β€” no signal",
},
}
class ZoneClassifier:
"""Classify VADUGWI coordinates into named emotional zones."""
def __init__(self):
self.zones = ZONES
# Structure patterns that override zone classification
_CRISIS_PATTERNS = {
"SELF_REMOVAL", "NO_EXIT", "SELF_NULLIFY", "METHOD_ACQUISITION",
"SUSPICIOUS_CALM", "BLANKET_APOLOGY", "FAREWELL",
}
_NEGATIVE_PATTERNS = {
"EXHAUSTION", "BETRAYAL", "VICTIMIZATION", "SARCASM_INVERSION",
"BRAVADO", "CALLING_OUT", "DIRECTED_POSITIVE", "MINIMIZER",
"EXCLUDED_POSITIVE", "POWER_OVER_SELF",
}
def classify(self, vadug: VADUG, structures=None) -> ZoneResult:
"""Find the closest emotional zone for a VADUGWI coordinate.
Uses weighted Euclidean distance normalized by zone radius.
Structures override when crisis or strong negative patterns fire.
"""
# Structure override: if crisis pattern fires, force CRISIS zone
if structures:
pattern_names = {s.pattern for s in structures}
crisis_hit = pattern_names & self._CRISIS_PATTERNS
if crisis_hit:
return ZoneResult(
zone="CRISIS",
confidence=0.85,
distance=0.5,
alternatives=[("ANXIETY", 1.0), ("GRIEF", 1.2)],
)
neg_hit = pattern_names & self._NEGATIVE_PATTERNS
if neg_hit and vadug.v < 135:
# Negative structure fired + V below positive threshold
# Don't let it land in JOY/EMPOWERMENT
pass # fall through to distance calc but we'll bias below
distances = []
for zone_name, zone in self.zones.items():
c = zone["center"]
r = zone["radius"]
# Normalized distance: how many radii away from center
dv = abs(vadug.v - c["v"]) / max(r["v"], 1)
dd = abs(vadug.d - c["d"]) / max(r["d"], 1)
dg = abs(vadug.g - c["g"]) / max(r["g"], 1)
# Weighted: V matters most, then D, then G
dist = (dv * 0.4 + dd * 0.35 + dg * 0.25)
# Penalize positive zones when negative structures fire
if structures:
pattern_names = {s.pattern for s in structures}
neg_hit = pattern_names & self._NEGATIVE_PATTERNS
if neg_hit and zone_name in ("JOY", "EMPOWERMENT", "NEUTRAL"):
dist += 2.0 # push away from positive zones
distances.append((zone_name, dist))
# Sort by distance (closest first)
distances.sort(key=lambda x: x[1])
best_zone, best_dist = distances[0]
# Confidence: inverse of distance, clamped to 0-1
confidence = max(0.0, min(1.0, 1.0 - best_dist * 0.4))
# Alternatives: next closest zones
alternatives = [(name, round(dist, 2)) for name, dist in distances[1:4]]
return ZoneResult(
zone=best_zone,
confidence=round(confidence, 2),
distance=round(best_dist, 2),
alternatives=alternatives,
)
def classify_cascading(self, vadug: VADUG) -> ZoneResult:
"""Cascading classification β€” precision first, then coverage.
Level 1: Strong zone match (distance < 1.0) β†’ high confidence
Level 2: Near zone boundary (1.0-1.5) β†’ medium confidence, check alternatives
Level 3: No clear zone β†’ return closest with low confidence + alternatives
This gives precision when the signal is clear and coverage
when it's ambiguous β€” without sacrificing either.
"""
result = self.classify(vadug)
if result.distance < 1.0:
# Strong match β€” high confidence
return result
if result.distance < 1.5:
# Near boundary β€” check if alternatives are close
if result.alternatives and result.alternatives[0][1] < 1.0:
# Alternative is also close β€” ambiguous, report both
alt_name = result.alternatives[0][0]
result.zone = f"{result.zone}/{alt_name}"
result.confidence = max(0.0, result.confidence - 0.15)
return result
# No clear zone β€” low confidence
result.confidence = max(0.0, result.confidence - 0.3)
return result
def is_negative_zone(self, zone: str, mode: str = "balanced") -> bool:
"""Check if a zone is negative, with configurable strictness.
Modes:
strict: only CRISIS (highest precision, lowest recall)
balanced: CRISIS + GRIEF (best accuracy)
broad: CRISIS + GRIEF + RESIGNATION + ANXIETY (high recall)
safety: everything except JOY, EMPOWERMENT, NEUTRAL (max recall)
"""
strict = {"CRISIS"}
balanced = {"CRISIS", "GRIEF"}
broad = {"CRISIS", "GRIEF", "RESIGNATION", "ANXIETY"}
safety = {"CRISIS", "GRIEF", "RESIGNATION", "ANXIETY", "RAGE", "DEFLECTION"}
zones_map = {
"strict": strict,
"balanced": balanced,
"broad": broad,
"safety": safety,
}
check_zones = zones_map.get(mode, balanced)
# Handle cascading dual-zone labels like "CRISIS/GRIEF"
for part in zone.split("/"):
if part in check_zones:
return True
return False
def describe(self, zone_name: str) -> str:
"""Get the description of a zone."""
if zone_name in self.zones:
return self.zones[zone_name]["description"]
return "unknown zone"