#!/usr/bin/env python
"""
camcops_server/cc_modules/cc_constants.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/>.
===============================================================================
**Various constants.**
"""
# Helpful UTF-8 characters: ‘’ “” – — × • ≤ ≥ ≠ ± →
import logging
import multiprocessing
import os
from cardinal_pythonlib.randomness import create_base64encoded_randomness
from cardinal_pythonlib.sqlalchemy.session import make_mysql_url
from cardinal_pythonlib.tcpipconst import Ports
from cardinal_pythonlib.uriconst import UriSchemes
from camcops_server.cc_modules.cc_baseconstants import (
DEFAULT_EXTRA_STRINGS_DIR,
ENVVAR_GENERATING_CAMCOPS_DOCS,
LINUX_DEFAULT_LOCK_DIR,
LINUX_DEFAULT_USER_DOWNLOAD_DIR,
STATIC_ROOT_DIR,
)
from camcops_server.cc_modules.cc_language import DEFAULT_LOCALE
# =============================================================================
# Number of ID numbers. Don't alter this lightly; influences database fields.
# =============================================================================
NUMBER_OF_IDNUMS_DEFUNCT = 8 # DEFUNCT BUT DO NOT REMOVE OR ALTER. EIGHT.
# ... In older versions: determined number of ID number fields.
# (Now this is arbitrary.) Still used to support old clients.
# =============================================================================
# File types
# =============================================================================
[docs]class FileType(object):
"""
Used to represent output formats and their file extensions.
"""
HTML = "html"
PDF = "pdf"
XML = "xml"
# =============================================================================
# Encodings
# =============================================================================
ASCII = "ascii"
UTF8 = "utf8"
# =============================================================================
# Launching
# =============================================================================
DEFAULT_FLOWER_ADDRESS = "127.0.0.1"
DEFAULT_FLOWER_PORT = (
5555 # http://docs.celeryproject.org/en/latest/userguide/monitoring.html
)
# =============================================================================
# Webview constants
# =============================================================================
DEFAULT_ROWS_PER_PAGE = 25
DEVICE_NAME_FOR_SERVER = "server" # Do not alter.
USER_NAME_FOR_SYSTEM = "system" # Do not alter.
# Address to download Windows/Mac versions:
GITHUB_RELEASES_URL = (
"https://github.com/ucam-department-of-psychiatry/camcops/releases/"
)
MINIMUM_PASSWORD_LENGTH = 10
OBSCURE_PHONE_ASTERISKS = "*" * 10
OBSCURE_EMAIL_ASTERISKS = "*" * 5
# =============================================================================
# Other display constants
# =============================================================================
JSON_INDENT = 4
# =============================================================================
# Date formats
# =============================================================================
# =============================================================================
# FHIR constants
# =============================================================================
CAMCOPS_DEFAULT_FHIR_APP_ID = "camcops"
# =============================================================================
# Permitted values in fields: some common settings
# =============================================================================
[docs]class PV(object):
"""
Collections of permitted values.
"""
BIT = (0, 1)
# Red/Yellow/Green
RYG = ("R", "Y", "G")
NO_CHAR = "N"
YES_CHAR = "Y"
# Database values:
SEX_FEMALE = "F"
SEX_MALE = "M"
SEX_OTHER_UNSPECIFIED = "X"
POSSIBLE_SEX_VALUES = (SEX_FEMALE, SEX_MALE, SEX_OTHER_UNSPECIFIED)
# =============================================================================
# Field names/specifications
# =============================================================================
# Do not alter these!
TABLET_ID_FIELD = "id"
MOVE_OFF_TABLET_FIELD = "_move_off_tablet"
CLIENT_DATE_FIELD = "when_last_modified"
# Used for old client support, and TSV field names etc.:
FP_ID_NUM = "idnum"
FP_ID_DESC = "iddesc"
FP_ID_SHORT_DESC = "idshortdesc"
# Additional fields for some exports:
EXTRA_IDNUM_FIELD_PREFIX = "_patient_idnum"
EXTRA_TASK_TABLENAME_FIELD = "_task_tablename"
EXTRA_TASK_SERVER_PK_FIELD = "_task_pk"
EXTRA_COMMENT_PREFIX = "(EXTRA) "
# =============================================================================
# Other special values
# =============================================================================
CAMCOPS_URL = "https://camcops.readthedocs.io/"
ERA_NOW = "NOW" # defines the current era in database records
# =============================================================================
# PDF engine: now always "pdfkit".
# =============================================================================
# PDF_ENGINE = "xhtml2pdf" # working
PDF_ENGINE = "pdfkit" # working
# PDF_ENGINE = "weasyprint" # working but table <tr> element bugs
# ... value must be one of: xhtml2pdf, weasyprint, pdfkit
# =============================================================================
# Simple constants for HTML/plots/display
# =============================================================================
[docs]class PlotDefaults(object):
"""
Defaults used with matplotlib plotting.
"""
DEFAULT_PLOT_DPI = 300
FULLWIDTH_PLOT_WIDTH = 6.7 # inches: full width is ~170mm
# zorder parameter:
# - higher = on top
# - defaults relate to the type of thing being plotted:
# https://matplotlib.org/3.1.1/gallery/misc/zorder_demo.html
# - Patch / PatchCollection = 1
# - Line2D / LineCollection = 2
# - Text = 3
# - within a Line2D object (points and lines), the default is
# "markers on top of lines"
ZORDER_PRESET_LINES = 1
ZORDER_PRESET_LABELS = 2
ZORDER_DATA_LINES_POINTS = 3 # the default
[docs]class MatplotlibConstants(object):
"""
Constants used by matplotlib
"""
# https://matplotlib.org/tutorials/colors/colors.html
COLOUR_BLACK = "k"
COLOUR_BLUE = "b"
COLOUR_GREEN = "g"
COLOUR_GREY_50 = "0.5"
COLOUR_GREY_90 = "0.9" # 0.9 is close to white (0 black, 1 white)
COLOUR_RED = "r"
# https://matplotlib.org/gallery/lines_bars_and_markers/line_styles_reference.html # noqa
# https://matplotlib.org/3.1.0/gallery/lines_bars_and_markers/linestyles.html # noqa
LINESTYLE_DOTTED = ":"
LINESTYLE_SOLID = "-"
LINESTYLE_NONE = "None"
# https://matplotlib.org/3.1.1/api/markers_api.html
MARKER_CIRCLE = "o"
MARKER_NONE = "" # also "None", " "
MARKER_PLUS = "+"
MARKER_STAR = "*"
WHOLE_PANEL = 111 # as in: ax = fig.add_subplot(111)
# Debugging option
USE_SVG_IN_HTML = True # set to False for PNG debugging
# =============================================================================
# CSS/HTML constants
# =============================================================================
CSS_PAGED_MEDIA = PDF_ENGINE != "pdfkit"
WKHTMLTOPDF_OPTIONS = { # dict for pdfkit
"page-size": "A4",
"margin-left": "20mm",
"margin-right": "20mm",
"margin-top": "21mm", # from paper edge down to top of content?
# ... inaccurate
"margin-bottom": "24mm", # from paper edge up to bottom of content?
# ... inaccurate
"header-spacing": "3", # mm, from content up to bottom of header
"footer-spacing": "3", # mm, from content down to top of footer
"quiet": "", # Suppress "Loading pages (1/6)" etc.
"enable-local-file-access": "",
}
[docs]class CssClass(object):
"""
CSS names.
Values should match e.g. ``camcops_server/templates/css/css_base.mako``.
"""
BAD_ID_POLICY_MILD = "badidpolicy_mild"
BAD_ID_POLICY_SEVERE = "badidpolicy_severe"
BANNER = "banner"
BANNER_REFERRAL_GENERAL_ADULT = "banner_referral_general_adult"
BANNER_REFERRAL_OLD_AGE = "banner_referral_old_age"
BANNER_REFERRAL_SUBSTANCE_MISUSE = "banner_referral_substance_misuse"
CENTREGAP_TD = "centregap_td"
CLINICIAN = "clinician"
COPYRIGHT = "copyright"
CTV_DATELIMIT_START = "ctv_datelimit_start"
CTV_DATELIMIT_END = "ctv_datelimit_end"
CTV_TASKHEADING = "ctv_taskheading"
CTV_FIELDHEADING = "ctv_fieldheading"
CTV_FIELDSUBHEADING = "ctv_fieldsubheading"
CTV_FIELDDESCRIPTION = "ctv_fielddescription"
CTV_FIELDCONTENT = "ctv_fieldcontent"
CTV_WARNINGS = "ctv_warnings"
ERROR = "error"
EXPLANATION = "explanation"
EXTRADETAIL = "extradetail"
EXTRADETAIL2 = "extradetail2"
FILTER = "filter"
FILTERS = "filters"
FIGURE = "figure"
FOOTNOTES = "footnotes"
FORMTITLE = "formtitle"
GENERAL = "general"
GREEN = "green"
HANGINGINDENT = "hangingindent"
HEADING = "heading"
HIGHLIGHT = "highlight"
IMAGE_TD = "image_td"
IMPORTANT = "important"
INCOMPLETE = "incomplete"
INDENT = "indent"
INDENTED = "indented"
LIVE_ON_TABLET = "live_on_tablet"
LOGO_LEFT = "logo_left"
LOGO_RIGHT = "logo_right"
NAVIGATION = "navigation"
NOBORDER = "noborder"
NOBORDERPHOTO = "noborderphoto"
OFFICE = "office"
PATIENT = "patient"
PHOTO = "photo"
PDF_LOGO_HEADER = "pdf_logo_header"
QA_TABLE_HEADING = "qa_tableheading"
RESPONDENT = "respondent"
SIGNATURE = "signature"
SIGNATURE_LABEL = "signature_label"
SMALLPRINT = "smallprint"
SPECIALNOTE = "specialnote"
SUBHEADING = "subheading"
SUBSUBHEADING = "subsubheading"
SUMMARY = "summary"
SUPERUSER = "superuser"
TASKCONFIG = "taskconfig"
TASKDETAIL = "taskdetail"
TASKHEADER = "taskheader"
TRACKERHEADER = "trackerheader"
TRACKER_ALL_CONSISTENT = "tracker_all_consistent"
WARNING = "warning"
WEB_LOGO_HEADER = "web_logo_header"
# =============================================================================
# Task constants
# =============================================================================
ANON_PATIENT = "XXXX"
DATA_COLLECTION_ONLY_DIV = """
<div class="copyright">
Reproduction of the original task/scale is not permitted.
This is a data collection tool only; use it only in conjunction with
a licensed copy of the original task.
</div>
"""
DATA_COLLECTION_UNLESS_UPGRADED_DIV = """
<div class="copyright">
Reproduction of the original task/scale is not permitted as part of
CamCOPS. This is a data collection tool only, unless the hosting
institution has supplied task text via its own permissions. <b>Any such
text, if shown here, is not part of CamCOPS, and copyright in
it belongs to the original task’s copyright holder.</b> Use this data
collection tool only in conjunction with a licensed copy of the
original task.
</div>
"""
ICD10_COPYRIGHT_DIV = """
<div class="copyright">
ICD-10 criteria: Copyright © 1992 World Health Organization.
Used here with permission.
</div>
"""
INVALID_VALUE = "[invalid_value]"
QUESTION = "Question"
SPREADSHEET_PATIENT_FIELD_PREFIX = "_patient_"
# =============================================================================
# Config constants
# =============================================================================
CONFIG_FILE_SITE_SECTION = "site"
CONFIG_FILE_SERVER_SECTION = "server"
CONFIG_FILE_EXPORT_SECTION = "export"
CONFIG_FILE_SMS_BACKEND_PREFIX = "sms_backend"
[docs]class ConfigParamSite(object):
"""
Parameters allowed in the main ``[site]`` section of the CamCOPS config
file.
"""
ALLOW_INSECURE_COOKIES = "ALLOW_INSECURE_COOKIES"
CAMCOPS_LOGO_FILE_ABSOLUTE = "CAMCOPS_LOGO_FILE_ABSOLUTE"
CLIENT_API_LOGLEVEL = "CLIENT_API_LOGLEVEL"
CTV_FILENAME_SPEC = "CTV_FILENAME_SPEC"
DB_URL = "DB_URL"
DB_ECHO = "DB_ECHO"
DISABLE_PASSWORD_AUTOCOMPLETE = "DISABLE_PASSWORD_AUTOCOMPLETE"
EMAIL_FROM = "EMAIL_FROM"
EMAIL_HOST = "EMAIL_HOST"
EMAIL_HOST_PASSWORD = "EMAIL_HOST_PASSWORD"
EMAIL_HOST_PASSWORD_GNU_PASS_LOOKUP = "EMAIL_HOST_PASSWORD_GNU_PASS_LOOKUP"
EMAIL_HOST_USERNAME = "EMAIL_HOST_USERNAME"
EMAIL_PORT = "EMAIL_PORT"
EMAIL_REPLY_TO = "EMAIL_REPLY_TO"
EMAIL_SENDER = "EMAIL_SENDER"
EMAIL_USE_TLS = "EMAIL_USE_TLS"
EXTRA_STRING_FILES = "EXTRA_STRING_FILES"
LANGUAGE = "LANGUAGE"
LOCAL_INSTITUTION_URL = "LOCAL_INSTITUTION_URL"
LOCAL_LOGO_FILE_ABSOLUTE = "LOCAL_LOGO_FILE_ABSOLUTE"
LOCKOUT_DURATION_INCREMENT_MINUTES = "LOCKOUT_DURATION_INCREMENT_MINUTES"
LOCKOUT_THRESHOLD = "LOCKOUT_THRESHOLD"
MFA_METHODS = "MFA_METHODS"
MFA_TIMEOUT_S = "MFA_TIMEOUT_S"
PASSWORD_CHANGE_FREQUENCY_DAYS = "PASSWORD_CHANGE_FREQUENCY_DAYS"
PATIENT_SPEC = "PATIENT_SPEC"
PATIENT_SPEC_IF_ANONYMOUS = "PATIENT_SPEC_IF_ANONYMOUS"
PERMIT_IMMEDIATE_DOWNLOADS = "PERMIT_IMMEDIATE_DOWNLOADS"
REGION_CODE = "REGION_CODE"
RESTRICTED_TASKS = "RESTRICTED_TASKS"
SESSION_COOKIE_SECRET = "SESSION_COOKIE_SECRET"
SESSION_TIMEOUT_MINUTES = "SESSION_TIMEOUT_MINUTES"
SESSION_CHECK_USER_IP = "SESSION_CHECK_USER_IP"
SMS_BACKEND = "SMS_BACKEND"
SNOMED_TASK_XML_FILENAME = "SNOMED_TASK_XML_FILENAME"
SNOMED_ICD9_XML_FILENAME = "SNOMED_ICD9_XML_FILENAME"
SNOMED_ICD10_XML_FILENAME = "SNOMED_ICD10_XML_FILENAME"
TASK_FILENAME_SPEC = "TASK_FILENAME_SPEC"
TRACKER_FILENAME_SPEC = "TRACKER_FILENAME_SPEC"
USER_DOWNLOAD_DIR = "USER_DOWNLOAD_DIR"
USER_DOWNLOAD_FILE_LIFETIME_MIN = "USER_DOWNLOAD_FILE_LIFETIME_MIN"
USER_DOWNLOAD_MAX_SPACE_MB = "USER_DOWNLOAD_MAX_SPACE_MB"
WEBVIEW_LOGLEVEL = "WEBVIEW_LOGLEVEL"
WKHTMLTOPDF_FILENAME = "WKHTMLTOPDF_FILENAME"
[docs]class ConfigParamServer(object):
"""
Parameters allowed in the web server (``[server]``) section of the CamCOPS
config file.
"""
CHERRYPY_LOG_SCREEN = "CHERRYPY_LOG_SCREEN"
CHERRYPY_ROOT_PATH = "CHERRYPY_ROOT_PATH"
CHERRYPY_SERVER_NAME = "CHERRYPY_SERVER_NAME"
CHERRYPY_THREADS_MAX = "CHERRYPY_THREADS_MAX"
CHERRYPY_THREADS_START = "CHERRYPY_THREADS_START"
DEBUG_REVERSE_PROXY = "DEBUG_REVERSE_PROXY"
DEBUG_SHOW_GUNICORN_OPTIONS = "DEBUG_SHOW_GUNICORN_OPTIONS"
DEBUG_TOOLBAR = "DEBUG_TOOLBAR"
EXTERNAL_URL_SCHEME = "EXTERNAL_URL_SCHEME"
EXTERNAL_SERVER_NAME = "EXTERNAL_SERVER_NAME"
EXTERNAL_SERVER_PORT = "EXTERNAL_SERVER_PORT"
EXTERNAL_SCRIPT_NAME = "EXTERNAL_SCRIPT_NAME"
GUNICORN_DEBUG_RELOAD = "GUNICORN_DEBUG_RELOAD"
GUNICORN_NUM_WORKERS = "GUNICORN_NUM_WORKERS"
GUNICORN_TIMEOUT_S = "GUNICORN_TIMEOUT_S"
HOST = "HOST"
PORT = "PORT"
PROXY_HTTP_HOST = "PROXY_HTTP_HOST"
PROXY_REMOTE_ADDR = "PROXY_REMOTE_ADDR"
PROXY_REWRITE_PATH_INFO = "PROXY_REWRITE_PATH_INFO"
PROXY_SCRIPT_NAME = "PROXY_SCRIPT_NAME"
PROXY_SERVER_NAME = "PROXY_SERVER_NAME"
PROXY_SERVER_PORT = "PROXY_SERVER_PORT"
PROXY_URL_SCHEME = "PROXY_URL_SCHEME"
SHOW_REQUEST_IMMEDIATELY = "SHOW_REQUEST_IMMEDIATELY"
SHOW_REQUESTS = "SHOW_REQUESTS"
SHOW_RESPONSE = "SHOW_RESPONSE"
SHOW_TIMING = "SHOW_TIMING"
SSL_CERTIFICATE = "SSL_CERTIFICATE"
SSL_PRIVATE_KEY = "SSL_PRIVATE_KEY"
STATIC_CACHE_DURATION_S = "STATIC_CACHE_DURATION_S"
TRUSTED_PROXY_HEADERS = "TRUSTED_PROXY_HEADERS"
UNIX_DOMAIN_SOCKET = "UNIX_DOMAIN_SOCKET"
[docs]class ConfigParamExportGeneral(object):
"""
Parameters allowed in the ``[export]`` section of the CamCOPS config file.
"""
CELERY_BEAT_EXTRA_ARGS = "CELERY_BEAT_EXTRA_ARGS"
CELERY_BEAT_SCHEDULE_DATABASE = "CELERY_BEAT_SCHEDULE_DATABASE"
CELERY_BROKER_URL = "CELERY_BROKER_URL"
CELERY_WORKER_EXTRA_ARGS = "CELERY_WORKER_EXTRA_ARGS"
CELERY_EXPORT_TASK_RATE_LIMIT = "CELERY_EXPORT_TASK_RATE_LIMIT"
EXPORT_LOCKDIR = "EXPORT_LOCKDIR"
RECIPIENTS = "RECIPIENTS"
SCHEDULE = "SCHEDULE"
SCHEDULE_TIMEZONE = "SCHEDULE_TIMEZONE"
[docs]class ConfigParamExportRecipient(object):
"""
Possible configuration file parameters that relate to "export recipient"
definitions.
"""
ALL_GROUPS = "ALL_GROUPS"
DB_ADD_SUMMARIES = "DB_ADD_SUMMARIES"
DB_ECHO = "DB_ECHO"
DB_INCLUDE_BLOBS = "DB_INCLUDE_BLOBS"
DB_PATIENT_ID_PER_ROW = "DB_PATIENT_ID_PER_ROW"
DB_URL = "DB_URL"
EMAIL_BCC = "EMAIL_BCC"
EMAIL_BODY = "EMAIL_BODY"
EMAIL_BODY_IS_HTML = "EMAIL_BODY_IS_HTML"
EMAIL_CC = "EMAIL_CC"
EMAIL_KEEP_MESSAGE = "EMAIL_KEEP_MESSAGE"
EMAIL_RECIPIENTS = "EMAIL_RECIPIENTS"
EMAIL_PATIENT_SPEC = "EMAIL_PATIENT_SPEC"
EMAIL_PATIENT_SPEC_IF_ANONYMOUS = "EMAIL_PATIENT_SPEC_IF_ANONYMOUS"
EMAIL_SUBJECT = "EMAIL_SUBJECT"
EMAIL_TIMEOUT = "EMAIL_TIMEOUT"
EMAIL_TO = "EMAIL_TO"
END_DATETIME_UTC = "END_DATETIME_UTC"
FHIR_API_URL = "FHIR_API_URL"
FHIR_APP_ID = "FHIR_APP_ID"
FHIR_APP_SECRET = "FHIR_APP_SECRET"
FHIR_LAUNCH_TOKEN = "FHIR_LAUNCH_TOKEN"
FHIR_CONCURRENT = "FHIR_CONCURRENT"
FILE_EXPORT_RIO_METADATA = "FILE_EXPORT_RIO_METADATA"
FILE_FILENAME_SPEC = "FILE_FILENAME_SPEC"
FILE_MAKE_DIRECTORY = "FILE_MAKE_DIRECTORY"
FILE_OVERWRITE_FILES = "FILE_OVERWRITE_FILES"
FILE_PATIENT_SPEC = "FILE_PATIENT_SPEC"
FILE_PATIENT_SPEC_IF_ANONYMOUS = "FILE_PATIENT_SPEC_IF_ANONYMOUS"
FILE_SCRIPT_AFTER_EXPORT = "FILE_SCRIPT_AFTER_EXPORT"
FINALIZED_ONLY = "FINALIZED_ONLY"
GROUPS = "GROUPS"
HL7_DEBUG_DIVERT_TO_FILE = "HL7_DEBUG_DIVERT_TO_FILE"
HL7_DEBUG_TREAT_DIVERTED_AS_SENT = "HL7_DEBUG_TREAT_DIVERTED_AS_SENT"
HL7_HOST = "HL7_HOST"
HL7_KEEP_MESSAGE = "HL7_KEEP_MESSAGE"
HL7_KEEP_REPLY = "HL7_KEEP_REPLY"
HL7_NETWORK_TIMEOUT_MS = "HL7_NETWORK_TIMEOUT_MS"
HL7_PING_FIRST = "HL7_PING_FIRST"
HL7_PORT = "HL7_PORT"
IDNUM_AA_PREFIX = "IDNUM_AA_" # unusual; prefix not parameter
IDNUM_TYPE_PREFIX = "IDNUM_TYPE_" # unusual; prefix not parameter
INCLUDE_ANONYMOUS = "INCLUDE_ANONYMOUS"
PRIMARY_IDNUM = "PRIMARY_IDNUM"
PUSH = "PUSH"
REDCAP_API_KEY = "REDCAP_API_KEY"
REDCAP_API_URL = "REDCAP_API_URL"
REDCAP_FIELDMAP_FILENAME = "REDCAP_FIELDMAP_FILENAME"
REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY = (
"REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY"
)
RIO_DOCUMENT_TYPE = "RIO_DOCUMENT_TYPE"
RIO_IDNUM = "RIO_IDNUM"
RIO_UPLOADING_USER = "RIO_UPLOADING_USER"
START_DATETIME_UTC = "START_DATETIME_UTC"
TASK_FORMAT = "TASK_FORMAT"
TASKS = "TASKS"
TRANSMISSION_METHOD = "TRANSMISSION_METHOD"
XML_FIELD_COMMENTS = "XML_FIELD_COMMENTS"
[docs]class MfaMethod:
"""
Open multi-factor authentication (MFA) standards are defined in RFC 4226
(HOTP: An HMAC-Based One-Time Password Algorithm) and in RFC 6238 (TOTP:
Time-Based One-Time Password Algorithm).
HMAC: Hash-based Message Authentication Code
https://en.wikipedia.org/wiki/HMAC
Values must be in lower case.
"""
HOTP_EMAIL = "hotp_email" # Send a code by email
HOTP_SMS = "hotp_sms" # Send a code by SMS
NO_MFA = "no_mfa" # No multi-factor authentication; username/password only
TOTP = "totp" # Use an app such as Google Authenticator, Twilio Authy
[docs] @classmethod
def valid(cls, method: str) -> bool:
"""
Is the method a known MFA method (including "no MFA")?
"""
return method in (cls.HOTP_EMAIL, cls.HOTP_SMS, cls.NO_MFA, cls.TOTP)
[docs] @classmethod
def requires_second_step(cls, method: str) -> bool:
"""
Does the method require a second authentication step?
"""
return method in (cls.HOTP_EMAIL, cls.HOTP_SMS, cls.TOTP)
[docs] @classmethod
def clean(cls, method: str) -> str:
"""
Returns a valid method, even if the input isn't.
Defaults to NO_MFA.
"""
if cls.requires_second_step(method):
return method
else:
# e.g. NO_MFA, None, "none", other junk
return cls.NO_MFA
[docs]class SmsBackendNames:
"""
Names of allowed SMS backends.
"""
CONSOLE = "console"
KAPOW = "kapow"
TWILIO = "twilio"
[docs]class DockerConstants(object):
"""
Constants for the Docker environment.
"""
# Directories
DOCKER_CAMCOPS_ROOT_DIR = "/camcops"
CONFIG_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "cfg")
TMP_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "tmp")
VENV_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "venv")
DEFAULT_USER_DOWNLOAD_DIR = os.path.join(TMP_DIR, "user_downloads")
DEFAULT_LOCKDIR = os.path.join(TMP_DIR, "lock")
# Container (internal) names
CONTAINER_RABBITMQ = "rabbitmq"
CONTAINER_MYSQL = "mysql"
# Other
CELERY_BROKER_URL = f"amqp://{CONTAINER_RABBITMQ}:{Ports.AMQP}/"
DEFAULT_MYSQL_CAMCOPS_USER = "camcops"
HOST = "0.0.0.0"
# ... not "localhost" or "127.0.0.1"; see
# https://nickjanetakis.com/blog/docker-tip-54-fixing-connection-reset-by-peer-or-similar-errors # noqa
# =============================================================================
# Configuration defaults
# =============================================================================
[docs]class ConfigDefaults(object):
"""
Contains default values for the config, plus some cosmetic defaults for
generating specimen config files.
- Re ``CHERRYPY_THREADS_MAX``: beware the default MySQL connection limit of
151; https://dev.mysql.com/doc/refman/5.7/en/too-many-connections.html
"""
# [site] section
ALLOW_INSECURE_COOKIES = False
CAMCOPS_LOGO_FILE_ABSOLUTE = os.path.join(
STATIC_ROOT_DIR, "logo_camcops.png"
)
CLIENT_API_LOGLEVEL = logging.INFO
CLIENT_API_LOGLEVEL_TEXTFORMAT = "info" # should match CLIENT_API_LOGLEVEL
DB_DATABASE = "camcops" # for demo configs only
DB_ECHO = False
DB_PORT = Ports.MYSQL # for demo configs only
DB_SERVER = "localhost" # for demo configs only
DB_USER = "YYY_USERNAME_REPLACE_ME" # cosmetic; for demo configs only
DB_PASSWORD = "ZZZ_PASSWORD_REPLACE_ME" # cosmetic; for demo configs only
DISABLE_PASSWORD_AUTOCOMPLETE = True
EMAIL_PORT = Ports.SMTP_MSA
EMAIL_USE_TLS = True
EXTERNAL_URL_SCHEME = UriSchemes.HTTPS
EXTERNAL_SERVER_NAME = "localhost"
EXTRA_STRING_FILES = os.path.join(
DEFAULT_EXTRA_STRINGS_DIR, "*.xml"
) # cosmetic; for demo configs only
LANGUAGE = DEFAULT_LOCALE
LOCAL_INSTITUTION_URL = "https://camcops.readthedocs.io/"
LOCAL_LOGO_FILE_ABSOLUTE = os.path.join(STATIC_ROOT_DIR, "logo_local.png")
LOCKOUT_DURATION_INCREMENT_MINUTES = 10
LOCKOUT_THRESHOLD = 10
MFA_METHODS = [MfaMethod.NO_MFA]
MFA_TIMEOUT_S = 600 # zero for never
PASSWORD_CHANGE_FREQUENCY_DAYS = 0 # zero for never
PATIENT_SPEC_IF_ANONYMOUS = "anonymous"
PERMIT_IMMEDIATE_DOWNLOADS = False
REGION_CODE = "GB"
SESSION_CHECK_USER_IP = True
SESSION_TIMEOUT_MINUTES = 30
SMS_BACKEND = SmsBackendNames.CONSOLE
USER_DOWNLOAD_DIR = (
LINUX_DEFAULT_USER_DOWNLOAD_DIR # for demo configs only
)
USER_DOWNLOAD_FILE_LIFETIME_MIN = 60
USER_DOWNLOAD_MAX_SPACE_MB = 100
WEBVIEW_LOGLEVEL = logging.INFO
WEBVIEW_LOGLEVEL_TEXTFORMAT = "info" # should match WEBVIEW_LOGLEVEL
# Not yet user-configurable
PLOT_FONTSIZE = 8
# [server] section
CHERRYPY_LOG_SCREEN = True
CHERRYPY_ROOT_PATH = "/"
CHERRYPY_SERVER_NAME = "localhost"
CHERRYPY_THREADS_MAX = 100
CHERRYPY_THREADS_START = 10
DEBUG_REVERSE_PROXY = False
DEBUG_SHOW_GUNICORN_OPTIONS = False
DEBUG_TOOLBAR = False
GUNICORN_DEBUG_RELOAD = False
if ENVVAR_GENERATING_CAMCOPS_DOCS in os.environ:
GUNICORN_NUM_WORKERS = 16
else:
GUNICORN_NUM_WORKERS = 2 * multiprocessing.cpu_count()
GUNICORN_TIMEOUT_S = 30
HOST = "127.0.0.1"
PORT = Ports.ALTERNATIVE_HTTP_NONSTANDARD
PROXY_REWRITE_PATH_INFO = False
SHOW_REQUEST_IMMEDIATELY = False
SHOW_REQUESTS = False
SHOW_RESPONSE = False
SHOW_TIMING = False
SSL_CERTIFICATE = ""
SSL_PRIVATE_KEY = ""
STATIC_CACHE_DURATION_S = 1 * 24 * 60 * 60 # 1 day, in seconds = 86400
# [export] section
CELERY_BROKER_URL = "amqp://"
CELERY_BEAT_SCHEDULE_DATABASE = os.path.join(
LINUX_DEFAULT_LOCK_DIR, "camcops_celerybeat_schedule"
) # for demo configs only
EXPORT_LOCKDIR = LINUX_DEFAULT_LOCK_DIR # for demo configs only
SCHEDULE_TIMEZONE = "UTC"
# Individual export recipients
# DB_ECHO: as above
ALL_GROUPS = False
DB_ADD_SUMMARIES = True
DB_INCLUDE_BLOBS = True
DB_PATIENT_ID_PER_ROW = False
EMAIL_BODY_IS_HTML = False
EMAIL_KEEP_MESSAGE = False
FHIR_APP_ID = CAMCOPS_DEFAULT_FHIR_APP_ID
FILE_EXPORT_RIO_METADATA = False
FILE_MAKE_DIRECTORY = False
FILE_OVERWRITE_FILES = False
FILE_PATIENT_SPEC_IF_ANONYMOUS = "anonymous"
FINALIZED_ONLY = True
HL7_DEBUG_DIVERT_TO_FILE = False
HL7_DEBUG_TREAT_DIVERTED_AS_SENT = False
HL7_KEEP_MESSAGE = False
HL7_KEEP_REPLY = False
HL7_NETWORK_TIMEOUT_MS = 10000
HL7_PING_FIRST = True
HL7_PORT = Ports.HL7_MLLP
INCLUDE_ANONYMOUS = False
PUSH = False
REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY = True
TASK_FORMAT = FileType.PDF
XML_FIELD_COMMENTS = True
[docs] def __init__(self, docker: bool = False) -> None:
"""
Args:
docker:
Amend defaults so it works within a Docker Compose application
without much fiddling?
Defaults for use within Docker:
- Note that a URL to another container/service looks like
``protocol://container:port/``. Values here must match the Docker
Compose file.
"""
self._docker = docker
if docker:
self.CELERY_BROKER_URL = DockerConstants.CELERY_BROKER_URL
self.CELERY_BEAT_SCHEDULE_DATABASE = os.path.join(
DockerConstants.DEFAULT_LOCKDIR, "camcops_celerybeat_schedule"
)
self.DB_SERVER = "@@db_server@@"
self.DB_PORT = "@@db_port@@"
self.DB_USER = "@@db_user@@"
self.DB_PASSWORD = "@@db_password@@"
self.DB_DATABASE = "@@db_database@@"
self.EXPORT_LOCKDIR = DockerConstants.DEFAULT_LOCKDIR
self.HOST = DockerConstants.HOST
self.SSL_CERTIFICATE = "@@ssl_certificate@@"
self.SSL_PRIVATE_KEY = "@@ssl_private_key@@"
self.USER_DOWNLOAD_DIR = DockerConstants.DEFAULT_USER_DOWNLOAD_DIR
@property
def demo_db_url(self) -> str:
"""
The demonstration SQLAlchemy URL.
"""
# mysqlclient ("mysqldb") for Docker -- the C-based fast one
# pymysql for standard installations -- fewer dependencies
driver = "mysqldb" if self._docker else "pymysql"
return make_mysql_url(
driver=driver,
host=self.DB_SERVER,
port=self.DB_PORT,
username=self.DB_USER,
password=self.DB_PASSWORD,
dbname=self.DB_DATABASE,
)
# =============================================================================
# String length limits
# =============================================================================
#
# Note: "191" relates to MySQL indexing of VARCHAR fields using utf8mb4;
# - https://stackoverflow.com/questions/6172798/
# - https://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html
# There are alternative workarounds, but these fields are OK at 191.
#
# Re commenting variables in Sphinx:
# - https://stackoverflow.com/questions/20227051/how-to-document-a-module-constant-in-python # noqa
# - http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autodata # noqa
# - URLs are a bit tricky; the colons get re-interpreted sometimes; it seems
# that inserting an extra colon after "#:", e.g. "#: : see http://somewhere"
# works.
# - If a comment needs "# noqa" for the linter, then make it a docstring,
# because it will appear in the Sphinx string.
class StringLengths:
# -------------------------------------------------------------------------
# Primary
# -------------------------------------------------------------------------
AUDIT_SOURCE_MAX_LEN = 20 #: our choice based on use in CamCOPS code
BASE32_MAX_LEN = 32
#: : See https://docs.python.org/3.7/library/codecs.html#standard-encodings. # noqa: E501
#: Probably ~18 so give it some headroom.
CHARSET_MAX_LEN = 64
CURRENCY_MAX_LEN = 3 #: Can have Unicode symbols like € or text like "GBP"
DATABASE_TITLE_MIN_LEN = 1
DATABASE_TITLE_MAX_LEN = 255 #: our choice
#: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index;
#: must be compatible with tablet
DEVICE_NAME_MAX_LEN = 191
#: : See https://en.wikipedia.org/wiki/Email_address.
EMAIL_ADDRESS_MAX_LEN = 255
#: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
EXPORT_RECIPIENT_NAME_MIN_LEN = 1
EXPORT_RECIPIENT_NAME_MAX_LEN = 191
#: Our choice
FILTER_TEXT_MAX_LEN = 255
#: Our choice; used for user full names on the server
FULLNAME_MAX_LEN = 255
#: Our choice
FILESPEC_MAX_LEN = 255
GROUP_DESCRIPTION_MIN_LEN = 1
#: Our choice
GROUP_DESCRIPTION_MAX_LEN = 255
GROUP_NAME_MIN_LEN = 1
#: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
GROUP_NAME_MAX_LEN = 191
HASHED_PW_MAX_LEN = 60
"""
:
We use ``bcrypt``. Empirically, the length of its hashed output is:
.. code-block:: none
"$2a$" (4)
cost parameter, e.g. "$09" for 9 rounds (3)
b64-enc 128-bit salt (22)
b64enc 184-bit hash (31)
... total 60
See https://stackoverflow.com/questions/5881169/what-column-type-length-should-i-use-for-storing-a-bcrypt-hashed-password-in-a-d
""" # noqa
HL7_AA_MAX_LEN = 20
"""
- The AA appears in Table 4.6 "Extended composite ID", p46-47 of
hl7guide-1-4-2012-08.pdf
- ... but is defined in Table 4.9 "Entity Identifier", p50, in which:
- component 2 is the Assigning Authority (see component 1)
- component 2 is also a Namespace ID with a length of 20
- ... and multiple other examples of an Assigning Authority being one
example of a Namespace ID
- ... and examples are in Table 0363 (p229 of the PDF), which are all
3-char.
- ... and several other examples of "Namespace ID" being of length 1..20
meaning 1-20.
"""
HL7_ID_TYPE_MAX_LEN = 5
"""
Table 4.6 "Extended composite ID", p46-47 of hl7guide-1-4-2012-08.pdf,
and Table 0203 "Identifier type", p204 of that PDF, in Appendix B.
"""
HOSTNAME_MAX_LEN = 255
"""
FQDN; see
https://stackoverflow.com/questions/8724954/what-is-the-maximum-number-of-characters-for-a-host-name-in-unix
""" # noqa
ICD9_CODE_MAX_LEN = 6
"""
Longest is "xxx.xx"; thus, 6; see
https://www.cms.gov/Medicare/Quality-Initiatives-Patient-Assessment-Instruments/HospitalQualityInits/Downloads/HospitalAppendix_F.pdf
""" # noqa
#: longest is e.g. "F00.000"; "F10.202"; thus, 7
ICD10_CODE_MAX_LEN = 7
#: Our choice
ID_DESCRIPTOR_MAX_LEN = 255
#: Our choice
ID_POLICY_MAX_LEN = 255
#: : See http://stackoverflow.com/questions/166132
IP_ADDRESS_MAX_LEN = 45
ISO8601_DATETIME_STRING_MAX_LEN = 32
"""
Max length e.g.
.. code-block:: none
2013-07-24T20:04:07.123456+01:00
1234567890123456789012345678901234567890
(with punctuation, T, microseconds, colon in timezone).
"""
#: See :func:`cardinal_pythonlib.datetimefunc.duration_to_iso`
ISO8601_DURATION_STRING_MAX_LEN = 29
#: Two-letter language, hyphen, 2/3-letter country
LANGUAGE_CODE_MAX_LEN = 6
# LONGBLOB_LONGTEXT_MAX_LEN = (2 ** 32) - 1
# ... https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html
# The longest is currently "hotp_email" (ViewArg, cc_pyramid.py)
MFA_METHOD_MAX_LEN = 20
#: See https://stackoverflow.com/questions/643690
MIMETYPE_MAX_LEN = 255
#: For forename and surname, each; our choice but must match tablet
PATIENT_NAME_MAX_LEN = 255
PHONE_NUMBER_MAX_LEN = 128
RFC_2822_DATE_MAX_LEN = 31
"""
e.g. ``Fri, 09 Nov 2001 01:08:47 -0000``; 3.3 in
https://tools.ietf.org/html/rfc2822, assuming extra white space not added
"""
#: for export; our choice based on use in CamCOPS code
SENDING_FORMAT_MAX_LEN = 50
#: our choice; 64 bytes => 512 bits, which is a lot in 2017
SESSION_TOKEN_MAX_BYTES = 64
SQL_SEARCH_LITERAL_MIN_LENGTH = 0 # permits: LIKE ''
SQL_SEARCH_LITERAL_MAX_LENGTH = 255 # arbitrary
TABLENAME_MAX_LEN = 128
"""
For
- MySQL: 64 -- https://dev.mysql.com/doc/refman/5.7/en/identifiers.html
- SQL Server: 128 -- https://msdn.microsoft.com/en-us/library/ms191240.aspx
- Oracle: 32, then 128 from v12.2 (2017)
""" # noqa: E501
TASK_SUMMARY_TEXT_FIELD_DEFAULT_MAX_LEN = 50
"""
... our choice, contains short strings like "normal", "abnormal", "severe".
Easy to change, since it's only used when exporting summaries, and not in
the core database.
"""
#: Our choice
URL_MAX_LEN = 255
USERNAME_CAMCOPS_MIN_LEN = 1
#: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index
USERNAME_CAMCOPS_MAX_LEN = 191
#: Our choice
USERNAME_EXTERNAL_MAX_LEN = 255
# -------------------------------------------------------------------------
# Derived
# -------------------------------------------------------------------------
DIAGNOSTIC_CODE_MAX_LEN = max(ICD9_CODE_MAX_LEN, ICD10_CODE_MAX_LEN)
SESSION_TOKEN_MAX_LEN = len(
create_base64encoded_randomness(SESSION_TOKEN_MAX_BYTES)
)
# =============================================================================
# FHIR string constants
# =============================================================================
[docs]class FHIRConst:
"""
Constants used, mainly as dictionary keys, by the Python ``fhirclient``
package.
- Capitalized: usually FHIR object types
- lower_case or lowerCamelCase: usually dictionary keys
- plainlowercase: often string constants
"""
# -------------------------------------------------------------------------
# Authentication (FHIRClient settings)
# -------------------------------------------------------------------------
API_BASE = "api_base"
APP_ID = "app_id"
APP_SECRET = "app_secret"
LAUNCH_TOKEN = "launch_token"
# -------------------------------------------------------------------------
# Generic keys (used by lots)
# -------------------------------------------------------------------------
CODE = "code"
DATE = "date"
DESCRIPTION = "description"
IDENTIFIER = "identifier"
ITEM = "item"
NAME = "name"
STATUS = "status"
SUBJECT = "subject"
SYSTEM = "system"
TRANSACTION = "transaction"
URL = "url"
VALUE = "value"
# -------------------------------------------------------------------------
# Resource types (usually: BundleEntryRequest keys)
# -------------------------------------------------------------------------
RESOURCE_TYPE_BUNDLE = "Bundle"
RESOURCE_TYPE_CONDITION = "Condition"
RESOURCE_TYPE_DOCUMENT_REFERENCE = "DocumentReference"
RESOURCE_TYPE_OBSERVATION = "Observation"
RESOURCE_TYPE_PATIENT = "Patient"
RESOURCE_TYPE_PRACTITIONER = "Practitioner"
RESOURCE_TYPE_QUESTIONNAIRE = "Questionnaire"
RESOURCE_TYPE_QUESTIONNAIRE_RESPONSE = "QuestionnaireResponse"
# -------------------------------------------------------------------------
# Resource-specific keys
# -------------------------------------------------------------------------
# Annotation keys
AUTHOR_REFERENCE = "authorReference"
AUTHOR_STRING = "authorString"
TIME = "time"
# Attachment and Binary keys
CONTENT_TYPE = "contentType"
DATA = "data"
# Bundle keys
TYPE = "type"
ENTRY = "entry"
# BundleEntry keys
REQUEST = "request"
RESOURCE = "resource"
# BundleEntryRequest keys
IF_NONE_EXIST = "ifNoneExist"
METHOD = "method"
# CodeableConcept keys
CODING = "coding"
TEXT = "text"
# Coding keys
DISPLAY = "display"
USER_SELECTED = "userSelected"
VERSION = "version"
# Coding values
# https://www.hl7.org/fhir/terminologies-systems.html
# http://www.hl7.org/fhir/snomedct.html
# noinspection HttpUrlsUsage
CODE_SYSTEM_SNOMED_CT = "http://snomed.info/sct"
# http://www.hl7.org/fhir/icd.html
# noinspection HttpUrlsUsage
CODE_SYSTEM_ICD9_CM = "http://hl7.org/fhir/sid/icd-9-cm"
# http://www.hl7.org/fhir/icd.html
# noinspection HttpUrlsUsage
CODE_SYSTEM_ICD10 = "http://hl7.org/fhir/sid/icd-10"
# noinspection HttpUrlsUsage
CODE_SYSTEM_LOINC = "http://loinc.org"
# noinspection HttpUrlsUsage
CODE_SYSTEM_UCUM = "http://unitsofmeasure.org"
# Condition keys
NOTE = "note"
RECORDER = "recorder" # = clinician
# ContactPoint values
TELECOM_SYSTEM_EMAIL = "email"
TELECOM_SYSTEM_OTHER = "other"
# DocumentReference keys
AUTHOR = "author"
CONTENT = "content"
DOCSTATUS = "docStatus"
MASTER_IDENTIFIER = "masterIdentifier"
# DocumentReference values
DOCSTATUS_CURRENT = "current"
DOCSTATUS_FINAL = "final"
DOCSTATUS_PRELIMINARY = "preliminary"
# DocumentReferenceContent keys:
ATTACHMENT = "attachment"
FORMAT = "format"
# HumanName keys
NAME_FAMILY = "family"
NAME_GIVEN = "given"
# Observation keys
COMPONENT = "component"
EFFECTIVE_DATE_TIME = "effectiveDateTime"
# Observation values
OBSSTATUS_FINAL = "final"
OBSSTATUS_PRELIMINARY = "preliminary"
# Observation/ObservationComponent/QuestionnaireResponseItemAnswer keys
# (not all are possible for all of those!).
VALUE_ATTACHMENT = "valueAttachment"
VALUE_BOOLEAN = "valueBoolean"
VALUE_CODEABLE_CONCEPT = "valueCodeableConcept"
VALUE_CODING = "valueCoding"
VALUE_DATE = "valueDate"
VALUE_DATETIME = "valueDateTime"
VALUE_DECIMAL = "valueDecimal"
VALUE_INTEGER = "valueInteger"
VALUE_QUANTITY = "valueQuantity"
VALUE_REFERENCE = "valueReference"
VALUE_STRING = "valueString"
VALUE_TIME = "valueTime"
VALUE_URI = "valueUri"
# Patient/Practitioner keys
ADDRESS = "address"
BIRTHDATE = "birthDate"
GENDER = "gender"
TELECOM = "telecom"
# Patient values
GENDER_FEMALE = "female"
GENDER_MALE = "male"
GENDER_OTHER = "other"
GENDER_UNKNOWN = "unknown"
# Address keys
ADDRESS_TEXT = "text"
# Quantity keys
# COMPARATOR = "comparator"
UNIT = "unit"
# Questionnaire keys
TITLE = "title"
COPYRIGHT = "copyright"
# Questionnaire values
QSTATUS_ACTIVE = "active"
QSTATUS_COMPLETED = "completed"
QSTATUS_IN_PROGRESS = "in-progress"
QSTATUS_STOPPED = "stopped"
# QuestionnaireItem keys
LINK_ID = "linkId"
ANSWER_OPTION = "answerOption"
# NB: answerValueSet isn't just a list; it's a fiddly thing.
# QuestionnaireItem values
QITEM_TYPE_ATTACHMENT = "attachment"
QITEM_TYPE_BOOLEAN = "boolean"
QITEM_TYPE_CHOICE = "choice"
QITEM_TYPE_DATE = "date"
QITEM_TYPE_DATETIME = "dateTime"
QITEM_TYPE_DECIMAL = "decimal"
QITEM_TYPE_DISPLAY = "display"
QITEM_TYPE_GROUP = "group"
QITEM_TYPE_INTEGER = "integer"
QITEM_TYPE_OPEN_CHOICE = "open-choice"
QITEM_TYPE_QUANTITY = "quantity"
QITEM_TYPE_QUESTION = "question"
QITEM_TYPE_REFERENCE = "reference"
QITEM_TYPE_STRING = "string"
QITEM_TYPE_TIME = "time"
QITEM_TYPE_URL = "url"
# Some belong here but are not in the fhirclient docs. See:
# https://www.hl7.org/fhir/codesystem-item-type.html
# https://www.hl7.org/fhir/valueset-item-type.html
# QuestionnaireResponse keys
AUTHORED = "authored"
QUESTIONNAIRE = "questionnaire"
# QuestionnaireResponseItem keys
ANSWER = "answer"
# -------------------------------------------------------------------------
# Very specific codes
# -------------------------------------------------------------------------
# For BMI and related:
# - https://www.hl7.org/fhir/observation-example-bmi-using-related.html
# - https://www.hl7.org/fhir/observation-example.html
# - https://hl7.org/fhir/us/core/2017Jan/ValueSet-us-core-ucum.html
# - Height: https://loinc.org/8302-2/
# - Waist circumference: https://loinc.org/8280-0/ -- but NB also several
# others. https://loinc.org/56117-5/ is the "natural waist" which is most
# likely in the absence of other detail.
LOINC_BMI_CODE = "39156-5"
LOINC_BMI_TEXT = "Body mass index (BMI) [Ratio]"
LOINC_BODY_WEIGHT_CODE = "29463-7"
LOINC_BODY_WEIGHT_TEXT = "Body weight"
LOINC_HEIGHT_CODE = "8302-2"
LOINC_HEIGHT_TEXT = "Body height"
LOINC_WAIST_CIRCUMFERENCE_CODE = "56117-5"
LOINC_WAIST_CIRCUMFERENCE_TEXT = "Waist Circumference by WHI"
UCUM_CODE_KG_PER_SQ_M = "kg/m2"
UCUM_CODE_KG = "kg"
UCUM_CODE_METRE = "m"
UCUM_CODE_CENTIMETRE = "cm"
# noinspection HttpUrlsUsage
VITAL_SIGNS_SYSTEM = (
"http://terminology.hl7.org/CodeSystem/observation-category"
)
VITAL_SIGNS_CODE = "vital-signs"
VITAL_SIGNS_DISPLAY = "Vital Signs"
# ID_SYSTEM_NHS_NUMBER = "https://fhir.nhs.uk/Id/nhs-number"
# -------------------------------------------------------------------------
# Response values
# -------------------------------------------------------------------------
ETAG = "etag"
ID = "id"
LAST_MODIFIED = "lastModified"
LINK = "link"
LOCATION = "location"
RELATION = "relation"
RESOURCE_TYPE = "resourceType"
RESPONSE = "response"
SELF = "self"
TRANSACTION_RESPONSE = "transaction-response"
RESPONSE_STATUS_200_OK = "200 OK"
RESPONSE_STATUS_201_CREATED = "201 Created"
# -------------------------------------------------------------------------
# CamCOPS tags
# -------------------------------------------------------------------------
CAMCOPS_VALUE_CLINICIAN_WITHIN_TASK = "clinician"
CAMCOPS_VALUE_PATIENT_WITHIN_TASK = "patient"
CAMCOPS_VALUE_QUESTIONNAIRE_RESPONSE_WITHIN_TASK = "qr"