Coverage for presidio_analyzer / recognizer_result.py: 91%
57 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-29 09:03 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-29 09:03 +0000
1import logging
2from typing import Dict
4from presidio_analyzer import AnalysisExplanation
7class RecognizerResult:
8 """
9 Recognizer Result represents the findings of the detected entity.
11 Result of a recognizer analyzing the text.
13 :param entity_type: the type of the entity
14 :param start: the start location of the detected entity
15 :param end: the end location of the detected entity
16 :param score: the score of the detection
17 :param analysis_explanation: contains the explanation of why this
18 entity was identified
19 :param recognition_metadata: a dictionary of metadata to be used in
20 recognizer specific cases, for example specific recognized context words
21 and recognizer name
22 """
24 # Keys for recognizer metadata
25 RECOGNIZER_NAME_KEY = "recognizer_name"
26 RECOGNIZER_IDENTIFIER_KEY = "recognizer_identifier"
28 # Key of a flag inside recognition_metadata dictionary
29 # which is set to true if the result enhanced by context
30 IS_SCORE_ENHANCED_BY_CONTEXT_KEY = "is_score_enhanced_by_context"
32 logger = logging.getLogger("presidio-analyzer")
34 def __init__(
35 self,
36 entity_type: str,
37 start: int,
38 end: int,
39 score: float,
40 analysis_explanation: AnalysisExplanation = None,
41 recognition_metadata: Dict = None,
42 ):
43 self.entity_type = entity_type
44 self.start = start
45 self.end = end
46 self.score = score
47 self.analysis_explanation = analysis_explanation
49 if not recognition_metadata:
50 self.logger.debug(
51 "recognition_metadata should be passed, "
52 "containing a recognizer_name value"
53 )
55 self.recognition_metadata = recognition_metadata
57 def append_analysis_explanation_text(self, text: str) -> None:
58 """Add text to the analysis explanation."""
59 if self.analysis_explanation:
60 self.analysis_explanation.append_textual_explanation_line(text)
62 def to_dict(self) -> Dict:
63 """
64 Serialize self to dictionary.
66 :return: a dictionary
67 """
68 return self.__dict__
70 @classmethod
71 def from_json(cls, data: Dict) -> "RecognizerResult":
72 """
73 Create RecognizerResult from json.
75 :param data: e.g. {
76 "start": 24,
77 "end": 32,
78 "score": 0.8,
79 "entity_type": "NAME"
80 }
81 :return: RecognizerResult
82 """
83 score = data.get("score")
84 entity_type = data.get("entity_type")
85 start = data.get("start")
86 end = data.get("end")
87 return cls(entity_type, start, end, score)
89 def __repr__(self) -> str:
90 """Return a string representation of the instance."""
91 return self.__str__()
93 def intersects(self, other: "RecognizerResult") -> int:
94 """
95 Check if self intersects with a different RecognizerResult.
97 :return: If intersecting, returns the number of
98 intersecting characters.
99 If not, returns 0
100 """
101 # if they do not overlap the intersection is 0
102 if self.end < other.start or other.end < self.start:
103 return 0
105 # otherwise the intersection is min(end) - max(start)
106 return min(self.end, other.end) - max(self.start, other.start)
108 def contained_in(self, other: "RecognizerResult") -> bool:
109 """
110 Check if self is contained in a different RecognizerResult.
112 :return: true if contained
113 """
114 return self.start >= other.start and self.end <= other.end
116 def contains(self, other: "RecognizerResult") -> bool:
117 """
118 Check if one result is contained or equal to another result.
120 :param other: another RecognizerResult
121 :return: bool
122 """
123 return self.start <= other.start and self.end >= other.end
125 def equal_indices(self, other: "RecognizerResult") -> bool:
126 """
127 Check if the indices are equal between two results.
129 :param other: another RecognizerResult
130 :return:
131 """
132 return self.start == other.start and self.end == other.end
134 def __gt__(self, other: "RecognizerResult") -> bool:
135 """
136 Check if one result is greater by using the results indices in the text.
138 :param other: another RecognizerResult
139 :return: bool
140 """
141 if self.start == other.start:
142 return self.end > other.end
143 return self.start > other.start
145 def __eq__(self, other: "RecognizerResult") -> bool:
146 """
147 Check two results are equal by using all class fields.
149 :param other: another RecognizerResult
150 :return: bool
151 """
152 equal_type = self.entity_type == other.entity_type
153 equal_score = self.score == other.score
154 return self.equal_indices(other) and equal_type and equal_score
156 def __hash__(self):
157 """
158 Hash the result data by using all class fields.
160 :return: int
161 """
162 return hash(
163 f"{str(self.start)} {str(self.end)} {str(self.score)} {self.entity_type}"
164 )
166 def __str__(self) -> str:
167 """Return a string representation of the instance."""
168 return (
169 f"type: {self.entity_type}, "
170 f"start: {self.start}, "
171 f"end: {self.end}, "
172 f"score: {self.score}"
173 )
175 def has_conflict(self, other: "RecognizerResult") -> bool:
176 """
177 Check if two recognizer results are conflicted or not.
179 I have a conflict if:
180 1. My indices are the same as the other and my score is lower.
181 2. If my indices are contained in another.
183 :param other: RecognizerResult
184 :return:
185 """
186 if self.equal_indices(other):
187 return self.score <= other.score
188 return other.contains(self)