File size: 2,793 Bytes
e4d7d50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
"""
ChessEcon Backend — Position Complexity Analyzer
Decides when a position is complex enough to warrant calling Claude.
Claude is only called when ALL three gates pass:
  1. Position complexity >= threshold
  2. Agent wallet >= minimum
  3. Agent's own policy requests coaching
"""
from __future__ import annotations
import os
from shared.models import ComplexityAnalysis, PositionComplexity


THRESHOLD_COMPLEX  = float(os.getenv("COMPLEXITY_THRESHOLD_COMPLEX", "0.45"))
THRESHOLD_CRITICAL = float(os.getenv("COMPLEXITY_THRESHOLD_CRITICAL", "0.70"))


class ComplexityAnalyzer:

    def analyze(self, features: dict) -> ComplexityAnalysis:
        """
        Compute a 0–1 complexity score from raw board features.
        Higher = more complex = more likely Claude is useful.
        """
        score = 0.0
        factors: dict = {}

        # Factor 1: Number of legal moves (high = complex position)
        num_moves = features.get("num_legal_moves", 20)
        move_score = min(num_moves / 60.0, 1.0)
        factors["mobility"] = round(move_score, 3)
        score += move_score * 0.30

        # Factor 2: Check pressure
        check_score = 0.8 if features.get("is_check") else 0.0
        factors["check_pressure"] = check_score
        score += check_score * 0.20

        # Factor 3: Tactical captures available
        capture_score = 0.6 if features.get("has_captures") else 0.0
        factors["captures_available"] = capture_score
        score += capture_score * 0.15

        # Factor 4: Endgame (few pieces = precise calculation needed)
        num_pieces = features.get("num_pieces", 32)
        endgame_score = max(0.0, (16 - num_pieces) / 16.0)
        factors["endgame_pressure"] = round(endgame_score, 3)
        score += endgame_score * 0.20

        # Factor 5: Material imbalance (unbalanced = harder to evaluate)
        material = abs(features.get("material_balance", 0.0))
        imbalance_score = min(material / 9.0, 1.0)  # queen = 9
        factors["material_imbalance"] = round(imbalance_score, 3)
        score += imbalance_score * 0.15

        score = round(min(score, 1.0), 4)

        if score >= THRESHOLD_CRITICAL:
            level = PositionComplexity.CRITICAL
        elif score >= THRESHOLD_COMPLEX:
            level = PositionComplexity.COMPLEX
        elif score >= 0.25:
            level = PositionComplexity.MODERATE
        else:
            level = PositionComplexity.SIMPLE

        recommend = level in (PositionComplexity.COMPLEX, PositionComplexity.CRITICAL)

        return ComplexityAnalysis(
            fen=features.get("fen", ""),
            score=score,
            level=level,
            factors=factors,
            recommend_coaching=recommend,
        )


# Singleton
complexity_analyzer = ComplexityAnalyzer()