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
« prev ^ index » next coverage.py v7.8.0, created at 2025-08-27 10:34 -0500
1"""
2crate_anon/crateweb/consent/lookup.py
4===============================================================================
6 Copyright (C) 2015, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CRATE.
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.
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.
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/>.
24===============================================================================
26**Core functions to look up patient details from a clinical database.**
28These functions then send the request to specialized functions according to
29which type of clinical database is in use.
31"""
33import logging
34from typing import Generator, List, Optional, Tuple, Union
36from cardinal_pythonlib.logs import BraceStyleAdapter
37from django.conf import settings
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
61log = BraceStyleAdapter(logging.getLogger(__name__))
64# =============================================================================
65# Look up patient ID
66# =============================================================================
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.
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.)
87 Returns:
88 a :class:`crate_anon.crateweb.consent.models.PatientLookup`
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
133# =============================================================================
134# Look up research consent choices
135# =============================================================================
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.
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`
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
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.
186 Args:
187 source_db: the type of the source database; see
188 :class:`crate_anon.crateweb.config.constants.ClinicalDatabaseType`
190 Yields:
191 tuples: ``pid, mpid`` for each patient opting out
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