Coverage for src / local_deep_research / advanced_search_system / constraint_checking / strict_checker.py: 10%

78 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2026-01-11 00:51 +0000

1""" 

2Strict constraint checker - example of creating a custom variant. 

3 

4This implementation is very strict about constraint satisfaction, 

5requiring high confidence for all constraints. 

6""" 

7 

8from typing import Dict, List, Tuple 

9 

10from loguru import logger 

11 

12from ..candidates.base_candidate import Candidate 

13from ..constraints.base_constraint import Constraint, ConstraintType 

14from .base_constraint_checker import ( 

15 BaseConstraintChecker, 

16 ConstraintCheckResult, 

17) 

18 

19 

20class StrictChecker(BaseConstraintChecker): 

21 """ 

22 Strict constraint checker that requires high confidence for all constraints. 

23 

24 This is an example of how to create custom constraint checking variants 

25 by inheriting from BaseConstraintChecker. 

26 """ 

27 

28 def __init__( 

29 self, 

30 *args, 

31 strict_threshold: float = 0.9, # Very high threshold 

32 name_pattern_required: bool = True, # NAME_PATTERN constraints are mandatory 

33 **kwargs, 

34 ): 

35 """ 

36 Initialize strict checker. 

37 

38 Args: 

39 strict_threshold: Very high threshold for all constraints 

40 name_pattern_required: Whether NAME_PATTERN constraints are mandatory 

41 """ 

42 super().__init__(*args, **kwargs) 

43 self.strict_threshold = strict_threshold 

44 self.name_pattern_required = name_pattern_required 

45 

46 def check_candidate( 

47 self, candidate: Candidate, constraints: List[Constraint] 

48 ) -> ConstraintCheckResult: 

49 """Check candidate with strict requirements.""" 

50 logger.info(f"Checking candidate: {candidate.name} (strict mode)") 

51 

52 constraint_scores = {} 

53 detailed_results = [] 

54 rejection_reason = None 

55 should_reject = False 

56 

57 for constraint in constraints: 

58 # Gather evidence 

59 evidence_list = self._gather_evidence_for_constraint( 

60 candidate, constraint 

61 ) 

62 

63 # Calculate score 

64 score = self._evaluate_constraint_strictly( 

65 candidate, constraint, evidence_list 

66 ) 

67 

68 # Check for rejection 

69 reject, reason = self.should_reject_candidate( 

70 candidate, constraint, evidence_list 

71 ) 

72 

73 if reject and not should_reject: 

74 should_reject = True 

75 rejection_reason = reason 

76 

77 # Store results 

78 constraint_scores[constraint.value] = { 

79 "total": score, 

80 "strict_pass": score >= self.strict_threshold, 

81 "weight": constraint.weight, 

82 } 

83 

84 detailed_results.append( 

85 { 

86 "constraint": constraint.value, 

87 "score": score, 

88 "strict_pass": score >= self.strict_threshold, 

89 "weight": constraint.weight, 

90 "type": constraint.type.value, 

91 } 

92 ) 

93 

94 self._log_constraint_result(candidate, constraint, score, {}) 

95 

96 # Calculate total score 

97 if should_reject: 

98 total_score = 0.0 

99 else: 

100 # All constraints must pass strict threshold 

101 all_pass = all(r["strict_pass"] for r in detailed_results) 

102 total_score = 1.0 if all_pass else 0.0 

103 

104 logger.info( 

105 f"Strict evaluation for {candidate.name}: {'PASS' if total_score > 0 else 'FAIL'}" 

106 ) 

107 

108 return ConstraintCheckResult( 

109 candidate=candidate, 

110 total_score=total_score, 

111 constraint_scores=constraint_scores, 

112 should_reject=should_reject, 

113 rejection_reason=rejection_reason, 

114 detailed_results=detailed_results, 

115 ) 

116 

117 def should_reject_candidate( 

118 self, 

119 candidate: Candidate, 

120 constraint: Constraint, 

121 evidence_data: List[Dict], 

122 ) -> Tuple[bool, str]: 

123 """Strict rejection rules.""" 

124 if not evidence_data: 

125 return True, f"No evidence for constraint '{constraint.value}'" 

126 

127 score = self._evaluate_constraint_strictly( 

128 candidate, constraint, evidence_data 

129 ) 

130 

131 # Special handling for NAME_PATTERN constraints 

132 if ( 

133 constraint.type == ConstraintType.NAME_PATTERN 

134 and self.name_pattern_required 

135 ): 

136 if score < 0.95: # Even stricter for name patterns 

137 return ( 

138 True, 

139 f"NAME_PATTERN constraint '{constraint.value}' failed strict evaluation", 

140 ) 

141 

142 # General strict threshold 

143 if score < self.strict_threshold: 

144 return ( 

145 True, 

146 f"Constraint '{constraint.value}' below strict threshold ({score:.0%})", 

147 ) 

148 

149 return False, "" 

150 

151 def _evaluate_constraint_strictly( 

152 self, 

153 candidate: Candidate, 

154 constraint: Constraint, 

155 evidence_list: List[Dict], 

156 ) -> float: 

157 """Evaluate constraint with strict criteria.""" 

158 if not evidence_list: 

159 return 0.0 

160 

161 # For NAME_PATTERN constraints, use direct name checking 

162 if constraint.type == ConstraintType.NAME_PATTERN: 

163 return self._check_name_pattern_strictly( 

164 candidate.name, constraint.value 

165 ) 

166 

167 # For other constraints, use LLM with strict prompt 

168 combined_evidence = "\n".join( 

169 [e.get("text", "")[:300] for e in evidence_list[:2]] 

170 ) 

171 

172 prompt = f""" 

173STRICT EVALUATION: Does "{candidate.name}" definitely and clearly satisfy: "{constraint.value}"? 

174 

175Evidence: 

176{combined_evidence} 

177 

178Be very strict. Only return a high score if there is clear, unambiguous evidence. 

179 

180Score (0.0-1.0): 

181""" 

182 

183 try: 

184 response = self.model.invoke(prompt).content.strip() 

185 import re 

186 

187 match = re.search(r"(\d*\.?\d+)", response) 

188 if match: 

189 return max(0.0, min(float(match.group(1)), 1.0)) 

190 except Exception: 

191 logger.exception("Error in strict evaluation") 

192 

193 return 0.0 # Default to fail on error 

194 

195 def _check_name_pattern_strictly( 

196 self, candidate_name: str, pattern_description: str 

197 ) -> float: 

198 """Strict name pattern checking.""" 

199 # Example: Check for body parts in name 

200 if "body part" in pattern_description.lower(): 

201 body_parts = [ 

202 "arm", 

203 "leg", 

204 "foot", 

205 "feet", 

206 "hand", 

207 "eye", 

208 "ear", 

209 "nose", 

210 "mouth", 

211 "tooth", 

212 "teeth", 

213 "head", 

214 "face", 

215 "neck", 

216 "back", 

217 "chest", 

218 "heart", 

219 "finger", 

220 "thumb", 

221 "toe", 

222 "knee", 

223 "elbow", 

224 "shoulder", 

225 "spine", 

226 "bone", 

227 ] 

228 

229 name_lower = candidate_name.lower() 

230 for part in body_parts: 

231 if part in name_lower.split() or part in name_lower: 

232 logger.info( 

233 f"✓ Found body part '{part}' in '{candidate_name}'" 

234 ) 

235 return 1.0 

236 

237 logger.info(f"✗ No body part found in '{candidate_name}'") 

238 return 0.0 

239 

240 # For other name patterns, use LLM 

241 prompt = f""" 

242Does the name "{candidate_name}" match this pattern: "{pattern_description}"? 

243 

244Be very strict. Return 1.0 only if it clearly matches, 0.0 otherwise. 

245 

246Score: 

247""" 

248 

249 try: 

250 response = self.model.invoke(prompt).content.strip() 

251 import re 

252 

253 match = re.search(r"(\d*\.?\d+)", response) 

254 if match: 

255 return max(0.0, min(float(match.group(1)), 1.0)) 

256 except Exception: 

257 pass 

258 

259 return 0.0