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
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-29 09:03 +0000
1from typing import List, Optional, Tuple
3from presidio_analyzer import Pattern, PatternRecognizer
4from presidio_analyzer.entity_recognizer import EntityRecognizer
7class UkVehicleRegistrationRecognizer(PatternRecognizer):
8 """
9 Recognizes UK vehicle registration numbers using regex.
11 Supports three formats still commonly seen on the road:
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
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 """
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 ]
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 ]
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 )
84 def validate_result(self, pattern_text: str) -> Optional[bool]:
85 """
86 Validate the matched pattern.
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).
92 Prefix and suffix formats return None (keep base score).
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 )
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)
109 return None