Coverage for cc_modules/cc_idnumdef.py: 66%
64 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/cc_modules/cc_idnumdef.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
13 CamCOPS is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
18 CamCOPS is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
26===============================================================================
28**ID number definitions.**
30"""
32import logging
33from typing import List, Optional, Tuple, TYPE_CHECKING
35from cardinal_pythonlib.logs import BraceStyleAdapter
36from cardinal_pythonlib.nhs import is_valid_nhs_number
37from cardinal_pythonlib.reprfunc import simple_repr
38from sqlalchemy.orm import Session as SqlASession
39from sqlalchemy.sql.schema import Column
40from sqlalchemy.sql.sqltypes import Integer, String
42from camcops_server.cc_modules.cc_pyramid import Routes
43from camcops_server.cc_modules.cc_sqla_coltypes import (
44 HL7AssigningAuthorityType,
45 HL7IdTypeType,
46 IdDescriptorColType,
47 UrlColType,
48)
49from camcops_server.cc_modules.cc_sqlalchemy import Base
51if TYPE_CHECKING:
52 from camcops_server.cc_modules.cc_request import CamcopsRequest
54log = BraceStyleAdapter(logging.getLogger(__name__))
57# =============================================================================
58# ID number validation
59# =============================================================================
61ID_NUM_VALIDATION_METHOD_MAX_LEN = 50
64class IdNumValidationMethod(object):
65 """
66 Constants representing ways that CamCOPS knows to validate ID numbers.
67 """
69 NONE = "" # special
70 UK_NHS_NUMBER = "uk_nhs_number"
73ID_NUM_VALIDATION_METHOD_CHOICES = (
74 # for HTML forms: value, description
75 (IdNumValidationMethod.NONE, "None"),
76 (IdNumValidationMethod.UK_NHS_NUMBER, "UK NHS number"),
77)
80def validate_id_number(
81 req: "CamcopsRequest", idnum: Optional[int], method: str
82) -> Tuple[bool, str]:
83 """
84 Validates an ID number according to a method (as per
85 :class:`IdNumValidationMethod`).
87 If the number is ``None``, that's valid (that's an ID policy failure, not
88 a number validation failure). If ``method`` is falsy, that's also valid
89 (no constraints).
91 Args:
92 req: a :class:`camcops_server.cc_modules.cc_request.CamcopsRequest`
93 idnum: the ID number, or ``None``
94 method:
96 Returns:
97 tuple: ``valid, why_invalid`` where ``valid`` is ``bool`` and
98 ``why_invalid`` is ``str``.
100 """
101 _ = req.gettext
102 if idnum is None or not method:
103 return True, ""
104 if not isinstance(idnum, int):
105 return False, _("Not an integer")
106 if method == IdNumValidationMethod.UK_NHS_NUMBER:
107 if is_valid_nhs_number(idnum):
108 return True, ""
109 else:
110 return False, _("Invalid UK NHS number")
111 return False, _("Unknown validation method")
114# =============================================================================
115# IdNumDefinition
116# =============================================================================
119class IdNumDefinition(Base):
120 """
121 Represents an ID number definition.
122 """
124 __tablename__ = "_idnum_definitions"
126 which_idnum = Column(
127 "which_idnum",
128 Integer,
129 primary_key=True,
130 index=True,
131 comment="Which of the server's ID numbers is this?",
132 )
133 description = Column(
134 "description",
135 IdDescriptorColType,
136 comment="Full description of the ID number",
137 )
138 short_description = Column(
139 "short_description",
140 IdDescriptorColType,
141 comment="Short description of the ID number",
142 )
143 hl7_id_type = Column(
144 "hl7_id_type",
145 HL7IdTypeType,
146 comment="HL7: Identifier Type code: 'a code corresponding to the type "
147 "of identifier. In some cases, this code may be used as a "
148 'qualifier to the "Assigning Authority" component.\'',
149 )
150 hl7_assigning_authority = Column(
151 "hl7_assigning_authority",
152 HL7AssigningAuthorityType,
153 comment="HL7: Assigning Authority for ID number (unique name of the "
154 "system/organization/agency/department that creates the data).",
155 )
156 validation_method = Column(
157 "validation_method",
158 String(length=ID_NUM_VALIDATION_METHOD_MAX_LEN),
159 comment="Optional validation method",
160 )
161 fhir_id_system = Column(
162 "fhir_id_system", UrlColType, comment="FHIR external ID 'system' URL"
163 )
165 def __init__(
166 self,
167 which_idnum: int = None,
168 description: str = "",
169 short_description: str = "",
170 hl7_id_type: str = "",
171 hl7_assigning_authority: str = "",
172 validation_method: str = "",
173 fhir_id_system: str = "",
174 ):
175 # We permit a "blank" constructor for automatic copying, e.g. merge_db.
176 self.which_idnum = which_idnum
177 self.description = description
178 self.short_description = short_description
179 self.hl7_id_type = hl7_id_type
180 self.hl7_assigning_authority = hl7_assigning_authority
181 self.validation_method = validation_method
182 self.fhir_id_system = fhir_id_system
184 def __repr__(self) -> str:
185 return simple_repr(
186 self,
187 ["which_idnum", "description", "short_description"],
188 with_addr=False,
189 )
191 def _camcops_default_fhir_id_system(self, req: "CamcopsRequest") -> str:
192 """
193 The built-in FHIR ID system URL that we'll use if the user hasn't
194 specified one for the selected ID number type.
195 """
196 return req.route_url(
197 Routes.FHIR_PATIENT_ID_SYSTEM, which_idnum=self.which_idnum
198 ) # path will be e.g. /fhir_patient_id_system/3
200 def effective_fhir_id_system(self, req: "CamcopsRequest") -> str:
201 """
202 If the user has set a FHIR ID system, return that. Otherwise, return
203 a CamCOPS default.
204 """
205 return self.fhir_id_system or self._camcops_default_fhir_id_system(req)
207 def verbose_fhir_id_system(self, req: "CamcopsRequest") -> str:
208 """
209 Returns a human-readable description of the FHIR ID system in effect,
210 in HTML form.
211 """
212 _ = req.gettext
213 if self.fhir_id_system:
214 prefix = ""
215 url = self.fhir_id_system
216 else:
217 prefix = _("Default:") + " "
218 url = self._camcops_default_fhir_id_system(req)
219 return f'{prefix} <a href="{url}">{url}</a>'
222# =============================================================================
223# Retrieving all IdNumDefinition objects
224# =============================================================================
227def get_idnum_definitions(dbsession: SqlASession) -> List[IdNumDefinition]:
228 """
229 Get all ID number definitions from the database, in order.
230 """
231 return list(
232 dbsession.query(IdNumDefinition).order_by(IdNumDefinition.which_idnum)
233 )