File size: 9,079 Bytes
e7f1d57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# Dependencies
from typing import Dict
from utils.logger import get_logger
from config.settings import settings
from config.constants import MetricType
from config.constants import SignalStatus
from config.constants import SIGNAL_THRESHOLDS


# Setup Logging
logger = get_logger(__name__)


class ThresholdManager:
    """
    Manage detection thresholds dynamically
    
    Purpose:
    --------
    Allows runtime adjustment of detection thresholds for:
    - A/B testing different sensitivity levels
    - Calibration based on real-world performance
    - Custom thresholds for specific use cases
    - Environment-specific tuning (production vs staging)
    
    Note: Changes are runtime-only and not persisted
    """
    def __init__(self):
        """
        Initialize Threshold Manager with current settings
        """
        self._review_threshold  = settings.REVIEW_THRESHOLD
        self._signal_thresholds = dict(SIGNAL_THRESHOLDS)
        self._metric_weights    = dict(settings.get_metric_weights())
        
        logger.info(f"ThresholdManager initialized: review_threshold={self._review_threshold}")
    

    def get_review_threshold(self) -> float:
        """
        Get current review threshold
        
        Returns:
        --------
            { float } : Current threshold [0.0, 1.0]
        """
        return self._review_threshold
    

    def set_review_threshold(self, new_threshold: float) -> bool:
        """
        Set new review threshold
        
        Arguments:
        ----------
            new_threshold { float } : New threshold value [0.0, 1.0]
        
        Returns:
        --------
            { bool }                : Success status
        """
        if not (0.0 <= new_threshold <= 1.0):
            logger.error(f"Invalid threshold: {new_threshold} (must be between 0.0 and 1.0)")
            return False
        
        old_threshold          = self._review_threshold
        self._review_threshold = new_threshold
        
        logger.info(f"Review threshold changed: {old_threshold:.2f} β†’ {new_threshold:.2f}")
        
        return True
    

    def adjust_sensitivity(self, sensitivity: str) -> bool:
        """
        Adjust sensitivity using preset levels
        
        Arguments:
        ----------
            sensitivity { str } : One of 'conservative', 'balanced', 'aggressive'
        
        Returns:
        --------
            { bool }            : Success status
        """
        presets = {'conservative' : 0.75,  # Fewer false positives, may miss some AI
                   'balanced'     : 0.65,  # Recommended default
                   'aggressive'   : 0.55,  # Catch more AI, more false positives
                  }
        
        if (sensitivity not in presets):
            logger.error(f"Invalid sensitivity: {sensitivity}. Must be one of {list(presets.keys())}")
            return False
        
        new_threshold = presets[sensitivity]
        success       = self.set_review_threshold(new_threshold = new_threshold)
        
        if success:
            logger.info(f"Sensitivity set to '{sensitivity}' (threshold={new_threshold})")
        
        return success
    

    def get_signal_thresholds(self) -> Dict[SignalStatus, float]:
        """
        Get current signal thresholds
        
        Returns:
        --------
            { dict } : Signal status β†’ threshold mapping
        """
        return self._signal_thresholds.copy()
    

    def set_signal_threshold(self, status: SignalStatus, threshold: float) -> bool:
        """
        Set threshold for specific signal status
        
        Arguments:
        ----------
            status    { SignalStatus } : Signal status to modify
            
            threshold { float }        : New threshold [0.0, 1.0]
        
        Returns:
        --------
            { bool }                   : Success status
        """
        if not (0.0 <= threshold <= 1.0):
            logger.error(f"Invalid threshold: {threshold}")
            return False
        
        old_threshold                    = self._signal_thresholds.get(status)
        self._signal_thresholds[status]  = threshold
        
        logger.info(f"Signal threshold for {status.value}: {old_threshold:.2f} β†’ {threshold:.2f}")
        
        return True
    

    def get_metric_weights(self) -> Dict[MetricType, float]:
        """
        Get current metric weights
        
        Returns:
        --------
            { dict } : Metric type β†’ weight mapping
        """
        return self._metric_weights.copy()
    

    def set_metric_weight(self, metric: MetricType, weight: float) -> bool:
        """
        Set weight for specific metric
        
        Arguments:
        ----------
            metric { MetricType } : Metric to modify
            
            weight   { float }    : New weight [0.0, 1.0]
        
        Returns:
        --------
            { bool }              : Success status
        """
        if not (0.0 <= weight <= 1.0):
            logger.error(f"Invalid weight: {weight}")
            return False
        
        old_weight                   = self._metric_weights.get(metric, 0.0)
        self._metric_weights[metric] = weight
        
        # Validate total weight
        total_weight                 = sum(self._metric_weights.values())
        
        if not (0.99 <= total_weight <= 1.01):
            logger.warning(f"Total metric weights = {total_weight:.3f} (should sum to 1.0)")
        
        logger.info(f"Metric weight for {metric.value}: {old_weight:.2f} β†’ {weight:.2f}")
        
        return True
    

    def set_all_metric_weights(self, weights: Dict[MetricType, float]) -> bool:
        """
        Set all metric weights at once (ensures sum = 1.0)
        
        Arguments:
        ----------
            weights { dict } : Complete metric weights mapping
        
        Returns:
        --------
            { bool }         : Success status
        """
        # Validate input
        if (not all(0.0 <= w <= 1.0 for w in weights.values())):
            logger.error("All weights must be between 0.0 and 1.0")
            return False
        
        total_weight         = sum(weights.values())
        
        if not (0.99 <= total_weight <= 1.01):
            logger.error(f"Weights must sum to 1.0, got {total_weight:.3f}")
            return False
        
        self._metric_weights = dict(weights)
        
        logger.info(f"All metric weights updated: {self._metric_weights}")
        
        return True
    

    def get_recommendations(self, score: float) -> Dict[str, str]:
        """
        Get action recommendations based on score
        
        Arguments:
        ----------
            score { float } : Overall suspicion score [0.0, 1.0]
        
        Returns:
        --------
            { dict }        : Recommendation details
        """
        if (score >= 0.85):
            return {"priority"   : "HIGH",
                    "action"     : "Immediate manual verification recommended",
                    "confidence" : "Very high likelihood of AI generation",
                    "next_steps" : "Forensic analysis, reverse image search, metadata inspection",
                   }
        
        elif (score >= 0.70):
            return {"priority"   : "MEDIUM",
                    "action"     : "Manual verification recommended",
                    "confidence" : "High likelihood of AI generation",
                    "next_steps" : "Visual inspection, compare with similar authentic images",
                   }
        
        elif (score >= 0.50):
            return {"priority"   : "LOW",
                    "action"     : "Optional review",
                    "confidence" : "Moderate indicators of AI generation",
                    "next_steps" : "May be heavily edited real photo, check source",
                   }
        
        else:
            return {"priority"   : "NONE",
                    "action"     : "No immediate action needed",
                    "confidence" : "Low likelihood of AI generation",
                    "next_steps" : "Likely authentic, proceed normally",
                   }
    

    def get_current_config(self) -> Dict[str, object]:
        """
        Get complete current configuration
        
        Returns:
        --------
            { dict } : All current threshold and weight settings
        """
        return {"review_threshold"  : self._review_threshold,
                "signal_thresholds" : self._signal_thresholds.copy(),
                "metric_weights"    : self._metric_weights.copy(),
               }
    

    def reset_to_defaults(self) -> None:
        """
        Reset all thresholds to default settings
        """
        self._review_threshold  = settings.REVIEW_THRESHOLD
        self._signal_thresholds = dict(SIGNAL_THRESHOLDS)
        self._metric_weights    = dict(settings.get_metric_weights())
        
        logger.info("All thresholds reset to default values")