Coverage for presidio_analyzer / predefined_recognizers / third_party / ahds_recognizer.py: 59%
56 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 os
2from typing import List, Optional
4try:
5 from azure.health.deidentification import DeidentificationClient
6 from azure.health.deidentification.models import (
7 DeidentificationContent,
8 DeidentificationOperationType,
9 PhiCategory,
10 )
11except ImportError:
12 DeidentificationClient = None
13 DeidentificationContent = None
14 DeidentificationOperationType = None
15 PhiCategory = None
17try:
18 from presidio_analyzer.llm_utils.azure_auth_helper import (
19 get_azure_credential,
20 )
21 AZURE_AUTH_AVAILABLE = True
22except ImportError: # pragma: no cover
23 get_azure_credential = None
24 AZURE_AUTH_AVAILABLE = False
26from presidio_analyzer import AnalysisExplanation, RecognizerResult, RemoteRecognizer
27from presidio_analyzer.nlp_engine import NlpArtifacts
30class AzureHealthDeidRecognizer(RemoteRecognizer):
31 """Wrapper for PHI detection using Azure Health Data Services de-identification."""
33 def __init__(
34 self,
35 supported_entities: Optional[List[str]] = None,
36 supported_language: str = "en",
37 client: Optional[DeidentificationClient] = None,
38 name: Optional[str] = None,
39 ):
40 """
41 Wrap PHI detection using Azure Health Data Services de-identification.
43 :param supported_entities: List of supported entities for this recognizer.
44 :param supported_language: Language code (not used, only 'en' supported).
45 :param client: Optional DeidentificationClient instance.
46 """
47 super().__init__(
48 supported_entities=supported_entities,
49 supported_language=supported_language,
50 name=name if name else "Azure Health Data Services Deidentification",
51 version="1.0.0",
52 )
55 endpoint = os.getenv("AHDS_ENDPOINT", None)
57 if client is None:
58 if endpoint is None:
59 raise ValueError(
60 "AHDS de-identification endpoint is required. "
61 "Please provide an endpoint "
62 "or set the AHDS_ENDPOINT environment variable."
63 )
65 if not DeidentificationClient:
66 raise ImportError(
67 "Azure Health Data Services Deidentification SDK is not available. "
68 "Please install azure-health-deidentification and azure-identity."
69 )
71 # Use environment-aware credential (DefaultAzureCredential for dev,
72 # ChainedTokenCredential for production)
73 credential = get_azure_credential()
74 client = DeidentificationClient(endpoint, credential)
76 self.deid_client = client
78 if not supported_entities:
79 self.supported_entities = self._get_supported_entities()
81 @staticmethod
82 def _get_supported_entities() -> List[str]:
83 if PhiCategory:
84 try:
85 # PhiCategory is an enum, try to get the actual enum names
86 return [category.name for category in PhiCategory]
87 except Exception:
88 return ImportError
90 def get_supported_entities(self) -> List[str]:
91 """
92 Return the list of entities supported by this recognizer.
94 Returns
95 List[str]: A list of supported entity names as strings.
96 """
97 return self.supported_entities
99 def analyze(
100 self, text: str, entities: List[str] = None, nlp_artifacts: NlpArtifacts = None
101 ) -> List[RecognizerResult]:
102 """
103 Analyze text using Azure Health Data Services Deidentification (TAG operation).
105 :param text: Text to analyze
106 :param entities: List of entities to return (optional)
107 :param nlp_artifacts: Not used
108 :return: List of RecognizerResult for each PHI entity found
109 """
110 if not entities:
111 entities = self.supported_entities
113 body = DeidentificationContent(
114 input_text=text,
115 operation_type=DeidentificationOperationType.TAG
116 )
117 result = self.deid_client.deidentify_text(body)
119 recognizer_results = []
120 if result.tagger_result and result.tagger_result.entities:
121 for entity in result.tagger_result.entities:
122 category = entity.category.upper()
123 if category not in [e.upper() for e in entities]:
124 continue
125 analysis_explanation = AzureHealthDeidRecognizer._build_explanation(
126 entity_type=category
127 )
128 recognizer_results.append(
129 RecognizerResult(
130 entity_type=category,
131 start=entity.offset.code_point,
132 end=entity.offset.code_point + entity.length.code_point,
133 score=round(entity.confidence_score, 2),
134 analysis_explanation=analysis_explanation,
135 )
136 )
137 return recognizer_results
139 @staticmethod
140 def _build_explanation(entity_type: str) -> AnalysisExplanation:
141 explanation = AnalysisExplanation(
142 recognizer=AzureHealthDeidRecognizer.__class__.__name__,
143 original_score=1.0,
144 textual_explanation=(
145 f"Identified as {entity_type} by Azure Health Data Services "
146 "Deidentification"
147 ),
148 )
149 return explanation