"""
camcops_server/cc_modules/cc_idnumdef.py
===============================================================================
Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CamCOPS.
CamCOPS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CamCOPS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
===============================================================================
**ID number definitions.**
"""
import logging
from typing import List, Optional, Tuple, TYPE_CHECKING
from cardinal_pythonlib.logs import BraceStyleAdapter
from cardinal_pythonlib.nhs import is_valid_nhs_number
from cardinal_pythonlib.reprfunc import simple_repr
from sqlalchemy.orm import Session as SqlASession
from sqlalchemy.sql.schema import Column
from sqlalchemy.sql.sqltypes import Integer, String
from camcops_server.cc_modules.cc_pyramid import Routes
from camcops_server.cc_modules.cc_sqla_coltypes import (
HL7AssigningAuthorityType,
HL7IdTypeType,
IdDescriptorColType,
UrlColType,
)
from camcops_server.cc_modules.cc_sqlalchemy import Base
if TYPE_CHECKING:
from camcops_server.cc_modules.cc_request import CamcopsRequest
log = BraceStyleAdapter(logging.getLogger(__name__))
# =============================================================================
# ID number validation
# =============================================================================
ID_NUM_VALIDATION_METHOD_MAX_LEN = 50
[docs]class IdNumValidationMethod(object):
"""
Constants representing ways that CamCOPS knows to validate ID numbers.
"""
NONE = "" # special
UK_NHS_NUMBER = "uk_nhs_number"
ID_NUM_VALIDATION_METHOD_CHOICES = (
# for HTML forms: value, description
(IdNumValidationMethod.NONE, "None"),
(IdNumValidationMethod.UK_NHS_NUMBER, "UK NHS number"),
)
[docs]def validate_id_number(
req: "CamcopsRequest", idnum: Optional[int], method: str
) -> Tuple[bool, str]:
"""
Validates an ID number according to a method (as per
:class:`IdNumValidationMethod`).
If the number is ``None``, that's valid (that's an ID policy failure, not
a number validation failure). If ``method`` is falsy, that's also valid
(no constraints).
Args:
req: a :class:`camcops_server.cc_modules.cc_request.CamcopsRequest`
idnum: the ID number, or ``None``
method:
Returns:
tuple: ``valid, why_invalid`` where ``valid`` is ``bool`` and
``why_invalid`` is ``str``.
"""
_ = req.gettext
if idnum is None or not method:
return True, ""
if not isinstance(idnum, int):
return False, _("Not an integer")
if method == IdNumValidationMethod.UK_NHS_NUMBER:
if is_valid_nhs_number(idnum):
return True, ""
else:
return False, _("Invalid UK NHS number")
return False, _("Unknown validation method")
# =============================================================================
# IdNumDefinition
# =============================================================================
[docs]class IdNumDefinition(Base):
"""
Represents an ID number definition.
"""
__tablename__ = "_idnum_definitions"
which_idnum = Column(
"which_idnum",
Integer,
primary_key=True,
index=True,
comment="Which of the server's ID numbers is this?",
)
description = Column(
"description",
IdDescriptorColType,
comment="Full description of the ID number",
)
short_description = Column(
"short_description",
IdDescriptorColType,
comment="Short description of the ID number",
)
hl7_id_type = Column(
"hl7_id_type",
HL7IdTypeType,
comment="HL7: Identifier Type code: 'a code corresponding to the type "
"of identifier. In some cases, this code may be used as a "
'qualifier to the "Assigning Authority" component.\'',
)
hl7_assigning_authority = Column(
"hl7_assigning_authority",
HL7AssigningAuthorityType,
comment="HL7: Assigning Authority for ID number (unique name of the "
"system/organization/agency/department that creates the data).",
)
validation_method = Column(
"validation_method",
String(length=ID_NUM_VALIDATION_METHOD_MAX_LEN),
comment="Optional validation method",
)
fhir_id_system = Column(
"fhir_id_system", UrlColType, comment="FHIR external ID 'system' URL"
)
def __init__(
self,
which_idnum: int = None,
description: str = "",
short_description: str = "",
hl7_id_type: str = "",
hl7_assigning_authority: str = "",
validation_method: str = "",
fhir_id_system: str = "",
):
# We permit a "blank" constructor for automatic copying, e.g. merge_db.
self.which_idnum = which_idnum
self.description = description
self.short_description = short_description
self.hl7_id_type = hl7_id_type
self.hl7_assigning_authority = hl7_assigning_authority
self.validation_method = validation_method
self.fhir_id_system = fhir_id_system
def __repr__(self) -> str:
return simple_repr(
self,
["which_idnum", "description", "short_description"],
with_addr=False,
)
def _camcops_default_fhir_id_system(self, req: "CamcopsRequest") -> str:
"""
The built-in FHIR ID system URL that we'll use if the user hasn't
specified one for the selected ID number type.
"""
return req.route_url(
Routes.FHIR_PATIENT_ID_SYSTEM, which_idnum=self.which_idnum
) # path will be e.g. /fhir_patient_id_system/3
[docs] def effective_fhir_id_system(self, req: "CamcopsRequest") -> str:
"""
If the user has set a FHIR ID system, return that. Otherwise, return
a CamCOPS default.
"""
return self.fhir_id_system or self._camcops_default_fhir_id_system(req)
[docs] def verbose_fhir_id_system(self, req: "CamcopsRequest") -> str:
"""
Returns a human-readable description of the FHIR ID system in effect,
in HTML form.
"""
_ = req.gettext
if self.fhir_id_system:
prefix = ""
url = self.fhir_id_system
else:
prefix = _("Default:") + " "
url = self._camcops_default_fhir_id_system(req)
return f'{prefix} <a href="{url}">{url}</a>'
# =============================================================================
# Retrieving all IdNumDefinition objects
# =============================================================================
[docs]def get_idnum_definitions(dbsession: SqlASession) -> List[IdNumDefinition]:
"""
Get all ID number definitions from the database, in order.
"""
return list(
dbsession.query(IdNumDefinition).order_by(IdNumDefinition.which_idnum)
)