Coverage for presidio_analyzer / predefined_recognizers / country_specific / uk / uk_vehicle_registration_recognizer.py: 100%

19 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-29 09:03 +0000

1from typing import List, Optional, Tuple 

2 

3from presidio_analyzer import Pattern, PatternRecognizer 

4from presidio_analyzer.entity_recognizer import EntityRecognizer 

5 

6 

7class UkVehicleRegistrationRecognizer(PatternRecognizer): 

8 """ 

9 Recognizes UK vehicle registration numbers using regex. 

10 

11 Supports three formats still commonly seen on the road: 

12 

13 - Current (2001+): 2 area letters + 2-digit age id + 3 random letters 

14 e.g. AB51 ABC 

15 - Prefix (1983-2001): year letter + 1-3 digits + 3 letters 

16 e.g. A123 BCD 

17 - Suffix (1963-1983): 3 letters + 1-3 digits + year letter 

18 e.g. ABC 123D 

19 

20 :param patterns: List of patterns to be used by this recognizer 

21 :param context: List of context words to increase confidence in detection 

22 :param supported_language: Language this recognizer supports 

23 :param supported_entity: The entity this recognizer can detect 

24 :param replacement_pairs: List of tuples for replacing characters in the 

25 pattern text for validation 

26 """ 

27 

28 PATTERNS = [ 

29 Pattern( 

30 "UK Vehicle Registration (current)", 

31 r"\b[A-HJ-PR-Y][A-HJ-PR-Y](?:0[1-9]|[1-7][0-9])[- ]?[A-HJ-PR-Z]{3}\b", 

32 0.3, 

33 ), 

34 Pattern( 

35 "UK Vehicle Registration (prefix)", 

36 r"\b[A-HJ-NPR-TV-Y]\d{1,3}[- ]?[A-HJ-PR-Y][A-HJ-PR-Z]{2}\b", 

37 0.2, 

38 ), 

39 Pattern( 

40 "UK Vehicle Registration (suffix)", 

41 r"\b[A-HJ-PR-Z]{3}[- ]?\d{1,3}[- ]?[A-HJ-NPR-TV-Y]\b", 

42 0.15, 

43 ), 

44 ] 

45 

46 CONTEXT = [ 

47 "vehicle", 

48 "registration", 

49 "number plate", 

50 "licence plate", 

51 "license plate", 

52 "reg", 

53 "vrn", 

54 "dvla", 

55 "v5c", 

56 "logbook", 

57 "mot", 

58 "car", 

59 "insured vehicle", 

60 ] 

61 

62 def __init__( 

63 self, 

64 patterns: Optional[List[Pattern]] = None, 

65 context: Optional[List[str]] = None, 

66 supported_language: str = "en", 

67 supported_entity: str = "UK_VEHICLE_REGISTRATION", 

68 replacement_pairs: Optional[List[Tuple[str, str]]] = None, 

69 name: Optional[str] = None, 

70 ): 

71 self.replacement_pairs = ( 

72 replacement_pairs if replacement_pairs else [("-", ""), (" ", "")] 

73 ) 

74 patterns = patterns if patterns else self.PATTERNS 

75 context = context if context else self.CONTEXT 

76 super().__init__( 

77 supported_entity=supported_entity, 

78 patterns=patterns, 

79 context=context, 

80 supported_language=supported_language, 

81 name=name, 

82 ) 

83 

84 def validate_result(self, pattern_text: str) -> Optional[bool]: 

85 """ 

86 Validate the matched pattern. 

87 

88 Only the current format (2001+) is validated further by checking 

89 that the two-digit age identifier falls in a valid range: 

90 02-29 (March registrations) or 51-79 (September registrations). 

91 

92 Prefix and suffix formats return None (keep base score). 

93 

94 :param pattern_text: The matched text to validate 

95 :return: True if valid current format, False if invalid current 

96 format, None for prefix/suffix formats 

97 """ 

98 sanitized_value = EntityRecognizer.sanitize_value( 

99 pattern_text, self.replacement_pairs 

100 ) 

101 

102 # Current format is exactly 7 chars after sanitization 

103 if len(sanitized_value) == 7 and sanitized_value[:2].isalpha(): 

104 age_id_str = sanitized_value[2:4] 

105 if age_id_str.isdigit(): 

106 age_id = int(age_id_str) 

107 return (2 <= age_id <= 29) or (51 <= age_id <= 79) 

108 

109 return None