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
« 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.
4This implementation is very strict about constraint satisfaction,
5requiring high confidence for all constraints.
6"""
8from typing import Dict, List, Tuple
10from loguru import logger
12from ..candidates.base_candidate import Candidate
13from ..constraints.base_constraint import Constraint, ConstraintType
14from .base_constraint_checker import (
15 BaseConstraintChecker,
16 ConstraintCheckResult,
17)
20class StrictChecker(BaseConstraintChecker):
21 """
22 Strict constraint checker that requires high confidence for all constraints.
24 This is an example of how to create custom constraint checking variants
25 by inheriting from BaseConstraintChecker.
26 """
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.
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
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)")
52 constraint_scores = {}
53 detailed_results = []
54 rejection_reason = None
55 should_reject = False
57 for constraint in constraints:
58 # Gather evidence
59 evidence_list = self._gather_evidence_for_constraint(
60 candidate, constraint
61 )
63 # Calculate score
64 score = self._evaluate_constraint_strictly(
65 candidate, constraint, evidence_list
66 )
68 # Check for rejection
69 reject, reason = self.should_reject_candidate(
70 candidate, constraint, evidence_list
71 )
73 if reject and not should_reject:
74 should_reject = True
75 rejection_reason = reason
77 # Store results
78 constraint_scores[constraint.value] = {
79 "total": score,
80 "strict_pass": score >= self.strict_threshold,
81 "weight": constraint.weight,
82 }
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 )
94 self._log_constraint_result(candidate, constraint, score, {})
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
104 logger.info(
105 f"Strict evaluation for {candidate.name}: {'PASS' if total_score > 0 else 'FAIL'}"
106 )
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 )
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}'"
127 score = self._evaluate_constraint_strictly(
128 candidate, constraint, evidence_data
129 )
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 )
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 )
149 return False, ""
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
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 )
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 )
172 prompt = f"""
173STRICT EVALUATION: Does "{candidate.name}" definitely and clearly satisfy: "{constraint.value}"?
175Evidence:
176{combined_evidence}
178Be very strict. Only return a high score if there is clear, unambiguous evidence.
180Score (0.0-1.0):
181"""
183 try:
184 response = self.model.invoke(prompt).content.strip()
185 import re
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")
193 return 0.0 # Default to fail on error
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 ]
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
237 logger.info(f"✗ No body part found in '{candidate_name}'")
238 return 0.0
240 # For other name patterns, use LLM
241 prompt = f"""
242Does the name "{candidate_name}" match this pattern: "{pattern_description}"?
244Be very strict. Return 1.0 only if it clearly matches, 0.0 otherwise.
246Score:
247"""
249 try:
250 response = self.model.invoke(prompt).content.strip()
251 import re
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
259 return 0.0