Coverage for crateweb/consent/lookup.py: 21%

66 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-08-27 10:34 -0500

1""" 

2crate_anon/crateweb/consent/lookup.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2015, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CRATE. 

10 

11 CRATE is free software: you can redistribute it and/or modify 

12 it under the terms of the GNU General Public License as published by 

13 the Free Software Foundation, either version 3 of the License, or 

14 (at your option) any later version. 

15 

16 CRATE is distributed in the hope that it will be useful, 

17 but WITHOUT ANY WARRANTY; without even the implied warranty of 

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

19 GNU General Public License for more details. 

20 

21 You should have received a copy of the GNU General Public License 

22 along with CRATE. If not, see <https://www.gnu.org/licenses/>. 

23 

24=============================================================================== 

25 

26**Core functions to look up patient details from a clinical database.** 

27 

28These functions then send the request to specialized functions according to 

29which type of clinical database is in use. 

30 

31""" 

32 

33import logging 

34from typing import Generator, List, Optional, Tuple, Union 

35 

36from cardinal_pythonlib.logs import BraceStyleAdapter 

37from django.conf import settings 

38 

39from crate_anon.crateweb.config.constants import ClinicalDatabaseType 

40from crate_anon.crateweb.consent.lookup_crs import ( 

41 lookup_cpft_crs, 

42) 

43from crate_anon.crateweb.consent.lookup_dummy import ( 

44 lookup_dummy_clinical, 

45) 

46from crate_anon.crateweb.consent.lookup_rio import ( 

47 gen_opt_out_pids_mpids_rio_cpft_datamart, 

48 gen_opt_out_pids_mpids_rio_raw, 

49 get_latest_consent_mode_from_rio_cpft_datamart, 

50 get_latest_consent_mode_from_rio_raw, 

51 lookup_cpft_rio_crate_preprocessed, 

52 lookup_cpft_rio_rcep, 

53) 

54from crate_anon.crateweb.consent.lookup_systmone import ( 

55 gen_opt_out_pids_mpids_cpft_systmone, 

56 get_latest_consent_mode_from_cpft_systmone, 

57 lookup_cpft_systmone, 

58) 

59from crate_anon.crateweb.consent.models import ConsentMode, PatientLookup 

60 

61log = BraceStyleAdapter(logging.getLogger(__name__)) 

62 

63 

64# ============================================================================= 

65# Look up patient ID 

66# ============================================================================= 

67 

68 

69def lookup_patient( 

70 nhs_number: int, 

71 source_db: str = None, 

72 save: bool = True, 

73 existing_ok: bool = False, 

74) -> PatientLookup: 

75 """ 

76 Looks up details of a patient. 

77 

78 Args: 

79 nhs_number: NHS number 

80 source_db: the type of the source database; see 

81 :class:`crate_anon.crateweb.config.constants.ClinicalDatabaseType` 

82 save: save the lookup in our admin database? 

83 existing_ok: if we have a lookup saved in our admin database, use that? 

84 (If ``False``, fetch a new one from the primary source, 

85 regardless.) 

86 

87 Returns: 

88 a :class:`crate_anon.crateweb.consent.models.PatientLookup` 

89 

90 """ 

91 source_db = source_db or settings.CLINICAL_LOOKUP_DB 

92 if existing_ok: 

93 try: 

94 lookup = PatientLookup.objects.filter( 

95 nhs_number=nhs_number 

96 ).latest("lookup_at") 

97 if lookup: 

98 return lookup 

99 except PatientLookup.DoesNotExist: 

100 # No existing lookup, so proceed to do it properly (below). 

101 pass 

102 lookup = PatientLookup(nhs_number=nhs_number, source_db=source_db) 

103 # ... this object will be modified by the subsequent calls 

104 decisions = [] # type: List[str] 

105 secret_decisions = [] # type: List[str] 

106 if source_db == ClinicalDatabaseType.DUMMY_CLINICAL: 

107 lookup_dummy_clinical(lookup, decisions, secret_decisions) 

108 elif source_db == ClinicalDatabaseType.CPFT_PCMIS: 

109 raise AssertionError("Don't know how to look up ID from PCMIS yet") 

110 # lookup_cpft_iapt(lookup, decisions, secret_decisions) 

111 elif source_db == ClinicalDatabaseType.CPFT_CRS: 

112 lookup_cpft_crs(lookup, decisions, secret_decisions) 

113 elif source_db == ClinicalDatabaseType.CPFT_RIO_RCEP: 

114 lookup_cpft_rio_rcep(lookup, decisions, secret_decisions) 

115 elif source_db == ClinicalDatabaseType.CPFT_RIO_CRATE_PREPROCESSED: 

116 lookup_cpft_rio_crate_preprocessed(lookup, decisions, secret_decisions) 

117 elif source_db == ClinicalDatabaseType.CPFT_RIO_DATAMART: 

118 raise AssertionError( 

119 "Not enough information in RiO Data Warehouse " 

120 "copy yet to look up patient ID" 

121 ) 

122 elif source_db == ClinicalDatabaseType.CPFT_SYSTMONE: 

123 lookup_cpft_systmone(lookup, decisions, secret_decisions) 

124 else: 

125 raise ValueError(f"Bad source_db for ID lookup: {source_db}") 

126 lookup.decisions = " ".join(decisions) 

127 lookup.secret_decisions = " ".join(secret_decisions) 

128 if save: 

129 lookup.save() 

130 return lookup 

131 

132 

133# ============================================================================= 

134# Look up research consent choices 

135# ============================================================================= 

136 

137 

138def lookup_consent( 

139 nhs_number: int, decisions: List[str], source_db: str = None 

140) -> Optional[ConsentMode]: 

141 """ 

142 Returns the latest :class:`crate_anon.crateweb.consent.models.ConsentMode` 

143 for this patient from the primary clinical source, or ``None``. Writes to 

144 ``decisions`` as it goes. 

145 

146 Args: 

147 nhs_number: NHS number 

148 decisions: list of human-readable decisions; will be modified 

149 source_db: the type of the source database; see 

150 :class:`crate_anon.crateweb.config.constants.ClinicalDatabaseType` 

151 

152 Returns: 

153 a :class:`crate_anon.crateweb.consent.models.ConsentMode` or ``None`` 

154 """ 

155 source_db = source_db or settings.CLINICAL_LOOKUP_CONSENT_DB 

156 if source_db == ClinicalDatabaseType.CPFT_RIO_DATAMART: 

157 return get_latest_consent_mode_from_rio_cpft_datamart( 

158 nhs_number=nhs_number, source_db=source_db, decisions=decisions 

159 ) 

160 elif source_db in [ 

161 ClinicalDatabaseType.CPFT_RIO_RAW, 

162 ClinicalDatabaseType.CPFT_RIO_CRATE_PREPROCESSED, 

163 ]: 

164 return get_latest_consent_mode_from_rio_raw( 

165 nhs_number=nhs_number, source_db=source_db, decisions=decisions 

166 ) 

167 elif source_db == ClinicalDatabaseType.CPFT_SYSTMONE: 

168 return get_latest_consent_mode_from_cpft_systmone( 

169 nhs_number=nhs_number, decisions=decisions 

170 ) 

171 else: 

172 # Don't know how to look up consent modes from other sources 

173 errmsg = f"Don't know how to look up consent modes from {source_db}" 

174 decisions.append(errmsg) 

175 log.warning(errmsg) 

176 return None 

177 

178 

179def gen_opt_out_pids_mpids( 

180 source_db: str, 

181) -> Generator[Tuple[Union[int, str], Union[int, str]], None, None]: 

182 """ 

183 Generates PID/MPID information for all patients wishing to opt out of the 

184 anonymous database. 

185 

186 Args: 

187 source_db: the type of the source database; see 

188 :class:`crate_anon.crateweb.config.constants.ClinicalDatabaseType` 

189 

190 Yields: 

191 tuples: ``pid, mpid`` for each patient opting out 

192 

193 """ 

194 if source_db == ClinicalDatabaseType.CPFT_RIO_DATAMART: 

195 generator = gen_opt_out_pids_mpids_rio_cpft_datamart(source_db) 

196 elif source_db in [ 

197 ClinicalDatabaseType.CPFT_RIO_RAW, 

198 ClinicalDatabaseType.CPFT_RIO_CRATE_PREPROCESSED, 

199 ]: 

200 generator = gen_opt_out_pids_mpids_rio_raw(source_db) 

201 elif source_db == ClinicalDatabaseType.CPFT_SYSTMONE: 

202 generator = gen_opt_out_pids_mpids_cpft_systmone() 

203 else: 

204 # Don't know how to look up consent modes from other sources 

205 log.error(f"Don't know how to look up opt-outs from {source_db}") 

206 return 

207 for pid, mpid in generator: 

208 yield pid, mpid