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

1import logging 

2from typing import Dict 

3 

4from presidio_analyzer import AnalysisExplanation 

5 

6 

7class RecognizerResult: 

8 """ 

9 Recognizer Result represents the findings of the detected entity. 

10 

11 Result of a recognizer analyzing the text. 

12 

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 """ 

23 

24 # Keys for recognizer metadata 

25 RECOGNIZER_NAME_KEY = "recognizer_name" 

26 RECOGNIZER_IDENTIFIER_KEY = "recognizer_identifier" 

27 

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" 

31 

32 logger = logging.getLogger("presidio-analyzer") 

33 

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 

48 

49 if not recognition_metadata: 

50 self.logger.debug( 

51 "recognition_metadata should be passed, " 

52 "containing a recognizer_name value" 

53 ) 

54 

55 self.recognition_metadata = recognition_metadata 

56 

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) 

61 

62 def to_dict(self) -> Dict: 

63 """ 

64 Serialize self to dictionary. 

65 

66 :return: a dictionary 

67 """ 

68 return self.__dict__ 

69 

70 @classmethod 

71 def from_json(cls, data: Dict) -> "RecognizerResult": 

72 """ 

73 Create RecognizerResult from json. 

74 

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) 

88 

89 def __repr__(self) -> str: 

90 """Return a string representation of the instance.""" 

91 return self.__str__() 

92 

93 def intersects(self, other: "RecognizerResult") -> int: 

94 """ 

95 Check if self intersects with a different RecognizerResult. 

96 

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 

104 

105 # otherwise the intersection is min(end) - max(start) 

106 return min(self.end, other.end) - max(self.start, other.start) 

107 

108 def contained_in(self, other: "RecognizerResult") -> bool: 

109 """ 

110 Check if self is contained in a different RecognizerResult. 

111 

112 :return: true if contained 

113 """ 

114 return self.start >= other.start and self.end <= other.end 

115 

116 def contains(self, other: "RecognizerResult") -> bool: 

117 """ 

118 Check if one result is contained or equal to another result. 

119 

120 :param other: another RecognizerResult 

121 :return: bool 

122 """ 

123 return self.start <= other.start and self.end >= other.end 

124 

125 def equal_indices(self, other: "RecognizerResult") -> bool: 

126 """ 

127 Check if the indices are equal between two results. 

128 

129 :param other: another RecognizerResult 

130 :return: 

131 """ 

132 return self.start == other.start and self.end == other.end 

133 

134 def __gt__(self, other: "RecognizerResult") -> bool: 

135 """ 

136 Check if one result is greater by using the results indices in the text. 

137 

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 

144 

145 def __eq__(self, other: "RecognizerResult") -> bool: 

146 """ 

147 Check two results are equal by using all class fields. 

148 

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 

155 

156 def __hash__(self): 

157 """ 

158 Hash the result data by using all class fields. 

159 

160 :return: int 

161 """ 

162 return hash( 

163 f"{str(self.start)} {str(self.end)} {str(self.score)} {self.entity_type}" 

164 ) 

165 

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 ) 

174 

175 def has_conflict(self, other: "RecognizerResult") -> bool: 

176 """ 

177 Check if two recognizer results are conflicted or not. 

178 

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. 

182 

183 :param other: RecognizerResult 

184 :return: 

185 """ 

186 if self.equal_indices(other): 

187 return self.score <= other.score 

188 return other.contains(self)